diff --git a/GL/clip.c b/GL/clip.c index 6928cc8..20be872 100644 --- a/GL/clip.c +++ b/GL/clip.c @@ -72,280 +72,236 @@ static inline void interpolateColour(const uint8_t* v1, const uint8_t* v2, const const uint32_t VERTEX_CMD_EOL = 0xf0000000; const uint32_t VERTEX_CMD = 0xe0000000; -void clipTriangleStrip2(const ClipVertex* vertices, const unsigned int count, AlignedVector* outBuffer) { +void clipTriangle(const ClipVertex* vertices, const uint8_t visible, AlignedVector* output) __attribute__((optimize("fast-math"))); +void clipTriangle(const ClipVertex* vertices, const uint8_t visible, AlignedVector* output) { + uint8_t i, c = 0; -} -void clipTriangleStrip(const ClipVertex* vertices, const unsigned int count, AlignedVector* outBuffer) __attribute__((optimize("fast-math"))); -void clipTriangleStrip(const ClipVertex* vertices, const unsigned int count, AlignedVector* outBuffer) { + uint8_t lastVisible = 255; + ClipVertex* last = NULL; - /* Clipping triangle strips is *hard* this is the algorithm we follow: - * - * - Treat each triangle in the strip individually. - * - If we find a triangle that needs clipping, treat it in isolation. - * - End the strip at the triangle - * - Generate a new single-triangle strip for it - * - Begin a new strip for the remainder of the strip - * - * There is probably more efficient way but there are so many different cases to handle that it's - * difficult to even write them down! - */ + for(i = 0; i < 4; ++i) { + uint8_t thisIndex = (i == 3) ? 0 : i; - uint32_t i; - uint32_t stripCount = 2; /* The number of vertices in the source strip so far */ + ClipVertex next; + next.flags = VERTEX_CMD; - ClipVertex* thisVertex = vertices + 1; + uint8_t thisVisible = (visible & (1 << (2 - thisIndex))) > 0; + if(i > 0) { + uint8_t lastIndex = (i == 3) ? 2 : thisIndex - 1; - for(i = 2; i < count; ++i) { - ++thisVertex; + if(lastVisible < 255 && lastVisible != thisVisible) { + const ClipVertex* v1 = &vertices[lastIndex]; + const ClipVertex* v2 = &vertices[thisIndex]; + float t; - if(stripCount < 2) { - stripCount++; - continue; - } + clipLineToNearZ(v1, v2, &next, &t); + interpolateFloat(v1->w, v2->w, t, &next.w); + interpolateVec3(v1->nxyz, v2->nxyz, t, next.nxyz); + interpolateVec2(v1->uv, v2->uv, t, next.uv); + interpolateVec2(v1->st, v2->st, t, next.st); + interpolateColour(v1->bgra, v2->bgra, t, next.bgra); - const ClipVertex* sourceTriangle[3] = { - thisVertex - 2, - thisVertex - 1, - thisVertex - }; - - /* If we're on an odd vertex, we need to swap the order of the first two vertices, as that's what - * triangle strips do */ - uint32_t swap = stripCount > 2 && (stripCount % 2 != 0); - const ClipVertex* v1 = swap ? sourceTriangle[1] : sourceTriangle[0]; - const ClipVertex* v2 = swap ? sourceTriangle[0] : sourceTriangle[1]; - const ClipVertex* v3 = sourceTriangle[2]; - - uint32_t visible = ((v1->w > 0) ? 4 : 0) | ((v2->w > 0) ? 2 : 0) | ((v3->w > 0) ? 1 : 0); - uint32_t startOfStrip = (i == 2) || (outBuffer->size > 2 && ((ClipVertex*) aligned_vector_back(outBuffer))->flags == VERTEX_CMD_EOL); - - /* All visible, we're fine! */ - if(visible == 0b111) { - if(startOfStrip) { - aligned_vector_push_back(outBuffer, v1, 1); - aligned_vector_push_back(outBuffer, v2, 1); + last = aligned_vector_push_back(output, &next, 1); + last->flags = VERTEX_CMD; + ++c; } - - aligned_vector_push_back(outBuffer, v3, 1); - } else if(visible == 0b000) { - /* Do nothing */ - continue; - } else if(visible == 0b100) { - /* Only the first vertex is visible */ - float t1 = 0, t2 = 0; - - ClipVertex output[3]; - - clipLineToNearZ(v1, v2, &output[1], &t1); - clipLineToNearZ(v1, v3, &output[2], &t2); - - interpolateFloat(v1->w, v2->w, t1, &output[1].w); - interpolateFloat(v1->w, v3->w, t2, &output[2].w); - - output[0] = *v1; - - /* Interpolate normals */ - interpolateVec3(v1->nxyz, v2->nxyz, t1, output[1].nxyz); - interpolateVec3(v1->nxyz, v3->nxyz, t2, output[2].nxyz); - - /* Interpolate texcoords */ - interpolateVec2(v1->uv, v2->uv, t1, output[1].uv); - interpolateVec2(v1->uv, v3->uv, t2, output[2].uv); - - interpolateVec2(v1->st, v2->st, t1, output[1].st); - interpolateVec2(v1->st, v3->st, t2, output[2].st); - - interpolateColour(v1->bgra, v2->bgra, t1, output[1].bgra); - interpolateColour(v1->bgra, v3->bgra, t2, output[2].bgra); - - output[0].flags = VERTEX_CMD; - output[1].flags = VERTEX_CMD; - output[2].flags = VERTEX_CMD_EOL; - - aligned_vector_push_back(outBuffer, output, 3); - } else if(visible == 0b010) { - /* Only the second vertex is visible */ - - float t1 = 0, t2 = 0; - - ClipVertex output[3]; - - clipLineToNearZ(v2, v1, &output[0], &t1); - clipLineToNearZ(v2, v3, &output[2], &t2); - - interpolateFloat(v2->w, v1->w, t1, &output[0].w); - interpolateFloat(v2->w, v3->w, t2, &output[2].w); - - output[1] = *v2; - - /* Interpolate normals */ - interpolateVec3(v2->nxyz, v1->nxyz, t1, output[0].nxyz); - interpolateVec3(v2->nxyz, v3->nxyz, t2, output[2].nxyz); - - /* Interpolate texcoords */ - interpolateVec2(v2->uv, v1->uv, t1, output[0].uv); - interpolateVec2(v2->uv, v3->uv, t2, output[2].uv); - - interpolateVec2(v2->st, v1->st, t1, output[0].st); - interpolateVec2(v2->st, v3->st, t2, output[2].st); - - interpolateColour(v2->bgra, v1->bgra, t1, output[0].bgra); - interpolateColour(v2->bgra, v3->bgra, t2, output[2].bgra); - - output[0].flags = VERTEX_CMD; - output[1].flags = VERTEX_CMD; - output[2].flags = VERTEX_CMD_EOL; - - aligned_vector_push_back(outBuffer, output, 3); - } else if(visible == 0b001) { - /* Only the third vertex is visible */ - - float t1 = 0, t2 = 0; - - ClipVertex output[3]; - - clipLineToNearZ(v3, v1, &output[0], &t1); - clipLineToNearZ(v3, v2, &output[1], &t2); - - interpolateFloat(v3->w, v1->w, t1, &output[0].w); - interpolateFloat(v3->w, v2->w, t2, &output[1].w); - - output[2] = *v3; - - /* Interpolate normals */ - interpolateVec3(v3->nxyz, v1->nxyz, t1, output[0].nxyz); - interpolateVec3(v3->nxyz, v2->nxyz, t2, output[1].nxyz); - - /* Interpolate texcoords */ - interpolateVec2(v3->uv, v1->uv, t1, output[0].uv); - interpolateVec2(v3->uv, v2->uv, t2, output[1].uv); - - interpolateVec2(v3->st, v1->st, t1, output[0].st); - interpolateVec2(v3->st, v2->st, t2, output[1].st); - - interpolateColour(v3->bgra, v1->bgra, t1, output[0].bgra); - interpolateColour(v3->bgra, v2->bgra, t2, output[1].bgra); - - output[0].flags = VERTEX_CMD; - output[1].flags = VERTEX_CMD; - output[2].flags = VERTEX_CMD_EOL; - - aligned_vector_push_back(outBuffer, output, 3); - } else if(visible == 0b110) { - /* Third vertex isn't visible */ - - float t1 = 0, t2 = 0; - - ClipVertex output[4]; - - clipLineToNearZ(v2, v3, &output[2], &t1); - clipLineToNearZ(v1, v3, &output[3], &t2); - - interpolateFloat(v2->w, v3->w, t1, &output[2].w); - interpolateFloat(v1->w, v3->w, t2, &output[3].w); - - output[0] = *v1; - output[1] = *v2; - - /* Interpolate normals */ - interpolateVec3(v2->nxyz, v3->nxyz, t1, output[2].nxyz); - interpolateVec3(v1->nxyz, v3->nxyz, t2, output[3].nxyz); - - /* Interpolate texcoords */ - interpolateVec2(v2->uv, v3->uv, t1, output[2].uv); - interpolateVec2(v1->uv, v3->uv, t2, output[3].uv); - - interpolateVec2(v2->st, v3->st, t1, output[2].st); - interpolateVec2(v1->st, v3->st, t2, output[3].st); - - interpolateColour(v2->bgra, v3->bgra, t1, output[2].bgra); - interpolateColour(v1->bgra, v3->bgra, t2, output[3].bgra); - - output[0].flags = VERTEX_CMD; - output[1].flags = VERTEX_CMD; - output[2].flags = VERTEX_CMD; - output[3].flags = VERTEX_CMD_EOL; - - aligned_vector_push_back(outBuffer, output, 4); - } else if(visible == 0b011) { - /* First vertex isn't visible, so let's clip along the lines to the second and third */ - float t1 = 0, t2 = 0; - - ClipVertex output[4]; - - clipLineToNearZ(v1, v2, &output[0], &t1); - clipLineToNearZ(v1, v3, &output[2], &t2); - - interpolateFloat(v1->w, v2->w, t1, &output[0].w); - interpolateFloat(v1->w, v3->w, t2, &output[2].w); - - output[1] = *v2; - output[3] = *v3; - - /* Interpolate normals */ - interpolateVec3(v1->nxyz, v2->nxyz, t1, output[0].nxyz); - interpolateVec3(v1->nxyz, v3->nxyz, t2, output[2].nxyz); - - /* Interpolate texcoords */ - interpolateVec2(v1->uv, v2->uv, t1, output[0].uv); - interpolateVec2(v1->uv, v3->uv, t2, output[2].uv); - - interpolateVec2(v1->st, v2->st, t1, output[0].st); - interpolateVec2(v1->st, v3->st, t2, output[2].st); - - interpolateColour(v1->bgra, v2->bgra, t1, output[0].bgra); - interpolateColour(v1->bgra, v3->bgra, t2, output[2].bgra); - - output[0].flags = VERTEX_CMD; - output[1].flags = VERTEX_CMD; - output[2].flags = VERTEX_CMD; - output[3].flags = VERTEX_CMD_EOL; - - aligned_vector_push_back(outBuffer, output, 4); - } else if(visible == 0b101) { - /* Second vertex isn't visible */ - float t1 = 0, t2 = 0; - - ClipVertex output[4]; - - clipLineToNearZ(v1, v2, &output[1], &t1); - clipLineToNearZ(v3, v2, &output[3], &t2); - - interpolateFloat(v1->w, v2->w, t1, &output[1].w); - interpolateFloat(v3->w, v2->w, t2, &output[3].w); - - output[0] = *v1; - output[2] = *v3; - - /* Interpolate normals */ - interpolateVec3(v1->nxyz, v2->nxyz, t1, output[1].nxyz); - interpolateVec3(v3->nxyz, v2->nxyz, t2, output[3].nxyz); - - /* Interpolate texcoords */ - interpolateVec2(v1->uv, v2->uv, t1, output[1].uv); - interpolateVec2(v3->uv, v2->uv, t2, output[3].uv); - - interpolateVec2(v1->st, v2->st, t1, output[1].st); - interpolateVec2(v3->st, v2->st, t2, output[3].st); - - interpolateColour(v1->bgra, v2->bgra, t1, output[1].bgra); - interpolateColour(v3->bgra, v2->bgra, t2, output[3].bgra); - - output[0].flags = VERTEX_CMD; - output[1].flags = VERTEX_CMD; - output[2].flags = VERTEX_CMD; - output[3].flags = VERTEX_CMD_EOL; - - aligned_vector_push_back(outBuffer, output, 4); } - /* If this vertex was the last in the list, reset the stripCount */ - if(thisVertex->flags == VERTEX_CMD_EOL) { - stripCount = 0; + if(thisVisible && i != 3) { + last = aligned_vector_push_back(output, &vertices[thisIndex], 1); + last->flags = VERTEX_CMD; + ++c; + } + + lastVisible = thisVisible; + } + + if(last) { + if(c == 4) { + /* Convert to two triangles */ + ClipVertex newVerts[3]; + newVerts[0] = *(last - 3); + newVerts[1] = *(last - 1); + newVerts[2] = *(last); + + (last - 1)->flags = VERTEX_CMD_EOL; + newVerts[0].flags = VERTEX_CMD; + newVerts[1].flags = VERTEX_CMD; + newVerts[2].flags = VERTEX_CMD_EOL; + + aligned_vector_resize(output, output->size - 1); + aligned_vector_push_back(output, newVerts, 3); } else { - stripCount++; + last->flags = VERTEX_CMD_EOL; } } } + +static inline void markDead(ClipVertex* vert) { + vert->flags = VERTEX_CMD_EOL; +} + +void clipTriangleStrip2(AlignedVector* vertices, uint32_t offset) { + /* Room for clipping 16 triangles */ + typedef struct { + ClipVertex vertex[3]; + uint8_t visible; + } Triangle; + + static Triangle TO_CLIP[256]; + static uint8_t CLIP_COUNT = 0; + + CLIP_COUNT = 0; + + uint32_t i = 0; + /* Skip the header */ + ClipVertex* header = (ClipVertex*) aligned_vector_at(vertices, offset); + ClipVertex* vertex = header + 1; + + uint32_t count = vertices->size - offset; + + int32_t triangle = 0; + + /* Start at 3 due to the header */ + for(i = 3; i < count; ++i, ++triangle) { + vertex = aligned_vector_at(vertices, offset + i); + + uint8_t even = (triangle % 2) == 0; + ClipVertex* v1 = (even) ? vertex - 2 : vertex - 1; + ClipVertex* v2 = (even) ? vertex - 1 : vertex - 2; + ClipVertex* v3 = vertex; + + uint8_t visible = ((v1->w > 0) ? 4 : 0) | ((v2->w > 0) ? 2 : 0) | ((v3->w > 0) ? 1 : 0); + + switch(visible) { + case 0b111: + /* All visible? Do nothing */ + continue; + break; + case 0b000: + /* + 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); + ClipVertex tmp = *v2; + *v2 = *v3; + *v3 = tmp; + + triangle = -1; + v2->flags = VERTEX_CMD; + v3->flags = VERTEX_CMD; + } + break; + case 0b100: + case 0b010: + case 0b001: + case 0b101: + case 0b011: + case 0b110: + /* 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; + 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(vertex - 2); + markDead(vertex - 1); + } else { + // End the strip + (vertex - 1)->flags = VERTEX_CMD_EOL; + } + + markDead(vertex); + } else if(triangle == 0) { + /* First triangle in strip, remove first vertex and swap latter two + to restart the strip */ + ClipVertex tmp = *v2; + *v2 = *v3; + *v3 = tmp; + + markDead(vertex - 2); + + (vertex - 1)->flags = VERTEX_CMD; + vertex->flags = VERTEX_CMD; + + triangle = -1; + } else { + ClipVertex* v4 = vertex + 1; + + TO_CLIP[CLIP_COUNT].vertex[0] = *v3; + TO_CLIP[CLIP_COUNT].vertex[1] = *v2; + TO_CLIP[CLIP_COUNT].vertex[2] = *v4; + + visible = ((v3->w > 0) ? 4 : 0) | ((v2->w > 0) ? 2 : 0) | ((v4->w > 0) ? 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(vertex); + markDead(v4); + } else { + /* Swap the next vertices to start a new strip */ + ClipVertex tmp = *vertex; + *vertex = *v4; + *v4 = tmp; + + vertex->flags = VERTEX_CMD; + v4->flags = VERTEX_CMD; + } + + i += 1; + } + break; + default: + break; + } + } + + /* Now, clip all the triangles and append them to the output */ + for(i = 0; i < CLIP_COUNT; ++i) { + clipTriangle(TO_CLIP[i].vertex, TO_CLIP[i].visible, vertices); + } +} diff --git a/GL/clip.h b/GL/clip.h index 44a3357..152e709 100644 --- a/GL/clip.h +++ b/GL/clip.h @@ -41,6 +41,7 @@ typedef struct { void clipLineToNearZ(const ClipVertex* v1, const ClipVertex* v2, ClipVertex* vout, float* t); void clipTriangleStrip(const ClipVertex* vertices, const unsigned int count, AlignedVector* outBuffer); +void clipTriangleStrip2(AlignedVector* vertices, uint32_t offset); #ifdef __cplusplus } diff --git a/GL/draw.c b/GL/draw.c index 6abf615..6926eb7 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -624,45 +624,10 @@ static void transform(ClipVertex* output, const GLsizei count) { } } -static GLsizei clip(AlignedVector* polylist, ClipVertex* output, const GLsizei count) { +static GLsizei clip(AlignedVector* polylist, uint32_t offset, const GLsizei count) { /* Perform clipping, generating new vertices as necessary */ - - static AlignedVector* CLIP_BUFFER = NULL; - - /* First entry into this, allocate the clip buffer */ - if(!CLIP_BUFFER) { - CLIP_BUFFER = (AlignedVector*) malloc(sizeof(AlignedVector)); - aligned_vector_init(CLIP_BUFFER, sizeof(ClipVertex)); - } - - /* Make sure we allocate roughly enough space */ - aligned_vector_reserve(CLIP_BUFFER, count * 1.5); - - /* Start from empty */ - aligned_vector_resize(CLIP_BUFFER, 0); - - /* Now perform clipping! */ - clipTriangleStrip(output, count, CLIP_BUFFER); - - /* Calculate the new required size for the poly list. This is the original size - * plus the difference in size between the original vertex count and the clip buffer - * count */ - GLsizei newSize = polylist->size + (CLIP_BUFFER->size - count); - - /* Copy the clip buffer over the vertices */ - aligned_vector_resize(polylist, newSize); - - GLsizei i = CLIP_BUFFER->size; - ClipVertex* dst = output; - ClipVertex* src = (ClipVertex*) CLIP_BUFFER->data; - while(i--) { - *dst = *src; - ++dst; - ++src; - } - - /* Return the new vertex count */ - return CLIP_BUFFER->size; + clipTriangleStrip2(polylist, offset); + return polylist->size; } static void mat_transform3(const float* xyz, const float* xyzOut, const uint32_t count, const uint32_t inStride, const uint32_t outStride) { @@ -832,7 +797,35 @@ static void submitVertices(GLenum mode, GLsizei first, GLsizei count, GLenum typ profiler_checkpoint("transform"); if(isClippingEnabled()) { - spaceNeeded = clip(&activeList->vector, start, spaceNeeded); + + uint32_t offset = ((start - 1) - (ClipVertex*) activeList->vector.data); + + /* Uncomment when debugging clipping + uint32_t i = 0; + fprintf(stderr, "=========\n"); + + for(i = offset; i < activeList->vector.size; ++i) { + ClipVertex* v = aligned_vector_at(&activeList->vector, i); + if(v->flags == 0xe0000000 || v->flags == 0xf0000000) { + fprintf(stderr, "(%f, %f, %f) -> %x\n", v->xyz[0], v->xyz[1], v->xyz[2], v->flags); + } else { + fprintf(stderr, "%x\n", *((uint32_t*)v)); + } + } */ + + spaceNeeded = clip(&activeList->vector, offset, spaceNeeded); + + /* Uncomment when debugging clipping + fprintf(stderr, "--------\n"); + for(i = offset; i < activeList->vector.size; ++i) { + ClipVertex* v = aligned_vector_at(&activeList->vector, i); + if(v->flags == 0xe0000000 || v->flags == 0xf0000000) { + fprintf(stderr, "(%f, %f, %f) -> %x\n", v->xyz[0], v->xyz[1], v->xyz[2], v->flags); + } else { + fprintf(stderr, "%x\n", *((uint32_t*)v)); + } + } + */ } profiler_checkpoint("clip"); @@ -880,7 +873,7 @@ static void submitVertices(GLenum mode, GLsizei first, GLsizei count, GLenum typ GLsizei i = spaceNeeded; while(i--) { vertex->uv[0] = vertex->st[0]; - vertex->uv[1] = vertex->st[1]; + vertex->uv[1] = vertex->st[1]; ++vertex; }