/********************************************************************************************** * * rlsw v1.0 - An OpenGL 1.1-style software renderer implementation * * DESCRIPTION: * rlsw is a custom OpenGL 1.1-style implementation on software, intended to provide all * functionality available on rlgl.h library used by raylib, becoming a direct software * rendering replacement for OpenGL 1.1 backend and allowing to run raylib on GPU-less * devices when required * * FEATURES: * - Rendering to custom internal framebuffer with multiple color modes supported: * - Color buffer: RGB - 8-bit (3:3:2) | RGB - 16-bit (5:6:5) | RGB - 24-bit (8:8:8) * - Depth buffer: D - 8-bit (unorm) | D - 16-bit (unorm) | D - 24-bit (unorm) * - Rendering modes supported: POINT, LINES, TRIANGLE, QUADS * - Additional features: Polygon modes, Point width, Line width * - Clipping support for all rendering modes * - Texture features supported: * - All uncompressed texture formats supported by raylib * - Texture Minification/Magnification checks * - Point and Bilinear filtering * - Texture Wrap Modes with separate checks for S/T coordinates * - Vertex Arrays support with direct primitive drawing mode * - Matrix Stack support (Matrix Push/Pop) * - Other GL misc features: * - GL-style getter functions * - Framebuffer resizing * - Perspective correction * - Scissor clipping * - Depth testing * - Blend modes * - Face culling * * ADDITIONAL NOTES: * Check PR for more info: https://github.com/raysan5/raylib/pull/4832 * * CONFIGURATION: * #define RLSW_IMPLEMENTATION * Generates the implementation of the library into the included file * If not defined, the library is in header only mode and can be included in other headers * or source files without problems. But only ONE file should hold the implementation * * rlsw capabilities could be customized just defining some internal * values before library inclusion (default values listed): * * #define SW_GL_FRAMEBUFFER_COPY_BGRA true * #define SW_GL_BINDING_COPY_TEXTURE true * #define SW_COLOR_BUFFER_BITS 24 * #define SW_DEPTH_BUFFER_BITS 16 * #define SW_MAX_PROJECTION_STACK_SIZE 2 * #define SW_MAX_MODELVIEW_STACK_SIZE 8 * #define SW_MAX_TEXTURE_STACK_SIZE 2 * #define SW_MAX_TEXTURES 128 * * * LICENSE: MIT * * Copyright (c) 2025-2026 Le Juez Victor (@Bigfoot71), reviewed by Ramon Santamaria (@raysan5) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * **********************************************************************************************/ #ifndef RLSW_H #define RLSW_H #include #include //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- // Function specifiers definition #ifndef SWAPI #define SWAPI // Functions defined as 'extern' by default (implicit specifiers) #endif #ifndef SW_MALLOC #define SW_MALLOC(sz) malloc(sz) #endif #ifndef SW_REALLOC #define SW_REALLOC(ptr, newSz) realloc(ptr, newSz) #endif #ifndef SW_FREE #define SW_FREE(ptr) free(ptr) #endif #ifndef SW_RESTRICT #ifdef _MSC_VER #define SW_RESTRICT __restrict #else #define SW_RESTRICT restrict #endif #endif #ifndef SW_GL_FRAMEBUFFER_COPY_BGRA #define SW_GL_FRAMEBUFFER_COPY_BGRA true #endif #ifndef SW_GL_BINDING_COPY_TEXTURE #define SW_GL_BINDING_COPY_TEXTURE true #endif #ifndef SW_COLOR_BUFFER_BITS #define SW_COLOR_BUFFER_BITS 24 #endif #ifndef SW_DEPTH_BUFFER_BITS #define SW_DEPTH_BUFFER_BITS 16 #endif #ifndef SW_MAX_PROJECTION_STACK_SIZE #define SW_MAX_PROJECTION_STACK_SIZE 2 #endif #ifndef SW_MAX_MODELVIEW_STACK_SIZE #define SW_MAX_MODELVIEW_STACK_SIZE 8 #endif #ifndef SW_MAX_TEXTURE_STACK_SIZE #define SW_MAX_TEXTURE_STACK_SIZE 2 #endif #ifndef SW_MAX_TEXTURES #define SW_MAX_TEXTURES 128 #endif // Under normal circumstances, clipping a polygon can add at most one vertex per clipping plane. // Considering the largest polygon involved is a quadrilateral (4 vertices), // and that clipping occurs against both the frustum (6 planes) and the scissors (4 planes), // the maximum number of vertices after clipping is: // 4 (original vertices) + 6 (frustum planes) + 4 (scissors planes) = 14 #ifndef SW_MAX_CLIPPED_POLYGON_VERTICES #define SW_MAX_CLIPPED_POLYGON_VERTICES 14 #endif #ifndef SW_CLIP_EPSILON #define SW_CLIP_EPSILON 1e-4f #endif //---------------------------------------------------------------------------------- // OpenGL Compatibility Types //---------------------------------------------------------------------------------- typedef unsigned int GLenum; typedef unsigned char GLboolean; typedef unsigned int GLbitfield; typedef void GLvoid; typedef signed char GLbyte; typedef short GLshort; typedef int GLint; typedef unsigned char GLubyte; typedef unsigned short GLushort; typedef unsigned int GLuint; typedef int GLsizei; typedef float GLfloat; typedef float GLclampf; typedef double GLdouble; typedef double GLclampd; //---------------------------------------------------------------------------------- // OpenGL Definitions // NOTE: Not used/supported definitions are commented //---------------------------------------------------------------------------------- #define GL_FALSE 0 #define GL_TRUE 1 #define GL_SCISSOR_TEST 0x0C11 #define GL_TEXTURE_2D 0x0DE1 #define GL_DEPTH_TEST 0x0B71 #define GL_CULL_FACE 0x0B44 #define GL_BLEND 0x0BE2 #define GL_VENDOR 0x1F00 #define GL_RENDERER 0x1F01 #define GL_VERSION 0x1F02 #define GL_EXTENSIONS 0x1F03 //#define GL_ATTRIB_STACK_DEPTH 0x0BB0 //#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 #define GL_COLOR_CLEAR_VALUE 0x0C22 //#define GL_COLOR_WRITEMASK 0x0C23 //#define GL_CURRENT_INDEX 0x0B01 #define GL_CURRENT_COLOR 0x0B00 //#define GL_CURRENT_NORMAL 0x0B02 //#define GL_CURRENT_RASTER_COLOR 0x0B04 //#define GL_CURRENT_RASTER_DISTANCE 0x0B09 //#define GL_CURRENT_RASTER_INDEX 0x0B05 //#define GL_CURRENT_RASTER_POSITION 0x0B07 //#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 //#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 #define GL_CURRENT_TEXTURE_COORDS 0x0B03 #define GL_POINT_SIZE 0x0B11 #define GL_LINE_WIDTH 0x0B21 //#define GL_INDEX_CLEAR_VALUE 0x0C20 //#define GL_INDEX_MODE 0x0C30 //#define GL_INDEX_WRITEMASK 0x0C21 #define GL_MODELVIEW_MATRIX 0x0BA6 #define GL_MODELVIEW_STACK_DEPTH 0x0BA3 //#define GL_NAME_STACK_DEPTH 0x0D70 #define GL_PROJECTION_MATRIX 0x0BA7 #define GL_PROJECTION_STACK_DEPTH 0x0BA4 //#define GL_RENDER_MODE 0x0C40 //#define GL_RGBA_MODE 0x0C31 #define GL_TEXTURE_MATRIX 0x0BA8 #define GL_TEXTURE_STACK_DEPTH 0x0BA5 #define GL_VIEWPORT 0x0BA2 #define GL_COLOR_BUFFER_BIT 0x00004000 #define GL_DEPTH_BUFFER_BIT 0x00000100 #define GL_MODELVIEW 0x1700 #define GL_PROJECTION 0x1701 #define GL_TEXTURE 0x1702 #define GL_VERTEX_ARRAY 0x8074 #define GL_NORMAL_ARRAY 0x8075 // WARNING: Not implemented (defined for RLGL) #define GL_COLOR_ARRAY 0x8076 //#define GL_INDEX_ARRAY 0x8077 #define GL_TEXTURE_COORD_ARRAY 0x8078 #define GL_POINTS 0x0000 #define GL_LINES 0x0001 //#define GL_LINE_LOOP 0x0002 //#define GL_LINE_STRIP 0x0003 #define GL_TRIANGLES 0x0004 //#define GL_TRIANGLE_STRIP 0x0005 //#define GL_TRIANGLE_FAN 0x0006 #define GL_QUADS 0x0007 //#define GL_QUAD_STRIP 0x0008 //#define GL_POLYGON 0x0009 #define GL_POINT 0x1B00 #define GL_LINE 0x1B01 #define GL_FILL 0x1B02 #define GL_FRONT 0x0404 #define GL_BACK 0x0405 #define GL_ZERO 0 #define GL_ONE 1 #define GL_SRC_COLOR 0x0300 #define GL_ONE_MINUS_SRC_COLOR 0x0301 #define GL_SRC_ALPHA 0x0302 #define GL_ONE_MINUS_SRC_ALPHA 0x0303 #define GL_DST_ALPHA 0x0304 #define GL_ONE_MINUS_DST_ALPHA 0x0305 #define GL_DST_COLOR 0x0306 #define GL_ONE_MINUS_DST_COLOR 0x0307 #define GL_SRC_ALPHA_SATURATE 0x0308 #define GL_NEAREST 0x2600 #define GL_LINEAR 0x2601 #define GL_REPEAT 0x2901 #define GL_CLAMP 0x2900 #define GL_TEXTURE_MAG_FILTER 0x2800 #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_TEXTURE_WRAP_S 0x2802 #define GL_TEXTURE_WRAP_T 0x2803 #define GL_NO_ERROR 0 #define GL_INVALID_ENUM 0x0500 #define GL_INVALID_VALUE 0x0501 #define GL_INVALID_OPERATION 0x0502 #define GL_STACK_OVERFLOW 0x0503 #define GL_STACK_UNDERFLOW 0x0504 #define GL_OUT_OF_MEMORY 0x0505 #define GL_ALPHA 0x1906 #define GL_LUMINANCE 0x1909 #define GL_LUMINANCE_ALPHA 0x190A #define GL_RGB 0x1907 #define GL_RGBA 0x1908 #define GL_BYTE 0x1400 #define GL_UNSIGNED_BYTE 0x1401 #define GL_SHORT 0x1402 #define GL_UNSIGNED_SHORT 0x1403 #define GL_INT 0x1404 #define GL_UNSIGNED_INT 0x1405 #define GL_FLOAT 0x1406 // OpenGL Definitions NOT USED #define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 #define GL_PACK_ALIGNMENT 0x0D05 #define GL_UNPACK_ALIGNMENT 0x0CF5 #define GL_LINE_SMOOTH 0x0B20 #define GL_SMOOTH 0x1D01 #define GL_NICEST 0x1102 #define GL_CCW 0x0901 #define GL_CW 0x0900 #define GL_NEVER 0x0200 #define GL_LESS 0x0201 #define GL_EQUAL 0x0202 #define GL_LEQUAL 0x0203 #define GL_GREATER 0x0204 #define GL_NOTEQUAL 0x0205 #define GL_GEQUAL 0x0206 #define GL_ALWAYS 0x0207 //---------------------------------------------------------------------------------- // OpenGL Bindings to rlsw //---------------------------------------------------------------------------------- #define glReadPixels(x, y, w, h, f, t, p) swCopyFramebuffer((x), (y), (w), (h), (f), (t), (p)) #define glEnable(state) swEnable((state)) #define glDisable(state) swDisable((state)) #define glGetFloatv(pname, params) swGetFloatv((pname), (params)) #define glGetString(pname) swGetString((pname)) #define glGetError() swGetError() #define glViewport(x, y, w, h) swViewport((x), (y), (w), (h)) #define glScissor(x, y, w, h) swScissor((x), (y), (w), (h)) #define glClearColor(r, g, b, a) swClearColor((r), (g), (b), (a)) #define glClear(bitmask) swClear((bitmask)) #define glBlendFunc(sfactor, dfactor) swBlendFunc((sfactor), (dfactor)) #define glPolygonMode(face, mode) swPolygonMode((mode)) #define glCullFace(face) swCullFace((face)) #define glPointSize(size) swPointSize((size)) #define glLineWidth(width) swLineWidth((width)) #define glMatrixMode(mode) swMatrixMode((mode)) #define glPushMatrix() swPushMatrix() #define glPopMatrix() swPopMatrix() #define glLoadIdentity() swLoadIdentity() #define glTranslatef(x, y, z) swTranslatef((x), (y), (z)) #define glRotatef(a, x, y, z) swRotatef((a), (x), (y), (z)) #define glScalef(x, y, z) swScalef((x), (y), (z)) #define glMultMatrixf(v) swMultMatrixf((v)) #define glFrustum(l, r, b, t, n, f) swFrustum((l), (r), (b), (t), (n), (f)) #define glOrtho(l, r, b, t, n, f) swOrtho((l), (r), (b), (t), (n), (f)) #define glBegin(mode) swBegin((mode)) #define glEnd() swEnd() #define glVertex2i(x, y) swVertex2i((x), (y)) #define glVertex2f(x, y) swVertex2f((x), (y)) #define glVertex2fv(v) swVertex2fv((v)) #define glVertex3i(x, y, z) swVertex3i((x), (y), (z)) #define glVertex3f(x, y, z) swVertex3f((x), (y), (z)) #define glvertex3fv(v) swVertex3fv((v)) #define glVertex4i(x, y, z, w) swVertex4i((x), (y), (z), (w)) #define glVertex4f(x, y, z, w) swVertex4f((x), (y), (z), (w)) #define glVertex4fv(v) swVertex4fv((v)) #define glColor3ub(r, g, b) swColor3ub((r), (g), (b)) #define glColor3ubv(v) swColor3ubv((v)) #define glColor3f(r, g, b) swColor3f((r), (g), (b)) #define glColor3fv(v) swColor3fv((v)) #define glColor4ub(r, g, b, a) swColor4ub((r), (g), (b), (a)) #define glColor4ubv(v) swColor4ubv((v)) #define glColor4f(r, g, b, a) swColor4f((r), (g), (b), (a)) #define glColor4fv(v) swColor4fv((v)) #define glTexCoord2f(u, v) swTexCoord2f((u), (v)) #define glTexCoord2fv(v) swTexCoord2fv((v)) #define glEnableClientState(t) ((void)(t)) #define glDisableClientState(t) swBindArray((t), 0) #define glVertexPointer(sz, t, s, p) swBindArray(SW_VERTEX_ARRAY, (p)) #define glTexCoordPointer(sz, t, s, p) swBindArray(SW_TEXTURE_COORD_ARRAY, (p)) #define glColorPointer(sz, t, s, p) swBindArray(SW_COLOR_ARRAY, (p)) #define glDrawArrays(m, o, c) swDrawArrays((m), (o), (c)) #define glGenTextures(c, v) swGenTextures((c), (v)) #define glDeleteTextures(c, v) swDeleteTextures((c), (v)) #define glTexImage2D(tr, l, if, w, h, b, f, t, p) swTexImage2D((w), (h), (f), (t), SW_GL_BINDING_COPY_TEXTURE, (p)) #define glTexParameteri(tr, pname, param) swTexParameteri((pname), (param)) #define glBindTexture(tr, id) swBindTexture((id)) // OpenGL functions NOT IMPLEMENTED by rlsw #define glClearDepth(X) ((void)(X)) #define glDepthMask(X) ((void)(X)) #define glColorMask(X,Y,Z,W) ((void)(X),(void)(Y),(void)(Z),(void)(W)) #define glPixelStorei(X,Y) ((void)(X),(void)(Y)) #define glHint(X,Y) ((void)(X),(void)(Y)) #define glShadeModel(X) ((void)(X)) #define glFrontFace(X) ((void)(X)) #define glDepthFunc(X) ((void)(X)) #define glTexSubImage2D(X,Y,Z,W,A,B,C,D,E) ((void)(X),(void)(Y),(void)(Z),(void)(W),(void)(A),(void)(B),(void)(C),(void)(D),(void)(E)) #define glGetTexImage(X,Y,Z,W,A) ((void)(X),(void)(Y),(void)(Z),(void)(W),(void)(A)) #define glDrawElements(X,Y,Z,W) ((void)(X),(void)(Y),(void)(Z),(void)(W)) #define glNormal3f(X,Y,Z) ((void)(X),(void)(Y),(void)(Z)) #define glNormal3fv(X) ((void)(X)) #define glNormalPointer(X,Y,Z) ((void)(X),(void)(Y),(void)(Z)) //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- typedef enum { SW_SCISSOR_TEST = GL_SCISSOR_TEST, SW_TEXTURE_2D = GL_TEXTURE_2D, SW_DEPTH_TEST = GL_DEPTH_TEST, SW_CULL_FACE = GL_CULL_FACE, SW_BLEND = GL_BLEND } SWstate; typedef enum { SW_VENDOR = GL_VENDOR, SW_RENDERER = GL_RENDERER, SW_VERSION = GL_VERSION, SW_EXTENSIONS = GL_EXTENSIONS, SW_COLOR_CLEAR_VALUE = GL_COLOR_CLEAR_VALUE, SW_CURRENT_COLOR = GL_CURRENT_COLOR, SW_CURRENT_TEXTURE_COORDS = GL_CURRENT_TEXTURE_COORDS, SW_POINT_SIZE = GL_POINT_SIZE, SW_LINE_WIDTH = GL_LINE_WIDTH, SW_MODELVIEW_MATRIX = GL_MODELVIEW_MATRIX, SW_MODELVIEW_STACK_DEPTH = GL_MODELVIEW_STACK_DEPTH, SW_PROJECTION_MATRIX = GL_PROJECTION_MATRIX, SW_PROJECTION_STACK_DEPTH = GL_PROJECTION_STACK_DEPTH, SW_TEXTURE_MATRIX = GL_TEXTURE_MATRIX, SW_TEXTURE_STACK_DEPTH = GL_TEXTURE_STACK_DEPTH, SW_VIEWPORT = GL_VIEWPORT } SWget; typedef enum { SW_COLOR_BUFFER_BIT = GL_COLOR_BUFFER_BIT, SW_DEPTH_BUFFER_BIT = GL_DEPTH_BUFFER_BIT } SWbuffer; typedef enum { SW_PROJECTION = GL_PROJECTION, SW_MODELVIEW = GL_MODELVIEW, SW_TEXTURE = GL_TEXTURE } SWmatrix; typedef enum { SW_VERTEX_ARRAY = GL_VERTEX_ARRAY, SW_TEXTURE_COORD_ARRAY = GL_TEXTURE_COORD_ARRAY, SW_COLOR_ARRAY = GL_COLOR_ARRAY } SWarray; typedef enum { SW_POINTS = GL_POINTS, SW_LINES = GL_LINES, SW_TRIANGLES = GL_TRIANGLES, SW_QUADS = GL_QUADS } SWdraw; typedef enum { SW_POINT = GL_POINT, SW_LINE = GL_LINE, SW_FILL = GL_FILL } SWpoly; typedef enum { SW_FRONT = GL_FRONT, SW_BACK = GL_BACK, } SWface; typedef enum { SW_ZERO = GL_ZERO, SW_ONE = GL_ONE, SW_SRC_COLOR = GL_SRC_COLOR, SW_ONE_MINUS_SRC_COLOR = GL_ONE_MINUS_SRC_COLOR, SW_SRC_ALPHA = GL_SRC_ALPHA, SW_ONE_MINUS_SRC_ALPHA = GL_ONE_MINUS_SRC_ALPHA, SW_DST_ALPHA = GL_DST_ALPHA, SW_ONE_MINUS_DST_ALPHA = GL_ONE_MINUS_DST_ALPHA, SW_DST_COLOR = GL_DST_COLOR, SW_ONE_MINUS_DST_COLOR = GL_ONE_MINUS_DST_COLOR, SW_SRC_ALPHA_SATURATE = GL_SRC_ALPHA_SATURATE } SWfactor; typedef enum { SW_LUMINANCE = GL_LUMINANCE, SW_LUMINANCE_ALPHA = GL_LUMINANCE_ALPHA, SW_RGB = GL_RGB, SW_RGBA = GL_RGBA, } SWformat; typedef enum { SW_UNSIGNED_BYTE = GL_UNSIGNED_BYTE, SW_BYTE = GL_BYTE, SW_UNSIGNED_SHORT = GL_UNSIGNED_SHORT, SW_SHORT = GL_SHORT, SW_UNSIGNED_INT = GL_UNSIGNED_INT, SW_INT = GL_INT, SW_FLOAT = GL_FLOAT } SWtype; typedef enum { SW_NEAREST = GL_NEAREST, SW_LINEAR = GL_LINEAR } SWfilter; typedef enum { SW_REPEAT = GL_REPEAT, SW_CLAMP = GL_CLAMP, } SWwrap; typedef enum { SW_TEXTURE_MIN_FILTER = GL_TEXTURE_MIN_FILTER, SW_TEXTURE_MAG_FILTER = GL_TEXTURE_MAG_FILTER, SW_TEXTURE_WRAP_S = GL_TEXTURE_WRAP_S, SW_TEXTURE_WRAP_T = GL_TEXTURE_WRAP_T } SWtexparam; typedef enum { SW_NO_ERROR = GL_NO_ERROR, SW_INVALID_ENUM = GL_INVALID_ENUM, SW_INVALID_VALUE = GL_INVALID_VALUE, SW_STACK_OVERFLOW = GL_STACK_OVERFLOW, SW_STACK_UNDERFLOW = GL_STACK_UNDERFLOW, SW_INVALID_OPERATION = GL_INVALID_OPERATION, } SWerrcode; //------------------------------------------------------------------------------------ // Functions Declaration - Public API //------------------------------------------------------------------------------------ SWAPI bool swInit(int w, int h); SWAPI void swClose(void); SWAPI bool swResizeFramebuffer(int w, int h); SWAPI void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels); SWAPI void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels); SWAPI void *swGetColorBuffer(int *w, int *h); SWAPI void swEnable(SWstate state); SWAPI void swDisable(SWstate state); SWAPI void swGetFloatv(SWget name, float *v); SWAPI const char *swGetString(SWget name); SWAPI SWerrcode swGetError(void); SWAPI void swViewport(int x, int y, int width, int height); SWAPI void swScissor(int x, int y, int width, int height); SWAPI void swClearColor(float r, float g, float b, float a); SWAPI void swClear(uint32_t bitmask); SWAPI void swBlendFunc(SWfactor sfactor, SWfactor dfactor); SWAPI void swPolygonMode(SWpoly mode); SWAPI void swCullFace(SWface face); SWAPI void swPointSize(float size); SWAPI void swLineWidth(float width); SWAPI void swMatrixMode(SWmatrix mode); SWAPI void swPushMatrix(void); SWAPI void swPopMatrix(void); SWAPI void swLoadIdentity(void); SWAPI void swTranslatef(float x, float y, float z); SWAPI void swRotatef(float angle, float x, float y, float z); SWAPI void swScalef(float x, float y, float z); SWAPI void swMultMatrixf(const float *mat); SWAPI void swFrustum(double left, double right, double bottom, double top, double znear, double zfar); SWAPI void swOrtho(double left, double right, double bottom, double top, double znear, double zfar); SWAPI void swBegin(SWdraw mode); SWAPI void swEnd(void); SWAPI void swVertex2i(int x, int y); SWAPI void swVertex2f(float x, float y); SWAPI void swVertex2fv(const float *v); SWAPI void swVertex3i(int x, int y, int z); SWAPI void swVertex3f(float x, float y, float z); SWAPI void swVertex3fv(const float *v); SWAPI void swVertex4i(int x, int y, int z, int w); SWAPI void swVertex4f(float x, float y, float z, float w); SWAPI void swVertex4fv(const float *v); SWAPI void swColor3ub(uint8_t r, uint8_t g, uint8_t b); SWAPI void swColor3ubv(const uint8_t *v); SWAPI void swColor3f(float r, float g, float b); SWAPI void swColor3fv(const float *v); SWAPI void swColor4ub(uint8_t r, uint8_t g, uint8_t b, uint8_t a); SWAPI void swColor4ubv(const uint8_t *v); SWAPI void swColor4f(float r, float g, float b, float a); SWAPI void swColor4fv(const float *v); SWAPI void swTexCoord2f(float u, float v); SWAPI void swTexCoord2fv(const float *v); SWAPI void swBindArray(SWarray type, void *buffer); SWAPI void swDrawArrays(SWdraw mode, int offset, int count); SWAPI void swGenTextures(int count, uint32_t *textures); SWAPI void swDeleteTextures(int count, uint32_t *textures); SWAPI void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy, const void *data); SWAPI void swTexParameteri(int param, int value); SWAPI void swBindTexture(uint32_t id); #endif // RLSW_H /*********************************************************************************** * * RLSW IMPLEMENTATION * ************************************************************************************/ #define RLSW_IMPLEMENTATION #if defined(RLSW_IMPLEMENTATION) #include #include #include // Required for: floorf(), fabsf() //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #define SW_PI 3.14159265358979323846f #define SW_DEG2RAD (SW_PI/180.0f) #define SW_RAD2DEG (180.0f/SW_PI) #define SW_COLOR_PIXEL_SIZE (SW_COLOR_BUFFER_BITS/8) #define SW_DEPTH_PIXEL_SIZE (SW_DEPTH_BUFFER_BITS/8) #define SW_STATE_CHECK(flags) ((RLSW.stateFlags & (flags)) == (flags)) #define SW_STATE_SCISSOR_TEST (1 << 0) #define SW_STATE_TEXTURE_2D (1 << 1) #define SW_STATE_DEPTH_TEST (1 << 2) #define SW_STATE_CULL_FACE (1 << 3) #define SW_STATE_BLEND (1 << 4) //---------------------------------------------------------------------------------- // Module Types and Structures Definition //---------------------------------------------------------------------------------- // Pixel data format type // NOTE: Enum aligned with raylib PixelFormat typedef enum { SW_PIXELFORMAT_UNKNOWN = 0, SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE, // 8 bit per pixel (no alpha) SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp SW_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) SW_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) } sw_pixelformat_t; typedef void (*sw_factor_f)( float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst ); typedef float sw_matrix_t[4*4]; typedef uint16_t sw_half_t; typedef struct { float position[4]; // Position coordinates float texcoord[2]; // Texture coordinates float color[4]; // Color value (RGBA) float homogeneous[4]; // Homogeneous coordinates float screen[2]; // Screen coordinates } sw_vertex_t; typedef struct { // Dirty hack for copied data // TODO: Rework copied image handling union { const void *cptr; // NOTE: Is used for all data reads void *ptr; // WARNING: Should only be used to allocate and free data } pixels; int width, height; // Dimensions of the texture int wMinus1, hMinus1; // Dimensions minus one sw_pixelformat_t format; // Pixel format (internal representation) SWfilter minFilter; // Minification filter SWfilter magFilter; // Magnification filter SWwrap sWrap; // texcoord.x wrap mode SWwrap tWrap; // texcoord.y wrap mode float tx; // Texel width float ty; // Texel height bool copy; // Flag indicating whether memory has been allocated } sw_texture_t; typedef struct { void *color; void *depth; int width; int height; int allocSz; } sw_framebuffer_t; typedef struct { sw_framebuffer_t framebuffer; // Main framebuffer float clearColor[4]; // Color used to clear the screen float clearDepth; // Depth value used to clear the screen int vpCenter[2]; // Viewport center int vpHalfSize[2]; // Viewport half dimensions int vpSize[2]; // Viewport dimensions (minus one) int vpMin[2]; // Viewport minimum renderable point (top-left) int vpMax[2]; // Viewport maximum renderable point (bottom-right) int scMin[2]; // Scissor rectangle minimum renderable point (top-left) int scMax[2]; // Scissor rectangle maximum renderable point (bottom-right) float scClipMin[2]; // Scissor rectangle minimum renderable point in clip space float scClipMax[2]; // Scissor rectangle maximum renderable point in clip space uint32_t currentTexture; // Current active texture id struct { float *positions; float *texcoords; uint8_t *colors; } array; struct { float texcoord[2]; float color[4]; } current; sw_vertex_t vertexBuffer[SW_MAX_CLIPPED_POLYGON_VERTICES]; // Buffer used for storing primitive vertices, used for processing and rendering int vertexCounter; // Number of vertices in 'ctx.vertexBuffer' SWdraw drawMode; // Current primitive mode (e.g., lines, triangles) SWpoly polyMode; // Current polygon filling mode (e.g., lines, triangles) int reqVertices; // Number of vertices required for the primitive being drawn float pointRadius; // Rasterized point radius float lineWidth; // Rasterized line width sw_matrix_t stackProjection[SW_MAX_PROJECTION_STACK_SIZE]; // Projection matrix stack for push/pop operations sw_matrix_t stackModelview[SW_MAX_MODELVIEW_STACK_SIZE]; // Modelview matrix stack for push/pop operations sw_matrix_t stackTexture[SW_MAX_TEXTURE_STACK_SIZE]; // Texture matrix stack for push/pop operations uint32_t stackProjectionCounter; // Counter for matrix stack operations uint32_t stackModelviewCounter; // Counter for matrix stack operations uint32_t stackTextureCounter; // Counter for matrix stack operations SWmatrix currentMatrixMode; // Current matrix mode (e.g., sw_MODELVIEW, sw_PROJECTION) sw_matrix_t *currentMatrix; // Pointer to the currently used matrix according to the mode sw_matrix_t matMVP; // Model view projection matrix, calculated and used internally bool isDirtyMVP; // Indicates if the MVP matrix should be rebuilt SWfactor srcFactor; SWfactor dstFactor; sw_factor_f srcFactorFunc; sw_factor_f dstFactorFunc; SWface cullFace; // Faces to cull SWerrcode errCode; // Last error code sw_texture_t *loadedTextures; int loadedTextureCount; uint32_t *freeTextureIds; int freeTextureIdCount; uint32_t stateFlags; } sw_context_t; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- static sw_context_t RLSW = { 0 }; //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- static inline void sw_matrix_id(sw_matrix_t dst) { dst[0] = 1, dst[1] = 0, dst[2] = 0, dst[3] = 0; dst[4] = 0, dst[5] = 1, dst[6] = 0, dst[7] = 0; dst[8] = 0, dst[9] = 0, dst[10] = 1, dst[11] = 0; dst[12] = 0, dst[13] = 0, dst[14] = 0, dst[15] = 1; } static inline void sw_matrix_mul_rst(float *SW_RESTRICT dst, const float *SW_RESTRICT left, const float *SW_RESTRICT right) { float l00 = left[0], l01 = left[1], l02 = left[2], l03 = left[3]; float l10 = left[4], l11 = left[5], l12 = left[6], l13 = left[7]; float l20 = left[8], l21 = left[9], l22 = left[10], l23 = left[11]; float l30 = left[12], l31 = left[13], l32 = left[14], l33 = left[15]; dst[0] = l00*right[0] + l01*right[4] + l02*right[8] + l03*right[12]; dst[4] = l10*right[0] + l11*right[4] + l12*right[8] + l13*right[12]; dst[8] = l20*right[0] + l21*right[4] + l22*right[8] + l23*right[12]; dst[12] = l30*right[0] + l31*right[4] + l32*right[8] + l33*right[12]; dst[1] = l00*right[1] + l01*right[5] + l02*right[9] + l03*right[13]; dst[5] = l10*right[1] + l11*right[5] + l12*right[9] + l13*right[13]; dst[9] = l20*right[1] + l21*right[5] + l22*right[9] + l23*right[13]; dst[13] = l30*right[1] + l31*right[5] + l32*right[9] + l33*right[13]; dst[2] = l00*right[2] + l01*right[6] + l02*right[10] + l03*right[14]; dst[6] = l10*right[2] + l11*right[6] + l12*right[10] + l13*right[14]; dst[10] = l20*right[2] + l21*right[6] + l22*right[10] + l23*right[14]; dst[14] = l30*right[2] + l31*right[6] + l32*right[10] + l33*right[14]; dst[3] = l00*right[3] + l01*right[7] + l02*right[11] + l03*right[15]; dst[7] = l10*right[3] + l11*right[7] + l12*right[11] + l13*right[15]; dst[11] = l20*right[3] + l21*right[7] + l22*right[11] + l23*right[15]; dst[15] = l30*right[3] + l31*right[7] + l32*right[11] + l33*right[15]; } static inline void sw_matrix_mul(sw_matrix_t dst, const sw_matrix_t left, const sw_matrix_t right) { float result[16]; sw_matrix_mul_rst(result, left, right); for (int i = 0; i < 16; i++) dst[i] = result[i]; } static inline float sw_saturate(float x) { union { float f; uint32_t u; } fb; fb.f = x; // Check if x < 0.0f // If sign bit is set (MSB), x is negative if ((fb.u & 0x80000000) != 0) return 0.0f; // Check if x > 1.0f // Works for positive floats: IEEE 754 ordering matches integer ordering if (fb.u > 0x3F800000) return 1.0f; // x is in [0.0f, 1.0f] return x; } static inline float sw_fract(float x) { return (x - floorf(x)); } static inline int sw_clampi(int v, int min, int max) { if (v < min) return min; if (v > max) return max; return v; } static inline void sw_lerp_vertex_PTCH( sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT a, const sw_vertex_t *SW_RESTRICT b, float t) { const float tInv = 1.0f - t; // Position interpolation (4 components) out->position[0] = a->position[0]*tInv + b->position[0]*t; out->position[1] = a->position[1]*tInv + b->position[1]*t; out->position[2] = a->position[2]*tInv + b->position[2]*t; out->position[3] = a->position[3]*tInv + b->position[3]*t; // Texture coordinate interpolation (2 components) out->texcoord[0] = a->texcoord[0]*tInv + b->texcoord[0]*t; out->texcoord[1] = a->texcoord[1]*tInv + b->texcoord[1]*t; // Color interpolation (4 components) out->color[0] = a->color[0]*tInv + b->color[0]*t; out->color[1] = a->color[1]*tInv + b->color[1]*t; out->color[2] = a->color[2]*tInv + b->color[2]*t; out->color[3] = a->color[3]*tInv + b->color[3]*t; // Homogeneous coordinate interpolation (4 components) out->homogeneous[0] = a->homogeneous[0]*tInv + b->homogeneous[0]*t; out->homogeneous[1] = a->homogeneous[1]*tInv + b->homogeneous[1]*t; out->homogeneous[2] = a->homogeneous[2]*tInv + b->homogeneous[2]*t; out->homogeneous[3] = a->homogeneous[3]*tInv + b->homogeneous[3]*t; } static inline void sw_get_vertex_grad_PTCH( sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT a, const sw_vertex_t *SW_RESTRICT b, float scale) { // Calculate gradients for Position out->position[0] = (b->position[0] - a->position[0])*scale; out->position[1] = (b->position[1] - a->position[1])*scale; out->position[2] = (b->position[2] - a->position[2])*scale; out->position[3] = (b->position[3] - a->position[3])*scale; // Calculate gradients for Texture coordinates out->texcoord[0] = (b->texcoord[0] - a->texcoord[0])*scale; out->texcoord[1] = (b->texcoord[1] - a->texcoord[1])*scale; // Calculate gradients for Color out->color[0] = (b->color[0] - a->color[0])*scale; out->color[1] = (b->color[1] - a->color[1])*scale; out->color[2] = (b->color[2] - a->color[2])*scale; out->color[3] = (b->color[3] - a->color[3])*scale; // Calculate gradients for Homogeneous coordinates out->homogeneous[0] = (b->homogeneous[0] - a->homogeneous[0])*scale; out->homogeneous[1] = (b->homogeneous[1] - a->homogeneous[1])*scale; out->homogeneous[2] = (b->homogeneous[2] - a->homogeneous[2])*scale; out->homogeneous[3] = (b->homogeneous[3] - a->homogeneous[3])*scale; } static inline void sw_add_vertex_grad_PTCH( sw_vertex_t *SW_RESTRICT out, const sw_vertex_t *SW_RESTRICT gradients) { // Add gradients to Position out->position[0] += gradients->position[0]; out->position[1] += gradients->position[1]; out->position[2] += gradients->position[2]; out->position[3] += gradients->position[3]; // Add gradients to Texture coordinates out->texcoord[0] += gradients->texcoord[0]; out->texcoord[1] += gradients->texcoord[1]; // Add gradients to Color out->color[0] += gradients->color[0]; out->color[1] += gradients->color[1]; out->color[2] += gradients->color[2]; out->color[3] += gradients->color[3]; // Add gradients to Homogeneous coordinates out->homogeneous[0] += gradients->homogeneous[0]; out->homogeneous[1] += gradients->homogeneous[1]; out->homogeneous[2] += gradients->homogeneous[2]; out->homogeneous[3] += gradients->homogeneous[3]; } // Half floating point management functions static inline uint32_t sw_f16_to_f32_ui(uint16_t h) { uint32_t s = (uint32_t)(h & 0x8000) << 16; int32_t em = h & 0x7fff; // bias exponent and pad mantissa with 0; 112 is relative exponent bias (127-15) int32_t r = (em + (112 << 10)) << 13; // denormal: flush to zero r = (em < (1 << 10))? 0 : r; // infinity/NaN; note that we preserve NaN payload as a byproduct of unifying inf/nan cases // 112 is an exponent bias fixup; since we already applied it once, applying it twice converts 31 to 255 r += (em >= (31 << 10))? (112 << 23) : 0; return s | r; } static inline float sw_f16_to_f32(sw_half_t y) { union { float f; uint32_t i; } v = { .i = sw_f16_to_f32_ui(y) }; return v.f; } static inline uint16_t sw_f16_from_f32_ui(uint32_t ui) { int32_t s = (ui >> 16) & 0x8000; int32_t em = ui & 0x7fffffff; // Bias exponent and round to nearest; 112 is relative exponent bias (127-15) int32_t h = (em - (112 << 23) + (1 << 12)) >> 13; // Underflow: flush to zero; 113 encodes exponent -14 h = (em < (113 << 23))? 0 : h; // Overflow: infinity; 143 encodes exponent 16 h = (em >= (143 << 23))? 0x7c00 : h; // NaN; note that we convert all types of NaN to qNaN h = (em > (255 << 23))? 0x7e00 : h; return (uint16_t)(s | h); } static inline sw_half_t sw_f16_from_f32(float i) { union { float f; uint32_t i; } v; v.f = i; return sw_f16_from_f32_ui(v.i); } // Framebuffer management functions static inline bool sw_framebuffer_load(int w, int h) { int size = w*h; RLSW.framebuffer.color = SW_MALLOC(SW_COLOR_PIXEL_SIZE*size); if (RLSW.framebuffer.color == NULL) return false; RLSW.framebuffer.depth = SW_MALLOC(SW_DEPTH_PIXEL_SIZE*size); if (RLSW.framebuffer.depth == NULL) return false; RLSW.framebuffer.width = w; RLSW.framebuffer.height = h; RLSW.framebuffer.allocSz = size; return true; } static inline bool sw_framebuffer_resize(int w, int h) { int newSize = w*h; if (newSize <= RLSW.framebuffer.allocSz) { RLSW.framebuffer.width = w; RLSW.framebuffer.height = h; return true; } void *newColor = SW_REALLOC(RLSW.framebuffer.color, newSize); if (newColor == NULL) return false; void *newDepth = SW_REALLOC(RLSW.framebuffer.depth, newSize); if (newDepth == NULL) return false; RLSW.framebuffer.color = newColor; RLSW.framebuffer.depth = newDepth; RLSW.framebuffer.width = w; RLSW.framebuffer.height = h; RLSW.framebuffer.allocSz = newSize; return true; } static inline void *sw_framebuffer_get_color_addr(const void *ptr, uint32_t offset) { return (uint8_t *)ptr + offset*SW_COLOR_PIXEL_SIZE; } static inline void sw_framebuffer_inc_color_addr(void **ptr) { *ptr = (void *)(((uint8_t *)*ptr) + SW_COLOR_PIXEL_SIZE); } static inline void sw_framebuffer_inc_const_color_addr(const void **ptr) { *ptr = (const void *)(((const uint8_t *)*ptr) + SW_COLOR_PIXEL_SIZE); } static inline void *sw_framebuffer_get_depth_addr(const void *ptr, uint32_t offset) { return (uint8_t *)ptr + offset*SW_DEPTH_PIXEL_SIZE; } static inline void sw_framebuffer_inc_depth_addr(void* *ptr) { *ptr = (void*)(((uint8_t *)*ptr) + SW_DEPTH_PIXEL_SIZE); } #if (SW_COLOR_BUFFER_BITS == 8) // RGB - (3:3:2) static inline void sw_framebuffer_read_color(float dst[4], const void *src) { uint8_t pixel = ((uint8_t *)src)[0]; dst[0] = ((pixel >> 5) & 0x07)*(1.0f/7.0f); dst[1] = ((pixel >> 2) & 0x07)*(1.0f/7.0f); dst[2] = (pixel & 0x03)*(1.0f/3.0f); dst[3] = 1.0f; } static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) { uint8_t pixel = ((const uint8_t *)src)[0]; uint8_t r = (pixel >> 5) & 0x07; uint8_t g = (pixel >> 2) & 0x07; uint8_t b = pixel & 0x03; dst[0] = (r*255 + 3)/7; dst[1] = (g*255 + 3)/7; dst[2] = (b*255 + 1)/3; dst[3] = 255; } static inline void sw_framebuffer_write_color(void *dst, const float color[3]) { uint8_t r = ((uint8_t)(color[0]*UINT8_MAX) >> 5) & 0x07; uint8_t g = ((uint8_t)(color[1]*UINT8_MAX) >> 5) & 0x07; uint8_t b = ((uint8_t)(color[2]*UINT8_MAX) >> 6) & 0x03; ((uint8_t *)dst)[0] = (r << 5) | (g << 2) | b; } static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) { uint8_t r8 = (uint8_t)(color[0]*7.0f + 0.5f); uint8_t g8 = (uint8_t)(color[1]*7.0f + 0.5f); uint8_t b8 = (uint8_t)(color[2]*3.0f + 0.5f); uint8_t packedColor = ((r8 & 0x07) << 5) | ((g8 & 0x07) << 2) | (b8 & 0x03); uint8_t *p = (uint8_t *)ptr; if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { uint8_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = packedColor; } } else { for (int i = 0; i < size; i++) *p++ = packedColor; } } #elif (SW_COLOR_BUFFER_BITS == 16) // RGB - (5:6:5) static inline void sw_framebuffer_read_color(float dst[4], const void *src) { uint16_t pixel = ((uint16_t *)src)[0]; dst[0] = ((pixel >> 11) & 0x1F)*(1.0f/31.0f); dst[1] = ((pixel >> 5) & 0x3F)*(1.0f/63.0f); dst[2] = (pixel & 0x1F)*(1.0f/31.0f); dst[3] = 1.0f; } static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) { uint16_t pixel = ((const uint16_t *)src)[0]; uint8_t r = (pixel >> 11) & 0x1F; uint8_t g = (pixel >> 5) & 0x3F; uint8_t b = pixel & 0x1F; dst[0] = (r*255 + 15)/31; dst[1] = (g*255 + 31)/63; dst[2] = (b*255 + 15)/31; dst[3] = 255; } static inline void sw_framebuffer_write_color(void *dst, const float color[3]) { uint8_t r = (uint8_t)(color[0]*31.0f + 0.5f) & 0x1F; uint8_t g = (uint8_t)(color[1]*63.0f + 0.5f) & 0x3F; uint8_t b = (uint8_t)(color[2]*31.0f + 0.5f) & 0x1F; ((uint16_t *)dst)[0] = (r << 11) | (g << 5) | b; } static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) { uint16_t r16 = (uint16_t)(color[0]*31.0f + 0.5f); uint16_t g_16 = (uint16_t)(color[1]*63.0f + 0.5f); uint16_t b_16 = (uint16_t)(color[2]*31.0f + 0.5f); uint16_t packedColor = ((r16 & 0x1F) << 11) | ((g_16 & 0x3F) << 5) | (b_16 & 0x1F); uint16_t *p = (uint16_t *)ptr; if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { uint16_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = packedColor; } } else { for (int i = 0; i < size; i++) *p++ = packedColor; } } #elif (SW_COLOR_BUFFER_BITS == 24) // RGB - (8:8:8) static inline void sw_framebuffer_read_color(float dst[4], const void *src) { dst[0] = ((uint8_t *)src)[0]*(1.0f/255.0f); dst[1] = ((uint8_t *)src)[1]*(1.0f/255.0f); dst[2] = ((uint8_t *)src)[2]*(1.0f/255.0f); dst[3] = 1.0f; } static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) { dst[0] = ((uint8_t *)src)[0]; dst[1] = ((uint8_t *)src)[1]; dst[2] = ((uint8_t *)src)[2]; dst[3] = 255; } static inline void sw_framebuffer_write_color(void *dst, const float color[3]) { ((uint8_t *)dst)[0] = (uint8_t)(color[0]*UINT8_MAX); ((uint8_t *)dst)[1] = (uint8_t)(color[1]*UINT8_MAX); ((uint8_t *)dst)[2] = (uint8_t)(color[2]*UINT8_MAX); } static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[3]) { uint8_t r = (uint8_t)(color[0]*255.0f); uint8_t g = (uint8_t)(color[1]*255.0f); uint8_t b = (uint8_t)(color[2]*255.0f); uint8_t *p = (uint8_t *)ptr; if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { uint8_t *curPtr = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); for (int xCount = 0; xCount < wScissor; xCount++) { *curPtr++ = r; *curPtr++ = g; *curPtr++ = b; } } } else { for (int i = 0; i < size; i++) { *p++ = r; *p++ = g; *p++ = b; } } } #endif // SW_COLOR_BUFFER_BITS #if (SW_DEPTH_BUFFER_BITS == 8) static inline float sw_framebuffer_read_depth(const void *src) { return (float)((uint8_t *)src)[0]*(1.0f/UINT8_MAX); } static inline void sw_framebuffer_write_depth(void *dst, float depth) { ((uint8_t *)dst)[0] = (uint8_t)(depth*UINT8_MAX); } static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) { uint8_t d8 = (uint8_t)(value*UINT8_MAX); uint8_t *p = (uint8_t *)ptr; if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { uint8_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = d8; } } else { for (int i = 0; i < size; i++) *p++ = d8; } } #elif (SW_DEPTH_BUFFER_BITS == 16) static inline float sw_framebuffer_read_depth(const void *src) { return (float)((uint16_t *)src)[0]*(1.0f/UINT16_MAX); } static inline void sw_framebuffer_write_depth(void *dst, float depth) { ((uint16_t *)dst)[0] = (uint16_t)(depth*UINT16_MAX); } static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) { uint16_t d16 = (uint16_t)(value*UINT16_MAX); uint16_t *p = (uint16_t *)ptr; if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { uint16_t *curPtr = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; for (int xCount = 0; xCount < wScissor; xCount++) *curPtr++ = d16; } } else { for (int i = 0; i < size; i++) *p++ = d16; } } #elif (SW_DEPTH_BUFFER_BITS == 24) static inline float sw_framebuffer_read_depth(const void *src) { uint32_t depth24 = (((uint8_t *)src)[0] << 16) | (((uint8_t *)src)[1] << 8) | ((uint8_t *)src)[2]; return depth24/(float)0xFFFFFF; } static inline void sw_framebuffer_write_depth(void *dst, float depth) { uint32_t depth24 = (uint32_t)(depth*0xFFFFFF); ((uint8_t *)dst)[0] = (depth24 >> 16) & 0xFF; ((uint8_t *)dst)[1] = (depth24 >> 8) & 0xFF; ((uint8_t *)dst)[2] = depth24 & 0xFF; } static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) { uint32_t d32 = (uint32_t)(value*UINT32_MAX); uint8_t d_byte0 = (uint8_t)((d32 >> 16) & 0xFF); uint8_t d_byte1 = (uint8_t)((d32 >> 8) & 0xFF); uint8_t d_byte2 = (uint8_t)(d32 & 0xFF); uint8_t *p = (uint8_t *)ptr; if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { uint8_t *curPtr = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); for (int xCount = 0; xCount < wScissor; xCount++) { *curPtr++ = d_byte0; *curPtr++ = d_byte1; *curPtr++ = d_byte2; } } } else { for (int i = 0; i < size; i++) { *p++ = d_byte0; *p++ = d_byte1; *p++ = d_byte2; } } } #endif // SW_DEPTH_BUFFER_BITS static inline void sw_framebuffer_fill(void *colorPtr, void *depthPtr, int size, float color[4], float depth_value) { #if (SW_COLOR_BUFFER_BITS == 8) // Calculate and pack 3:3:2 color // Scale color components to the max value for each bit depth and round uint8_t r8 = (uint8_t)(color[0]*7.0f + 0.5f); uint8_t g8 = (uint8_t)(color[1]*7.0f + 0.5f); uint8_t b8 = (uint8_t)(color[2]*3.0f + 0.5f); // Pack the components into a single byte uint8_t packedColor = ((r8 & 0x07) << 5) | ((g8 & 0x07) << 2) | (b8 & 0x03); uint8_t *cptr = (uint8_t *)colorPtr; #elif (SW_COLOR_BUFFER_BITS == 16) // Calculate and pack 5:6:5 color // Scale color components to the max value for each bit depth and round uint16_t r16 = (uint16_t)(color[0]*31.0f + 0.5f); uint16_t g16 = (uint16_t)(color[1]*63.0f + 0.5f); uint16_t b16 = (uint16_t)(color[2]*31.0f + 0.5f); // Pack the components into a 16-bit value uint16_t packedColor = ((r16 & 0x1F) << 11) | ((g16 & 0x3F) << 5) | (b16 & 0x1F); uint16_t *cptr = (uint16_t *)colorPtr; #elif (SW_COLOR_BUFFER_BITS == 24) // Calculate 8:8:8 color components uint8_t r24 = (uint8_t)(color[0]*255.0f); uint8_t g24 = (uint8_t)(color[1]*255.0f); uint8_t b24 = (uint8_t)(color[2]*255.0f); uint8_t *cptr = (uint8_t *)colorPtr; #endif #if (SW_DEPTH_BUFFER_BITS == 8) // Calculate 8-bit depth uint8_t d8 = (uint8_t)(depth_value*UINT8_MAX); uint8_t *dptr = (uint8_t *)depthPtr; #elif (SW_DEPTH_BUFFER_BITS == 16) // Calculate 16-bit depth uint16_t d16 = (uint16_t)(depth_value*UINT16_MAX); uint16_t *dptr = (uint16_t *)depthPtr; #elif (SW_DEPTH_BUFFER_BITS == 24) // Calculate 24-bit depth and pre-calculate bytes uint32_t d32 = (uint32_t)(depth_value*UINT32_MAX); uint8_t dByte0 = (uint8_t)((d32 >> 16) & 0xFF); uint8_t dByte1 = (uint8_t)((d32 >> 8) & 0xFF); uint8_t dByte2 = (uint8_t)(d32 & 0xFF); uint8_t *dptr = (uint8_t *)depthPtr; #endif if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int wScissor = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { int rowStartIdx = y*RLSW.framebuffer.width + RLSW.scMin[0]; // Calculate starting pointers for the current row within the scissor rectangle #if (SW_COLOR_BUFFER_BITS == 8) uint8_t *curCPtr = cptr + rowStartIdx; #elif (SW_COLOR_BUFFER_BITS == 16) uint16_t *curCPtr = cptr + rowStartIdx; #elif (SW_COLOR_BUFFER_BITS == 24) uint8_t *curCPtr = cptr + 3*rowStartIdx; #endif #if (SW_DEPTH_BUFFER_BITS == 8) uint8_t *curDPtr = dptr + rowStartIdx; #elif (SW_DEPTH_BUFFER_BITS == 16) uint16_t *curDPtr = dptr + rowStartIdx; #elif (SW_DEPTH_BUFFER_BITS == 24) uint8_t *curDPtr = dptr + 3*rowStartIdx; #endif // Fill the current row within the scissor rectangle for (int xCount = 0; xCount < wScissor; xCount++) { // Write color #if (SW_COLOR_BUFFER_BITS == 8) *curCPtr++ = packedColor; #elif (SW_COLOR_BUFFER_BITS == 16) *curCPtr++ = packedColor; #elif (SW_COLOR_BUFFER_BITS == 24) *curCPtr++ = r24; *curCPtr++ = g24; *curCPtr++ = b24; #endif // Write depth #if (SW_DEPTH_BUFFER_BITS == 8) *curDPtr++ = d8; #elif (SW_DEPTH_BUFFER_BITS == 16) *curDPtr++ = d16; #elif (SW_DEPTH_BUFFER_BITS == 24) *curDPtr++ = dByte0; *curDPtr++ = dByte1; *curDPtr++ = dByte2; #endif } } return; } for (int i = 0; i < size; i++) { // Write color #if (SW_COLOR_BUFFER_BITS == 8) *cptr++ = packedColor; #elif (SW_COLOR_BUFFER_BITS == 16) *cptr++ = packedColor; #elif (SW_COLOR_BUFFER_BITS == 24) *cptr++ = r24; *cptr++ = g24; *cptr++ = b24; #endif // Write depth #if (SW_DEPTH_BUFFER_BITS == 8) *dptr++ = d8; #elif (SW_DEPTH_BUFFER_BITS == 16) *dptr++ = d16; #elif (SW_DEPTH_BUFFER_BITS == 24) *dptr++ = dByte0; *dptr++ = dByte1; *dptr++ = dByte2; #endif } } #define DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ { \ const void *src = RLSW.framebuffer.color; \ \ for (int iy = y; iy < h; iy++) { \ for (int ix = x; ix < w; ix++) { \ uint8_t color[4]; \ sw_framebuffer_read_color8(color, src); \ #define DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ { \ const void *src = RLSW.framebuffer.color; \ \ for (int iy = y; iy < h; iy++) { \ for (int ix = x; ix < w; ix++) { \ float color[4]; \ sw_framebuffer_read_color(color, src); \ #define DEFINE_FRAMEBUFFER_COPY_END() \ sw_framebuffer_inc_const_color_addr(&src); \ } \ } \ } DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYSCALE, uint8_t) { // NTSC grayscale conversion: Y = 0.299R + 0.587G + 0.114B uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); *dst++ = gray; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(GRAYALPHA, uint8_t) { // Convert RGB to grayscale using NTSC formula uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); dst[0] = gray; dst[1] = color[3]; // alpha dst += 2; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G6B5, uint16_t) { // Convert 8-bit RGB to 5:6:5 format uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g6 = (color[1]*63 + 127)/255; uint8_t b5 = (color[2]*31 + 127)/255; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t rgb565 = (b5 << 11) | (g6 << 5) | r5; #else // RGBA uint16_t rgb565 = (r5 << 11) | (g6 << 5) | b5; #endif *dst++ = rgb565; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst += 3; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R5G5B5A1, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; uint8_t b5 = (color[2]*31 + 127)/255; uint8_t a1 = color[3] >= 128 ? 1 : 0; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t pixel = (b5 << 11) | (g5 << 6) | (r5 << 1) | a1; #else // RGBA uint16_t pixel = (r5 << 11) | (g5 << 6) | (b5 << 1) | a1; #endif *dst++ = pixel; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R4G4B4A4, uint16_t) { uint8_t r4 = (color[0]*15 + 127)/255; uint8_t g4 = (color[1]*15 + 127)/255; uint8_t b4 = (color[2]*15 + 127)/255; uint8_t a4 = (color[3]*15 + 127)/255; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t pixel = (b4 << 12) | (g4 << 8) | (r4 << 4) | a4; #else // RGBA uint16_t pixel = (r4 << 12) | (g4 << 8) | (b4 << 4) | a4; #endif *dst++ = pixel; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_U32_BEGIN(R8G8B8A8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst[3] = color[3]; dst += 4; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32, float) { dst[0] = color[0]; dst++; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32G32B32, float) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst += 3; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R32G32B32A32, float) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst[3] = color[3]; dst += 4; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16, sw_half_t) { dst[0] = sw_f16_from_f32(color[0]); dst++; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16G16B16, sw_half_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = sw_f16_from_f32(color[2]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[0]); #else // RGBA dst[0] = sw_f16_from_f32(color[0]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[2]); #endif dst += 3; } DEFINE_FRAMEBUFFER_COPY_END() DEFINE_FRAMEBUFFER_COPY_F32_BEGIN(R16G16B16A16, sw_half_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = sw_f16_from_f32(color[2]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[0]); #else // RGBA dst[0] = sw_f16_from_f32(color[0]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[2]); #endif dst[3] = sw_f16_from_f32(color[3]); dst += 4; } DEFINE_FRAMEBUFFER_COPY_END() #define DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_blit_to_##name( \ int xDst, int yDst, int wDst, int hDst, \ int xSrc, int ySrc, int wSrc, int hSrc, \ DST_PTR_T *dst) \ { \ const uint8_t *srcBase = RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ \ for (int dy = 0; dy < hDst; dy++) { \ uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ int sy = yFix >> 16; \ \ for (int dx = 0; dx < wDst; dx++) { \ uint32_t xFix = dx*xScale; \ int sx = xFix >> 16; \ const void *srcPtr = sw_framebuffer_get_color_addr(srcBase, sy*fbWidth + sx); \ uint8_t color[4]; \ sw_framebuffer_read_color8(color, srcPtr); \ #define DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(name, DST_PTR_T) \ static inline void sw_framebuffer_blit_to_##name( \ int xDst, int yDst, int wDst, int hDst, \ int xSrc, int ySrc, int wSrc, int hSrc, \ DST_PTR_T *dst) \ { \ const uint8_t *srcBase = RLSW.framebuffer.color; \ int fbWidth = RLSW.framebuffer.width; \ \ uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ \ for (int dy = 0; dy < hDst; dy++) { \ uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ int sy = yFix >> 16; \ \ for (int dx = 0; dx < wDst; dx++) { \ uint32_t xFix = dx*xScale; \ int sx = xFix >> 16; \ const void *srcPtr = sw_framebuffer_get_color_addr(srcBase, sy*fbWidth + sx); \ float color[4]; \ sw_framebuffer_read_color(color, srcPtr); \ #define DEFINE_FRAMEBUFFER_BLIT_END() \ } \ } \ } DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYSCALE, uint8_t) { uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); *dst++ = gray; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(GRAYALPHA, uint8_t) { uint8_t gray = (uint8_t)((color[0]*299 + color[1]*587 + color[2]*114 + 500)/1000); dst[0] = gray; dst[1] = color[3]; // alpha dst += 2; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G6B5, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g6 = (color[1]*63 + 127)/255; uint8_t b5 = (color[2]*31 + 127)/255; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t rgb565 = (b5 << 11) | (g6 << 5) | r5; #else // RGBA uint16_t rgb565 = (r5 << 11) | (g6 << 5) | b5; #endif *dst++ = rgb565; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst += 3; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R5G5B5A1, uint16_t) { uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; uint8_t b5 = (color[2]*31 + 127)/255; uint8_t a1 = color[3] >= 128 ? 1 : 0; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t pixel = (b5 << 11) | (g5 << 6) | (r5 << 1) | a1; #else // RGBA uint16_t pixel = (r5 << 11) | (g5 << 6) | (b5 << 1) | a1; #endif *dst++ = pixel; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R4G4B4A4, uint16_t) { uint8_t r4 = (color[0]*15 + 127)/255; uint8_t g4 = (color[1]*15 + 127)/255; uint8_t b4 = (color[2]*15 + 127)/255; uint8_t a4 = (color[3]*15 + 127)/255; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t pixel = (b4 << 12) | (g4 << 8) | (r4 << 4) | a4; #else // RGBA uint16_t pixel = (r4 << 12) | (g4 << 8) | (b4 << 4) | a4; #endif *dst++ = pixel; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_U32_BEGIN(R8G8B8A8, uint8_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst[3] = color[3]; dst += 4; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32, uint8_t) { dst[0] = color[0]; dst++; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32G32B32, float) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst += 3; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R32G32B32A32, float) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = color[2]; dst[1] = color[1]; dst[2] = color[0]; #else // RGBA dst[0] = color[0]; dst[1] = color[1]; dst[2] = color[2]; #endif dst[3] = color[3]; dst += 4; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16, sw_half_t) { dst[0] = sw_f16_from_f32(color[0]); dst++; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16G16B16, sw_half_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = sw_f16_from_f32(color[2]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[0]); #else // RGBA dst[0] = sw_f16_from_f32(color[0]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[2]); #endif dst += 3; } DEFINE_FRAMEBUFFER_BLIT_END() DEFINE_FRAMEBUFFER_BLIT_F32_BEGIN(R16G16B16A16, sw_half_t) { #if SW_GL_FRAMEBUFFER_COPY_BGRA dst[0] = sw_f16_from_f32(color[2]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[0]); #else // RGBA dst[0] = sw_f16_from_f32(color[0]); dst[1] = sw_f16_from_f32(color[1]); dst[2] = sw_f16_from_f32(color[2]); #endif dst[3] = sw_f16_from_f32(color[3]); dst += 4; } DEFINE_FRAMEBUFFER_BLIT_END() // Pixel format management functions static inline int sw_get_pixel_format(SWformat format, SWtype type) { int channels = 0; int bitsPerChannel = 8; // Default: 8 bits per channel // Determine the number of channels (format) switch (format) { case SW_LUMINANCE: channels = 1; break; case SW_LUMINANCE_ALPHA: channels = 2; break; case SW_RGB: channels = 3; break; case SW_RGBA: channels = 4; break; default: return SW_PIXELFORMAT_UNKNOWN; } // Determine the depth of each channel (type) switch (type) { case SW_UNSIGNED_BYTE: bitsPerChannel = 8; break; case SW_BYTE: bitsPerChannel = 8; break; case SW_UNSIGNED_SHORT: bitsPerChannel = 16; break; case SW_SHORT: bitsPerChannel = 16; break; case SW_UNSIGNED_INT: bitsPerChannel = 32; break; case SW_INT: bitsPerChannel = 32; break; case SW_FLOAT: bitsPerChannel = 32; break; default: return SW_PIXELFORMAT_UNKNOWN; } // Map the format and type to the correct internal format if (bitsPerChannel == 8) { if (channels == 1) return SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; if (channels == 2) return SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; if (channels == 3) return SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8; if (channels == 4) return SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; } else if (bitsPerChannel == 16) { if (channels == 1) return SW_PIXELFORMAT_UNCOMPRESSED_R16; if (channels == 3) return SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16; if (channels == 4) return SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16; } else if (bitsPerChannel == 32) { if (channels == 1) return SW_PIXELFORMAT_UNCOMPRESSED_R32; if (channels == 3) return SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32; if (channels == 4) return SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; } return SW_PIXELFORMAT_UNKNOWN; } int sw_get_pixel_bytes(sw_pixelformat_t format) { int bpp = 0; switch (format) { case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 1; break; case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 2; break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 4; break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 3; break; case SW_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 4; break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 4*3; break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 4*4; break; case SW_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 2; break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 2*3; break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 2*4; break; default: break; } return bpp; } static inline void sw_get_pixel_grayscale(float *color, const void *pixels, uint32_t offset) { float gray = (float)((uint8_t *)pixels)[offset]*(1.0f/255); color[0] = gray; color[1] = gray; color[2] = gray; color[3] = 1.0f; } static inline void sw_get_pixel_red_16(float *color, const void *pixels, uint32_t offset) { float value = sw_f16_to_f32(((sw_half_t *)pixels)[offset]); color[0] = value; color[1] = value; color[2] = value; color[3] = 1.0f; } static inline void sw_get_pixel_red_32(float *color, const void *pixels, uint32_t offset) { float value = ((float *)pixels)[offset]; color[0] = value; color[1] = value; color[2] = value; color[3] = 1.0f; } static inline void sw_get_pixel_grayscale_alpha(float *color, const void *pixels, uint32_t offset) { const uint8_t *pixelData = (const uint8_t *)pixels + 2*offset; color[0] = color[1] = color[2] = (float)pixelData[0]*(1.0f/255); color[3] = (float)pixelData[1]*(1.0f/255); } static inline void sw_get_pixel_rgb_565(float *color, const void *pixels, uint32_t offset) { uint16_t pixel = ((uint16_t *)pixels)[offset]; color[0] = (float)((pixel & 0xF800) >> 11)/31; color[1] = (float)((pixel & 0x7E0) >> 5)/63; color[2] = (float)(pixel & 0x1F)/31; color[3] = 1.0f; } static inline void sw_get_pixel_rgb_888(float *color, const void *pixels, uint32_t offset) { const uint8_t *pixel = (const uint8_t *)pixels + 3*offset; color[0] = (float)pixel[0]*(1.0f/255); color[1] = (float)pixel[1]*(1.0f/255); color[2] = (float)pixel[2]*(1.0f/255); color[3] = 1.0f; } static inline void sw_get_pixel_rgb_161616(float *color, const void *pixels, uint32_t offset) { const sw_half_t *pixel = (sw_half_t *)pixels + 3*offset; color[0] = sw_f16_to_f32(pixel[0]); color[1] = sw_f16_to_f32(pixel[1]); color[2] = sw_f16_to_f32(pixel[2]); color[3] = 1.0f; } static inline void sw_get_pixel_rgb_323232(float *color, const void *pixels, uint32_t offset) { const float *pixel = (float *)pixels + 3*offset; color[0] = pixel[0]; color[1] = pixel[1]; color[2] = pixel[2]; color[3] = 1.0f; } static inline void sw_get_pixel_rgba_5551(float *color, const void *pixels, uint32_t offset) { uint16_t pixel = ((uint16_t *)pixels)[offset]; color[0] = (float)((pixel & 0xF800) >> 11)/31; color[1] = (float)((pixel & 0x7C0) >> 6)/31; color[2] = (float)((pixel & 0x3E) >> 1)/31; color[3] = (float)(pixel & 0x1); } static inline void sw_get_pixel_rgba_4444(float *color, const void *pixels, uint32_t offset) { uint16_t pixel = ((uint16_t *)pixels)[offset]; color[0] = (float)((pixel & 0xF000) >> 12)/15; color[1] = (float)((pixel & 0xF00) >> 8)/15; color[2] = (float)((pixel & 0xF0) >> 4)/15; color[3] = (float)(pixel & 0xF)/15; } static inline void sw_get_pixel_rgba_8888(float *color, const void *pixels, uint32_t offset) { const uint8_t *pixel = (uint8_t *)pixels + 4*offset; color[0] = (float)pixel[0]*(1.0f/255); color[1] = (float)pixel[1]*(1.0f/255); color[2] = (float)pixel[2]*(1.0f/255); color[3] = (float)pixel[3]*(1.0f/255); } static inline void sw_get_pixel_rgba_16161616(float *color, const void *pixels, uint32_t offset) { const sw_half_t *pixel = (sw_half_t *)pixels + 4*offset; color[0] = sw_f16_to_f32(pixel[0]); color[1] = sw_f16_to_f32(pixel[1]); color[2] = sw_f16_to_f32(pixel[2]); color[3] = sw_f16_to_f32(pixel[3]); } static inline void sw_get_pixel_rgba_32323232(float *color, const void *pixels, uint32_t offset) { const float *pixel = (float *)pixels + 4*offset; color[0] = pixel[0]; color[1] = pixel[1]; color[2] = pixel[2]; color[3] = pixel[3]; } static inline void sw_get_pixel(float *color, const void *pixels, uint32_t offset, sw_pixelformat_t format) { switch (format) { case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_get_pixel_grayscale(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_get_pixel_grayscale_alpha(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_get_pixel_rgb_565(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_get_pixel_rgb_888(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_get_pixel_rgba_5551(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_get_pixel_rgba_4444(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_get_pixel_rgba_8888(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_get_pixel_red_32(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_get_pixel_rgb_323232(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_get_pixel_rgba_32323232(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_get_pixel_red_16(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_get_pixel_rgb_161616(color, pixels, offset); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_get_pixel_rgba_16161616(color, pixels, offset); break; case SW_PIXELFORMAT_UNKNOWN: break; default: break; } } // Texture sampling functionality static inline void sw_texture_sample_nearest(float *color, const sw_texture_t *tex, float u, float v) { u = (tex->sWrap == SW_REPEAT)? sw_fract(u) : sw_saturate(u); v = (tex->tWrap == SW_REPEAT)? sw_fract(v) : sw_saturate(v); int x = u*tex->width, y = v*tex->height; sw_get_pixel(color, tex->pixels.cptr, y*tex->width + x, tex->format); } static inline void sw_texture_sample_linear(float *color, const sw_texture_t *tex, float u, float v) { // TODO: REVIEW: With a bit more cleverness we could clearly reduce the // number of operations here, but for now it works fine. float xf = u*tex->width - 0.5f; float yf = v*tex->height - 0.5f; int x0 = (int)floorf(xf); int y0 = (int)floorf(yf); float fx = xf - x0; float fy = yf - y0; int x1 = x0 + 1; int y1 = y0 + 1; if (tex->sWrap == SW_CLAMP) { x0 = (x0 > tex->wMinus1)? tex->wMinus1 : x0; x1 = (x1 > tex->wMinus1)? tex->wMinus1 : x1; } else { x0 = (x0%tex->width + tex->width)%tex->width; x1 = (x1%tex->width + tex->width)%tex->width; } if (tex->tWrap == SW_CLAMP) { y0 = (y0 > tex->hMinus1)? tex->hMinus1 : y0; y1 = (y1 > tex->hMinus1)? tex->hMinus1 : y1; } else { y0 = (y0%tex->height + tex->height)%tex->height; y1 = (y1%tex->height + tex->height)%tex->height; } float c00[4], c10[4], c01[4], c11[4]; sw_get_pixel(c00, tex->pixels.cptr, y0*tex->width + x0, tex->format); sw_get_pixel(c10, tex->pixels.cptr, y0*tex->width + x1, tex->format); sw_get_pixel(c01, tex->pixels.cptr, y1*tex->width + x0, tex->format); sw_get_pixel(c11, tex->pixels.cptr, y1*tex->width + x1, tex->format); for (int i = 0; i < 4; i++) { float t = c00[i] + fx*(c10[i] - c00[i]); float b = c01[i] + fx*(c11[i] - c01[i]); color[i] = t + fy*(b - t); } } static inline void sw_texture_sample(float *color, const sw_texture_t *tex, float u, float v, float duDx, float duDy, float dvDx, float dvDy) { // Previous method: There is no need to compute the square root // because using the squared value, the comparison remains `L2 > 1.0f*1.0f` //float du = sqrtf(duDx*duDx + duDy*duDy); //float dv = sqrtf(dvDx*dvDx + dvDy*dvDy); //float L = (du > dv)? du : dv; // Calculate the derivatives for each axis float du2 = duDx*duDx + duDy*duDy; float dv2 = dvDx*dvDx + dvDy*dvDy; float L2 = (du2 > dv2)? du2 : dv2; SWfilter filter = (L2 > 1.0f)? tex->minFilter : tex->magFilter; switch (filter) { case SW_NEAREST: sw_texture_sample_nearest(color, tex, u, v); break; case SW_LINEAR: sw_texture_sample_linear(color, tex, u, v); break; default: break; } } // Color Blending functionality static inline void sw_factor_zero(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = factor[1] = factor[2] = factor[3] = 0.0f; } static inline void sw_factor_one(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = factor[1] = factor[2] = factor[3] = 1.0f; } static inline void sw_factor_src_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = src[0]; factor[1] = src[1]; factor[2] = src[2]; factor[3] = src[3]; } static inline void sw_factor_one_minus_src_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = 1.0f - src[0]; factor[1] = 1.0f - src[1]; factor[2] = 1.0f - src[2]; factor[3] = 1.0f - src[3]; } static inline void sw_factor_src_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = factor[1] = factor[2] = factor[3] = src[3]; } static inline void sw_factor_one_minus_src_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { float invAlpha = 1.0f - src[3]; factor[0] = factor[1] = factor[2] = factor[3] = invAlpha; } static inline void sw_factor_dst_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = factor[1] = factor[2] = factor[3] = dst[3]; } static inline void sw_factor_one_minus_dst_alpha(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { float invAlpha = 1.0f - dst[3]; factor[0] = factor[1] = factor[2] = factor[3] = invAlpha; } static inline void sw_factor_dst_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = dst[0]; factor[1] = dst[1]; factor[2] = dst[2]; factor[3] = dst[3]; } static inline void sw_factor_one_minus_dst_color(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = 1.0f - dst[0]; factor[1] = 1.0f - dst[1]; factor[2] = 1.0f - dst[2]; factor[3] = 1.0f - dst[3]; } static inline void sw_factor_src_alpha_saturate(float *SW_RESTRICT factor, const float *SW_RESTRICT src, const float *SW_RESTRICT dst) { factor[0] = factor[1] = factor[2] = 1.0f; factor[3] = (src[3] < 1.0f)? src[3] : 1.0f; } static inline void sw_blend_colors(float *SW_RESTRICT dst/*[4]*/, const float *SW_RESTRICT src/*[4]*/) { float srcFactor[4], dstFactor[4]; RLSW.srcFactorFunc(srcFactor, src, dst); RLSW.dstFactorFunc(dstFactor, src, dst); dst[0] = srcFactor[0]*src[0] + dstFactor[0]*dst[0]; dst[1] = srcFactor[1]*src[1] + dstFactor[1]*dst[1]; dst[2] = srcFactor[2]*src[2] + dstFactor[2]*dst[2]; dst[3] = srcFactor[3]*src[3] + dstFactor[3]*dst[3]; } // Projection helper functions static inline void sw_project_ndc_to_screen(float screen[2], const float ndc[4]) { screen[0] = RLSW.vpCenter[0] + ndc[0]*RLSW.vpHalfSize[0]; screen[1] = RLSW.vpCenter[1] - ndc[1]*RLSW.vpHalfSize[1]; } // Polygon Clipping management #define DEFINE_CLIP_FUNC(name, FUNC_IS_INSIDE, FUNC_COMPUTE_T) \ static inline int sw_clip_##name( \ sw_vertex_t output[SW_MAX_CLIPPED_POLYGON_VERTICES], \ const sw_vertex_t input[SW_MAX_CLIPPED_POLYGON_VERTICES], \ int n) \ { \ const sw_vertex_t *prev = &input[n - 1]; \ int prevInside = FUNC_IS_INSIDE(prev->homogeneous); \ int outputCount = 0; \ \ for (int i = 0; i < n; i++) { \ const sw_vertex_t *curr = &input[i]; \ int currInside = FUNC_IS_INSIDE(curr->homogeneous); \ \ /* If transition between interior/exterior, calculate intersection point */ \ if (prevInside != currInside) { \ float t = FUNC_COMPUTE_T(prev->homogeneous, curr->homogeneous); \ sw_lerp_vertex_PTCH(&output[outputCount++], prev, curr, t); \ } \ \ /* If current vertex inside, add it */ \ if (currInside) { \ output[outputCount++] = *curr; \ } \ \ prev = curr; \ prevInside = currInside; \ } \ \ return outputCount; \ } // Frustum cliping functions #define IS_INSIDE_PLANE_W(h) ((h)[3] >= SW_CLIP_EPSILON) #define IS_INSIDE_PLANE_X_POS(h) ((h)[0] <= (h)[3]) #define IS_INSIDE_PLANE_X_NEG(h) (-(h)[0] <= (h)[3]) #define IS_INSIDE_PLANE_Y_POS(h) ((h)[1] <= (h)[3]) #define IS_INSIDE_PLANE_Y_NEG(h) (-(h)[1] <= (h)[3]) #define IS_INSIDE_PLANE_Z_POS(h) ((h)[2] <= (h)[3]) #define IS_INSIDE_PLANE_Z_NEG(h) (-(h)[2] <= (h)[3]) #define COMPUTE_T_PLANE_W(hPrev, hCurr) ((SW_CLIP_EPSILON - (hPrev)[3])/((hCurr)[3] - (hPrev)[3])) #define COMPUTE_T_PLANE_X_POS(hPrev, hCurr) (((hPrev)[3] - (hPrev)[0])/(((hPrev)[3] - (hPrev)[0]) - ((hCurr)[3] - (hCurr)[0]))) #define COMPUTE_T_PLANE_X_NEG(hPrev, hCurr) (((hPrev)[3] + (hPrev)[0])/(((hPrev)[3] + (hPrev)[0]) - ((hCurr)[3] + (hCurr)[0]))) #define COMPUTE_T_PLANE_Y_POS(hPrev, hCurr) (((hPrev)[3] - (hPrev)[1])/(((hPrev)[3] - (hPrev)[1]) - ((hCurr)[3] - (hCurr)[1]))) #define COMPUTE_T_PLANE_Y_NEG(hPrev, hCurr) (((hPrev)[3] + (hPrev)[1])/(((hPrev)[3] + (hPrev)[1]) - ((hCurr)[3] + (hCurr)[1]))) #define COMPUTE_T_PLANE_Z_POS(hPrev, hCurr) (((hPrev)[3] - (hPrev)[2])/(((hPrev)[3] - (hPrev)[2]) - ((hCurr)[3] - (hCurr)[2]))) #define COMPUTE_T_PLANE_Z_NEG(hPrev, hCurr) (((hPrev)[3] + (hPrev)[2])/(((hPrev)[3] + (hPrev)[2]) - ((hCurr)[3] + (hCurr)[2]))) DEFINE_CLIP_FUNC(w, IS_INSIDE_PLANE_W, COMPUTE_T_PLANE_W) DEFINE_CLIP_FUNC(x_pos, IS_INSIDE_PLANE_X_POS, COMPUTE_T_PLANE_X_POS) DEFINE_CLIP_FUNC(x_neg, IS_INSIDE_PLANE_X_NEG, COMPUTE_T_PLANE_X_NEG) DEFINE_CLIP_FUNC(y_pos, IS_INSIDE_PLANE_Y_POS, COMPUTE_T_PLANE_Y_POS) DEFINE_CLIP_FUNC(y_neg, IS_INSIDE_PLANE_Y_NEG, COMPUTE_T_PLANE_Y_NEG) DEFINE_CLIP_FUNC(z_pos, IS_INSIDE_PLANE_Z_POS, COMPUTE_T_PLANE_Z_POS) DEFINE_CLIP_FUNC(z_neg, IS_INSIDE_PLANE_Z_NEG, COMPUTE_T_PLANE_Z_NEG) // Scissor clip functions #define COMPUTE_T_SCISSOR_X_MIN(hPrev, hCurr) (((RLSW.scClipMin[0])*(hPrev)[3] - (hPrev)[0])/(((hCurr)[0] - (RLSW.scClipMin[0])*(hCurr)[3]) - ((hPrev)[0] - (RLSW.scClipMin[0])*(hPrev)[3]))) #define COMPUTE_T_SCISSOR_X_MAX(hPrev, hCurr) (((RLSW.scClipMax[0])*(hPrev)[3] - (hPrev)[0])/(((hCurr)[0] - (RLSW.scClipMax[0])*(hCurr)[3]) - ((hPrev)[0] - (RLSW.scClipMax[0])*(hPrev)[3]))) #define COMPUTE_T_SCISSOR_Y_MIN(hPrev, hCurr) (((RLSW.scClipMin[1])*(hPrev)[3] - (hPrev)[1])/(((hCurr)[1] - (RLSW.scClipMin[1])*(hCurr)[3]) - ((hPrev)[1] - (RLSW.scClipMin[1])*(hPrev)[3]))) #define COMPUTE_T_SCISSOR_Y_MAX(hPrev, hCurr) (((RLSW.scClipMax[1])*(hPrev)[3] - (hPrev)[1])/(((hCurr)[1] - (RLSW.scClipMax[1])*(hCurr)[3]) - ((hPrev)[1] - (RLSW.scClipMax[1])*(hPrev)[3]))) #define IS_INSIDE_SCISSOR_X_MIN(h) ((h)[0] >= (RLSW.scClipMin[0])*(h)[3]) #define IS_INSIDE_SCISSOR_X_MAX(h) ((h)[0] <= (RLSW.scClipMax[0])*(h)[3]) #define IS_INSIDE_SCISSOR_Y_MIN(h) ((h)[1] >= (RLSW.scClipMin[1])*(h)[3]) #define IS_INSIDE_SCISSOR_Y_MAX(h) ((h)[1] <= (RLSW.scClipMax[1])*(h)[3]) DEFINE_CLIP_FUNC(scissor_x_min, IS_INSIDE_SCISSOR_X_MIN, COMPUTE_T_SCISSOR_X_MIN) DEFINE_CLIP_FUNC(scissor_x_max, IS_INSIDE_SCISSOR_X_MAX, COMPUTE_T_SCISSOR_X_MAX) DEFINE_CLIP_FUNC(scissor_y_min, IS_INSIDE_SCISSOR_Y_MIN, COMPUTE_T_SCISSOR_Y_MIN) DEFINE_CLIP_FUNC(scissor_y_max, IS_INSIDE_SCISSOR_Y_MAX, COMPUTE_T_SCISSOR_Y_MAX) // Main clip function static inline bool sw_polygon_clip(sw_vertex_t polygon[SW_MAX_CLIPPED_POLYGON_VERTICES], int *vertexCounter) { static sw_vertex_t tmp[SW_MAX_CLIPPED_POLYGON_VERTICES]; int n = *vertexCounter; #define CLIP_AGAINST_PLANE(FUNC_CLIP) \ { \ n = FUNC_CLIP(tmp, polygon, n); \ if (n < 3) \ { \ *vertexCounter = 0; \ return false; \ } \ for (int i = 0; i < n; i++) polygon[i] = tmp[i]; \ } CLIP_AGAINST_PLANE(sw_clip_w); CLIP_AGAINST_PLANE(sw_clip_x_pos); CLIP_AGAINST_PLANE(sw_clip_x_neg); CLIP_AGAINST_PLANE(sw_clip_y_pos); CLIP_AGAINST_PLANE(sw_clip_y_neg); CLIP_AGAINST_PLANE(sw_clip_z_pos); CLIP_AGAINST_PLANE(sw_clip_z_neg); if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { CLIP_AGAINST_PLANE(sw_clip_scissor_x_min); CLIP_AGAINST_PLANE(sw_clip_scissor_x_max); CLIP_AGAINST_PLANE(sw_clip_scissor_y_min); CLIP_AGAINST_PLANE(sw_clip_scissor_y_max); } *vertexCounter = n; return (n >= 3); } // Triangle rendering logic static inline bool sw_triangle_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations. // To handle triangles crossing the w=0 plane correctly, // we perform the winding order test in homogeneous coordinates directly, // before the perspective division (division by w). // This test determines the orientation of the triangle in the (x,y,w) plane, // which corresponds to the projected 2D winding order sign, // even with negative w values. // Preload homogeneous coordinates into local variables const float *h0 = RLSW.vertexBuffer[0].homogeneous; const float *h1 = RLSW.vertexBuffer[1].homogeneous; const float *h2 = RLSW.vertexBuffer[2].homogeneous; // Compute a value proportional to the signed area in the projected 2D plane, // calculated directly using homogeneous coordinates BEFORE division by w. // This is the determinant of the matrix formed by the (x, y, w) components // of the vertices, which correctly captures the winding order in homogeneous // space and its relationship to the projected 2D winding order, even with // negative w values. // The determinant formula used here is: // h0.x*(h1.y*h2.w - h2.y*h1.w) + // h1.x*(h2.y*h0.w - h0.y*h2.w) + // h2.x*(h0.y*h1.w - h1.y*h0.w) const float hSgnArea = h0[0]*(h1[1]*h2[3] - h2[1]*h1[3]) + h1[0]*(h2[1]*h0[3] - h0[1]*h2[3]) + h2[0]*(h0[1]*h1[3] - h1[1]*h0[3]); // Discard the triangle if its winding order (determined by the sign // of the homogeneous area/determinant) matches the culled direction. // A positive hSgnArea typically corresponds to a counter-clockwise // winding in the projected space when all w > 0. // This test is robust for points with w > 0 or w < 0, correctly // capturing the change in orientation when crossing the w=0 plane. // The culling logic remains the same based on the signed area/determinant. // A value of 0 for hSgnArea means the points are collinear in (x, y, w) // space, which corresponds to a degenerate triangle projection. // Such triangles are typically not culled by this test (0 < 0 is false, 0 > 0 is false) // and should be handled by the clipper if necessary. return (RLSW.cullFace == SW_FRONT) ? (hSgnArea < 0) // Cull if winding is "clockwise" in the projected sense : (hSgnArea > 0); // Cull if winding is "counter-clockwise" in the projected sense } static inline void sw_triangle_clip_and_project(void) { sw_vertex_t *polygon = RLSW.vertexBuffer; int *vertexCounter = &RLSW.vertexCounter; if (sw_polygon_clip(polygon, vertexCounter)) { // Transformation to screen space and normalization for (int i = 0; i < *vertexCounter; i++) { sw_vertex_t *v = &polygon[i]; // Calculation of the reciprocal of W for normalization // as well as perspective-correct attributes const float invW = 1.0f/v->homogeneous[3]; v->homogeneous[3] = invW; // Division of XYZ coordinates by weight v->homogeneous[0] *= invW; v->homogeneous[1] *= invW; v->homogeneous[2] *= invW; // Division of texture coordinates (perspective-correct) v->texcoord[0] *= invW; v->texcoord[1] *= invW; // Division of colors (perspective-correct) v->color[0] *= invW; v->color[1] *= invW; v->color[2] *= invW; v->color[3] *= invW; // Transformation to screen space sw_project_ndc_to_screen(v->screen, v->homogeneous); } } } #define DEFINE_TRIANGLE_RASTER_SCANLINE(FUNC_NAME, ENABLE_TEXTURE, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, \ const sw_vertex_t *end, float duDy, float dvDy) \ { \ /* Convert and center the screen coordinates */ \ int xStart = (int)(start->screen[0] + 0.5f); \ int xEnd = (int)(end->screen[0] + 0.5f); \ int y = (int)start->screen[1]; \ \ /* Compute the inverse horizontal distance along the X axis */ \ float dx = end->screen[0] - start->screen[0]; \ if (fabsf(dx) < 1e-6f) return; \ float dxRcp = 1.0f/dx; \ \ /* Compute the interpolation steps along the X axis */ \ float dzDx = (end->homogeneous[2] - start->homogeneous[2])*dxRcp; \ float dwDx = (end->homogeneous[3] - start->homogeneous[3])*dxRcp; \ \ float dcDx[4] = { 0 }; \ dcDx[0] = (end->color[0] - start->color[0])*dxRcp; \ dcDx[1] = (end->color[1] - start->color[1])*dxRcp; \ dcDx[2] = (end->color[2] - start->color[2])*dxRcp; \ dcDx[3] = (end->color[3] - start->color[3])*dxRcp; \ \ float duDx = 0.0f, dvDx = 0.0f; \ if (ENABLE_TEXTURE) { \ duDx = (end->texcoord[0] - start->texcoord[0])*dxRcp; \ dvDx = (end->texcoord[1] - start->texcoord[1])*dxRcp; \ } \ \ /* Initializing the interpolation starting values */ \ float z = start->homogeneous[2]; \ float w = start->homogeneous[3]; \ \ float color[4] = { 0 }; \ color[0] = start->color[0]; \ color[1] = start->color[1]; \ color[2] = start->color[2]; \ color[3] = start->color[3]; \ \ float u = 0.0f, v = 0.0f; \ if (ENABLE_TEXTURE) { \ u = start->texcoord[0]; \ v = start->texcoord[1]; \ } \ \ /* Pre-calculate the starting pointers for the framebuffer row */ \ void *cptr = sw_framebuffer_get_color_addr(RLSW.framebuffer.color, y*RLSW.framebuffer.width + xStart); \ void *dptr = sw_framebuffer_get_depth_addr(RLSW.framebuffer.depth, y*RLSW.framebuffer.width + xStart); \ \ /* Scanline rasterization */ \ for (int x = xStart; x < xEnd; x++) \ { \ /* Test and write depth */ \ if (ENABLE_DEPTH_TEST) \ { \ /* TODO: Implement different depth funcs? */ \ float depth = sw_framebuffer_read_depth(dptr); \ if (z > depth) goto discard; \ } \ \ sw_framebuffer_write_depth(dptr, z); \ \ /* Pixel color computation */ \ float wRcp = 1.0f/w; \ float srcColor[4] = { \ color[0]*wRcp, \ color[1]*wRcp, \ color[2]*wRcp, \ color[3]*wRcp \ }; \ \ if (ENABLE_TEXTURE) \ { \ float texColor[4]; \ float s = u*wRcp; \ float t = v*wRcp; \ sw_texture_sample(texColor, tex, s, t, duDx, duDy, dvDx, dvDy); \ srcColor[0] *= texColor[0]; \ srcColor[1] *= texColor[1]; \ srcColor[2] *= texColor[2]; \ srcColor[3] *= texColor[3]; \ } \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ \ sw_blend_colors(dstColor, srcColor); \ dstColor[0] = sw_saturate(dstColor[0]); \ dstColor[1] = sw_saturate(dstColor[1]); \ dstColor[2] = sw_saturate(dstColor[2]); \ \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else \ { \ sw_framebuffer_write_color(cptr, srcColor); \ } \ \ /* Increment the interpolation parameter, UVs, and pointers */ \ discard: \ z += dzDx; \ w += dwDx; \ color[0] += dcDx[0]; \ color[1] += dcDx[1]; \ color[2] += dcDx[2]; \ color[3] += dcDx[3]; \ if (ENABLE_TEXTURE) \ { \ u += duDx; \ v += dvDx; \ } \ \ sw_framebuffer_inc_color_addr(&cptr); \ sw_framebuffer_inc_depth_addr(&dptr); \ } \ } #define DEFINE_TRIANGLE_RASTER(FUNC_NAME, FUNC_SCANLINE, ENABLE_TEXTURE) \ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1, \ const sw_vertex_t *v2, const sw_texture_t *tex) \ { \ /* Swap vertices by increasing y */ \ if (v0->screen[1] > v1->screen[1]) { const sw_vertex_t *tmp = v0; v0 = v1; v1 = tmp; } \ if (v1->screen[1] > v2->screen[1]) { const sw_vertex_t *tmp = v1; v1 = v2; v2 = tmp; } \ if (v0->screen[1] > v1->screen[1]) { const sw_vertex_t *tmp = v0; v0 = v1; v1 = tmp; } \ \ /* Extracting coordinates from the sorted vertices */ \ float x0 = v0->screen[0], y0 = v0->screen[1]; \ float x1 = v1->screen[0], y1 = v1->screen[1]; \ float x2 = v2->screen[0], y2 = v2->screen[1]; \ \ /* Compute height differences */ \ float h02 = y2 - y0; \ float h01 = y1 - y0; \ float h12 = y2 - y1; \ \ if (h02 < 1e-6f) return; \ \ /* Precompute the inverse values without additional checks */ \ float invH02 = 1.0f/h02; \ float invH01 = (h01 > 1e-6f)? 1.0f/h01 : 0.0f; \ float invH12 = (h12 > 1e-6f)? 1.0f/h12 : 0.0f; \ \ /* Pre-calculation of slopes */ \ float dx02 = (x2 - x0)*invH02; \ float dx01 = (x1 - x0)*invH01; \ float dx12 = (x2 - x1)*invH12; \ \ /* Y bounds (vertical clipping) */ \ int yTop = (int)(y0 + 0.5f); \ int yMiddle = (int)(y1 + 0.5f); \ int yBottom = (int)(y2 + 0.5f); \ \ /* Compute gradients for each side of the triangle */ \ sw_vertex_t vDy02, vDy01, vDy12; \ sw_get_vertex_grad_PTCH(&vDy02, v0, v2, invH02); \ sw_get_vertex_grad_PTCH(&vDy01, v0, v1, invH01); \ sw_get_vertex_grad_PTCH(&vDy12, v1, v2, invH12); \ \ /* Initializing scanline variables */ \ sw_vertex_t vLeft = *v0; \ vLeft.screen[0] = x0; \ sw_vertex_t vRight = *v0; \ vRight.screen[0] = x0; \ \ /* Scanline for the upper part of the triangle */ \ for (int y = yTop; y < yMiddle; y++) \ { \ vLeft.screen[1] = vRight.screen[1] = y; \ \ if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, vDy02.texcoord[0], vDy02.texcoord[1]); \ else FUNC_SCANLINE(tex, &vRight, &vLeft, vDy02.texcoord[0], vDy02.texcoord[1]); \ \ sw_add_vertex_grad_PTCH(&vLeft, &vDy02); \ vLeft.screen[0] += dx02; \ sw_add_vertex_grad_PTCH(&vRight, &vDy01); \ vRight.screen[0] += dx01; \ } \ \ /* Scanline for the lower part of the triangle */ \ vRight = *v1, vRight.screen[0] = x1; \ \ for (int y = yMiddle; y < yBottom; y++) \ { \ vLeft.screen[1] = vRight.screen[1] = y; \ \ if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, vDy02.texcoord[0], vDy02.texcoord[1]); \ else FUNC_SCANLINE(tex, &vRight, &vLeft, vDy02.texcoord[0], vDy02.texcoord[1]); \ \ sw_add_vertex_grad_PTCH(&vLeft, &vDy02); \ vLeft.screen[0] += dx02; \ sw_add_vertex_grad_PTCH(&vRight, &vDy12); \ vRight.screen[0] += dx12; \ } \ } DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline, 0, 0, 0) DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX, 1, 0, 0) DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_DEPTH, 0, 1, 0) DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_BLEND, 0, 0, 1) DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX_DEPTH, 1, 1, 0) DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX_BLEND, 1, 0, 1) DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_DEPTH_BLEND, 0, 1, 1) DEFINE_TRIANGLE_RASTER_SCANLINE(sw_triangle_raster_scanline_TEX_DEPTH_BLEND, 1, 1, 1) DEFINE_TRIANGLE_RASTER(sw_triangle_raster, sw_triangle_raster_scanline, false) DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX, sw_triangle_raster_scanline_TEX, true) DEFINE_TRIANGLE_RASTER(sw_triangle_raster_DEPTH, sw_triangle_raster_scanline_DEPTH, false) DEFINE_TRIANGLE_RASTER(sw_triangle_raster_BLEND, sw_triangle_raster_scanline_BLEND, false) DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH, sw_triangle_raster_scanline_TEX_DEPTH, true) DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND, sw_triangle_raster_scanline_TEX_BLEND, true) DEFINE_TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND, sw_triangle_raster_scanline_DEPTH_BLEND, false) DEFINE_TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND, sw_triangle_raster_scanline_TEX_DEPTH_BLEND, true) static inline void sw_triangle_render(void) { if (RLSW.stateFlags & SW_STATE_CULL_FACE) { if (!sw_triangle_face_culling()) return; } sw_triangle_clip_and_project(); if (RLSW.vertexCounter < 3) return; #define TRIANGLE_RASTER(RASTER_FUNC) \ { \ for (int i = 0; i < RLSW.vertexCounter - 2; i++) \ { \ RASTER_FUNC( \ &RLSW.vertexBuffer[0], \ &RLSW.vertexBuffer[i + 1], \ &RLSW.vertexBuffer[i + 2], \ &RLSW.loadedTextures[RLSW.currentTexture] \ ); \ } \ } if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) else if (SW_STATE_CHECK(SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) else TRIANGLE_RASTER(sw_triangle_raster) #undef TRIANGLE_RASTER } // Quad rendering logic static inline bool sw_quad_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations. // To handle quads crossing the w=0 plane correctly, // we perform the winding order test in homogeneous coordinates directly, // before the perspective division (division by w). // For a convex quad with vertices P0, P1, P2, P3 in sequential order, // the winding order of the quad is the same as the winding order // of the triangle P0 P1 P2. We use the homogeneous triangle // winding test on this first triangle. // Preload homogeneous coordinates into local variables const float *h0 = RLSW.vertexBuffer[0].homogeneous; const float *h1 = RLSW.vertexBuffer[1].homogeneous; const float *h2 = RLSW.vertexBuffer[2].homogeneous; // NOTE: h3 is not needed for this test // const float *h3 = RLSW.vertexBuffer[3].homogeneous; // Compute a value proportional to the signed area of the triangle P0 P1 P2 // in the projected 2D plane, calculated directly using homogeneous coordinates // BEFORE division by w. // This is the determinant of the matrix formed by the (x, y, w) components // of the vertices P0, P1, and P2. Its sign correctly indicates the winding order // in homogeneous space and its relationship to the projected 2D winding order, // even with negative w values. // The determinant formula used here is: // h0.x*(h1.y*h2.w - h2.y*h1.w) + // h1.x*(h2.y*h0.w - h0.y*h2.w) + // h2.x*(h0.y*h1.w - h1.y*h0.w) const float hSgnArea = h0[0]*(h1[1]*h2[3] - h2[1]*h1[3]) + h1[0]*(h2[1]*h0[3] - h0[1]*h2[3]) + h2[0]*(h0[1]*h1[3] - h1[1]*h0[3]); // Perform face culling based on the winding order determined by the sign // of the homogeneous area/determinant of triangle P0 P1 P2. // This test is robust for points with w > 0 or w < 0 within the triangle, // correctly capturing the change in orientation when crossing the w=0 plane. // A positive hSgnArea typically corresponds to a counter-clockwise // winding in the projected space when all w > 0. // A value of 0 for hSgnArea means P0, P1, P2 are collinear in (x, y, w) // space, which corresponds to a degenerate triangle projection. // Such quads might also be degenerate or non-planar. They are typically // not culled by this test (0 < 0 is false, 0 > 0 is false) // and should be handled by the clipper if necessary. return (RLSW.cullFace == SW_FRONT) ? (hSgnArea < 0.0f) // Cull if winding is "clockwise" in the projected sense : (hSgnArea > 0.0f); // Cull if winding is "counter-clockwise" in the projected sense } static inline void sw_quad_clip_and_project(void) { sw_vertex_t *polygon = RLSW.vertexBuffer; int *vertexCounter = &RLSW.vertexCounter; if (sw_polygon_clip(polygon, vertexCounter)) { // Transformation to screen space and normalization for (int i = 0; i < *vertexCounter; i++) { sw_vertex_t *v = &polygon[i]; // Calculation of the reciprocal of W for normalization // as well as perspective-correct attributes const float invW = 1.0f/v->homogeneous[3]; v->homogeneous[3] = invW; // Division of XYZ coordinates by weight v->homogeneous[0] *= invW; v->homogeneous[1] *= invW; v->homogeneous[2] *= invW; // Division of texture coordinates (perspective-correct) v->texcoord[0] *= invW; v->texcoord[1] *= invW; // Division of colors (perspective-correct) v->color[0] *= invW; v->color[1] *= invW; v->color[2] *= invW; v->color[3] *= invW; // Transformation to screen space sw_project_ndc_to_screen(v->screen, v->homogeneous); } } } static inline bool sw_quad_is_axis_aligned(void) { int horizontal = 0; int vertical = 0; for (int i = 0; i < 4; i++) { if (RLSW.vertexBuffer[i].homogeneous[3] != 1.0f) return false; const float *v0 = RLSW.vertexBuffer[i].position; const float *v1 = RLSW.vertexBuffer[(i + 1)%4].position; float dx = v1[0] - v0[0]; float dy = v1[1] - v0[1]; if ((fabsf(dx) > 1e-6f) && (fabsf(dy) < 1e-6f)) horizontal++; else if ((fabsf(dy) > 1e-6f) && (fabsf(dx) < 1e-6f)) vertical++; else return false; // Diagonal edge -> not axis-aligned } return ((horizontal == 2) && (vertical == 2)); } static inline void sw_quad_sort_cw(const sw_vertex_t* *output) { // Sort 4 quad vertices into clockwise order with fixed layout: // // v0 -- v1 // | | // v3 -- v2 // // The goal is: // - v0: top-left (minimum Y, then minimum X) // - v1: top-right (minimum Y row, maximum X) // - v2: bottom-right (maximum Y, maximum X) // - v3: bottom-left (maximum Y, minimum X) const sw_vertex_t *input = RLSW.vertexBuffer; // Separate vertices into top and bottom based on Y-coordinate const sw_vertex_t *top[2] = {NULL, NULL}; const sw_vertex_t *bottom[2] = {NULL, NULL}; int topCount = 0, bottomCount = 0; // Find minimum and maximum Y float minY = input[0].screen[1]; float maxY = input[0].screen[1]; for (int i = 1; i < 4; i++) { if (input[i].screen[1] < minY) minY = input[i].screen[1]; if (input[i].screen[1] > maxY) maxY = input[i].screen[1]; } // Separate vertices based on Y-coordinate for (int i = 0; i < 4; i++) { if ((input[i].screen[1] == minY) && (topCount < 2)) top[topCount++] = &input[i]; else if ((input[i].screen[1] == maxY) && (bottomCount < 2)) bottom[bottomCount++] = &input[i]; } // If we don't have enough top/bottom vertices (e.g., Y values are all different), // classify vertices as top or bottom based on whether they're closer to minY or maxY for (int i = 0; i < 4; i++) { if ((topCount < 2) && (&input[i] != top[0]) && (&input[i] != bottom[0]) && (&input[i] != bottom[1])) { if (fabsf(input[i].screen[1] - minY) <= fabsf(input[i].screen[1] - maxY)) top[topCount++] = &input[i]; } if ((bottomCount < 2) && (&input[i] != top[0]) && (&input[i] != top[1]) && (&input[i] != bottom[0])) { if (fabsf(input[i].screen[1] - maxY) < fabsf(input[i].screen[1] - minY)) bottom[bottomCount++] = &input[i]; } } // Sort top vertices by X (left to right) if ((topCount == 2) && (top[0]->screen[0] > top[1]->screen[0])) { const sw_vertex_t *temp = top[0]; top[0] = top[1]; top[1] = temp; } // Sort bottom vertices by X (left to right) if ((bottomCount == 2) && (bottom[0]->screen[0] > bottom[1]->screen[0])) { const sw_vertex_t *temp = bottom[0]; bottom[0] = bottom[1]; bottom[1] = temp; } // Assign vertices in clockwise order as per the required layout output[0] = top[0]; // v0: top-left output[1] = top[topCount-1]; // v1: top-right output[2] = bottom[bottomCount-1]; // v2: bottom-right output[3] = bottom[0]; // v3: bottom-left } // TODO: REVIEW: Could a perfectly aligned quad, where one of the four points has a different depth, // still appear perfectly aligned from a certain point of view? // Because in that case, we would still need to perform perspective division for textures and colors... #define DEFINE_QUAD_RASTER_AXIS_ALIGNED(FUNC_NAME, ENABLE_TEXTURE, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \ static inline void FUNC_NAME(void) \ { \ const sw_vertex_t *sortedVerts[4]; \ sw_quad_sort_cw(sortedVerts); \ \ const sw_vertex_t *v0 = sortedVerts[0]; \ const sw_vertex_t *v1 = sortedVerts[1]; \ const sw_vertex_t *v2 = sortedVerts[2]; \ const sw_vertex_t *v3 = sortedVerts[3]; \ \ /* Screen bounds (axis-aligned) */ \ int xMin = (int)(v0->screen[0] + 0.5f); \ int yMin = (int)(v0->screen[1] + 0.5f); \ int xMax = (int)(v2->screen[0] + 0.5f); \ int yMax = (int)(v2->screen[1] + 0.5f); \ \ int width = xMax - xMin; \ int height = yMax - yMin; \ \ if ((width == 0) || (height == 0)) return; \ \ float wRcp = (width > 0.0f)? 1.0f/width : 0.0f; \ float hRcp = (height > 0.0f)? 1.0f/height : 0.0f; \ \ /* Calculation of vertex gradients in X and Y */ \ float tcDx[2], tcDy[2]; \ if (ENABLE_TEXTURE) { \ tcDx[0] = (v1->texcoord[0] - v0->texcoord[0])*wRcp; \ tcDx[1] = (v1->texcoord[1] - v0->texcoord[1])*wRcp; \ tcDy[0] = (v3->texcoord[0] - v0->texcoord[0])*hRcp; \ tcDy[1] = (v3->texcoord[1] - v0->texcoord[1])*hRcp; \ } \ \ float cDx[4], cDy[4]; \ cDx[0] = (v1->color[0] - v0->color[0])*wRcp; \ cDx[1] = (v1->color[1] - v0->color[1])*wRcp; \ cDx[2] = (v1->color[2] - v0->color[2])*wRcp; \ cDx[3] = (v1->color[3] - v0->color[3])*wRcp; \ cDy[0] = (v3->color[0] - v0->color[0])*hRcp; \ cDy[1] = (v3->color[1] - v0->color[1])*hRcp; \ cDy[2] = (v3->color[2] - v0->color[2])*hRcp; \ cDy[3] = (v3->color[3] - v0->color[3])*hRcp; \ \ float zDx, zDy; \ zDx = (v1->homogeneous[2] - v0->homogeneous[2])*wRcp; \ zDy = (v3->homogeneous[2] - v0->homogeneous[2])*hRcp; \ \ /* Start of quad rasterization */ \ const sw_texture_t *tex; \ if (ENABLE_TEXTURE) tex = &RLSW.loadedTextures[RLSW.currentTexture]; \ \ void *cDstBase = RLSW.framebuffer.color; \ void *dDstBase = RLSW.framebuffer.depth; \ int wDst = RLSW.framebuffer.width; \ \ float zScanline = v0->homogeneous[2]; \ float uScanline = v0->texcoord[0]; \ float vScanline = v0->texcoord[1]; \ \ float colorScanline[4] = { \ v0->color[0], \ v0->color[1], \ v0->color[2], \ v0->color[3] \ }; \ \ for (int y = yMin; y < yMax; y++) \ { \ void *cptr = sw_framebuffer_get_color_addr(cDstBase, y*wDst + xMin); \ void *dptr = sw_framebuffer_get_depth_addr(dDstBase, y*wDst + xMin); \ \ float z = zScanline; \ float u = uScanline; \ float v = vScanline; \ \ float color[4] = { \ colorScanline[0], \ colorScanline[1], \ colorScanline[2], \ colorScanline[3] \ }; \ \ /* Scanline rasterization */ \ for (int x = xMin; x < xMax; x++) \ { \ /* Test and write depth */ \ if (ENABLE_DEPTH_TEST) \ { \ /* TODO: Implement different depth funcs? */ \ float depth = sw_framebuffer_read_depth(dptr); \ if (z > depth) goto discard; \ } \ \ sw_framebuffer_write_depth(dptr, z); \ \ /* Pixel color computation */ \ float srcColor[4] = { \ color[0], \ color[1], \ color[2], \ color[3] \ }; \ \ if (ENABLE_TEXTURE) \ { \ float texColor[4]; \ sw_texture_sample(texColor, tex, u, v, tcDx[0], tcDy[0], tcDx[1], tcDy[1]); \ srcColor[0] *= texColor[0]; \ srcColor[1] *= texColor[1]; \ srcColor[2] *= texColor[2]; \ srcColor[3] *= texColor[3]; \ } \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ \ sw_blend_colors(dstColor, srcColor); \ dstColor[0] = sw_saturate(dstColor[0]); \ dstColor[1] = sw_saturate(dstColor[1]); \ dstColor[2] = sw_saturate(dstColor[2]); \ dstColor[3] = sw_saturate(dstColor[3]); \ \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else sw_framebuffer_write_color(cptr, srcColor); \ \ discard: \ z += zDx; \ color[0] += cDx[0]; \ color[1] += cDx[1]; \ color[2] += cDx[2]; \ color[3] += cDx[3]; \ \ if (ENABLE_TEXTURE) \ { \ u += tcDx[0]; \ v += tcDx[1]; \ } \ \ sw_framebuffer_inc_color_addr(&cptr); \ sw_framebuffer_inc_depth_addr(&dptr); \ } \ \ zScanline += zDy; \ colorScanline[0] += cDy[0]; \ colorScanline[1] += cDy[1]; \ colorScanline[2] += cDy[2]; \ colorScanline[3] += cDy[3]; \ \ if (ENABLE_TEXTURE) \ { \ uScanline += tcDy[0]; \ vScanline += tcDy[1]; \ } \ } \ } DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned, 0, 0, 0) DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX, 1, 0, 0) DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_DEPTH, 0, 1, 0) DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_BLEND, 0, 0, 1) DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX_DEPTH, 1, 1, 0) DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX_BLEND, 1, 0, 1) DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_DEPTH_BLEND, 0, 1, 1) DEFINE_QUAD_RASTER_AXIS_ALIGNED(sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND, 1, 1, 1) static inline void sw_quad_render(void) { if (RLSW.stateFlags & SW_STATE_CULL_FACE) { if (!sw_quad_face_culling()) return; } sw_quad_clip_and_project(); if (RLSW.vertexCounter < 3) return; if ((RLSW.vertexCounter == 4) && sw_quad_is_axis_aligned()) { if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_DEPTH_BLEND(); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_DEPTH_BLEND(); else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) sw_quad_raster_axis_aligned_TEX_BLEND(); else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_TEX_DEPTH(); else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_quad_raster_axis_aligned_BLEND(); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_quad_raster_axis_aligned_DEPTH(); else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) sw_quad_raster_axis_aligned_TEX(); else sw_quad_raster_axis_aligned(); return; } #define TRIANGLE_RASTER(RASTER_FUNC) \ { \ for (int i = 0; i < RLSW.vertexCounter - 2; i++) \ { \ RASTER_FUNC( \ &RLSW.vertexBuffer[0], \ &RLSW.vertexBuffer[i + 1], \ &RLSW.vertexBuffer[i + 2], \ &RLSW.loadedTextures[RLSW.currentTexture] \ ); \ } \ } if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH_BLEND) else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH_BLEND) else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_TEX_BLEND) else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D | SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_TEX_DEPTH) else if (SW_STATE_CHECK(SW_STATE_BLEND)) TRIANGLE_RASTER(sw_triangle_raster_BLEND) else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) TRIANGLE_RASTER(sw_triangle_raster_DEPTH) else if (SW_STATE_CHECK(SW_STATE_TEXTURE_2D)) TRIANGLE_RASTER(sw_triangle_raster_TEX) else TRIANGLE_RASTER(sw_triangle_raster) #undef TRIANGLE_RASTER } // Line rendering logic static inline bool sw_line_clip_coord(float q, float p, float *t0, float *t1) { if (fabsf(p) < SW_CLIP_EPSILON) { // Check if the line is entirely outside the window if (q < -SW_CLIP_EPSILON) return 0; // Completely outside return 1; // Completely inside or on the edges } const float r = q/p; if (p < 0) { if (r > *t1) return 0; if (r > *t0) *t0 = r; } else { if (r < *t0) return 0; if (r < *t1) *t1 = r; } return 1; } static inline bool sw_line_clip(sw_vertex_t *v0, sw_vertex_t *v1) { float t0 = 0.0f, t1 = 1.0f; float dH[4], dC[4]; for (int i = 0; i < 4; i++) { dH[i] = v1->homogeneous[i] - v0->homogeneous[i]; dC[i] = v1->color[i] - v0->color[i]; } // Clipping Liang-Barsky if (!sw_line_clip_coord(v0->homogeneous[3] - v0->homogeneous[0], -dH[3] + dH[0], &t0, &t1)) return false; if (!sw_line_clip_coord(v0->homogeneous[3] + v0->homogeneous[0], -dH[3] - dH[0], &t0, &t1)) return false; if (!sw_line_clip_coord(v0->homogeneous[3] - v0->homogeneous[1], -dH[3] + dH[1], &t0, &t1)) return false; if (!sw_line_clip_coord(v0->homogeneous[3] + v0->homogeneous[1], -dH[3] - dH[1], &t0, &t1)) return false; if (!sw_line_clip_coord(v0->homogeneous[3] - v0->homogeneous[2], -dH[3] + dH[2], &t0, &t1)) return false; if (!sw_line_clip_coord(v0->homogeneous[3] + v0->homogeneous[2], -dH[3] - dH[2], &t0, &t1)) return false; // Clipping Scissor if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { if (!sw_line_clip_coord(v0->homogeneous[0] - RLSW.scClipMin[0]*v0->homogeneous[3], RLSW.scClipMin[0]*dH[3] - dH[0], &t0, &t1)) return false; if (!sw_line_clip_coord(RLSW.scClipMax[0]*v0->homogeneous[3] - v0->homogeneous[0], dH[0] - RLSW.scClipMax[0]*dH[3], &t0, &t1)) return false; if (!sw_line_clip_coord(v0->homogeneous[1] - RLSW.scClipMin[1]*v0->homogeneous[3], RLSW.scClipMin[1]*dH[3] - dH[1], &t0, &t1)) return false; if (!sw_line_clip_coord(RLSW.scClipMax[1]*v0->homogeneous[3] - v0->homogeneous[1], dH[1] - RLSW.scClipMax[1]*dH[3], &t0, &t1)) return false; } // Interpolation of new coordinates if (t1 < 1.0f) { for (int i = 0; i < 4; i++) { v1->homogeneous[i] = v0->homogeneous[i] + t1*dH[i]; v1->color[i] = v0->color[i] + t1*dC[i]; } } if (t0 > 0.0f) { for (int i = 0; i < 4; i++) { v0->homogeneous[i] += t0*dH[i]; v0->color[i] += t0*dC[i]; } } return true; } static inline bool sw_line_clip_and_project(sw_vertex_t *v0, sw_vertex_t *v1) { if (!sw_line_clip(v0, v1)) return false; // Convert homogeneous coordinates to NDC v0->homogeneous[3] = 1.0f/v0->homogeneous[3]; v1->homogeneous[3] = 1.0f/v1->homogeneous[3]; for (int i = 0; i < 3; i++) { v0->homogeneous[i] *= v0->homogeneous[3]; v1->homogeneous[i] *= v1->homogeneous[3]; } // Convert NDC coordinates to screen space sw_project_ndc_to_screen(v0->screen, v0->homogeneous); sw_project_ndc_to_screen(v1->screen, v1->homogeneous); return true; } #define DEFINE_LINE_RASTER(FUNC_NAME, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ { \ int x1 = (int)(v0->screen[0] + 0.5f); \ int y1 = (int)(v0->screen[1] + 0.5f); \ int x2 = (int)(v1->screen[0] + 0.5f); \ int y2 = (int)(v1->screen[1] + 0.5f); \ \ int dx = x2 - x1; \ int dy = y2 - y1; \ \ /* Handling of lines that are more horizontal or vertical */ \ if ((dx == 0) && (dy == 0)) \ { \ /* TODO: A point should be rendered here */ \ return; \ } \ \ bool yLonger = (abs(dy) > abs(dx)); \ int longLen, shortLen; \ \ if (yLonger) \ { \ longLen = dy; \ shortLen = dx; \ } \ else \ { \ longLen = dx; \ shortLen = dy; \ } \ \ /* Handling of traversal direction */ \ int sgnInc = (longLen < 0)? -1 : 1; \ int abslongLen = abs(longLen); \ \ /* Calculation of the increment step for the shorter coordinate */ \ int decInc = 0; \ if (abslongLen != 0) decInc = (shortLen << 16)/abslongLen; \ \ float longLenRcp = (abslongLen != 0)? (1.0f/abslongLen) : 0.0f; \ \ /* Calculation of interpolation steps */ \ const float zStep = (v1->homogeneous[2] - v0->homogeneous[2])*longLenRcp; \ const float rStep = (v1->color[0] - v0->color[0])*longLenRcp; \ const float gStep = (v1->color[1] - v0->color[1])*longLenRcp; \ const float bStep = (v1->color[2] - v0->color[2])*longLenRcp; \ const float aStep = (v1->color[3] - v0->color[3])*longLenRcp; \ \ float z = v0->homogeneous[2]; \ \ float color[4] = { \ v0->color[0], \ v0->color[1], \ v0->color[2], \ v0->color[3] \ }; \ \ const int fbWidth = RLSW.framebuffer.width; \ void *cBuffer = RLSW.framebuffer.color; \ void *dBuffer = RLSW.framebuffer.depth; \ \ int j = 0; \ if (yLonger) \ { \ for (int i = 0; i != longLen; i += sgnInc) \ { \ int offset = (y1 + i)*fbWidth + (x1 + (j >> 16)); \ void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ \ if (ENABLE_DEPTH_TEST) \ { \ float depth = sw_framebuffer_read_depth(dptr); \ if (z > depth) goto discardA; \ } \ \ sw_framebuffer_write_depth(dptr, z); \ \ void *cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else sw_framebuffer_write_color(cptr, color); \ \ discardA: \ j += decInc; \ z += zStep; \ color[0] += rStep; \ color[1] += gStep; \ color[2] += bStep; \ color[3] += aStep; \ } \ } \ else \ { \ for (int i = 0; i != longLen; i += sgnInc) \ { \ int offset = (y1 + (j >> 16))*fbWidth + (x1 + i); \ void *dptr = sw_framebuffer_get_depth_addr(dBuffer, offset); \ \ if (ENABLE_DEPTH_TEST) \ { \ float depth = sw_framebuffer_read_depth(dptr); \ if (z > depth) goto discardB; \ } \ \ sw_framebuffer_write_depth(dptr, z); \ \ void *cptr = sw_framebuffer_get_color_addr(cBuffer, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else sw_framebuffer_write_color(cptr, color); \ \ discardB: \ j += decInc; \ z += zStep; \ color[0] += rStep; \ color[1] += gStep; \ color[2] += bStep; \ color[3] += aStep; \ } \ } \ } #define DEFINE_LINE_THICK_RASTER(FUNC_NAME, RASTER_FUNC) \ void FUNC_NAME(const sw_vertex_t *v1, const sw_vertex_t *v2) \ { \ sw_vertex_t tv1, tv2; \ \ int x1 = (int)v1->screen[0]; \ int y1 = (int)v1->screen[1]; \ int x2 = (int)v2->screen[0]; \ int y2 = (int)v2->screen[1]; \ \ int dx = x2 - x1; \ int dy = y2 - y1; \ \ RASTER_FUNC(v1, v2); \ \ if ((dx != 0) && (abs(dy/dx) < 1)) \ { \ int wy = (int)((RLSW.lineWidth - 1.0f)*abs(dx)/sqrtf(dx*dx + dy*dy)); \ wy >>= 1; \ for (int i = 1; i <= wy; i++) \ { \ tv1 = *v1, tv2 = *v2; \ tv1.screen[1] -= i; \ tv2.screen[1] -= i; \ RASTER_FUNC(&tv1, &tv2); \ tv1 = *v1, tv2 = *v2; \ tv1.screen[1] += i; \ tv2.screen[1] += i; \ RASTER_FUNC(&tv1, &tv2); \ } \ } \ else if (dy != 0) \ { \ int wx = (int)((RLSW.lineWidth - 1.0f)*abs(dy)/sqrtf(dx*dx + dy*dy)); \ wx >>= 1; \ for (int i = 1; i <= wx; i++) \ { \ tv1 = *v1, tv2 = *v2; \ tv1.screen[0] -= i; \ tv2.screen[0] -= i; \ RASTER_FUNC(&tv1, &tv2); \ tv1 = *v1, tv2 = *v2; \ tv1.screen[0] += i; \ tv2.screen[0] += i; \ RASTER_FUNC(&tv1, &tv2); \ } \ } \ } DEFINE_LINE_RASTER(sw_line_raster, 0, 0) DEFINE_LINE_RASTER(sw_line_raster_DEPTH, 1, 0) DEFINE_LINE_RASTER(sw_line_raster_BLEND, 0, 1) DEFINE_LINE_RASTER(sw_line_raster_DEPTH_BLEND, 1, 1) DEFINE_LINE_THICK_RASTER(sw_line_thick_raster, sw_line_raster) DEFINE_LINE_THICK_RASTER(sw_line_thick_raster_DEPTH, sw_line_raster_DEPTH) DEFINE_LINE_THICK_RASTER(sw_line_thick_raster_BLEND, sw_line_raster_BLEND) DEFINE_LINE_THICK_RASTER(sw_line_thick_raster_DEPTH_BLEND, sw_line_raster_DEPTH_BLEND) static inline void sw_line_render(sw_vertex_t *vertices) { if (!sw_line_clip_and_project(&vertices[0], &vertices[1])) return; if (RLSW.lineWidth >= 2.0f) { if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_line_thick_raster_DEPTH_BLEND(&vertices[0], &vertices[1]); else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_line_thick_raster_BLEND(&vertices[0], &vertices[1]); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_line_thick_raster_DEPTH(&vertices[0], &vertices[1]); else sw_line_thick_raster(&vertices[0], &vertices[1]); } else { if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_line_raster_DEPTH_BLEND(&vertices[0], &vertices[1]); else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_line_raster_BLEND(&vertices[0], &vertices[1]); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_line_raster_DEPTH(&vertices[0], &vertices[1]); else sw_line_raster(&vertices[0], &vertices[1]); } } // Point rendering logic static inline bool sw_point_clip_and_project(sw_vertex_t *v) { if (v->homogeneous[3] != 1.0f) { for (int_fast8_t i = 0; i < 3; i++) { if ((v->homogeneous[i] < -v->homogeneous[3]) || (v->homogeneous[i] > v->homogeneous[3])) return false; } v->homogeneous[3] = 1.0f/v->homogeneous[3]; v->homogeneous[0] *= v->homogeneous[3]; v->homogeneous[1] *= v->homogeneous[3]; v->homogeneous[2] *= v->homogeneous[3]; } sw_project_ndc_to_screen(v->screen, v->homogeneous); const int *min = NULL, *max = NULL; if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { min = RLSW.scMin; max = RLSW.scMax; } else { min = RLSW.vpMin; max = RLSW.vpMax; } bool insideX = (v->screen[0] - RLSW.pointRadius < max[0]) && (v->screen[0] + RLSW.pointRadius > min[0]); bool insideY = (v->screen[1] - RLSW.pointRadius < max[1]) && (v->screen[1] + RLSW.pointRadius > min[1]); return (insideX && insideY); } #define DEFINE_POINT_RASTER(FUNC_NAME, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND, CHECK_BOUNDS) \ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ { \ if (CHECK_BOUNDS == 1) \ { \ if ((x < RLSW.vpMin[0]) || (x >= RLSW.vpMax[0])) return; \ if ((y < RLSW.vpMin[1]) || (y >= RLSW.vpMax[1])) return; \ } \ else if (CHECK_BOUNDS == SW_SCISSOR_TEST) \ { \ if ((x < RLSW.scMin[0]) || (x >= RLSW.scMax[0])) return; \ if ((y < RLSW.scMin[1]) || (y >= RLSW.scMax[1])) return; \ } \ \ int offset = y*RLSW.framebuffer.width + x; \ \ void *dptr = sw_framebuffer_get_depth_addr(RLSW.framebuffer.depth, offset); \ \ if (ENABLE_DEPTH_TEST) \ { \ float depth = sw_framebuffer_read_depth(dptr); \ if (z > depth) return; \ } \ \ sw_framebuffer_write_depth(dptr, z); \ \ void *cptr = sw_framebuffer_get_color_addr(RLSW.framebuffer.color, offset); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ sw_framebuffer_read_color(dstColor, cptr); \ \ sw_blend_colors(dstColor, color); \ sw_framebuffer_write_color(cptr, dstColor); \ } \ else sw_framebuffer_write_color(cptr, color); \ } #define DEFINE_POINT_THICK_RASTER(FUNC_NAME, RASTER_FUNC) \ static inline void FUNC_NAME(sw_vertex_t *v) \ { \ int cx = v->screen[0]; \ int cy = v->screen[1]; \ float cz = v->homogeneous[2]; \ int radius = RLSW.pointRadius; \ const float *color = v->color; \ \ int x = 0; \ int y = radius; \ int d = 3 - 2*radius; \ \ while (x <= y) \ { \ for (int i = -x; i <= x; i++) \ { \ RASTER_FUNC(cx + i, cy + y, cz, color); \ RASTER_FUNC(cx + i, cy - y, cz, color); \ } \ for (int i = -y; i <= y; i++) \ { \ RASTER_FUNC(cx + i, cy + x, cz, color); \ RASTER_FUNC(cx + i, cy - x, cz, color); \ } \ if (d > 0) \ { \ y--; \ d = d + 4*(x - y) + 10; \ } \ else d = d + 4*x + 6; \ x++; \ } \ } DEFINE_POINT_RASTER(sw_point_raster, 0, 0, 0) DEFINE_POINT_RASTER(sw_point_raster_DEPTH, 1, 0, 0) DEFINE_POINT_RASTER(sw_point_raster_BLEND, 0, 1, 0) DEFINE_POINT_RASTER(sw_point_raster_DEPTH_BLEND, 1, 1, 0) DEFINE_POINT_RASTER(sw_point_raster_CHECK, 0, 0, 1) DEFINE_POINT_RASTER(sw_point_raster_DEPTH_CHECK, 1, 0, 1) DEFINE_POINT_RASTER(sw_point_raster_BLEND_CHECK, 0, 1, 1) DEFINE_POINT_RASTER(sw_point_raster_DEPTH_BLEND_CHECK, 1, 1, 1) DEFINE_POINT_RASTER(sw_point_raster_CHECK_SCISSOR, 0, 0, SW_SCISSOR_TEST) DEFINE_POINT_RASTER(sw_point_raster_DEPTH_CHECK_SCISSOR, 1, 0, SW_SCISSOR_TEST) DEFINE_POINT_RASTER(sw_point_raster_BLEND_CHECK_SCISSOR, 0, 1, SW_SCISSOR_TEST) DEFINE_POINT_RASTER(sw_point_raster_DEPTH_BLEND_CHECK_SCISSOR, 1, 1, SW_SCISSOR_TEST) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster, sw_point_raster_CHECK) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH, sw_point_raster_DEPTH_CHECK) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_BLEND, sw_point_raster_BLEND_CHECK) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH_BLEND, sw_point_raster_DEPTH_BLEND_CHECK) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_SCISSOR, sw_point_raster_CHECK_SCISSOR) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH_SCISSOR, sw_point_raster_DEPTH_CHECK_SCISSOR) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_BLEND_SCISSOR, sw_point_raster_BLEND_CHECK_SCISSOR) DEFINE_POINT_THICK_RASTER(sw_point_thick_raster_DEPTH_BLEND_SCISSOR, sw_point_raster_DEPTH_BLEND_CHECK_SCISSOR) static inline void sw_point_render(sw_vertex_t *v) { if (!sw_point_clip_and_project(v)) return; if (RLSW.pointRadius >= 1.0f) { if (SW_STATE_CHECK(SW_STATE_SCISSOR_TEST)) { if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_point_thick_raster_DEPTH_BLEND_SCISSOR(v); else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_point_thick_raster_BLEND_SCISSOR(v); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_point_thick_raster_DEPTH_SCISSOR(v); else sw_point_thick_raster_SCISSOR(v); } else { if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_point_thick_raster_DEPTH_BLEND(v); else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_point_thick_raster_BLEND(v); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_point_thick_raster_DEPTH(v); else sw_point_thick_raster(v); } } else { if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST | SW_STATE_BLEND)) sw_point_raster_DEPTH_BLEND(v->screen[0], v->screen[1], v->homogeneous[2], v->color); else if (SW_STATE_CHECK(SW_STATE_BLEND)) sw_point_raster_BLEND(v->screen[0], v->screen[1], v->homogeneous[2], v->color); else if (SW_STATE_CHECK(SW_STATE_DEPTH_TEST)) sw_point_raster_DEPTH(v->screen[0], v->screen[1], v->homogeneous[2], v->color); else sw_point_raster(v->screen[0], v->screen[1], v->homogeneous[2], v->color); } } // Polygon modes mendering logic static inline void sw_poly_point_render(void) { for (int i = 0; i < RLSW.vertexCounter; i++) sw_point_render(&RLSW.vertexBuffer[i]); } static inline void sw_poly_line_render(void) { const sw_vertex_t *vertices = RLSW.vertexBuffer; int cm1 = RLSW.vertexCounter - 1; for (int i = 0; i < cm1; i++) sw_line_render((sw_vertex_t[2]){ vertices[i], vertices[i + 1] }); sw_line_render((sw_vertex_t[2]){ vertices[cm1], vertices[0] }); } static inline void sw_poly_fill_render(void) { switch (RLSW.drawMode) { case SW_POINTS: sw_point_render(&RLSW.vertexBuffer[0]); break; case SW_LINES: sw_line_render(RLSW.vertexBuffer); break; case SW_TRIANGLES: sw_triangle_render(); break; case SW_QUADS: sw_quad_render(); break; } } // Validity check helper functions static inline bool sw_is_texture_valid(uint32_t id) { bool valid = true; if (id == 0) valid = false; else if (id >= SW_MAX_TEXTURES) valid = false; else if (RLSW.loadedTextures[id].pixels.cptr == 0) valid = false; return true; } static inline bool sw_is_texture_filter_valid(int filter) { return ((filter == SW_NEAREST) || (filter == SW_LINEAR)); } static inline bool sw_is_texture_wrap_valid(int wrap) { return ((wrap == SW_REPEAT) || (wrap == SW_CLAMP)); } static inline bool sw_is_draw_mode_valid(int mode) { bool result = false; switch (mode) { case SW_POINTS: case SW_LINES: case SW_TRIANGLES: case SW_QUADS: result = true; break; default: break; } return result; } static inline bool sw_is_poly_mode_valid(int mode) { bool result = false; switch (mode) { case SW_POINT: case SW_LINE: case SW_FILL: result = true; break; default: break; } return result; } static inline bool sw_is_face_valid(int face) { return (face == SW_FRONT || face == SW_BACK); } static inline bool sw_is_blend_src_factor_valid(int blend) { bool result = false; switch (blend) { case SW_ZERO: case SW_ONE: case SW_SRC_COLOR: case SW_ONE_MINUS_SRC_COLOR: case SW_SRC_ALPHA: case SW_ONE_MINUS_SRC_ALPHA: case SW_DST_ALPHA: case SW_ONE_MINUS_DST_ALPHA: case SW_DST_COLOR: case SW_ONE_MINUS_DST_COLOR: case SW_SRC_ALPHA_SATURATE: result = true; break; default: break; } return result; } static inline bool sw_is_blend_dst_factor_valid(int blend) { bool result = false; switch (blend) { case SW_ZERO: case SW_ONE: case SW_SRC_COLOR: case SW_ONE_MINUS_SRC_COLOR: case SW_SRC_ALPHA: case SW_ONE_MINUS_SRC_ALPHA: case SW_DST_ALPHA: case SW_ONE_MINUS_DST_ALPHA: case SW_DST_COLOR: case SW_ONE_MINUS_DST_COLOR: result = true; break; default: break; } return result; } //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- bool swInit(int w, int h) { if (!sw_framebuffer_load(w, h)) { swClose(); return false; } swViewport(0, 0, w, h); swScissor(0, 0, w, h); RLSW.loadedTextures = SW_MALLOC(SW_MAX_TEXTURES*sizeof(sw_texture_t)); if (RLSW.loadedTextures == NULL) { swClose(); return false; } RLSW.freeTextureIds = SW_MALLOC(SW_MAX_TEXTURES*sizeof(uint32_t)); if (RLSW.loadedTextures == NULL) { swClose(); return false; } RLSW.clearColor[0] = 0.0f; RLSW.clearColor[1] = 0.0f; RLSW.clearColor[2] = 0.0f; RLSW.clearColor[3] = 1.0f; RLSW.clearDepth = 1.0f; RLSW.currentMatrixMode = SW_MODELVIEW; RLSW.currentMatrix = &RLSW.stackModelview[0]; sw_matrix_id(RLSW.stackProjection[0]); sw_matrix_id(RLSW.stackModelview[0]); sw_matrix_id(RLSW.stackTexture[0]); sw_matrix_id(RLSW.matMVP); RLSW.stackProjectionCounter = 1; RLSW.stackModelviewCounter = 1; RLSW.stackTextureCounter = 1; RLSW.isDirtyMVP = false; RLSW.current.texcoord[0] = 0.0f; RLSW.current.texcoord[1] = 0.0f; RLSW.current.color[0] = 1.0f; RLSW.current.color[1] = 1.0f; RLSW.current.color[2] = 1.0f; RLSW.current.color[3] = 1.0f; RLSW.srcFactor = SW_SRC_ALPHA; RLSW.dstFactor = SW_ONE_MINUS_SRC_ALPHA; RLSW.srcFactorFunc = sw_factor_src_alpha; RLSW.dstFactorFunc = sw_factor_one_minus_src_alpha; RLSW.polyMode = SW_FILL; RLSW.cullFace = SW_BACK; static const float defTex[3*2*2] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; RLSW.loadedTextures[0].pixels.cptr = defTex; RLSW.loadedTextures[0].width = 2; RLSW.loadedTextures[0].height = 2; RLSW.loadedTextures[0].wMinus1 = 1; RLSW.loadedTextures[0].hMinus1 = 1; RLSW.loadedTextures[0].format = SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32; RLSW.loadedTextures[0].minFilter = SW_NEAREST; RLSW.loadedTextures[0].magFilter = SW_NEAREST; RLSW.loadedTextures[0].sWrap = SW_REPEAT; RLSW.loadedTextures[0].tWrap = SW_REPEAT; RLSW.loadedTextures[0].tx = 0.5f; RLSW.loadedTextures[0].ty = 0.5f; RLSW.loadedTextures[0].copy = false; RLSW.loadedTextureCount = 1; return true; } void swClose(void) { for (int i = 1; i < RLSW.loadedTextureCount; i++) { sw_texture_t *texture = &RLSW.loadedTextures[i]; if (sw_is_texture_valid(i) && texture->copy) SW_FREE(texture->pixels.ptr); } SW_FREE(RLSW.framebuffer.color); SW_FREE(RLSW.framebuffer.depth); SW_FREE(RLSW.loadedTextures); SW_FREE(RLSW.freeTextureIds); RLSW = (sw_context_t) { 0 }; } bool swResizeFramebuffer(int w, int h) { return sw_framebuffer_resize(w, h); } void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels) { sw_pixelformat_t pFormat = sw_get_pixel_format(format, type); if (w <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } if (h <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } if (w > RLSW.framebuffer.width) w = RLSW.framebuffer.width; if (h > RLSW.framebuffer.height) h = RLSW.framebuffer.height; x = sw_clampi(x, 0, w); y = sw_clampi(y, 0, h); switch (pFormat) { case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_copy_to_R5G6B5(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_copy_to_R8G8B8(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_copy_to_R32(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_copy_to_R32G32B32(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_copy_to_R32G32B32A32(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_copy_to_R16(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_copy_to_R16G16B16(x, y, w, h, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_copy_to_R16G16B16A16(x, y, w, h, pixels); break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels) { sw_pixelformat_t pFormat = sw_get_pixel_format(format, type); if (wSrc <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } if (hSrc <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } if (wSrc > RLSW.framebuffer.width) wSrc = RLSW.framebuffer.width; if (hSrc > RLSW.framebuffer.height) hSrc = RLSW.framebuffer.height; xSrc = sw_clampi(xSrc, 0, wSrc); ySrc = sw_clampi(ySrc, 0, hSrc); switch (pFormat) { case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5: sw_framebuffer_blit_to_R5G6B5(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_blit_to_R8G8B8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_blit_to_R5G5B5A1(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_blit_to_R4G4B4A4(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_blit_to_R8G8B8A8(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32: sw_framebuffer_blit_to_R32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: sw_framebuffer_blit_to_R32G32B32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: sw_framebuffer_blit_to_R32G32B32A32(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16: sw_framebuffer_blit_to_R16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: sw_framebuffer_blit_to_R16G16B16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: sw_framebuffer_blit_to_R16G16B16A16(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, pixels); break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void *swGetColorBuffer(int *w, int *h) { if (w) *w = RLSW.framebuffer.width; if (h) *h = RLSW.framebuffer.height; return RLSW.framebuffer.color; } void swEnable(SWstate state) { switch (state) { case SW_SCISSOR_TEST: RLSW.stateFlags |= SW_STATE_SCISSOR_TEST; break; case SW_TEXTURE_2D: RLSW.stateFlags |= SW_STATE_TEXTURE_2D; break; case SW_DEPTH_TEST: RLSW.stateFlags |= SW_STATE_DEPTH_TEST; break; case SW_CULL_FACE: RLSW.stateFlags |= SW_STATE_CULL_FACE; break; case SW_BLEND: RLSW.stateFlags |= SW_STATE_BLEND; break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void swDisable(SWstate state) { switch (state) { case SW_SCISSOR_TEST: RLSW.stateFlags &= ~SW_STATE_SCISSOR_TEST; break; case SW_TEXTURE_2D: RLSW.stateFlags &= ~SW_STATE_TEXTURE_2D; break; case SW_DEPTH_TEST: RLSW.stateFlags &= ~SW_STATE_DEPTH_TEST; break; case SW_CULL_FACE: RLSW.stateFlags &= ~SW_STATE_CULL_FACE; break; case SW_BLEND: RLSW.stateFlags &= ~SW_STATE_BLEND; break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void swGetIntegerv(SWget name, int *v) { switch (name) { case SW_MODELVIEW_STACK_DEPTH: *v = SW_MODELVIEW_STACK_DEPTH; break; case SW_PROJECTION_STACK_DEPTH: *v = SW_PROJECTION_STACK_DEPTH; break; case SW_TEXTURE_STACK_DEPTH: *v = SW_TEXTURE_STACK_DEPTH; break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void swGetFloatv(SWget name, float *v) { switch (name) { case SW_COLOR_CLEAR_VALUE: { v[0] = RLSW.clearColor[0]; v[1] = RLSW.clearColor[1]; v[2] = RLSW.clearColor[2]; v[3] = RLSW.clearColor[3]; } break; case SW_CURRENT_COLOR: { v[0] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[0]; v[1] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[1]; v[2] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[2]; v[3] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].color[3]; } break; case SW_CURRENT_TEXTURE_COORDS: { v[0] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].texcoord[0]; v[1] = RLSW.vertexBuffer[RLSW.vertexCounter - 1].texcoord[1]; } break; case SW_POINT_SIZE: { v[0] = 2.0f*RLSW.pointRadius; } break; case SW_LINE_WIDTH: { v[0] = RLSW.lineWidth; } break; case SW_MODELVIEW_MATRIX: { for (int i = 0; i < 16; i++) v[i] = RLSW.stackModelview[RLSW.stackModelviewCounter - 1][i]; } break; case SW_PROJECTION_MATRIX: { for (int i = 0; i < 16; i++) v[i] = RLSW.stackProjection[RLSW.stackProjectionCounter - 1][i]; } break; case SW_TEXTURE_MATRIX: { for (int i = 0; i < 16; i++) v[i] = RLSW.stackTexture[RLSW.stackTextureCounter - 1][i]; } break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } const char *swGetString(SWget name) { const char *result = NULL; switch (name) { case SW_VENDOR: result = "RLSW Header"; break; case SW_RENDERER: result = "RLSW Software Renderer"; break; case SW_VERSION: result = "RLSW 1.0"; break; case SW_EXTENSIONS: result = "None"; break; default: RLSW.errCode = SW_INVALID_ENUM; break; } return result; } SWerrcode swGetError(void) { SWerrcode ret = RLSW.errCode; RLSW.errCode = SW_NO_ERROR; return ret; } void swViewport(int x, int y, int width, int height) { if ((width < 0) || (height < 0)) { RLSW.errCode = SW_INVALID_VALUE; return; } RLSW.vpSize[0] = width; RLSW.vpSize[1] = height; RLSW.vpHalfSize[0] = (int)(width/2.0f + 0.5f); RLSW.vpHalfSize[1] = (int)(height/2.0f + 0.5f); RLSW.vpCenter[0] = x + RLSW.vpHalfSize[0]; RLSW.vpCenter[1] = y + RLSW.vpHalfSize[1]; RLSW.vpMin[0] = sw_clampi(x, 0, RLSW.framebuffer.width - 1); RLSW.vpMin[1] = sw_clampi(y, 0, RLSW.framebuffer.height - 1); RLSW.vpMax[0] = sw_clampi(x + width, 0, RLSW.framebuffer.width - 1); RLSW.vpMax[1] = sw_clampi(y + height, 0, RLSW.framebuffer.height - 1); } void swScissor(int x, int y, int width, int height) { if ((width < 0) || (height < 0)) { RLSW.errCode = SW_INVALID_VALUE; return; } RLSW.scMin[0] = sw_clampi(x, 0, RLSW.framebuffer.width - 1); RLSW.scMin[1] = sw_clampi(y, 0, RLSW.framebuffer.height - 1); RLSW.scMax[0] = sw_clampi(x + width, 0, RLSW.framebuffer.width - 1); RLSW.scMax[1] = sw_clampi(y + height, 0, RLSW.framebuffer.height - 1); RLSW.scClipMin[0] = (2.0f*(float)RLSW.scMin[0]/(float)RLSW.vpSize[0]) - 1.0f; RLSW.scClipMax[0] = (2.0f*(float)RLSW.scMax[0]/(float)RLSW.vpSize[0]) - 1.0f; RLSW.scClipMax[1] = 1.0f - (2.0f*(float)RLSW.scMin[1]/(float)RLSW.vpSize[1]); RLSW.scClipMin[1] = 1.0f - (2.0f*(float)RLSW.scMax[1]/(float)RLSW.vpSize[1]); } void swClearColor(float r, float g, float b, float a) { RLSW.clearColor[0] = r; RLSW.clearColor[1] = g; RLSW.clearColor[2] = b; RLSW.clearColor[3] = a; } void swClear(uint32_t bitmask) { int size = RLSW.framebuffer.width*RLSW.framebuffer.height; if ((bitmask & (SW_COLOR_BUFFER_BIT | SW_DEPTH_BUFFER_BIT)) == (SW_COLOR_BUFFER_BIT | SW_DEPTH_BUFFER_BIT)) { sw_framebuffer_fill(RLSW.framebuffer.color, RLSW.framebuffer.depth,size, RLSW.clearColor, RLSW.clearDepth); } else if (bitmask & (SW_COLOR_BUFFER_BIT)) { sw_framebuffer_fill_color(RLSW.framebuffer.color, size, RLSW.clearColor); } else if (bitmask & SW_DEPTH_BUFFER_BIT) { sw_framebuffer_fill_depth(RLSW.framebuffer.depth, size, RLSW.clearDepth); } } void swBlendFunc(SWfactor sfactor, SWfactor dfactor) { if (!sw_is_blend_src_factor_valid(sfactor) || !sw_is_blend_dst_factor_valid(dfactor)) { RLSW.errCode = SW_INVALID_ENUM; return; } RLSW.srcFactor = sfactor; RLSW.dstFactor = dfactor; switch (sfactor) { case SW_ZERO: RLSW.srcFactorFunc = sw_factor_zero; break; case SW_ONE: RLSW.srcFactorFunc = sw_factor_one; break; case SW_SRC_COLOR: RLSW.srcFactorFunc = sw_factor_src_color; break; case SW_ONE_MINUS_SRC_COLOR: RLSW.srcFactorFunc = sw_factor_one_minus_src_color; break; case SW_SRC_ALPHA: RLSW.srcFactorFunc = sw_factor_src_alpha; break; case SW_ONE_MINUS_SRC_ALPHA: RLSW.srcFactorFunc = sw_factor_one_minus_src_alpha; break; case SW_DST_ALPHA: RLSW.srcFactorFunc = sw_factor_dst_alpha; break; case SW_ONE_MINUS_DST_ALPHA: RLSW.srcFactorFunc = sw_factor_one_minus_dst_alpha; break; case SW_DST_COLOR: RLSW.srcFactorFunc = sw_factor_dst_color; break; case SW_ONE_MINUS_DST_COLOR: RLSW.srcFactorFunc = sw_factor_one_minus_dst_color; break; case SW_SRC_ALPHA_SATURATE: RLSW.srcFactorFunc = sw_factor_src_alpha_saturate; break; default: break; } switch (dfactor) { case SW_ZERO: RLSW.dstFactorFunc = sw_factor_zero; break; case SW_ONE: RLSW.dstFactorFunc = sw_factor_one; break; case SW_SRC_COLOR: RLSW.dstFactorFunc = sw_factor_src_color; break; case SW_ONE_MINUS_SRC_COLOR: RLSW.dstFactorFunc = sw_factor_one_minus_src_color; break; case SW_SRC_ALPHA: RLSW.dstFactorFunc = sw_factor_src_alpha; break; case SW_ONE_MINUS_SRC_ALPHA: RLSW.dstFactorFunc = sw_factor_one_minus_src_alpha; break; case SW_DST_ALPHA: RLSW.dstFactorFunc = sw_factor_dst_alpha; break; case SW_ONE_MINUS_DST_ALPHA: RLSW.dstFactorFunc = sw_factor_one_minus_dst_alpha; break; case SW_DST_COLOR: RLSW.dstFactorFunc = sw_factor_dst_color; break; case SW_ONE_MINUS_DST_COLOR: RLSW.dstFactorFunc = sw_factor_one_minus_dst_color; break; case SW_SRC_ALPHA_SATURATE: break; default: break; } } void swPolygonMode(SWpoly mode) { if (!sw_is_poly_mode_valid(mode)) { RLSW.errCode = SW_INVALID_ENUM; return; } RLSW.polyMode = mode; } void swCullFace(SWface face) { if (!sw_is_face_valid(face)) { RLSW.errCode = SW_INVALID_ENUM; return; } RLSW.cullFace = face; } void swPointSize(float size) { RLSW.pointRadius = floorf(size*0.5f); } void swLineWidth(float width) { RLSW.lineWidth = roundf(width); } void swMatrixMode(SWmatrix mode) { switch (mode) { case SW_PROJECTION: RLSW.currentMatrix = &RLSW.stackProjection[RLSW.stackProjectionCounter - 1]; break; case SW_MODELVIEW: RLSW.currentMatrix = &RLSW.stackModelview[RLSW.stackModelviewCounter - 1]; break; case SW_TEXTURE: RLSW.currentMatrix = &RLSW.stackTexture[RLSW.stackTextureCounter - 1]; break; default: RLSW.errCode = SW_INVALID_ENUM; return; } RLSW.currentMatrixMode = mode; } void swPushMatrix(void) { switch (RLSW.currentMatrixMode) { case SW_PROJECTION: { if (RLSW.stackProjectionCounter >= SW_MAX_PROJECTION_STACK_SIZE) { RLSW.errCode = SW_STACK_OVERFLOW; return; } int iOld = RLSW.stackProjectionCounter - 1; int iNew = RLSW.stackProjectionCounter++; for (int i = 0; i < 16; i++) { RLSW.stackProjection[iNew][i] = RLSW.stackProjection[iOld][i]; } RLSW.currentMatrix = &RLSW.stackProjection[iNew]; } break; case SW_MODELVIEW: { if (RLSW.stackModelviewCounter >= SW_MAX_MODELVIEW_STACK_SIZE) { RLSW.errCode = SW_STACK_OVERFLOW; return; } int iOld = RLSW.stackModelviewCounter - 1; int iNew = RLSW.stackModelviewCounter++; for (int i = 0; i < 16; i++) { RLSW.stackModelview[iNew][i] = RLSW.stackModelview[iOld][i]; } RLSW.currentMatrix = &RLSW.stackModelview[iNew]; } break; case SW_TEXTURE: { if (RLSW.stackTextureCounter >= SW_MAX_TEXTURE_STACK_SIZE) { RLSW.errCode = SW_STACK_OVERFLOW; return; } int iOld = RLSW.stackTextureCounter - 1; int iNew = RLSW.stackTextureCounter++; for (int i = 0; i < 16; i++) { RLSW.stackTexture[iNew][i] = RLSW.stackTexture[iOld][i]; } RLSW.currentMatrix = &RLSW.stackTexture[iNew]; } break; default: break; } } void swPopMatrix(void) { switch (RLSW.currentMatrixMode) { case SW_PROJECTION: { if (RLSW.stackProjectionCounter <= 0) { RLSW.errCode = SW_STACK_UNDERFLOW; return; } RLSW.currentMatrix = &RLSW.stackProjection[--RLSW.stackProjectionCounter]; RLSW.isDirtyMVP = true; //< The MVP is considered to have been changed } break; case SW_MODELVIEW: { if (RLSW.stackModelviewCounter <= 0) { RLSW.errCode = SW_STACK_UNDERFLOW; return; } RLSW.currentMatrix = &RLSW.stackModelview[--RLSW.stackModelviewCounter]; RLSW.isDirtyMVP = true; //< The MVP is considered to have been changed } break; case SW_TEXTURE: { if (RLSW.stackTextureCounter <= 0) { RLSW.errCode = SW_STACK_UNDERFLOW; return; } RLSW.currentMatrix = &RLSW.stackTexture[--RLSW.stackTextureCounter]; } break; default: break; } } void swLoadIdentity(void) { sw_matrix_id(*RLSW.currentMatrix); if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true; } void swTranslatef(float x, float y, float z) { sw_matrix_t mat; sw_matrix_id(mat); mat[12] = x; mat[13] = y; mat[14] = z; sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix); if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true; } void swRotatef(float angle, float x, float y, float z) { angle *= SW_DEG2RAD; float lengthSq = x*x + y*y + z*z; if ((lengthSq != 1.0f) && (lengthSq != 0.0f)) { float invLength = 1.0f/sqrtf(lengthSq); x *= invLength; y *= invLength; z *= invLength; } float sinres = sinf(angle); float cosres = cosf(angle); float t = 1.0f - cosres; sw_matrix_t mat; mat[0] = x*x*t + cosres; mat[1] = y*x*t + z*sinres; mat[2] = z*x*t - y*sinres; mat[3] = 0.0f; mat[4] = x*y*t - z*sinres; mat[5] = y*y*t + cosres; mat[6] = z*y*t + x*sinres; mat[7] = 0.0f; mat[8] = x*z*t + y*sinres; mat[9] = y*z*t - x*sinres; mat[10] = z*z*t + cosres; mat[11] = 0.0f; mat[12] = 0.0f; mat[13] = 0.0f; mat[14] = 0.0f; mat[15] = 1.0f; sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix); if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true; } void swScalef(float x, float y, float z) { sw_matrix_t mat; mat[0] = x, mat[1] = 0, mat[2] = 0, mat[3] = 0; mat[4] = 0, mat[5] = y, mat[6] = 0, mat[7] = 0; mat[8] = 0, mat[9] = 0, mat[10] = z, mat[11] = 0; mat[12] = 0, mat[13] = 0, mat[14] = 0, mat[15] = 1; sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix); if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true; } void swMultMatrixf(const float *mat) { sw_matrix_mul(*RLSW.currentMatrix, mat, *RLSW.currentMatrix); if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true; } void swFrustum(double left, double right, double bottom, double top, double znear, double zfar) { sw_matrix_t mat; double rl = right - left; double tb = top - bottom; double fn = zfar - znear; mat[0] = (float)(znear*2.0)/rl; mat[1] = 0.0f; mat[2] = 0.0f; mat[3] = 0.0f; mat[4] = 0.0f; mat[5] = (float)(znear*2.0)/tb; mat[6] = 0.0f; mat[7] = 0.0f; mat[8] = (float)(right + left)/rl; mat[9] = (float)(top + bottom)/tb; mat[10] = -(float)(zfar + znear)/fn; mat[11] = -1.0f; mat[12] = 0.0f; mat[13] = 0.0f; mat[14] = -(zfar*znear*2.0)/fn; mat[15] = 0.0f; sw_matrix_mul(*RLSW.currentMatrix, *RLSW.currentMatrix, mat); if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true; } void swOrtho(double left, double right, double bottom, double top, double znear, double zfar) { sw_matrix_t mat; double rl = right - left; double tb = top - bottom; double fn = zfar - znear; mat[0] = 2.0f/(float)rl; mat[1] = 0.0f; mat[2] = 0.0f; mat[3] = 0.0f; mat[4] = 0.0f; mat[5] = 2.0f/(float)tb; mat[6] = 0.0f; mat[7] = 0.0f; mat[8] = 0.0f; mat[9] = 0.0f; mat[10] = -2.0f/(float)fn; mat[11] = 0.0f; mat[12] = -(float)(left + right)/rl; mat[13] = -(float)(top + bottom)/tb; mat[14] = -(float)(zfar + znear)/fn; mat[15] = 1.0f; sw_matrix_mul(*RLSW.currentMatrix, *RLSW.currentMatrix, mat); if (RLSW.currentMatrixMode != SW_TEXTURE) RLSW.isDirtyMVP = true; } void swBegin(SWdraw mode) { // Check if the draw mode is valid if (!sw_is_draw_mode_valid(mode)) { RLSW.errCode = SW_INVALID_ENUM; return; } // Recalculate the MVP if this is needed if (RLSW.isDirtyMVP) { sw_matrix_mul_rst(RLSW.matMVP, RLSW.stackModelview[RLSW.stackModelviewCounter - 1], RLSW.stackProjection[RLSW.stackProjectionCounter - 1]); RLSW.isDirtyMVP = false; } // Obtain the number of vertices needed for this primitive switch (mode) { case SW_POINTS: RLSW.reqVertices = 1; break; case SW_LINES: RLSW.reqVertices = 2; break; case SW_TRIANGLES: RLSW.reqVertices = 3; break; case SW_QUADS: RLSW.reqVertices = 4; break; } // Initialize required values RLSW.vertexCounter = 0; RLSW.drawMode = mode; } void swEnd(void) { RLSW.drawMode = 0; } void swVertex2i(int x, int y) { const float v[4] = { (float)x, (float)y, 0.0f, 1.0f }; swVertex4fv(v); } void swVertex2f(float x, float y) { const float v[4] = { x, y, 0.0f, 1.0f }; swVertex4fv(v); } void swVertex2fv(const float *v) { const float v4[4] = { v[0], v[1], 0.0f, 1.0f }; swVertex4fv(v4); } void swVertex3i(int x, int y, int z) { const float v[4] = { (float)x, (float)y, (float)z, 1.0f }; swVertex4fv(v); } void swVertex3f(float x, float y, float z) { const float v[4] = { x, y, z, 1.0f }; swVertex4fv(v); } void swVertex3fv(const float *v) { const float v4[4] = { v[0], v[1], v[2], 1.0f }; swVertex4fv(v4); } void swVertex4i(int x, int y, int z, int w) { const float v[4] = { (float)x, (float)y, (float)z, (float)w }; swVertex4fv(v); } void swVertex4f(float x, float y, float z, float w) { const float v[4] = { x, y, z, w }; swVertex4fv(v); } void swVertex4fv(const float *v) { // Copy the position in the current vertex sw_vertex_t *vertex = &RLSW.vertexBuffer[RLSW.vertexCounter++]; for (int i = 0; i < 4; i++) vertex->position[i] = v[i]; // Copy additonal vertex data for (int i = 0; i < 2; i++) vertex->texcoord[i] = RLSW.current.texcoord[i]; for (int i = 0; i < 4; i++) vertex->color[i] = RLSW.current.color[i]; // Calculation of homogeneous coordinates const float *m = RLSW.matMVP; vertex->homogeneous[0] = m[0]*v[0] + m[4]*v[1] + m[8]*v[2] + m[12]*v[3]; vertex->homogeneous[1] = m[1]*v[0] + m[5]*v[1] + m[9]*v[2] + m[13]*v[3]; vertex->homogeneous[2] = m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3]; vertex->homogeneous[3] = m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3]; // Immediate rendering of the primitive if the required number is reached if (RLSW.vertexCounter == RLSW.reqVertices) { switch (RLSW.polyMode) { case SW_FILL: sw_poly_fill_render(); break; case SW_LINE: sw_poly_line_render(); break; case SW_POINT: sw_poly_point_render(); break; default: break; } RLSW.vertexCounter = 0; } } void swColor3ub(uint8_t r, uint8_t g, uint8_t b) { float cv[4]; cv[0] = (float)r/255; cv[1] = (float)g/255; cv[2] = (float)b/255; cv[3] = 1.0f; swColor4fv(cv); } void swColor3ubv(const uint8_t *v) { float cv[4]; cv[0] = (float)v[0]/255; cv[1] = (float)v[1]/255; cv[2] = (float)v[2]/255; cv[3] = 1.0f; swColor4fv(cv); } void swColor3f(float r, float g, float b) { float cv[4]; cv[0] = r; cv[1] = g; cv[2] = b; cv[3] = 1.0f; swColor4fv(cv); } void swColor3fv(const float *v) { float cv[4]; cv[0] = v[0]; cv[1] = v[1]; cv[2] = v[2]; cv[3] = 1.0f; swColor4fv(cv); } void swColor4ub(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { float cv[4]; cv[0] = (float)r/255; cv[1] = (float)g/255; cv[2] = (float)b/255; cv[3] = (float)a/255; swColor4fv(cv); } void swColor4ubv(const uint8_t *v) { float cv[4]; cv[0] = (float)v[0]/255; cv[1] = (float)v[1]/255; cv[2] = (float)v[2]/255; cv[3] = (float)v[3]/255; swColor4fv(cv); } void swColor4f(float r, float g, float b, float a) { float cv[4]; cv[0] = r; cv[1] = g; cv[2] = b; cv[3] = a; swColor4fv(cv); } void swColor4fv(const float *v) { for (int i = 0; i < 4; i++) RLSW.current.color[i] = v[i]; } void swTexCoord2f(float u, float v) { const float *m = RLSW.stackTexture[RLSW.stackTextureCounter - 1]; RLSW.current.texcoord[0] = m[0]*u + m[4]*v + m[12]; RLSW.current.texcoord[1] = m[1]*u + m[5]*v + m[13]; } void swTexCoord2fv(const float *v) { const float *m = RLSW.stackTexture[RLSW.stackTextureCounter - 1]; RLSW.current.texcoord[0] = m[0]*v[0] + m[4]*v[1] + m[12]; RLSW.current.texcoord[1] = m[1]*v[0] + m[5]*v[1] + m[13]; } void swBindArray(SWarray type, void *buffer) { switch (type) { case SW_VERTEX_ARRAY: RLSW.array.positions = buffer; break; case SW_TEXTURE_COORD_ARRAY: RLSW.array.texcoords = buffer; break; case SW_COLOR_ARRAY: RLSW.array.colors = buffer; break; default: break; } } void swDrawArrays(SWdraw mode, int offset, int count) { if (RLSW.array.positions == 0) { RLSW.errCode = SW_INVALID_OPERATION; return; } swBegin(mode); { swTexCoord2f(0.0f, 0.0f); swColor4f(1.0f, 1.0f, 1.0f, 1.0f); for (int i = offset; i < count; i++) { if (RLSW.array.texcoords) swTexCoord2fv(RLSW.array.texcoords + 2*i); if (RLSW.array.colors) swColor4ubv(RLSW.array.colors + 4*i); swVertex3fv(RLSW.array.positions + 3*i); } } swEnd(); } void swGenTextures(int count, uint32_t *textures) { if ((count == 0) || (textures == NULL)) return; for (int i = 0; i < count; i++) { if (RLSW.loadedTextureCount >= SW_MAX_TEXTURES) { RLSW.errCode = SW_STACK_OVERFLOW; // WARNING: Out of memory, not really stack overflow return; } uint32_t id = 0; if (RLSW.freeTextureIdCount > 0) id = RLSW.freeTextureIds[--RLSW.freeTextureIdCount]; else id = RLSW.loadedTextureCount++; RLSW.loadedTextures[id] = RLSW.loadedTextures[0]; textures[i] = id; } } void swDeleteTextures(int count, uint32_t *textures) { if ((count == 0) || (textures == NULL)) return; for (int i = 0; i < count; i++) { if (!sw_is_texture_valid(textures[i])) { RLSW.errCode = SW_INVALID_VALUE; continue; } if (RLSW.loadedTextures[textures[i]].copy) { SW_FREE(RLSW.loadedTextures[textures[i]].pixels.ptr); } RLSW.loadedTextures[textures[i]].pixels.cptr = NULL; RLSW.freeTextureIds[RLSW.freeTextureIdCount++] = textures[i]; } } void swTexImage2D(int width, int height, SWformat format, SWtype type, bool copy, const void *data) { uint32_t id = RLSW.currentTexture; if (!sw_is_texture_valid(id)) { RLSW.errCode = SW_INVALID_VALUE; return; } int pixelFormat = sw_get_pixel_format(format, type); if (pixelFormat <= SW_PIXELFORMAT_UNKNOWN) { RLSW.errCode = SW_INVALID_ENUM; return; } sw_texture_t *texture = &RLSW.loadedTextures[id]; if (copy) { int bytes = sw_get_pixel_bytes(pixelFormat); int size = bytes*width*height; texture->pixels.ptr = SW_MALLOC(size); if (texture->pixels.ptr == NULL) { RLSW.errCode = SW_STACK_OVERFLOW; // WARING: Out of memory... return; } for (int i = 0; i < size; i++) { ((uint8_t *)texture->pixels.ptr)[i] = ((uint8_t *)data)[i]; } } else texture->pixels.cptr = data; texture->width = width; texture->height = height; texture->wMinus1 = width - 1; texture->hMinus1 = height - 1; texture->format = pixelFormat; texture->tx = 1.0f/width; texture->ty = 1.0f/height; texture->copy = copy; } void swTexParameteri(int param, int value) { uint32_t id = RLSW.currentTexture; if (!sw_is_texture_valid(id)) { RLSW.errCode = SW_INVALID_VALUE; return; } sw_texture_t *texture = &RLSW.loadedTextures[id]; switch (param) { case SW_TEXTURE_MIN_FILTER: { if (!sw_is_texture_filter_valid(value)) { RLSW.errCode = SW_INVALID_ENUM; return; } texture->minFilter = value; } break; case SW_TEXTURE_MAG_FILTER: { if (!sw_is_texture_filter_valid(value)) { RLSW.errCode = SW_INVALID_ENUM; return; } texture->magFilter = value; } break; case SW_TEXTURE_WRAP_S: { if (!sw_is_texture_wrap_valid(value)) { RLSW.errCode = SW_INVALID_ENUM; return; } texture->sWrap = value; } break; case SW_TEXTURE_WRAP_T: { if (!sw_is_texture_wrap_valid(value)) { RLSW.errCode = SW_INVALID_ENUM; return; } texture->tWrap = value; } break; default: RLSW.errCode = SW_INVALID_ENUM; break; } } void swBindTexture(uint32_t id) { if (id >= SW_MAX_TEXTURES) { RLSW.errCode = SW_INVALID_VALUE; return; } if (RLSW.loadedTextures[id].pixels.cptr == NULL) { RLSW.errCode = SW_INVALID_OPERATION; return; } RLSW.currentTexture = id; } #endif // RLSW_IMPLEMENTATION