mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-01-22 09:01:57 -05:00
Add a broken software renderer backend
This commit is contained in:
parent
df9eb0c4bf
commit
029b6d80de
1 changed files with 488 additions and 0 deletions
488
src/Graphics_SoftGPU.c
Normal file
488
src/Graphics_SoftGPU.c
Normal file
|
@ -0,0 +1,488 @@
|
|||
#include "Core.h"
|
||||
#if defined CC_BUILD_SOFTGPU
|
||||
#include "_GraphicsBase.h"
|
||||
#include "Errors.h"
|
||||
#include "Window.h"
|
||||
|
||||
static cc_bool faceCulling;
|
||||
static int width, height;
|
||||
static struct Bitmap fb_bmp;
|
||||
static float vp_hwidth, vp_hheight;
|
||||
static int sc_maxX, sc_maxY;
|
||||
|
||||
static cc_bool alphaBlending;
|
||||
static cc_bool alphaTest;
|
||||
|
||||
static PackedCol* colorBuffer;
|
||||
static PackedCol clearColor;
|
||||
static cc_bool colWrite = true;
|
||||
|
||||
static float* depthBuffer;
|
||||
static void* gfx_vertices;
|
||||
static cc_bool depthTest = true;
|
||||
static cc_bool depthWrite = true;
|
||||
static GfxResourceID white_square;
|
||||
|
||||
void Gfx_RestoreState(void) {
|
||||
InitDefaultResources();
|
||||
|
||||
// 1x1 dummy white texture
|
||||
struct Bitmap bmp;
|
||||
BitmapCol pixels[1] = { BITMAPCOLOR_WHITE };
|
||||
Bitmap_Init(bmp, 1, 1, pixels);
|
||||
white_square = Gfx_CreateTexture(&bmp, 0, false);
|
||||
}
|
||||
|
||||
void Gfx_FreeState(void) {
|
||||
FreeDefaultResources();
|
||||
Gfx_DeleteTexture(&white_square);
|
||||
}
|
||||
|
||||
void Gfx_Create(void) {
|
||||
Gfx.MaxTexWidth = 4096;
|
||||
Gfx.MaxTexHeight = 4096;
|
||||
Gfx.Created = true;
|
||||
|
||||
Gfx_RestoreState();
|
||||
}
|
||||
|
||||
static void DestroyBuffers(void) {
|
||||
Window_FreeFramebuffer(&fb_bmp);
|
||||
Mem_Free(depthBuffer);
|
||||
depthBuffer = NULL;
|
||||
}
|
||||
|
||||
void Gfx_Free(void) {
|
||||
Gfx_FreeState();
|
||||
DestroyBuffers();
|
||||
}
|
||||
|
||||
|
||||
typedef struct CCTexture {
|
||||
int width, height;
|
||||
BitmapCol pixels[];
|
||||
} CCTexture;
|
||||
|
||||
static CCTexture* curTexture;
|
||||
static BitmapCol* curTexPixels;
|
||||
static int curTexWidth, curTexHeight;
|
||||
|
||||
void Gfx_BindTexture(GfxResourceID texId) {
|
||||
if (!texId) texId = white_square;
|
||||
CCTexture* tex = texId;
|
||||
|
||||
curTexture = tex;
|
||||
curTexPixels = tex->pixels;
|
||||
curTexWidth = tex->width;
|
||||
curTexHeight = tex->height;
|
||||
}
|
||||
|
||||
void Gfx_DeleteTexture(GfxResourceID* texId) {
|
||||
GfxResourceID data = *texId;
|
||||
if (data) Mem_Free(data);
|
||||
*texId = NULL;
|
||||
}
|
||||
|
||||
static GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, cc_uint8 flags, cc_bool mipmaps) {
|
||||
int size = bmp->width * bmp->height * 4;
|
||||
CCTexture* tex = (CCTexture*)Mem_Alloc(2 + bmp->width * bmp->height, 4, "Texture");
|
||||
|
||||
tex->width = bmp->width;
|
||||
tex->height = bmp->height;
|
||||
Mem_Copy(tex->pixels, bmp->scan0, size);
|
||||
return tex;
|
||||
}
|
||||
|
||||
void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
|
||||
CCTexture* tex = (CCTexture*)texId;
|
||||
cc_uint32* dst = (tex->pixels + x) + y * tex->width;
|
||||
CopyTextureData(dst, tex->width * 4, part, rowWidth << 2);
|
||||
}
|
||||
|
||||
void Gfx_UpdateTexturePart(GfxResourceID texId, int x, int y, struct Bitmap* part, cc_bool mipmaps) {
|
||||
Gfx_UpdateTexture(texId, x, y, part, part->width, mipmaps);
|
||||
}
|
||||
|
||||
void Gfx_SetTexturing(cc_bool enabled) { }
|
||||
void Gfx_EnableMipmaps(void) { }
|
||||
void Gfx_DisableMipmaps(void) { }
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*------------------------------------------------------State management---------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
void Gfx_SetFog(cc_bool enabled) { }
|
||||
void Gfx_SetFogCol(PackedCol col) { }
|
||||
void Gfx_SetFogDensity(float value) { }
|
||||
void Gfx_SetFogEnd(float value) { }
|
||||
void Gfx_SetFogMode(FogFunc func) { }
|
||||
|
||||
void Gfx_SetFaceCulling(cc_bool enabled) {
|
||||
faceCulling = enabled;
|
||||
}
|
||||
|
||||
void Gfx_SetAlphaTest(cc_bool enabled) {
|
||||
alphaTest = enabled;
|
||||
}
|
||||
|
||||
void Gfx_SetAlphaBlending(cc_bool enabled) {
|
||||
alphaBlending = enabled;
|
||||
}
|
||||
|
||||
void Gfx_SetAlphaArgBlend(cc_bool enabled) { }
|
||||
|
||||
void Gfx_Clear(void) {
|
||||
int i, size = width * height;
|
||||
|
||||
for (i = 0; i < size; i++) colorBuffer[i] = clearColor;
|
||||
for (i = 0; i < size; i++) depthBuffer[i] = 1.0f;
|
||||
}
|
||||
|
||||
void Gfx_ClearCol(PackedCol color) {
|
||||
int R = PackedCol_R(color);
|
||||
int G = PackedCol_G(color);
|
||||
int B = PackedCol_B(color);
|
||||
int A = PackedCol_A(color);
|
||||
|
||||
clearColor = BitmapCol_Make(R, G, B, A);
|
||||
}
|
||||
|
||||
void Gfx_SetDepthTest(cc_bool enabled) {
|
||||
depthTest = enabled;
|
||||
}
|
||||
|
||||
void Gfx_SetDepthWrite(cc_bool enabled) {
|
||||
depthWrite = enabled;
|
||||
}
|
||||
|
||||
void Gfx_SetColWriteMask(cc_bool r, cc_bool g, cc_bool b, cc_bool a) { }
|
||||
|
||||
void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
|
||||
colWrite = !depthOnly;
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-------------------------------------------------------Index buffers-----------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
|
||||
return (void*)1;
|
||||
}
|
||||
|
||||
void Gfx_BindIb(GfxResourceID ib) { }
|
||||
void Gfx_DeleteIb(GfxResourceID* ib) { }
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*-------------------------------------------------------Vertex buffers----------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static GfxResourceID Gfx_AllocStaticVb(VertexFormat fmt, int count) {
|
||||
return Mem_TryAlloc(count, strideSizes[fmt]);
|
||||
}
|
||||
|
||||
void Gfx_BindVb(GfxResourceID vb) { gfx_vertices = vb; }
|
||||
|
||||
void Gfx_DeleteVb(GfxResourceID* vb) {
|
||||
GfxResourceID data = *vb;
|
||||
if (data) Mem_Free(data);
|
||||
*vb = 0;
|
||||
}
|
||||
|
||||
void* Gfx_LockVb(GfxResourceID vb, VertexFormat fmt, int count) {
|
||||
return vb;
|
||||
}
|
||||
|
||||
void Gfx_UnlockVb(GfxResourceID vb) {
|
||||
gfx_vertices = vb;
|
||||
}
|
||||
|
||||
|
||||
static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices) {
|
||||
return Mem_TryAlloc(maxVertices, strideSizes[fmt]);
|
||||
}
|
||||
|
||||
void Gfx_BindDynamicVb(GfxResourceID vb) { Gfx_BindVb(vb); }
|
||||
|
||||
void* Gfx_LockDynamicVb(GfxResourceID vb, VertexFormat fmt, int count) {
|
||||
return vb;
|
||||
}
|
||||
|
||||
void Gfx_UnlockDynamicVb(GfxResourceID vb) {
|
||||
gfx_vertices = vb;
|
||||
}
|
||||
|
||||
void Gfx_DeleteDynamicVb(GfxResourceID* vb) { Gfx_DeleteVb(vb); }
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*---------------------------------------------------------Matrices--------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
static float texOffsetX, texOffsetY;
|
||||
static struct Matrix _view, _proj, mvp;
|
||||
|
||||
void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
|
||||
if (type == MATRIX_VIEW) _view = *matrix;
|
||||
if (type == MATRIX_PROJECTION) _proj = *matrix;
|
||||
|
||||
Matrix_Mul(&mvp, &_view, &_proj);
|
||||
}
|
||||
|
||||
void Gfx_LoadIdentityMatrix(MatrixType type) {
|
||||
Gfx_LoadMatrix(type, &Matrix_Identity);
|
||||
}
|
||||
|
||||
void Gfx_EnableTextureOffset(float x, float y) {
|
||||
texOffsetX = x;
|
||||
texOffsetY = y;
|
||||
}
|
||||
|
||||
void Gfx_DisableTextureOffset(void) {
|
||||
texOffsetX = 0;
|
||||
texOffsetY = 0;
|
||||
}
|
||||
|
||||
void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
|
||||
/* Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glortho */
|
||||
/* The simplified calculation below uses: L = 0, R = width, T = 0, B = height */
|
||||
*matrix = Matrix_Identity;
|
||||
|
||||
matrix->row1.X = 2.0f / width;
|
||||
matrix->row2.Y = -2.0f / height;
|
||||
matrix->row3.Z = -2.0f / (zFar - zNear);
|
||||
|
||||
matrix->row4.X = -1.0f;
|
||||
matrix->row4.Y = 1.0f;
|
||||
matrix->row4.Z = -(zFar + zNear) / (zFar - zNear);
|
||||
}
|
||||
|
||||
static double Cotangent(double x) { return Math_Cos(x) / Math_Sin(x); }
|
||||
void Gfx_CalcPerspectiveMatrix(struct Matrix* matrix, float fov, float aspect, float zFar) {
|
||||
float zNear = 0.1f;
|
||||
float c = (float)Cotangent(0.5f * fov);
|
||||
|
||||
/* Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glfrustum */
|
||||
/* For pos FOV based perspective matrix, left/right/top/bottom are calculated as: */
|
||||
/* left = -c * aspect, right = c * aspect, bottom = -c, top = c */
|
||||
/* Calculations are simplified because of left/right and top/bottom symmetry */
|
||||
*matrix = Matrix_Identity;
|
||||
|
||||
matrix->row1.X = c / aspect;
|
||||
matrix->row2.Y = c;
|
||||
matrix->row3.Z = -(zFar + zNear) / (zFar - zNear);
|
||||
matrix->row3.W = -1.0f;
|
||||
matrix->row4.Z = -(2.0f * zFar * zNear) / (zFar - zNear);
|
||||
matrix->row4.W = 0.0f;
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*---------------------------------------------------------Rendering-------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
typedef struct Vector4 { float X, Y, Z, W; } Vector4;
|
||||
typedef struct Vector3 { float X, Y, Z; } Vector3;
|
||||
typedef struct Vector2 { float X, Y; } Vector2;
|
||||
|
||||
static void TransformVertex(int index, Vector4* frag, Vector2* uv, PackedCol* color) {
|
||||
// TODO: avoid the multiply, just add down in DrawTriangles
|
||||
char* ptr = (char*)gfx_vertices + index * gfx_stride;
|
||||
Vector3* pos = (Vector3*)ptr;
|
||||
|
||||
Vector4 coord;
|
||||
coord.X = pos->X * mvp.row1.X + pos->Y * mvp.row2.X + pos->Z * mvp.row3.X + mvp.row4.X;
|
||||
coord.Y = pos->X * mvp.row1.Y + pos->Y * mvp.row2.Y + pos->Z * mvp.row3.Y + mvp.row4.Y;
|
||||
coord.Z = pos->X * mvp.row1.Z + pos->Y * mvp.row2.Z + pos->Z * mvp.row3.Z + mvp.row4.Z;
|
||||
coord.W = pos->X * mvp.row1.W + pos->Y * mvp.row2.W + pos->Z * mvp.row3.W + mvp.row4.W;
|
||||
|
||||
frag->X = vp_hwidth * (1 + coord.X / coord.W);
|
||||
frag->Y = vp_hheight * (1 - coord.Y / coord.W);
|
||||
frag->Z = coord.Z / coord.W;
|
||||
frag->W = 1.0f / coord.W;
|
||||
|
||||
if (gfx_format != VERTEX_FORMAT_TEXTURED) {
|
||||
struct VertexColoured* v = (struct VertexColoured*)ptr;
|
||||
*color = v->Col;
|
||||
} else {
|
||||
struct VertexTextured* v = (struct VertexTextured*)ptr;
|
||||
*color = v->Col;
|
||||
uv->X = v->U + texOffsetX;
|
||||
uv->Y = v->V + texOffsetY;
|
||||
}
|
||||
}
|
||||
|
||||
static int MultiplyColours(PackedCol vColor, BitmapCol tColor) {
|
||||
int a1 = PackedCol_A(vColor), a2 = BitmapCol_A(tColor);
|
||||
int a = ( a1 * a2 ) / 255;
|
||||
int r1 = PackedCol_R(vColor), r2 = BitmapCol_R(tColor);
|
||||
int r = ( r1 * r2 ) / 255;
|
||||
int g1 = PackedCol_G(vColor), g2 = BitmapCol_G(tColor);
|
||||
int g = ( g1 * g2 ) / 255;
|
||||
int b1 = PackedCol_B(vColor), b2 = BitmapCol_B(tColor);
|
||||
int b = ( b1 * b2 ) / 255;
|
||||
return PackedCol_Make(r, g, b, a);
|
||||
}
|
||||
|
||||
static void DrawTriangle(Vector4 frag1, Vector4 frag2, Vector4 frag3,
|
||||
Vector2 uv1, Vector2 uv2, Vector2 uv3, PackedCol color) {
|
||||
int x1 = (int)frag1.X, y1 = (int)frag1.Y;
|
||||
int x2 = (int)frag2.X, y2 = (int)frag2.Y;
|
||||
int x3 = (int)frag3.X, y3 = (int)frag3.Y;
|
||||
int minX = min(x1, min(x2, x3));
|
||||
int minY = min(y1, min(y2, y3));
|
||||
int maxX = max(x1, max(x2, x3));
|
||||
int maxY = max(y1, max(y2, y3));
|
||||
|
||||
// TODO backface culling
|
||||
|
||||
// Reject triangles completely outside
|
||||
if (minX < 0 && maxX < 0 || minX >= width && maxX >= width ) return;
|
||||
if (minY < 0 && maxY < 0 || minY >= height && maxY >= height) return;
|
||||
|
||||
// Perform scissoring
|
||||
//minX = max(minX, 0); maxX = min(maxX, sc_maxX);
|
||||
//minY = max(minY, 0); maxY = min(maxY, sc_maxY);
|
||||
// TODO why doesn't this work
|
||||
|
||||
// NOTE: W in frag variables below is actually 1/W
|
||||
|
||||
float factor = 1.0f / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
if (x < 0 || y < 0 || x >= width || y >= height) return;
|
||||
|
||||
float ic0 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * factor;
|
||||
if (ic0 < 0 || ic0 > 1) continue;
|
||||
float ic1 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * factor;
|
||||
if (ic1 < 0 || ic1 > 1) continue;
|
||||
float ic2 = 1.0f - ic0 - ic1;
|
||||
if (ic2 < 0 || ic2 > 1) continue;
|
||||
|
||||
int index = y * width + x;
|
||||
float w = 1 / (ic0 * frag1.W + ic1 * frag2.W + ic2 * frag3.W);
|
||||
|
||||
if (depthTest && w <= depthBuffer[index]) continue;
|
||||
if (depthWrite) depthBuffer[index] = w;
|
||||
if (!colWrite) continue;
|
||||
|
||||
PackedCol fragColor = color;
|
||||
if (gfx_format == VERTEX_FORMAT_TEXTURED) {
|
||||
float u = (ic0 * uv1.X * frag1.W + ic1 * uv2.X * frag2.W + ic2 * uv3.X * frag3.W) * w;
|
||||
float v = (ic0 * uv1.Y * frag1.W + ic1 * uv2.Y * frag2.W + ic2 * uv3.Y * frag3.W) * w;
|
||||
int texX = (int)(Math_AbsF(u - Math_Floor(u)) * curTexWidth);
|
||||
int texY = (int)(Math_AbsF(v - Math_Floor(v)) * curTexHeight);
|
||||
int texIndex = texY * curTexWidth + texX;
|
||||
|
||||
fragColor = MultiplyColours(fragColor, curTexPixels[texIndex]);
|
||||
}
|
||||
|
||||
int R = PackedCol_R(fragColor);
|
||||
int G = PackedCol_G(fragColor);
|
||||
int B = PackedCol_B(fragColor);
|
||||
int A = PackedCol_A(fragColor);
|
||||
|
||||
if (alphaBlending) {
|
||||
PackedCol dst = colorBuffer[index];
|
||||
int dstR = BitmapCol_R(dst);
|
||||
int dstG = BitmapCol_G(dst);
|
||||
int dstB = BitmapCol_B(dst);
|
||||
|
||||
R = (R * A) / 255 + (dstR * (255 - A)) / 255;
|
||||
G = (G * A) / 255 + (dstG * (255 - A)) / 255;
|
||||
B = (B * A) / 255 + (dstB * (255 - A)) / 255;
|
||||
}
|
||||
if (alphaTest && A < 0x80) continue;
|
||||
|
||||
colorBuffer[index] = BitmapCol_Make(R, G, B, 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawQuads(int startVertex, int verticesCount) {
|
||||
Vector4 frag[4];
|
||||
Vector2 uv[4];
|
||||
PackedCol color[4];
|
||||
int j = startVertex;
|
||||
|
||||
// 4 vertices = 1 quad = 2 triangles
|
||||
for (int i = 0; i < verticesCount / 4; i++, j += 4)
|
||||
{
|
||||
TransformVertex(j + 0, &frag[0], &uv[0], &color[0]);
|
||||
TransformVertex(j + 1, &frag[1], &uv[1], &color[1]);
|
||||
TransformVertex(j + 2, &frag[2], &uv[2], &color[2]);
|
||||
TransformVertex(j + 3, &frag[3], &uv[3], &color[3]);
|
||||
|
||||
DrawTriangle(frag[0], frag[1], frag[2],
|
||||
uv[0], uv[1], uv[2], color[0]);
|
||||
|
||||
DrawTriangle(frag[2], frag[3], frag[0],
|
||||
uv[2], uv[3], uv[0], color[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void Gfx_SetVertexFormat(VertexFormat fmt) {
|
||||
gfx_format = fmt;
|
||||
gfx_stride = strideSizes[fmt];
|
||||
}
|
||||
|
||||
void Gfx_DrawVb_Lines(int verticesCount) { } /* TODO */
|
||||
|
||||
void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex) {
|
||||
DrawQuads(startVertex, verticesCount);
|
||||
}
|
||||
|
||||
void Gfx_DrawVb_IndexedTris(int verticesCount) {
|
||||
DrawQuads(0, verticesCount);
|
||||
}
|
||||
|
||||
void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
|
||||
DrawQuads(startVertex, verticesCount);
|
||||
}
|
||||
|
||||
|
||||
/*########################################################################################################################*
|
||||
*---------------------------------------------------------Other/Misc------------------------------------------------------*
|
||||
*#########################################################################################################################*/
|
||||
cc_result Gfx_TakeScreenshot(struct Stream* output) {
|
||||
return ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
cc_bool Gfx_WarnIfNecessary(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Gfx_BeginFrame(void) { }
|
||||
|
||||
void Gfx_EndFrame(void) {
|
||||
Rect2D r = { 0, 0, width, height };
|
||||
Window_DrawFramebuffer(r);
|
||||
}
|
||||
|
||||
void Gfx_SetFpsLimit(cc_bool vsync, float minFrameMs) {
|
||||
gfx_minFrameMs = minFrameMs;
|
||||
gfx_vsync = vsync;
|
||||
}
|
||||
|
||||
void Gfx_OnWindowResize(void) {
|
||||
if (depthBuffer) DestroyBuffers();
|
||||
|
||||
fb_bmp.width = width = Game.Width;
|
||||
fb_bmp.height = height = Game.Height;
|
||||
|
||||
vp_hwidth = width / 2.0f;
|
||||
vp_hheight = height / 2.0f;
|
||||
|
||||
sc_maxX = width - 1;
|
||||
sc_maxY = height - 1;
|
||||
|
||||
Window_AllocFramebuffer(&fb_bmp);
|
||||
depthBuffer = Mem_Alloc(width * height, 4, "depth buffer");
|
||||
colorBuffer = fb_bmp.scan0;
|
||||
}
|
||||
|
||||
void Gfx_GetApiInfo(cc_string* info) {
|
||||
int pointerSize = sizeof(void*) * 8;
|
||||
String_Format1(info, "-- Using software (%i bit) --\n", &pointerSize);
|
||||
String_Format2(info, "Max texture size: (%i x %i)\n", &Gfx.MaxTexWidth, &Gfx.MaxTexHeight);
|
||||
}
|
||||
|
||||
cc_bool Gfx_TryRestoreContext(void) { return true; }
|
||||
#endif
|
Loading…
Reference in a new issue