Compare commits
28 Commits
master
...
mega-clipp
Author | SHA1 | Date | |
---|---|---|---|
|
1d09002720 | ||
|
585f8e836b | ||
|
9e861f0601 | ||
|
e6db0986ce | ||
|
85c60ffc5a | ||
|
174483941e | ||
|
82930e6faa | ||
|
8d26e1b2c4 | ||
|
6d814fbc5b | ||
|
30ef3efcc3 | ||
|
5b9a0502b5 | ||
|
34741420bc | ||
|
0e780ad271 | ||
|
3fde2abd3e | ||
|
2ab606f8dc | ||
|
753d1a5c82 | ||
|
a3c24e7913 | ||
|
4ee1bc0210 | ||
|
56c567600f | ||
|
0b2d575487 | ||
|
53c0afce1e | ||
|
b70a9df716 | ||
|
6eec9d4be3 | ||
|
0de40d4f27 | ||
|
40aedc2530 | ||
|
952c915ee8 | ||
|
26abe44336 | ||
|
36fe13095c |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@
|
|||
dc-build.sh
|
||||
.buildconfig
|
||||
GL/version.h
|
||||
tests/test_runner.cpp
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
stages:
|
||||
- build
|
||||
- test
|
||||
|
||||
build:sh4-gcc:
|
||||
stage: build
|
||||
|
@ -8,3 +9,20 @@ build:sh4-gcc:
|
|||
- source /etc/bash.bashrc
|
||||
- make clean
|
||||
- make samples
|
||||
|
||||
test:sh4-gcc:
|
||||
stage: test
|
||||
image: kazade/dreamcast-sdk
|
||||
before_script:
|
||||
- dnf install -y wget findutils && dnf clean all
|
||||
- wget https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
|
||||
- rpm -i rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
|
||||
- dnf update -y
|
||||
- dnf install -y lxdream
|
||||
script:
|
||||
- source /etc/bash.bashrc
|
||||
- make test
|
||||
- lxdream --help
|
||||
- lxdream -H tests/tests.elf | python3 utils/test_parser.py
|
||||
|
||||
|
||||
|
|
61
GL/clip.c
61
GL/clip.c
|
@ -85,16 +85,12 @@ 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;
|
||||
|
||||
|
@ -102,25 +98,22 @@ void _glClipTriangle(const Triangle* triangle, const uint8_t visible, Submission
|
|||
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) \
|
||||
#define PUSH_VERT(vert) \
|
||||
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) \
|
||||
#define CLIP_TO_PLANE(vert1, vert2) \
|
||||
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); \
|
||||
interpolateVec3((vert1)->nxyz, (vert2)->nxyz, t, tmp.nxyz); \
|
||||
interpolateVec2((vert1)->st, (vert2)->st, t, tmp.st); \
|
||||
if(flatShade) { \
|
||||
interpolateColour((const uint8_t*) &finalColour, (const uint8_t*) &finalColour, t, tmp.bgra); \
|
||||
} else { interpolateColour((vert1)->bgra, (vert2)->bgra, t, tmp.bgra); } \
|
||||
|
@ -130,44 +123,38 @@ void _glClipTriangle(const Triangle* triangle, const uint8_t visible, Submission
|
|||
uint8_t v1 = IS_VISIBLE(1);
|
||||
uint8_t v2 = IS_VISIBLE(2);
|
||||
if(v0) {
|
||||
PUSH_VERT(&vertices[0], &extras[0]);
|
||||
PUSH_VERT(&vertices[0]);
|
||||
}
|
||||
|
||||
if(v0 != v1) {
|
||||
CLIP_TO_PLANE(&vertices[0], &extras[0], &vertices[1], &extras[1]);
|
||||
PUSH_VERT(&tmp, &veTmp);
|
||||
CLIP_TO_PLANE(&vertices[0], &vertices[1]);
|
||||
PUSH_VERT(&tmp);
|
||||
}
|
||||
|
||||
if(v1) {
|
||||
PUSH_VERT(&vertices[1], &extras[1]);
|
||||
PUSH_VERT(&vertices[1]);
|
||||
}
|
||||
|
||||
if(v1 != v2) {
|
||||
CLIP_TO_PLANE(&vertices[1], &extras[1], &vertices[2], &extras[2]);
|
||||
PUSH_VERT(&tmp, &veTmp);
|
||||
CLIP_TO_PLANE(&vertices[1], &vertices[2]);
|
||||
PUSH_VERT(&tmp);
|
||||
}
|
||||
|
||||
if(v2) {
|
||||
PUSH_VERT(&vertices[2], &extras[2]);
|
||||
PUSH_VERT(&vertices[2]);
|
||||
}
|
||||
|
||||
if(v2 != v0) {
|
||||
CLIP_TO_PLANE(&vertices[2], &extras[2], &vertices[0], &extras[0]);
|
||||
PUSH_VERT(&tmp, &veTmp);
|
||||
CLIP_TO_PLANE(&vertices[2], &vertices[0]);
|
||||
PUSH_VERT(&tmp);
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -178,7 +165,7 @@ void _glClipTriangle(const Triangle* triangle, const uint8_t visible, Submission
|
|||
}
|
||||
|
||||
static inline void markDead(Vertex* vert) {
|
||||
vert->flags = VERTEX_CMD_EOL;
|
||||
vert->flags = DEAD; //VERTEX_CMD_EOL;
|
||||
|
||||
// If we're debugging, wipe out the xyz
|
||||
#ifndef NDEBUG
|
||||
|
@ -309,15 +296,6 @@ void _glClipTriangleStrip(SubmissionTarget* target, uint8_t fladeShade) {
|
|||
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;
|
||||
|
||||
|
@ -353,17 +331,11 @@ void _glClipTriangleStrip(SubmissionTarget* target, uint8_t fladeShade) {
|
|||
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);
|
||||
|
@ -385,11 +357,6 @@ void _glClipTriangleStrip(SubmissionTarget* target, uint8_t fladeShade) {
|
|||
swapVertex(v3, v4);
|
||||
v3->flags = VERTEX_CMD;
|
||||
v4->flags = VERTEX_CMD;
|
||||
|
||||
/* Swap the extra data too */
|
||||
VertexExtra t = *ve4;
|
||||
*ve3 = *ve4;
|
||||
*ve4 = t;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
111
GL/draw.c
111
GL/draw.c
|
@ -651,35 +651,35 @@ GL_FORCE_INLINE void _readUVData(const GLuint first, const GLuint count, Vertex*
|
|||
}
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE void _readSTData(const GLuint first, const GLuint count, VertexExtra* extra) {
|
||||
GL_FORCE_INLINE void _readSTData(const GLuint first, const GLuint count, Vertex* outpu) {
|
||||
const GLubyte ststride = (ST_POINTER.stride) ? ST_POINTER.stride : ST_POINTER.size * byte_size(ST_POINTER.type);
|
||||
const void* stptr = ((GLubyte*) ST_POINTER.ptr + (first * ststride));
|
||||
|
||||
ReadUVFunc func = calcReadUVFunc();
|
||||
GLubyte* out = (GLubyte*) extra[0].st;
|
||||
GLubyte* out = (GLubyte*) outpu[0].st;
|
||||
|
||||
ITERATE(count) {
|
||||
func(stptr, out);
|
||||
stptr += ststride;
|
||||
out += sizeof(VertexExtra);
|
||||
out += sizeof(Vertex);
|
||||
}
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE void _readNormalData(const GLuint first, const GLuint count, VertexExtra* extra) {
|
||||
GL_FORCE_INLINE void _readNormalData(const GLuint first, const GLuint count, Vertex* output) {
|
||||
const GLuint nstride = (NORMAL_POINTER.stride) ? NORMAL_POINTER.stride : NORMAL_POINTER.size * byte_size(NORMAL_POINTER.type);
|
||||
const void* nptr = ((GLubyte*) NORMAL_POINTER.ptr + (first * nstride));
|
||||
|
||||
ReadNormalFunc func = calcReadNormalFunc();
|
||||
GLubyte* out = (GLubyte*) extra[0].nxyz;
|
||||
GLubyte* out = (GLubyte*) output[0].nxyz;
|
||||
|
||||
ITERATE(count) {
|
||||
func(nptr, out);
|
||||
nptr += nstride;
|
||||
out += sizeof(VertexExtra);
|
||||
out += sizeof(Vertex);
|
||||
}
|
||||
|
||||
if(_glIsNormalizeEnabled()) {
|
||||
GLubyte* ptr = (GLubyte*) extra->nxyz;
|
||||
GLubyte* ptr = (GLubyte*) output->nxyz;
|
||||
ITERATE(count) {
|
||||
GLfloat* n = (GLfloat*) ptr;
|
||||
float temp = n[0] * n[0];
|
||||
|
@ -691,7 +691,7 @@ GL_FORCE_INLINE void _readNormalData(const GLuint first, const GLuint count, Ver
|
|||
n[1] *= ilength;
|
||||
n[2] *= ilength;
|
||||
|
||||
ptr += sizeof(VertexExtra);
|
||||
ptr += sizeof(Vertex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -724,7 +724,6 @@ static void generateElements(
|
|||
GLubyte* nxyz;
|
||||
|
||||
Vertex* output = _glSubmissionTargetStart(target);
|
||||
VertexExtra* ve = aligned_vector_at(target->extras, 0);
|
||||
|
||||
uint32_t i = first;
|
||||
uint32_t idx = 0;
|
||||
|
@ -762,12 +761,11 @@ static void generateElements(
|
|||
pos_func(xyz, (GLubyte*) output->xyz);
|
||||
uv_func(uv, (GLubyte*) output->uv);
|
||||
diffuse_func(bgra, output->bgra);
|
||||
st_func(st, (GLubyte*) ve->st);
|
||||
normal_func(nxyz, (GLubyte*) ve->nxyz);
|
||||
st_func(st, (GLubyte*) output->st);
|
||||
normal_func(nxyz, (GLubyte*) output->nxyz);
|
||||
|
||||
output->flags = PVR_CMD_VERTEX;
|
||||
++output;
|
||||
++ve;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -804,10 +802,8 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei
|
|||
}
|
||||
}
|
||||
|
||||
VertexExtra* ve = aligned_vector_at(target->extras, 0);
|
||||
|
||||
_readNormalData(first, count, ve);
|
||||
_readSTData(first, count, ve);
|
||||
_readNormalData(first, count, start);
|
||||
_readSTData(first, count, start);
|
||||
|
||||
} else {
|
||||
generateElements(
|
||||
|
@ -864,16 +860,6 @@ static void transform(SubmissionTarget* target) {
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
uint8_t* dataIn = (uint8_t*) xyz;
|
||||
uint8_t* dataOut = (uint8_t*) xyzOut;
|
||||
|
@ -917,38 +903,18 @@ static void light(SubmissionTarget* target) {
|
|||
|
||||
/* Perform lighting calculations and manipulate the colour */
|
||||
Vertex* vertex = _glSubmissionTargetStart(target);
|
||||
VertexExtra* extra = aligned_vector_at(target->extras, 0);
|
||||
EyeSpaceData* eye_space = (EyeSpaceData*) eye_space_data->data;
|
||||
|
||||
_glMatrixLoadModelView();
|
||||
mat_transform3(vertex->xyz, eye_space->xyz, target->count, sizeof(Vertex), sizeof(EyeSpaceData));
|
||||
|
||||
_glMatrixLoadNormal();
|
||||
mat_transform_normal3(extra->nxyz, eye_space->n, target->count, sizeof(VertexExtra), sizeof(EyeSpaceData));
|
||||
mat_transform_normal3(vertex->nxyz, eye_space->n, target->count, sizeof(Vertex), sizeof(EyeSpaceData));
|
||||
|
||||
EyeSpaceData* ES = aligned_vector_at(eye_space_data, 0);
|
||||
_glPerformLighting(vertex, ES, target->count);
|
||||
}
|
||||
|
||||
#define PVR_MIN_Z 0.2f
|
||||
#define PVR_MAX_Z 1.0 + PVR_MIN_Z
|
||||
|
||||
GL_FORCE_INLINE void divide(SubmissionTarget* target) {
|
||||
TRACE();
|
||||
|
||||
/* Perform perspective divide on each vertex */
|
||||
Vertex* vertex = _glSubmissionTargetStart(target);
|
||||
|
||||
ITERATE(target->count) {
|
||||
float f = MATH_Fast_Invert(vertex->w);
|
||||
vertex->xyz[0] *= f;
|
||||
vertex->xyz[1] *= f;
|
||||
vertex->xyz[2] *= f;
|
||||
vertex->xyz[2] = MAX(1.0f - (vertex->xyz[2] * 0.5f + 0.5f), 0.0001f);
|
||||
++vertex;
|
||||
}
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE void push(PVRHeader* header, GLboolean multiTextureHeader, PolyList* activePolyList, GLshort textureUnit) {
|
||||
TRACE();
|
||||
|
||||
|
@ -1005,18 +971,13 @@ GL_FORCE_INLINE void submitVertices(GLenum mode, GLsizei first, GLuint count, GL
|
|||
}
|
||||
|
||||
static SubmissionTarget* target = NULL;
|
||||
static AlignedVector extras;
|
||||
|
||||
/* Initialization of the target and extras */
|
||||
/* Initialization of the target */
|
||||
if(!target) {
|
||||
target = (SubmissionTarget*) malloc(sizeof(SubmissionTarget));
|
||||
target->extras = NULL;
|
||||
target->count = 0;
|
||||
target->output = NULL;
|
||||
target->header_offset = target->start_offset = 0;
|
||||
|
||||
aligned_vector_init(&extras, sizeof(VertexExtra));
|
||||
target->extras = &extras;
|
||||
}
|
||||
|
||||
GLboolean doMultitexture, doTexture, doLighting;
|
||||
|
@ -1057,9 +1018,6 @@ GL_FORCE_INLINE void submitVertices(GLenum mode, GLsizei first, GLuint count, GL
|
|||
|
||||
assert(target->count);
|
||||
|
||||
/* Make sure we have enough room for all the "extra" data */
|
||||
aligned_vector_resize(&extras, target->count);
|
||||
|
||||
/* Make room for the vertices and header */
|
||||
aligned_vector_extend(&target->output->vector, target->count + 1);
|
||||
generate(target, mode, first, count, (GLubyte*) indices, type);
|
||||
|
@ -1069,41 +1027,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
|
||||
|
||||
}
|
||||
|
||||
divide(target);
|
||||
push(_glSubmissionTargetHeader(target), GL_FALSE, target->output, 0);
|
||||
|
||||
/*
|
||||
|
@ -1139,12 +1062,10 @@ GL_FORCE_INLINE void submitVertices(GLenum mode, GLsizei first, GLuint count, GL
|
|||
PVRHeader* mtHeader = (PVRHeader*) vertex++;
|
||||
|
||||
/* Replace the UV coordinates with the ST ones */
|
||||
VertexExtra* ve = aligned_vector_at(target->extras, 0);
|
||||
ITERATE(target->count) {
|
||||
vertex->uv[0] = ve->st[0];
|
||||
vertex->uv[1] = ve->st[1];
|
||||
vertex->uv[0] = vertex->st[0];
|
||||
vertex->uv[1] = vertex->st[1];
|
||||
++vertex;
|
||||
++ve;
|
||||
}
|
||||
|
||||
/* Send the buffer again to the transparent list */
|
||||
|
|
527
GL/flush.c
527
GL/flush.c
|
@ -1,6 +1,7 @@
|
|||
|
||||
|
||||
#include <kos.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../include/glkos.h"
|
||||
#include "../containers/aligned_vector.h"
|
||||
|
@ -8,6 +9,10 @@
|
|||
#include "profiler.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "flush.h"
|
||||
|
||||
#define CLIP_DEBUG 0
|
||||
|
||||
#define TA_SQ_ADDR (unsigned int *)(void *) \
|
||||
(0xe0000000 | (((unsigned long)0x10000000) & 0x03ffffe0))
|
||||
|
||||
|
@ -15,13 +20,516 @@ static PolyList OP_LIST;
|
|||
static PolyList PT_LIST;
|
||||
static PolyList TR_LIST;
|
||||
|
||||
static const int STRIDE = sizeof(Vertex) / sizeof(GLuint);
|
||||
|
||||
#define CLIP_TO_PLANE(vert1, vert2) \
|
||||
do { \
|
||||
float t = _glClipLineToNearZ((vert1), (vert2), out); \
|
||||
interpolateVec2((vert1)->uv, (vert2)->uv, t, out->uv); \
|
||||
interpolateVec3((vert1)->nxyz, (vert2)->nxyz, t, out->nxyz); \
|
||||
interpolateVec2((vert1)->st, (vert2)->st, t, out->st); \
|
||||
interpolateColour((vert1)->bgra, (vert2)->bgra, t, out->bgra); \
|
||||
} while(0); \
|
||||
|
||||
|
||||
GL_FORCE_INLINE float _glClipLineToNearZ(const Vertex* v1, const Vertex* v2, Vertex* vout) {
|
||||
TRACE();
|
||||
|
||||
const float d0 = v1->w;
|
||||
const float d1 = v2->w;
|
||||
|
||||
assert(isVisible(v1) ^ isVisible(v2));
|
||||
|
||||
/* 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]);
|
||||
vout->w = MATH_fmac(v2->w - v1->w, t, v1->w);
|
||||
|
||||
#if CLIP_DEBUG
|
||||
printf(
|
||||
"(%f, %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], vout->w
|
||||
);
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static Vertex* interpolate_vertex(const Vertex* v0, const Vertex* v1, Vertex* out) {
|
||||
/* If v0 is in front of the near plane, and v1 is behind the near plane, this
|
||||
* generates a vertex *on* the near plane */
|
||||
CLIP_TO_PLANE(v0, v1);
|
||||
|
||||
/* We can't have a W == 0, or we'll get a divide by zero. If we have a W < 0
|
||||
* then our clipping has gone wrong! */
|
||||
assert(out->w > 0.0f);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE ListIterator* header_reset(ListIterator* it, Vertex* header) {
|
||||
it->active = header;
|
||||
it->visibility = 0;
|
||||
it->triangle_count = 0;
|
||||
it->stack_idx = -1;
|
||||
return it;
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE Vertex* current_postinc(ListIterator* it) {
|
||||
if(it->remaining == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
it->remaining--;
|
||||
Vertex* current = it->src;
|
||||
it->src++;
|
||||
return current;
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE Vertex* push_stack(ListIterator* it) {
|
||||
#if CLIP_DEBUG
|
||||
printf("Using stack: %d\n", it->stack_idx + 1);
|
||||
#endif
|
||||
|
||||
assert(it->stack_idx + 1 < MAX_STACK);
|
||||
return &it->stack[++it->stack_idx];
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE GLboolean shift(ListIterator* it, Vertex* new_vertex) {
|
||||
TRACE();
|
||||
|
||||
/*
|
||||
* Shifts in a new vertex, dropping the oldest. If
|
||||
* new_vertex is NULL it will return GL_FALSE (but still
|
||||
* shift) */
|
||||
if(new_vertex) {
|
||||
it->triangle_count++;
|
||||
} else {
|
||||
// We shifted a NULL, so we're reducing
|
||||
// the available count rather than increasing
|
||||
it->triangle_count--;
|
||||
}
|
||||
|
||||
if(it->triangle_count > 3) {
|
||||
it->triangle_count = 3;
|
||||
}
|
||||
|
||||
it->triangle[0] = it->triangle[1];
|
||||
it->triangle[1] = it->triangle[2];
|
||||
it->triangle[2] = new_vertex;
|
||||
|
||||
it->visibility <<= 1;
|
||||
it->visibility &= 7;
|
||||
it->visibility += (new_vertex) ? isVisible(new_vertex) : 0;
|
||||
|
||||
return new_vertex != NULL;
|
||||
}
|
||||
|
||||
static ListIterator* finish_clip(ListIterator* it) {
|
||||
/* When we've clipped a triangle, we either need to reduce
|
||||
* the triangle_count so that next iteration we move to the next
|
||||
* triangle OR we need to shift away the vertices in the triangle
|
||||
* buffer entirely so next iteration starts a new strip.
|
||||
* FIXME: Do we need to swap the verts in the triangle buffer for winding? */
|
||||
|
||||
if(it->src && isVertex(it->src)) {
|
||||
/* Continue */
|
||||
it->triangle_count--;
|
||||
} else {
|
||||
/* Restart strip */
|
||||
while(it->triangle_count) {
|
||||
shift(it, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
static ListIterator* clip100(ListIterator* it) {
|
||||
#if CLIP_DEBUG
|
||||
printf(">> clip100\n");
|
||||
#endif
|
||||
|
||||
TRACE();
|
||||
|
||||
/* First visible only */
|
||||
Vertex* gen3 = push_stack(it);
|
||||
Vertex* gen2 = push_stack(it);
|
||||
Vertex* gen1 = push_stack(it);
|
||||
|
||||
assert(gen1);
|
||||
assert(gen2);
|
||||
assert(gen3);
|
||||
|
||||
*gen1 = *it->triangle[0];
|
||||
|
||||
interpolate_vertex(it->triangle[0], it->triangle[1], gen2);
|
||||
interpolate_vertex(it->triangle[0], it->triangle[2], gen3);
|
||||
|
||||
gen1->flags = PVR_CMD_VERTEX;
|
||||
gen2->flags = PVR_CMD_VERTEX;
|
||||
gen3->flags = PVR_CMD_VERTEX_EOL;
|
||||
|
||||
assert(gen1);
|
||||
assert(gen2);
|
||||
assert(gen3);
|
||||
|
||||
assert(isVisible(gen1));
|
||||
assert(isVisible(gen2));
|
||||
assert(isVisible(gen3));
|
||||
|
||||
assert(isVertex(gen1));
|
||||
assert(isVertex(gen2));
|
||||
assert(isVertex(gen3));
|
||||
|
||||
it->active = gen1;
|
||||
it->stack_idx--;
|
||||
|
||||
return finish_clip(it);
|
||||
}
|
||||
|
||||
static ListIterator* clip110(ListIterator* it) {
|
||||
#if CLIP_DEBUG
|
||||
printf(">> clip110\n");
|
||||
#endif
|
||||
|
||||
TRACE();
|
||||
|
||||
/* First two visible. so we need to create 2 new vertices from
|
||||
* A -> C, and B -> C. */
|
||||
Vertex* gen4 = push_stack(it);
|
||||
Vertex* gen3 = push_stack(it);
|
||||
Vertex* gen2 = push_stack(it);
|
||||
Vertex* gen1 = push_stack(it);
|
||||
|
||||
gen1->flags = PVR_CMD_VERTEX;
|
||||
gen2->flags = PVR_CMD_VERTEX;
|
||||
gen3->flags = PVR_CMD_VERTEX;
|
||||
gen4->flags = PVR_CMD_VERTEX_EOL;
|
||||
|
||||
*gen1 = *it->triangle[0];
|
||||
*gen2 = *it->triangle[1];
|
||||
|
||||
interpolate_vertex(it->triangle[0], it->triangle[2], gen3);
|
||||
interpolate_vertex(it->triangle[1], it->triangle[2], gen4);
|
||||
|
||||
/* Return A */
|
||||
it->active = gen1;
|
||||
it->stack_idx--;
|
||||
|
||||
assert(isVisible(gen1));
|
||||
assert(isVisible(gen2));
|
||||
assert(isVisible(gen3));
|
||||
assert(isVisible(gen4));
|
||||
assert(isVertex(gen1));
|
||||
assert(isVertex(gen2));
|
||||
assert(isVertex(gen3));
|
||||
assert(isVertex(gen4));
|
||||
|
||||
return finish_clip(it);
|
||||
}
|
||||
|
||||
static ListIterator* clip101(ListIterator* it) {
|
||||
#if CLIP_DEBUG
|
||||
printf(">> clip101\n");
|
||||
#endif
|
||||
|
||||
TRACE();
|
||||
/* First visible and last visible. Need to create two
|
||||
* vertices in between first and last! */
|
||||
|
||||
Vertex* gen4 = push_stack(it);
|
||||
Vertex* gen3 = push_stack(it);
|
||||
Vertex* gen2 = push_stack(it);
|
||||
Vertex* gen1 = push_stack(it);
|
||||
|
||||
/* First and last need to be the same*/
|
||||
*gen1 = *it->triangle[0];
|
||||
*gen2 = *it->triangle[2];
|
||||
|
||||
gen1->flags = PVR_CMD_VERTEX;
|
||||
gen2->flags = PVR_CMD_VERTEX;
|
||||
gen3->flags = PVR_CMD_VERTEX;
|
||||
gen4->flags = PVR_CMD_VERTEX_EOL; /* 4 is now last in the list */
|
||||
|
||||
interpolate_vertex(it->triangle[0], it->triangle[1], gen3);
|
||||
interpolate_vertex(it->triangle[1], it->triangle[2], gen4);
|
||||
|
||||
it->active = gen1;
|
||||
it->stack_idx--;
|
||||
|
||||
return finish_clip(it);
|
||||
}
|
||||
|
||||
static ListIterator* clip011(ListIterator* it) {
|
||||
#if CLIP_DEBUG
|
||||
printf(">> clip011\n");
|
||||
#endif
|
||||
|
||||
TRACE();
|
||||
/* Last two visible, we need to create two new vertices */
|
||||
|
||||
Vertex* gen4 = push_stack(it);
|
||||
Vertex* gen3 = push_stack(it);
|
||||
Vertex* gen2 = push_stack(it);
|
||||
Vertex* gen1 = push_stack(it);
|
||||
|
||||
*gen3 = *it->triangle[1];
|
||||
*gen4 = *it->triangle[2];
|
||||
|
||||
gen1->flags = PVR_CMD_VERTEX;
|
||||
gen2->flags = PVR_CMD_VERTEX;
|
||||
gen3->flags = PVR_CMD_VERTEX;
|
||||
gen4->flags = PVR_CMD_VERTEX_EOL; /* 4 is now last in the list */
|
||||
|
||||
interpolate_vertex(it->triangle[0], it->triangle[1], gen1);
|
||||
interpolate_vertex(it->triangle[0], it->triangle[2], gen2);
|
||||
|
||||
it->active = gen1;
|
||||
it->stack_idx--;
|
||||
|
||||
return finish_clip(it);
|
||||
}
|
||||
|
||||
static ListIterator* clip001(ListIterator* it) {
|
||||
#if CLIP_DEBUG
|
||||
printf(">> clip001\n");
|
||||
#endif
|
||||
|
||||
TRACE();
|
||||
/* Last visible? Just replace the first two vertices */
|
||||
|
||||
Vertex* gen3 = push_stack(it);
|
||||
Vertex* gen2 = push_stack(it);
|
||||
Vertex* gen1 = push_stack(it);
|
||||
|
||||
*gen3 = *it->triangle[2];
|
||||
|
||||
gen1->flags = PVR_CMD_VERTEX;
|
||||
gen2->flags = PVR_CMD_VERTEX;
|
||||
gen3->flags = PVR_CMD_VERTEX_EOL;
|
||||
|
||||
interpolate_vertex(it->triangle[0], it->triangle[2], gen1);
|
||||
interpolate_vertex(it->triangle[2], it->triangle[1], gen2);
|
||||
|
||||
it->active = gen1;
|
||||
it->stack_idx--;
|
||||
|
||||
return finish_clip(it);
|
||||
}
|
||||
|
||||
static ListIterator* clip010(ListIterator* it) {
|
||||
#if CLIP_DEBUG
|
||||
printf(">> clip010\n");
|
||||
#endif
|
||||
|
||||
TRACE();
|
||||
/* First and last need replacing */
|
||||
|
||||
Vertex* gen3 = push_stack(it);
|
||||
Vertex* gen2 = push_stack(it);
|
||||
Vertex* gen1 = push_stack(it);
|
||||
|
||||
*gen2 = *it->triangle[1];
|
||||
|
||||
gen1->flags = PVR_CMD_VERTEX;
|
||||
gen2->flags = PVR_CMD_VERTEX;
|
||||
gen3->flags = PVR_CMD_VERTEX_EOL;
|
||||
|
||||
interpolate_vertex(it->triangle[0], it->triangle[1], gen1);
|
||||
interpolate_vertex(it->triangle[1], it->triangle[2], gen3);
|
||||
|
||||
it->active = gen1;
|
||||
it->stack_idx--;
|
||||
|
||||
return finish_clip(it);
|
||||
}
|
||||
|
||||
ListIterator* _glIteratorBegin(void* src, int n) {
|
||||
TRACE();
|
||||
|
||||
ListIterator* it = (ListIterator*) malloc(sizeof(ListIterator));
|
||||
it->remaining = n - 1;
|
||||
it->active = (Vertex*) src;
|
||||
it->src = it->active + 1;
|
||||
it->stack_idx = -1;
|
||||
it->triangle_count = 0;
|
||||
it->visibility = 0;
|
||||
it->triangle[0] = it->triangle[1] = it->triangle[2] = NULL;
|
||||
return (n) ? it : NULL;
|
||||
}
|
||||
|
||||
ListIterator* _glIteratorNext(ListIterator* it) {
|
||||
TRACE();
|
||||
|
||||
/* Return any vertices we generated */
|
||||
if(it->stack_idx > -1) {
|
||||
#if CLIP_DEBUG
|
||||
printf("Yielding stack: %d\n", it->stack_idx);
|
||||
#endif
|
||||
|
||||
it->active = &it->stack[it->stack_idx--];
|
||||
return it;
|
||||
}
|
||||
|
||||
/* None remaining in the list, and the stack is empty */
|
||||
if(!it->remaining && !it->triangle_count) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
_Bool retry = 1;
|
||||
while(retry) {
|
||||
retry = 0;
|
||||
assert(it);
|
||||
assert(it->src);
|
||||
|
||||
_Bool is_header = !isVertex(it->src);
|
||||
|
||||
/* If we hit a header, and we have vertices still
|
||||
* not returned, shift them out and return them */
|
||||
if(is_header && it->triangle_count) {
|
||||
shift(it, NULL);
|
||||
it->active = it->triangle[0];
|
||||
return it;
|
||||
} else if(is_header) {
|
||||
return header_reset(it, current_postinc(it));
|
||||
} else {
|
||||
/* Make sure we have a full triangle of vertices */
|
||||
while(it->triangle_count < 3) {
|
||||
if(!shift(it, current_postinc(it))) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* OK, by this point we should have info for a complete triangle
|
||||
* including visibility */
|
||||
switch(it->visibility) {
|
||||
case B111:
|
||||
#if CLIP_DEBUG
|
||||
printf("111\n");
|
||||
#endif
|
||||
|
||||
/* Totally visible, return the first vertex */
|
||||
it->active = it->triangle[0];
|
||||
it->triangle_count--;
|
||||
return it;
|
||||
break;
|
||||
case B100: {
|
||||
return clip100(it);
|
||||
} break;
|
||||
case B101: {
|
||||
return clip101(it);
|
||||
} break;
|
||||
case B110: {
|
||||
return clip110(it);
|
||||
} break;
|
||||
case B011: {
|
||||
return clip011(it);
|
||||
} break;
|
||||
case B001: {
|
||||
return clip001(it);
|
||||
} break;
|
||||
case B010: {
|
||||
return clip010(it);
|
||||
} break;
|
||||
case B000: {
|
||||
#if CLIP_DEBUG
|
||||
printf("000\n");
|
||||
#endif
|
||||
|
||||
/* If a triangle is invisible, there are 3 situations:
|
||||
*
|
||||
* 1. It's the last triangle, so we end here
|
||||
* 2. It was the last triangle before a header, in which
|
||||
* case we return the header
|
||||
* 3. It was not the last triangle in the strip, so we just
|
||||
* go around again to shift the next vertex (we don't return
|
||||
* anything because it's invisible...) */
|
||||
assert(it);
|
||||
assert(it->src);
|
||||
|
||||
if(!it->remaining) {
|
||||
return NULL;
|
||||
} else if(!isVertex(it->src)) {
|
||||
return header_reset(it, current_postinc(it));
|
||||
} else {
|
||||
it->triangle_count--;
|
||||
retry = 1;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break; // Impossible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(0 && "Fell threw!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE void perspective_divide(Vertex* vertex) {
|
||||
float f = MATH_Fast_Invert(vertex->w);
|
||||
vertex->xyz[0] *= f;
|
||||
vertex->xyz[1] *= f;
|
||||
vertex->xyz[2] *= f;
|
||||
vertex->xyz[2] = MAX(1.0f - (vertex->xyz[2] * 0.5f + 0.5f), 0.0001f);
|
||||
}
|
||||
|
||||
static void pvr_list_submit(void *src, int n) {
|
||||
GLuint *d = TA_SQ_ADDR;
|
||||
GLuint *s = src;
|
||||
|
||||
/* First entry is assumed to always be a header and therefore
|
||||
* always submitted (e.g. not clipped) */
|
||||
|
||||
ListIterator* it = _glIteratorBegin(src, n);
|
||||
|
||||
/* fill/write queues as many times necessary */
|
||||
while(n--) {
|
||||
__asm__("pref @%0" : : "r"(s + 8)); /* prefetch 32 bytes for next loop */
|
||||
while(it) {
|
||||
__asm__("pref @%0" : : "r"(it->active + 1)); /* prefetch 64 bytes for next loop */
|
||||
|
||||
if(isVertex(it->active)) {
|
||||
perspective_divide(it->active);
|
||||
}
|
||||
|
||||
GLuint* s = (GLuint*) it->active;
|
||||
|
||||
d[0] = *(s++);
|
||||
d[1] = *(s++);
|
||||
d[2] = *(s++);
|
||||
|
@ -30,13 +538,20 @@ static void pvr_list_submit(void *src, int n) {
|
|||
d[5] = *(s++);
|
||||
d[6] = *(s++);
|
||||
d[7] = *(s++);
|
||||
|
||||
/* This prefetch actually commits 32 bytes to the SQ */
|
||||
__asm__("pref @%0" : : "r"(d));
|
||||
d += 8;
|
||||
|
||||
d += 8; /* Move to the next SQ address */
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
}
|
||||
|
||||
/* Wait for both store queues to complete */
|
||||
d = (GLuint *)0xe0000000;
|
||||
d[0] = d[8] = 0;
|
||||
|
||||
free(it);
|
||||
}
|
||||
|
||||
static void _glInitPVR(GLboolean autosort, GLboolean fsaa) {
|
||||
|
@ -45,8 +560,8 @@ static void _glInitPVR(GLboolean autosort, GLboolean fsaa) {
|
|||
{PVR_BINSIZE_32, PVR_BINSIZE_0, PVR_BINSIZE_32, PVR_BINSIZE_0, PVR_BINSIZE_32},
|
||||
PVR_VERTEX_BUF_SIZE, /* Vertex buffer size */
|
||||
0, /* No DMA */
|
||||
fsaa, /* No FSAA */
|
||||
(autosort) ? 0 : 1 /* Disable translucent auto-sorting to match traditional GL */
|
||||
fsaa,
|
||||
(autosort) ? 0 : 1
|
||||
};
|
||||
|
||||
pvr_init(¶ms);
|
||||
|
|
66
GL/flush.h
Normal file
66
GL/flush.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#pragma once
|
||||
|
||||
#include "private.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MAX_STACK 4
|
||||
|
||||
#define B000 0
|
||||
#define B111 7
|
||||
#define B100 4
|
||||
#define B010 2
|
||||
#define B001 1
|
||||
#define B101 5
|
||||
#define B011 3
|
||||
#define B110 6
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* Remaining vertices in the source list */
|
||||
int remaining;
|
||||
|
||||
/* Current position in the source list */
|
||||
Vertex* src;
|
||||
|
||||
/* Vertex to read from (this may not exist in the source list) */
|
||||
Vertex* active;
|
||||
|
||||
/* Sliding window into the source view */
|
||||
Vertex* triangle[3];
|
||||
|
||||
/* Stack of temporary vertices */
|
||||
Vertex stack[MAX_STACK];
|
||||
int8_t stack_idx;
|
||||
|
||||
/* < 8. Bitmask of the last 3 vertices */
|
||||
uint8_t visibility;
|
||||
uint8_t triangle_count;
|
||||
uint8_t padding;
|
||||
} ListIterator;
|
||||
|
||||
ListIterator* _glIteratorBegin(void* src, int n);
|
||||
|
||||
GL_FORCE_INLINE GLboolean isVertex(const Vertex* vertex) {
|
||||
assert(vertex);
|
||||
|
||||
return (
|
||||
vertex->flags == PVR_CMD_VERTEX ||
|
||||
vertex->flags == PVR_CMD_VERTEX_EOL
|
||||
);
|
||||
}
|
||||
|
||||
GL_FORCE_INLINE GLboolean isVisible(const Vertex* vertex) {
|
||||
TRACE();
|
||||
|
||||
assert(vertex != NULL);
|
||||
return vertex->w > 0.0000f; // && vertex->xyz[2] >= -vertex->w;
|
||||
}
|
||||
|
||||
ListIterator* _glIteratorNext(ListIterator* it);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
45
GL/private.h
45
GL/private.h
|
@ -13,11 +13,20 @@
|
|||
#include "../containers/named_array.h"
|
||||
#include "sh4_math.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern void* memcpy4 (void *dest, const void *src, size_t count);
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define GL_FORCE_INLINE inline
|
||||
#else
|
||||
#define GL_NO_INSTRUMENT inline __attribute__((no_instrument_function))
|
||||
#define GL_INLINE_DEBUG GL_NO_INSTRUMENT __attribute__((always_inline))
|
||||
#define GL_FORCE_INLINE static GL_INLINE_DEBUG
|
||||
#endif
|
||||
|
||||
#define _GL_UNUSED(x) (void)(x)
|
||||
|
||||
#define FASTCPY(dst, src, bytes) \
|
||||
|
@ -192,17 +201,31 @@ typedef struct {
|
|||
GLfloat specularMaterial[4];
|
||||
} LightSource;
|
||||
|
||||
#define DEAD 0xDEADBEEF
|
||||
|
||||
typedef struct {
|
||||
/* Same 32 byte layout as pvr_vertex_t */
|
||||
uint32_t flags;
|
||||
float xyz[3];
|
||||
float uv[2];
|
||||
uint8_t bgra[4];
|
||||
uint8_t obgra[4];
|
||||
/* End 32 pvr_vertex_t */
|
||||
|
||||
/* In the pvr_vertex_t structure, this next 4 bytes is oargb
|
||||
* but we're not using that for now, so having W here makes the code
|
||||
* simpler */
|
||||
float w;
|
||||
/*
|
||||
* The following are necessary for our purposes
|
||||
* W - W coordinate - for clipping
|
||||
* ST - ST coordinate for multitexture
|
||||
* NXYZ - Normal
|
||||
*/
|
||||
|
||||
float w; // 4
|
||||
float st[2]; // +8 (12)
|
||||
float nxyz[3]; // +12 (24)
|
||||
uint8_t visible; // +1 (25)
|
||||
|
||||
uint8_t padding0[3]; // +3 (28)
|
||||
uint32_t padding1; // +4 (32)
|
||||
} Vertex;
|
||||
|
||||
|
||||
|
@ -242,13 +265,6 @@ do { \
|
|||
*b = c; \
|
||||
} while(0)
|
||||
|
||||
/* ClipVertex doesn't have room for these, so we need to parse them
|
||||
* out separately. Potentially 'w' will be housed here if we support oargb */
|
||||
typedef struct {
|
||||
float nxyz[3];
|
||||
float st[2];
|
||||
} VertexExtra;
|
||||
|
||||
/* Generating PVR vertices from the user-submitted data gets complicated, particularly
|
||||
* when a realloc could invalidate pointers. This structure holds all the information
|
||||
* we need on the target vertex array to allow passing around to the various stages (e.g. generate/clip etc.)
|
||||
|
@ -281,9 +297,6 @@ typedef enum {
|
|||
#define G8IDX 1
|
||||
#define B8IDX 0
|
||||
|
||||
struct SubmissionTarget;
|
||||
|
||||
float _glClipLineToNearZ(const Vertex* v1, const Vertex* v2, Vertex* vout);
|
||||
void _glClipTriangleStrip(SubmissionTarget* target, uint8_t fladeShade);
|
||||
|
||||
PolyList *_glActivePolyList();
|
||||
|
@ -382,4 +395,8 @@ GLubyte _glKosHasError();
|
|||
#define MAX(a,b) (((a)>(b))?(a):(b))
|
||||
#define CLAMP( X, _MIN, _MAX ) ( (X)<(_MIN) ? (_MIN) : ((X)>(_MAX) ? (_MAX) : (X)) )
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // PRIVATE_H
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
#pragma once
|
||||
|
||||
#define GLDC_VERSION "1.2.0alpha"
|
||||
#define GLDC_VERSION ""
|
||||
|
|
2
Makefile
2
Makefile
|
@ -22,6 +22,8 @@ link:
|
|||
|
||||
build: GL/version.h $(OBJS) link
|
||||
|
||||
test: build
|
||||
$(KOS_MAKE) -C tests all
|
||||
|
||||
samples: build
|
||||
$(KOS_MAKE) -C samples all
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <kos.h>
|
||||
|
||||
#include "gl.h"
|
||||
#include "glu.h"
|
||||
|
@ -44,6 +45,8 @@ void ReSizeGLScene(int Width, int Height)
|
|||
/* The main drawing function. */
|
||||
void DrawGLScene()
|
||||
{
|
||||
usleep(500000);
|
||||
|
||||
static GLfloat rotation = 0.0f;
|
||||
static GLfloat movement = 0.0f;
|
||||
static GLboolean increasing = GL_TRUE;
|
||||
|
@ -60,7 +63,7 @@ void DrawGLScene()
|
|||
movement -= 0.05f;
|
||||
}
|
||||
|
||||
rotation += 0.5f;
|
||||
rotation += 10.0f;
|
||||
rotation = (rotation > 360.0f) ? rotation - 360.0f : rotation;
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
|
||||
|
@ -69,15 +72,15 @@ void DrawGLScene()
|
|||
glDisable(GL_CULL_FACE);
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(0.0f, -1.0f, movement);
|
||||
glTranslatef(0.0f, -0.1f, 0 /*movement*/);
|
||||
glRotatef(rotation, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
glBegin(GL_TRIANGLES);
|
||||
glColor3f(1.0f, 0.0f, 0.0f);
|
||||
glVertex3f(0.0f, 0.0f, -5.0f);
|
||||
glVertex3f(-2.5f, 0.0f, 5.0f);
|
||||
|
||||
glColor3f(1.0f, 0.0f, 0.0f);
|
||||
glVertex3f(-2.5f, 0.0f, 5.0f);
|
||||
glVertex3f(0.0f, 0.0f, -5.0f);
|
||||
|
||||
glColor3f(0.0f, 0.0f, 1.0f);
|
||||
glVertex3f(2.5f, 0.0f, 5.0f);
|
||||
|
|
39
tests/Makefile
Normal file
39
tests/Makefile
Normal file
|
@ -0,0 +1,39 @@
|
|||
TARGET = tests.elf
|
||||
OBJS = test_runner.o
|
||||
TESTS := $(shell find . -name \*.h)
|
||||
|
||||
CXXFLAGS := $(CXXFLAGS) -std=c++11
|
||||
|
||||
all: rm-elf $(TARGET)
|
||||
|
||||
include $(KOS_BASE)/Makefile.rules
|
||||
|
||||
.FORCE:
|
||||
|
||||
clean:
|
||||
-rm -f $(TARGET) $(OBJS) romdisk.* test_runner.*
|
||||
|
||||
rm-elf:
|
||||
-rm -f $(TARGET) romdisk.*
|
||||
|
||||
test_runner.cpp:
|
||||
$(shell python3 test_generator.py --output=test_runner.cpp $(TESTS))
|
||||
|
||||
test_runner.o: .FORCE test_runner.cpp
|
||||
|
||||
$(TARGET): $(OBJS) romdisk.o
|
||||
kos-c++ -o $(TARGET) $(OBJS) ../libGLdc.a romdisk.o
|
||||
|
||||
romdisk.img:
|
||||
$(KOS_GENROMFS) -f romdisk.img -d romdisk -v
|
||||
|
||||
romdisk.o: romdisk.img
|
||||
$(KOS_BASE)/utils/bin2o/bin2o romdisk.img romdisk romdisk.o
|
||||
|
||||
run: $(TARGET)
|
||||
$(KOS_LOADER) $(TARGET)
|
||||
|
||||
dist:
|
||||
rm -f $(OBJS) romdisk.o romdisk.img
|
||||
$(KOS_STRIP) $(TARGET)
|
||||
|
0
tests/romdisk/.keep
Normal file
0
tests/romdisk/.keep
Normal file
212
tests/test_generator.py
Executable file
212
tests/test_generator.py
Executable file
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generate C++ unit tests")
|
||||
parser.add_argument("--output", type=str, nargs=1, help="The output source file for the generated test main()", required=True)
|
||||
parser.add_argument("test_files", type=str, nargs="+", help="The list of C++ files containing your tests")
|
||||
parser.add_argument("--verbose", help="Verbose logging", action="store_true", default=False)
|
||||
|
||||
|
||||
CLASS_REGEX = r"\s*class\s+(\w+)\s*([\:|,]\s*(?:public|private|protected)\s+[\w|::]+\s*)*"
|
||||
TEST_FUNC_REGEX = r"void\s+(?P<func_name>test_\S[^\(]+)\(\s*(void)?\s*\)"
|
||||
|
||||
|
||||
INCLUDE_TEMPLATE = "#include \"%(file_path)s\""
|
||||
|
||||
REGISTER_TEMPLATE = """
|
||||
runner->register_case<%(class_name)s>(
|
||||
std::vector<void (%(class_name)s::*)()>({%(members)s}),
|
||||
{%(names)s}
|
||||
);"""
|
||||
|
||||
MAIN_TEMPLATE = """
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
#include "../utils/test.h"
|
||||
|
||||
%(includes)s
|
||||
|
||||
|
||||
std::map<std::string, std::string> parse_args(int argc, char* argv[]) {
|
||||
std::map<std::string, std::string> ret;
|
||||
|
||||
for(int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
auto eq = arg.find('=');
|
||||
if(eq != std::string::npos && arg[0] == '-' && arg[1] == '-') {
|
||||
auto key = std::string(arg.begin(), arg.begin() + eq);
|
||||
auto value = std::string(arg.begin() + eq + 1, arg.end());
|
||||
ret[key] = value;
|
||||
} else if(arg[0] == '-' && arg[1] == '-') {
|
||||
auto key = arg;
|
||||
if(i < (argc - 1)) {
|
||||
auto value = argv[++i];
|
||||
ret[key] = value;
|
||||
} else {
|
||||
ret[key] = "";
|
||||
}
|
||||
} else {
|
||||
ret[arg] = ""; // Positional, not key=value
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
auto runner = std::make_shared<gldc::test::TestRunner>();
|
||||
|
||||
auto args = parse_args(argc, argv);
|
||||
|
||||
std::string junit_xml;
|
||||
auto junit_xml_it = args.find("--junit-xml");
|
||||
if(junit_xml_it != args.end()) {
|
||||
junit_xml = junit_xml_it->second;
|
||||
std::cout << " Outputting junit XML to: " << junit_xml << std::endl;
|
||||
args.erase(junit_xml_it);
|
||||
}
|
||||
|
||||
std::string test_case;
|
||||
if(args.size()) {
|
||||
test_case = args.begin()->first;
|
||||
}
|
||||
|
||||
%(registrations)s
|
||||
|
||||
return runner->run(test_case, junit_xml);
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
|
||||
VERBOSE = True
|
||||
|
||||
def log_verbose(message):
|
||||
if VERBOSE:
|
||||
print(message)
|
||||
|
||||
|
||||
def find_tests(files):
|
||||
|
||||
subclasses = []
|
||||
|
||||
# First pass, find all class definitions
|
||||
for path in files:
|
||||
with open(path, "rt") as f:
|
||||
source_file_data = f.read().replace("\r\n", "").replace("\n", "")
|
||||
|
||||
while True:
|
||||
match = re.search(CLASS_REGEX, source_file_data)
|
||||
if not match:
|
||||
break
|
||||
|
||||
class_name = match.group().split(":")[0].replace("class", "").strip()
|
||||
|
||||
try:
|
||||
parents = match.group().split(":", 1)[1]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
parents = [ x.strip() for x in parents.split(",") ]
|
||||
parents = [
|
||||
x.replace("public", "").replace("private", "").replace("protected", "").strip()
|
||||
for x in parents
|
||||
]
|
||||
|
||||
subclasses.append((path, class_name, parents, []))
|
||||
log_verbose("Found: %s" % str(subclasses[-1]))
|
||||
|
||||
start = match.end()
|
||||
|
||||
# Find the next opening brace
|
||||
while source_file_data[start] in (' ', '\t'):
|
||||
start += 1
|
||||
|
||||
start -= 1
|
||||
end = start
|
||||
if source_file_data[start+1] == '{':
|
||||
|
||||
class_data = []
|
||||
brace_counter = 1
|
||||
for i in range(start+2, len(source_file_data)):
|
||||
class_data.append(source_file_data[i])
|
||||
if class_data[-1] == '{': brace_counter += 1
|
||||
if class_data[-1] == '}': brace_counter -= 1
|
||||
if not brace_counter:
|
||||
end = i
|
||||
break
|
||||
|
||||
class_data = "".join(class_data)
|
||||
|
||||
while True:
|
||||
match = re.search(TEST_FUNC_REGEX, class_data)
|
||||
if not match:
|
||||
break
|
||||
|
||||
subclasses[-1][-1].append(match.group('func_name'))
|
||||
class_data = class_data[match.end():]
|
||||
|
||||
source_file_data = source_file_data[end:]
|
||||
|
||||
|
||||
# Now, simplify the list by finding all potential superclasses, and then keeping any classes
|
||||
# that subclass them.
|
||||
test_case_subclasses = []
|
||||
i = 0
|
||||
while i < len(subclasses):
|
||||
subclass_names = [x.rsplit("::")[-1] for x in subclasses[i][2]]
|
||||
|
||||
# If this subclasses TestCase, or it subclasses any of the already found testcase subclasses
|
||||
# then add it to the list
|
||||
if "TestCase" in subclass_names or "GLdcTestCase" in subclass_names or any(x[1] in subclasses[i][2] for x in test_case_subclasses):
|
||||
if subclasses[i] not in test_case_subclasses:
|
||||
test_case_subclasses.append(subclasses[i])
|
||||
|
||||
i = 0 # Go back to the start, as we may have just found another parent class
|
||||
continue
|
||||
i += 1
|
||||
|
||||
log_verbose("\n".join([str(x) for x in test_case_subclasses]))
|
||||
return test_case_subclasses
|
||||
|
||||
|
||||
def main():
|
||||
global VERBOSE
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
VERBOSE = args.verbose
|
||||
|
||||
testcases = find_tests(args.test_files)
|
||||
|
||||
includes = "\n".join([ INCLUDE_TEMPLATE % { 'file_path' : x } for x in set([y[0] for y in testcases]) ])
|
||||
registrations = []
|
||||
|
||||
for path, class_name, superclasses, funcs in testcases:
|
||||
BIND_TEMPLATE = "&%(class_name)s::%(func)s"
|
||||
|
||||
members = ", ".join([ BIND_TEMPLATE % { 'class_name' : class_name, 'func' : x } for x in funcs ])
|
||||
names = ", ".join([ '"%s::%s"' % (class_name, x) for x in funcs ])
|
||||
|
||||
registrations.append(REGISTER_TEMPLATE % { 'class_name' : class_name, 'members' : members, 'names' : names })
|
||||
|
||||
registrations = "\n".join(registrations)
|
||||
|
||||
final = MAIN_TEMPLATE % {
|
||||
'registrations' : registrations,
|
||||
'includes' : includes
|
||||
}
|
||||
|
||||
open(args.output[0], "w").write(final)
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
390
tests/test_nearz_clipping.h
Normal file
390
tests/test_nearz_clipping.h
Normal file
|
@ -0,0 +1,390 @@
|
|||
#pragma once
|
||||
|
||||
#include "../utils/test.h"
|
||||
#include "../GL/flush.h"
|
||||
#include "../containers/aligned_vector.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct VertexBuilder {
|
||||
VertexBuilder() {
|
||||
aligned_vector_init(&list_, sizeof(Vertex));
|
||||
}
|
||||
|
||||
~VertexBuilder() {
|
||||
aligned_vector_clear(&list_);
|
||||
}
|
||||
|
||||
VertexBuilder& add_header() {
|
||||
Vertex v;
|
||||
v.flags = 100; // I dunno what this bit of memory would be
|
||||
|
||||
aligned_vector_push_back(&list_, &v, 1);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
VertexBuilder& add(float x, float y, float z, float w) {
|
||||
Vertex v;
|
||||
v.flags = PVR_CMD_VERTEX;
|
||||
v.xyz[0] = x;
|
||||
v.xyz[1] = y;
|
||||
v.xyz[2] = z;
|
||||
v.w = w;
|
||||
aligned_vector_push_back(&list_, &v, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
VertexBuilder& add_last(float x, float y, float z, float w) {
|
||||
add(x, y, z, w);
|
||||
Vertex* back = (Vertex*) aligned_vector_back(&list_);
|
||||
back->flags = PVR_CMD_VERTEX_EOL;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::pair<Vertex*, int> done() {
|
||||
return std::make_pair((Vertex*) aligned_vector_at(&list_, 0), list_.size);
|
||||
}
|
||||
|
||||
private:
|
||||
AlignedVector list_;
|
||||
};
|
||||
|
||||
#define assert_vertex_equal(v, x, y, z) \
|
||||
assert_is_not_null(v); \
|
||||
assert_close(v->xyz[0], x, 0.0001f); \
|
||||
assert_close(v->xyz[1], y, 0.0001f); \
|
||||
assert_close(v->xyz[2], z, 0.0001f); \
|
||||
|
||||
#define assert_is_header(v) \
|
||||
assert_false(isVertex(v)); \
|
||||
|
||||
|
||||
class NearZClippingTests : public gldc::test::GLdcTestCase {
|
||||
public:
|
||||
void test_clipping_100() {
|
||||
/* When only the first vertex is visible, we still
|
||||
* end up with 4 elements (3 vertices + 1 header). The
|
||||
* first vertex should be the same */
|
||||
|
||||
VertexBuilder builder;
|
||||
|
||||
auto list = builder.
|
||||
add_header().
|
||||
add(1, 1, 2, 1).
|
||||
add(1, 0, 2, -1).
|
||||
add_last(0, 1, 2, -1).done();
|
||||
|
||||
ListIterator* it = _glIteratorBegin(list.first, list.second);
|
||||
Vertex* v0 = it->active;
|
||||
assert_is_not_null(v0);
|
||||
assert_false(isVertex(v0)); // Should be a header
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
Vertex* v1 = it->active;
|
||||
assert_is_not_null(v1);
|
||||
assert_true(isVertex(v1));
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
Vertex* v2 = it->active;
|
||||
assert_is_not_null(v2);
|
||||
assert_true(isVertex(v2));
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
Vertex* v3 = it->active;
|
||||
assert_is_not_null(v3);
|
||||
assert_true(isVertex(v3));
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_null(it);
|
||||
}
|
||||
|
||||
void test_clipping_110() {
|
||||
/* First two vertices are visible, so we need to
|
||||
* generate 2 vertices and manipulate the strip */
|
||||
|
||||
VertexBuilder builder;
|
||||
|
||||
auto list = builder.
|
||||
add_header().
|
||||
add(1, 1, 2, 1).
|
||||
add(1, 0, 2, 1).
|
||||
add_last(0, 1, 2, -1).done();
|
||||
|
||||
ListIterator* it = _glIteratorBegin(list.first, list.second);
|
||||
Vertex* v0 = it->active;
|
||||
assert_is_not_null(v0);
|
||||
assert_false(isVertex(v0)); // Should be a header
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
Vertex* v1 = it->active;
|
||||
assert_is_not_null(v1);
|
||||
assert_true(isVertex(v1));
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
Vertex* v2 = it->active;
|
||||
assert_is_not_null(v2);
|
||||
assert_true(isVertex(v2));
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
Vertex* v3 = it->active;
|
||||
assert_is_not_null(v3);
|
||||
assert_true(isVertex(v3));
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
Vertex* v4 = it->active;
|
||||
assert_is_not_null(v4);
|
||||
assert_true(isVertex(v4));
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_null(it);
|
||||
}
|
||||
|
||||
void test_clipping_111() {
|
||||
/* All vertices are visible, list should be
|
||||
* totally unchanged */
|
||||
|
||||
VertexBuilder builder;
|
||||
|
||||
/* 2 triangle strips, with positive W coords */
|
||||
auto list = builder.
|
||||
add_header().
|
||||
add(1, 1, 2, 1).
|
||||
add(1, 0, 2, 1).
|
||||
add_last(0, 1, 2, 1).
|
||||
add_header().
|
||||
add(1, 1, 2, 1).
|
||||
add(1, 0, 2, 1).
|
||||
add_last(0, 1, 2, 1).done();
|
||||
|
||||
ListIterator* it = _glIteratorBegin(list.first, list.second);
|
||||
assert_is_not_null(it);
|
||||
assert_is_header(it->active);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 1, 1, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 1, 0, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 0, 1, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_is_header(it->active);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 1, 1, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 1, 0, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 0, 1, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_null(it);
|
||||
}
|
||||
|
||||
void test_clipping_101() {
|
||||
VertexBuilder builder;
|
||||
|
||||
auto list = builder.
|
||||
add_header().
|
||||
add(1, 1, 2, 1).
|
||||
add(1, 0, 2, -1).
|
||||
add_last(0, 1, 2, 1).done();
|
||||
|
||||
ListIterator* it = _glIteratorBegin(list.first, list.second);
|
||||
Vertex* v0 = it->active;
|
||||
assert_is_not_null(v0);
|
||||
assert_false(isVertex(v0)); // Should be a header
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 1, 1, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 0, 1, 2);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 1, 0.5f, 2);
|
||||
|
||||
}
|
||||
|
||||
void test_clipping_010() {
|
||||
|
||||
}
|
||||
|
||||
void test_clipping_001() {
|
||||
|
||||
}
|
||||
|
||||
void test_clipping_000() {
|
||||
/* If no vertices are visible, they should be culled
|
||||
* we (currently) leave headers as removing them before
|
||||
* submission is too costly */
|
||||
|
||||
VertexBuilder builder;
|
||||
|
||||
/* 2 triangle strips, with negative W coords */
|
||||
auto list = builder.
|
||||
add_header().
|
||||
add(1, 1, 2, -1).
|
||||
add(1, 0, 2, -1).
|
||||
add_last(0, 1, 2, -1).
|
||||
add_header().
|
||||
add(1, 1, 2, -1).
|
||||
add(1, 0, 2, -1).
|
||||
add_last(0, 1, 2, -1).done();
|
||||
|
||||
ListIterator* it = _glIteratorBegin(list.first, list.second);
|
||||
assert_is_not_null(it);
|
||||
assert_is_header(it->active);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_is_header(it->active);
|
||||
|
||||
// Done!
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_null(it);
|
||||
}
|
||||
|
||||
void test_clipping_011() {
|
||||
|
||||
}
|
||||
|
||||
void test_complex_strip() {
|
||||
VertexBuilder builder;
|
||||
|
||||
auto list = builder.
|
||||
add_header().
|
||||
add(5, 0, -8, 8).
|
||||
add(2, 0, -4, 4).
|
||||
add(6, 0, -3, 3).
|
||||
add(4, 0, 5, -5).
|
||||
add(10, 0, 3, -3).
|
||||
add(11, 0, 5, -5).
|
||||
add(12, 0, 3, -3).
|
||||
add(17, 0, 5, -5).
|
||||
add(16, 0, -3, 3).
|
||||
add(19, 0, -2, 2).
|
||||
add_last(17, 0, -7, 7).done();
|
||||
|
||||
ListIterator* it = _glIteratorBegin(list.first, list.second);
|
||||
assert_is_not_null(it);
|
||||
assert_is_header(it->active);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 5, 0, -8); // A
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 2, 0, -4); // B
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 6, 0, -3); // C
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 2.88888f, 0, 0); // BD
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 5.25f, 0, 0); // CD
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX_EOL);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 6, 0, -3); // C
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 5.25f, 0, 0); // CD
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 8.0f, 0, 0); // CE
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX_EOL);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 14.0f, 0, 0);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 16.375f, 0, 0);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 16.0f, 0, -3); // 8
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX_EOL);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 16.375f, 0, 0);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 18.4286f, 0, 0);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 16.0f, 0, -3);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 19.0f, 0, -2);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX_EOL);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 16.0f, 0, -3);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 19.0f, 0, -2);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_not_null(it);
|
||||
assert_vertex_equal(it->active, 17.0f, 0, -7);
|
||||
assert_equal(it->active->flags, PVR_CMD_VERTEX_EOL);
|
||||
|
||||
it = _glIteratorNext(it);
|
||||
assert_is_null(it);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
456
utils/test.h
Normal file
456
utils/test.h
Normal file
|
@ -0,0 +1,456 @@
|
|||
/* * Copyright (c) 2011-2017 Luke Benstead https://simulant-engine.appspot.com
|
||||
*
|
||||
* This file is part of Simulant.
|
||||
*
|
||||
* Simulant is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Simulant is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Simulant. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#define assert_equal(expected, actual) _assert_equal((expected), (actual), __FILE__, __LINE__)
|
||||
#define assert_not_equal(expected, actual) _assert_not_equal((expected), (actual), __FILE__, __LINE__)
|
||||
#define assert_false(actual) _assert_false((actual), __FILE__, __LINE__)
|
||||
#define assert_true(actual) _assert_true((actual), __FILE__, __LINE__)
|
||||
#define assert_close(expected, actual, difference) _assert_close((expected), (actual), (difference), __FILE__, __LINE__)
|
||||
#define assert_is_null(actual) _assert_is_null((actual), __FILE__, __LINE__)
|
||||
#define assert_is_not_null(actual) _assert_is_not_null((actual), __FILE__, __LINE__)
|
||||
#define assert_raises(exception, func) _assert_raises<exception>((func), __FILE__, __LINE__)
|
||||
#define assert_items_equal(expected, actual) _assert_items_equal((actual), (expected), __FILE__, __LINE__)
|
||||
#define not_implemented() _not_implemented(__FILE__, __LINE__)
|
||||
|
||||
|
||||
namespace gldc {
|
||||
namespace test {
|
||||
|
||||
|
||||
class StringFormatter {
|
||||
public:
|
||||
StringFormatter(const std::string& templ):
|
||||
templ_(templ) { }
|
||||
|
||||
struct Counter {
|
||||
Counter(uint32_t c): c(c) {}
|
||||
uint32_t c;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
std::string format(T value) {
|
||||
std::stringstream ss;
|
||||
ss << value;
|
||||
return _do_format(0, ss.str());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string format(Counter count, T value) {
|
||||
std::stringstream ss;
|
||||
ss << value;
|
||||
return _do_format(count.c, ss.str());
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
std::string format(T value, const Args&... args) {
|
||||
std::stringstream ss;
|
||||
ss << value;
|
||||
return StringFormatter(_do_format(0, ss.str())).format(Counter(1), args...);
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
std::string format(Counter count, T value, const Args&... args) {
|
||||
std::stringstream ss;
|
||||
ss << value;
|
||||
return StringFormatter(_do_format(count.c, ss.str())).format(Counter(count.c + 1), args...);
|
||||
}
|
||||
|
||||
std::string _do_format(uint32_t counter, const std::string& value) {
|
||||
std::stringstream ss; // Can't use to_string on all platforms
|
||||
ss << counter;
|
||||
|
||||
const std::string to_replace = "{" + ss.str() + "}";
|
||||
std::string output = templ_;
|
||||
|
||||
auto replace = [](std::string& str, const std::string& from, const std::string& to) -> bool {
|
||||
size_t start_pos = str.find(from);
|
||||
if(start_pos == std::string::npos)
|
||||
return false;
|
||||
str.replace(start_pos, from.length(), to);
|
||||
return true;
|
||||
};
|
||||
|
||||
replace(output, to_replace, value);
|
||||
return output;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string templ_;
|
||||
};
|
||||
|
||||
class StringSplitter {
|
||||
public:
|
||||
StringSplitter(const std::string& str):
|
||||
str_(str) {
|
||||
|
||||
}
|
||||
|
||||
std::vector<std::string> split() {
|
||||
std::vector<std::string> result;
|
||||
std::string buffer;
|
||||
|
||||
for(auto c: str_) {
|
||||
if(c == '\n') {
|
||||
if(!buffer.empty()) {
|
||||
result.push_back(buffer);
|
||||
buffer.clear();
|
||||
}
|
||||
} else {
|
||||
buffer.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
if(!buffer.empty()) {
|
||||
result.push_back(buffer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string str_;
|
||||
};
|
||||
|
||||
typedef StringFormatter _Format;
|
||||
|
||||
class AssertionError : public std::logic_error {
|
||||
public:
|
||||
AssertionError(const std::string& what):
|
||||
std::logic_error(what),
|
||||
file(""),
|
||||
line(-1) {
|
||||
}
|
||||
|
||||
AssertionError(const std::pair<std::string, int> file_and_line, const std::string& what):
|
||||
std::logic_error(what),
|
||||
file(file_and_line.first),
|
||||
line(file_and_line.second) {
|
||||
|
||||
}
|
||||
|
||||
~AssertionError() noexcept (true) {
|
||||
|
||||
}
|
||||
|
||||
std::string file;
|
||||
int line;
|
||||
};
|
||||
|
||||
|
||||
class NotImplementedError: public std::logic_error {
|
||||
public:
|
||||
NotImplementedError(const std::string& file, int line):
|
||||
std::logic_error(_Format("Not implemented at {0}:{1}").format(file, line)) {}
|
||||
};
|
||||
|
||||
|
||||
class SkippedTestError: public std::logic_error {
|
||||
public:
|
||||
SkippedTestError(const std::string& reason):
|
||||
std::logic_error(reason) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
class TestCase {
|
||||
public:
|
||||
virtual ~TestCase() {}
|
||||
|
||||
virtual void set_up() {}
|
||||
virtual void tear_down() {}
|
||||
|
||||
void skip_if(const bool& flag, const std::string& reason) {
|
||||
if(flag) { throw test::SkippedTestError(reason); }
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
void _assert_equal(T expected, U actual, std::string file, int line) {
|
||||
if(expected != actual) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, test::_Format("{0} does not match {1}").format(actual, expected));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
void _assert_not_equal(T lhs, U rhs, std::string file, int line) {
|
||||
if(lhs == (T) rhs) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, test::_Format("{0} should not match {1}").format(lhs, rhs));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _assert_true(T actual, std::string file, int line) {
|
||||
if(!bool(actual)) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, test::_Format("{0} is not true").format(bool(actual) ? "true" : "false"));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _assert_false(T actual, std::string file, int line) {
|
||||
if(bool(actual)) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, test::_Format("{0} is not false").format(bool(actual) ? "true" : "false"));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename V>
|
||||
void _assert_close(T expected, U actual, V difference, std::string file, int line) {
|
||||
if(actual < expected - difference ||
|
||||
actual > expected + difference) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, test::_Format("{0} is not close enough to {1}").format(actual, expected));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _assert_is_null(T* thing, std::string file, int line) {
|
||||
if(thing != nullptr) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, "Pointer was not NULL");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _assert_is_not_null(T* thing, std::string file, int line) {
|
||||
if(thing == nullptr) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, "Pointer was unexpectedly NULL");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void _assert_raises(Func func, std::string file, int line) {
|
||||
try {
|
||||
func();
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
throw test::AssertionError(file_and_line, test::_Format("Expected exception ({0}) was not thrown").format(typeid(T).name()));
|
||||
} catch(T& e) {}
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
void _assert_items_equal(const T& lhs, const U& rhs, std::string file, int line) {
|
||||
auto file_and_line = std::make_pair(file, line);
|
||||
|
||||
if(lhs.size() != rhs.size()) {
|
||||
throw test::AssertionError(file_and_line, "Containers are not the same length");
|
||||
}
|
||||
|
||||
for(auto item: lhs) {
|
||||
if(std::find(rhs.begin(), rhs.end(), item) == rhs.end()) {
|
||||
throw test::AssertionError(file_and_line, test::_Format("Container does not contain {0}").format(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _not_implemented(std::string file, int line) {
|
||||
throw test::NotImplementedError(file, line);
|
||||
}
|
||||
};
|
||||
|
||||
class TestRunner {
|
||||
public:
|
||||
template<typename T, typename U>
|
||||
void register_case(std::vector<U> methods, std::vector<std::string> names) {
|
||||
std::shared_ptr<TestCase> instance = std::make_shared<T>();
|
||||
|
||||
instances_.push_back(instance); //Hold on to it
|
||||
|
||||
for(std::string name: names) {
|
||||
names_.push_back(name);
|
||||
}
|
||||
|
||||
for(U& method: methods) {
|
||||
std::function<void()> func = std::bind(method, dynamic_cast<T*>(instance.get()));
|
||||
tests_.push_back([=]() {
|
||||
instance->set_up();
|
||||
func();
|
||||
instance->tear_down();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int32_t run(const std::string& test_case, const std::string& junit_output="") {
|
||||
int failed = 0;
|
||||
int skipped = 0;
|
||||
int ran = 0;
|
||||
int crashed = 0;
|
||||
|
||||
auto new_tests = tests_;
|
||||
auto new_names = names_;
|
||||
|
||||
if(!test_case.empty()) {
|
||||
new_tests.clear();
|
||||
new_names.clear();
|
||||
|
||||
for(uint32_t i = 0; i < names_.size(); ++i) {
|
||||
if(names_[i].find(test_case) == 0) {
|
||||
new_tests.push_back(tests_[i]);
|
||||
new_names.push_back(names_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl << "Running " << new_tests.size() << " tests" << std::endl << std::endl;
|
||||
|
||||
std::vector<std::string> junit_lines;
|
||||
junit_lines.push_back("<testsuites>\n");
|
||||
|
||||
std::string klass = "";
|
||||
|
||||
for(std::function<void ()> test: new_tests) {
|
||||
std::string name = new_names[ran];
|
||||
std::string this_klass(name.begin(), name.begin() + name.find_first_of(":"));
|
||||
bool close_klass = ran == (int) new_tests.size() - 1;
|
||||
|
||||
if(this_klass != klass) {
|
||||
if(!klass.empty()) {
|
||||
junit_lines.push_back(" </testsuite>\n");
|
||||
}
|
||||
klass = this_klass;
|
||||
junit_lines.push_back(" <testsuite name=\"" + this_klass + "\">\n");
|
||||
}
|
||||
|
||||
try {
|
||||
junit_lines.push_back(" <testcase name=\"" + new_names[ran] + "\">\n");
|
||||
std::string output = " " + new_names[ran];
|
||||
|
||||
for(int i = output.length(); i < 76; ++i) {
|
||||
output += " ";
|
||||
}
|
||||
|
||||
std::cout << output;
|
||||
test();
|
||||
std::cout << "\033[32m" << " OK " << "\033[0m" << std::endl;
|
||||
junit_lines.push_back(" </testcase>\n");
|
||||
} catch(test::NotImplementedError& e) {
|
||||
std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
|
||||
++skipped;
|
||||
junit_lines.push_back(" </testcase>\n");
|
||||
} catch(test::SkippedTestError& e) {
|
||||
std::cout << "\033[34m" << " SKIPPED" << "\033[0m" << std::endl;
|
||||
++skipped;
|
||||
junit_lines.push_back(" </testcase>\n");
|
||||
} catch(test::AssertionError& e) {
|
||||
std::cout << "\033[33m" << " FAILED " << "\033[0m" << std::endl;
|
||||
std::cout << " " << e.what() << std::endl;
|
||||
if(!e.file.empty()) {
|
||||
std::cout << " " << e.file << ":" << e.line << std::endl;
|
||||
|
||||
std::ifstream ifs(e.file);
|
||||
if(ifs.good()) {
|
||||
std::string buffer;
|
||||
std::vector<std::string> lines;
|
||||
while(std::getline(ifs, buffer)) {
|
||||
lines.push_back(buffer);
|
||||
}
|
||||
|
||||
int line_count = lines.size();
|
||||
if(line_count && e.line <= line_count) {
|
||||
std::cout << lines.at(e.line - 1) << std::endl << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
++failed;
|
||||
|
||||
junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
|
||||
junit_lines.push_back(" </testcase>\n");
|
||||
} catch(std::exception& e) {
|
||||
std::cout << "\033[31m" << " EXCEPT " << std::endl;
|
||||
std::cout << " " << e.what() << "\033[0m" << std::endl;
|
||||
++crashed;
|
||||
|
||||
junit_lines.push_back(" <failure message=\"" + std::string(e.what()) + "\"/>\n");
|
||||
junit_lines.push_back(" </testcase>\n");
|
||||
}
|
||||
std::cout << "\033[0m";
|
||||
++ran;
|
||||
|
||||
if(close_klass) {
|
||||
junit_lines.push_back(" </testsuite>\n");
|
||||
}
|
||||
}
|
||||
|
||||
junit_lines.push_back("</testsuites>\n");
|
||||
|
||||
if(!junit_output.empty()) {
|
||||
FILE* f = fopen(junit_output.c_str(), "wt");
|
||||
if(f) {
|
||||
for(auto& line: junit_lines) {
|
||||
fwrite(line.c_str(), sizeof(char), line.length(), f);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
std::cout << "-----------------------" << std::endl;
|
||||
if(!failed && !crashed && !skipped) {
|
||||
std::cout << "All tests passed" << std::endl << std::endl;
|
||||
} else {
|
||||
if(skipped) {
|
||||
std::cout << skipped << " tests skipped";
|
||||
}
|
||||
|
||||
if(failed) {
|
||||
if(skipped) {
|
||||
std::cout << ", ";
|
||||
}
|
||||
std::cout << failed << " tests failed";
|
||||
}
|
||||
|
||||
if(crashed) {
|
||||
if(failed) {
|
||||
std::cout << ", ";
|
||||
}
|
||||
std::cout << crashed << " tests crashed";
|
||||
}
|
||||
std::cout << std::endl << std::endl;
|
||||
}
|
||||
|
||||
return failed + crashed;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<TestCase>> instances_;
|
||||
std::vector<std::function<void()> > tests_;
|
||||
std::vector<std::string> names_;
|
||||
};
|
||||
|
||||
class GLdcTestCase : public TestCase {
|
||||
public:
|
||||
virtual void set_up() {
|
||||
TestCase::set_up();
|
||||
}
|
||||
};
|
||||
|
||||
} // test
|
||||
} // gldc
|
||||
|
18
utils/test_parser.py
Normal file
18
utils/test_parser.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import re
|
||||
import fileinput
|
||||
import sys
|
||||
|
||||
REGEX = "(\d+) tests ([failed|crashed])"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for line in sys.stdin:
|
||||
print(line, end="")
|
||||
|
||||
if re.search(REGEX, line):
|
||||
print("DETECTED FAILURES")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("TESTS PASSED!")
|
||||
sys.exit(0)
|
||||
|
Loading…
Reference in New Issue
Block a user