Merge pull request #24 from Kazade/new-clipping

New clipping algorithm
This commit is contained in:
Luke Benstead 2018-09-06 19:26:52 +01:00 committed by GitHub
commit d94ee888dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 251 additions and 301 deletions

488
GL/clip.c
View File

@ -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;
uint8_t lastVisible = 255;
ClipVertex* last = NULL;
for(i = 0; i < 4; ++i) {
uint8_t thisIndex = (i == 3) ? 0 : i;
ClipVertex next;
next.flags = VERTEX_CMD;
uint8_t thisVisible = (visible & (1 << (2 - thisIndex))) > 0;
if(i > 0) {
uint8_t lastIndex = (i == 3) ? 2 : thisIndex - 1;
if(lastVisible < 255 && lastVisible != thisVisible) {
const ClipVertex* v1 = &vertices[lastIndex];
const ClipVertex* v2 = &vertices[thisIndex];
float t;
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);
last = aligned_vector_push_back(output, &next, 1);
last->flags = VERTEX_CMD;
++c;
}
}
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 {
last->flags = VERTEX_CMD_EOL;
}
}
}
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) {
static inline void markDead(ClipVertex* vert) {
vert->flags = VERTEX_CMD_EOL;
}
/* 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!
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
*/
uint32_t i;
uint32_t stripCount = 2; /* The number of vertices in the source strip so far */
ClipVertex* thisVertex = vertices + 1;
for(i = 2; i < count; ++i) {
++thisVertex;
if(stripCount < 2) {
stripCount++;
continue;
}
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);
}
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;
/* 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 {
stripCount++;
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);
}
}

View File

@ -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
}

View File

@ -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");