#include #include namespace rs { typedef int8_t i8; typedef uint8_t u8; typedef int16_t i16; typedef uint16_t u16; typedef int32_t i32; typedef uint32_t u32; typedef int64_t i64; typedef uint64_t u64; typedef struct Canvas Canvas; struct Canvas { u8 *fb; u32 *zbuf; int w, h; }; extern Canvas *canvas; typedef struct Texture Texture; struct Texture { u8 *pixels; int w, h; int wrap; }; typedef struct Point3 Point3; struct Point3 { int x, y, z; }; typedef struct Color Color; struct Color { u8 r, g, b, a; }; typedef struct Vertex Vertex; struct Vertex { i32 x, y, z; float q; // 1/z u8 r, g, b, a; u8 f; // fog float s, t; }; Canvas *makecanvas(int w, int h); Texture *maketexture(int w, int h); void putpixel(Canvas *canvas, Point3 p, Color c); void clearcanvas(Canvas *canvas); void drawTriangle(Canvas *canvas, Vertex p1, Vertex p2, Vertex p3); // not good void drawRect(Canvas *canvas, Point3 p1, Point3 p2, Color c); void drawLine(Canvas *canvas, Point3 p1, Point3 p2, Color c); //#define trace(...) printf(__VA_ARGS__) #define trace(...) int clamp(int x); /* * Render States */ enum TextureWrap { WRAP_REPEAT, WRAP_CLAMP, WRAP_BORDER, }; enum TextureFunction { TFUNC_MODULATE, TFUNC_DECAL, TFUNC_HIGHLIGHT, TFUNC_HIGHLIGHT2, }; enum AlphaTestFunc { ALPHATEST_NEVER, ALPHATEST_ALWAYS, ALPHATEST_LESS, ALPHATEST_LEQUAL, ALPHATEST_EQUAL, ALPHATEST_GEQUAL, ALPHATEST_GREATER, ALPHATEST_NOTEQUAL, }; enum AlphaTestFail { ALPHAFAIL_KEEP, ALPHAFAIL_FB_ONLY, ALPHAFAIL_ZB_ONLY, }; enum DepthTestFunc { DEPTHTEST_NEVER, DEPTHTEST_ALWAYS, DEPTHTEST_GEQUAL, DEPTHTEST_GREATER, }; // The blend equation is // out = ((A - B) * C >> 7) + D // A, B and D select the color, C the alpha value enum AlphaBlendOp { ALPHABLEND_SRC, ALPHABLEND_DST, ALPHABLEND_ZERO, ALPHABLEND_FIX = ALPHABLEND_ZERO, }; extern int srScissorX0, srScissorX1; extern int srScissorY0, srScissorY1; extern int srDepthTestEnable; extern int srDepthTestFunction; extern int srWriteZ; extern int srAlphaTestEnable; extern int srAlphaTestFunction; extern int srAlphaTestReference; extern int srAlphaTestFail; extern int srAlphaBlendEnable; extern int srAlphaBlendA; extern int srAlphaBlendB; extern int srAlphaBlendC; extern int srAlphaBlendD; extern int srAlphaBlendFix; extern int srTexEnable; extern Texture *srTexture; extern int srWrapU; extern int srWrapV; extern Color srBorder; extern int srTexUseAlpha; extern int srTexFunc; extern int srFogEnable; extern Color srFogCol; // end header #define CEIL(p) (((p)+15) >> 4) // render states int srScissorX0, srScissorX1; int srScissorY0, srScissorY1; int srDepthTestEnable = 1; int srDepthTestFunction = DEPTHTEST_GEQUAL; int srWriteZ = 1; int srAlphaTestEnable = 1; int srAlphaTestFunction = ALPHATEST_ALWAYS; int srAlphaTestReference; int srAlphaTestFail = ALPHAFAIL_FB_ONLY; int srAlphaBlendEnable = 1; int srAlphaBlendA = ALPHABLEND_SRC; int srAlphaBlendB = ALPHABLEND_DST; int srAlphaBlendC = ALPHABLEND_SRC; int srAlphaBlendD = ALPHABLEND_DST; int srAlphaBlendFix = 0x80; int srTexEnable = 0; Texture *srTexture; int srWrapU = WRAP_REPEAT; int srWrapV = WRAP_REPEAT; Color srBorder = { 255, 0, 0, 255 }; int srTexUseAlpha = 1; int srTexFunc = TFUNC_MODULATE; int srFogEnable = 0; Color srFogCol = { 0, 0, 0, 0 }; int clamp(int x) { if(x < 0) return 0; if(x > 255) return 255; return x; } Canvas* makecanvas(int w, int h) { Canvas *canv; canv = (Canvas*)malloc(sizeof(*canv) + w*h*(4+4)); canv->w = w; canv->h = h; canv->fb = ((u8*)canv + sizeof(*canv)); canv->zbuf = (u32*)(canv->fb + w*h*4); return canv; } Texture* maketexture(int w, int h) { Texture *t; t = (Texture*)malloc(sizeof(*t) + w*h*4); t->w = w; t->h = h; t->pixels = (u8*)t + sizeof(*t); t->wrap = 0x11; // wrap u and v return t; } void clearcanvas(Canvas *canvas) { memset(canvas->fb, 0, canvas->w*canvas->h*4); memset(canvas->zbuf, 0, canvas->w*canvas->h*4); } void writefb(Canvas *canvas, int x, int y, Color c) { u8 *px = &canvas->fb[(y*canvas->w + x)*4]; u32 *z = &canvas->zbuf[y*canvas->w + x]; px[3] = c.r; px[2] = c.g; px[1] = c.b; px[0] = c.a; } void putpixel(Canvas *canvas, Point3 p, Color c) { // scissor test if(p.x < srScissorX0 || p.x > srScissorX1 || p.y < srScissorY0 || p.y > srScissorY1) return; u8 *px = &canvas->fb[(p.y*canvas->w + p.x)*4]; u32 *z = &canvas->zbuf[p.y*canvas->w + p.x]; int fbwrite = 1; int zbwrite = srWriteZ; // alpha test if(srAlphaTestEnable){ int fail; switch(srAlphaTestFunction){ case ALPHATEST_NEVER: fail = 1; break; case ALPHATEST_ALWAYS: fail = 0; break; case ALPHATEST_LESS: fail = c.a >= srAlphaTestReference; break; case ALPHATEST_LEQUAL: fail = c.a > srAlphaTestReference; break; case ALPHATEST_EQUAL: fail = c.a != srAlphaTestReference; break; case ALPHATEST_GEQUAL: fail = c.a < srAlphaTestReference; break; case ALPHATEST_GREATER: fail = c.a <= srAlphaTestReference; break; case ALPHATEST_NOTEQUAL: fail = c.a == srAlphaTestReference; break; } if(fail){ switch(srAlphaTestFail){ case ALPHAFAIL_KEEP: return; case ALPHAFAIL_FB_ONLY: zbwrite = 0; break; case ALPHAFAIL_ZB_ONLY: fbwrite = 0; } } } // ztest if(srDepthTestEnable){ switch(srDepthTestFunction){ case DEPTHTEST_NEVER: return; case DEPTHTEST_ALWAYS: break; case DEPTHTEST_GEQUAL: if(p.z < *z) return; break; case DEPTHTEST_GREATER: if(p.z <= *z) return; break; } } Color d = { px[3], px[2], px[1], px[0] }; // blend if(srAlphaBlendEnable){ int ar, ag, ab; int br, bg, bb; int dr, dg, db; int ca; switch(srAlphaBlendA){ case ALPHABLEND_SRC: ar = c.r; ag = c.g; ab = c.b; break; case ALPHABLEND_DST: ar = d.r; ag = d.g; ab = d.b; break; case ALPHABLEND_ZERO: ar = 0; ag = 0; ab = 0; break; default: assert(0); } switch(srAlphaBlendB){ case ALPHABLEND_SRC: br = c.r; bg = c.g; bb = c.b; break; case ALPHABLEND_DST: br = d.r; bg = d.g; bb = d.b; break; case ALPHABLEND_ZERO: br = 0; bg = 0; bb = 0; break; default: assert(0); } switch(srAlphaBlendC){ case ALPHABLEND_SRC: ca = c.a; break; case ALPHABLEND_DST: ca = d.a; break; case ALPHABLEND_FIX: ca = srAlphaBlendFix; break; default: assert(0); } switch(srAlphaBlendD){ case ALPHABLEND_SRC: dr = c.r; dg = c.g; db = c.b; break; case ALPHABLEND_DST: dr = d.r; dg = d.g; db = d.b; break; case ALPHABLEND_ZERO: dr = 0; dg = 0; db = 0; break; default: assert(0); } int r, g, b; r = ((ar - br) * ca >> 7) + dr; g = ((ag - bg) * ca >> 7) + dg; b = ((ab - bb) * ca >> 7) + db; c.r = clamp(r); c.g = clamp(g); c.b = clamp(b); } if(fbwrite) writefb(canvas, p.x, p.y, c); if(zbwrite) *z = p.z; } Color sampletex_nearest(int u, int v) { Texture *tex = srTexture; const int usize = tex->w; const int vsize = tex->h; int iu = u >> 4; int iv = v >> 4; switch(srWrapU){ case WRAP_REPEAT: iu %= usize; break; case WRAP_CLAMP: if(iu < 0) iu = 0; if(iu >= usize) iu = usize-1; break; case WRAP_BORDER: if(iu < 0 || iu >= usize) return srBorder; } switch(srWrapV){ case WRAP_REPEAT: iv %= vsize; break; case WRAP_CLAMP: if(iv < 0) iv = 0; if(iv >= vsize) iv = vsize-1; break; case WRAP_BORDER: if(iv < 0 || iv >= vsize) return srBorder; } u8 *cp = &tex->pixels[(iv*tex->w + iu)*4]; Color c = { cp[0], cp[1], cp[2], cp[3] }; return c; } // t is texture, f is fragment Color texfunc(Color t, Color f) { int r, g, b, a; switch(srTexFunc){ case TFUNC_MODULATE: r = t.r * f.r >> 7; g = t.g * f.g >> 7; b = t.b * f.b >> 7; a = srTexUseAlpha ? t.a * f.a >> 7 : f.a; break; case TFUNC_DECAL: r = t.r; g = t.g; b = t.b; a = srTexUseAlpha ? t.a : f.a; break; case TFUNC_HIGHLIGHT: r = (t.r * f.r >> 7) + f.a; g = (t.g * f.g >> 7) + f.a; b = (t.b * f.b >> 7) + f.a; a = srTexUseAlpha ? t.a + f.a : f.a; break; case TFUNC_HIGHLIGHT2: r = (t.r * f.r >> 7) + f.a; g = (t.g * f.g >> 7) + f.a; b = (t.b * f.b >> 7) + f.a; a = srTexUseAlpha ? t.a : f.a; break; } Color v; v.r = clamp(r); v.g = clamp(g); v.b = clamp(b); v.a = clamp(a); return v; } Point3 mkpnt(int x, int y, int z) { Point3 p = { x, y, z}; return p; } void drawRect(Canvas *canvas, Point3 p1, Point3 p2, Color c) { int x, y; for(y = p1.y; y <= p2.y; y++) for(x = p1.x; x <= p2.x; x++) putpixel(canvas, mkpnt(x, y, 0), c); } void drawLine(Canvas *canvas, Point3 p1, Point3 p2, Color c) { int dx, dy; int incx, incy; int e; int x, y; dx = abs(p2.x-p1.x); incx = p2.x > p1.x ? 1 : -1; dy = abs(p2.y-p1.y); incy = p2.y > p1.y ? 1 : -1; e = 0; if(dx == 0){ for(y = p1.y; y != p2.y; y += incy) putpixel(canvas, mkpnt(p1.x, y, 0), c); }else if(dx > dy){ y = p1.y; for(x = p1.x; x != p2.x; x += incx){ putpixel(canvas, mkpnt(x, y, 0), c); e += dy; if(2*e >= dx){ e -= dx; y += incy; } } }else{ x = p1.x; for(y = p1.y; y != p2.y; y += incy){ putpixel(canvas, mkpnt(x, y, 0), c); e += dx; if(2*e >= dy){ e -= dy; x += incx; } } } } /* attibutes we want to interpolate: R G B A U V / S T Q X Y Z F */ struct TriAttribs { i64 z; i32 r, g, b, a; i32 f; float s, t; float q; }; static void add1(struct TriAttribs *a, struct TriAttribs *b) { a->z += b->z; a->r += b->r; a->g += b->g; a->b += b->b; a->a += b->a; a->f += b->f; a->s += b->s; a->t += b->t; a->q += b->q; } static void sub1(struct TriAttribs *a, struct TriAttribs *b) { a->z -= b->z; a->r -= b->r; a->g -= b->g; a->b -= b->b; a->a -= b->a; a->f -= b->f; a->s -= b->s; a->t -= b->t; a->q -= b->q; } static void guard(struct TriAttribs *a) { if(a->z < 0) a->z = 0; else if(a->z > 0x3FFFFFFFC000LL) a->z = 0x3FFFFFFFC000LL; if(a->r < 0) a->r = 0; else if(a->r > 0xFF000) a->r = 0xFF000; if(a->g < 0) a->g = 0; else if(a->g > 0xFF000) a->g = 0xFF000; if(a->b < 0) a->b = 0; else if(a->b > 0xFF000) a->b = 0xFF000; if(a->a < 0) a->a = 0; else if(a->a > 0xFF000) a->a = 0xFF000; if(a->f < 0) a->f = 0; else if(a->f > 0xFF000) a->f = 0xFF000; } struct RasTri { int x, y; int ymid, yend; int right; int e[2], dx[3], dy[3]; struct TriAttribs gx, gy, v, s; }; static int triangleSetup(struct RasTri *tri, Vertex v1, Vertex v2, Vertex v3) { int dx1, dx2, dx3; int dy1, dy2, dy3; dy1 = v3.y - v1.y; // long edge if(dy1 == 0) return 1; dx1 = v3.x - v1.x; dx2 = v2.x - v1.x; // first small edge dy2 = v2.y - v1.y; dx3 = v3.x - v2.x; // second small edge dy3 = v3.y - v2.y; // this is twice the triangle area const int area = dx2*dy1 - dx1*dy2; if(area == 0) return 1; // figure out if 0 or 1 is the right edge tri->right = area < 0; /* The gradients are to step whole pixels, * so they are pre-multiplied by 16. */ float denom = 16.0f/area; // gradients x #define GX(p) ((v2.p - v1.p)*dy1 - (v3.p - v1.p)*dy2) tri->gx.z = GX(z)*denom * 16384; tri->gx.r = GX(r)*denom * 4096; tri->gx.g = GX(g)*denom * 4096; tri->gx.b = GX(b)*denom * 4096; tri->gx.a = GX(a)*denom * 4096; tri->gx.f = GX(f)*denom * 4096; tri->gx.s = GX(s)*denom; tri->gx.t = GX(t)*denom; tri->gx.q = GX(q)*denom; // gradients y denom = -denom; #define GY(p) ((v2.p - v1.p)*dx1 - (v3.p - v1.p)*dx2) tri->gy.z = GY(z)*denom * 16384; tri->gy.r = GY(r)*denom * 4096; tri->gy.g = GY(g)*denom * 4096; tri->gy.b = GY(b)*denom * 4096; tri->gy.a = GY(a)*denom * 4096; tri->gy.f = GY(f)*denom * 4096; tri->gy.s = GY(s)*denom; tri->gy.t = GY(t)*denom; tri->gy.q = GY(q)*denom; tri->ymid = CEIL(v2.y); tri->yend = CEIL(v3.y); tri->y = CEIL(v1.y); tri->x = CEIL(v1.x); tri->dy[0] = dy2<<4; // upper edge tri->dy[1] = dy1<<4; // lower edge tri->dy[2] = dy3<<4; // long edge tri->dx[0] = dx2<<4; tri->dx[1] = dx1<<4; tri->dx[2] = dx3<<4; // prestep to land on pixel center int stepx = v1.x - (tri->x<<4); int stepy = v1.y - (tri->y<<4); tri->e[0] = (-stepy*tri->dx[0] + stepx*tri->dy[0]) >> 4; tri->e[1] = (-stepy*tri->dx[1] + stepx*tri->dy[1]) >> 4; // attributes along interpolated edge // why is this cast needed? (mingw) tri->v.z = (i64)v1.z*16384 - (stepy*tri->gy.z + stepx*tri->gx.z)/16; tri->v.r = v1.r*4096 - (stepy*tri->gy.r + stepx*tri->gx.r)/16; tri->v.g = v1.g*4096 - (stepy*tri->gy.g + stepx*tri->gx.g)/16; tri->v.b = v1.b*4096 - (stepy*tri->gy.b + stepx*tri->gx.b)/16; tri->v.a = v1.a*4096 - (stepy*tri->gy.a + stepx*tri->gx.a)/16; tri->v.f = v1.f*4096 - (stepy*tri->gy.f + stepx*tri->gx.f)/16; tri->v.s = v1.s - (stepy*tri->gy.s + stepx*tri->gx.s)/16.0f; tri->v.t = v1.t - (stepy*tri->gy.t + stepx*tri->gx.t)/16.0f; tri->v.q = v1.q - (stepy*tri->gy.q + stepx*tri->gx.q)/16.0f; return 0; } void drawTriangle(Canvas *canvas, Vertex v1, Vertex v2, Vertex v3) { Color c; struct RasTri tri; int stepx, stepy; // Sort such that we have from top to bottom v1,v2,v3 if(v2.y < v1.y){ Vertex tmp = v1; v1 = v2; v2 = tmp; } if(v3.y < v1.y){ Vertex tmp = v1; v1 = v3; v3 = tmp; } if(v3.y < v2.y){ Vertex tmp = v2; v2 = v3; v3 = tmp; } if(triangleSetup(&tri, v1, v2, v3)) return; // Current scanline start and end int xn[2] = { tri.x, tri.x }; int a = !tri.right; // left edge int b = tri.right; // right edge // If upper triangle has no height, only do the lower part if(tri.dy[0] == 0) goto secondtri; while(tri.y < tri.yend){ /* TODO: is this the righ way to step the edges? */ /* Step x and interpolated value down left edge */ while(tri.e[a] <= -tri.dy[a]){ xn[a]--; tri.e[a] += tri.dy[a]; sub1(&tri.v, &tri.gx); } while(tri.e[a] > 0){ xn[a]++; tri.e[a] -= tri.dy[a]; add1(&tri.v, &tri.gx); } /* Step x down right edge */ while(tri.e[b] <= -tri.dy[b]){ xn[b]--; tri.e[b] += tri.dy[b]; } while(tri.e[b] > 0){ xn[b]++; tri.e[b] -= tri.dy[b]; } // When we reach the mid vertex, change state and jump to start of loop again // TODO: this is a bit ugly in here...can we fix it? if(tri.y == tri.ymid){ secondtri: tri.dx[0] = tri.dx[2]; tri.dy[0] = tri.dy[2]; // Either the while prevents this or we returned early because dy1 == 0 assert(tri.dy[0] != 0); stepx = v2.x - (xn[0]<<4); stepy = v2.y - (tri.y<<4); tri.e[0] = (-stepy*tri.dx[0] + stepx*tri.dy[0]) >> 4; tri.ymid = -1; // so we don't do this again continue; } /* Rasterize one line */ tri.s = tri.v; for(tri.x = xn[a]; tri.x < xn[b]; tri.x++){ guard(&tri.s); c.r = tri.s.r >> 12; c.g = tri.s.g >> 12; c.b = tri.s.b >> 12; c.a = tri.s.a >> 12; if(srTexEnable && srTexture){ float w = 1.0f/tri.s.q; float s = tri.s.s * w; float t = tri.s.t * w; int u = s * srTexture->w * 16; int v = t * srTexture->h * 16; Color texc = sampletex_nearest(u, v); c = texfunc(texc, c); } if(srFogEnable){ const int f = tri.s.f >> 12; c.r = (f*c.r >> 8) + ((255 - f)*srFogCol.r >> 8); c.g = (f*c.g >> 8) + ((255 - f)*srFogCol.g >> 8); c.b = (f*c.b >> 8) + ((255 - f)*srFogCol.b >> 8); } putpixel(canvas, mkpnt(tri.x, tri.y, tri.s.z>>14), c); add1(&tri.s, &tri.gx); } /* Step in y */ tri.y++; tri.e[a] += tri.dx[a]; tri.e[b] += tri.dx[b]; add1(&tri.v, &tri.gy); } } Canvas *canvas; } using namespace rw; void rastest_renderTriangles(RWDEVICE::Im2DVertex *scrverts, int32 numVerts, uint16 *indices, int32 numTris) { int i; RGBA col; rs::Vertex v[3]; RWDEVICE::Im2DVertex *iv; rs::srDepthTestEnable = 1; rs::srAlphaTestEnable = 0; rs::srTexEnable = 0; rs::srAlphaBlendEnable = 0; while(numTris--){ for(i = 0; i < 3; i++){ iv = &scrverts[indices[i]]; v[i].x = iv->x * 16.0f; v[i].y = iv->y * 16.0f; v[i].z = 16777216*(1.0f-iv->z); v[i].q = iv->w; col = iv->getColor(); v[i].r = col.red; v[i].g = col.green; v[i].b = col.blue; v[i].a = col.alpha; v[i].f = 0; v[i].s = iv->u*iv->w; v[i].t = iv->v*iv->w; } drawTriangle(rs::canvas, v[0], v[1], v[2]); indices += 3; } } extern rw::Raster *testras; void beginSoftras(void) { Camera *cam = (Camera*)engine->currentCamera; if(rs::canvas == nil || cam->frameBuffer->width != rs::canvas->w || cam->frameBuffer->height != rs::canvas->h){ rs::canvas = rs::makecanvas(cam->frameBuffer->width, cam->frameBuffer->height); testras = rw::Raster::create(rs::canvas->w, rs::canvas->h, 32, rw::Raster::C8888); } clearcanvas(rs::canvas); rs::srScissorX0 = 0; rs::srScissorX1 = rs::canvas->w-1; rs::srScissorY0 = 0; rs::srScissorY1 = rs::canvas->h-1; } void endSoftras(void) { int i; uint8 *dst = testras->lock(0, Raster::LOCKWRITE|Raster::LOCKNOFETCH); uint8 *src = rs::canvas->fb; for(i = 0; i < rs::canvas->w*rs::canvas->h; i++){ dst[0] = src[1]; dst[1] = src[2]; dst[2] = src[3]; dst[3] = src[0]; dst += 4; src += 4; } // abgr in canvas // bgra in raster testras->unlock(0); } /* typedef struct PixVert PixVert; struct PixVert { float x, y, z, q; int r, g, b, a; float u, v; }; #include "test.inc" void drawtest(void) { int i, j; rs::Vertex v[3]; rs::srDepthTestEnable = 1; rs::srAlphaTestEnable = 0; rs::srTexEnable = 0; rs::srAlphaBlendEnable = 0; for(i = 0; i < nelem(verts); i += 3){ for(j = 0; j < 3; j++){ v[j].x = verts[i+j].x * 16.0f; v[j].y = verts[i+j].y * 16.0f; v[j].z = 16777216*(1.0f - verts[i+j].z); v[j].q = verts[i+j].q; v[j].r = verts[i+j].r; v[j].g = verts[i+j].g; v[j].b = verts[i+j].b; v[j].a = verts[i+j].a; v[j].f = 0; v[j].s = verts[i+j].u*v[j].q; v[j].t = verts[i+j].v*v[j].q; } drawTriangle(rs::canvas, v[0], v[1], v[2]); } //exit(0); } */