From dbb94d0cb99b0435d6701433c3ca5b83a5d4b17d Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Thu, 9 Jun 2022 13:07:51 +0100 Subject: [PATCH 01/10] Move clipping into list submission --- GL/clip.c | 2 +- GL/draw.c | 43 ------- GL/flush.c | 91 +------------ GL/platforms/sh4.c | 314 ++++++++++++++++++++++++++++++++++++++++++--- GL/platforms/sh4.h | 2 + 5 files changed, 302 insertions(+), 150 deletions(-) diff --git a/GL/clip.c b/GL/clip.c index 4573afe..ed051d8 100644 --- a/GL/clip.c +++ b/GL/clip.c @@ -251,7 +251,7 @@ void _glClipTriangleStrip(SubmissionTarget* target, uint8_t fladeShade) { */ #define _VERT_VISIBLE(v) \ - (v->w >= 0 && v->xyz[2] >= -v->w) \ + (v->xyz[2] > -v->w) \ uint8_t visible = ( (_VERT_VISIBLE(v1) ? 4 : 0) | diff --git a/GL/draw.c b/GL/draw.c index ee822d3..1caa2ed 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -933,16 +933,6 @@ static void transform(SubmissionTarget* target) { TransformVertices(vertex, target->count); } -static void clip(SubmissionTarget* target) { - TRACE(); - - /* Perform clipping, generating new vertices as necessary */ - _glClipTriangleStrip(target, _glGetShadeModel() == GL_FLAT); - - /* Reset the count now that we may have added vertices */ - target->count = target->output->vector.size - target->start_offset; -} - static void mat_transform3(const float* xyz, const float* xyzOut, const uint32_t count, const uint32_t inStride, const uint32_t outStride) { const uint8_t* dataIn = (const uint8_t*) xyz; uint8_t* dataOut = (uint8_t*) xyzOut; @@ -1172,39 +1162,6 @@ GL_FORCE_INLINE void submitVertices(GLenum mode, GLsizei first, GLuint count, GL transform(target); } - if(_glIsClippingEnabled()) { -#if DEBUG_CLIPPING - uint32_t i = 0; - fprintf(stderr, "=========\n"); - - for(i = 0; i < target->count; ++i) { - Vertex* v = aligned_vector_at(&target->output->vector, target->start_offset + i); - if(v->flags == 0xe0000000 || v->flags == 0xf0000000) { - fprintf(stderr, "(%f, %f, %f, %f) -> %x\n", v->xyz[0], v->xyz[1], v->xyz[2], v->w, v->flags); - } else { - fprintf(stderr, "%x\n", *((uint32_t*)v)); - } - } -#endif - - clip(target); - - assert(extras.size == target->count); - -#if DEBUG_CLIPPING - fprintf(stderr, "--------\n"); - for(i = 0; i < target->count; ++i) { - Vertex* v = aligned_vector_at(&target->output->vector, target->start_offset + i); - if(v->flags == 0xe0000000 || v->flags == 0xf0000000) { - fprintf(stderr, "(%f, %f, %f, %f) -> %x\n", v->xyz[0], v->xyz[1], v->xyz[2], v->w, v->flags); - } else { - fprintf(stderr, "%x\n", *((uint32_t*)v)); - } - } -#endif - - } - push(_glSubmissionTargetHeader(target), GL_FALSE, target->output, 0); /* diff --git a/GL/flush.c b/GL/flush.c index bd5602a..31f311e 100644 --- a/GL/flush.c +++ b/GL/flush.c @@ -88,108 +88,19 @@ void APIENTRY glKosInit() { glKosInitEx(&config); } -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) - -GL_FORCE_INLINE bool glIsVertex(const float flags) { - return flags == GPU_CMD_VERTEX_EOL || flags == GPU_CMD_VERTEX; -} - - -GL_FORCE_INLINE void glPerspectiveDivideStandard(void* src, uint32_t n) { - TRACE(); - - /* Perform perspective divide on each vertex */ - Vertex* vertex = (Vertex*) src; - PREFETCH(vertex + 1); - - const float h = GetVideoMode()->height; - - while(n--) { - PREFETCH(vertex + 2); - - if(likely(glIsVertex(vertex->flags))) { - const float f = MATH_Fast_Invert(vertex->w); - - /* Convert to NDC and apply viewport */ - vertex->xyz[0] = __builtin_fmaf( - VIEWPORT.hwidth, vertex->xyz[0] * f, VIEWPORT.x_plus_hwidth - ); - - vertex->xyz[1] = h - __builtin_fmaf( - VIEWPORT.hheight, vertex->xyz[1] * f, VIEWPORT.y_plus_hheight - ); - - /* Orthographic projections need to use invZ otherwise we lose - the depth information. As w == 1, and clip-space range is -w to +w - we add 1.0 to the Z to bring it into range. We add a little extra to - avoid a divide by zero. - */ - if(unlikely(vertex->w == 1.0f)) { - vertex->xyz[2] = MATH_Fast_Invert(1.0001f + vertex->xyz[2]); - } else { - vertex->xyz[2] = f; - } - } - - ++vertex; - } -} - -GL_FORCE_INLINE void glPerspectiveDivideFastMode(void* src, uint32_t n) { - TRACE(); - - /* Perform perspective divide on each vertex */ - Vertex* vertex = (Vertex*) src; - - const float h = GetVideoMode()->height; - - while(n--) { - PREFETCH(vertex + 1); - - if(likely(glIsVertex(vertex->flags))) { - const float f = MATH_Fast_Invert(vertex->w); - - /* Convert to NDC and apply viewport */ - vertex->xyz[0] = MATH_fmac( - VIEWPORT.hwidth, vertex->xyz[0] * f, VIEWPORT.x_plus_hwidth - ); - - vertex->xyz[1] = h - MATH_fmac( - VIEWPORT.hheight, vertex->xyz[1] * f, VIEWPORT.y_plus_hheight - ); - - vertex->xyz[2] = f; - } - - ++vertex; - } -} - -GL_FORCE_INLINE void glPerspectiveDivide(void* src, uint32_t n) { -#if FAST_MODE - glPerspectiveDivideFastMode(src, n); -#else - glPerspectiveDivideStandard(src, n); -#endif -} - void APIENTRY glKosSwapBuffers() { TRACE(); SceneBegin(); SceneListBegin(GPU_LIST_OP_POLY); - glPerspectiveDivide(OP_LIST.vector.data, OP_LIST.vector.size); SceneListSubmit(OP_LIST.vector.data, OP_LIST.vector.size); SceneListFinish(); SceneListBegin(GPU_LIST_PT_POLY); - glPerspectiveDivide(PT_LIST.vector.data, PT_LIST.vector.size); SceneListSubmit(PT_LIST.vector.data, PT_LIST.vector.size); SceneListFinish(); SceneListBegin(GPU_LIST_TR_POLY); - glPerspectiveDivide(TR_LIST.vector.data, TR_LIST.vector.size); SceneListSubmit(TR_LIST.vector.data, TR_LIST.vector.size); SceneListFinish(); SceneFinish(); @@ -199,4 +110,4 @@ void APIENTRY glKosSwapBuffers() { aligned_vector_clear(&TR_LIST.vector); _glApplyScissor(true); -} +} \ No newline at end of file diff --git a/GL/platforms/sh4.c b/GL/platforms/sh4.c index 23d8c7a..321108a 100644 --- a/GL/platforms/sh4.c +++ b/GL/platforms/sh4.c @@ -8,6 +8,18 @@ #define PVR_VERTEX_BUF_SIZE 2560 * 256 +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +GL_FORCE_INLINE bool glIsVertex(const float flags) { + return flags == GPU_CMD_VERTEX_EOL || flags == GPU_CMD_VERTEX; +} + +GL_FORCE_INLINE bool glIsLastVertex(const float flags) { + return flags == GPU_CMD_VERTEX_EOL; +} + + void InitGPU(_Bool autosort, _Bool fsaa) { pvr_init_params_t params = { /* Enable opaque and translucent polygons with size 32 and 32 */ @@ -32,25 +44,295 @@ void SceneListBegin(GPUList list) { pvr_list_begin(list); } -void SceneListSubmit(void* src, int n) { - uint32_t *d = (uint32_t*) TA_SQ_ADDR; - uint32_t *s = src; +GL_FORCE_INLINE void _glPerspectiveDivideVertex(Vertex* vertex, const float h) { + const float f = MATH_Fast_Invert(vertex->w); - /* fill/write queues as many times necessary */ - while(n--) { - __asm__("pref @%0" : : "r"(s + 8)); /* prefetch 32 bytes for next loop */ - d[0] = *(s++); - d[1] = *(s++); - d[2] = *(s++); - d[3] = *(s++); - d[4] = *(s++); - d[5] = *(s++); - d[6] = *(s++); - d[7] = *(s++); - __asm__("pref @%0" : : "r"(d)); - d += 8; + /* Convert to NDC and apply viewport */ + vertex->xyz[0] = __builtin_fmaf( + VIEWPORT.hwidth, vertex->xyz[0] * f, VIEWPORT.x_plus_hwidth + ); + + vertex->xyz[1] = h - __builtin_fmaf( + VIEWPORT.hheight, vertex->xyz[1] * f, VIEWPORT.y_plus_hheight + ); + + /* Orthographic projections need to use invZ otherwise we lose + the depth information. As w == 1, and clip-space range is -w to +w + we add 1.0 to the Z to bring it into range. We add a little extra to + avoid a divide by zero. + */ + if(unlikely(vertex->w == 1.0f)) { + vertex->xyz[2] = MATH_Fast_Invert(1.0001f + vertex->xyz[2]); + } else { + vertex->xyz[2] = f; } +} +static uint32_t *d; // SQ target + +GL_FORCE_INLINE void _glSubmitHeaderOrVertex(const Vertex* v) { + uint32_t *s = (uint32_t*) v; + __asm__("pref @%0" : : "r"(s + 8)); /* prefetch 32 bytes for next loop */ + d[0] = *(s++); + d[1] = *(s++); + d[2] = *(s++); + d[3] = *(s++); + d[4] = *(s++); + d[5] = *(s++); + d[6] = *(s++); + d[7] = *(s++); + __asm__("pref @%0" : : "r"(d)); + d += 8; +} + +static struct { + Vertex* v; + int visible; +} triangle[3]; + +static int tri_count = 0; + + +GL_FORCE_INLINE void _glClipEdge(const Vertex* v1, const Vertex* v2, Vertex* vout) { + /* Clipping time! */ + const float d0 = v1->w + v1->xyz[2]; + const float d1 = v2->w + v2->xyz[2]; + + float t = MATH_Fast_Divide(d0, (d0 - d1)); + + vout->xyz[0] = MATH_fmac(v2->xyz[0] - v1->xyz[0], t, v1->xyz[0]); + vout->xyz[1] = MATH_fmac(v2->xyz[1] - v1->xyz[1], t, v1->xyz[1]); + vout->xyz[2] = MATH_fmac(v2->xyz[2] - v1->xyz[2], t, v1->xyz[2]); + vout->w = MATH_fmac(v2->w - v1->w, t, v1->w); + + vout->uv[0] = MATH_fmac(v2->uv[0] - v1->uv[0], t, v1->uv[0]); + vout->uv[1] = MATH_fmac(v2->uv[1] - v1->uv[1], t, v1->uv[1]); + + vout->bgra[0] = 0xFF; + vout->bgra[1] = 0xFF; + vout->bgra[2] = 0xFF; + vout->bgra[3] = 0xFF; +} + +GL_FORCE_INLINE void ClearTriangle() { + tri_count = 0; +} + +GL_FORCE_INLINE void ShiftTriangle() { + tri_count--; + triangle[0] = triangle[1]; + triangle[1] = triangle[2]; + +#ifndef NDEBUG + triangle[2].v = NULL; + triangle[2].visible = false; +#endif +} + +void SceneListSubmit(void* src, int n) { + /* Do everything, everywhere, all at once */ + + /* Prep store queues */ + d = (uint32_t*) TA_SQ_ADDR; + + /* Perform perspective divide on each vertex */ + Vertex* vertex = (Vertex*) src; + + const float h = GetVideoMode()->height; + + tri_count = 0; + + int strip_count = 0; + + for(int i = 0; i < n; ++i) { + PREFETCH(vertex + 1); + + bool is_last_in_strip = glIsLastVertex(vertex->flags); + + /* Wait until we fill the triangle */ + if(tri_count < 3) { + if(likely(glIsVertex(vertex->flags))) { + triangle[tri_count].v = vertex; + triangle[tri_count].visible = vertex->w > 0 && vertex->xyz[2] > -vertex->w; + tri_count++; + strip_count++; + } else { + /* We hit a header */ + tri_count = 0; + strip_count = 0; + _glSubmitHeaderOrVertex(vertex); + } + + if(tri_count < 3) { + ++vertex; + continue; + } + } + + /* If we got here, then triangle contains 3 vertices */ + int visible_mask = triangle[0].visible | (triangle[1].visible << 1) | (triangle[2].visible << 2); + if(visible_mask == 7) { + /* All the vertices are visible! We divide and submit v0, then shift */ + _glPerspectiveDivideVertex(triangle[0].v, h); + _glSubmitHeaderOrVertex(triangle[0].v); + } else if(!visible_mask) { + /* None visible, just shift for the next in the strip */ + } else { + /* Clipping time! + + There are 6 distinct possibilities when clipping a triangle. 3 of them result + in another triangle, 3 of them result in a quadrilateral. + + Assuming you iterate the edges of the triangle in order, and create a new *visible* + vertex when you cross the plane, and discard vertices behind the plane, then the only + difference between the two cases is that the final two vertices that need submitting have + to be reversed. + + Unfortunately we have to copy vertices here, because if we persp-divide a vertex it may + be used in a subsequent triangle in the strip and would end up being double divided. + */ + + Vertex tmp0, tmp1, tmp2, tmp3; + + switch(visible_mask) { + case 1: { + /* 0, 0a, 2a */ + tmp0 = *triangle[0].v; + _glClipEdge(triangle[0].v, triangle[1].v, &tmp1); + _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); + + _glPerspectiveDivideVertex(&tmp0, h); + _glPerspectiveDivideVertex(&tmp1, h); + _glPerspectiveDivideVertex(&tmp2, h); + + tmp0.flags = tmp1.flags = GPU_CMD_VERTEX; + tmp2.flags = GPU_CMD_VERTEX_EOL; + + _glSubmitHeaderOrVertex(&tmp0); + _glSubmitHeaderOrVertex(&tmp1); + _glSubmitHeaderOrVertex(&tmp2); + } break; + case 2: { + /* 0a, 1, 1a */ + _glClipEdge(triangle[0].v, triangle[1].v, &tmp0); + tmp1 = *triangle[1].v; + _glClipEdge(triangle[1].v, triangle[2].v, &tmp2); + + _glPerspectiveDivideVertex(&tmp0, h); + _glPerspectiveDivideVertex(&tmp1, h); + _glPerspectiveDivideVertex(&tmp2, h); + + tmp0.flags = tmp1.flags = GPU_CMD_VERTEX; + tmp2.flags = GPU_CMD_VERTEX_EOL; + + _glSubmitHeaderOrVertex(&tmp0); + _glSubmitHeaderOrVertex(&tmp1); + _glSubmitHeaderOrVertex(&tmp2); + } break; + case 3: { + /* 0, 1, 2a, 1a */ + tmp0 = *triangle[0].v; + tmp1 = *triangle[1].v; + _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); + _glClipEdge(triangle[1].v, triangle[2].v, &tmp3); + + _glPerspectiveDivideVertex(&tmp0, h); + _glPerspectiveDivideVertex(&tmp1, h); + _glPerspectiveDivideVertex(&tmp2, h); + _glPerspectiveDivideVertex(&tmp3, h); + + tmp0.flags = tmp1.flags = tmp2.flags = GPU_CMD_VERTEX; + tmp3.flags = GPU_CMD_VERTEX_EOL; + + _glSubmitHeaderOrVertex(&tmp0); + _glSubmitHeaderOrVertex(&tmp1); + _glSubmitHeaderOrVertex(&tmp2); + _glSubmitHeaderOrVertex(&tmp3); + } break; + case 4: { + /* 1a, 2, 2a */ + _glClipEdge(triangle[1].v, triangle[2].v, &tmp0); + tmp1 = *triangle[2].v; + _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); + + _glPerspectiveDivideVertex(&tmp0, h); + _glPerspectiveDivideVertex(&tmp1, h); + _glPerspectiveDivideVertex(&tmp2, h); + + tmp0.flags = tmp1.flags = GPU_CMD_VERTEX; + tmp2.flags = GPU_CMD_VERTEX_EOL; + + _glSubmitHeaderOrVertex(&tmp0); + _glSubmitHeaderOrVertex(&tmp1); + _glSubmitHeaderOrVertex(&tmp2); + } break; + case 5: { + /* 0, 0a, 2, 1a */ + tmp0 = *triangle[0].v; + _glClipEdge(triangle[0].v, triangle[1].v, &tmp1); + tmp2 = *triangle[2].v; + _glClipEdge(triangle[1].v, triangle[2].v, &tmp3); + + _glPerspectiveDivideVertex(&tmp0, h); + _glPerspectiveDivideVertex(&tmp1, h); + _glPerspectiveDivideVertex(&tmp2, h); + _glPerspectiveDivideVertex(&tmp3, h); + + tmp0.flags = tmp1.flags = tmp2.flags = GPU_CMD_VERTEX; + tmp3.flags = GPU_CMD_VERTEX_EOL; + + _glSubmitHeaderOrVertex(&tmp0); + _glSubmitHeaderOrVertex(&tmp1); + _glSubmitHeaderOrVertex(&tmp2); + _glSubmitHeaderOrVertex(&tmp3); + } break; + case 6: { + /* 0a, 1, 2a, 2 */ + _glClipEdge(triangle[0].v, triangle[1].v, &tmp0); + tmp1 = *triangle[1].v; + _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); + tmp3 = *triangle[2].v; + + _glPerspectiveDivideVertex(&tmp0, h); + _glPerspectiveDivideVertex(&tmp1, h); + _glPerspectiveDivideVertex(&tmp2, h); + _glPerspectiveDivideVertex(&tmp3, h); + + tmp0.flags = tmp1.flags = tmp2.flags = GPU_CMD_VERTEX; + tmp3.flags = GPU_CMD_VERTEX_EOL; + + _glSubmitHeaderOrVertex(&tmp0); + _glSubmitHeaderOrVertex(&tmp1); + _glSubmitHeaderOrVertex(&tmp2); + _glSubmitHeaderOrVertex(&tmp3); + } break; + default: + break; + } + + /* If this was the last in the strip, we don't need to + submit anything else, we just wipe the tri_count */ + if(is_last_in_strip) { + tri_count = 0; + strip_count = 0; + } + } + + /* If this was the last vertex in the strip, we're done with the + strip so we need to wipe out the tri_count */ + ShiftTriangle(); + + if(is_last_in_strip) { + for(int i = 0; i < tri_count; ++i) { + if(triangle[i].visible) { + _glPerspectiveDivideVertex(triangle[i].v, h); + _glSubmitHeaderOrVertex(triangle[i].v); + } + } + ClearTriangle(); + } + ++vertex; + } /* Wait for both store queues to complete */ d = (uint32_t *)0xe0000000; d[0] = d[8] = 0; diff --git a/GL/platforms/sh4.h b/GL/platforms/sh4.h index 9e518f1..c8cedf7 100644 --- a/GL/platforms/sh4.h +++ b/GL/platforms/sh4.h @@ -8,6 +8,8 @@ #include #include "../types.h" +#include "../private.h" + #include "sh4_math.h" #ifndef NDEBUG From 68c6936b25917e5b7bb283cf46bc6396bd2d3047 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 10 Jun 2022 20:26:51 +0100 Subject: [PATCH 02/10] Add nehe10 sample --- CMakeLists.txt | 1 + samples/nehe10/main.c | 317 +++++++++++++++++++++++++++++++ samples/nehe10/romdisk/brick.bmp | Bin 0 -> 49290 bytes samples/nehe10/romdisk/world.txt | 160 ++++++++++++++++ 4 files changed, 478 insertions(+) create mode 100644 samples/nehe10/main.c create mode 100644 samples/nehe10/romdisk/brick.bmp create mode 100644 samples/nehe10/romdisk/world.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 13ca114..32a7d8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,7 @@ gen_sample(nehe06 samples/nehe06/main.c samples/loadbmp.c) gen_sample(nehe06_vq samples/nehe06_vq/main.c) gen_sample(nehe06_4444twid samples/nehe06_4444twid/main.c) gen_sample(nehe08 samples/nehe08/main.c samples/nehe08/pvr-texture.c) +gen_sample(nehe10 samples/nehe10/main.c samples/loadbmp.c) gen_sample(nehe20 samples/nehe20/main.c samples/loadbmp.c) gen_sample(ortho2d samples/ortho2d/main.c) gen_sample(paletted samples/paletted/main.c) diff --git a/samples/nehe10/main.c b/samples/nehe10/main.c new file mode 100644 index 0000000..7aa1252 --- /dev/null +++ b/samples/nehe10/main.c @@ -0,0 +1,317 @@ +/* + KallistiOS 2.0.0 + + nehe08.c + (c)2021 Luke Benstead + (c)2014 Josh Pearson + (c)2001 Benoit Miller + (c)2000 Jeff Molofee +*/ + +#ifdef __DREAMCAST__ +#include +#endif + +#include +#include +#include + +#include + +#include "../loadbmp.h" + +#ifdef __DREAMCAST__ +extern uint8 romdisk[]; +KOS_INIT_ROMDISK(romdisk); +#define IMG_PATH "/rd/brick.bmp" +#else +#define IMG_PATH "../samples/nehe10/romdisk/brick.bmp" +#endif + +bool keys[256]; // Array Used For The Keyboard Routine +bool active = GL_TRUE; // Window Active Flag Set To TRUE By Default +bool fullscreen = GL_TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default +bool blend; // Blending ON/OFF +bool bp; // B Pressed? +bool fp; // F Pressed? + +const float piover180 = 0.0174532925f; +float heading; +float xpos; +float zpos; + +GLfloat yrot; // Y Rotation +GLfloat walkbias = 0; +GLfloat walkbiasangle = 0; +GLfloat lookupdown = 0.0f; +GLfloat z=0.0f; // Depth Into The Screen + +GLuint filter; // Which Filter To Use +GLuint texture[3]; // Storage For 3 Textures + +typedef struct tagVERTEX +{ + float x, y, z; + float u, v; +} VERTEX; + +typedef struct tagTRIANGLE +{ + VERTEX vertex[3]; +} TRIANGLE; + +typedef struct tagSECTOR +{ + int numtriangles; + TRIANGLE* triangle; +} SECTOR; + +SECTOR sector1; + +void readstr(FILE *f,char *string) +{ + do + { + fgets(string, 255, f); + } while ((string[0] == '/') || (string[0] == '\n')); + return; +} + +void SetupWorld() +{ + float x, y, z, u, v; + int numtriangles; + FILE *filein; + char oneline[255]; + filein = fopen("/rd/world.txt", "rt"); // File To Load World Data From + + readstr(filein,oneline); + sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles); + + sector1.triangle = (TRIANGLE*) malloc(sizeof(TRIANGLE) * numtriangles); + sector1.numtriangles = numtriangles; + for (int loop = 0; loop < numtriangles; loop++) + { + for (int vert = 0; vert < 3; vert++) + { + readstr(filein,oneline); + sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v); + sector1.triangle[loop].vertex[vert].x = x; + sector1.triangle[loop].vertex[vert].y = y; + sector1.triangle[loop].vertex[vert].z = z; + sector1.triangle[loop].vertex[vert].u = u; + sector1.triangle[loop].vertex[vert].v = v; + } + } + fclose(filein); + return; +} + +int LoadGLTextures() // Load Bitmaps And Convert To Textures +{ + int Status = GL_FALSE; // Status Indicator + + Image image1; + + // Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit + if (ImageLoad(IMG_PATH, &image1)) + { + Status = GL_TRUE; // Set The Status To TRUE + + glGenTextures(3, &texture[0]); // Create Three Textures + + // Create Nearest Filtered Texture + glBindTexture(GL_TEXTURE_2D, texture[0]); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, 3, image1.sizeX, image1.sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1.data); + + // Create Linear Filtered Texture + glBindTexture(GL_TEXTURE_2D, texture[1]); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, 3, image1.sizeX, image1.sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1.data); + + // Create MipMapped Texture + glBindTexture(GL_TEXTURE_2D, texture[2]); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); + gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image1.sizeX, image1.sizeY, GL_RGB, GL_UNSIGNED_BYTE, image1.data); + } + + return Status; // Return The Status +} + +/* A general OpenGL initialization function. Sets all of the initial parameters. */ +GLboolean InitGL(int width, int height) // We call this right after our OpenGL window is created. +{ + glViewport(0, 0, width, height); // Reset The Current Viewport + + glMatrixMode(GL_PROJECTION); // Select The Projection Matrix + glLoadIdentity(); // Reset The Projection Matrix + + // Calculate The Aspect Ratio Of The Window + gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); + + glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix + glLoadIdentity(); + + if (!LoadGLTextures()) // Jump To Texture Loading Routine + { + return GL_FALSE; // If Texture Didn't Load Return false + } + + glEnable(GL_TEXTURE_2D); // Enable Texture Mapping + glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Set The Blending Function For Translucency + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // This Will Clear The Background Color To Black + glClearDepth(1.0); // Enables Clearing Of The Depth Buffer + glDepthFunc(GL_LESS); // The Type Of Depth Test To Do + glEnable(GL_DEPTH_TEST); // Enables Depth Testing + glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations + + SetupWorld(); + + return GL_TRUE; +} + +void DrawGLScene(void) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer + glLoadIdentity(); // Reset The View + + GLfloat x_m, y_m, z_m, u_m, v_m; + GLfloat xtrans = -xpos; + GLfloat ztrans = -zpos; + GLfloat ytrans = -walkbias-0.25f; + GLfloat sceneroty = 360.0f - yrot; + + int numtriangles; + + glRotatef(lookupdown,1.0f,0,0); + glRotatef(sceneroty,0,1.0f,0); + + glTranslatef(xtrans, ytrans, ztrans); + glBindTexture(GL_TEXTURE_2D, texture[filter]); + + numtriangles = sector1.numtriangles; + + // Process Each Triangle + for (int loop_m = 0; loop_m < numtriangles; loop_m++) + { + glBegin(GL_TRIANGLES); + glNormal3f( 0.0f, 0.0f, 1.0f); + x_m = sector1.triangle[loop_m].vertex[0].x; + y_m = sector1.triangle[loop_m].vertex[0].y; + z_m = sector1.triangle[loop_m].vertex[0].z; + u_m = sector1.triangle[loop_m].vertex[0].u; + v_m = sector1.triangle[loop_m].vertex[0].v; + glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); + + x_m = sector1.triangle[loop_m].vertex[1].x; + y_m = sector1.triangle[loop_m].vertex[1].y; + z_m = sector1.triangle[loop_m].vertex[1].z; + u_m = sector1.triangle[loop_m].vertex[1].u; + v_m = sector1.triangle[loop_m].vertex[1].v; + glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); + + x_m = sector1.triangle[loop_m].vertex[2].x; + y_m = sector1.triangle[loop_m].vertex[2].y; + z_m = sector1.triangle[loop_m].vertex[2].z; + u_m = sector1.triangle[loop_m].vertex[2].u; + v_m = sector1.triangle[loop_m].vertex[2].v; + glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); + glEnd(); + } + /* Finish the frame */ + glKosSwapBuffers(); +} + +int ReadController(void) { +#ifdef __DREAMCAST__ + maple_device_t *cont; + cont_state_t *state; + + cont = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); + + /* Check key status */ + state = (cont_state_t *)maple_dev_status(cont); + if(!state) { + printf("Error reading controller\n"); + return 0; + } + + if(state->buttons & CONT_START) + return 0; + + if(state->buttons & CONT_DPAD_UP) { + xpos -= (float)sin(heading*piover180) * 0.05f; + zpos -= (float)cos(heading*piover180) * 0.05f; + if (walkbiasangle >= 359.0f) + { + walkbiasangle = 0.0f; + } + else + { + walkbiasangle+= 10; + } + walkbias = (float)sin(walkbiasangle * piover180)/20.0f; + } + + + if(state->buttons & CONT_DPAD_DOWN) { + xpos += (float)sin(heading*piover180) * 0.05f; + zpos += (float)cos(heading*piover180) * 0.05f; + if (walkbiasangle <= 1.0f) + { + walkbiasangle = 359.0f; + } + else + { + walkbiasangle-= 10; + } + walkbias = (float)sin(walkbiasangle * piover180)/20.0f; + } + + + if(state->buttons & CONT_DPAD_LEFT) { + heading += 1.0f; + yrot = heading; + } + + if(state->buttons & CONT_DPAD_RIGHT) { + heading -= 1.0f; + yrot = heading; + } + +#endif + + /* Switch to the blended polygon list if needed */ + if(blend) { + glEnable(GL_BLEND); + glDepthMask(0); + } + else { + glDisable(GL_BLEND); + glDepthMask(1); + } + + return 1; +} + +int main(int argc, char **argv) { + printf("nehe10 beginning\n"); + + /* Get basic stuff initialized */ + glKosInit(); + + InitGL(640, 480); + + while(1) { + if (!ReadController()) + break; + + DrawGLScene(); + } + + return 0; +} diff --git a/samples/nehe10/romdisk/brick.bmp b/samples/nehe10/romdisk/brick.bmp new file mode 100644 index 0000000000000000000000000000000000000000..54ac213a3504904da2f6d0d3b080874962b00d79 GIT binary patch literal 49290 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 literal 0 HcmV?d00001 diff --git a/samples/nehe10/romdisk/world.txt b/samples/nehe10/romdisk/world.txt new file mode 100644 index 0000000..a3368bb --- /dev/null +++ b/samples/nehe10/romdisk/world.txt @@ -0,0 +1,160 @@ + +NUMPOLLIES 36 + +// Floor 1 +-3.0 0.0 -3.0 0.0 6.0 +-3.0 0.0 3.0 0.0 0.0 + 3.0 0.0 3.0 6.0 0.0 + +-3.0 0.0 -3.0 0.0 6.0 + 3.0 0.0 -3.0 6.0 6.0 + 3.0 0.0 3.0 6.0 0.0 + +// Ceiling 1 +-3.0 1.0 -3.0 0.0 6.0 +-3.0 1.0 3.0 0.0 0.0 + 3.0 1.0 3.0 6.0 0.0 +-3.0 1.0 -3.0 0.0 6.0 + 3.0 1.0 -3.0 6.0 6.0 + 3.0 1.0 3.0 6.0 0.0 + +// A1 + +-2.0 1.0 -2.0 0.0 1.0 +-2.0 0.0 -2.0 0.0 0.0 +-0.5 0.0 -2.0 1.5 0.0 +-2.0 1.0 -2.0 0.0 1.0 +-0.5 1.0 -2.0 1.5 1.0 +-0.5 0.0 -2.0 1.5 0.0 + +// A2 + + 2.0 1.0 -2.0 2.0 1.0 + 2.0 0.0 -2.0 2.0 0.0 + 0.5 0.0 -2.0 0.5 0.0 + 2.0 1.0 -2.0 2.0 1.0 + 0.5 1.0 -2.0 0.5 1.0 + 0.5 0.0 -2.0 0.5 0.0 + +// B1 + +-2.0 1.0 2.0 2.0 1.0 +-2.0 0.0 2.0 2.0 0.0 +-0.5 0.0 2.0 0.5 0.0 +-2.0 1.0 2.0 2.0 1.0 +-0.5 1.0 2.0 0.5 1.0 +-0.5 0.0 2.0 0.5 0.0 + +// B2 + + 2.0 1.0 2.0 2.0 1.0 + 2.0 0.0 2.0 2.0 0.0 + 0.5 0.0 2.0 0.5 0.0 + 2.0 1.0 2.0 2.0 1.0 + 0.5 1.0 2.0 0.5 1.0 + 0.5 0.0 2.0 0.5 0.0 + +// C1 + +-2.0 1.0 -2.0 0.0 1.0 +-2.0 0.0 -2.0 0.0 0.0 +-2.0 0.0 -0.5 1.5 0.0 +-2.0 1.0 -2.0 0.0 1.0 +-2.0 1.0 -0.5 1.5 1.0 +-2.0 0.0 -0.5 1.5 0.0 + +// C2 + +-2.0 1.0 2.0 2.0 1.0 +-2.0 0.0 2.0 2.0 0.0 +-2.0 0.0 0.5 0.5 0.0 +-2.0 1.0 2.0 2.0 1.0 +-2.0 1.0 0.5 0.5 1.0 +-2.0 0.0 0.5 0.5 0.0 + +// D1 + +2.0 1.0 -2.0 0.0 1.0 +2.0 0.0 -2.0 0.0 0.0 +2.0 0.0 -0.5 1.5 0.0 +2.0 1.0 -2.0 0.0 1.0 +2.0 1.0 -0.5 1.5 1.0 +2.0 0.0 -0.5 1.5 0.0 + +// D2 + +2.0 1.0 2.0 2.0 1.0 +2.0 0.0 2.0 2.0 0.0 +2.0 0.0 0.5 0.5 0.0 +2.0 1.0 2.0 2.0 1.0 +2.0 1.0 0.5 0.5 1.0 +2.0 0.0 0.5 0.5 0.0 + +// Upper hallway - L +-0.5 1.0 -3.0 0.0 1.0 +-0.5 0.0 -3.0 0.0 0.0 +-0.5 0.0 -2.0 1.0 0.0 +-0.5 1.0 -3.0 0.0 1.0 +-0.5 1.0 -2.0 1.0 1.0 +-0.5 0.0 -2.0 1.0 0.0 + +// Upper hallway - R +0.5 1.0 -3.0 0.0 1.0 +0.5 0.0 -3.0 0.0 0.0 +0.5 0.0 -2.0 1.0 0.0 +0.5 1.0 -3.0 0.0 1.0 +0.5 1.0 -2.0 1.0 1.0 +0.5 0.0 -2.0 1.0 0.0 + +// Lower hallway - L +-0.5 1.0 3.0 0.0 1.0 +-0.5 0.0 3.0 0.0 0.0 +-0.5 0.0 2.0 1.0 0.0 +-0.5 1.0 3.0 0.0 1.0 +-0.5 1.0 2.0 1.0 1.0 +-0.5 0.0 2.0 1.0 0.0 + +// Lower hallway - R +0.5 1.0 3.0 0.0 1.0 +0.5 0.0 3.0 0.0 0.0 +0.5 0.0 2.0 1.0 0.0 +0.5 1.0 3.0 0.0 1.0 +0.5 1.0 2.0 1.0 1.0 +0.5 0.0 2.0 1.0 0.0 + + +// Left hallway - Lw + +-3.0 1.0 0.5 1.0 1.0 +-3.0 0.0 0.5 1.0 0.0 +-2.0 0.0 0.5 0.0 0.0 +-3.0 1.0 0.5 1.0 1.0 +-2.0 1.0 0.5 0.0 1.0 +-2.0 0.0 0.5 0.0 0.0 + +// Left hallway - Hi + +-3.0 1.0 -0.5 1.0 1.0 +-3.0 0.0 -0.5 1.0 0.0 +-2.0 0.0 -0.5 0.0 0.0 +-3.0 1.0 -0.5 1.0 1.0 +-2.0 1.0 -0.5 0.0 1.0 +-2.0 0.0 -0.5 0.0 0.0 + +// Right hallway - Lw + +3.0 1.0 0.5 1.0 1.0 +3.0 0.0 0.5 1.0 0.0 +2.0 0.0 0.5 0.0 0.0 +3.0 1.0 0.5 1.0 1.0 +2.0 1.0 0.5 0.0 1.0 +2.0 0.0 0.5 0.0 0.0 + +// Right hallway - Hi + +3.0 1.0 -0.5 1.0 1.0 +3.0 0.0 -0.5 1.0 0.0 +2.0 0.0 -0.5 0.0 0.0 +3.0 1.0 -0.5 1.0 1.0 +2.0 1.0 -0.5 0.0 1.0 +2.0 0.0 -0.5 0.0 0.0 \ No newline at end of file From 53c54997c30042f19bd7b449517fa16655b1f929 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 10 Jun 2022 20:27:21 +0100 Subject: [PATCH 03/10] Fix a number of issues! --- GL/platforms/sh4.c | 63 +++++++++++++++++++----------- samples/zclip_trianglestrip/main.c | 1 + 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/GL/platforms/sh4.c b/GL/platforms/sh4.c index 321108a..29b9f62 100644 --- a/GL/platforms/sh4.c +++ b/GL/platforms/sh4.c @@ -71,6 +71,12 @@ GL_FORCE_INLINE void _glPerspectiveDivideVertex(Vertex* vertex, const float h) { static uint32_t *d; // SQ target GL_FORCE_INLINE void _glSubmitHeaderOrVertex(const Vertex* v) { +#ifndef NDEBUG + assert(!isnan(v->xyz[2])); + assert(!isnan(v->w)); + assert(v->xyz[2] > 0.0f); +#endif + uint32_t *s = (uint32_t*) v; __asm__("pref @%0" : : "r"(s + 8)); /* prefetch 32 bytes for next loop */ d[0] = *(s++); @@ -91,7 +97,21 @@ static struct { } triangle[3]; static int tri_count = 0; +static int strip_count = 0; +GL_FORCE_INLINE void interpolateColour(const uint8_t* v1, const uint8_t* v2, const float t, uint8_t* out) { + const int MASK1 = 0x00FF00FF; + const int MASK2 = 0xFF00FF00; + + const int f2 = 256 * t; + const int f1 = 256 - f2; + + const uint32_t a = *(uint32_t*) v1; + const uint32_t b = *(uint32_t*) v2; + + *((uint32_t*) out) = (((((a & MASK1) * f1) + ((b & MASK1) * f2)) >> 8) & MASK1) | + (((((a & MASK2) * f1) + ((b & MASK2) * f2)) >> 8) & MASK2); +} GL_FORCE_INLINE void _glClipEdge(const Vertex* v1, const Vertex* v2, Vertex* vout) { /* Clipping time! */ @@ -108,10 +128,7 @@ GL_FORCE_INLINE void _glClipEdge(const Vertex* v1, const Vertex* v2, Vertex* vou vout->uv[0] = MATH_fmac(v2->uv[0] - v1->uv[0], t, v1->uv[0]); vout->uv[1] = MATH_fmac(v2->uv[1] - v1->uv[1], t, v1->uv[1]); - vout->bgra[0] = 0xFF; - vout->bgra[1] = 0xFF; - vout->bgra[2] = 0xFF; - vout->bgra[3] = 0xFF; + interpolateColour(v1->bgra, v2->bgra, t, vout->bgra); } GL_FORCE_INLINE void ClearTriangle() { @@ -119,9 +136,16 @@ GL_FORCE_INLINE void ClearTriangle() { } GL_FORCE_INLINE void ShiftTriangle() { + if(!tri_count) { + return; + } + tri_count--; - triangle[0] = triangle[1]; - triangle[1] = triangle[2]; + if(strip_count % 2 == 0) { + triangle[1] = triangle[2]; + } else { + triangle[0] = triangle[2]; + } #ifndef NDEBUG triangle[2].v = NULL; @@ -141,8 +165,7 @@ void SceneListSubmit(void* src, int n) { const float h = GetVideoMode()->height; tri_count = 0; - - int strip_count = 0; + strip_count = 0; for(int i = 0; i < n; ++i) { PREFETCH(vertex + 1); @@ -175,9 +198,16 @@ void SceneListSubmit(void* src, int n) { /* All the vertices are visible! We divide and submit v0, then shift */ _glPerspectiveDivideVertex(triangle[0].v, h); _glSubmitHeaderOrVertex(triangle[0].v); - } else if(!visible_mask) { - /* None visible, just shift for the next in the strip */ - } else { + + if(is_last_in_strip) { + _glPerspectiveDivideVertex(triangle[1].v, h); + _glSubmitHeaderOrVertex(triangle[1].v); + _glPerspectiveDivideVertex(triangle[2].v, h); + _glSubmitHeaderOrVertex(triangle[2].v); + ClearTriangle(); + strip_count = 0; + } + } else if(visible_mask) { /* Clipping time! There are 6 distinct possibilities when clipping a triangle. 3 of them result @@ -193,7 +223,6 @@ void SceneListSubmit(void* src, int n) { */ Vertex tmp0, tmp1, tmp2, tmp3; - switch(visible_mask) { case 1: { /* 0, 0a, 2a */ @@ -321,16 +350,6 @@ void SceneListSubmit(void* src, int n) { /* If this was the last vertex in the strip, we're done with the strip so we need to wipe out the tri_count */ ShiftTriangle(); - - if(is_last_in_strip) { - for(int i = 0; i < tri_count; ++i) { - if(triangle[i].visible) { - _glPerspectiveDivideVertex(triangle[i].v, h); - _glSubmitHeaderOrVertex(triangle[i].v); - } - } - ClearTriangle(); - } ++vertex; } /* Wait for both store queues to complete */ diff --git a/samples/zclip_trianglestrip/main.c b/samples/zclip_trianglestrip/main.c index 4bea868..7d4b698 100644 --- a/samples/zclip_trianglestrip/main.c +++ b/samples/zclip_trianglestrip/main.c @@ -20,6 +20,7 @@ void InitGL(int Width, int Height) // We call this right after our OpenG glEnable(GL_DEPTH_TEST); // Enables Depth Testing glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading glEnable(GL_TEXTURE_2D); + glEnable(GL_CULL_FACE); glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Reset The Projection Matrix From 4827fd4c5b416577596dc656819b6f70bee12066 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sat, 11 Jun 2022 21:27:09 +0100 Subject: [PATCH 04/10] Fix various clipping glitches --- GL/platforms/sh4.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/GL/platforms/sh4.c b/GL/platforms/sh4.c index 29b9f62..e12535c 100644 --- a/GL/platforms/sh4.c +++ b/GL/platforms/sh4.c @@ -74,7 +74,6 @@ GL_FORCE_INLINE void _glSubmitHeaderOrVertex(const Vertex* v) { #ifndef NDEBUG assert(!isnan(v->xyz[2])); assert(!isnan(v->w)); - assert(v->xyz[2] > 0.0f); #endif uint32_t *s = (uint32_t*) v; @@ -118,15 +117,20 @@ GL_FORCE_INLINE void _glClipEdge(const Vertex* v1, const Vertex* v2, Vertex* vou const float d0 = v1->w + v1->xyz[2]; const float d1 = v2->w + v2->xyz[2]; - float t = MATH_Fast_Divide(d0, (d0 - d1)); + const float epsilon = (d0 < d1) ? -0.00001f : 0.00001f; - vout->xyz[0] = MATH_fmac(v2->xyz[0] - v1->xyz[0], t, v1->xyz[0]); - vout->xyz[1] = MATH_fmac(v2->xyz[1] - v1->xyz[1], t, v1->xyz[1]); - vout->xyz[2] = MATH_fmac(v2->xyz[2] - v1->xyz[2], t, v1->xyz[2]); - vout->w = MATH_fmac(v2->w - v1->w, t, v1->w); + float t = MATH_Fast_Divide(d0, (d0 - d1)) + epsilon; - vout->uv[0] = MATH_fmac(v2->uv[0] - v1->uv[0], t, v1->uv[0]); - vout->uv[1] = MATH_fmac(v2->uv[1] - v1->uv[1], t, v1->uv[1]); + t = (t > 1.0f) ? 1.0f : t; + t = (t < 0.0f) ? 0.0f : t; + + vout->xyz[0] = __builtin_fmaf(v2->xyz[0] - v1->xyz[0], t, v1->xyz[0]); + vout->xyz[1] = __builtin_fmaf(v2->xyz[1] - v1->xyz[1], t, v1->xyz[1]); + vout->xyz[2] = __builtin_fmaf(v2->xyz[2] - v1->xyz[2], t, v1->xyz[2]); + vout->w = __builtin_fmaf(v2->w - v1->w, t, v1->w); + + vout->uv[0] = __builtin_fmaf(v2->uv[0] - v1->uv[0], t, v1->uv[0]); + vout->uv[1] = __builtin_fmaf(v2->uv[1] - v1->uv[1], t, v1->uv[1]); interpolateColour(v1->bgra, v2->bgra, t, vout->bgra); } @@ -176,7 +180,7 @@ void SceneListSubmit(void* src, int n) { if(tri_count < 3) { if(likely(glIsVertex(vertex->flags))) { triangle[tri_count].v = vertex; - triangle[tri_count].visible = vertex->w > 0 && vertex->xyz[2] > -vertex->w; + triangle[tri_count].visible = vertex->xyz[2] > -vertex->w; tri_count++; strip_count++; } else { From 16f6100afa3e25dfc9d8ecf71602bad39dec865f Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sun, 12 Jun 2022 19:53:26 +0100 Subject: [PATCH 05/10] Fix issues and optimise --- GL/platforms/sh4.c | 166 +++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/GL/platforms/sh4.c b/GL/platforms/sh4.c index e12535c..66a7928 100644 --- a/GL/platforms/sh4.c +++ b/GL/platforms/sh4.c @@ -180,7 +180,7 @@ void SceneListSubmit(void* src, int n) { if(tri_count < 3) { if(likely(glIsVertex(vertex->flags))) { triangle[tri_count].v = vertex; - triangle[tri_count].visible = vertex->xyz[2] > -vertex->w; + triangle[tri_count].visible = vertex->xyz[2] >= -vertex->w; tri_count++; strip_count++; } else { @@ -226,118 +226,124 @@ void SceneListSubmit(void* src, int n) { be used in a subsequent triangle in the strip and would end up being double divided. */ - Vertex tmp0, tmp1, tmp2, tmp3; + Vertex tmp; switch(visible_mask) { case 1: { /* 0, 0a, 2a */ - tmp0 = *triangle[0].v; - _glClipEdge(triangle[0].v, triangle[1].v, &tmp1); - _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); + tmp = *triangle[0].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glPerspectiveDivideVertex(&tmp0, h); - _glPerspectiveDivideVertex(&tmp1, h); - _glPerspectiveDivideVertex(&tmp2, h); + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - tmp0.flags = tmp1.flags = GPU_CMD_VERTEX; - tmp2.flags = GPU_CMD_VERTEX_EOL; - - _glSubmitHeaderOrVertex(&tmp0); - _glSubmitHeaderOrVertex(&tmp1); - _glSubmitHeaderOrVertex(&tmp2); + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); } break; case 2: { /* 0a, 1, 1a */ - _glClipEdge(triangle[0].v, triangle[1].v, &tmp0); - tmp1 = *triangle[1].v; - _glClipEdge(triangle[1].v, triangle[2].v, &tmp2); + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glPerspectiveDivideVertex(&tmp0, h); - _glPerspectiveDivideVertex(&tmp1, h); - _glPerspectiveDivideVertex(&tmp2, h); + tmp = *triangle[1].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - tmp0.flags = tmp1.flags = GPU_CMD_VERTEX; - tmp2.flags = GPU_CMD_VERTEX_EOL; - - _glSubmitHeaderOrVertex(&tmp0); - _glSubmitHeaderOrVertex(&tmp1); - _glSubmitHeaderOrVertex(&tmp2); + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); } break; case 3: { /* 0, 1, 2a, 1a */ - tmp0 = *triangle[0].v; - tmp1 = *triangle[1].v; - _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); - _glClipEdge(triangle[1].v, triangle[2].v, &tmp3); + tmp = *triangle[0].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glPerspectiveDivideVertex(&tmp0, h); - _glPerspectiveDivideVertex(&tmp1, h); - _glPerspectiveDivideVertex(&tmp2, h); - _glPerspectiveDivideVertex(&tmp3, h); + tmp = *triangle[1].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - tmp0.flags = tmp1.flags = tmp2.flags = GPU_CMD_VERTEX; - tmp3.flags = GPU_CMD_VERTEX_EOL; + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glSubmitHeaderOrVertex(&tmp0); - _glSubmitHeaderOrVertex(&tmp1); - _glSubmitHeaderOrVertex(&tmp2); - _glSubmitHeaderOrVertex(&tmp3); + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); } break; case 4: { /* 1a, 2, 2a */ - _glClipEdge(triangle[1].v, triangle[2].v, &tmp0); - tmp1 = *triangle[2].v; - _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glPerspectiveDivideVertex(&tmp0, h); - _glPerspectiveDivideVertex(&tmp1, h); - _glPerspectiveDivideVertex(&tmp2, h); + tmp = *triangle[2].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - tmp0.flags = tmp1.flags = GPU_CMD_VERTEX; - tmp2.flags = GPU_CMD_VERTEX_EOL; - - _glSubmitHeaderOrVertex(&tmp0); - _glSubmitHeaderOrVertex(&tmp1); - _glSubmitHeaderOrVertex(&tmp2); + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); } break; case 5: { /* 0, 0a, 2, 1a */ - tmp0 = *triangle[0].v; - _glClipEdge(triangle[0].v, triangle[1].v, &tmp1); - tmp2 = *triangle[2].v; - _glClipEdge(triangle[1].v, triangle[2].v, &tmp3); + tmp = *triangle[0].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glPerspectiveDivideVertex(&tmp0, h); - _glPerspectiveDivideVertex(&tmp1, h); - _glPerspectiveDivideVertex(&tmp2, h); - _glPerspectiveDivideVertex(&tmp3, h); + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - tmp0.flags = tmp1.flags = tmp2.flags = GPU_CMD_VERTEX; - tmp3.flags = GPU_CMD_VERTEX_EOL; + tmp = *triangle[2].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glSubmitHeaderOrVertex(&tmp0); - _glSubmitHeaderOrVertex(&tmp1); - _glSubmitHeaderOrVertex(&tmp2); - _glSubmitHeaderOrVertex(&tmp3); + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); } break; case 6: { /* 0a, 1, 2a, 2 */ - _glClipEdge(triangle[0].v, triangle[1].v, &tmp0); - tmp1 = *triangle[1].v; - _glClipEdge(triangle[2].v, triangle[0].v, &tmp2); - tmp3 = *triangle[2].v; + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glPerspectiveDivideVertex(&tmp0, h); - _glPerspectiveDivideVertex(&tmp1, h); - _glPerspectiveDivideVertex(&tmp2, h); - _glPerspectiveDivideVertex(&tmp3, h); + tmp = *triangle[1].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - tmp0.flags = tmp1.flags = tmp2.flags = GPU_CMD_VERTEX; - tmp3.flags = GPU_CMD_VERTEX_EOL; + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); - _glSubmitHeaderOrVertex(&tmp0); - _glSubmitHeaderOrVertex(&tmp1); - _glSubmitHeaderOrVertex(&tmp2); - _glSubmitHeaderOrVertex(&tmp3); + tmp = *triangle[2].v; + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); } break; default: break; From b17104afea2072a29b5f511f35436c248ae24004 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 13 Jun 2022 20:05:39 +0100 Subject: [PATCH 06/10] Add a missing header --- samples/nehe10/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/nehe10/main.c b/samples/nehe10/main.c index 7aa1252..faa3bc0 100644 --- a/samples/nehe10/main.c +++ b/samples/nehe10/main.c @@ -12,6 +12,7 @@ #include #endif +#include #include #include #include From 6fb15ee4c71781e15844da127aa3e1dd090cafa9 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 13 Jun 2022 20:05:54 +0100 Subject: [PATCH 07/10] Fix some winding issues --- GL/platforms/sh4.c | 101 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/GL/platforms/sh4.c b/GL/platforms/sh4.c index 66a7928..9c63a0d 100644 --- a/GL/platforms/sh4.c +++ b/GL/platforms/sh4.c @@ -1,6 +1,9 @@ #include "../platform.h" #include "sh4.h" + +#define CLIP_DEBUG 0 + #define TA_SQ_ADDR (unsigned int *)(void *) \ (0xe0000000 | (((unsigned long)0x10000000) & 0x03ffffe0)) @@ -76,6 +79,10 @@ GL_FORCE_INLINE void _glSubmitHeaderOrVertex(const Vertex* v) { assert(!isnan(v->w)); #endif +#if CLIP_DEBUG + printf("Submitting: %x (%x)\n", v, v->flags); +#endif + uint32_t *s = (uint32_t*) v; __asm__("pref @%0" : : "r"(s + 8)); /* prefetch 32 bytes for next loop */ d[0] = *(s++); @@ -145,11 +152,8 @@ GL_FORCE_INLINE void ShiftTriangle() { } tri_count--; - if(strip_count % 2 == 0) { - triangle[1] = triangle[2]; - } else { - triangle[0] = triangle[2]; - } + triangle[0] = triangle[1]; + triangle[1] = triangle[2]; #ifndef NDEBUG triangle[2].v = NULL; @@ -157,6 +161,22 @@ GL_FORCE_INLINE void ShiftTriangle() { #endif } + +GL_FORCE_INLINE void ShiftRotateTriangle() { + if(!tri_count) { + return; + } + + if(triangle[0].v < triangle[1].v) { + triangle[0] = triangle[2]; + } else { + triangle[1] = triangle[2]; + } + + tri_count--; +} + + void SceneListSubmit(void* src, int n) { /* Do everything, everywhere, all at once */ @@ -171,7 +191,11 @@ void SceneListSubmit(void* src, int n) { tri_count = 0; strip_count = 0; - for(int i = 0; i < n; ++i) { +#if CLIP_DEBUG + printf("----\n"); +#endif + + for(int i = 0; i < n; ++i, ++vertex) { PREFETCH(vertex + 1); bool is_last_in_strip = glIsLastVertex(vertex->flags); @@ -191,26 +215,35 @@ void SceneListSubmit(void* src, int n) { } if(tri_count < 3) { - ++vertex; continue; } } +#if CLIP_DEBUG + printf("SC: %d\n", strip_count); +#endif + /* If we got here, then triangle contains 3 vertices */ int visible_mask = triangle[0].visible | (triangle[1].visible << 1) | (triangle[2].visible << 2); if(visible_mask == 7) { +#if CLIP_DEBUG + printf("Visible\n"); +#endif /* All the vertices are visible! We divide and submit v0, then shift */ - _glPerspectiveDivideVertex(triangle[0].v, h); - _glSubmitHeaderOrVertex(triangle[0].v); + _glPerspectiveDivideVertex(vertex - 2, h); + _glSubmitHeaderOrVertex(vertex - 2); if(is_last_in_strip) { - _glPerspectiveDivideVertex(triangle[1].v, h); - _glSubmitHeaderOrVertex(triangle[1].v); - _glPerspectiveDivideVertex(triangle[2].v, h); - _glSubmitHeaderOrVertex(triangle[2].v); - ClearTriangle(); + _glPerspectiveDivideVertex(vertex - 1, h); + _glSubmitHeaderOrVertex(vertex - 1); + _glPerspectiveDivideVertex(vertex, h); + _glSubmitHeaderOrVertex(vertex); + tri_count = 0; strip_count = 0; } + + ShiftRotateTriangle(); + } else if(visible_mask) { /* Clipping time! @@ -225,8 +258,26 @@ void SceneListSubmit(void* src, int n) { Unfortunately we have to copy vertices here, because if we persp-divide a vertex it may be used in a subsequent triangle in the strip and would end up being double divided. */ - +#if CLIP_DEBUG + printf("Clip: %d, SC: %d\n", visible_mask, strip_count); + printf("%d, %d, %d\n", triangle[0].v - (Vertex*) src - 1, triangle[1].v - (Vertex*) src - 1, triangle[2].v - (Vertex*) src - 1); +#endif Vertex tmp; + if(strip_count > 3) { +#if CLIP_DEBUG + printf("Flush\n"); +#endif + tmp = *(vertex - 2); + /* If we had triangles ahead of this one, submit and finalize */ + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *(vertex - 1); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } + switch(visible_mask) { case 1: { /* 0, 0a, 2a */ @@ -319,7 +370,7 @@ void SceneListSubmit(void* src, int n) { _glSubmitHeaderOrVertex(&tmp); _glClipEdge(triangle[1].v, triangle[2].v, &tmp); - tmp.flags = GPU_CMD_VERTEX; + tmp.flags = GPU_CMD_VERTEX_EOL; _glPerspectiveDivideVertex(&tmp, h); _glSubmitHeaderOrVertex(&tmp); } break; @@ -354,14 +405,22 @@ void SceneListSubmit(void* src, int n) { if(is_last_in_strip) { tri_count = 0; strip_count = 0; + } else { + ShiftRotateTriangle(); + strip_count = 2; } - } + } else { + /* Invisible? Move to the next in the strip */ - /* If this was the last vertex in the strip, we're done with the - strip so we need to wipe out the tri_count */ - ShiftTriangle(); - ++vertex; + if(is_last_in_strip) { + tri_count = 0; + strip_count = 0; + } + strip_count = 2; + ShiftRotateTriangle(); + } } + /* Wait for both store queues to complete */ d = (uint32_t *)0xe0000000; d[0] = d[8] = 0; From 99ae70a72bd656256b1093c9ba378d6df72992ac Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 13 Jun 2022 20:06:04 +0100 Subject: [PATCH 08/10] Make the software implementation use the same clipping code --- GL/platforms/software.c | 435 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 393 insertions(+), 42 deletions(-) diff --git a/GL/platforms/software.c b/GL/platforms/software.c index d37cccc..a6f1974 100644 --- a/GL/platforms/software.c +++ b/GL/platforms/software.c @@ -3,11 +3,14 @@ #include #include +#include "../private.h" #include "../platform.h" #include "software.h" #include "software/edge_equation.h" #include "software/parameter_equation.h" +#define CLIP_DEBUG 0 + static size_t AVAILABLE_VRAM = 16 * 1024 * 1024; static Matrix4x4 MATRIX; @@ -23,28 +26,16 @@ static VideoMode vid_mode = { 640, 480 }; - -typedef struct GPUVertex { - uint32_t flags; - float x; - float y; - float z; - float u; - float v; - uint8_t bgra[4]; - uint8_t obgra[4]; -} GPUVertex; - #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) -static void DrawTriangle(GPUVertex* v0, GPUVertex* v1, GPUVertex* v2) { +static void DrawTriangle(Vertex* v0, Vertex* v1, Vertex* v2) { // Compute triangle bounding box. - int minX = MIN(MIN(v0->x, v1->x), v2->x); - int maxX = MAX(MAX(v0->x, v1->x), v2->x); - int minY = MIN(MIN(v0->y, v1->y), v2->y); - int maxY = MAX(MAX(v0->y, v1->y), v2->y); + int minX = MIN(MIN(v0->xyz[0], v1->xyz[0]), v2->xyz[0]); + int maxX = MAX(MAX(v0->xyz[0], v1->xyz[0]), v2->xyz[0]); + int minY = MIN(MIN(v0->xyz[1], v1->xyz[1]), v2->xyz[1]); + int maxY = MAX(MAX(v0->xyz[1], v1->xyz[1]), v2->xyz[1]); // Clip to scissor rect. @@ -56,9 +47,9 @@ static void DrawTriangle(GPUVertex* v0, GPUVertex* v1, GPUVertex* v2) { // Compute edge equations. EdgeEquation e0, e1, e2; - EdgeEquationInit(&e0, &v0->x, &v1->x); - EdgeEquationInit(&e1, &v1->x, &v2->x); - EdgeEquationInit(&e2, &v2->x, &v0->x); + EdgeEquationInit(&e0, &v0->xyz[0], &v1->xyz[0]); + EdgeEquationInit(&e1, &v1->xyz[0], &v2->xyz[0]); + EdgeEquationInit(&e2, &v2->xyz[0], &v0->xyz[0]); float area = 0.5 * (e0.c + e1.c + e2.c); @@ -66,12 +57,12 @@ static void DrawTriangle(GPUVertex* v0, GPUVertex* v1, GPUVertex* v2) { * so I just swap the vertex order if something is back-facing * and we want to render it. Patches welcome! */ #define REVERSE_WINDING() \ - GPUVertex* tv = v0; \ + Vertex* tv = v0; \ v0 = v1; \ v1 = tv; \ - EdgeEquationInit(&e0, &v0->x, &v1->x); \ - EdgeEquationInit(&e1, &v1->x, &v2->x); \ - EdgeEquationInit(&e2, &v2->x, &v0->x); \ + EdgeEquationInit(&e0, &v0->xyz[0], &v1->xyz[0]); \ + EdgeEquationInit(&e1, &v1->xyz[0], &v2->xyz[0]); \ + EdgeEquationInit(&e2, &v2->xyz[0], &v0->xyz[0]); \ area = 0.5f * (e0.c + e1.c + e2.c) \ // Check if triangle is backfacing. @@ -135,18 +126,382 @@ void SceneBegin() { SDL_RenderClear(RENDERER); } -void SceneListBegin(GPUList list) { +static Vertex BUFFER[1024 * 32]; +static uint32_t vertex_counter = 0; +GL_FORCE_INLINE bool glIsVertex(const float flags) { + return flags == GPU_CMD_VERTEX_EOL || flags == GPU_CMD_VERTEX; +} + +GL_FORCE_INLINE bool glIsLastVertex(const float flags) { + return flags == GPU_CMD_VERTEX_EOL; +} + + +void SceneListBegin(GPUList list) { + vertex_counter = 0; +} + +GL_FORCE_INLINE void _glPerspectiveDivideVertex(Vertex* vertex, const float h) { + const float f = 1.0f / (vertex->w); + + /* Convert to NDC and apply viewport */ + vertex->xyz[0] = __builtin_fmaf( + VIEWPORT.hwidth, vertex->xyz[0] * f, VIEWPORT.x_plus_hwidth + ); + + vertex->xyz[1] = h - __builtin_fmaf( + VIEWPORT.hheight, vertex->xyz[1] * f, VIEWPORT.y_plus_hheight + ); + + if(vertex->w == 1.0f) { + vertex->xyz[2] = 1.0f / (1.0001f + vertex->xyz[2]); + } else { + vertex->xyz[2] = f; + } +} + +GL_FORCE_INLINE void _glSubmitHeaderOrVertex(const Vertex* v) { +#ifndef NDEBUG + if(glIsVertex(v->flags)) { + assert(!isnan(v->xyz[2])); + assert(!isnan(v->w)); + } +#endif + +#if CLIP_DEBUG + printf("Submitting: %x (%x)\n", v, v->flags); +#endif + + BUFFER[vertex_counter++] = *v; +} + +static struct { + Vertex* v; + int visible; +} triangle[3]; + +static int tri_count = 0; +static int strip_count = 0; + +GL_FORCE_INLINE void interpolateColour(const uint8_t* v1, const uint8_t* v2, const float t, uint8_t* out) { + const int MASK1 = 0x00FF00FF; + const int MASK2 = 0xFF00FF00; + + const int f2 = 256 * t; + const int f1 = 256 - f2; + + const uint32_t a = *(uint32_t*) v1; + const uint32_t b = *(uint32_t*) v2; + + *((uint32_t*) out) = (((((a & MASK1) * f1) + ((b & MASK1) * f2)) >> 8) & MASK1) | + (((((a & MASK2) * f1) + ((b & MASK2) * f2)) >> 8) & MASK2); +} + +GL_FORCE_INLINE void _glClipEdge(const Vertex* v1, const Vertex* v2, Vertex* vout) { + /* Clipping time! */ + const float d0 = v1->w + v1->xyz[2]; + const float d1 = v2->w + v2->xyz[2]; + + const float epsilon = (d0 < d1) ? -0.00001f : 0.00001f; + + float t = (d0 / (d0 - d1)) + epsilon; + + t = (t > 1.0f) ? 1.0f : t; + t = (t < 0.0f) ? 0.0f : t; + + vout->xyz[0] = __builtin_fmaf(v2->xyz[0] - v1->xyz[0], t, v1->xyz[0]); + vout->xyz[1] = __builtin_fmaf(v2->xyz[1] - v1->xyz[1], t, v1->xyz[1]); + vout->xyz[2] = __builtin_fmaf(v2->xyz[2] - v1->xyz[2], t, v1->xyz[2]); + vout->w = __builtin_fmaf(v2->w - v1->w, t, v1->w); + + vout->uv[0] = __builtin_fmaf(v2->uv[0] - v1->uv[0], t, v1->uv[0]); + vout->uv[1] = __builtin_fmaf(v2->uv[1] - v1->uv[1], t, v1->uv[1]); + + interpolateColour(v1->bgra, v2->bgra, t, vout->bgra); +} + +GL_FORCE_INLINE void ClearTriangle() { + tri_count = 0; +} + +GL_FORCE_INLINE void ShiftTriangle() { + if(!tri_count) { + return; + } + + tri_count--; + triangle[0] = triangle[1]; + triangle[1] = triangle[2]; + +#ifndef NDEBUG + triangle[2].v = NULL; + triangle[2].visible = false; +#endif +} + +GL_FORCE_INLINE void ShiftRotateTriangle() { + if(!tri_count) { + return; + } + + if(triangle[0].v < triangle[1].v) { + triangle[0] = triangle[2]; + } else { + triangle[1] = triangle[2]; + } + + tri_count--; } void SceneListSubmit(void* src, int n) { - uint32_t vertex_counter = 0; - const uint32_t* flags = (const uint32_t*) src; - uint32_t step = sizeof(GPUVertex) / sizeof(uint32_t); + /* Perform perspective divide on each vertex */ + Vertex* vertex = (Vertex*) src; - for(int i = 0; i < n; ++i, flags += step) { + const float h = GetVideoMode()->height; + + tri_count = 0; + strip_count = 0; + +#if CLIP_DEBUG + printf("----\n"); +#endif + + for(int i = 0; i < n; ++i, ++vertex) { + PREFETCH(vertex + 1); + + bool is_last_in_strip = glIsLastVertex(vertex->flags); + + /* Wait until we fill the triangle */ + if(tri_count < 3) { + if(glIsVertex(vertex->flags)) { + triangle[tri_count].v = vertex; + triangle[tri_count].visible = vertex->xyz[2] >= -vertex->w; + tri_count++; + strip_count++; + } else { + /* We hit a header */ + tri_count = 0; + strip_count = 0; + _glSubmitHeaderOrVertex(vertex); + } + + if(tri_count < 3) { + continue; + } + } + +#if CLIP_DEBUG + printf("SC: %d\n", strip_count); +#endif + + /* If we got here, then triangle contains 3 vertices */ + int visible_mask = triangle[0].visible | (triangle[1].visible << 1) | (triangle[2].visible << 2); + if(visible_mask == 7) { +#if CLIP_DEBUG + printf("Visible\n"); +#endif + /* All the vertices are visible! We divide and submit v0, then shift */ + _glPerspectiveDivideVertex(vertex - 2, h); + _glSubmitHeaderOrVertex(vertex - 2); + + if(is_last_in_strip) { + _glPerspectiveDivideVertex(vertex - 1, h); + _glSubmitHeaderOrVertex(vertex - 1); + _glPerspectiveDivideVertex(vertex, h); + _glSubmitHeaderOrVertex(vertex); + tri_count = 0; + strip_count = 0; + } + + ShiftRotateTriangle(); + + } else if(visible_mask) { + /* Clipping time! + + There are 6 distinct possibilities when clipping a triangle. 3 of them result + in another triangle, 3 of them result in a quadrilateral. + + Assuming you iterate the edges of the triangle in order, and create a new *visible* + vertex when you cross the plane, and discard vertices behind the plane, then the only + difference between the two cases is that the final two vertices that need submitting have + to be reversed. + + Unfortunately we have to copy vertices here, because if we persp-divide a vertex it may + be used in a subsequent triangle in the strip and would end up being double divided. + */ +#if CLIP_DEBUG + printf("Clip: %d, SC: %d\n", visible_mask, strip_count); + printf("%d, %d, %d\n", triangle[0].v - (Vertex*) src - 1, triangle[1].v - (Vertex*) src - 1, triangle[2].v - (Vertex*) src - 1); +#endif + Vertex tmp; + if(strip_count > 3) { +#if CLIP_DEBUG + printf("Flush\n"); +#endif + tmp = *(vertex - 2); + /* If we had triangles ahead of this one, submit and finalize */ + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *(vertex - 1); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } + + switch(visible_mask) { + case 1: { + /* 0, 0a, 2a */ + tmp = *triangle[0].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } break; + case 2: { + /* 0a, 1, 1a */ + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *triangle[1].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } break; + case 3: { + /* 0, 1, 2a, 1a */ + tmp = *triangle[0].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *triangle[1].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } break; + case 4: { + /* 1a, 2, 2a */ + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *triangle[2].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } break; + case 5: { + /* 0, 0a, 2, 1a */ + tmp = *triangle[0].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *triangle[2].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[1].v, triangle[2].v, &tmp); + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } break; + case 6: { + /* 0a, 1, 2a, 2 */ + _glClipEdge(triangle[0].v, triangle[1].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *triangle[1].v; + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + _glClipEdge(triangle[2].v, triangle[0].v, &tmp); + tmp.flags = GPU_CMD_VERTEX; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + + tmp = *triangle[2].v; + tmp.flags = GPU_CMD_VERTEX_EOL; + _glPerspectiveDivideVertex(&tmp, h); + _glSubmitHeaderOrVertex(&tmp); + } break; + default: + break; + } + + /* If this was the last in the strip, we don't need to + submit anything else, we just wipe the tri_count */ + if(is_last_in_strip) { + tri_count = 0; + strip_count = 0; + } else { + ShiftRotateTriangle(); + strip_count = 2; + } + } else { + /* Invisible? Move to the next in the strip */ + + if(is_last_in_strip) { + tri_count = 0; + strip_count = 0; + } + strip_count = 2; + ShiftRotateTriangle(); + } + } +} + +void SceneListFinish() { + uint32_t vidx = 0; + const uint32_t* flags = (const uint32_t*) BUFFER; + uint32_t step = sizeof(Vertex) / sizeof(uint32_t); + + for(int i = 0; i < vertex_counter; ++i, flags += step) { if((*flags & GPU_CMD_POLYHDR) == GPU_CMD_POLYHDR) { - vertex_counter = 0; + vidx = 0; uint32_t mode1 = *(flags + 1); // Extract culling mode @@ -157,33 +512,29 @@ void SceneListSubmit(void* src, int n) { switch(*flags) { case GPU_CMD_VERTEX_EOL: case GPU_CMD_VERTEX: // Fallthrough - vertex_counter++; + vidx++; break; default: break; } } - if(vertex_counter > 2) { - GPUVertex* v0 = (GPUVertex*) (flags - step - step); - GPUVertex* v1 = (GPUVertex*) (flags - step); - GPUVertex* v2 = (GPUVertex*) (flags); - (vertex_counter % 2 == 0) ? DrawTriangle(v0, v1, v2) : DrawTriangle(v1, v0, v2); + if(vidx > 2) { + Vertex* v0 = (Vertex*) (flags - step - step); + Vertex* v1 = (Vertex*) (flags - step); + Vertex* v2 = (Vertex*) (flags); + (vidx % 2 == 0) ? DrawTriangle(v0, v1, v2) : DrawTriangle(v1, v0, v2); } if((*flags) == GPU_CMD_VERTEX_EOL) { - vertex_counter = 0; + vidx = 0; } } } -void SceneListFinish() { - -} - void SceneFinish() { SDL_RenderPresent(RENDERER); - + return; /* Only sensible place to hook the quit signal */ SDL_Event e; while (SDL_PollEvent(&e)) { From 3d69003c5f50edf61ac4990d6f9d234a76d6a84f Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 13 Jun 2022 20:16:37 +0100 Subject: [PATCH 09/10] Allow disabling near-z --- GL/platforms/sh4.c | 16 ++++++++++++++++ GL/platforms/software.c | 13 +++++++++++++ GL/private.h | 5 ++--- GL/state.c | 6 ++++-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/GL/platforms/sh4.c b/GL/platforms/sh4.c index 9c63a0d..f18bdc2 100644 --- a/GL/platforms/sh4.c +++ b/GL/platforms/sh4.c @@ -188,6 +188,22 @@ void SceneListSubmit(void* src, int n) { const float h = GetVideoMode()->height; + if(!ZNEAR_CLIPPING_ENABLED) { + for(int i = 0; i < n; ++i, ++vertex) { + PREFETCH(vertex + 1); + if(glIsVertex(vertex->flags)) { + _glPerspectiveDivideVertex(vertex, h); + } + _glSubmitHeaderOrVertex(vertex); + } + + /* Wait for both store queues to complete */ + d = (uint32_t *)0xe0000000; + d[0] = d[8] = 0; + + return; + } + tri_count = 0; strip_count = 0; diff --git a/GL/platforms/software.c b/GL/platforms/software.c index a6f1974..94916a7 100644 --- a/GL/platforms/software.c +++ b/GL/platforms/software.c @@ -260,6 +260,19 @@ void SceneListSubmit(void* src, int n) { const float h = GetVideoMode()->height; + /* If Z-clipping is disabled, just fire everything over to the buffer */ + if(!ZNEAR_CLIPPING_ENABLED) { + for(int i = 0; i < n; ++i, ++vertex) { + PREFETCH(vertex + 1); + if(glIsVertex(vertex->flags)) { + _glPerspectiveDivideVertex(vertex, h); + } + _glSubmitHeaderOrVertex(vertex); + } + + return; + } + tri_count = 0; strip_count = 0; diff --git a/GL/private.h b/GL/private.h index 46287dd..ba286d1 100644 --- a/GL/private.h +++ b/GL/private.h @@ -279,9 +279,6 @@ typedef enum { struct SubmissionTarget; -float _glClipLineToNearZ(const Vertex* v1, const Vertex* v2, Vertex* vout); -void _glClipTriangleStrip(SubmissionTarget* target, uint8_t fladeShade); - PolyList* _glOpaquePolyList(); PolyList* _glPunchThruPolyList(); PolyList *_glTransparentPolyList(); @@ -385,6 +382,8 @@ GLboolean _glIsMipmapComplete(const TextureObject* obj); GLubyte* _glGetMipmapLocation(const TextureObject* obj, GLuint level); GLuint _glGetMipmapLevelCount(const TextureObject* obj); +extern GLboolean ZNEAR_CLIPPING_ENABLED; + extern GLboolean LIGHTING_ENABLED; GLboolean _glIsLightingEnabled(); diff --git a/GL/state.c b/GL/state.c index 5cf1fae..20f28c5 100644 --- a/GL/state.c +++ b/GL/state.c @@ -18,6 +18,8 @@ static GLenum FRONT_FACE = GL_CCW; static GLboolean CULLING_ENABLED = GL_FALSE; static GLboolean COLOR_MATERIAL_ENABLED = GL_FALSE; +GLboolean ZNEAR_CLIPPING_ENABLED = GL_TRUE; + GLboolean LIGHTING_ENABLED = GL_FALSE; /* Is the shared texture palette enabled? */ @@ -356,7 +358,7 @@ GLAPI void APIENTRY glEnable(GLenum cap) { _glEnableLight(cap & 0xF, GL_TRUE); break; case GL_NEARZ_CLIPPING_KOS: - _glEnableClipping(GL_TRUE); + ZNEAR_CLIPPING_ENABLED = GL_TRUE; break; case GL_POLYGON_OFFSET_POINT: case GL_POLYGON_OFFSET_LINE: @@ -418,7 +420,7 @@ GLAPI void APIENTRY glDisable(GLenum cap) { _glEnableLight(cap & 0xF, GL_FALSE); break; case GL_NEARZ_CLIPPING_KOS: - _glEnableClipping(GL_FALSE); + ZNEAR_CLIPPING_ENABLED = GL_FALSE; break; case GL_POLYGON_OFFSET_POINT: case GL_POLYGON_OFFSET_LINE: From 9d9a502e96a57b543235f1bd3b6a0ac8fad4f1f6 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 13 Jun 2022 20:16:46 +0100 Subject: [PATCH 10/10] Remove old clipping --- CMakeLists.txt | 1 - GL/clip.c | 405 ------------------------------------------------- 2 files changed, 406 deletions(-) delete mode 100644 GL/clip.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 32a7d8d..b613495 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,6 @@ set( containers/aligned_vector.c containers/named_array.c containers/stack.c - GL/clip.c GL/draw.c GL/error.c GL/flush.c diff --git a/GL/clip.c b/GL/clip.c deleted file mode 100644 index ed051d8..0000000 --- a/GL/clip.c +++ /dev/null @@ -1,405 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#ifdef _arch_dreamcast -#include -#else -#define PVR_PACK_COLOR(a, r, g, b) {} -#endif - -#include "private.h" -#include "../containers/aligned_vector.h" - -static unsigned char ZCLIP_ENABLED = 1; - -unsigned char _glIsClippingEnabled() { - return ZCLIP_ENABLED; -} - -void _glEnableClipping(unsigned char v) { - ZCLIP_ENABLED = v; -} - -inline float _glClipLineToNearZ(const Vertex* v1, const Vertex* v2, Vertex* vout) { - const float d0 = v1->w + v1->xyz[2]; - const float d1 = v2->w + v2->xyz[2]; - - /* We need to shift 't' a little, to avoid the possibility that a - * rounding error leaves the new vertex behind the near plane. We shift - * according to the direction we're clipping across the plane */ - const float epsilon = (d0 < d1) ? -0.000001 : 0.000001; - - float t = MATH_Fast_Divide(d0, (d0 - d1)) + epsilon; - - vout->xyz[0] = MATH_fmac(v2->xyz[0] - v1->xyz[0], t, v1->xyz[0]); - vout->xyz[1] = MATH_fmac(v2->xyz[1] - v1->xyz[1], t, v1->xyz[1]); - vout->xyz[2] = MATH_fmac(v2->xyz[2] - v1->xyz[2], t, v1->xyz[2]); - - /* - printf( - "(%f, %f, %f, %f) -> %f -> (%f, %f, %f, %f) = (%f, %f, %f)\n", - v1->xyz[0], v1->xyz[1], v1->xyz[2], v1->w, t, - v2->xyz[0], v2->xyz[1], v2->xyz[2], v2->w, - vout->xyz[0], vout->xyz[1], vout->xyz[2] - );*/ - - return t; -} - -GL_FORCE_INLINE void interpolateFloat(const float v1, const float v2, const float t, float* out) { - *out = MATH_fmac(v2 - v1,t, v1); -} - -GL_FORCE_INLINE void interpolateVec2(const float* v1, const float* v2, const float t, float* out) { - interpolateFloat(v1[0], v2[0], t, &out[0]); - interpolateFloat(v1[1], v2[1], t, &out[1]); -} - -GL_FORCE_INLINE void interpolateVec3(const float* v1, const float* v2, const float t, float* out) { - interpolateFloat(v1[0], v2[0], t, &out[0]); - interpolateFloat(v1[1], v2[1], t, &out[1]); - interpolateFloat(v1[2], v2[2], t, &out[2]); -} - -GL_FORCE_INLINE void interpolateVec4(const float* v1, const float* v2, const float t, float* out) { - interpolateFloat(v1[0], v2[0], t, &out[0]); - interpolateFloat(v1[1], v2[1], t, &out[1]); - interpolateFloat(v1[2], v2[2], t, &out[2]); - interpolateFloat(v1[3], v2[3], t, &out[3]); -} - -GL_FORCE_INLINE void interpolateColour(const uint8_t* v1, const uint8_t* v2, const float t, uint8_t* out) { - out[0] = v1[0] + (uint32_t) (((float) (v2[0] - v1[0])) * t); - out[1] = v1[1] + (uint32_t) (((float) (v2[1] - v1[1])) * t); - out[2] = v1[2] + (uint32_t) (((float) (v2[2] - v1[2])) * t); - out[3] = v1[3] + (uint32_t) (((float) (v2[3] - v1[3])) * t); -} - -const uint32_t VERTEX_CMD_EOL = 0xf0000000; -const uint32_t VERTEX_CMD = 0xe0000000; - -typedef struct { - Vertex vertex[3]; - VertexExtra extra[3]; - uint8_t visible; -} Triangle; - -void _glClipTriangle(const Triangle* triangle, const uint8_t visible, SubmissionTarget* target, const uint8_t flatShade) { - Vertex* last = NULL; - VertexExtra* veLast = NULL; - - const Vertex* vertices = triangle->vertex; - const VertexExtra* extras = triangle->extra; - - char* bgra = (char*) vertices[2].bgra; - - /* Used when flat shading is enabled */ - uint32_t finalColour = *((uint32_t*) bgra); - - Vertex tmp; - VertexExtra veTmp; - - uint8_t pushedCount = 0; - -#define IS_VISIBLE(x) (visible & (1 << (2 - (x)))) > 0 - -#define PUSH_VERT(vert, ve) \ - last = aligned_vector_push_back(&target->output->vector, vert, 1); \ - last->flags = VERTEX_CMD; \ - veLast = aligned_vector_push_back(target->extras, ve, 1); \ - ++pushedCount; - -#define CLIP_TO_PLANE(vert1, ve1, vert2, ve2) \ - do { \ - float t = _glClipLineToNearZ((vert1), (vert2), &tmp); \ - interpolateFloat((vert1)->w, (vert2)->w, t, &tmp.w); \ - interpolateVec2((vert1)->uv, (vert2)->uv, t, tmp.uv); \ - interpolateVec3((ve1)->nxyz, (ve2)->nxyz, t, veTmp.nxyz); \ - interpolateVec2((ve1)->st, (ve2)->st, t, veTmp.st); \ - if(flatShade) { \ - interpolateColour((const uint8_t*) &finalColour, (const uint8_t*) &finalColour, t, tmp.bgra); \ - } else { interpolateColour((vert1)->bgra, (vert2)->bgra, t, tmp.bgra); } \ - } while(0); \ - - uint8_t v0 = IS_VISIBLE(0); - uint8_t v1 = IS_VISIBLE(1); - uint8_t v2 = IS_VISIBLE(2); - if(v0) { - PUSH_VERT(&vertices[0], &extras[0]); - } - - if(v0 != v1) { - CLIP_TO_PLANE(&vertices[0], &extras[0], &vertices[1], &extras[1]); - PUSH_VERT(&tmp, &veTmp); - } - - if(v1) { - PUSH_VERT(&vertices[1], &extras[1]); - } - - if(v1 != v2) { - CLIP_TO_PLANE(&vertices[1], &extras[1], &vertices[2], &extras[2]); - PUSH_VERT(&tmp, &veTmp); - } - - if(v2) { - PUSH_VERT(&vertices[2], &extras[2]); - } - - if(v2 != v0) { - CLIP_TO_PLANE(&vertices[2], &extras[2], &vertices[0], &extras[0]); - PUSH_VERT(&tmp, &veTmp); - } - - if(pushedCount == 4) { - Vertex* prev = last - 1; - VertexExtra* prevVe = veLast - 1; - - tmp = *prev; - veTmp = *prevVe; - - *prev = *last; - *prevVe = *veLast; - - *last = tmp; - *veLast = veTmp; - - prev->flags = VERTEX_CMD; - last->flags = VERTEX_CMD_EOL; - } else { - /* Set the last flag to the end of the new strip */ - last->flags = VERTEX_CMD_EOL; - } -} - -static inline void markDead(Vertex* vert) { - vert->flags = VERTEX_CMD_EOL; - - // If we're debugging, wipe out the xyz -#ifndef NDEBUG - typedef union { - float* f; - int* i; - } cast; - - cast v1, v2, v3; - v1.f = &vert->xyz[0]; - v2.f = &vert->xyz[1]; - v3.f = &vert->xyz[2]; - - *v1.i = 0xDEADBEEF; - *v2.i = 0xDEADBEEF; - *v3.i = 0xDEADBEEF; -#endif -} - -#define B000 0 -#define B111 7 -#define B100 4 -#define B010 2 -#define B001 1 -#define B101 5 -#define B011 3 -#define B110 6 - -#define MAX_CLIP_TRIANGLES 255 - -void _glClipTriangleStrip(SubmissionTarget* target, uint8_t fladeShade) { - static Triangle TO_CLIP[MAX_CLIP_TRIANGLES]; - static uint8_t CLIP_COUNT = 0; - - CLIP_COUNT = 0; - - Vertex* vertex = _glSubmissionTargetStart(target); - const Vertex* end = _glSubmissionTargetEnd(target); - const Vertex* start = vertex; - - int32_t triangle = -1; - - /* Go to the (potential) end of the first triangle */ - vertex++; - - uint32_t vi1, vi2, vi3; - - while(vertex < end) { - vertex++; - triangle++; - - uint8_t even = (triangle % 2) == 0; - Vertex* v1 = (even) ? vertex - 2 : vertex - 1; - Vertex* v2 = (even) ? vertex - 1 : vertex - 2; - Vertex* v3 = vertex; - - /* Skip ahead if we don't have a complete triangle yet */ - if(v1->flags != VERTEX_CMD || v2->flags != VERTEX_CMD) { - triangle = -1; - continue; - } - - /* Indexes into extras array */ - vi1 = v1 - start; - vi2 = v2 - start; - vi3 = v3 - start; - - /* - * A vertex is visible if it's in front of the camera (W > 0) - * and it's in front of the near plane (Z > -W) - */ - -#define _VERT_VISIBLE(v) \ - (v->xyz[2] > -v->w) \ - - uint8_t visible = ( - (_VERT_VISIBLE(v1) ? 4 : 0) | - (_VERT_VISIBLE(v2) ? 2 : 0) | - (_VERT_VISIBLE(v3) ? 1 : 0) - ); - - switch(visible) { - case B111: - /* All visible? Do nothing */ - continue; - break; - case B000: - /* - It is not possible that this is any trangle except the first - in a strip. That's because: - - It's either the first triangle submitted - - A previous triangle must have been clipped and the strip - restarted behind the plane - - So, we effectively reboot the strip. We mark the first vertex - as the end (so it's ignored) then mark the next two as the - start of a new strip. Then if the next triangle crosses - back into view, we clip correctly. This will potentially - result in a bunch of pointlessly submitted vertices. - - FIXME: Skip submitting those verts - */ - - /* Even though this is always the first in the strip, it can also - * be the last */ - if(v3->flags == VERTEX_CMD_EOL) { - /* Wipe out the triangle */ - markDead(v1); - markDead(v2); - markDead(v3); - } else { - markDead(v1); - swapVertex(v2, v3); - triangle = -1; - v2->flags = VERTEX_CMD; - v3->flags = VERTEX_CMD; - } - break; - case B100: - case B010: - case B001: - case B101: - case B011: - case B110: - assert(CLIP_COUNT < MAX_CLIP_TRIANGLES); - - /* Store the triangle for clipping */ - TO_CLIP[CLIP_COUNT].vertex[0] = *v1; - TO_CLIP[CLIP_COUNT].vertex[1] = *v2; - TO_CLIP[CLIP_COUNT].vertex[2] = *v3; - - VertexExtra* ve1 = (VertexExtra*) aligned_vector_at(target->extras, vi1); - VertexExtra* ve2 = (VertexExtra*) aligned_vector_at(target->extras, vi2); - VertexExtra* ve3 = (VertexExtra*) aligned_vector_at(target->extras, vi3); - - TO_CLIP[CLIP_COUNT].extra[0] = *ve1; - TO_CLIP[CLIP_COUNT].extra[1] = *ve2; - TO_CLIP[CLIP_COUNT].extra[2] = *ve3; - - TO_CLIP[CLIP_COUNT].visible = visible; - ++CLIP_COUNT; - - /* - OK so here's the clever bit. If any triangle except - the first or last needs clipping, then the next one does aswell - (you can't draw a plane through a single triangle in the middle of a - strip, only 2+). This means we can clip in pairs which frees up two - vertices in the middle of the strip, which is exactly the space - we need to restart the triangle strip after the next triangle - */ - if(v3->flags == VERTEX_CMD_EOL) { - /* Last triangle in strip so end a vertex early */ - if(triangle == 0) { - // Wipe out the triangle completely - markDead(v1); - markDead(v2); - } else { - // End the strip - (vertex - 1)->flags = VERTEX_CMD_EOL; - } - - markDead(vertex); - - triangle = -1; - } else if(triangle == 0) { - /* First triangle in strip, remove first vertex */ - markDead(v1); - - v2->flags = VERTEX_CMD; - v3->flags = VERTEX_CMD; - - triangle = -1; - } else { - Vertex* v4 = v3 + 1; - uint32_t vi4 = v4 - start; - - TO_CLIP[CLIP_COUNT].vertex[0] = *v3; - TO_CLIP[CLIP_COUNT].vertex[1] = *v2; - TO_CLIP[CLIP_COUNT].vertex[2] = *v4; - - VertexExtra* ve4 = (VertexExtra*) aligned_vector_at(target->extras, vi4); - TO_CLIP[CLIP_COUNT].extra[0] = *(VertexExtra*) aligned_vector_at(target->extras, vi3); - TO_CLIP[CLIP_COUNT].extra[1] = *(VertexExtra*) aligned_vector_at(target->extras, vi2); - TO_CLIP[CLIP_COUNT].extra[2] = *ve4; - - visible = (_VERT_VISIBLE(v3) ? 4 : 0) | - (_VERT_VISIBLE(v2) ? 2 : 0) | - (_VERT_VISIBLE(v4) ? 1 : 0); - - TO_CLIP[CLIP_COUNT].visible = visible; - ++CLIP_COUNT; - - // Restart strip - triangle = -1; - - // Mark the second vertex as the end of the strip - (vertex - 1)->flags = VERTEX_CMD_EOL; - - if(v4->flags == VERTEX_CMD_EOL) { - markDead(v3); - markDead(v4); - } else { - // Swap the next vertices to start a new strip - swapVertex(v3, v4); - v3->flags = VERTEX_CMD; - v4->flags = VERTEX_CMD; - - /* Swap the extra data too */ - VertexExtra t = *ve4; - *ve3 = *ve4; - *ve4 = t; - } - } - break; - default: - break; - } - } - - /* Now, clip all the triangles and append them to the output */ - GLushort i; - for(i = 0; i < CLIP_COUNT; ++i) { - _glClipTriangle(&TO_CLIP[i], TO_CLIP[i].visible, target, fladeShade); - } -}