From 6cfb4e437ccbdbe6423455c198194e3de5828c67 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sun, 14 Apr 2019 07:17:27 +0100 Subject: [PATCH 01/64] Handle GL_DOUBLE like GL_FLOAT everywhere --- GL/draw.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/GL/draw.c b/GL/draw.c index 753ee11..ca73026 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -549,6 +549,7 @@ static inline void _readPositionData(const GLuint first, const GLuint count, Ver if(VERTEX_POINTER.size == 3) { switch(VERTEX_POINTER.type) { + case GL_DOUBLE: case GL_FLOAT: _readVertexData3f3f(vptr, count, vstride, output[0].xyz); break; @@ -569,6 +570,7 @@ static inline void _readPositionData(const GLuint first, const GLuint count, Ver } } else if(VERTEX_POINTER.size == 2) { switch(VERTEX_POINTER.type) { + case GL_DOUBLE: case GL_FLOAT: _readVertexData2f3f(vptr, count, vstride, output[0].xyz); break; @@ -603,6 +605,7 @@ static inline void _readUVData(const GLuint first, const GLuint count, Vertex* o if(UV_POINTER.size == 2) { switch(UV_POINTER.type) { + case GL_DOUBLE: case GL_FLOAT: _readVertexData2f2f(uvptr, count, uvstride, output[0].uv); break; @@ -637,6 +640,7 @@ static inline void _readSTData(const GLuint first, const GLuint count, VertexExt if(ST_POINTER.size == 2) { switch(ST_POINTER.type) { + case GL_DOUBLE: case GL_FLOAT: _readVertexData2f2fVE(stptr, count, ststride, extra->st); break; @@ -671,6 +675,7 @@ static inline void _readNormalData(const GLuint first, const GLuint count, Verte if(NORMAL_POINTER.size == 3) { switch(NORMAL_POINTER.type) { + case GL_DOUBLE: case GL_FLOAT: _readVertexData3f3fVE(nptr, count, nstride, extra->nxyz); break; @@ -706,6 +711,7 @@ static inline void _readDiffuseData(const GLuint first, const GLuint count, Vert if(DIFFUSE_POINTER.size == 3) { switch(DIFFUSE_POINTER.type) { + case GL_DOUBLE: case GL_FLOAT: _readVertexData3fARGB(cptr, count, cstride, output[0].bgra); break; @@ -726,6 +732,7 @@ static inline void _readDiffuseData(const GLuint first, const GLuint count, Vert } } else if(DIFFUSE_POINTER.size == 4) { switch(DIFFUSE_POINTER.type) { + case GL_DOUBLE: case GL_FLOAT: _readVertexData4fARGB(cptr, count, cstride, output[0].bgra); break; From a8d420299cd059f09a3bfe9dbea37169b9d916cd Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sun, 14 Apr 2019 08:51:37 +0100 Subject: [PATCH 02/64] Correctly throw GL_INVALID_VALUE for invalid sizes --- GL/draw.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/GL/draw.c b/GL/draw.c index ca73026..113b11c 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -1318,6 +1318,12 @@ void APIENTRY glClientActiveTextureARB(GLenum texture) { void APIENTRY glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) { TRACE(); + if(size < 1 || size > 4) { + _glKosThrowError(GL_INVALID_VALUE, __func__); + _glKosPrintError(); + return; + } + AttribPointer* tointer = (ACTIVE_CLIENT_TEXTURE == 0) ? &UV_POINTER : &ST_POINTER; tointer->ptr = pointer; @@ -1329,6 +1335,12 @@ void APIENTRY glTexCoordPointer(GLint size, GLenum type, GLsizei stride, cons void APIENTRY glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) { TRACE(); + if(size < 2 || size > 4) { + _glKosThrowError(GL_INVALID_VALUE, __func__); + _glKosPrintError(); + return; + } + VERTEX_POINTER.ptr = pointer; VERTEX_POINTER.stride = stride; VERTEX_POINTER.type = type; @@ -1338,6 +1350,12 @@ void APIENTRY glVertexPointer(GLint size, GLenum type, GLsizei stride, const void APIENTRY glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) { TRACE(); + if(size != 3 && size != 4) { + _glKosThrowError(GL_INVALID_VALUE, __func__); + _glKosPrintError(); + return; + } + DIFFUSE_POINTER.ptr = pointer; DIFFUSE_POINTER.stride = stride; DIFFUSE_POINTER.type = type; From 62743d42cb63cebd0129f835d4e45b8d726bb190 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 17 Apr 2019 14:55:30 +0100 Subject: [PATCH 03/64] Only allocate space for mipmaps when necessary --- GL/framebuffer.c | 3 +++ GL/private.h | 1 + GL/texture.c | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/GL/framebuffer.c b/GL/framebuffer.c index 8e86493..cd77798 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -222,6 +222,9 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { GLuint prevWidth = tex->width; GLuint prevHeight = tex->height; + /* Make sure there is room for the mipmap data on the texture object */ + _glAllocateSpaceForMipmaps(tex); + for(; i < _glGetMipmapLevelCount(tex); ++i) { GLubyte* prevData = _glGetMipmapLocation(tex, i - 1); GLubyte* thisData = _glGetMipmapLocation(tex, i); diff --git a/GL/private.h b/GL/private.h index 45286f8..d708fef 100644 --- a/GL/private.h +++ b/GL/private.h @@ -200,6 +200,7 @@ pvr_poly_cxt_t* _glGetPVRContext(); GLubyte _glInitTextures(); void _glUpdatePVRTextureContext(pvr_poly_cxt_t* context, GLshort textureUnit); +void _glAllocateSpaceForMipmaps(TextureObject* active); typedef struct { const void* ptr; diff --git a/GL/texture.c b/GL/texture.c index 1ea25c8..62bc950 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -781,6 +781,31 @@ GLboolean _glIsMipmapComplete(const TextureObject* obj) { #define MIN(a, b) ( (a)<(b)? (a):(b) ) +void _glAllocateSpaceForMipmaps(TextureObject* active) { + if(active->data && active->mipmap > 1) { + /* Already done */ + return; + } + + /* We've allocated level 0 before, but now we're allocating + * a level beyond that, we need to reallocate the data, copy level 0 + * then free the original + */ + + GLubyte* src = active->data; + GLubyte* dest = active->data = pvr_mem_malloc(_glGetMipmapDataSize(active)); + + /* If there was existing data, then copy it across before freeing */ + if(src) { + GLuint i = 0; + for(; i < active->width * active->height * active->dataStride; ++i) { + *dest++ = *src++; + } + + pvr_mem_free(src); + } +} + void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *data) { @@ -888,7 +913,12 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, active->mipmapCount = _glGetMipmapLevelCount(active); active->dataStride = destStride; - GLuint size = _glGetMipmapDataSize(active); + GLuint size = bytes; + + /* If we're uploading a mipmap level, we need to allocate the full amount of space */ + if(level > 0) { + size = _glGetMipmapDataSize(active); + } assert(size); active->data = pvr_mem_malloc(size); @@ -898,6 +928,12 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, active->isPaletted = isPaletted; } + /* We're supplying a mipmap level, but previously we only had + * data for the first level (level 0, e.g. 1 << 0 == 1) */ + if(level > 0 && active->mipmap == 1) { + _glAllocateSpaceForMipmaps(active); + } + /* Mark this level as set in the mipmap bitmask */ active->mipmap |= (1 << level); From 3395f17687bfe3f32249e7d1be583a7fe77c194e Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 17 Apr 2019 16:19:03 +0100 Subject: [PATCH 04/64] Throw GL_INVALID_OPERATION if mipmaps are specified for non-square textures --- GL/framebuffer.c | 7 +++++++ GL/texture.c | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/GL/framebuffer.c b/GL/framebuffer.c index cd77798..9af7575 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -206,6 +206,13 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { TextureObject* tex = _glGetBoundTexture(); + if(tex->width != tex->height) { + fprintf(stderr, "[GL ERROR] Mipmaps cannot be supported on non-square textures\n"); + _glKosThrowError(GL_INVALID_OPERATION, __func__); + _glKosPrintError(); + return; + } + if(!tex || !tex->data || !tex->mipmapCount) { _glKosThrowError(GL_INVALID_OPERATION, __func__); _glKosPrintError(); diff --git a/GL/texture.c b/GL/texture.c index 62bc950..f3a1662 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -852,6 +852,11 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, _glKosThrowError(GL_INVALID_VALUE, "glTexImage2D"); } + if(level > 0 && width != height) { + fprintf(stderr, "[GL ERROR] Mipmaps cannot be supported on non-square textures\n"); + _glKosThrowError(GL_INVALID_OPERATION, __func__); + } + if(border) { _glKosThrowError(GL_INVALID_VALUE, "glTexImage2D"); } From 00c38edba9ae4a1764ef41f51c8cea96cfee1336 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 17 Jul 2019 10:45:29 +0100 Subject: [PATCH 05/64] Add polygon_offset sample (thanks mrneo240) --- include/gl.h | 7 ++ samples/Makefile | 1 + samples/polygon_offset/Makefile | 23 ++++ samples/polygon_offset/main.c | 215 ++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 samples/polygon_offset/Makefile create mode 100644 samples/polygon_offset/main.c diff --git a/include/gl.h b/include/gl.h index 2937fe8..bb9c52c 100644 --- a/include/gl.h +++ b/include/gl.h @@ -232,6 +232,13 @@ __BEGIN_DECLS #define GL_SINGLE_COLOR 0x81F9 #define GL_SEPARATE_SPECULAR_COLOR 0x81FA +/* glPolygonOffset */ +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + /* Client state caps */ #define GL_VERTEX_ARRAY 0x8074 #define GL_NORMAL_ARRAY 0x8075 diff --git a/samples/Makefile b/samples/Makefile index 177f547..598cd17 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -26,3 +26,4 @@ all: $(KOS_MAKE) -C paletted_pcx all $(KOS_MAKE) -C depth_funcs all $(KOS_MAKE) -C polymark all + $(KOS_MAKE) -C polygon_offset all diff --git a/samples/polygon_offset/Makefile b/samples/polygon_offset/Makefile new file mode 100644 index 0000000..572bd91 --- /dev/null +++ b/samples/polygon_offset/Makefile @@ -0,0 +1,23 @@ +TARGET = offset.elf +OBJS = main.o + +all: rm-elf $(TARGET) + +include $(KOS_BASE)/Makefile.rules + +clean: + -rm -f $(TARGET) $(OBJS) + +rm-elf: + -rm -f $(TARGET) + +$(TARGET): $(OBJS) + $(KOS_CC) $(KOS_CFLAGS) $(KOS_LDFLAGS) -o $(TARGET) $(KOS_START) \ + $(OBJS) $(OBJEXTRA) -lm -lkosutils $(KOS_LIBS) + +run: $(TARGET) + $(KOS_LOADER) $(TARGET) + +dist: $(TARGET) + $(KOS_CC_BASE)/sh-elf/bin/objcopy -R .stack -O binary $(TARGET) $(basename $(TARGET)) + $(KOS_BASE)/utils/scramble/scramble $(basename $(TARGET)) 1ST_READ.BIN diff --git a/samples/polygon_offset/main.c b/samples/polygon_offset/main.c new file mode 100644 index 0000000..101f4d6 --- /dev/null +++ b/samples/polygon_offset/main.c @@ -0,0 +1,215 @@ +#include +typedef enum +{ + false, + true +} bool; +#include "gl.h" +#include "glu.h" +#include "glkos.h" + +/* A general OpenGL initialization function. Sets all of the initial parameters. */ +void InitGL(int Width, int Height) // We call this right after our OpenGL window is created. +{ + glClearColor(0.93f, 0.93f, 0.93f, 0.0f); + glClearDepth(1.0); // Enables Clearing Of The Depth Buffer + glDepthFunc(GL_LEQUAL); // The Type Of Depth Test To Do + glEnable(GL_DEPTH_TEST); // Enables Depth Testing + glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); // Reset The Projection Matrix + + gluPerspective(45.0f, (GLfloat)Width / (GLfloat)Height, 1.0f, 100.0f); // Calculate The Aspect Ratio Of The Window + + glMatrixMode(GL_MODELVIEW); +} + +/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */ +void ReSizeGLScene(int Width, int Height) +{ + if (Height == 0) // Prevent A Divide By Zero If The Window Is Too Small + Height = 1; + + glViewport(0, 0, Width, Height); // Reset The Current Viewport And Perspective Transformation + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + gluPerspective(45.0f, (GLfloat)Width / (GLfloat)Height, 1.0f, 100.0f); + glMatrixMode(GL_MODELVIEW); +} + +float xrot = 0.0f; +float yrot = 0.0f; + +float xdiff = 0; +float ydiff = 0; + +bool offset = true; + +void drawBox() +{ + glBegin(GL_QUADS); + // FRONT + glVertex3f(-0.5f, -0.5f, 0.5f); + glVertex3f(0.5f, -0.5f, 0.5f); + glVertex3f(0.5f, 0.5f, 0.5f); + glVertex3f(-0.5f, 0.5f, 0.5f); + // BACK + glVertex3f(-0.5f, -0.5f, -0.5f); + glVertex3f(-0.5f, 0.5f, -0.5f); + glVertex3f(0.5f, 0.5f, -0.5f); + glVertex3f(0.5f, -0.5f, -0.5f); + // LEFT + glVertex3f(-0.5f, -0.5f, 0.5f); + glVertex3f(-0.5f, 0.5f, 0.5f); + glVertex3f(-0.5f, 0.5f, -0.5f); + glVertex3f(-0.5f, -0.5f, -0.5f); + // RIGHT + glVertex3f(0.5f, -0.5f, -0.5f); + glVertex3f(0.5f, 0.5f, -0.5f); + glVertex3f(0.5f, 0.5f, 0.5f); + glVertex3f(0.5f, -0.5f, 0.5f); + // TOP + glVertex3f(-0.5f, 0.5f, 0.5f); + glVertex3f(0.5f, 0.5f, 0.5f); + glVertex3f(0.5f, 0.5f, -0.5f); + glVertex3f(-0.5f, 0.5f, -0.5f); + // BOTTOM + glVertex3f(-0.5f, -0.5f, 0.5f); + glVertex3f(-0.5f, -0.5f, -0.5f); + glVertex3f(0.5f, -0.5f, -0.5f); + glVertex3f(0.5f, -0.5f, 0.5f); + glEnd(); +} + +void drawPolygon() +{ + glBegin(GL_QUADS); + glVertex3f(-0.5f, -0.5f, 0.0f); + glVertex3f(0.5f, -0.5f, 0.0f); + glVertex3f(0.5f, 0.5f, 0.0f); + glVertex3f(-0.5f, 0.5f, 0.0f); + glEnd(); +} +int frames = 0; +void check_input() +{ + maple_device_t *cont; + cont_state_t *state; + + cont = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); + + if (cont) + { + state = (cont_state_t *)maple_dev_status(cont); + + if (state) + { + if (frames <= 0) + { + if (state->buttons & CONT_START) + { + offset = !offset; + frames = 60; + } + } + } + } +} + +/* The main drawing function. */ +void DrawGLScene() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer + glLoadIdentity(); // Reset The View + + gluLookAt( + 0.0f, 0.0f, 3.0f, + 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f); + + glRotatef(xrot, 1.0f, 0.0f, 0.0f); + glRotatef(yrot, 0.0f, 1.0f, 0.0f); + + //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + glColor3f(1.0f, 0.0f, 0.0f); + //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + drawBox(); + + if (offset) + { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.0f, 1.0f); + } + + glColor3f(0.0f, 1.0f, 0.0f); + glPushMatrix(); + glTranslatef(-0.25f, -0.25f, 0.5f); + glScalef(0.5f, 0.5f, 0.5f); + drawPolygon(); + glPopMatrix(); + + glColor3f(1.0f, 1.0f, 0.0f); + glPushMatrix(); + glTranslatef(0.25f, 0.25f, 0.5f); + glScalef(0.5f, 0.5f, 0.5f); + drawPolygon(); + glPopMatrix(); + + if (offset) + { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, -1.0f); + } + + glColor3f(0.0f, 0.0f, 1.0f); + glPushMatrix(); + glTranslatef(0.0f, 0.0f, 0.5f); + glScalef(0.5f, 0.5f, 0.5f); + drawPolygon(); + glPopMatrix(); + + if (offset) + glDisable(GL_POLYGON_OFFSET_FILL); +#if 0 + if (offset) + { + glEnable(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-1.0f, -1.0f); + } + + + glColor3f(0.0f, 0.0f, 0.0f); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + drawBox(); + + if (offset) + glDisable(GL_POLYGON_OFFSET_LINE); + + glFlush(); +#endif + // swap buffers to display, since we're double buffered. + glKosSwapBuffers(); +} + +int main(int argc, char **argv) +{ + glKosInit(); + + InitGL(640, 480); + ReSizeGLScene(640, 480); + + while (1) + { + check_input(); + DrawGLScene(); + xrot += 0.1f; + yrot += 0.25f; + frames--; + } + + return 0; +} From b82d28130d023b473afa7d818726034a50660253 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Thu, 1 Aug 2019 20:21:03 +0100 Subject: [PATCH 06/64] Rewrite the lighting code --- GL/draw.c | 25 +----- GL/lighting.c | 224 ++++++++++++++++++++++++++++++-------------------- GL/private.h | 8 +- 3 files changed, 145 insertions(+), 112 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index 113b11c..87106c2 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -935,11 +935,6 @@ static void light(SubmissionTarget* target) { return; } - typedef struct { - float xyz[3]; - float n[3]; - } EyeSpaceData; - static AlignedVector* eye_space_data = NULL; if(!eye_space_data) { @@ -966,25 +961,7 @@ static void light(SubmissionTarget* target) { for(i = 0; i < target->count; ++i, ++vertex, ++ES) { /* We ignore diffuse colour when lighting is enabled. If GL_COLOR_MATERIAL is enabled * then the lighting calculation should possibly take it into account */ - - GLfloat total [] = {0.0f, 0.0f, 0.0f, 0.0f}; - GLfloat to_add [] = {0.0f, 0.0f, 0.0f, 0.0f}; - GLubyte j; - for(j = 0; j < MAX_LIGHTS; ++j) { - if(_glIsLightEnabled(j)) { - _glCalculateLightingContribution(j, ES->xyz, ES->n, vertex->bgra, to_add); - - total[0] += to_add[0]; - total[1] += to_add[1]; - total[2] += to_add[2]; - total[3] += to_add[3]; - } - } - - vertex->bgra[A8IDX] = (GLubyte) (255.0f * fminf(total[3], 1.0f)); - vertex->bgra[R8IDX] = (GLubyte) (255.0f * fminf(total[0], 1.0f)); - vertex->bgra[G8IDX] = (GLubyte) (255.0f * fminf(total[1], 1.0f)); - vertex->bgra[B8IDX] = (GLubyte) (255.0f * fminf(total[2], 1.0f)); + _glCalculateLighting(ES, vertex); } } diff --git a/GL/lighting.c b/GL/lighting.c index 00c4e0a..2aa6188 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -281,98 +281,148 @@ static inline float FPOW(float b, float p) { return FEXP(FLOG(b) * p); } -void _glCalculateLightingContribution(const GLint light, const GLfloat* pos, const GLfloat* normal, uint8_t* bgra, GLfloat* colour) __attribute__((optimize("fast-math"))); -void _glCalculateLightingContribution(const GLint light, const GLfloat* pos, const GLfloat* normal, uint8_t* bgra, GLfloat* colour) { - LightSource* l = &LIGHTS[light]; +void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex) { - struct vec3f L = { - l->position[0], - l->position[1], - l->position[2] - }; + /* Before we begin, lets fiddle some pointers if COLOR_MATERIAL + * is enabled */ - if(!l->is_directional) { - L.x -= pos[0]; - L.y -= pos[1]; - L.z -= pos[2]; + const GLboolean colorMaterial = _glIsColorMaterialEnabled(); + const GLboolean isDiffuseCM = isDiffuseColorMaterial(); + const GLboolean isAmbientCM = isAmbientColorMaterial(); + const GLboolean isSpecularCM = isSpecularColorMaterial(); + + static GLfloat CM[4]; + + if(colorMaterial) { + CM[0] = ((GLfloat) vertex->bgra[R8IDX]) / 255.0f; + CM[1] = ((GLfloat) vertex->bgra[G8IDX]) / 255.0f; + CM[2] = ((GLfloat) vertex->bgra[B8IDX]) / 255.0f; + CM[3] = ((GLfloat) vertex->bgra[A8IDX]) / 255.0f; } - struct vec3f N = { - normal[0], - normal[1], - normal[2] - }; + const GLfloat* MD = (colorMaterial && isDiffuseCM) ? CM : MATERIAL.diffuse; + const GLfloat* MA = (colorMaterial && isAmbientCM) ? CM : MATERIAL.ambient; + const GLfloat* MS = (colorMaterial && isSpecularCM) ? CM : MATERIAL.specular; - struct vec3f V = { - pos[0], - pos[1], - pos[2] - }; + /* Right.. + * + * global propertie: + * + * acs - Global Ambient + * + * vertex-specific properties: + * + * ecm - Material Emission + * acm - Material Ambient + * dcm - Material Diffuse + * n - Normal + * V - Vertex Position + * VPe - Vector from V to eye point (0, 0, 0, -1) basically negative V + * + * light-specifc properties: + * + * att - Attenution + * acli - Light Ambient + * Ppli - Light Position + * dcli - Light Diffuse + * fi - 1/0 facing light or not + * VPpli - Vector from V to Ppli + * ndotPpli - Dot product between n and Ppli + * hi - + * PpliV - vector from Ppli to V + */ - GLfloat d; - vec3f_length(L.x, L.y, L.z, d); - GLfloat oneOverL = 1.0f / d; - - L.x *= oneOverL; - L.y *= oneOverL; - L.z *= oneOverL; - - vec3f_normalize(V.x, V.y, V.z); - - GLfloat NdotL, VdotN; - vec3f_dot(N.x, N.y, N.z, L.x, L.y, L.z, NdotL); - vec3f_dot(V.x, V.y, V.z, N.x, N.y, N.z, VdotN); - - GLfloat VdotR = VdotN - NdotL; - GLfloat specularPower = FPOW(VdotR > 0 ? VdotR : 0, MATERIAL.exponent); - - GLboolean colorMaterial = _glIsColorMaterialEnabled(); - - GLfloat mD [] = { - (colorMaterial && isDiffuseColorMaterial()) ? ((GLfloat)bgra[R8IDX]) / 255.0f : MATERIAL.diffuse[0], - (colorMaterial && isDiffuseColorMaterial()) ? ((GLfloat)bgra[G8IDX]) / 255.0f : MATERIAL.diffuse[1], - (colorMaterial && isDiffuseColorMaterial()) ? ((GLfloat)bgra[B8IDX]) / 255.0f : MATERIAL.diffuse[2], - (colorMaterial && isDiffuseColorMaterial()) ? ((GLfloat)bgra[A8IDX]) / 255.0f : MATERIAL.diffuse[3] - }; - - GLfloat mA [] = { - (colorMaterial && isAmbientColorMaterial()) ? ((GLfloat)bgra[R8IDX]) / 255.0f : MATERIAL.ambient[0], - (colorMaterial && isAmbientColorMaterial()) ? ((GLfloat)bgra[G8IDX]) / 255.0f : MATERIAL.ambient[1], - (colorMaterial && isAmbientColorMaterial()) ? ((GLfloat)bgra[B8IDX]) / 255.0f : MATERIAL.ambient[2], - (colorMaterial && isAmbientColorMaterial()) ? ((GLfloat)bgra[A8IDX]) / 255.0f : MATERIAL.ambient[3] - }; - - GLfloat mS [] = { - (colorMaterial && isSpecularColorMaterial()) ? ((GLfloat)bgra[R8IDX]) / 255.0f : MATERIAL.specular[0], - (colorMaterial && isSpecularColorMaterial()) ? ((GLfloat)bgra[G8IDX]) / 255.0f : MATERIAL.specular[1], - (colorMaterial && isSpecularColorMaterial()) ? ((GLfloat)bgra[B8IDX]) / 255.0f : MATERIAL.specular[2], - (colorMaterial && isSpecularColorMaterial()) ? ((GLfloat)bgra[A8IDX]) / 255.0f : MATERIAL.specular[3] - }; - - colour[0] = l->ambient[0] * mA[0]; - colour[1] = l->ambient[1] * mA[1]; - colour[2] = l->ambient[2] * mA[2]; - colour[3] = mD[3]; - - if(NdotL >= 0) { - colour[0] += (l->diffuse[0] * mD[0] * NdotL + l->specular[0] * mS[0] * specularPower); - colour[1] += (l->diffuse[1] * mD[1] * NdotL + l->specular[1] * mS[1] * specularPower); - colour[2] += (l->diffuse[2] * mD[2] * NdotL + l->specular[2] * mS[2] * specularPower); - } - - if(!l->is_directional) { - GLfloat att = ( - 1.0f / (l->constant_attenuation + (l->linear_attenuation * d) + (l->quadratic_attenuation * d * d)) - ); - - colour[0] *= att; - colour[1] *= att; - colour[2] *= att; - } - - if(colour[0] > 1.0f) colour[0] = 1.0f; - if(colour[1] > 1.0f) colour[1] = 1.0f; - if(colour[2] > 1.0f) colour[2] = 1.0f; - if(colour[3] > 1.0f) colour[3] = 1.0f; +/* Each colour component is calculated in its own scope + * so that the SH4 float registers don't get flooded */ +#define LIGHT_COMPONENT(C) { \ + const GLfloat acm = MA[C]; \ + const GLfloat dcm = MD[C]; \ + const GLfloat scm = MS[C]; \ + const GLfloat scli = light->specular[C]; \ + const GLfloat dcli = light->diffuse[C]; \ + const GLfloat acli = light->ambient[C]; \ + const GLfloat srm = MATERIAL.exponent; \ +\ + final[C] += (att * spot * ( \ + (acm * acli) + (ndotVPpli * dcm * dcli) + \ + (FPOW((fi * ndothi), srm) * scm * scli) \ + )); \ } + + const GLfloat* n = ES->n; + const GLfloat* V = ES->xyz; + + GLfloat Vpe [] = {-V[0], -V[1], -V[2]}; + GLfloat VpeL; + vec3f_length(Vpe[0], Vpe[1], Vpe[2], VpeL); + Vpe[0] /= VpeL; + Vpe[1] /= VpeL; + Vpe[2] /= VpeL; + + GLfloat final[4] = { + MATERIAL.emissive[0] + (MA[0] * SCENE_AMBIENT[0]), + MATERIAL.emissive[1] + (MA[1] * SCENE_AMBIENT[1]), + MATERIAL.emissive[2] + (MA[2] * SCENE_AMBIENT[2]), + MD[3] // GL spec says alpha is always from the diffuse + }; + + GLubyte i; + for(i = 0; i < MAX_LIGHTS; ++i) { + if(!_glIsLightEnabled(i)) continue; + + const LightSource* light = &LIGHTS[i]; + + const GLfloat* Ppli = light->position; + GLfloat VPpli [] = { + Ppli[0] - V[0], + Ppli[1] - V[1], + Ppli[2] - V[2] + }; + + GLfloat VPpliL; + vec3f_length(VPpli[0], VPpli[1], VPpli[2], VPpliL); + + VPpli[0] /= VPpliL; + VPpli[1] /= VPpliL; + VPpli[2] /= VPpliL; + + GLfloat ndotVPpli; + vec3f_dot(n[0], n[1], n[2], VPpli[0], VPpli[1], VPpli[2], ndotVPpli); + + const GLfloat k0 = light->constant_attenuation; + const GLfloat k1 = light->linear_attenuation; + const GLfloat k2 = light->quadratic_attenuation; + const GLfloat att = (light->position[3] == 0) ? 1.0f : 1.0f / k0 + (k1 * VPpliL) + (k2 * VPpliL * VPpliL); + const GLfloat spot = 1.0f; // FIXME: Spotlights + + const GLfloat fi = (ndotVPpli == 0) ? 0 : 1; + + GLfloat hi [3]; + if(!VIEWER_IN_EYE_COORDINATES) { + // FIXME: Docs show power of T or something? + hi[0] = VPpli[0] + 0; + hi[1] = VPpli[1] + 0; + hi[2] = VPpli[2] + 1; + } else { + hi[0] = VPpli[0] + Vpe[0]; + hi[1] = VPpli[1] + Vpe[1]; + hi[2] = VPpli[2] + Vpe[2]; + } + + GLfloat ndothi; + vec3f_dot(n[0], n[1], n[2], hi[0], hi[1], hi[2], ndothi); + + LIGHT_COMPONENT(0); + LIGHT_COMPONENT(1); + LIGHT_COMPONENT(2); + } + +#undef LIGHT_COMPONENT + + vertex->bgra[R8IDX] = (GLubyte)(fminf(final[0] * 255.0f, 255.0f)); + vertex->bgra[G8IDX] = (GLubyte)(fminf(final[1] * 255.0f, 255.0f)); + vertex->bgra[B8IDX] = (GLubyte)(fminf(final[2] * 255.0f, 255.0f)); + vertex->bgra[A8IDX] = (GLubyte)(fminf(final[3] * 255.0f, 255.0f)); +} + diff --git a/GL/private.h b/GL/private.h index d708fef..7fef33f 100644 --- a/GL/private.h +++ b/GL/private.h @@ -240,7 +240,13 @@ GLuint _glGetMipmapLevelCount(TextureObject* obj); GLboolean _glIsLightingEnabled(); GLboolean _glIsLightEnabled(GLubyte light); GLboolean _glIsColorMaterialEnabled(); -void _glCalculateLightingContribution(const GLint light, const GLfloat* pos, const GLfloat* normal, uint8_t* bgra, GLfloat* colour); + +typedef struct { + float xyz[3]; + float n[3]; +} EyeSpaceData; + +extern void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex); unsigned char _glIsClippingEnabled(); void _glEnableClipping(unsigned char v); From b6fccd31487a843387f1284b54ae5d30231bca62 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Thu, 8 Aug 2019 09:36:38 +0100 Subject: [PATCH 07/64] Use PVR_TXRENV_MODULATE instead of MODULATEALPHA. Fixes #48 --- GL/texture.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GL/texture.c b/GL/texture.c index f3a1662..91352fe 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -260,7 +260,7 @@ static void _glInitializeTextureObject(TextureObject* txr, unsigned int id) { txr->width = txr->height = 0; txr->mipmap = 0; txr->uv_clamp = 0; - txr->env = PVR_TXRENV_MODULATEALPHA; + txr->env = PVR_TXRENV_MODULATE; txr->data = NULL; txr->mipmapCount = 0; txr->minFilter = GL_NEAREST; @@ -370,7 +370,7 @@ void APIENTRY glTexEnvi(GLenum target, GLenum pname, GLint param) { switch(param) { case GL_MODULATE: - active->env = PVR_TXRENV_MODULATEALPHA; + active->env = PVR_TXRENV_MODULATE; break; case GL_DECAL: active->env = PVR_TXRENV_DECAL; From 57f4f62f86f23719452de5c0af293b5b3edab115 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Tue, 20 Aug 2019 20:42:29 +0100 Subject: [PATCH 08/64] Implement glColor3ubv --- GL/immediate.c | 7 +++++++ include/gl.h | 1 + 2 files changed, 8 insertions(+) diff --git a/GL/immediate.c b/GL/immediate.c index 77b1919..d95c887 100644 --- a/GL/immediate.c +++ b/GL/immediate.c @@ -131,6 +131,13 @@ void APIENTRY glColor3ub(GLubyte red, GLubyte green, GLubyte blue) { COLOR[3] = 255; } +void APIENTRY glColor3ubv(const GLubyte *v) { + COLOR[0] = v[0]; + COLOR[1] = v[1]; + COLOR[2] = v[2]; + COLOR[3] = 255; +} + void APIENTRY glColor3fv(const GLfloat* v) { COLOR[0] = (GLubyte)(v[0] * 255); COLOR[1] = (GLubyte)(v[1] * 255); diff --git a/include/gl.h b/include/gl.h index bb9c52c..b0b5c44 100644 --- a/include/gl.h +++ b/include/gl.h @@ -417,6 +417,7 @@ GLAPI void APIENTRY glColor1ui(GLuint argb); GLAPI void APIENTRY glColor4ub(GLubyte r, GLubyte g, GLubyte b, GLubyte a); GLAPI void APIENTRY glColor3f(GLfloat r, GLfloat g, GLfloat b); GLAPI void APIENTRY glColor3ub(GLubyte r, GLubyte g, GLubyte b); +GLAPI void APIENTRY glColor3ubv(const GLubyte *v); GLAPI void APIENTRY glColor3fv(const GLfloat *rgb); GLAPI void APIENTRY glColor4f(GLfloat r, GLfloat g, GLfloat b, GLfloat a); GLAPI void APIENTRY glColor4fv(const GLfloat *rgba); From 041b545177a3ea9f58c6827abbafa688a8bb981a Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Tue, 20 Aug 2019 21:02:08 +0100 Subject: [PATCH 09/64] Add missing glDepthRange declaration --- include/gl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/gl.h b/include/gl.h index b0b5c44..3908833 100644 --- a/include/gl.h +++ b/include/gl.h @@ -475,6 +475,7 @@ GLAPI void APIENTRY glClearDepth(GLfloat depth); GLAPI void APIENTRY glClearDepthf(GLfloat depth); GLAPI void APIENTRY glDepthMask(GLboolean flag); GLAPI void APIENTRY glDepthFunc(GLenum func); +GLAPI void APIENTRY glDepthRange(GLclampf n, GLclampf f); /* Hints */ /* Currently Supported Capabilities: From 8e59b9adb59e2e5e9a3e7f6c5f9b88db9b33a386 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 4 Sep 2019 17:58:55 +0100 Subject: [PATCH 10/64] Make sure we clamp floating point colours to avoid overflows --- GL/draw.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index 87106c2..abd59d1 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -74,6 +74,11 @@ typedef void (*ByteParseFunc)(GLubyte* out, const GLubyte* in); typedef void (*PolyBuildFunc)(Vertex* first, Vertex* previous, Vertex* vertex, Vertex* next, const GLsizei i); +static inline float clamp(float d, float min, float max) { + const float t = d < min ? min : d; + return t > max ? max : t; +} + static void _readVertexData3f3f(const float* input, GLuint count, GLubyte stride, float* output) { ITERATE(count) { output[0] = input[0]; @@ -306,21 +311,21 @@ static void _readVertexData4ubARGB(const GLubyte* input, GLuint count, GLubyte s static void _readVertexData4fARGB(const float* input, GLuint count, GLubyte stride, GLubyte* output) { ITERATE(count) { - output[R8IDX] = (GLubyte) (input[0] * 255.0f); - output[G8IDX] = (GLubyte) (input[1] * 255.0f); - output[B8IDX] = (GLubyte) (input[2] * 255.0f); - output[A8IDX] = (GLubyte) (input[3] * 255.0f); + output[R8IDX] = (GLubyte) clamp(input[0] * 255.0f, 0, 255); + output[G8IDX] = (GLubyte) clamp(input[1] * 255.0f, 0, 255); + output[B8IDX] = (GLubyte) clamp(input[2] * 255.0f, 0, 255); + output[A8IDX] = (GLubyte) clamp(input[3] * 255.0f, 0, 255); input = (float*) (((GLubyte*) input) + stride); - output = (GLubyte*) (((GLubyte*) output) + sizeof(Vertex)); + output += sizeof(Vertex); } } static void _readVertexData3fARGB(const float* input, GLuint count, GLubyte stride, GLubyte* output) { ITERATE(count) { - output[R8IDX] = (GLubyte) (input[0] * 255.0f); - output[G8IDX] = (GLubyte) (input[1] * 255.0f); - output[B8IDX] = (GLubyte) (input[2] * 255.0f); + output[R8IDX] = (GLubyte) clamp(input[0] * 255.0f, 0, 255); + output[G8IDX] = (GLubyte) clamp(input[1] * 255.0f, 0, 255); + output[B8IDX] = (GLubyte) clamp(input[2] * 255.0f, 0, 255); output[A8IDX] = 1.0f; input = (float*) (((GLubyte*) input) + stride); From 83bad86bca2d569deda370e48612f82c5ceacdfc Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 6 Sep 2019 09:34:33 +0100 Subject: [PATCH 11/64] Add a blend_test sample --- samples/Makefile | 1 + samples/blend_test/Makefile | 29 +++++++ samples/blend_test/main.c | 100 +++++++++++++++++++++++++ samples/blend_test/romdisk/PLACEHOLDER | 0 4 files changed, 130 insertions(+) create mode 100644 samples/blend_test/Makefile create mode 100644 samples/blend_test/main.c create mode 100644 samples/blend_test/romdisk/PLACEHOLDER diff --git a/samples/Makefile b/samples/Makefile index 598cd17..d8df580 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -27,3 +27,4 @@ all: $(KOS_MAKE) -C depth_funcs all $(KOS_MAKE) -C polymark all $(KOS_MAKE) -C polygon_offset all + $(KOS_MAKE) -C blend_test all diff --git a/samples/blend_test/Makefile b/samples/blend_test/Makefile new file mode 100644 index 0000000..cfff063 --- /dev/null +++ b/samples/blend_test/Makefile @@ -0,0 +1,29 @@ +TARGET = blend_test.elf +OBJS = main.o + +all: rm-elf $(TARGET) + +include $(KOS_BASE)/Makefile.rules + +clean: + -rm -f $(TARGET) $(OBJS) romdisk.* + +rm-elf: + -rm -f $(TARGET) romdisk.* + +$(TARGET): $(OBJS) romdisk.o + $(KOS_CC) $(KOS_CFLAGS) $(KOS_LDFLAGS) -o $(TARGET) $(KOS_START) \ + $(OBJS) romdisk.o $(OBJEXTRA) -lm -lkosutils $(KOS_LIBS) + +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) diff --git a/samples/blend_test/main.c b/samples/blend_test/main.c new file mode 100644 index 0000000..4fcad50 --- /dev/null +++ b/samples/blend_test/main.c @@ -0,0 +1,100 @@ +/* + * This sample is to demonstrate a bug where rendering an unblended + * polygon, before a series of blended ones would result in no blended + * output and incorrect depth testing + */ + +#include "gl.h" +#include "glu.h" +#include "glkos.h" + +/* A general OpenGL initialization function. Sets all of the initial parameters. */ +void InitGL(int Width, int Height) // We call this right after our OpenGL window is created. +{ + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // This Will Clear The Background Color To Black + glClearDepth(1.0); // Enables Clearing Of The Depth Buffer + glDepthFunc(GL_LEQUAL); // The Type Of Depth Test To Do + glEnable(GL_DEPTH_TEST); // Enables Depth Testing + glEnable(GL_TEXTURE_2D); + glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading + glDisable(GL_BLEND); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); // Reset The Projection Matrix + + gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); // Calculate The Aspect Ratio Of The Window + + glMatrixMode(GL_MODELVIEW); +} + +/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */ +void ReSizeGLScene(int Width, int Height) +{ + if (Height == 0) // Prevent A Divide By Zero If The Window Is Too Small + Height = 1; + + glViewport(0, 0, Width, Height); // Reset The Current Viewport And Perspective Transformation + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); + glMatrixMode(GL_MODELVIEW); +} + + +void DrawQuad(const float* colour) { + glBegin(GL_QUADS); + glColor4fv(colour); + glVertex3f(-1.0,-1.0, 0.0); + glVertex3f( 1.0,-1.0, 0.0); + glVertex3f( 1.0, 1.0, 0.0); + glVertex3f(-1.0, 1.0, 0.0); + glEnd(); +} + +/* The main drawing function. */ +void DrawGLScene() +{ + const float RED [] = {1.0, 0, 0, 0.5}; + const float BLUE [] = {0.0, 0, 1, 0.5}; + const float NONE [] = {0, 0, 0, 0}; + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer + glLoadIdentity(); // Reset The View + + glTranslatef(0, 0, -10.0f); // Move Left 1.5 Units And Into The Screen 6.0 + + glPushMatrix(); + glTranslatef(-4.0, 0, -10); + DrawQuad(RED); + glPopMatrix(); + + glTranslatef(4.0, 0, 0); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + /* Draw 3 overlapping quads, 2 of which should be totally transparent so the + * output should be the third */ + DrawQuad(NONE); + DrawQuad(NONE); + DrawQuad(BLUE); + + glDisable(GL_BLEND); + + glKosSwapBuffers(); +} + +int main(int argc, char **argv) +{ + glKosInit(); + + InitGL(640, 480); + ReSizeGLScene(640, 480); + + while(1) { + DrawGLScene(); + } + + return 0; +} diff --git a/samples/blend_test/romdisk/PLACEHOLDER b/samples/blend_test/romdisk/PLACEHOLDER new file mode 100644 index 0000000..e69de29 From 4170655a88f58e4608dfef32a31306592b4c2766 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 6 Sep 2019 09:34:55 +0100 Subject: [PATCH 12/64] If someone attempts to use lines, just log an error and render nothing (for now) --- GL/draw.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GL/draw.c b/GL/draw.c index abd59d1..b7a76ec 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -1024,6 +1024,11 @@ static void submitVertices(GLenum mode, GLsizei first, GLuint count, GLenum type return; } + if(mode == GL_LINE_STRIP || mode == GL_LINES) { + fprintf(stderr, "Line drawing is currently unsupported\n"); + return; + } + static SubmissionTarget* target = NULL; static AlignedVector extras; From 22690600d0c27700d56c26d6e35d48992ed129bc Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 6 Sep 2019 09:35:33 +0100 Subject: [PATCH 13/64] Don't include kos headers from gl.h --- GL/matrix.c | 5 ++++- GL/private.h | 5 +++++ include/gl.h | 7 ------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/GL/matrix.c b/GL/matrix.c index 36f1455..2de3e38 100644 --- a/GL/matrix.c +++ b/GL/matrix.c @@ -1,7 +1,10 @@ #include -#include #include +#include +#include +#include +#include #include "../include/gl.h" #include "../containers/stack.h" diff --git a/GL/private.h b/GL/private.h index 7fef33f..ee78b3d 100644 --- a/GL/private.h +++ b/GL/private.h @@ -2,6 +2,11 @@ #define PRIVATE_H #include +#include +#include +#include +#include +#include #include "../include/gl.h" #include "../containers/aligned_vector.h" diff --git a/include/gl.h b/include/gl.h index 3908833..52740dc 100644 --- a/include/gl.h +++ b/include/gl.h @@ -19,13 +19,6 @@ __BEGIN_DECLS #include -#include -#include -#include -#include -#include -#include - /* Primitive Types taken from GL for compatability */ /* Not all types are implemented in Open GL DC V.1.0 */ #define GL_POINTS 0x01 From 1d32834b52ae90e8a8be62599813aead4946e726 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 6 Sep 2019 09:35:57 +0100 Subject: [PATCH 14/64] Simplify texture and blending context updates --- GL/state.c | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/GL/state.c b/GL/state.c index 5f7186e..2e718bf 100644 --- a/GL/state.c +++ b/GL/state.c @@ -118,17 +118,14 @@ static int _calcPVRBlendFactor(GLenum factor) { static void _updatePVRBlend(pvr_poly_cxt_t* context) { if(BLEND_ENABLED) { context->gen.alpha = PVR_ALPHA_ENABLE; - context->blend.src = _calcPVRBlendFactor(BLEND_SFACTOR); - context->blend.dst = _calcPVRBlendFactor(BLEND_DFACTOR); - context->blend.src_enable = PVR_BLEND_DISABLE; - context->blend.dst_enable = PVR_BLEND_DISABLE; + context->txr.alpha = PVR_TXRALPHA_ENABLE; } else { context->gen.alpha = PVR_ALPHA_DISABLE; - context->blend.src = PVR_BLEND_ONE; - context->blend.dst = PVR_BLEND_ZERO; - context->blend.src_enable = PVR_BLEND_DISABLE; - context->blend.dst_enable = PVR_BLEND_DISABLE; + context->txr.alpha = PVR_TXRALPHA_DISABLE; } + + context->blend.src = _calcPVRBlendFactor(BLEND_SFACTOR); + context->blend.dst = _calcPVRBlendFactor(BLEND_DFACTOR); } GLboolean _glCheckValidEnum(GLint param, GLint* values, const char* func) { @@ -155,16 +152,15 @@ static GLboolean TEXTURES_ENABLED [] = {GL_FALSE, GL_FALSE}; void _glUpdatePVRTextureContext(pvr_poly_cxt_t* context, GLshort textureUnit) { const TextureObject *tx1 = (textureUnit == 0) ? _glGetTexture0() : _glGetTexture1(); - if(!TEXTURES_ENABLED[textureUnit] || !tx1) { - context->txr.enable = PVR_TEXTURE_DISABLE; - context->txr.base = 0; - context->txr.format = 0; - return; - } - + /* Disable all texturing to start with */ + context->txr.enable = PVR_TEXTURE_DISABLE; context->txr2.enable = PVR_TEXTURE_DISABLE; context->txr2.alpha = PVR_TXRALPHA_DISABLE; + if(!TEXTURES_ENABLED[textureUnit] || !tx1) { + return; + } + GLuint filter = PVR_FILTER_NEAREST; GLboolean enableMipmaps = GL_FALSE; @@ -206,9 +202,6 @@ void _glUpdatePVRTextureContext(pvr_poly_cxt_t* context, GLshort textureUnit) { * This is effectively what standard GL does (it renders a white texture) */ if(!_glIsMipmapComplete(tx1) && enableMipmaps) { - context->txr.enable = PVR_TEXTURE_DISABLE; - context->txr.base = 0; - context->txr.format = 0; return; } @@ -234,9 +227,6 @@ void _glUpdatePVRTextureContext(pvr_poly_cxt_t* context, GLshort textureUnit) { context->txr.env = tx1->env; context->txr.uv_flip = PVR_UVFLIP_NONE; context->txr.uv_clamp = tx1->uv_clamp; - context->txr.alpha = PVR_TXRALPHA_ENABLE; - } else { - context->txr.enable = PVR_TEXTURE_DISABLE; } } From 200db3593e7c60168b431ebc6b68d34fa47cbb9e Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 6 Sep 2019 09:36:14 +0100 Subject: [PATCH 15/64] Make primitive constants match the spec --- include/gl.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/gl.h b/include/gl.h index 52740dc..a1d2f57 100644 --- a/include/gl.h +++ b/include/gl.h @@ -21,16 +21,16 @@ __BEGIN_DECLS /* Primitive Types taken from GL for compatability */ /* Not all types are implemented in Open GL DC V.1.0 */ -#define GL_POINTS 0x01 -#define GL_LINES 0x02 -#define GL_LINE_LOOP 0x03 -#define GL_LINE_STRIP 0x04 -#define GL_TRIANGLES 0x05 -#define GL_TRIANGLE_STRIP 0x06 -#define GL_TRIANGLE_FAN 0x07 -#define GL_QUADS 0x08 -#define GL_QUAD_STRIP 0x09 -#define GL_POLYGON 0x0A +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 /* FrontFaceDirection */ #define GL_CW 0x0900 From 3af18cb5143ac25d16c6f3c5e14ac0461215348a Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sun, 8 Sep 2019 17:27:56 +0100 Subject: [PATCH 16/64] Refactor the perspective divide to work with glDepthRange --- GL/draw.c | 10 +++++++--- GL/matrix.c | 7 ++++--- GL/private.h | 3 +++ GL/state.c | 1 - 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index b7a76ec..b68e0c5 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -811,6 +811,7 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei genTriangleStrip(_glSubmissionTargetStart(target), count); break; default: + fprintf(stderr, "Unhandled mode %d\n", (int) mode); assert(0 && "Not Implemented"); } @@ -977,9 +978,12 @@ static void divide(SubmissionTarget* target) { Vertex* vertex = _glSubmissionTargetStart(target); ITERATE(target->count) { - vertex->xyz[2] = 1.0f / vertex->w; - vertex->xyz[0] *= vertex->xyz[2]; - vertex->xyz[1] *= vertex->xyz[2]; + // fprintf(stderr, "%f %f %f -> ", vertex->xyz[0], vertex->xyz[1], vertex->xyz[2]); + vertex->xyz[0] /= vertex->w; + vertex->xyz[1] /= vertex->w; + vertex->xyz[2] /= vertex->w; + vertex->xyz[2] = (DEPTH_RANGE_MULTIPLIER_L * (1.0f / vertex->xyz[2])) + DEPTH_RANGE_MULTIPLIER_H; + // fprintf(stderr, "%f %f %f %f\n", vertex->xyz[0], vertex->xyz[1], vertex->xyz[2], vertex->w); ++vertex; } } diff --git a/GL/matrix.c b/GL/matrix.c index 2de3e38..a40e7ca 100644 --- a/GL/matrix.c +++ b/GL/matrix.c @@ -15,7 +15,8 @@ static GLfloat gl_viewport_scale[3], gl_viewport_offset[3]; /* Depth range */ -static GLclampf gl_depthrange_near, gl_depthrange_far; +GLfloat DEPTH_RANGE_MULTIPLIER_L = (1 - 0) / 2; +GLfloat DEPTH_RANGE_MULTIPLIER_H = (0 + 1) / 2; /* Viewport size */ static GLint gl_viewport_x1, gl_viewport_y1, gl_viewport_width, gl_viewport_height; @@ -316,8 +317,8 @@ void APIENTRY glDepthRange(GLclampf n, GLclampf f) { if(f < 0.0f) f = 0.0f; else if(f > 1.0f) f = 1.0f; - gl_depthrange_near = n; - gl_depthrange_far = f; + DEPTH_RANGE_MULTIPLIER_L = (f - n) / 2.0f; + DEPTH_RANGE_MULTIPLIER_H = (n + f) / 2.0f; } /* Vector Cross Product - Used by glhLookAtf2 */ diff --git a/GL/private.h b/GL/private.h index ee78b3d..36e898a 100644 --- a/GL/private.h +++ b/GL/private.h @@ -195,6 +195,9 @@ void _glMatrixLoadModelView(); void _glMatrixLoadTexture(); void _glApplyRenderMatrix(); +extern GLfloat DEPTH_RANGE_MULTIPLIER_L; +extern GLfloat DEPTH_RANGE_MULTIPLIER_H; + matrix_t* _glGetProjectionMatrix(); matrix_t* _glGetModelViewMatrix(); diff --git a/GL/state.c b/GL/state.c index 2e718bf..f927c54 100644 --- a/GL/state.c +++ b/GL/state.c @@ -421,7 +421,6 @@ GLAPI void APIENTRY glReadBuffer(GLenum mode) { GLAPI void APIENTRY glDepthMask(GLboolean flag) { GL_CONTEXT.depth.write = (flag == GL_TRUE) ? PVR_DEPTHWRITE_ENABLE : PVR_DEPTHWRITE_DISABLE; - GL_CONTEXT.depth.comparison = _calc_pvr_depth_test(); } GLAPI void APIENTRY glDepthFunc(GLenum func) { From e7f2ad7dccbead2c44e6c2a23488d70a35bc3336 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 9 Sep 2019 09:36:06 +0100 Subject: [PATCH 17/64] Optimise multitexture submission --- GL/draw.c | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index b68e0c5..a278392 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -988,7 +988,7 @@ static void divide(SubmissionTarget* target) { } } -static void push(PVRHeader* header, Vertex* output, const GLuint count, PolyList* activePolyList, GLshort textureUnit) { +static void push(PVRHeader* header, GLboolean multiTextureHeader, PolyList* activePolyList, GLshort textureUnit) { TRACE(); // Compile the header @@ -997,6 +997,16 @@ static void push(PVRHeader* header, Vertex* output, const GLuint count, PolyList _glUpdatePVRTextureContext(&cxt, textureUnit); + if(multiTextureHeader) { + assert(cxt.list_type == PVR_LIST_TR_POLY); + + cxt.gen.alpha = PVR_ALPHA_ENABLE; + cxt.txr.alpha = PVR_TXRALPHA_ENABLE; + cxt.blend.src = PVR_BLEND_ZERO; + cxt.blend.dst = PVR_BLEND_DESTCOLOR; + cxt.depth.comparison = PVR_DEPTHCMP_EQUAL; + } + pvr_poly_compile(&header->hdr, &cxt); /* Post-process the vertex list */ @@ -1147,7 +1157,7 @@ static void submitVertices(GLenum mode, GLsizei first, GLuint count, GLenum type profiler_checkpoint("divide"); - push(_glSubmissionTargetHeader(target), _glSubmissionTargetStart(target), target->count, target->output, 0); + push(_glSubmissionTargetHeader(target), GL_FALSE, target->output, 0); profiler_checkpoint("push"); /* @@ -1183,7 +1193,6 @@ static void submitVertices(GLenum mode, GLsizei first, GLuint count, GLenum type assert(vertex); PVRHeader* mtHeader = (PVRHeader*) vertex++; - Vertex* mtStart = vertex; /* Replace the UV coordinates with the ST ones */ VertexExtra* ve = aligned_vector_at(target->extras, 0); @@ -1194,29 +1203,8 @@ static void submitVertices(GLenum mode, GLsizei first, GLuint count, GLenum type ++ve; } - /* Store state, as we're about to mess around with it */ - GLint depthFunc, blendSrc, blendDst; - glGetIntegerv(GL_DEPTH_FUNC, &depthFunc); - glGetIntegerv(GL_BLEND_SRC, &blendSrc); - glGetIntegerv(GL_BLEND_DST, &blendDst); - - GLboolean blendEnabled = glIsEnabled(GL_BLEND); - GLboolean depthEnabled = glIsEnabled(GL_DEPTH_TEST); - - glDepthFunc(GL_EQUAL); - glEnable(GL_BLEND); - - /* This is modulation, we need to switch depending on the texture env mode! */ - glBlendFunc(GL_DST_COLOR, GL_ZERO); - /* Send the buffer again to the transparent list */ - push(mtHeader, mtStart, target->count, _glTransparentPolyList(), 1); - - /* Reset state */ - glDepthFunc(depthFunc); - glBlendFunc(blendSrc, blendDst); - (blendEnabled) ? glEnable(GL_BLEND) : glDisable(GL_BLEND); - (depthEnabled) ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST); + push(mtHeader, GL_TRUE, _glTransparentPolyList(), 1); profiler_pop(); } From e39632bcc4257d8d4d5b43652679223bc59c7bb9 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sat, 14 Sep 2019 20:51:47 +0100 Subject: [PATCH 18/64] Refactor matrix management and clipping --- GL/clip.c | 9 +- GL/draw.c | 12 +- GL/glu.c | 10 +- GL/matrix.c | 427 +++++++++++++++++++++++++++++++++++---------------- GL/private.h | 51 +++++- 5 files changed, 361 insertions(+), 148 deletions(-) diff --git a/GL/clip.c b/GL/clip.c index c140c01..772386f 100644 --- a/GL/clip.c +++ b/GL/clip.c @@ -26,7 +26,7 @@ void _glEnableClipping(unsigned char v) { void _glClipLineToNearZ(const Vertex* v1, const Vertex* v2, Vertex* vout, float* t) __attribute__((optimize("fast-math"))); void _glClipLineToNearZ(const Vertex* v1, const Vertex* v2, Vertex* vout, float* t) { - const float NEAR_PLANE = 0.2; // FIXME: this needs to be read from the projection matrix.. somehow + const float NEAR_PLANE = NEAR_PLANE_DISTANCE + 0.0001f; *t = (NEAR_PLANE - v1->w) / (v2->w - v1->w); @@ -182,6 +182,13 @@ void _glClipTriangle(const Triangle* triangle, const uint8_t visible, Submission static inline void markDead(Vertex* vert) { vert->flags = VERTEX_CMD_EOL; + + // If we're debugging, wipe out the xyz +#ifndef NDEBUG + *((uint32_t*) &vert->xyz[0]) = 0xDEADBEEF; + *((uint32_t*) &vert->xyz[1]) = 0xDEADBEEF; + *((uint32_t*) &vert->xyz[2]) = 0xDEADBEEF; +#endif } #define B000 0 diff --git a/GL/draw.c b/GL/draw.c index a278392..81d50f4 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -879,7 +879,7 @@ static void transform(SubmissionTarget* target) { register float __x __asm__("fr12") = (vertex->xyz[0]); register float __y __asm__("fr13") = (vertex->xyz[1]); register float __z __asm__("fr14") = (vertex->xyz[2]); - register float __w __asm__("fr15"); + register float __w __asm__("fr15") = (vertex->w); __asm__ __volatile__( "fldi1 fr15\n" @@ -978,12 +978,10 @@ static void divide(SubmissionTarget* target) { Vertex* vertex = _glSubmissionTargetStart(target); ITERATE(target->count) { - // fprintf(stderr, "%f %f %f -> ", vertex->xyz[0], vertex->xyz[1], vertex->xyz[2]); - vertex->xyz[0] /= vertex->w; - vertex->xyz[1] /= vertex->w; - vertex->xyz[2] /= vertex->w; - vertex->xyz[2] = (DEPTH_RANGE_MULTIPLIER_L * (1.0f / vertex->xyz[2])) + DEPTH_RANGE_MULTIPLIER_H; - // fprintf(stderr, "%f %f %f %f\n", vertex->xyz[0], vertex->xyz[1], vertex->xyz[2], vertex->w); + float f = 1.0f / vertex->w; + vertex->xyz[0] *= f; + vertex->xyz[1] *= f; + vertex->xyz[2] = 1.0 - ((DEPTH_RANGE_MULTIPLIER_L * vertex->xyz[2] * f) + DEPTH_RANGE_MULTIPLIER_H); ++vertex; } } diff --git a/GL/glu.c b/GL/glu.c index ae9a590..0888207 100644 --- a/GL/glu.c +++ b/GL/glu.c @@ -4,14 +4,12 @@ /* Set the Perspective */ void APIENTRY gluPerspective(GLfloat angle, GLfloat aspect, GLfloat znear, GLfloat zfar) { - GLfloat xmin, xmax, ymin, ymax; + GLdouble fW, fH; - ymax = znear * tanf(angle * F_PI / 360.0f); - ymin = -ymax; - xmin = ymin * aspect; - xmax = ymax * aspect; + fH = tan(angle / 360 * F_PI) * znear; + fW = fH * aspect; - glFrustum(xmin, xmax, ymin, ymax, znear, zfar); + glFrustum(-fW, fW, -fH, fH, znear, zfar); } void APIENTRY gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top) { diff --git a/GL/matrix.c b/GL/matrix.c index a40e7ca..4037ca1 100644 --- a/GL/matrix.c +++ b/GL/matrix.c @@ -6,6 +6,7 @@ #include #include +#include "private.h" #include "../include/gl.h" #include "../containers/stack.h" @@ -22,40 +23,63 @@ GLfloat DEPTH_RANGE_MULTIPLIER_H = (0 + 1) / 2; static GLint gl_viewport_x1, gl_viewport_y1, gl_viewport_width, gl_viewport_height; static Stack MATRIX_STACKS[3]; // modelview, projection, texture -static matrix_t NORMAL_MATRIX __attribute__((aligned(32))); -static matrix_t SCREENVIEW_MATRIX __attribute__((aligned(32))); +static Matrix4x4 NORMAL_MATRIX __attribute__((aligned(32))); +static Matrix4x4 SCREENVIEW_MATRIX __attribute__((aligned(32))); static GLenum MATRIX_MODE = GL_MODELVIEW; static GLubyte MATRIX_IDX = 0; -static const matrix_t IDENTITY = { - {1.0f, 0.0f, 0.0f, 0.0f}, - {0.0f, 1.0f, 0.0f, 0.0f}, - {0.0f, 0.0f, 1.0f, 0.0f}, - {0.0f, 0.0f, 0.0f, 1.0f} +static const Matrix4x4 IDENTITY = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; +GLfloat NEAR_PLANE_DISTANCE = 0.0f; + +static void _glStoreNearPlane() { + Matrix4x4* proj = (Matrix4x4*) stack_top(MATRIX_STACKS + (GL_PROJECTION & 0xF)); + + GLfloat a = *(*proj + 10); + GLfloat b = *(*proj + 14); + + NEAR_PLANE_DISTANCE = -b / (1.0f - a); +} + void APIENTRY glDepthRange(GLclampf n, GLclampf f); -matrix_t* _glGetProjectionMatrix() { - return (matrix_t*) stack_top(&MATRIX_STACKS[1]); +static inline void upload_matrix(Matrix4x4* m) { + mat_load((matrix_t*) m); } -matrix_t* _glGetModelViewMatrix() { - return (matrix_t*) stack_top(&MATRIX_STACKS[0]); +static inline void multiply_matrix(Matrix4x4* m) { + mat_apply((matrix_t*) m); +} + +static inline void download_matrix(Matrix4x4* m) { + mat_store((matrix_t*) m); +} + +Matrix4x4* _glGetProjectionMatrix() { + return (Matrix4x4*) stack_top(&MATRIX_STACKS[1]); +} + +Matrix4x4* _glGetModelViewMatrix() { + return (Matrix4x4*) stack_top(&MATRIX_STACKS[0]); } void _glInitMatrices() { - init_stack(&MATRIX_STACKS[0], sizeof(matrix_t), 32); - init_stack(&MATRIX_STACKS[1], sizeof(matrix_t), 32); - init_stack(&MATRIX_STACKS[2], sizeof(matrix_t), 32); + init_stack(&MATRIX_STACKS[0], sizeof(Matrix4x4), 32); + init_stack(&MATRIX_STACKS[1], sizeof(Matrix4x4), 32); + init_stack(&MATRIX_STACKS[2], sizeof(Matrix4x4), 32); stack_push(&MATRIX_STACKS[0], IDENTITY); stack_push(&MATRIX_STACKS[1], IDENTITY); stack_push(&MATRIX_STACKS[2], IDENTITY); - memcpy(NORMAL_MATRIX, IDENTITY, sizeof(matrix_t)); - memcpy(SCREENVIEW_MATRIX, IDENTITY, sizeof(matrix_t)); + memcpy(NORMAL_MATRIX, IDENTITY, sizeof(Matrix4x4)); + memcpy(SCREENVIEW_MATRIX, IDENTITY, sizeof(Matrix4x4)); glDepthRange(0.0f, 1.0f); glViewport(0, 0, vid_mode->width, vid_mode->height); @@ -99,7 +123,7 @@ static void transpose(GLfloat* m) { } static void recalculateNormalMatrix() { - memcpy(NORMAL_MATRIX, stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF)), sizeof(matrix_t)); + memcpy(NORMAL_MATRIX, stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF)), sizeof(Matrix4x4)); inverse((GLfloat*) NORMAL_MATRIX); transpose((GLfloat*) NORMAL_MATRIX); } @@ -125,9 +149,20 @@ void APIENTRY glLoadIdentity() { } void APIENTRY glTranslatef(GLfloat x, GLfloat y, GLfloat z) { - mat_load(stack_top(MATRIX_STACKS + MATRIX_IDX)); - mat_translate(x, y, z); - mat_store(stack_top(MATRIX_STACKS + MATRIX_IDX)); + static Matrix4x4 trn __attribute__((aligned(32))) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + trn[M12] = x; + trn[M13] = y; + trn[M14] = z; + + upload_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + multiply_matrix(&trn); + download_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); if(MATRIX_MODE == GL_MODELVIEW) { recalculateNormalMatrix(); @@ -136,9 +171,20 @@ void APIENTRY glTranslatef(GLfloat x, GLfloat y, GLfloat z) { void APIENTRY glScalef(GLfloat x, GLfloat y, GLfloat z) { - mat_load(stack_top(MATRIX_STACKS + MATRIX_IDX)); - mat_scale(x, y, z); - mat_store(stack_top(MATRIX_STACKS + MATRIX_IDX)); + static Matrix4x4 scale __attribute__((aligned(32))) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + scale[M0] = x; + scale[M5] = y; + scale[M10] = z; + + upload_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + multiply_matrix(&scale); + download_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); if(MATRIX_MODE == GL_MODELVIEW) { recalculateNormalMatrix(); @@ -146,13 +192,42 @@ void APIENTRY glScalef(GLfloat x, GLfloat y, GLfloat z) { } void APIENTRY glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) { + static Matrix4x4 rotate __attribute__((aligned(32))) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + float r = DEG2RAD * -angle; + float c = cos(r); + float s = sin(r); + float invc = 1.0f - c; + float xs = x * s; + float zs = z * s; + float ys = y * s; + float xz = x * z; + float xy = y * x; + float yz = y * z; + vec3f_normalize(x, y, z); - mat_load(stack_top(MATRIX_STACKS + MATRIX_IDX)); - mat_rotate(r * x, r * y, r * z); - mat_store(stack_top(MATRIX_STACKS + MATRIX_IDX)); + rotate[M0] = (x * x) * invc + c; + rotate[M1] = xy * invc + zs; + rotate[M2] = xz * invc - ys; + + rotate[M4] = xy * invc - zs; + rotate[M5] = (y * y) * invc + c; + rotate[M6] = yz * invc + xs; + + rotate[M8] = xz * invc + ys; + rotate[M9] = yz * invc - xs; + rotate[M10] = (z * z) * invc + c; + + upload_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + multiply_matrix(&rotate); + download_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); if(MATRIX_MODE == GL_MODELVIEW) { recalculateNormalMatrix(); @@ -161,7 +236,29 @@ void APIENTRY glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) { /* Load an arbitrary matrix */ void APIENTRY glLoadMatrixf(const GLfloat *m) { - stack_replace(MATRIX_STACKS + MATRIX_IDX, m); + static Matrix4x4 TEMP; + + TEMP[M0] = m[0]; + TEMP[M1] = m[1]; + TEMP[M2] = m[2]; + TEMP[M3] = m[3]; + + TEMP[M4] = m[4]; + TEMP[M5] = m[5]; + TEMP[M6] = m[6]; + TEMP[M7] = m[7]; + + TEMP[M8] = m[8]; + TEMP[M9] = m[9]; + TEMP[M10] = m[10]; + TEMP[M11] = m[11]; + + TEMP[M12] = m[12]; + TEMP[M13] = m[13]; + TEMP[M14] = m[14]; + TEMP[M15] = m[15]; + + stack_replace(MATRIX_STACKS + MATRIX_IDX, TEMP); if(MATRIX_MODE == GL_MODELVIEW) { recalculateNormalMatrix(); @@ -174,23 +271,23 @@ void APIENTRY glOrtho(GLfloat left, GLfloat right, GLfloat znear, GLfloat zfar) { /* Ortho Matrix */ - static matrix_t OrthoMatrix __attribute__((aligned(32))) = { - { 1.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 1.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 1.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 1.0f } + static Matrix4x4 OrthoMatrix __attribute__((aligned(32))) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; - OrthoMatrix[0][0] = 2.0f / (right - left); - OrthoMatrix[1][1] = 2.0f / (top - bottom); - OrthoMatrix[2][2] = -2.0f / (zfar - znear); - OrthoMatrix[3][0] = -(right + left) / (right - left);; - OrthoMatrix[3][1] = -(top + bottom) / (top - bottom); - OrthoMatrix[3][2] = -(zfar + znear) / (zfar - znear); + OrthoMatrix[M0] = 2.0f / (right - left); + OrthoMatrix[M5] = 2.0f / (top - bottom); + OrthoMatrix[M10] = -2.0f / (zfar - znear); + OrthoMatrix[M12] = -(right + left) / (right - left); + OrthoMatrix[M13] = -(top + bottom) / (top - bottom); + OrthoMatrix[M14] = -(zfar + znear) / (zfar - znear); - mat_load(stack_top(MATRIX_STACKS + MATRIX_IDX)); - mat_apply(&OrthoMatrix); - mat_store(stack_top(MATRIX_STACKS + MATRIX_IDX)); + upload_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + multiply_matrix(&OrthoMatrix); + download_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); } @@ -200,84 +297,145 @@ void APIENTRY glFrustum(GLfloat left, GLfloat right, GLfloat znear, GLfloat zfar) { /* Frustum Matrix */ - static matrix_t FrustumMatrix __attribute__((aligned(32))) = { - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, -1.0f }, - { 0.0f, 0.0f, 0.0f, 0.0f } - }; + static Matrix4x4 FrustumMatrix __attribute__((aligned(32))); - FrustumMatrix[0][0] = (2.0f * znear) / (right - left); - FrustumMatrix[2][0] = (right + left) / (right - left); - FrustumMatrix[1][1] = (2.0f * znear) / (top - bottom); - FrustumMatrix[2][1] = (top + bottom) / (top - bottom); - FrustumMatrix[2][2] = zfar / (zfar - znear); - FrustumMatrix[3][2] = -(zfar * znear) / (zfar - znear); + memset(FrustumMatrix, 0, sizeof(float) * 16); - mat_load(stack_top(MATRIX_STACKS + MATRIX_IDX)); - mat_apply(&FrustumMatrix); - mat_store(stack_top(MATRIX_STACKS + MATRIX_IDX)); + const float near2 = 2.0f * znear; + const float A = (right + left) / (right - left); + const float B = (top + bottom) / (top - bottom); + const float C = -((zfar + znear) / (zfar - znear)); + const float D = -((2.0f * zfar * znear) / (zfar - znear)); + + FrustumMatrix[M0] = near2 / (right - left); + FrustumMatrix[M5] = near2 / (top - bottom); + + FrustumMatrix[M8] = A; + FrustumMatrix[M9] = B; + FrustumMatrix[M10] = C; + FrustumMatrix[M11] = -1.0f; + FrustumMatrix[M14] = D; + + upload_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + multiply_matrix(&FrustumMatrix); + download_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + + if(MATRIX_MODE == GL_PROJECTION) { + _glStoreNearPlane(); + } } /* Multiply the current matrix by an arbitrary matrix */ void glMultMatrixf(const GLfloat *m) { - static matrix_t TEMP __attribute__((aligned(32))) = { - { 1.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 1.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 1.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 1.0f } - }; + static Matrix4x4 TEMP; - memcpy(TEMP, m, sizeof(matrix_t)); + TEMP[M0] = m[0]; + TEMP[M1] = m[1]; + TEMP[M2] = m[2]; + TEMP[M3] = m[3]; - mat_load(stack_top(MATRIX_STACKS + MATRIX_IDX)); - mat_apply(&TEMP); - mat_store(stack_top(MATRIX_STACKS + MATRIX_IDX)); + TEMP[M4] = m[4]; + TEMP[M5] = m[5]; + TEMP[M6] = m[6]; + TEMP[M7] = m[7]; + + TEMP[M8] = m[8]; + TEMP[M9] = m[9]; + TEMP[M10] = m[10]; + TEMP[M11] = m[11]; + + TEMP[M12] = m[12]; + TEMP[M13] = m[13]; + TEMP[M14] = m[14]; + TEMP[M15] = m[15]; + + upload_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + multiply_matrix((Matrix4x4*) &TEMP); + download_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); if(MATRIX_MODE == GL_MODELVIEW) { recalculateNormalMatrix(); } + + if(MATRIX_MODE == GL_PROJECTION) { + _glStoreNearPlane(); + } } /* Load an arbitrary transposed matrix */ void glLoadTransposeMatrixf(const GLfloat *m) { - stack_replace(MATRIX_STACKS + MATRIX_IDX, m); - transpose(stack_top(MATRIX_STACKS + MATRIX_IDX)); + /* We store matrices transpose anyway, so m will be + * transpose compared to all other matrices */ + + static Matrix4x4 TEMP __attribute__((aligned(32))); + + TEMP[M0] = m[0]; + TEMP[M1] = m[4]; + TEMP[M2] = m[8]; + TEMP[M3] = m[12]; + + TEMP[M4] = m[1]; + TEMP[M5] = m[5]; + TEMP[M6] = m[9]; + TEMP[M7] = m[13]; + + TEMP[M8] = m[3]; + TEMP[M9] = m[6]; + TEMP[M10] = m[10]; + TEMP[M11] = m[14]; + + TEMP[M12] = m[4]; + TEMP[M13] = m[7]; + TEMP[M14] = m[11]; + TEMP[M15] = m[15]; + + stack_replace(MATRIX_STACKS + MATRIX_IDX, TEMP); if(MATRIX_MODE == GL_MODELVIEW) { recalculateNormalMatrix(); } + + if(MATRIX_MODE == GL_PROJECTION) { + _glStoreNearPlane(); + } } /* Multiply the current matrix by an arbitrary transposed matrix */ void glMultTransposeMatrixf(const GLfloat *m) { - static matrix_t ml; + static Matrix4x4 TEMP __attribute__((aligned(32))); - ml[0][0] = m[0]; - ml[0][1] = m[4]; - ml[0][2] = m[8]; - ml[0][3] = m[12]; - ml[1][0] = m[1]; - ml[1][1] = m[5]; - ml[1][2] = m[9]; - ml[1][3] = m[13]; - ml[2][0] = m[2]; - ml[2][1] = m[6]; - ml[2][2] = m[10]; - ml[2][3] = m[14]; - ml[3][0] = m[3]; - ml[3][1] = m[7]; - ml[3][2] = m[11]; - ml[3][3] = m[15]; + TEMP[M0] = m[0]; + TEMP[M1] = m[4]; + TEMP[M2] = m[8]; + TEMP[M3] = m[12]; - mat_load(stack_top(MATRIX_STACKS + MATRIX_IDX)); - mat_apply(&ml); - mat_store(stack_top(MATRIX_STACKS + MATRIX_IDX)); + TEMP[M4] = m[1]; + TEMP[M5] = m[5]; + TEMP[M6] = m[9]; + TEMP[M7] = m[13]; + + TEMP[M8] = m[3]; + TEMP[M9] = m[6]; + TEMP[M10] = m[10]; + TEMP[M11] = m[14]; + + TEMP[M12] = m[4]; + TEMP[M13] = m[7]; + TEMP[M14] = m[11]; + TEMP[M15] = m[15]; + + upload_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); + multiply_matrix(&TEMP); + download_matrix(stack_top(MATRIX_STACKS + MATRIX_IDX)); if(MATRIX_MODE == GL_MODELVIEW) { recalculateNormalMatrix(); } + + if(MATRIX_MODE == GL_PROJECTION) { + _glStoreNearPlane(); + } } /* Set the GL viewport */ @@ -295,22 +453,19 @@ void APIENTRY glViewport(GLint x, GLint y, GLsizei width, GLsizei height) { GLfloat hw = ((GLfloat) width) / 2.0f; GLfloat hh = ((GLfloat) height) / 2.0f; - SCREENVIEW_MATRIX[0][0] = hw; - SCREENVIEW_MATRIX[1][1] = -hh; - SCREENVIEW_MATRIX[2][2] = 1; //(gl_depthrange_far - gl_depthrange_near) / 2.0f; - SCREENVIEW_MATRIX[3][0] = (rw + lw) / 2.0f; - SCREENVIEW_MATRIX[3][1] = (tw + bw) / 2.0f; - // SCREENVIEW_MATRIX[3][2] = (gl_depthrange_far + gl_depthrange_near) / 2.0f; + SCREENVIEW_MATRIX[M0] = hw; + SCREENVIEW_MATRIX[M5] = -hh; + SCREENVIEW_MATRIX[M10] = 1; + SCREENVIEW_MATRIX[M12] = (rw + lw) / 2.0f; + SCREENVIEW_MATRIX[M13] = (tw + bw) / 2.0f; +} + +GLfloat _glGetNearPlane() { + return NEAR_PLANE_DISTANCE; } /* Set the depth range */ void APIENTRY glDepthRange(GLclampf n, GLclampf f) { - /* FIXME: This currently does nothing because the SCREENVIEW_MATRIX is multiplied prior to perpective division - * and not after as traditional GL. See here for more info: http://www.thecodecrate.com/opengl-es/opengl-viewport-matrix/ - * - * We probably need to make tweaks to the SCREENVIEW matrix or clipping or whatever to make this work - */ - if(n < 0.0f) n = 0.0f; else if(n > 1.0f) n = 1.0f; @@ -334,11 +489,11 @@ void glhLookAtf2(const GLfloat* eyePosition3D, const GLfloat* upVector3D) { /* Look-At Matrix */ - static matrix_t MatrixLookAt __attribute__((aligned(32))) = { - { 1.0f, 0.0f, 0.0f, 0.0f }, - { 0.0f, 1.0f, 0.0f, 0.0f }, - { 0.0f, 0.0f, 1.0f, 0.0f }, - { 0.0f, 0.0f, 0.0f, 1.0f } + static Matrix4x4 MatrixLookAt __attribute__((aligned(32))) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; GLfloat forward[3]; @@ -356,32 +511,40 @@ void glhLookAtf2(const GLfloat* eyePosition3D, //Recompute up as: up = side x forward vec3f_cross(side, forward, up); - MatrixLookAt[0][0] = side[0]; - MatrixLookAt[1][0] = side[1]; - MatrixLookAt[2][0] = side[2]; - MatrixLookAt[3][0] = 0; + MatrixLookAt[M0] = side[0]; + MatrixLookAt[M4] = side[1]; + MatrixLookAt[M8] = side[2]; + MatrixLookAt[M12] = 0; - MatrixLookAt[0][1] = up[0]; - MatrixLookAt[1][1] = up[1]; - MatrixLookAt[2][1] = up[2]; - MatrixLookAt[3][1] = 0; + MatrixLookAt[M1] = up[0]; + MatrixLookAt[M5] = up[1]; + MatrixLookAt[M9] = up[2]; + MatrixLookAt[M13] = 0; - MatrixLookAt[0][2] = -forward[0]; - MatrixLookAt[1][2] = -forward[1]; - MatrixLookAt[2][2] = -forward[2]; - MatrixLookAt[3][2] = 0; + MatrixLookAt[M2] = -forward[0]; + MatrixLookAt[M6] = -forward[1]; + MatrixLookAt[M10] = -forward[2]; + MatrixLookAt[M14] = 0; - MatrixLookAt[0][3] = - MatrixLookAt[1][3] = - MatrixLookAt[2][3] = 0; - MatrixLookAt[3][3] = 1; + MatrixLookAt[M3] = MatrixLookAt[11] = MatrixLookAt[15] = 0; + MatrixLookAt[M15] = 1; + + static Matrix4x4 trn __attribute__((aligned(32))) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + trn[M12] = -eyePosition3D[0]; + trn[M13] = -eyePosition3D[1]; + trn[M14] = -eyePosition3D[2]; // Does not modify internal Modelview matrix - mat_load(&MatrixLookAt); - mat_translate(-eyePosition3D[0], -eyePosition3D[1], -eyePosition3D[2]); - - mat_apply(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); - mat_store(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); + upload_matrix(&MatrixLookAt); + multiply_matrix(&trn); + multiply_matrix(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); + download_matrix(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); } void gluLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez, GLfloat centerx, @@ -394,19 +557,19 @@ void gluLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez, GLfloat centerx, } void _glApplyRenderMatrix() { - mat_load(&SCREENVIEW_MATRIX); - mat_apply(stack_top(MATRIX_STACKS + (GL_PROJECTION & 0xF))); - mat_apply(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); + upload_matrix(&SCREENVIEW_MATRIX); + multiply_matrix(stack_top(MATRIX_STACKS + (GL_PROJECTION & 0xF))); + multiply_matrix(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); } void _glMatrixLoadTexture() { - mat_load(stack_top(MATRIX_STACKS + (GL_TEXTURE & 0xF))); + upload_matrix(stack_top(MATRIX_STACKS + (GL_TEXTURE & 0xF))); } void _glMatrixLoadModelView() { - mat_load(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); + upload_matrix(stack_top(MATRIX_STACKS + (GL_MODELVIEW & 0xF))); } void _glMatrixLoadNormal() { - mat_load(&NORMAL_MATRIX); + upload_matrix(&NORMAL_MATRIX); } diff --git a/GL/private.h b/GL/private.h index 36e898a..e1ad867 100644 --- a/GL/private.h +++ b/GL/private.h @@ -23,6 +23,49 @@ #define MAX_TEXTURE_SIZE 1024 +typedef float Matrix4x4[16]; + +/* This gives us an easy way to switch + * internal matrix order if necessary */ + +#define TRANSPOSE 0 + +#if TRANSPOSE +#define M0 0 +#define M1 4 +#define M2 8 +#define M3 12 +#define M4 1 +#define M5 5 +#define M6 9 +#define M7 13 +#define M8 2 +#define M9 6 +#define M10 10 +#define M11 14 +#define M12 3 +#define M13 7 +#define M14 11 +#define M15 15 +#else +#define M0 0 +#define M1 1 +#define M2 2 +#define M3 3 +#define M4 4 +#define M5 5 +#define M6 6 +#define M7 7 +#define M8 8 +#define M9 9 +#define M10 10 +#define M11 11 +#define M12 12 +#define M13 13 +#define M14 14 +#define M15 15 +#endif + typedef struct { pvr_poly_hdr_t hdr; } PVRHeader; @@ -198,8 +241,8 @@ void _glApplyRenderMatrix(); extern GLfloat DEPTH_RANGE_MULTIPLIER_L; extern GLfloat DEPTH_RANGE_MULTIPLIER_H; -matrix_t* _glGetProjectionMatrix(); -matrix_t* _glGetModelViewMatrix(); +Matrix4x4* _glGetProjectionMatrix(); +Matrix4x4* _glGetModelViewMatrix(); void _glWipeTextureOnFramebuffers(GLuint texture); GLubyte _glCheckImmediateModeInactive(const char* func); @@ -210,6 +253,10 @@ GLubyte _glInitTextures(); void _glUpdatePVRTextureContext(pvr_poly_cxt_t* context, GLshort textureUnit); void _glAllocateSpaceForMipmaps(TextureObject* active); +extern GLfloat NEAR_PLANE_DISTANCE; + +GLfloat _glGetNearPlane(); + typedef struct { const void* ptr; GLenum type; From cb675434c76424755e5910697ac5d68e209f2f96 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Mon, 16 Sep 2019 20:48:06 -0500 Subject: [PATCH 19/64] Fixed GL_ALPHA support (convert to argb format) --- GL/texture.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/GL/texture.c b/GL/texture.c index 91352fe..8b69061 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -143,9 +143,9 @@ static GLint _determineStride(GLenum format, GLenum type) { switch(type) { case GL_BYTE: case GL_UNSIGNED_BYTE: - return (format == GL_RED) ? 1 : (format == GL_RGB) ? 3 : 4; + return (format == GL_RED || format == GL_ALPHA) ? 1 : (format == GL_RGB) ? 3 : 4; case GL_UNSIGNED_SHORT: - return (format == GL_RED) ? 2 : (format == GL_RGB) ? 6 : 8; + return (format == GL_RED || format == GL_ALPHA) ? 2 : (format == GL_RGB) ? 6 : 8; case GL_UNSIGNED_SHORT_5_6_5: case GL_UNSIGNED_SHORT_5_6_5_REV: case GL_UNSIGNED_SHORT_5_6_5_TWID_KOS: @@ -680,10 +680,17 @@ static inline void _i8_to_i8(const GLubyte* source, GLubyte* dest) { *dst = *source; } +static inline void _alpha8_to_argb4444(const GLubyte* source, GLubyte* dest) { + *((GLushort*) dest) = (*source & 0xF0) << 8 | (0xFF & 0xF0) << 4 | (0xFF & 0xF0) | (0xFF & 0xF0) >> 4; +} + static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum format, GLenum type) { switch(internalFormat) { case GL_ALPHA: { - if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { + if(format == GL_ALPHA) { + /* Dreamcast doesn't really support GL_RED internally, so store as argb with each rgb value as white */ + return _alpha8_to_argb4444; + } else if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { return _rgba8888_to_a000; } else if(type == GL_BYTE && format == GL_RGBA) { return _rgba8888_to_a000; @@ -748,6 +755,7 @@ static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum f static GLboolean _isSupportedFormat(GLenum format) { switch(format) { + case GL_ALPHA: case GL_RED: case GL_RGB: case GL_RGBA: From b192bfc4514bb14f06e20820c36f1e5fb8734b5e Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Tue, 17 Sep 2019 08:28:43 -0500 Subject: [PATCH 20/64] Fixed comment --- GL/texture.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GL/texture.c b/GL/texture.c index 8b69061..5eb7178 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -688,7 +688,7 @@ static TextureConversionFunc _determineConversion(GLint internalFormat, GLenum f switch(internalFormat) { case GL_ALPHA: { if(format == GL_ALPHA) { - /* Dreamcast doesn't really support GL_RED internally, so store as argb with each rgb value as white */ + /* Dreamcast doesn't really support GL_ALPHA internally, so store as argb with each rgb value as white */ return _alpha8_to_argb4444; } else if(type == GL_UNSIGNED_BYTE && format == GL_RGBA) { return _rgba8888_to_a000; From 8f84cbb14248132ffe64865a399b48228c280828 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sun, 22 Sep 2019 20:52:58 +0100 Subject: [PATCH 21/64] Various cleanups from @mrneo --- GL/draw.c | 4 ++-- GL/framebuffer.c | 2 +- GL/texture.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index 81d50f4..d3b00fe 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -1246,7 +1246,7 @@ void APIENTRY glEnableClientState(GLenum cap) { (ENABLED_VERTEX_ATTRIBUTES |= UV_ENABLED_FLAG); break; default: - _glKosThrowError(GL_INVALID_ENUM, "glEnableClientState"); + _glKosThrowError(GL_INVALID_ENUM, __func__); } } @@ -1269,7 +1269,7 @@ void APIENTRY glDisableClientState(GLenum cap) { (ENABLED_VERTEX_ATTRIBUTES &= ~UV_ENABLED_FLAG); break; default: - _glKosThrowError(GL_INVALID_ENUM, "glDisableClientState"); + _glKosThrowError(GL_INVALID_ENUM, __func__); } } diff --git a/GL/framebuffer.c b/GL/framebuffer.c index 9af7575..663a9c7 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -189,7 +189,7 @@ GLboolean _glCalculateAverageTexel(const GLubyte* src, const GLuint srcWidth, co *d1 = (r << 11 | g << 5 | b); } else { fprintf(stderr, "ERROR: Unsupported PVR format for mipmap generation"); - _glKosThrowError(GL_INVALID_OPERATION, "glGenerateMipmapEXT"); + _glKosThrowError(GL_INVALID_OPERATION, __func__); _glKosPrintError(); return GL_FALSE; } diff --git a/GL/texture.c b/GL/texture.c index 5eb7178..f8fe327 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -37,7 +37,7 @@ static TexturePalette* _initTexturePalette() { TexturePalette* palette = (TexturePalette*) malloc(sizeof(TexturePalette)); assert(palette); - memset(palette, 0x0, sizeof(TexturePalette)); + sq_clr(palette, (sizeof(TexturePalette) & 0xfffffffc) + 4); palette->bank = -1; return palette; } From 888a48562f19abd166716dfcf73e56c0b3d796b4 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sun, 22 Sep 2019 21:48:21 +0100 Subject: [PATCH 22/64] Generate version.h and print version on boot. Fixes #46 --- .gitignore | 1 + GL/flush.c | 3 +++ Makefile | 7 +++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 46e6163..c1b78b3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ *.img dc-build.sh .buildconfig +GL/version.h diff --git a/GL/flush.c b/GL/flush.c index 6c68fe7..408c680 100644 --- a/GL/flush.c +++ b/GL/flush.c @@ -6,6 +6,7 @@ #include "../containers/aligned_vector.h" #include "private.h" #include "profiler.h" +#include "version.h" #define TA_SQ_ADDR (unsigned int *)(void *) \ (0xe0000000 | (((unsigned long)0x10000000) & 0x03ffffe0)) @@ -87,6 +88,8 @@ void APIENTRY glKosInitConfig(GLdcConfig* config) { void APIENTRY glKosInitEx(GLdcConfig* config) { TRACE(); + printf("\nWelcome to GLdc! Git revision: %s\n\n", GLDC_VERSION); + _glInitPVR(config->autosort_enabled); _glInitMatrices(); diff --git a/Makefile b/Makefile index 76a1114..f761d3a 100644 --- a/Makefile +++ b/Makefile @@ -6,17 +6,20 @@ # Copyright (C) 2018 Luke Benstead TARGET = libGLdc.a -OBJS = GL/draw.o GL/flush.o GL/framebuffer.o GL/immediate.o GL/lighting.o GL/state.o GL/texture.o GL/glu.o +OBJS = GL/draw.o GL/flush.o GL/framebuffer.o GL/immediate.o GL/lighting.o GL/state.o GL/texture.o GL/glu.o GL/version.h OBJS += GL/matrix.o GL/fog.o GL/error.o GL/clip.o containers/stack.o containers/named_array.o containers/aligned_vector.o GL/profiler.o SUBDIRS = KOS_CFLAGS += -ffast-math -Ofast -Iinclude +GL/version.h: + @echo -e '#pragma once\n#define GLDC_VERSION "$(shell git describe --abbrev=4 --dirty --always --tags)"\n' > $@ + link: $(KOS_AR) rcs $(TARGET) $(OBJS) -build: $(OBJS) link +build: GL/version.h $(OBJS) link samples: build From aafb99dedeae920f0e20744cd17af0350e709a0b Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 23 Sep 2019 21:31:11 +0100 Subject: [PATCH 23/64] Add a mipmap sample --- samples/Makefile | 1 + samples/mipmap/Makefile | 29 ++++ samples/mipmap/main.c | 216 +++++++++++++++++++++++++++++ samples/mipmap/romdisk/NeHe.bmp | Bin 0 -> 196662 bytes samples/mipmap/romdisk/PLACEHOLDER | 0 5 files changed, 246 insertions(+) create mode 100644 samples/mipmap/Makefile create mode 100644 samples/mipmap/main.c create mode 100644 samples/mipmap/romdisk/NeHe.bmp create mode 100644 samples/mipmap/romdisk/PLACEHOLDER diff --git a/samples/Makefile b/samples/Makefile index d8df580..7fc4b0f 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -28,3 +28,4 @@ all: $(KOS_MAKE) -C polymark all $(KOS_MAKE) -C polygon_offset all $(KOS_MAKE) -C blend_test all + $(KOS_MAKE) -C mipmap all diff --git a/samples/mipmap/Makefile b/samples/mipmap/Makefile new file mode 100644 index 0000000..6b36730 --- /dev/null +++ b/samples/mipmap/Makefile @@ -0,0 +1,29 @@ +TARGET = mipmap.elf +OBJS = main.o + +all: rm-elf $(TARGET) + +include $(KOS_BASE)/Makefile.rules + +clean: + -rm -f $(TARGET) $(OBJS) romdisk.* + +rm-elf: + -rm -f $(TARGET) romdisk.* + +$(TARGET): $(OBJS) romdisk.o + $(KOS_CC) $(KOS_CFLAGS) $(KOS_LDFLAGS) -o $(TARGET) $(KOS_START) \ + $(OBJS) romdisk.o $(OBJEXTRA) -lm -lkosutils $(KOS_LIBS) + +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) diff --git a/samples/mipmap/main.c b/samples/mipmap/main.c new file mode 100644 index 0000000..1eb1909 --- /dev/null +++ b/samples/mipmap/main.c @@ -0,0 +1,216 @@ +#include +#include + +#include "gl.h" +#include "glu.h" +#include "glkos.h" + +extern uint8 romdisk[]; +KOS_INIT_ROMDISK(romdisk); + +/* storage for one texture */ +int texture[1]; + +/* Image type - contains height, width, and data */ +struct Image { + unsigned long sizeX; + unsigned long sizeY; + char *data; +}; +typedef struct Image Image; + +// quick and dirty bitmap loader...for 24 bit bitmaps with 1 plane only. +// See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/BMP.txt for more info. +int ImageLoad(char *filename, Image *image) { + FILE *file; + unsigned long size; // size of the image in bytes. + unsigned long i; // standard counter. + unsigned short int planes; // number of planes in image (must be 1) + unsigned short int bpp; // number of bits per pixel (must be 24) + char temp; // temporary color storage for bgr-rgb conversion. + + // make sure the file is there. + if ((file = fopen(filename, "rb"))==NULL) + { + printf("File Not Found : %s\n",filename); + return 0; + } + + // seek through the bmp header, up to the width/height: + fseek(file, 18, SEEK_CUR); + + // read the width + if ((i = fread(&image->sizeX, 4, 1, file)) != 1) { + printf("Error reading width from %s.\n", filename); + return 0; + } + printf("Width of %s: %lu\n", filename, image->sizeX); + + // read the height + if ((i = fread(&image->sizeY, 4, 1, file)) != 1) { + printf("Error reading height from %s.\n", filename); + return 0; + } + printf("Height of %s: %lu\n", filename, image->sizeY); + + // calculate the size (assuming 24 bits or 3 bytes per pixel). + size = image->sizeX * image->sizeY * 3; + + // read the planes + if ((fread(&planes, 2, 1, file)) != 1) { + printf("Error reading planes from %s.\n", filename); + return 0; + } + if (planes != 1) { + printf("Planes from %s is not 1: %u\n", filename, planes); + return 0; + } + + // read the bpp + if ((i = fread(&bpp, 2, 1, file)) != 1) { + printf("Error reading bpp from %s.\n", filename); + return 0; + } + if (bpp != 24) { + printf("Bpp from %s is not 24: %u\n", filename, bpp); + return 0; + } + + // seek past the rest of the bitmap header. + fseek(file, 24, SEEK_CUR); + + // read the data. + image->data = (char *) malloc(size); + if (image->data == NULL) { + printf("Error allocating memory for color-corrected image data"); + return 0; + } + + if ((i = fread(image->data, size, 1, file)) != 1) { + printf(stderr, "Error reading image data from %s.\n", filename); + return 0; + } + + for (i=0;i rgb) + temp = image->data[i]; + image->data[i] = image->data[i+2]; + image->data[i+2] = temp; + } + + // we're done. + return 1; +} + +// Load Bitmaps And Convert To Textures +void LoadGLTextures() { + // Load Texture + Image *image1; + + // allocate space for texture + image1 = (Image *) malloc(sizeof(Image)); + if (image1 == NULL) { + printf("Error allocating space for image"); + exit(0); + } + + if (!ImageLoad("/rd/NeHe.bmp", image1)) { + exit(1); + } + + // Create Texture + glGenTextures(1, &texture[0]); + glBindTexture(GL_TEXTURE_2D, texture[0]); // 2d texture (x and y size) + + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // scale linearly when image bigger than texture + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); // scale linearly when image smalled than texture + + // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, + // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself. + glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data); + + glGenerateMipmapEXT(GL_TEXTURE_2D); +}; + +/* A general OpenGL initialization function. Sets all of the initial parameters. */ +void InitGL(int Width, int Height) // We call this right after our OpenGL window is created. +{ + LoadGLTextures(); + glEnable(GL_TEXTURE_2D); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // This Will Clear The Background Color To Black + glClearDepth(1.0); // Enables Clearing Of The Depth Buffer + glDepthFunc(GL_LESS); // The Type Of Depth Test To Do + glEnable(GL_DEPTH_TEST); // Enables Depth Testing + glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); // Reset The Projection Matrix + + gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); // Calculate The Aspect Ratio Of The Window + + glMatrixMode(GL_MODELVIEW); +} + +/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */ +void ReSizeGLScene(int Width, int Height) +{ + if (Height == 0) // Prevent A Divide By Zero If The Window Is Too Small + Height = 1; + + glViewport(0, 0, Width, Height); // Reset The Current Viewport And Perspective Transformation + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); + glMatrixMode(GL_MODELVIEW); +} + +void DrawQuad() { + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); // Bottom Left Of The Texture and Quad + glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f); // Bottom Right Of The Texture and Quad + glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f); // Top Right Of The Texture and Quad + glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f); // Top Left Of The Texture and Quad + glEnd(); // done with the polygon. +} + +/* The main drawing function. */ +void DrawGLScene() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + glClearColor(0.5, 0.5, 0.5, 1.0); + + glBindTexture(GL_TEXTURE_2D, texture[0]); + + glTranslatef(-1.5f, 0.0f, -4.5f); + DrawQuad(); + + glTranslatef(1.0f, 0.0f, -5.0f); + DrawQuad(); + + glTranslatef(1.5f, 0.0f, -5.0f); + DrawQuad(); + + glTranslatef(2.0f, 0.0f, -5.0f); + DrawQuad(); + + glTranslatef(3.5f, 0.0f, -5.0f); + DrawQuad(); + + glKosSwapBuffers(); +} + +int main(int argc, char **argv) +{ + glKosInit(); + + InitGL(640, 480); + ReSizeGLScene(640, 480); + + while(1) { + DrawGLScene(); + } + + return 0; +} diff --git a/samples/mipmap/romdisk/NeHe.bmp b/samples/mipmap/romdisk/NeHe.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6b3db10f2b8d60997f7e213255809961985c2ef3 GIT binary patch literal 196662 zcmeFa_j4RccJJHXRabXcwTbrLn`rNWh7EWSpaDP=^xn=4gF|vSepb6HZAiPix=*_D zSN8|KYva8?@x4!>W-t^9atLCFOANaL>}Xb3Wu5%yH&32CdGgfkdPk$zC`X5XdF6{% z{ioIVmH+K~{D0$PjKGJDz`p%rd~%GyFCGE4-~ZyPIj-OsfqfAe^M7Ah#wR}z1jhXT zKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah z#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n z&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV z^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXT zKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zF#5dXD59QzhEJ~>9<$RThb{_B4@bUNKT ztEt0oOGW*IMsBuOobQ*0jch&{k{ouu{@uxNn99Dl^1p>(Kj=T@twQhS(Cc)P(_TnN zMxEl~L~X8L?o>0ekk@84|4{@u5dTdkli6$*MbY7Kr2Mvi(r~6LTwhN;xw`zD&#wK$ zZ=d}8-+ui+|LHgX{qKMEufPBLuYdLN*H11#`{>x?tFsT!&s^M`>^Jg0kMxFO>vh^# z(6c(-e|-JIcc0$;`oY%Y%M%Yzl|S9CetvWAtB<#?T|CxnmRwHpg#v>PO(&)~)B91d zfBfy!|NdXT`#=BkyZ`l1zx}sAeEa47%Zn3TpX}hqgwL|pi#)wH_2-|R`@6q<`0s!B z?f?FlzxW^j@Mr)0mtTB*Woy`|*llKuQCp6Q$7eH7t}XxevupqK7tgqW%l_}b`}QAx z^W^f$m2x)yf(-ok?c|4G=$MGCA7;!~W_%AXbie!L%x^!t_wD_wC*~*8QQylCN(F?q z>D*WMPX5hrKK=K<{d21M=+1>+tNikn>Y29_Oug6e!+`=6&6;M)ac;Fl)BMe^AN~8^ z{`UX*x8MKg-#*>hT#N+2m(yX@b+YosjoLRK@BGbgxaQCQ-~aZ9|Ms`PzH#AXF`Imq zgz9;tL7#~Sww9;AynFRGPw#yD(az^LXTP{K`25D~Hy@vQbm#oW!fYZMH0aege!af1 zSN?h+{@ZLem&+9h1TvXSrI0Qr{k5oNIv2V;SATYK_7Bgl(*^$Z_fP)Qw|76fyuLbF zYNUd-s4z?!PIg@Pwrk%$JooJ3jbn@Rkx<~30`30bw42+FgTO4~U7Nvl=x|<-%v?8jmi`|HT(K zA??5Y^|QbF^7g%p>$Ckb{7;76+%?QtRy(2V3svO*>(6ie)9*h2x4-`EyGNI=oSGYU zD)WPuBw8I7?I>@bYsRl1Ykq!v@`r+;K53X&!AkrZPQ*Q@etJNw90$;p>Zl>he=$1b|IrY0o7ykKoU;K~1|FhqJ z^YO#m=X%ZWdtXe}&sU}Ge(B-X;CG*1{O8|&{y+cm*Z<{jzNUw6Y%J_6p?XqwScaYK z?F(zDzK^b*?bLJ8M7mZ;=F&;Q?irL553kJr{#SQDxp{iB*RTt=*A(^opAN)-G=DG{ zg#U>|!k4Ito@P!6RC98(Gb(qM6UA;Q5DY}^^=AF-skLVxU;F6h=845jKX0pL0#Yzu z4HX|=SpEH1pItk*Q!bQV)pPA;U9T88yVk#Sa=zbg%7S}iX6?r5`JLHFKj^7Sa=Yv= z)$2}Q^X%Hxx1Zj-d}bq;PN>zCB&lBrq}Jk z$J5zHASx!aAufA(<2?HR1^ypKel$!x9&e>nF;_M3bT!M7;q3C(_Qj7bokRbt{D=H1 z3Hh{J^ryS_Yp1%6h3mD}d^i|(`#g-l|Md^wyxi`G5lk%ͰF-6LSL>~!sG^Y!B7 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 literal 0 HcmV?d00001 diff --git a/samples/mipmap/romdisk/PLACEHOLDER b/samples/mipmap/romdisk/PLACEHOLDER new file mode 100644 index 0000000..e69de29 From 150c95bd331f8fabff81d9a1fc77a67743a7b368 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Tue, 24 Sep 2019 15:47:23 +0100 Subject: [PATCH 24/64] Huge refactor of mipmap handling. All textures are now stored twiddled on the PVR --- GL/private.h | 8 ++ GL/state.c | 14 ++- GL/texture.c | 256 +++++++++++++++++++++++++++--------------- samples/mipmap/main.c | 19 ++++ 4 files changed, 205 insertions(+), 92 deletions(-) diff --git a/GL/private.h b/GL/private.h index e1ad867..5ce6a3e 100644 --- a/GL/private.h +++ b/GL/private.h @@ -105,6 +105,7 @@ typedef struct { GLuint index; GLvoid *data; GLuint dataStride; + GLuint baseDataSize; /* The data size of mipmap level 0 */ GLenum minFilter; GLenum magFilter; @@ -112,6 +113,13 @@ typedef struct { GLboolean isCompressed; GLboolean isPaletted; + /* Mipmap textures have a different + * offset for the base level when supplying the data, this + * keeps track of that. baseDataOffset == 0 + * means that the texture has no mipmaps + */ + GLuint baseDataOffset; + TexturePalette* palette; /* When using the shared palette, this is the bank (0-3) */ diff --git a/GL/state.c b/GL/state.c index f927c54..ef026a5 100644 --- a/GL/state.c +++ b/GL/state.c @@ -208,11 +208,19 @@ void _glUpdatePVRTextureContext(pvr_poly_cxt_t* context, GLshort textureUnit) { if(tx1->data) { context->txr.enable = PVR_TEXTURE_ENABLE; context->txr.filter = filter; - context->txr.mipmap = (enableMipmaps) ? PVR_MIPMAP_ENABLE : PVR_MIPMAP_DISABLE; - context->txr.mipmap_bias = PVR_MIPBIAS_NORMAL; context->txr.width = tx1->width; context->txr.height = tx1->height; - context->txr.base = tx1->data; + + if(enableMipmaps) { + context->txr.base = tx1->data; + context->txr.mipmap = PVR_MIPMAP_ENABLE; + context->txr.mipmap_bias = PVR_MIPBIAS_NORMAL; + } else { + context->txr.base = tx1->data + tx1->baseDataOffset; + context->txr.mipmap = PVR_MIPMAP_DISABLE; + context->txr.mipmap_bias = PVR_MIPBIAS_NORMAL; + } + context->txr.format = tx1->color; if(tx1->isPaletted) { diff --git a/GL/texture.c b/GL/texture.c index f8fe327..223ffa7 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -161,26 +161,83 @@ static GLint _determineStride(GLenum format, GLenum type) { return -1; } -GLubyte* _glGetMipmapLocation(TextureObject* obj, GLuint level) { +static GLuint _glGetMipmapDataOffset(TextureObject* obj, GLuint level) { GLuint offset = 0; + GLuint size = obj->height; - GLuint i = 0; - GLuint width = obj->width; - GLuint height = obj->height; + if(obj->width != obj->height) { + fprintf(stderr, "ERROR: Accessing memory location of mipmaps on non-square texture\n"); + return obj->baseDataOffset; + } - for(; i < level; ++i) { - offset += (width * height * obj->dataStride); - - if(width > 1) { - width /= 2; + if(obj->isPaletted){ + switch(size >> level) { + case 256: + offset = 0x05558; + break; + case 128: + offset = 0x01558; + break; + case 64: + offset = 0x00558; + break; + case 32: + offset = 0x00158; + break; + case 16: + offset = 0x00058; + break; + case 8: + offset = 0x00018; + break; + case 4: + offset = 0x00008; + break; + case 2: + offset = 0x00004; + break; + case 1: + case 0: + offset = 0x00000; + break; } - - if(height > 1) { - height /= 2; + } else { + switch(size >> level) { + case 256: + offset = 0x0AAB0; + break; + case 128: + offset = 0x02AB0; + break; + case 64: + offset = 0x00AB0; + break; + case 32: + offset = 0x002B0; + break; + case 16: + offset = 0x000B0; + break; + case 8: + offset = 0x00030; + break; + case 4: + offset = 0x00010; + break; + case 2: + offset = 0x00008; + break; + case 1: + offset = 0x00006; + break; } } - return ((GLubyte*) obj->data) + offset; + return offset; +} + +GLubyte* _glGetMipmapLocation(TextureObject* obj, GLuint level) { + return ((GLubyte*) obj->data) + _glGetMipmapDataOffset(obj, level); } GLuint _glGetMipmapLevelCount(TextureObject* obj) { @@ -188,25 +245,13 @@ GLuint _glGetMipmapLevelCount(TextureObject* obj) { } static GLuint _glGetMipmapDataSize(TextureObject* obj) { - GLuint size = 0; + /* The mipmap data size is the offset + the size of the + * image */ - GLuint i = 0; - GLuint width = obj->width; - GLuint height = obj->height; + GLuint imageSize = obj->width * obj->height * obj->dataStride; + GLuint offset = _glGetMipmapDataOffset(obj, 0); - for(; i < _glGetMipmapLevelCount(obj); ++i) { - size += (width * height * obj->dataStride); - - if(width > 1) { - width /= 2; - } - - if(height > 1) { - height /= 2; - } - } - - return size; + return imageSize + offset; } GLubyte _glInitTextures() { @@ -269,6 +314,9 @@ static void _glInitializeTextureObject(TextureObject* txr, unsigned int id) { txr->isCompressed = GL_FALSE; txr->isPaletted = GL_FALSE; + /* Not mipmapped by default */ + txr->baseDataOffset = 0; + /* Always default to the first shared bank */ txr->shared_bank = 0; } @@ -768,6 +816,12 @@ static GLboolean _isSupportedFormat(GLenum format) { } GLboolean _glIsMipmapComplete(const TextureObject* obj) { + + // Non-square textures can't have mipmaps + if(obj->width != obj->height) { + return GL_FALSE; + } + if(!obj->mipmap || !obj->mipmapCount) { return GL_FALSE; } @@ -782,16 +836,9 @@ GLboolean _glIsMipmapComplete(const TextureObject* obj) { return GL_TRUE; } -#define TWIDTAB(x) ( (x&1)|((x&2)<<1)|((x&4)<<2)|((x&8)<<3)|((x&16)<<4)| \ - ((x&32)<<5)|((x&64)<<6)|((x&128)<<7)|((x&256)<<8)|((x&512)<<9) ) - -#define TWIDOUT(x, y) ( TWIDTAB((y)) | (TWIDTAB((x)) << 1) ) -#define MIN(a, b) ( (a)<(b)? (a):(b) ) - - void _glAllocateSpaceForMipmaps(TextureObject* active) { - if(active->data && active->mipmap > 1) { - /* Already done */ + if(active->data && active->baseDataOffset > 0) { + /* Already done - mipmaps have a dataOffset */ return; } @@ -800,18 +847,29 @@ void _glAllocateSpaceForMipmaps(TextureObject* active) { * then free the original */ - GLubyte* src = active->data; - GLubyte* dest = active->data = pvr_mem_malloc(_glGetMipmapDataSize(active)); + GLuint size = active->baseDataSize; - /* If there was existing data, then copy it across before freeing */ - if(src) { - GLuint i = 0; - for(; i < active->width * active->height * active->dataStride; ++i) { - *dest++ = *src++; - } + /* Copy the data out of the pvr and back to ram */ + GLubyte* temp = (GLubyte*) malloc(size); + memcpy(temp, active->data, size); - pvr_mem_free(src); - } + /* Free the PVR data */ + pvr_mem_free(active->data); + active->data = NULL; + + /* Figure out how much room to allocate for mipmaps */ + GLuint bytes = _glGetMipmapDataSize(active); + + fprintf(stderr, "Allocating %d PVR bytes\n", bytes); + active->data = pvr_mem_malloc(bytes); + + /* If there was existing data, then copy it where it should go */ + fprintf(stderr, "Copying data\n"); + memcpy(_glGetMipmapLocation(active, 0), temp, size); + + /* Set the data offset depending on whether or not this is a + * paletted texure */ + active->baseDataOffset = _glGetMipmapDataOffset(active, 0); } void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, @@ -892,7 +950,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, assert(active); - if(active->data) { + if(active->data && level == 0) { /* pre-existing texture - check if changed */ if(active->width != width || active->height != height || @@ -903,6 +961,8 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, active->mipmap = 0; active->mipmapCount = 0; active->dataStride = 0; + active->baseDataOffset = 0; + active->baseDataSize = 0; } } @@ -925,25 +985,25 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, /* Set the required mipmap count */ active->mipmapCount = _glGetMipmapLevelCount(active); active->dataStride = destStride; + active->baseDataSize = bytes; - GLuint size = bytes; + assert(bytes); - /* If we're uploading a mipmap level, we need to allocate the full amount of space */ if(level > 0) { - size = _glGetMipmapDataSize(active); + /* If we're uploading a mipmap level, we need to allocate the full amount of space */ + _glAllocateSpaceForMipmaps(active); + } else { + active->data = pvr_mem_malloc(active->baseDataSize); } - assert(size); - active->data = pvr_mem_malloc(size); assert(active->data); - active->isCompressed = GL_FALSE; active->isPaletted = isPaletted; } /* We're supplying a mipmap level, but previously we only had - * data for the first level (level 0, e.g. 1 << 0 == 1) */ - if(level > 0 && active->mipmap == 1) { + * data for the first level (level 0) */ + if(level > 0 && active->baseDataOffset == 0) { _glAllocateSpaceForMipmaps(active); } @@ -952,7 +1012,9 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, /* Let's assume we need to convert */ GLboolean needsConversion = GL_TRUE; - GLboolean needsTwiddling = GL_FALSE; + + /* Let's assume we need twiddling - we always store things twiddled! */ + GLboolean needsTwiddling = GL_TRUE; /* * These are the only formats where the source format passed in matches the pvr format. @@ -961,7 +1023,6 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, if(format == GL_COLOR_INDEX) { /* Don't convert color indexes */ needsConversion = GL_FALSE; - needsTwiddling = type == GL_UNSIGNED_BYTE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_4_4_4_4_REV && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_1_5_5_5_REV && internalFormat == GL_RGBA) { @@ -970,45 +1031,32 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, needsConversion = GL_FALSE; } else if(format == GL_RGB && type == GL_UNSIGNED_SHORT_5_6_5_TWID_KOS && internalFormat == GL_RGB) { needsConversion = GL_FALSE; + needsTwiddling = GL_FALSE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_1_5_5_5_REV_TWID_KOS && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; + needsTwiddling = GL_FALSE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_4_4_4_4_REV_TWID_KOS && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; + needsTwiddling = GL_FALSE; } - GLubyte* targetData = _glGetMipmapLocation(active, level); + GLubyte* targetData = (active->baseDataOffset == 0) ? active->data : _glGetMipmapLocation(active, level); assert(targetData); + GLubyte* conversionBuffer = NULL; + if(!data) { /* No data? Do nothing! */ return; - } else if(!needsConversion) { + } else if(!needsConversion && !needsTwiddling) { assert(targetData); assert(data); assert(bytes); - if(needsTwiddling) { - assert(type == GL_UNSIGNED_BYTE); // Anything else needs this loop adjusting - GLuint x, y, min, mask; - - GLubyte *pixels = (GLubyte*) data; - GLushort *vtex = (GLushort*) targetData; - - min = MIN(w, h); - mask = min - 1; - - for(y = 0; y < h; y += 2) { - for(x = 0; x < w; x++) { - vtex[TWIDOUT((y & mask) / 2, x & mask) + (x / min + y / min)*min * min / 2] = pixels[y * w + x] | (pixels[(y + 1) * w + x] << 8); - } - } - } else { - /* No conversion? Just copy the data, and the pvr_format is correct */ - sq_cpy(targetData, data, bytes); - } - + /* No conversion? Just copy the data, and the pvr_format is correct */ + sq_cpy(targetData, data, bytes); return; - } else { + } else if(needsConversion) { TextureConversionFunc convert = _determineConversion( internalFormat, format, @@ -1020,12 +1068,6 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, return; } - GLubyte* dest = (GLubyte*) targetData; - const GLubyte* source = data; - - assert(dest); - assert(source); - GLint stride = _determineStride(format, type); assert(stride > -1); @@ -1034,6 +1076,14 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, return; } + conversionBuffer = malloc(bytes); + + GLubyte* dest = conversionBuffer; + const GLubyte* source = data; + + assert(conversionBuffer); + assert(source); + /* Perform the conversion */ GLuint i; for(i = 0; i < bytes; i += destStride) { @@ -1043,6 +1093,34 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, source += stride; } } + + if(needsTwiddling) { + const GLubyte *pixels = (GLubyte*) (conversionBuffer) ? conversionBuffer : data; + + if(internalFormat == GL_COLOR_INDEX8_EXT) { + pvr_txr_load_ex((void*) pixels, targetData, width, height, PVR_TXRLOAD_8BPP); + } else { + pvr_txr_load_ex((void*) pixels, targetData, width, height, PVR_TXRLOAD_16BPP); + } + + /* We make sure we remove nontwiddled and add twiddled. We could always + * make it twiddled when determining the format but I worry that would make the + * code less flexible to change in the future */ + active->color &= ~PVR_TXRFMT_NONTWIDDLED; + active->color |= PVR_TXRFMT_TWIDDLED; + } else { + /* We should only get here if we converted twiddled data... which is never currently */ + assert(conversionBuffer); + + // We've already converted the data and we + // don't need to twiddle it! + sq_cpy(targetData, conversionBuffer, bytes); + } + + if(conversionBuffer) { + free(conversionBuffer); + conversionBuffer = NULL; + } } void APIENTRY glTexParameteri(GLenum target, GLenum pname, GLint param) { diff --git a/samples/mipmap/main.c b/samples/mipmap/main.c index 1eb1909..15397d3 100644 --- a/samples/mipmap/main.c +++ b/samples/mipmap/main.c @@ -174,9 +174,28 @@ void DrawQuad() { glEnd(); // done with the polygon. } +static GLboolean mipmap_enabled = GL_FALSE; +static GLuint timer = 0; + /* The main drawing function. */ void DrawGLScene() { + timer++; + if(timer > 60) { + timer = 0; + mipmap_enabled = !mipmap_enabled; + + if(mipmap_enabled) { + printf("Enabling mipmaps!\n"); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + } else { + printf("Disabling mipmaps!\n"); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); + } + } + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glClearColor(0.5, 0.5, 0.5, 1.0); From 2b53f50c462a7c9299c20d355e58816db9f20330 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Tue, 24 Sep 2019 19:39:23 +0100 Subject: [PATCH 25/64] Add some copy safety and remove some print statements --- GL/private.h | 3 +++ GL/texture.c | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/GL/private.h b/GL/private.h index 5ce6a3e..c33a1a1 100644 --- a/GL/private.h +++ b/GL/private.h @@ -12,6 +12,9 @@ #include "../containers/aligned_vector.h" #include "../containers/named_array.h" +#define FASTCPY(dst, src, bytes) \ + (bytes % 32 == 0) ? sq_cpy(dst, src, bytes) : memcpy(dst, src, bytes); + #define TRACE_ENABLED 0 #define TRACE() if(TRACE_ENABLED) {fprintf(stderr, "%s\n", __func__);} diff --git a/GL/texture.c b/GL/texture.c index 223ffa7..23413b3 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -860,11 +860,9 @@ void _glAllocateSpaceForMipmaps(TextureObject* active) { /* Figure out how much room to allocate for mipmaps */ GLuint bytes = _glGetMipmapDataSize(active); - fprintf(stderr, "Allocating %d PVR bytes\n", bytes); active->data = pvr_mem_malloc(bytes); /* If there was existing data, then copy it where it should go */ - fprintf(stderr, "Copying data\n"); memcpy(_glGetMipmapLocation(active, 0), temp, size); /* Set the data offset depending on whether or not this is a @@ -1054,7 +1052,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, assert(bytes); /* No conversion? Just copy the data, and the pvr_format is correct */ - sq_cpy(targetData, data, bytes); + FASTCPY(targetData, data, bytes); return; } else if(needsConversion) { TextureConversionFunc convert = _determineConversion( @@ -1114,7 +1112,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, // We've already converted the data and we // don't need to twiddle it! - sq_cpy(targetData, conversionBuffer, bytes); + FASTCPY(targetData, conversionBuffer, bytes); } if(conversionBuffer) { From e14db20a419628b8cbb6037aeb42ccbfd1f1690b Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Tue, 24 Sep 2019 21:26:17 +0100 Subject: [PATCH 26/64] Fix a bunch of mipmap issues --- GL/framebuffer.c | 25 ++++++++++++++----------- GL/private.h | 8 ++++++++ GL/texture.c | 37 +++++++++++++++++++++---------------- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/GL/framebuffer.c b/GL/framebuffer.c index 663a9c7..c011952 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -1,4 +1,6 @@ #include +#include + #include "private.h" #include "../include/glkos.h" #include "../include/glext.h" @@ -156,37 +158,37 @@ GLboolean _glCalculateAverageTexel(const GLubyte* src, const GLuint srcWidth, co if((pvrFormat & PVR_TXRFMT_ARGB1555) == PVR_TXRFMT_ARGB1555) { a = A1555(*s1) + A1555(*s2) + A1555(*s3) + A1555(*s4); r = R1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); - g = G1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); - b = B1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); + g = G1555(*s1) + G1555(*s2) + G1555(*s3) + G1555(*s4); + b = B1555(*s1) + B1555(*s2) + B1555(*s3) + B1555(*s4); a /= 4; r /= 4; g /= 4; b /= 4; - *d1 = (a << 15 | r << 10 | g << 5 | b); + *d1 = PACK_ARGB1555(a, r, g, b); } else if((pvrFormat & PVR_TXRFMT_ARGB4444) == PVR_TXRFMT_ARGB4444) { a = A4444(*s1) + A4444(*s2) + A4444(*s3) + A4444(*s4); r = R4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); - g = G4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); - b = B4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); + g = G4444(*s1) + G4444(*s2) + G4444(*s3) + G4444(*s4); + b = B4444(*s1) + B4444(*s2) + B4444(*s3) + B4444(*s4); a /= 4; r /= 4; g /= 4; b /= 4; - *d1 = (a << 12 | r << 8 | g << 4 | b); - } else if((pvrFormat & PVR_TXRFMT_RGB565) == PVR_TXRFMT_ARGB4444) { + *d1 = PACK_ARGB4444(a, r, g, b); + } else if((pvrFormat & PVR_TXRFMT_RGB565) == PVR_TXRFMT_RGB565) { r = R565(*s1) + R565(*s2) + R565(*s3) + R565(*s4); - g = G565(*s1) + R565(*s2) + R565(*s3) + R565(*s4); - b = B565(*s1) + R565(*s2) + R565(*s3) + R565(*s4); + g = G565(*s1) + G565(*s2) + G565(*s3) + G565(*s4); + b = B565(*s1) + B565(*s2) + B565(*s3) + B565(*s4); r /= 4; g /= 4; b /= 4; - *d1 = (r << 11 | g << 5 | b); + *d1 = PACK_RGB565(r, g, b); } else { fprintf(stderr, "ERROR: Unsupported PVR format for mipmap generation"); _glKosThrowError(GL_INVALID_OPERATION, __func__); @@ -239,7 +241,6 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { GLuint thisWidth = (prevWidth > 1) ? prevWidth / 2 : 1; GLuint thisHeight = (prevHeight > 1) ? prevHeight / 2 : 1; - /* Do the minification */ for(sx = 0, dx = 0; sx < prevWidth; sx += 2, dx += 1) { for(sy = 0, dy = 0; sy < prevHeight; sy += 2, dy += 1) { GLubyte* srcTexel = &prevData[ @@ -266,6 +267,8 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { prevWidth = thisWidth; prevHeight = thisHeight; } + + assert(_glIsMipmapComplete(tex)); } GLenum APIENTRY glCheckFramebufferStatusEXT(GLenum target) { diff --git a/GL/private.h b/GL/private.h index c33a1a1..ed1ac9a 100644 --- a/GL/private.h +++ b/GL/private.h @@ -15,6 +15,14 @@ #define FASTCPY(dst, src, bytes) \ (bytes % 32 == 0) ? sq_cpy(dst, src, bytes) : memcpy(dst, src, bytes); +#define _PACK4(v) ((v * 0xF) / 0xFF) +#define PACK_ARGB4444(a,r,g,b) (_PACK4(a) << 12) | (_PACK4(r) << 8) | (_PACK4(g) << 4) | (_PACK4(b)) +#define PACK_ARGB8888(a,r,g,b) ( ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF) ) +#define PACK_ARGB1555(a,r,g, b) \ + (a > 0 | (r >> 3) << 10 | (g >> 3) << 5 | (b >> 3)) +#define PACK_RGB565(r,g,b) \ + ((r >> 3) << 15 | (g >> 2) << 11 | b >> 3) + #define TRACE_ENABLED 0 #define TRACE() if(TRACE_ENABLED) {fprintf(stderr, "%s\n", __func__);} diff --git a/GL/texture.c b/GL/texture.c index 23413b3..39c01e9 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -23,12 +23,6 @@ static TexturePalette* SHARED_PALETTES[4] = {NULL, NULL, NULL, NULL}; static GLuint _determinePVRFormat(GLint internalFormat, GLenum type); -#define PACK_ARGB8888(a,r,g,b) ( ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF) ) - - -#define _PACK4(v) ((v * 0xF) / 0xFF) -#define PACK_ARGB4444(a,r,g,b) (_PACK4(a) << 12) | (_PACK4(r) << 8) | (_PACK4(g) << 4) | (_PACK4(b)) - static GLboolean BANKS_USED[4]; // Each time a 256 colour bank is used, this is set to true static GLboolean SUBBANKS_USED[4][16]; // 4 counts of the used 16 colour banks within the 256 ones static GLenum INTERNAL_PALETTE_FORMAT = GL_RGBA4; @@ -172,37 +166,48 @@ static GLuint _glGetMipmapDataOffset(TextureObject* obj, GLuint level) { if(obj->isPaletted){ switch(size >> level) { + case 1024: + offset = 0x15556; + break; + case 512: + offset = 0x05556; + break; case 256: - offset = 0x05558; + offset = 0x01556; break; case 128: - offset = 0x01558; + offset = 0x00556; break; case 64: - offset = 0x00558; + offset = 0x00156; break; case 32: - offset = 0x00158; + offset = 0x00056; break; case 16: - offset = 0x00058; + offset = 0x00016; break; case 8: - offset = 0x00018; + offset = 0x00006; break; case 4: - offset = 0x00008; + offset = 0x00002; break; case 2: - offset = 0x00004; + offset = 0x00001; break; case 1: - case 0: offset = 0x00000; break; } } else { switch(size >> level) { + case 1024: + offset = 0xAAAB0; + break; + case 512: + offset = 0x2AAB0; + break; case 256: offset = 0x0AAB0; break; @@ -248,7 +253,7 @@ static GLuint _glGetMipmapDataSize(TextureObject* obj) { /* The mipmap data size is the offset + the size of the * image */ - GLuint imageSize = obj->width * obj->height * obj->dataStride; + GLuint imageSize = obj->baseDataSize; GLuint offset = _glGetMipmapDataOffset(obj, 0); return imageSize + offset; From 36a3f7082f8532c4e8a6a50886b487cc2b82ab6c Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 25 Sep 2019 13:10:34 +0100 Subject: [PATCH 27/64] Implement mipmap generation for twiddled textures --- GL/framebuffer.c | 110 +++++++++++++++++++++++++++++++---------------- GL/private.h | 5 ++- 2 files changed, 75 insertions(+), 40 deletions(-) diff --git a/GL/framebuffer.c b/GL/framebuffer.c index c011952..08013c5 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -91,24 +91,24 @@ void APIENTRY glFramebufferTexture2DEXT(GLenum target, GLenum attachment, GLenum ACTIVE_FRAMEBUFFER->texture_id = texture; } -static inline GLuint A1555(GLuint v) { +static inline GLubyte A1555(GLushort v) { const GLuint MASK = (1 << 15); - return (v & MASK) >> 15; + return (v & MASK) >> 8; } -static inline GLuint R1555(GLuint v) { +static inline GLubyte R1555(GLushort v) { const GLuint MASK = (31 << 10); - return (v & MASK) >> 10; + return (v & MASK) >> 7; } -static inline GLuint G1555(GLuint v) { +static inline GLubyte G1555(GLushort v) { const GLuint MASK = (31 << 5); - return (v & MASK) >> 5; + return (v & MASK) >> 2; } -static inline GLuint B1555(GLuint v) { +static inline GLubyte B1555(GLushort v) { const GLuint MASK = (31 << 0); - return (v & MASK) >> 0; + return (v & MASK) << 3; } static inline GLuint A4444(GLuint v) { @@ -146,16 +146,16 @@ static inline GLuint B565(GLuint v) { return (v & MASK) >> 0; } -GLboolean _glCalculateAverageTexel(const GLubyte* src, const GLuint srcWidth, const GLuint pvrFormat, GLubyte* dest) { - GLushort* s1 = ((GLushort*) src); - GLushort* s2 = ((GLushort*) src) + 1; - GLushort* s3 = ((GLushort*) src) + srcWidth; - GLushort* s4 = ((GLushort*) src) + srcWidth + 1; - GLushort* d1 = ((GLushort*) dest); - +GLboolean _glCalculateAverageTexel(GLuint pvrFormat, const GLubyte* src1, const GLubyte* src2, const GLubyte* src3, const GLubyte* src4, GLubyte* t) { GLuint a, r, g, b; if((pvrFormat & PVR_TXRFMT_ARGB1555) == PVR_TXRFMT_ARGB1555) { + GLushort* s1 = (GLushort*) src1; + GLushort* s2 = (GLushort*) src2; + GLushort* s3 = (GLushort*) src3; + GLushort* s4 = (GLushort*) src4; + GLushort* d1 = (GLushort*) t; + a = A1555(*s1) + A1555(*s2) + A1555(*s3) + A1555(*s4); r = R1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); g = G1555(*s1) + G1555(*s2) + G1555(*s3) + G1555(*s4); @@ -166,8 +166,14 @@ GLboolean _glCalculateAverageTexel(const GLubyte* src, const GLuint srcWidth, co g /= 4; b /= 4; - *d1 = PACK_ARGB1555(a, r, g, b); + *d1 = PACK_ARGB1555((GLubyte) a, (GLubyte) r, (GLubyte) g, (GLubyte) b); } else if((pvrFormat & PVR_TXRFMT_ARGB4444) == PVR_TXRFMT_ARGB4444) { + GLushort* s1 = (GLushort*) src1; + GLushort* s2 = (GLushort*) src2; + GLushort* s3 = (GLushort*) src3; + GLushort* s4 = (GLushort*) src4; + GLushort* d1 = (GLushort*) t; + a = A4444(*s1) + A4444(*s2) + A4444(*s3) + A4444(*s4); r = R4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); g = G4444(*s1) + G4444(*s2) + G4444(*s3) + G4444(*s4); @@ -180,6 +186,12 @@ GLboolean _glCalculateAverageTexel(const GLubyte* src, const GLuint srcWidth, co *d1 = PACK_ARGB4444(a, r, g, b); } else if((pvrFormat & PVR_TXRFMT_RGB565) == PVR_TXRFMT_RGB565) { + GLushort* s1 = (GLushort*) src1; + GLushort* s2 = (GLushort*) src2; + GLushort* s3 = (GLushort*) src3; + GLushort* s4 = (GLushort*) src4; + GLushort* d1 = (GLushort*) t; + r = R565(*s1) + R565(*s2) + R565(*s3) + R565(*s4); g = G565(*s1) + G565(*s2) + G565(*s3) + G565(*s4); b = B565(*s1) + B565(*s2) + B565(*s3) + B565(*s4); @@ -199,6 +211,44 @@ GLboolean _glCalculateAverageTexel(const GLubyte* src, const GLuint srcWidth, co return GL_TRUE; } +GLboolean _glGenerateMipmapTwiddled(const GLuint pvrFormat, const GLubyte* prevData, GLuint thisWidth, GLuint thisHeight, GLubyte* thisData) { + uint32_t lastWidth = thisWidth * 2; + uint32_t lastHeight = thisHeight * 2; + + uint32_t i, j; + uint32_t stride = 0; + + if((pvrFormat & PVR_TXRFMT_PAL8BPP) == PVR_TXRFMT_PAL8BPP) { + stride = 1; + } else { + stride = 2; + } + + for(i = 0, j = 0; i < lastWidth * lastHeight; i += 4, j++) { + + /* In a twiddled texture, the neighbouring texels + * are next to each other. By averaging them we just basically shrink + * the reverse Ns so each reverse N becomes the next level down... if that makes sense!? */ + + GLubyte* s1 = &prevData[i * stride]; + GLubyte* s2 = s1 + stride; + GLubyte* s3 = s2 + stride; + GLubyte* s4 = s3 + stride; + GLubyte* t = &thisData[j * stride]; + + assert(s4 < prevData + (lastHeight * lastWidth * stride)); + assert(t < thisData + (thisHeight * thisWidth * stride)); + + _glCalculateAverageTexel(pvrFormat, s1, s2, s3, s4, t); + } + + return GL_TRUE; +} + +GLboolean _glGenerateMipmap(const GLuint pvrFormat, const GLubyte* prevData, GLuint thisWidth, GLuint thisHeight, GLubyte* thisData) { + return GL_TRUE; +} + void APIENTRY glGenerateMipmapEXT(GLenum target) { if(target != GL_TEXTURE_2D) { _glKosThrowError(GL_INVALID_OPERATION, __func__); @@ -226,40 +276,24 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { return; } - GLuint i = 1; - GLuint sx, sy, dx, dy; + GLuint i; GLuint prevWidth = tex->width; GLuint prevHeight = tex->height; /* Make sure there is room for the mipmap data on the texture object */ _glAllocateSpaceForMipmaps(tex); - for(; i < _glGetMipmapLevelCount(tex); ++i) { + for(i = 1; i < _glGetMipmapLevelCount(tex); ++i) { GLubyte* prevData = _glGetMipmapLocation(tex, i - 1); GLubyte* thisData = _glGetMipmapLocation(tex, i); GLuint thisWidth = (prevWidth > 1) ? prevWidth / 2 : 1; GLuint thisHeight = (prevHeight > 1) ? prevHeight / 2 : 1; - for(sx = 0, dx = 0; sx < prevWidth; sx += 2, dx += 1) { - for(sy = 0, dy = 0; sy < prevHeight; sy += 2, dy += 1) { - GLubyte* srcTexel = &prevData[ - ((sy * prevWidth) + sx) * tex->dataStride - ]; - - GLubyte* destTexel = &thisData[ - ((dy * thisWidth) + dx) * tex->dataStride - ]; - - if(!_glCalculateAverageTexel( - srcTexel, - prevWidth, - tex->color, - destTexel - )) { - return; - } - } + if((tex->color & PVR_TXRFMT_TWIDDLED) == PVR_TXRFMT_TWIDDLED) { + _glGenerateMipmapTwiddled(tex->color, prevData, thisWidth, thisHeight, thisData); + } else { + _glGenerateMipmap(tex->color, prevData, thisWidth, thisHeight, thisData); } tex->mipmap |= (1 << i); diff --git a/GL/private.h b/GL/private.h index ed1ac9a..73dc9e6 100644 --- a/GL/private.h +++ b/GL/private.h @@ -18,8 +18,9 @@ #define _PACK4(v) ((v * 0xF) / 0xFF) #define PACK_ARGB4444(a,r,g,b) (_PACK4(a) << 12) | (_PACK4(r) << 8) | (_PACK4(g) << 4) | (_PACK4(b)) #define PACK_ARGB8888(a,r,g,b) ( ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF) ) -#define PACK_ARGB1555(a,r,g, b) \ - (a > 0 | (r >> 3) << 10 | (g >> 3) << 5 | (b >> 3)) +#define PACK_ARGB1555(a,r,g,b) \ + (((GLushort)(a > 0) << 15) | (((GLushort) r >> 3) << 10) | (((GLushort)g >> 3) << 5) | ((GLushort)b >> 3)) + #define PACK_RGB565(r,g,b) \ ((r >> 3) << 15 | (g >> 2) << 11 | b >> 3) From 2e1e28ce510bbcb1af126fe40fcfc5ae361c9162 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 25 Sep 2019 13:32:13 +0100 Subject: [PATCH 28/64] More mipmap generation fixes --- GL/framebuffer.c | 94 ++++++++++++++++++++++++------------------------ GL/private.h | 2 +- GL/texture.c | 1 - 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/GL/framebuffer.c b/GL/framebuffer.c index 08013c5..f96fdd7 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -131,61 +131,25 @@ static inline GLuint B4444(GLuint v) { return (v & MASK) >> 0; } -static inline GLuint R565(GLuint v) { +static inline GLubyte R565(GLshort v) { const GLuint MASK = (31 << 11); - return (v & MASK) >> 11; + return (v & MASK) >> 8; } -static inline GLuint G565(GLuint v) { - const GLuint MASK = (63 << 5); - return (v & MASK) >> 5; +static inline GLubyte G565(GLushort v) { + const GLuint MASK = (31 << 5); + return (v & MASK) >> 3; } -static inline GLuint B565(GLuint v) { +static inline GLubyte B565(GLushort v) { const GLuint MASK = (31 << 0); - return (v & MASK) >> 0; + return (v & MASK); } GLboolean _glCalculateAverageTexel(GLuint pvrFormat, const GLubyte* src1, const GLubyte* src2, const GLubyte* src3, const GLubyte* src4, GLubyte* t) { GLuint a, r, g, b; - if((pvrFormat & PVR_TXRFMT_ARGB1555) == PVR_TXRFMT_ARGB1555) { - GLushort* s1 = (GLushort*) src1; - GLushort* s2 = (GLushort*) src2; - GLushort* s3 = (GLushort*) src3; - GLushort* s4 = (GLushort*) src4; - GLushort* d1 = (GLushort*) t; - - a = A1555(*s1) + A1555(*s2) + A1555(*s3) + A1555(*s4); - r = R1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); - g = G1555(*s1) + G1555(*s2) + G1555(*s3) + G1555(*s4); - b = B1555(*s1) + B1555(*s2) + B1555(*s3) + B1555(*s4); - - a /= 4; - r /= 4; - g /= 4; - b /= 4; - - *d1 = PACK_ARGB1555((GLubyte) a, (GLubyte) r, (GLubyte) g, (GLubyte) b); - } else if((pvrFormat & PVR_TXRFMT_ARGB4444) == PVR_TXRFMT_ARGB4444) { - GLushort* s1 = (GLushort*) src1; - GLushort* s2 = (GLushort*) src2; - GLushort* s3 = (GLushort*) src3; - GLushort* s4 = (GLushort*) src4; - GLushort* d1 = (GLushort*) t; - - a = A4444(*s1) + A4444(*s2) + A4444(*s3) + A4444(*s4); - r = R4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); - g = G4444(*s1) + G4444(*s2) + G4444(*s3) + G4444(*s4); - b = B4444(*s1) + B4444(*s2) + B4444(*s3) + B4444(*s4); - - a /= 4; - r /= 4; - g /= 4; - b /= 4; - - *d1 = PACK_ARGB4444(a, r, g, b); - } else if((pvrFormat & PVR_TXRFMT_RGB565) == PVR_TXRFMT_RGB565) { + if((pvrFormat & PVR_TXRFMT_RGB565) == PVR_TXRFMT_RGB565) { GLushort* s1 = (GLushort*) src1; GLushort* s2 = (GLushort*) src2; GLushort* s3 = (GLushort*) src3; @@ -202,10 +166,43 @@ GLboolean _glCalculateAverageTexel(GLuint pvrFormat, const GLubyte* src1, const *d1 = PACK_RGB565(r, g, b); } else { - fprintf(stderr, "ERROR: Unsupported PVR format for mipmap generation"); - _glKosThrowError(GL_INVALID_OPERATION, __func__); - _glKosPrintError(); - return GL_FALSE; + if((pvrFormat & PVR_TXRFMT_ARGB4444) == PVR_TXRFMT_ARGB4444) { + GLushort* s1 = (GLushort*) src1; + GLushort* s2 = (GLushort*) src2; + GLushort* s3 = (GLushort*) src3; + GLushort* s4 = (GLushort*) src4; + GLushort* d1 = (GLushort*) t; + + a = A4444(*s1) + A4444(*s2) + A4444(*s3) + A4444(*s4); + r = R4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); + g = G4444(*s1) + G4444(*s2) + G4444(*s3) + G4444(*s4); + b = B4444(*s1) + B4444(*s2) + B4444(*s3) + B4444(*s4); + + a /= 4; + r /= 4; + g /= 4; + b /= 4; + + *d1 = PACK_ARGB4444(a, r, g, b); + } else { + GLushort* s1 = (GLushort*) src1; + GLushort* s2 = (GLushort*) src2; + GLushort* s3 = (GLushort*) src3; + GLushort* s4 = (GLushort*) src4; + GLushort* d1 = (GLushort*) t; + + a = A1555(*s1) + A1555(*s2) + A1555(*s3) + A1555(*s4); + r = R1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); + g = G1555(*s1) + G1555(*s2) + G1555(*s3) + G1555(*s4); + b = B1555(*s1) + B1555(*s2) + B1555(*s3) + B1555(*s4); + + a /= 4; + r /= 4; + g /= 4; + b /= 4; + + *d1 = PACK_ARGB1555((GLubyte) a, (GLubyte) r, (GLubyte) g, (GLubyte) b); + } } return GL_TRUE; @@ -291,6 +288,7 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { GLuint thisHeight = (prevHeight > 1) ? prevHeight / 2 : 1; if((tex->color & PVR_TXRFMT_TWIDDLED) == PVR_TXRFMT_TWIDDLED) { + fprintf(stderr, "Format: %d\n", tex->color); _glGenerateMipmapTwiddled(tex->color, prevData, thisWidth, thisHeight, thisData); } else { _glGenerateMipmap(tex->color, prevData, thisWidth, thisHeight, thisData); diff --git a/GL/private.h b/GL/private.h index 73dc9e6..581e1d8 100644 --- a/GL/private.h +++ b/GL/private.h @@ -22,7 +22,7 @@ (((GLushort)(a > 0) << 15) | (((GLushort) r >> 3) << 10) | (((GLushort)g >> 3) << 5) | ((GLushort)b >> 3)) #define PACK_RGB565(r,g,b) \ - ((r >> 3) << 15 | (g >> 2) << 11 | b >> 3) + (((r & 0xf8) << 8) | ((g & 0xfc) << 3) | (b >> 3)) #define TRACE_ENABLED 0 #define TRACE() if(TRACE_ENABLED) {fprintf(stderr, "%s\n", __func__);} diff --git a/GL/texture.c b/GL/texture.c index 39c01e9..cfa0d05 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -1110,7 +1110,6 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, * make it twiddled when determining the format but I worry that would make the * code less flexible to change in the future */ active->color &= ~PVR_TXRFMT_NONTWIDDLED; - active->color |= PVR_TXRFMT_TWIDDLED; } else { /* We should only get here if we converted twiddled data... which is never currently */ assert(conversionBuffer); From 08ba39f6d10c55154fa43b7bde64f1499ebc9767 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 25 Sep 2019 13:45:08 +0100 Subject: [PATCH 29/64] Fix twiddled mipmap generation --- GL/framebuffer.c | 5 ++--- GL/private.h | 2 +- GL/texture.c | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/GL/framebuffer.c b/GL/framebuffer.c index f96fdd7..0f802a5 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -137,13 +137,13 @@ static inline GLubyte R565(GLshort v) { } static inline GLubyte G565(GLushort v) { - const GLuint MASK = (31 << 5); + const GLuint MASK = (63 << 5); return (v & MASK) >> 3; } static inline GLubyte B565(GLushort v) { const GLuint MASK = (31 << 0); - return (v & MASK); + return (v & MASK) << 3; } GLboolean _glCalculateAverageTexel(GLuint pvrFormat, const GLubyte* src1, const GLubyte* src2, const GLubyte* src3, const GLubyte* src4, GLubyte* t) { @@ -288,7 +288,6 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { GLuint thisHeight = (prevHeight > 1) ? prevHeight / 2 : 1; if((tex->color & PVR_TXRFMT_TWIDDLED) == PVR_TXRFMT_TWIDDLED) { - fprintf(stderr, "Format: %d\n", tex->color); _glGenerateMipmapTwiddled(tex->color, prevData, thisWidth, thisHeight, thisData); } else { _glGenerateMipmap(tex->color, prevData, thisWidth, thisHeight, thisData); diff --git a/GL/private.h b/GL/private.h index 581e1d8..7fb68b8 100644 --- a/GL/private.h +++ b/GL/private.h @@ -22,7 +22,7 @@ (((GLushort)(a > 0) << 15) | (((GLushort) r >> 3) << 10) | (((GLushort)g >> 3) << 5) | ((GLushort)b >> 3)) #define PACK_RGB565(r,g,b) \ - (((r & 0xf8) << 8) | ((g & 0xfc) << 3) | (b >> 3)) + ((((GLushort)r & 0xf8) << 8) | (((GLushort) g & 0xfc) << 3) | ((GLushort) b >> 3)) #define TRACE_ENABLED 0 #define TRACE() if(TRACE_ENABLED) {fprintf(stderr, "%s\n", __func__);} diff --git a/GL/texture.c b/GL/texture.c index cfa0d05..eb79898 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -1109,7 +1109,7 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, /* We make sure we remove nontwiddled and add twiddled. We could always * make it twiddled when determining the format but I worry that would make the * code less flexible to change in the future */ - active->color &= ~PVR_TXRFMT_NONTWIDDLED; + active->color &= ~(1 << 26); } else { /* We should only get here if we converted twiddled data... which is never currently */ assert(conversionBuffer); From 2c5b71b2b013d3b26ba7e486562649b169c49cbf Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Thu, 26 Sep 2019 09:17:07 +0100 Subject: [PATCH 30/64] Implement mipmap generation for paletted textures --- GL/framebuffer.c | 128 +++++++++++++++++++++++----------------- GL/texture.c | 30 ++++++---- samples/paletted/main.c | 3 +- 3 files changed, 94 insertions(+), 67 deletions(-) diff --git a/GL/framebuffer.c b/GL/framebuffer.c index 0f802a5..fa38f61 100644 --- a/GL/framebuffer.c +++ b/GL/framebuffer.c @@ -111,27 +111,27 @@ static inline GLubyte B1555(GLushort v) { return (v & MASK) << 3; } -static inline GLuint A4444(GLuint v) { +static inline GLubyte A4444(GLushort v) { const GLuint MASK = (0xF << 12); return (v & MASK) >> 12; } -static inline GLuint R4444(GLuint v) { +static inline GLubyte R4444(GLushort v) { const GLuint MASK = (0xF << 8); return (v & MASK) >> 8; } -static inline GLuint G4444(GLuint v) { +static inline GLubyte G4444(GLushort v) { const GLuint MASK = (0xF << 4); return (v & MASK) >> 4; } -static inline GLuint B4444(GLuint v) { +static inline GLubyte B4444(GLushort v) { const GLuint MASK = (0xF << 0); return (v & MASK) >> 0; } -static inline GLubyte R565(GLshort v) { +static inline GLubyte R565(GLushort v) { const GLuint MASK = (31 << 11); return (v & MASK) >> 8; } @@ -149,7 +149,19 @@ static inline GLubyte B565(GLushort v) { GLboolean _glCalculateAverageTexel(GLuint pvrFormat, const GLubyte* src1, const GLubyte* src2, const GLubyte* src3, const GLubyte* src4, GLubyte* t) { GLuint a, r, g, b; - if((pvrFormat & PVR_TXRFMT_RGB565) == PVR_TXRFMT_RGB565) { + GLubyte format = ((pvrFormat & (1 << 27)) | (pvrFormat & (1 << 26))) >> 26; + + const GLubyte ARGB1555 = 0; + const GLubyte ARGB4444 = 1; + const GLubyte RGB565 = 2; + + if((pvrFormat & PVR_TXRFMT_PAL8BPP) == PVR_TXRFMT_PAL8BPP) { + /* Paletted... all we can do really is just pick one of the + * 4 texels.. unless we want to change the palette (bad) or + * pick the closest available colour (slow, and probably bad) + */ + *t = *src1; + } else if(format == RGB565) { GLushort* s1 = (GLushort*) src1; GLushort* s2 = (GLushort*) src2; GLushort* s3 = (GLushort*) src3; @@ -165,44 +177,44 @@ GLboolean _glCalculateAverageTexel(GLuint pvrFormat, const GLubyte* src1, const b /= 4; *d1 = PACK_RGB565(r, g, b); + } else if(format == ARGB4444) { + GLushort* s1 = (GLushort*) src1; + GLushort* s2 = (GLushort*) src2; + GLushort* s3 = (GLushort*) src3; + GLushort* s4 = (GLushort*) src4; + GLushort* d1 = (GLushort*) t; + + a = A4444(*s1) + A4444(*s2) + A4444(*s3) + A4444(*s4); + r = R4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); + g = G4444(*s1) + G4444(*s2) + G4444(*s3) + G4444(*s4); + b = B4444(*s1) + B4444(*s2) + B4444(*s3) + B4444(*s4); + + a /= 4; + r /= 4; + g /= 4; + b /= 4; + + *d1 = PACK_ARGB4444(a, r, g, b); } else { - if((pvrFormat & PVR_TXRFMT_ARGB4444) == PVR_TXRFMT_ARGB4444) { - GLushort* s1 = (GLushort*) src1; - GLushort* s2 = (GLushort*) src2; - GLushort* s3 = (GLushort*) src3; - GLushort* s4 = (GLushort*) src4; - GLushort* d1 = (GLushort*) t; + assert(format == ARGB1555); - a = A4444(*s1) + A4444(*s2) + A4444(*s3) + A4444(*s4); - r = R4444(*s1) + R4444(*s2) + R4444(*s3) + R4444(*s4); - g = G4444(*s1) + G4444(*s2) + G4444(*s3) + G4444(*s4); - b = B4444(*s1) + B4444(*s2) + B4444(*s3) + B4444(*s4); + GLushort* s1 = (GLushort*) src1; + GLushort* s2 = (GLushort*) src2; + GLushort* s3 = (GLushort*) src3; + GLushort* s4 = (GLushort*) src4; + GLushort* d1 = (GLushort*) t; - a /= 4; - r /= 4; - g /= 4; - b /= 4; + a = A1555(*s1) + A1555(*s2) + A1555(*s3) + A1555(*s4); + r = R1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); + g = G1555(*s1) + G1555(*s2) + G1555(*s3) + G1555(*s4); + b = B1555(*s1) + B1555(*s2) + B1555(*s3) + B1555(*s4); - *d1 = PACK_ARGB4444(a, r, g, b); - } else { - GLushort* s1 = (GLushort*) src1; - GLushort* s2 = (GLushort*) src2; - GLushort* s3 = (GLushort*) src3; - GLushort* s4 = (GLushort*) src4; - GLushort* d1 = (GLushort*) t; + a /= 4; + r /= 4; + g /= 4; + b /= 4; - a = A1555(*s1) + A1555(*s2) + A1555(*s3) + A1555(*s4); - r = R1555(*s1) + R1555(*s2) + R1555(*s3) + R1555(*s4); - g = G1555(*s1) + G1555(*s2) + G1555(*s3) + G1555(*s4); - b = B1555(*s1) + B1555(*s2) + B1555(*s3) + B1555(*s4); - - a /= 4; - r /= 4; - g /= 4; - b /= 4; - - *d1 = PACK_ARGB1555((GLubyte) a, (GLubyte) r, (GLubyte) g, (GLubyte) b); - } + *d1 = PACK_ARGB1555((GLubyte) a, (GLubyte) r, (GLubyte) g, (GLubyte) b); } return GL_TRUE; @@ -227,10 +239,10 @@ GLboolean _glGenerateMipmapTwiddled(const GLuint pvrFormat, const GLubyte* prevD * are next to each other. By averaging them we just basically shrink * the reverse Ns so each reverse N becomes the next level down... if that makes sense!? */ - GLubyte* s1 = &prevData[i * stride]; - GLubyte* s2 = s1 + stride; - GLubyte* s3 = s2 + stride; - GLubyte* s4 = s3 + stride; + const GLubyte* s1 = &prevData[i * stride]; + const GLubyte* s2 = s1 + stride; + const GLubyte* s3 = s2 + stride; + const GLubyte* s4 = s3 + stride; GLubyte* t = &thisData[j * stride]; assert(s4 < prevData + (lastHeight * lastWidth * stride)); @@ -242,10 +254,6 @@ GLboolean _glGenerateMipmapTwiddled(const GLuint pvrFormat, const GLubyte* prevD return GL_TRUE; } -GLboolean _glGenerateMipmap(const GLuint pvrFormat, const GLubyte* prevData, GLuint thisWidth, GLuint thisHeight, GLubyte* thisData) { - return GL_TRUE; -} - void APIENTRY glGenerateMipmapEXT(GLenum target) { if(target != GL_TEXTURE_2D) { _glKosThrowError(GL_INVALID_OPERATION, __func__); @@ -268,7 +276,25 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { return; } - if(_glIsMipmapComplete(tex)) { + if((tex->color & PVR_TXRFMT_NONTWIDDLED) == PVR_TXRFMT_NONTWIDDLED) { + /* glTexImage2D should twiddle internally textures in nearly all cases + * so this error is unlikely */ + + fprintf(stderr, "[GL ERROR] Mipmaps are only supported on twiddled textures\n"); + _glKosThrowError(GL_INVALID_OPERATION, __func__); + _glKosPrintError(); + return; + } + + GLboolean complete = _glIsMipmapComplete(tex); + if(!complete && tex->isCompressed) { + fprintf(stderr, "[GL ERROR] Generating mipmaps for compressed textures is not yet supported\n"); + _glKosThrowError(GL_INVALID_OPERATION, __func__); + _glKosPrintError(); + return; + } + + if(complete) { /* Nothing to do */ return; } @@ -287,11 +313,7 @@ void APIENTRY glGenerateMipmapEXT(GLenum target) { GLuint thisWidth = (prevWidth > 1) ? prevWidth / 2 : 1; GLuint thisHeight = (prevHeight > 1) ? prevHeight / 2 : 1; - if((tex->color & PVR_TXRFMT_TWIDDLED) == PVR_TXRFMT_TWIDDLED) { - _glGenerateMipmapTwiddled(tex->color, prevData, thisWidth, thisHeight, thisData); - } else { - _glGenerateMipmap(tex->color, prevData, thisWidth, thisHeight, thisData); - } + _glGenerateMipmapTwiddled(tex->color, prevData, thisWidth, thisHeight, thisData); tex->mipmap |= (1 << i); diff --git a/GL/texture.c b/GL/texture.c index eb79898..e8cdeb9 100644 --- a/GL/texture.c +++ b/GL/texture.c @@ -31,7 +31,7 @@ static TexturePalette* _initTexturePalette() { TexturePalette* palette = (TexturePalette*) malloc(sizeof(TexturePalette)); assert(palette); - sq_clr(palette, (sizeof(TexturePalette) & 0xfffffffc) + 4); + memset(palette, 0x0, sizeof(TexturePalette)); palette->bank = -1; return palette; } @@ -165,39 +165,39 @@ static GLuint _glGetMipmapDataOffset(TextureObject* obj, GLuint level) { } if(obj->isPaletted){ - switch(size >> level) { + switch(size >> level){ case 1024: - offset = 0x15556; + offset = 0x55558; break; case 512: - offset = 0x05556; + offset = 0x15558; break; case 256: - offset = 0x01556; + offset = 0x05558; break; case 128: - offset = 0x00556; + offset = 0x01558; break; case 64: - offset = 0x00156; + offset = 0x00558; break; case 32: - offset = 0x00056; + offset = 0x00158; break; case 16: - offset = 0x00016; + offset = 0x00058; break; case 8: - offset = 0x00006; + offset = 0x00018; break; case 4: - offset = 0x00002; + offset = 0x00008; break; case 2: - offset = 0x00001; + offset = 0x00004; break; case 1: - offset = 0x00000; + offset = 0x00003; break; } } else { @@ -1026,6 +1026,10 @@ void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalFormat, if(format == GL_COLOR_INDEX) { /* Don't convert color indexes */ needsConversion = GL_FALSE; + + if(type == GL_UNSIGNED_BYTE_TWID_KOS) { + needsTwiddling = GL_FALSE; + } } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_4_4_4_4_REV && internalFormat == GL_RGBA) { needsConversion = GL_FALSE; } else if(format == GL_BGRA && type == GL_UNSIGNED_SHORT_1_5_5_5_REV && internalFormat == GL_RGBA) { diff --git a/samples/paletted/main.c b/samples/paletted/main.c index 3cbce97..d520ba2 100644 --- a/samples/paletted/main.c +++ b/samples/paletted/main.c @@ -123,6 +123,7 @@ void LoadGLTextures() { // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself. glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, image1->width, image1->height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE_TWID_KOS, image1->data); + glGenerateMipmapEXT(GL_TEXTURE_2D); } /* A general OpenGL initialization function. Sets all of the initial parameters. */ @@ -130,7 +131,7 @@ void InitGL(int Width, int Height) // We call this right after our OpenG { LoadGLTextures(); glEnable(GL_TEXTURE_2D); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // This Will Clear The Background Color To Black + glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClearDepth(1.0); // Enables Clearing Of The Depth Buffer glDepthFunc(GL_LESS); // The Type Of Depth Test To Do glEnable(GL_DEPTH_TEST); // Enables Depth Testing From 2aafbfb5713dd98300dd301b20b06620d7d77d74 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Thu, 26 Sep 2019 09:46:25 +0100 Subject: [PATCH 31/64] Actually enable mipmapping in the paletted sample --- samples/paletted/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/paletted/main.c b/samples/paletted/main.c index d520ba2..5def756 100644 --- a/samples/paletted/main.c +++ b/samples/paletted/main.c @@ -117,8 +117,8 @@ void LoadGLTextures() { glGenTextures(1, &texture[0]); glBindTexture(GL_TEXTURE_2D, texture[0]); // 2d texture (x and y size) - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR); // scale linearly when image bigger than texture - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR); // scale linearly when image smalled than texture + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); // 2d texture, level of detail 0 (normal), 3 components (red, green, blue), x size from image, y size from image, // border 0 (normal), rgb color data, unsigned byte data, and finally the data itself. From d7e424a766528e6ab184148cd11ffe95f0196283 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 30 Sep 2019 09:14:43 +0100 Subject: [PATCH 32/64] Initial implementation of a fast-path (currently some corruption) --- GL/draw.c | 98 ++++++++++++++++++++++++++++++++++++++++--------- GL/immediate.c | 89 ++++++++++++++++++++++++-------------------- GL/private.h | 2 + GL/util.c | 15 ++++++++ include/glkos.h | 14 +++++++ 5 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 GL/util.c diff --git a/GL/draw.c b/GL/draw.c index d3b00fe..807d43a 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -19,7 +19,7 @@ static AttribPointer DIFFUSE_POINTER; static GLuint ENABLED_VERTEX_ATTRIBUTES = 0; static GLubyte ACTIVE_CLIENT_TEXTURE = 0; - +static GLboolean FAST_PATH_ENABLED = GL_FALSE; #define ITERATE(count) \ GLuint i = count; \ @@ -55,6 +55,42 @@ void _glInitAttributePointers() { NORMAL_POINTER.size = 3; } +static GLboolean _glIsVertexDataFastPathCompatible() { + /* + * We provide a "fast path" if vertex data is provided in + * exactly the right format that matches what the PVR can handle. + * This function returns true if all the requirements are met. + */ + + /* + * At least these attributes need to be enabled, because we're not going to do any checking + * in the loop + */ + if((ENABLED_VERTEX_ATTRIBUTES & VERTEX_ENABLED_FLAG) != VERTEX_ENABLED_FLAG) return GL_FALSE; + if((ENABLED_VERTEX_ATTRIBUTES & UV_ENABLED_FLAG) != UV_ENABLED_FLAG) return GL_FALSE; + if((ENABLED_VERTEX_ATTRIBUTES & DIFFUSE_ENABLED_FLAG) != DIFFUSE_ENABLED_FLAG) return GL_FALSE; + + // All 3 attribute types must have a stride of 32 + if(VERTEX_POINTER.stride != 32) return GL_FALSE; + if(UV_POINTER.stride != 32) return GL_FALSE; + if(DIFFUSE_POINTER.stride != 32) return GL_FALSE; + + // UV must follow vertex, diffuse must follow UV + if((UV_POINTER.ptr - VERTEX_POINTER.ptr) != sizeof(GLfloat) * 3) return GL_FALSE; + if((DIFFUSE_POINTER.ptr - UV_POINTER.ptr) != sizeof(GLfloat) * 2) return GL_FALSE; + + if(VERTEX_POINTER.type != GL_FLOAT) return GL_FALSE; + if(VERTEX_POINTER.size != 3) return GL_FALSE; + + if(UV_POINTER.type != GL_FLOAT) return GL_FALSE; + if(UV_POINTER.size != 2) return GL_FALSE; + + if(DIFFUSE_POINTER.type != GL_UNSIGNED_BYTE) return GL_FALSE; + if(DIFFUSE_POINTER.size != 4) return GL_FALSE; + + return GL_TRUE; +} + static inline GLuint byte_size(GLenum type) { switch(type) { case GL_BYTE: return sizeof(GLbyte); @@ -304,8 +340,8 @@ static void _readVertexData4ubARGB(const GLubyte* input, GLuint count, GLubyte s output[B8IDX] = input[2]; output[A8IDX] = input[3]; - input = (GLubyte*) (((GLubyte*) input) + stride); - output = (GLubyte*) (((GLubyte*) output) + sizeof(Vertex)); + input += stride; + output += sizeof(Vertex); } } @@ -712,7 +748,7 @@ static inline void _readDiffuseData(const GLuint first, const GLuint count, Vert } const GLubyte cstride = (DIFFUSE_POINTER.stride) ? DIFFUSE_POINTER.stride : DIFFUSE_POINTER.size * byte_size(DIFFUSE_POINTER.type); - const void* cptr = ((GLubyte*) DIFFUSE_POINTER.ptr + (first * cstride)); + const void* cptr = ((GLubyte*) DIFFUSE_POINTER.ptr) + (first * cstride); if(DIFFUSE_POINTER.size == 3) { switch(DIFFUSE_POINTER.type) { @@ -773,13 +809,35 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei Vertex* start = _glSubmissionTargetStart(target); - _readPositionData(first, count, start); - profiler_checkpoint("positions"); + if(FAST_PATH_ENABLED) { + /* Copy the pos, uv and color directly in one go */ + const GLfloat* pos = VERTEX_POINTER.ptr; + Vertex* it = start; + ITERATE(count) { + it->flags = PVR_CMD_VERTEX; - _readDiffuseData(first, count, start); - profiler_checkpoint("diffuse"); + memcpy(it->xyz, pos, sizeof(GLfloat) * 3); + memcpy(it->uv, pos + 3, sizeof(GLfloat) * 2); + memcpy(it->bgra, pos + 5, sizeof(GLubyte) * 4); + pos += 32 / sizeof(GLfloat); + it++; + } + } else { + _readPositionData(first, count, start); + profiler_checkpoint("positions"); - if(doTexture) _readUVData(first, count, start); + _readDiffuseData(first, count, start); + profiler_checkpoint("diffuse"); + + if(doTexture) _readUVData(first, count, start); + + Vertex* it = _glSubmissionTargetStart(target); + + ITERATE(count) { + it->flags = PVR_CMD_VERTEX; + ++it; + } + } VertexExtra* ve = aligned_vector_at(target->extras, 0); @@ -787,15 +845,6 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei if(doTexture && doMultitexture) _readSTData(first, count, ve); profiler_checkpoint("others"); - Vertex* it = _glSubmissionTargetStart(target); - - ITERATE(count) { - it->flags = PVR_CMD_VERTEX; - ++it; - } - - profiler_checkpoint("flags"); - // Drawing arrays switch(mode) { case GL_TRIANGLES: @@ -1292,6 +1341,11 @@ void APIENTRY glClientActiveTextureARB(GLenum texture) { ACTIVE_CLIENT_TEXTURE = (texture == GL_TEXTURE1_ARB) ? 1 : 0; } +GLboolean _glRecalcFastPath() { + FAST_PATH_ENABLED = _glIsVertexDataFastPathCompatible(); + return FAST_PATH_ENABLED; +} + void APIENTRY glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) { TRACE(); @@ -1307,6 +1361,8 @@ void APIENTRY glTexCoordPointer(GLint size, GLenum type, GLsizei stride, cons tointer->stride = stride; tointer->type = type; tointer->size = size; + + _glRecalcFastPath(); } void APIENTRY glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) { @@ -1322,6 +1378,8 @@ void APIENTRY glVertexPointer(GLint size, GLenum type, GLsizei stride, const VERTEX_POINTER.stride = stride; VERTEX_POINTER.type = type; VERTEX_POINTER.size = size; + + _glRecalcFastPath(); } void APIENTRY glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) { @@ -1337,6 +1395,8 @@ void APIENTRY glColorPointer(GLint size, GLenum type, GLsizei stride, const G DIFFUSE_POINTER.stride = stride; DIFFUSE_POINTER.type = type; DIFFUSE_POINTER.size = size; + + _glRecalcFastPath(); } void APIENTRY glNormalPointer(GLenum type, GLsizei stride, const GLvoid * pointer) { @@ -1346,4 +1406,6 @@ void APIENTRY glNormalPointer(GLenum type, GLsizei stride, const GLvoid * poin NORMAL_POINTER.stride = stride; NORMAL_POINTER.type = type; NORMAL_POINTER.size = 3; + + _glRecalcFastPath(); } diff --git a/GL/immediate.c b/GL/immediate.c index d95c887..eceb8e2 100644 --- a/GL/immediate.c +++ b/GL/immediate.c @@ -7,8 +7,12 @@ * 3. This is entirely untested. */ +#include +#include + #include "../include/gl.h" #include "../include/glext.h" +#include "../include/glkos.h" #include "profiler.h" #include "private.h" @@ -17,18 +21,14 @@ static GLboolean IMMEDIATE_MODE_ACTIVE = GL_FALSE; static GLenum ACTIVE_POLYGON_MODE = GL_TRIANGLES; static AlignedVector VERTICES; -static AlignedVector COLOURS; -static AlignedVector UV_COORDS; static AlignedVector ST_COORDS; static AlignedVector NORMALS; - static GLfloat NORMAL[3] = {0.0f, 0.0f, 1.0f}; static GLubyte COLOR[4] = {255, 255, 255, 255}; static GLfloat UV_COORD[2] = {0.0f, 0.0f}; static GLfloat ST_COORD[2] = {0.0f, 0.0f}; - static AttribPointer VERTEX_ATTRIB; static AttribPointer DIFFUSE_ATTRIB; static AttribPointer UV_ATTRIB; @@ -36,42 +36,38 @@ static AttribPointer ST_ATTRIB; static AttribPointer NORMAL_ATTRIB; void _glInitImmediateMode(GLuint initial_size) { - aligned_vector_init(&VERTICES, sizeof(GLfloat)); - aligned_vector_init(&COLOURS, sizeof(GLubyte)); - aligned_vector_init(&UV_COORDS, sizeof(GLfloat)); + aligned_vector_init(&VERTICES, sizeof(GLVertexKOS)); aligned_vector_init(&ST_COORDS, sizeof(GLfloat)); aligned_vector_init(&NORMALS, sizeof(GLfloat)); aligned_vector_reserve(&VERTICES, initial_size); - aligned_vector_reserve(&COLOURS, initial_size); - aligned_vector_reserve(&UV_COORDS, initial_size); - aligned_vector_reserve(&ST_COORDS, initial_size); - aligned_vector_reserve(&NORMALS, initial_size); + aligned_vector_reserve(&ST_COORDS, initial_size * 2); + aligned_vector_reserve(&NORMALS, initial_size * 3); - VERTEX_ATTRIB.ptr = VERTICES.data; + VERTEX_ATTRIB.ptr = VERTICES.data + sizeof(uint32_t); VERTEX_ATTRIB.size = 3; VERTEX_ATTRIB.type = GL_FLOAT; - VERTEX_ATTRIB.stride = 0; + VERTEX_ATTRIB.stride = 32; - DIFFUSE_ATTRIB.ptr = COLOURS.data; - DIFFUSE_ATTRIB.size = 4; - DIFFUSE_ATTRIB.type = GL_UNSIGNED_BYTE; - DIFFUSE_ATTRIB.stride = 0; - - UV_ATTRIB.ptr = UV_COORDS.data; - UV_ATTRIB.stride = 0; + UV_ATTRIB.ptr = VERTEX_ATTRIB.ptr + (sizeof(GLfloat) * 3); + UV_ATTRIB.stride = 32; UV_ATTRIB.type = GL_FLOAT; UV_ATTRIB.size = 2; - ST_ATTRIB.ptr = ST_COORDS.data; - ST_ATTRIB.stride = 0; - ST_ATTRIB.type = GL_FLOAT; - ST_ATTRIB.size = 2; + DIFFUSE_ATTRIB.ptr = VERTEX_ATTRIB.ptr + (sizeof(GLfloat) * 5); + DIFFUSE_ATTRIB.size = 4; + DIFFUSE_ATTRIB.type = GL_UNSIGNED_BYTE; + DIFFUSE_ATTRIB.stride = 32; NORMAL_ATTRIB.ptr = NORMALS.data; NORMAL_ATTRIB.stride = 0; NORMAL_ATTRIB.type = GL_FLOAT; NORMAL_ATTRIB.size = 3; + + ST_ATTRIB.ptr = ST_COORDS.data; + ST_ATTRIB.stride = 0; + ST_ATTRIB.type = GL_FLOAT; + ST_ATTRIB.size = 2; } GLubyte _glCheckImmediateModeInactive(const char* func) { @@ -146,17 +142,23 @@ void APIENTRY glColor3fv(const GLfloat* v) { } void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { - aligned_vector_reserve(&VERTICES, VERTICES.size + 3); - aligned_vector_push_back(&VERTICES, &x, 1); - aligned_vector_push_back(&VERTICES, &y, 1); - aligned_vector_push_back(&VERTICES, &z, 1); + GLVertexKOS* vert = aligned_vector_extend(&VERTICES, 1); + GLfloat* st = aligned_vector_extend(&ST_COORDS, 2); + GLfloat* n = aligned_vector_extend(&NORMALS, 3); + vert->x = x; + vert->y = y; + vert->z = z; + vert->u = UV_COORD[0]; + vert->v = UV_COORD[1]; - /* Push back the stashed colour, normal and uv_coordinate */ - aligned_vector_push_back(&COLOURS, COLOR, 4); - aligned_vector_push_back(&UV_COORDS, UV_COORD, 2); - aligned_vector_push_back(&ST_COORDS, ST_COORD, 2); - aligned_vector_push_back(&NORMALS, NORMAL, 3); + vert->rgba[0] = COLOR[0]; + vert->rgba[1] = COLOR[1]; + vert->rgba[2] = COLOR[2]; + vert->rgba[3] = COLOR[3]; + + memcpy(st, ST_COORD, sizeof(GLfloat) * 2); + memcpy(n, NORMAL, sizeof(GLfloat) * 3); } void APIENTRY glVertex3fv(const GLfloat* v) { @@ -218,11 +220,12 @@ void APIENTRY glEnd() { IMMEDIATE_MODE_ACTIVE = GL_FALSE; /* Resizing could have invalidated these pointers */ - VERTEX_ATTRIB.ptr = VERTICES.data; - DIFFUSE_ATTRIB.ptr = COLOURS.data; - UV_ATTRIB.ptr = UV_COORDS.data; - ST_ATTRIB.ptr = ST_COORDS.data; + VERTEX_ATTRIB.ptr = VERTICES.data + sizeof(uint32_t); + UV_ATTRIB.ptr = VERTEX_ATTRIB.ptr + (sizeof(GLfloat) * 3); + DIFFUSE_ATTRIB.ptr = VERTEX_ATTRIB.ptr + (sizeof(GLfloat) * 5); + NORMAL_ATTRIB.ptr = NORMALS.data; + ST_ATTRIB.ptr = ST_COORDS.data; GLuint* attrs = _glGetEnabledAttributes(); @@ -250,7 +253,15 @@ void APIENTRY glEnd() { *attrs = ~0; // Enable everything - glDrawArrays(ACTIVE_POLYGON_MODE, 0, VERTICES.size / 3); +#ifndef NDEBUG + _glRecalcFastPath(); +#else + // Immediate mode should always activate the fast path + GLboolean fastPathEnabled = _glRecalcFastPath(); + assert(fastPathEnabled); +#endif + + glDrawArrays(ACTIVE_POLYGON_MODE, 0, VERTICES.size); /* Restore everything */ *vattr = vptr; @@ -263,8 +274,6 @@ void APIENTRY glEnd() { /* Clear arrays for next polys */ aligned_vector_clear(&VERTICES); - aligned_vector_clear(&COLOURS); - aligned_vector_clear(&UV_COORDS); aligned_vector_clear(&ST_COORDS); aligned_vector_clear(&NORMALS); diff --git a/GL/private.h b/GL/private.h index 7fb68b8..35d3407 100644 --- a/GL/private.h +++ b/GL/private.h @@ -316,6 +316,8 @@ GLboolean _glIsLightingEnabled(); GLboolean _glIsLightEnabled(GLubyte light); GLboolean _glIsColorMaterialEnabled(); +GLboolean _glRecalcFastPath(); + typedef struct { float xyz[3]; float n[3]; diff --git a/GL/util.c b/GL/util.c new file mode 100644 index 0000000..cef1d67 --- /dev/null +++ b/GL/util.c @@ -0,0 +1,15 @@ +#include "../include/glkos.h" + +void APIENTRY glVertexPackColor3fKOS(GLVertexKOS* vertex, float r, float g, float b) { + vertex->color[3] = 255; + vertex->color[2] = (r * 255.0f); + vertex->color[1] = (g * 255.0f); + vertex->color[0] = (b * 255.0f); +} + +void APIENTRY glVertexPackColor4fKOS(GLVertexKOS* vertex, float r, float g, float b, float a) { + vertex->color[3] = (a * 255.0f); + vertex->color[2] = (r * 255.0f); + vertex->color[1] = (g * 255.0f); + vertex->color[0] = (b * 255.0f); +} diff --git a/include/glkos.h b/include/glkos.h index 34435bd..082895c 100644 --- a/include/glkos.h +++ b/include/glkos.h @@ -53,6 +53,20 @@ typedef struct { } GLdcConfig; +typedef struct { + GLuint padding0; + GLfloat x; + GLfloat y; + GLfloat z; + GLfloat u; + GLfloat v; + GLubyte rgba[4]; + GLuint padding1; +} GLVertexKOS; + +GLAPI void APIENTRY glVertexPackColor3fKOS(GLVertexKOS* vertex, float r, float g, float b); +GLAPI void APIENTRY glVertexPackColor4fKOS(GLVertexKOS* vertex, float r, float g, float b, float a); + GLAPI void APIENTRY glKosInitConfig(GLdcConfig* config); /* Usage: From f59e9bf56ec2c144311d0d1c1c7c01dcd1f6d403 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 30 Sep 2019 21:59:11 +0100 Subject: [PATCH 33/64] Implement GL_ARB_vertex_array_bgra --- GL/draw.c | 69 +++++++++++++++++++++++++++++++++++++++++++------ GL/immediate.c | 10 +++---- GL/state.c | 2 +- include/glkos.h | 2 +- 4 files changed, 68 insertions(+), 15 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index 807d43a..b1f98ef 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -86,7 +86,9 @@ static GLboolean _glIsVertexDataFastPathCompatible() { if(UV_POINTER.size != 2) return GL_FALSE; if(DIFFUSE_POINTER.type != GL_UNSIGNED_BYTE) return GL_FALSE; - if(DIFFUSE_POINTER.size != 4) return GL_FALSE; + + /* BGRA is the required color order */ + if(DIFFUSE_POINTER.size != GL_BGRA) return GL_FALSE; return GL_TRUE; } @@ -381,6 +383,30 @@ static void _readVertexData3ubARGB(const GLubyte* input, GLuint count, GLubyte s } } +static void _readVertexData4ubRevARGB(const GLubyte* input, GLuint count, GLubyte stride, GLubyte* output) { + ITERATE(count) { + output[0] = input[0]; + output[1] = input[1]; + output[2] = input[2]; + output[3] = input[3]; + + input += stride; + output += sizeof(Vertex); + } +} + +static void _readVertexData4fRevARGB(const float* input, GLuint count, GLubyte stride, GLubyte* output) { + ITERATE(count) { + output[0] = (GLubyte) clamp(input[0] * 255.0f, 0, 255); + output[1] = (GLubyte) clamp(input[1] * 255.0f, 0, 255); + output[2] = (GLubyte) clamp(input[2] * 255.0f, 0, 255); + output[3] = (GLubyte) clamp(input[3] * 255.0f, 0, 255); + + input = (float*) (((GLubyte*) input) + stride); + output += sizeof(Vertex); + } +} + static void _fillWithNegZVE(GLuint count, GLfloat* output) { ITERATE(count) { output[0] = output[1] = 0.0f; @@ -430,6 +456,14 @@ static void _readVertexData4uiARGB(const GLuint* input, GLuint count, GLubyte st assert(0 && "Not Implemented"); } +static void _readVertexData4usRevARGB(const GLushort* input, GLuint count, GLubyte stride, GLubyte* output) { + assert(0 && "Not Implemented"); +} + +static void _readVertexData4uiRevARGB(const GLuint* input, GLuint count, GLubyte stride, GLubyte* output) { + assert(0 && "Not Implemented"); +} + GLuint* _glGetEnabledAttributes() { return &ENABLED_VERTEX_ATTRIBUTES; } @@ -792,7 +826,28 @@ static inline void _readDiffuseData(const GLuint first, const GLuint count, Vert default: assert(0 && "Not Implemented"); } - } else { + } else if(DIFFUSE_POINTER.size == GL_BGRA) { + switch(DIFFUSE_POINTER.type) { + case GL_DOUBLE: + case GL_FLOAT: + _readVertexData4fRevARGB(cptr, count, cstride, output[0].bgra); + break; + case GL_BYTE: + case GL_UNSIGNED_BYTE: + _readVertexData4ubRevARGB(cptr, count, cstride, output[0].bgra); + break; + case GL_SHORT: + case GL_UNSIGNED_SHORT: + _readVertexData4usRevARGB(cptr, count, cstride, output[0].bgra); + break; + case GL_INT: + case GL_UNSIGNED_INT: + _readVertexData4uiRevARGB(cptr, count, cstride, output[0].bgra); + break; + default: + assert(0 && "Not Implemented"); + } + }else { assert(0 && "Not Implemented"); } } @@ -802,6 +857,7 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei /* Read from the client buffers and generate an array of ClipVertices */ TRACE(); + static const uint32_t FAST_PATH_BYTE_SIZE = (sizeof(GLfloat) * 3) + (sizeof(GLfloat) * 2) + (sizeof(GLubyte) * 4); const GLsizei istride = byte_size(type); if(!indices) { @@ -815,12 +871,9 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei Vertex* it = start; ITERATE(count) { it->flags = PVR_CMD_VERTEX; - - memcpy(it->xyz, pos, sizeof(GLfloat) * 3); - memcpy(it->uv, pos + 3, sizeof(GLfloat) * 2); - memcpy(it->bgra, pos + 5, sizeof(GLubyte) * 4); - pos += 32 / sizeof(GLfloat); + memcpy(it->xyz, pos, FAST_PATH_BYTE_SIZE); it++; + pos += 32 / sizeof(GLfloat); } } else { _readPositionData(first, count, start); @@ -1385,7 +1438,7 @@ void APIENTRY glVertexPointer(GLint size, GLenum type, GLsizei stride, const void APIENTRY glColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid * pointer) { TRACE(); - if(size != 3 && size != 4) { + if(size != 3 && size != 4 && size != GL_BGRA) { _glKosThrowError(GL_INVALID_VALUE, __func__); _glKosPrintError(); return; diff --git a/GL/immediate.c b/GL/immediate.c index eceb8e2..ae3ad7a 100644 --- a/GL/immediate.c +++ b/GL/immediate.c @@ -55,7 +55,7 @@ void _glInitImmediateMode(GLuint initial_size) { UV_ATTRIB.size = 2; DIFFUSE_ATTRIB.ptr = VERTEX_ATTRIB.ptr + (sizeof(GLfloat) * 5); - DIFFUSE_ATTRIB.size = 4; + DIFFUSE_ATTRIB.size = GL_BGRA; /* Flipped color order */ DIFFUSE_ATTRIB.type = GL_UNSIGNED_BYTE; DIFFUSE_ATTRIB.stride = 32; @@ -152,10 +152,10 @@ void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { vert->u = UV_COORD[0]; vert->v = UV_COORD[1]; - vert->rgba[0] = COLOR[0]; - vert->rgba[1] = COLOR[1]; - vert->rgba[2] = COLOR[2]; - vert->rgba[3] = COLOR[3]; + vert->bgra[R8IDX] = COLOR[0]; + vert->bgra[G8IDX] = COLOR[1]; + vert->bgra[B8IDX] = COLOR[2]; + vert->bgra[A8IDX] = COLOR[3]; memcpy(st, ST_COORD, sizeof(GLfloat) * 2); memcpy(n, NORMAL, sizeof(GLfloat) * 3); diff --git a/GL/state.c b/GL/state.c index ef026a5..239f5d9 100644 --- a/GL/state.c +++ b/GL/state.c @@ -671,7 +671,7 @@ const GLubyte *glGetString(GLenum name) { return (const GLubyte*) "1.2 (partial) - GLdc 1.1"; case GL_EXTENSIONS: - return (const GLubyte*) "GL_ARB_framebuffer_object, GL_ARB_multitexture, GL_ARB_texture_rg, GL_EXT_paletted_texture, GL_EXT_shared_texture_palette, GL_KOS_multiple_shared_palette"; + return (const GLubyte*) "GL_ARB_framebuffer_object, GL_ARB_multitexture, GL_ARB_texture_rg, GL_EXT_paletted_texture, GL_EXT_shared_texture_palette, GL_KOS_multiple_shared_palette, GL_ARB_vertex_array_bgra"; } return (const GLubyte*) "GL_KOS_ERROR: ENUM Unsupported\n"; diff --git a/include/glkos.h b/include/glkos.h index 082895c..5af510b 100644 --- a/include/glkos.h +++ b/include/glkos.h @@ -60,7 +60,7 @@ typedef struct { GLfloat z; GLfloat u; GLfloat v; - GLubyte rgba[4]; + GLubyte bgra[4]; GLuint padding1; } GLVertexKOS; From 805c68e97333e2ee60d4c15abfd4616f2eed141f Mon Sep 17 00:00:00 2001 From: Hayden Kowalchuk Date: Fri, 4 Oct 2019 10:29:54 -0400 Subject: [PATCH 34/64] remove single minus sign --- GL/matrix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GL/matrix.c b/GL/matrix.c index 4037ca1..28b8177 100644 --- a/GL/matrix.c +++ b/GL/matrix.c @@ -199,7 +199,7 @@ void APIENTRY glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) { 0.0f, 0.0f, 0.0f, 1.0f }; - float r = DEG2RAD * -angle; + float r = DEG2RAD * angle; float c = cos(r); float s = sin(r); From 9f3adc099ade682bb5f49bc2a9bf9f5be33a9fdc Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Tue, 12 Nov 2019 17:28:39 +0000 Subject: [PATCH 35/64] Fix a bad reference --- GL/util.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/GL/util.c b/GL/util.c index cef1d67..eaf2e17 100644 --- a/GL/util.c +++ b/GL/util.c @@ -1,15 +1,15 @@ #include "../include/glkos.h" void APIENTRY glVertexPackColor3fKOS(GLVertexKOS* vertex, float r, float g, float b) { - vertex->color[3] = 255; - vertex->color[2] = (r * 255.0f); - vertex->color[1] = (g * 255.0f); - vertex->color[0] = (b * 255.0f); + vertex->bgra[3] = 255; + vertex->bgra[2] = (r * 255.0f); + vertex->bgra[1] = (g * 255.0f); + vertex->bgra[0] = (b * 255.0f); } void APIENTRY glVertexPackColor4fKOS(GLVertexKOS* vertex, float r, float g, float b, float a) { - vertex->color[3] = (a * 255.0f); - vertex->color[2] = (r * 255.0f); - vertex->color[1] = (g * 255.0f); - vertex->color[0] = (b * 255.0f); + vertex->bgra[3] = (a * 255.0f); + vertex->bgra[2] = (r * 255.0f); + vertex->bgra[1] = (g * 255.0f); + vertex->bgra[0] = (b * 255.0f); } From 8654ff24a2b9601b888e2282ed44c7e782c8cd39 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Thu, 14 Nov 2019 10:04:15 +0000 Subject: [PATCH 36/64] Add light sample and fix lighting bug --- GL/lighting.c | 3 +- samples/Makefile | 1 + samples/lights/Makefile | 29 +++ samples/lights/main.c | 276 +++++++++++++++++++++++++++++ samples/lights/romdisk/NeHe.bmp | Bin 0 -> 196662 bytes samples/lights/romdisk/PLACEHOLDER | 0 6 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 samples/lights/Makefile create mode 100644 samples/lights/main.c create mode 100644 samples/lights/romdisk/NeHe.bmp create mode 100644 samples/lights/romdisk/PLACEHOLDER diff --git a/GL/lighting.c b/GL/lighting.c index 2aa6188..6283350 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -388,7 +388,9 @@ void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex) { VPpli[2] /= VPpliL; GLfloat ndotVPpli; + vec3f_dot(n[0], n[1], n[2], VPpli[0], VPpli[1], VPpli[2], ndotVPpli); + ndotVPpli = (ndotVPpli < 0) ? 0 : ndotVPpli; const GLfloat k0 = light->constant_attenuation; const GLfloat k1 = light->linear_attenuation; @@ -425,4 +427,3 @@ void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex) { vertex->bgra[B8IDX] = (GLubyte)(fminf(final[2] * 255.0f, 255.0f)); vertex->bgra[A8IDX] = (GLubyte)(fminf(final[3] * 255.0f, 255.0f)); } - diff --git a/samples/Makefile b/samples/Makefile index 7fc4b0f..b565d58 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -29,3 +29,4 @@ all: $(KOS_MAKE) -C polygon_offset all $(KOS_MAKE) -C blend_test all $(KOS_MAKE) -C mipmap all + $(KOS_MAKE) -C lights all diff --git a/samples/lights/Makefile b/samples/lights/Makefile new file mode 100644 index 0000000..b268753 --- /dev/null +++ b/samples/lights/Makefile @@ -0,0 +1,29 @@ +TARGET = lights.elf +OBJS = main.o + +all: rm-elf $(TARGET) + +include $(KOS_BASE)/Makefile.rules + +clean: + -rm -f $(TARGET) $(OBJS) romdisk.* + +rm-elf: + -rm -f $(TARGET) romdisk.* + +$(TARGET): $(OBJS) romdisk.o + $(KOS_CC) $(KOS_CFLAGS) $(KOS_LDFLAGS) -o $(TARGET) $(KOS_START) \ + $(OBJS) romdisk.o $(OBJEXTRA) -lm -lkosutils $(KOS_LIBS) + +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) diff --git a/samples/lights/main.c b/samples/lights/main.c new file mode 100644 index 0000000..e02d85a --- /dev/null +++ b/samples/lights/main.c @@ -0,0 +1,276 @@ +#include + +#include "gl.h" +#include "glu.h" +#include "glkos.h" + +extern uint8 romdisk[]; +KOS_INIT_ROMDISK(romdisk); + + +float xrot, yrot, zrot; + + +int texture[1]; + + +struct Image { + unsigned long sizeX; + unsigned long sizeY; + char *data; +}; +typedef struct Image Image; + + + +int ImageLoad(char *filename, Image *image) { + FILE *file; + unsigned long size; + unsigned long i; + unsigned short int planes; + unsigned short int bpp; + char temp; + + + if ((file = fopen(filename, "rb"))==NULL) + { + printf("File Not Found : %s\n",filename); + return 0; + } + + + fseek(file, 18, SEEK_CUR); + + + if ((i = fread(&image->sizeX, 4, 1, file)) != 1) { + printf("Error reading width from %s.\n", filename); + return 0; + } + printf("Width of %s: %lu\n", filename, image->sizeX); + + + if ((i = fread(&image->sizeY, 4, 1, file)) != 1) { + printf("Error reading height from %s.\n", filename); + return 0; + } + printf("Height of %s: %lu\n", filename, image->sizeY); + + + size = image->sizeX * image->sizeY * 3; + + + if ((fread(&planes, 2, 1, file)) != 1) { + printf("Error reading planes from %s.\n", filename); + return 0; + } + if (planes != 1) { + printf("Planes from %s is not 1: %u\n", filename, planes); + return 0; + } + + + if ((i = fread(&bpp, 2, 1, file)) != 1) { + printf("Error reading bpp from %s.\n", filename); + return 0; + } + if (bpp != 24) { + printf("Bpp from %s is not 24: %u\n", filename, bpp); + return 0; + } + + + fseek(file, 24, SEEK_CUR); + + + image->data = (char *) malloc(size); + if (image->data == NULL) { + printf("Error allocating memory for color-corrected image data"); + return 0; + } + + if ((i = fread(image->data, size, 1, file)) != 1) { + printf(stderr, "Error reading image data from %s.\n", filename); + return 0; + } + + for (i=0;idata[i]; + image->data[i] = image->data[i+2]; + image->data[i+2] = temp; + } + + + return 1; +} + + +void LoadGLTextures() { + + Image *image1; + + + image1 = (Image *) malloc(sizeof(Image)); + if (image1 == NULL) { + printf("Error allocating space for image"); + exit(0); + } + + if (!ImageLoad("/rd/NeHe.bmp", image1)) { + exit(1); + } + + glGenTextures(1, &texture[0]); + glBindTexture(GL_TEXTURE_2D, texture[0]); + + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + + + + glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data); + + glGenerateMipmapEXT(GL_TEXTURE_2D); +}; + + +void InitGL(int Width, int Height) +{ + LoadGLTextures(); + glEnable(GL_TEXTURE_2D); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearDepth(1.0); + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); + glShadeModel(GL_SMOOTH); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); + + glMatrixMode(GL_MODELVIEW); + + glEnable(GL_COLOR_MATERIAL); + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + + glClearColor(0.5, 0.5, 0.5, 0.5); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + + // Create light components + GLfloat ambientLight[] = { 0.2f, 0.2f, 0.2f, 1.0f }; + GLfloat diffuseLight[] = { 1.0f, 0.0f, 0.0, 1.0f }; + GLfloat specularLight[] = { 0.5f, 0.5f, 0.5f, 1.0f }; + GLfloat position[] = { -1.5f, -1.0f, 0.0f, 0.0f }; + + // Assign created components to GL_LIGHT0 + glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); + glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); + glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight); + glLightfv(GL_LIGHT0, GL_POSITION, position); + + glDisable(GL_TEXTURE_2D); +} + + +void ReSizeGLScene(int Width, int Height) +{ + if (Height == 0) + Height = 1; + + glViewport(0, 0, Width, Height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); + glMatrixMode(GL_MODELVIEW); +} + +void DrawCube(float x, float z) { + glPushMatrix(); + glTranslatef(x, 0, z); + glColor4f(1, 1, 1, 1); + + glBegin(GL_QUADS); + + + glNormal3f(0, 0, 1); + glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); + glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); + glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); + glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); + + + glNormal3f(0, 0, -1); + glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); + glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); + glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); + glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); + + + glNormal3f(0, 1, 0); + glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); + glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f); + glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f); + glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); + + glNormal3f(0, -1, 0); + glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); + glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); + glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); + glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); + + glNormal3f(1, 0, 0); + glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); + glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); + glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); + glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); + + glNormal3f(-1, 0, 0); + glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); + glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); + glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); + glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); + + glEnd(); + + glPopMatrix(); +} + +void DrawGLScene() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + + + glTranslatef(0.0f, 0.0f, -55.0f); + glRotatef(45, 1, 0, 0); + glTranslatef(0, 0, -30); + + glBindTexture(GL_TEXTURE_2D, texture[0]); + + int x, z; + for(z = -100; z < 100; z += 10) { + for(x = -100; x < 100; x += 10) { + DrawCube(x, z); + } + } + + glKosSwapBuffers(); +} + +int main(int argc, char **argv) +{ + glKosInit(); + + InitGL(640, 480); + ReSizeGLScene(640, 480); + + while(1) { + DrawGLScene(); + } + + return 0; +} diff --git a/samples/lights/romdisk/NeHe.bmp b/samples/lights/romdisk/NeHe.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6b3db10f2b8d60997f7e213255809961985c2ef3 GIT binary patch literal 196662 zcmeFa_j4RccJJHXRabXcwTbrLn`rNWh7EWSpaDP=^xn=4gF|vSepb6HZAiPix=*_D zSN8|KYva8?@x4!>W-t^9atLCFOANaL>}Xb3Wu5%yH&32CdGgfkdPk$zC`X5XdF6{% z{ioIVmH+K~{D0$PjKGJDz`p%rd~%GyFCGE4-~ZyPIj-OsfqfAe^M7Ah#wR}z1jhXT zKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah z#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n z&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV z^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zFV^M7Ah#wR}z1jhXT zKpJ{n&3zFV^M7Ah#wR}z1jhXTKpJ{n&3zF#5dXD59QzhEJ~>9<$RThb{_B4@bUNKT ztEt0oOGW*IMsBuOobQ*0jch&{k{ouu{@uxNn99Dl^1p>(Kj=T@twQhS(Cc)P(_TnN zMxEl~L~X8L?o>0ekk@84|4{@u5dTdkli6$*MbY7Kr2Mvi(r~6LTwhN;xw`zD&#wK$ zZ=d}8-+ui+|LHgX{qKMEufPBLuYdLN*H11#`{>x?tFsT!&s^M`>^Jg0kMxFO>vh^# z(6c(-e|-JIcc0$;`oY%Y%M%Yzl|S9CetvWAtB<#?T|CxnmRwHpg#v>PO(&)~)B91d zfBfy!|NdXT`#=BkyZ`l1zx}sAeEa47%Zn3TpX}hqgwL|pi#)wH_2-|R`@6q<`0s!B z?f?FlzxW^j@Mr)0mtTB*Woy`|*llKuQCp6Q$7eH7t}XxevupqK7tgqW%l_}b`}QAx z^W^f$m2x)yf(-ok?c|4G=$MGCA7;!~W_%AXbie!L%x^!t_wD_wC*~*8QQylCN(F?q z>D*WMPX5hrKK=K<{d21M=+1>+tNikn>Y29_Oug6e!+`=6&6;M)ac;Fl)BMe^AN~8^ z{`UX*x8MKg-#*>hT#N+2m(yX@b+YosjoLRK@BGbgxaQCQ-~aZ9|Ms`PzH#AXF`Imq zgz9;tL7#~Sww9;AynFRGPw#yD(az^LXTP{K`25D~Hy@vQbm#oW!fYZMH0aege!af1 zSN?h+{@ZLem&+9h1TvXSrI0Qr{k5oNIv2V;SATYK_7Bgl(*^$Z_fP)Qw|76fyuLbF zYNUd-s4z?!PIg@Pwrk%$JooJ3jbn@Rkx<~30`30bw42+FgTO4~U7Nvl=x|<-%v?8jmi`|HT(K zA??5Y^|QbF^7g%p>$Ckb{7;76+%?QtRy(2V3svO*>(6ie)9*h2x4-`EyGNI=oSGYU zD)WPuBw8I7?I>@bYsRl1Ykq!v@`r+;K53X&!AkrZPQ*Q@etJNw90$;p>Zl>he=$1b|IrY0o7ykKoU;K~1|FhqJ z^YO#m=X%ZWdtXe}&sU}Ge(B-X;CG*1{O8|&{y+cm*Z<{jzNUw6Y%J_6p?XqwScaYK z?F(zDzK^b*?bLJ8M7mZ;=F&;Q?irL553kJr{#SQDxp{iB*RTt=*A(^opAN)-G=DG{ zg#U>|!k4Ito@P!6RC98(Gb(qM6UA;Q5DY}^^=AF-skLVxU;F6h=845jKX0pL0#Yzu z4HX|=SpEH1pItk*Q!bQV)pPA;U9T88yVk#Sa=zbg%7S}iX6?r5`JLHFKj^7Sa=Yv= z)$2}Q^X%Hxx1Zj-d}bq;PN>zCB&lBrq}Jk z$J5zHASx!aAufA(<2?HR1^ypKel$!x9&e>nF;_M3bT!M7;q3C(_Qj7bokRbt{D=H1 z3Hh{J^ryS_Yp1%6h3mD}d^i|(`#g-l|Md^wyxi`G5lk%ͰF-6LSL>~!sG^Y!B7 z*?M;=la9GOft}+^47Dd#r_}4KVg30DX=YT<_D$8!0U%k669(*NrpfAiU=*I$-U zJtGUcK{Q1AQf4K`-44S-Ow>_KRxVTj)F4%P9#%TG|JC}>u zK)pQ}^jf**tlrbAgyosd%kkR8M8EXW^&PrEG8P6s`XAec?e^DCgp>*^~K+Qz36MAq75vc;n&iGtFY$B^ccv zUo4);yZrr_wp6cFrp{*Ti%B_|$fiI0=n7+SITQA{guSLbr2OdBN~KaS=Ujob)tf6j zJ@H6jqfzLT6PbwHWw-DkXb%C=MI0`hc`>J3?}mn}=j)?0-e4#kl>YJ0Kf@N<6G{DY zNWs)XcrFSM8hAV&$t90smdUcGId!Zxb1t2!hZ3&EsS!i%>5Un#XLzcF^oZBYdMbmZ z#?<9pp%YHHa=!4tq69ztk^j$6E38Hhy=G^wuy$c5TbQ?)?3+_7kMCSgL|wIJx7wMK z;;pDtoSR$gPHe}0(xp?!p4`7QJ!tZ{7pnXw|2+`@>Hd*Oq);g2ayj&X&*ux}2jJ;W{d4EeB~qDWd0UvxU`vR0zqAy_yZE|<%xbjBfFG{OoJ15L>}Ay~~@O=RdNGyABWR?!z?vlp_BrFgIu6uf`QBvAYwKH%NXKdqesulZmv>KigRzq*Ptt0^{J@%7?G>VnJ9lc6i=m+J ztA|&f+&!O)D+=(S$AS2dcB1>2N+now4xT8JjCTl$*-&mZfGo$R} zyioDlwQF4L@rUb6*WJ$M^0AY1o42Bg+(fzXXP@1^eDQ?;xf!v=sA+~Zi+Sy2JD*uN z;f&R7j?mMOPJeN0o;ml%#s&zW{aQ08{mq?jC9rb(R)1>8WVYVExbs(E-R;!#n5M(s z!xb)sDvJnKOFBCfgG8-o%hfNO=renY2b>Ta*+QX^oa&oG-KeB#<%8|1*+98#NmstU zxANKbX^_n4^AzTGd!*FRC}vY0eZ8KoP0V@k`dtUsjuJQVdZ zH#f)FWDYhAkrjtv*(mGEQLEarMxzn#!G9bPK;gE@MM+G$HQh#`KD8-_n^s@-n~!$B zcz9gBURAA3;SVnu1@c^`eh5Gp5F81YF&Pq##h7b2xs>Uyx#dCKzkYi8*|T%1gnUvL)wLp6^sR_yKBpZvvz7TxYpkYs zgq}Rues*Ivl}_*M?DTrQShk)UT}XN4)f1;jtJi|j^wOyL&wu&V@um5_0^j`0f%uQ* zN!{s#baN<#B1)8cna;XhCuP0bZY^DIbOq5h-$_0@--ZA12mL=747h7z{hGJFHPz2A z@7(N8Zrk1RrHz$8{Py$7QA?E;4dAuwhH+gxswwAUrI}?%x@;00&pulJ>dFKVp%qV@ zIFU}L%e{qU_f+0(TUuS8JbpVG$(1XafB4Pg>!&x6u+!Fs&#Y5-#{eH$TWfp8Sd-a}#;KJoq%ZBQMktbQeuKV)#;^()%Cj-2q zn|NHdUi7enAshOiK@(y_Z-bmQI0_!CA?el*x{b=@vdJIu`<juHIg#B+_!EnKYnyVu$t7S$Dm@cpm4ui?1W9th_;-Kw5Apn0@&=I z-CzCU(hy@~ZEcO_2n3VmsU3$cKR4Z&J9#6MUl5(T-#+>1i@P^O;d?Ei7W*cj2jV}N z!+ER=hynj;g>a^ks4ogyDdW|4Dv4^N%5=IPz*XBhk_nPy7J)~eT%a%)Q|UvpyGm#NtGhA(a}z<=rjC015e zP>iucC)QXEnWd>=dGh4-cyZ(~Ie+!^#y6i{vzkrFHZwD07X#t)j9nw;;D0S$Y4k*w z=j?m|Bvt(n$53UF;-tSY1OJPDZLd|y*9K;*@a5$pm+?gUFMvA)Q?xv73s&QjwvlqQ zTg`B?7;#&<=Ila|%OC?CoWt*t^Df8R$Zy=0|FI%JWN0)RXc(7-kGW?v8MYecdeW)3 zCMZEaT0v3ldaK{y4O<<4QBz6#TkT#jmNghn-&~*OkL$UFHbStV&r@)ThZKwgDGf{* zH_#c$03C=d6UtgOVRKgGgZH&w`LB6vbtSsfc+R0(1zj-B+nGsFV&mLU=>a&|Jbezcq z0tuHhZ1IE~nsVIP9}JR(CPv|RH|HpgN7Lg_+UOr|tQ;)OdJL{!-aWa#5$zvy+LL!~ zp8EFLRj(v6Kuku$vJ0WVAZE5?BT(vcHNZxacC5lP}U8qnD8;T0$hu+*2{4WKx zgHFBBn6%jJUtb-mPo#tJM9E&<66=2lkm)~e6qL6{$4Yu1S&qbFds`5l@{Hr#0U zec|Nu{HG{J6Dli9VUHzzNWpkI{SMh^2)$Ns^p^FGoJY_`T$)BXU26@^p@dU!eSBx> z{`Cd;pG(J*PP5q(h5yQo`UE9mmC-;_95v5X81oSu{w#yZFo1-CzwiFXF zP$J_B= zRRXj-R16fRpc(w{)iRCtQ1S-n8Zq?GpsKj#G5%4Z3b(7%zQPP9M6clOudW9hOAcH7 z^7%Eyj`$^jg||MZ%VtdlL~SWz9rU}Ia@Sxoe|uv_)n>FK#HQ=HBZY8j)~RzMIj7pEQ}07Jnz@e4C>{%bxSDGALO2VdUCp*RZ)8L z&!3zBs?1a^s#w}n5to*gsp0e}Ib7DszU`BPCy!2MVqQiQ{2qc3(tE2x_!BZJuFl%P zWU)arm5sDJT?u0NVx??r$!c(CJ=#twnyWX$jfwTC5(~u1pbI3iZ44H#FE>fYd*>f(@E=8i3j$+6 zuQ$qCPt@#|tvdK$DI_b+zU&J<&wn~*y z{xkb1rb3}a(Ho4rLqTRwn5^{BVlt3)*vv-1!=k}LOGo5fz9`8N$>r%6ytHXEAEHcp z3Qdok)C+Y)t9h9(`}FmEB%R5{v(<+uOOH=CxRSn%0x{{$-t5p|i=lESnz`EKoGFwT zb&_6Id{3L9c9>1z0SKomy}461F~G=M4BJYXNVVRWEtPMt!~-r9BMvAT#gIBMy#xL; zb|V$d=S}w`?D6ee`mGMycORprZxJXv3_6u&m@rtVc6*O z7O&nLvA86wM&W-kU8?saPvCj}LsU?O|K4=>HG;hfuu-ov0cQwidWAOAnjP@pY6`gp zO+eJ-;y%%vaQh?KRP4dY+PBwd>{fFr=}m}sv%v%ZeRd6t^2t=%Zuc_~r~Fptbi=B$ zoQ)NMkfP&RamPp#8%{+El{LapbLeb4hBMlol^V-U;@UL)A+fvtrUV1q6ryy8j(!wS+_WrMdO7y=$a z(e0^-M`sVR{>w_@#f3CO0C7|0q@1ujEPBad)_4U?#A^>nV}ezdJ${=(k0Wleo1#qg zKjg=^1HDJfTkAX5Z6=M!u8GN-q}PxPifa{dr(**a%uz-kn@uqJYjy*x{F)9^PQ~O7 z%{F7-T$^Ue=|JS?;&;h^W|}Jh2klCwJ!N;uM7Chw)97dwOgmekbm#IDg||IG!>aG_p+-acbg{1~1%X+~oEq+U+(PPmUDB<=J<`e|5$I z|HFj|Pj;lvreBu-dHyptsrvurxru5I1BhD_e%b!Vlc1O%xXW~|kO?N4Vl}VQ zbxK}lOwVf!Iwt(bu6T`LuLm|EYjMb9lQ1w*6q<@@g~9IEc`6RA+iTbOL`^*8wwXP$ zQ*M{ytDV5J^Bsp-S53I^&04ii_)j+px@}H(z$b}3&hBt>&53EndknFsrl#?&BRbaXR<2y?;!-RGrtK*?eWEg-PF4q zC7l!U(*>eIkI(3oMcYIzK2emOoM||W#(F|b*kqfQF+hV!4mi$_a9%r^>A(bdTF?64tHzubd8nD-6d~6-M&ynaQiG) zd?^Hqi0#pK#ceS(6SkzyW7Apj|IkUjlHHS^ zi&d(%bSmj^x&46v^C^6T(R}Bf>Hl}ef6N}puP{(!3@9sKPL@^`{!8XeHW$lO;J?vi zVQNVicys>09@u81UMyr~w_BZh`N@fb zUof^4=A;##nw+~rx+T$V6x%T)QLM+Ns*koRPtPlhj2xI%1h=qG6puw^$t75cy45o( z<@1Fa!Nj#nDiPzq4C^+%r4!ZHf=aK)=Hf=;Fd6k`Yrc;*b1WOF1-|*`d(MBW-NC$u zIf7av^ba9$Oo73Q`2u3*TUE5 z17WukAA6wVz2*Nzzm@6C;v>aD^wr%ZwNkVKWP|?g9i^75r_+7Ww6<7 z@+%DCvOuzi|L0Y6Os-)l!DjfQtbe^T{?o6K9JYvBM~DS;t6AZ{RpW7(B9RzTNss}L z8KyN|0L1p?|3QI8&oHHe;gBEO5^OaZttC)3Ito#jviRjg^9Mr1q^=kdR$G3HNq=`W z5we?yNo~gF7fgzTl3;YYq@vHTP*H4^E5}-w=9C~A#&G1MU!&RZ+F`BX;$|#3zI&+d zKDq!?9-YAq?FR`WRTPVh39{X0u?nK>_J)Fijk>(m5s1_}5c#?I{p5c+sGA&i5`!g{ z_gNs=i)KV%@JH2y3GG5h;9yY#EEed9WG3Z&(5{M>Sg}F1+YMh$3`VT*b<&Z>;ut&W_uPdJuVu*EP4as z`GTpPac*?IR*Ui0QZ(T*ps3U6f3q?|5kn$6rB+n8*>=uUy|<3HHrotCvZj*Epe~qSj9M*W@o2b z*a1G@L;eSM{XbI^y~t=?ABaBs_}H_jr{APXc#>%#22I5l@}*fx{DtX%Y!7%S3KpN< z9kED~P0PFz?~2{5m0T{#<#)?&V#pqBWSCuo*bDqu9cym^EJZP;LJ4DMVqvoMOz?xn zn!*DUm9&13)9nv$Hibb!-s-!3F3Z&!Z^o_1Z<(@r*e1+=F3BYl7(AJ^oEggOhEbbj3w!}vLOQIbJAv1>dpdO z-mjU57b_tBphLevz-uUA4A7jN|Uq=vAX9Q+fl=z=t0RK(T#s4t& zK|iNWA2hlN`-J?oK9(@+AZ!5iI2}IO&1(4Vl^BTv0P1D_zcsLwhW*bH6|^+yECx@R zs92T;;%&P9LQw~snk9=(T&p|i8hn1bQ)9ls zXD$7s$A)ROx(kL+zN-xl&)Gi1Htk`~o(wu{g56{?3l_qvHMo_SS;S>co2_{PWl(sF z<9+16LtF4^C$axWtJ-kt>bcR?>nn5pB7ZZT+^CrvHBx+8nyejL=$~4jJ$+*N%*oZc zQ8%54G6FJQV-6W?eotoj3-EtF&wm`1Xh^qBA2G`To0SzJ_>UUI7_gX48Og*7)NCtx zVQu$jHN5IR2)x0|NZ z9qX;7$kVH%-+g}J?|=W8v;!pl`0nYgtz+}#SHW4q+?M#~UkLxTHsU`!c`xm&@;_iR z#jLW|X7q~6x+)uc>Gfm_X-D*uBp)B8ugxmXz?b>Y;`?EM#YBPpI1P+iyUty5X}xKW zCKVFBKA*uXUZ3+XwSwDCLo04s%NsM2r5Cmk|G)|bjl%MQ(P+Lh8^BAmSh66VUGL#@ zRExffx4UMKez;5isMm_k1Nj}yeME~S=+OMQUNatT+M{z5PGL|w2lMFX$BtK({QUX% zlmF}{YgN*@O4|{N4@>!Ovpn6&RZ||XV6s?bi$RF_{MlL~Q<&@*bEIEjEVzAT`*&a7 z%O)afG>%E{`sMLoaqzn3s9=j(CAUq>+LOnwHR&-Mw4zCiy&dt(JG0rV(+air<^S6Q zn^Zz;;6Khuqu!}?mt>uk@n|wp958;X-f&|+Fe-bGSGE1Pb}k`gUDj?;NJ3Y;;RKv8{H($0yUbR}-s6wjCDi-Z`(YQVh6SgI;a& zc&WY=jYNYn@yWgGq@XEhMchgx+KJH<8p-64L3p z+?6T$qvJ|w%?tJq`XBzkHL!d9*PA>Ve3>SZ(z0>C><#!F`kRZPZbn=$8wXL-MA(sX z*gAd(@y}kD%@+t`AUOo%ot5~R3I8CeI7oxh6rZQ2m45odfjLLmPcN<;WUScysC}c6 zZE<$lVU_L5YJyMDMI6SU-6|Rt@-uOU`K+6({6A0^ct7<&+r((5SSqK}8$Ldszqgj8 z>G6iKM1p4^*<8{_W~woJXKJN3dnT2vrQDG_*U$d`+XqCp_{5Xr-;d(5j?q|K;drD*I198Iq)Vz)Fru9V9Z@UkN+Mu<+(Bivg{ZFaqlh>m#X zTDi#Qm6h)k2<&wMCcdmG^kSNBOiOAzyGSIF374&hE{(#MF)md2VhV#1!n13<) zUyTuMlv43@k!aqBr%LxXGMK)&g;@~6!&9GFi+4_h1hH99_ZPNerMalTf9~x1SC21c zV?Nl34_R+=zq9=>KP&&${a>xDvj2;^r~Q_s=wRhbiMo*tZkJ#%8Z9YG^~JCymq`%3U5q*Jti}so!$RKBbbARv z^jJ+5*_HMfSe)_%VvOV2sLZCrmH>}Z!<%4eIb9YY=8B`9o2Jjy0fB298r&Y}*^EInY z#Fb4KpmwbAlK%w%M}4qV9&NddaxE?lrY7Ru8GS6Xb)wJysZ`jlgprR%I*ZqzA02i7 zPtL&pM+;6Bb^ou+=_m#bDboH}^%(!`Nx`p{rh*WPVy74ld8eEXSfbGw zAv#8rxsmlVE14|1@E*;I&YWi=C{4#z%g zg6N-2b(rfN6SXq?2Zx<XrZ=lP zmH;d!164zBD@$NzeIu-62F>V(dtbB~@M}433i+Af&?(sS#azQ`QIfhaE~vCWsLp@B zYrKd2$E;0aR$m$0|BK^A0rpcD&vFpp6Y?Tl-uWgy1>d|$x+=W}2Ft7%@|8dywFu#JU%S}AX~rC9u` zXMBZXQE<4HI#H%+2D2YWyIFrU+q~(RFOg;(@R7%3p%cg9p9`9EQP+CGu+k1r zC(N92JCvk7STgY0BiUz(D{BhIXzn}b9_rJUl zroI=n!~XHt#P_@B|1QGush#zxA;2*_enJ*4$n&2WP-<7n7#!TsTomuu4Lw#lN` zV<~OVWcYj){|^H!_6FF7eJ=QKHAmTS*T@C)#WJC!n-fVaE~7Kj%Q{`=oE6htZ;0EZ zTr!x<79ElY;g1c|tV%~E9WJAVlx49Z*1h6`jck)1wj^uUI87=nDAD8?*cg+9icWk}T;}~l&rC!Sa^J4y!pK1P!{!eMEHlrTfrk-?Q z9*|a|5z;x$29`VHejywR=nY1fByV-4t(IA`o6oe(Cnpnp?(6^D+nD)6HQ!LNU}-CX zpk#cI3pP`j#otCDk}p<>INh1apvj&7R5vSGMHUEf&FCUF2k)&&<;v_z&}nsB(>eH0 zzN}m-HWd?QGuo6O;}I>yl@K6;ih?=$}sy1HQ0ovQ0<3DImaw3MwM zdYh3%ks4+?%0^(FF6eV#oRn6}#;6y&TDQ>;?=%hju74ep|4cSF*2Cd3SmJZ~1j;7JC^k?CSjQzzp*+qF@!%4?5Z*GP&xlL=ng@Snc8 z`CtF~TV8AOhhIJ1-drr@_FqkVO@|os|9$3vT;CwJ^JG<9jR{Nx`w4Bqt0&rxbRx-! z*W*+4e<9();ophsQX%I?$++6`ZPj(y6*T$_{+~ngAI}?$zaG0D{=2j?u9#*il_^x& zad~sS1poQotHg|frqS+oSPWsirJRj%k_iI#&#hx+_-`?b@Vw=7bUc31=tJ^88T5B^ zn-KqRa_5ZxI{9+h+n$dr+1#D7|HLp%a1krr6v8wi?9?xnZS(zNwzE>N3@7`A#fj?j z+~i+=^I&20{f5fd5dMXq-V^>4=FR?hF;sz#>iqZRY;5}u)4ko?hRm?eEtJ^wnhYrk zDlu9#@7DjriUWa>WE1t}m2A4q-``q(b^kA#R?>x|hK6X-5mZo)Si+Kl9VIcB9y5Rq z@9_wUbiDW#hb1o#|A=TImUt;;Tdc_^Yue}ek7|t+Ck`Q4aTi#L&};ZF^LVK!>m95} zG%J}@zRLRZ$ET|BztLKR+4ZINRxD_(ko!vB25CMwxVKi*RIJUU&nvc1bA)>uTa z`I)Mdx&hOUsCD+glK?@C^+*6n=c?qQv1E?9!_Cz!&Q(%a~mRnOByaT3_Rx0_*e2uAc)P?+O3$ZQ|Q;MrwiL%n$spm;Pfm!k784;4;5}q z<(lIq;2#sa!Z91$dFdOhW`fMl z4)&VG?BQxq+wQkIOXpI>sjM%)x;+1f?;b2pzPOt9lYQYm;Xhj!*~#TjG-I^|*1r@t zMW81G2K*JYd2}xQ5E%xA+o@kEKX54H|gE#h>U=|GOX|OP8S6F3oET47LUhOk8!F# zb6~+#U?tD#*W(rq~X8AMGkGQxwev{5!vLGc3HBzQYheKAz)`F%OV%bpQsgIs?Dx#_)l|m>tq4$ zvbwdwC4f4t`IcVD#dUj&xS9!y)_xZ2pqO6!Th8r(eMgd z-pWzUMbQ7WPEfXS&6(L0g9xfV?ldQ~wOrhLvSzrlQo!Vc0O*i#zL#mQz<(47nZe49 z0dwcQ<{zs-sR4yn}2p=W>7vF`QKjVKQtq^RiZTL&TYG` zhH_9l(NMCflhBoH{3`z$I;lSVcLsC59I*{f!oN#NcZ?($-q6WG@cw$*Y1N}iNS!Ts zQ=!t#e(?`Vy1Big{F$D6H)CPJ#|s+DtT?l$8ik|oABX?6DY`3Im^Ay#@V^$fH|kXy zJ?s|PzN)_ef(B*FHm%c~Tt$A5?LW$Y>2>m#lb%>2Nd~`@!yx+>1CkXDiI!tF!TS(= zmIkVGrKly^Z6U8E$c@-Yr&Svb`P?3|!0GBK7yK^=w2%`zm4(a3cDTLX4KnOb^cs=UR3O`9 z4x935YPm?ZplGLb$iX$6!#MR~tdBU2Ol^3X$nlE)-th|hALY;De!SRgPM=T#95%?Q zuB6k4|7toT)z54t^LlndFT`-bR(aE5F6R%YhQ%mBGHR3*9skXbKehc+h1*P83GBRb zjTMy$WE?VX9C)t!)%1NhGpWGZ0wdgMSjcA_1+ zG;$j(B7KAr))C0<>VGG51afTFyHi$AkaSy0y3w%AY73;LC}*@T#(T6T+lX2%w= zP$Jxdhw?;bj-+NKIlG+f>fHsldzZpiG7b78sgUeuHg{^2;4-$G(RnGYF<3x;;~UDa zRpqz&JnmpPexe<@Jmq1KM9b?II>U1e>tmllTITB_a$w=ZrjP<=*8 ztOE2uSznY4s9KRgSv(7hgGV4Bu%6`O-uQy@mV(Wm!U z(-98}2QZ;WAQ&?;nmY(A(Nqg5W^-;?A}ECo+B^{wDiJTqq$FpJwggV;jL(pa1sRnR zvH0!f=zLw-mc?*$;lc&f8nx*tkazL&pKEg=6+?N_^RBifVS=MR{DRq=cI^}J%3 zyw#g^$7;o*(JE>d{m(2w=U^7uVB`2gPRk}oMginB>exhsaQOO6JmMVO$L{;_zM)3 zq|Cqq{~KvH$vkK3A#j+RLHYM%m09wI601|shjT?GD;F<)pdE*$0P9>BQ9-AWvXqwE z;pv28-jl|GPD&mdyhG%_^+xmDJIFbP-DbSfRnocKRtTUjKu~wVT{Kznjj~3jlk8SP ztfw>Xtu|iQ1MdNTPxz1R_vq0h63lE}yfd|Q!mhX0!urW-q&}P?nFg;^p!)dGaDUOJ zA-Bh9Pg}Jj%b-NU5Ecu2mkZjQM^6Vqv18r{-V9bEeFQ7CRg5({6JoN+-YEFb>jo%| zAplJqa(g9HgmTz$K@#I|G+rv^*_W|Bt=P{nmZBo0zHzyotc~1wH5jxI}n&lS{z<7iKcbRxkKZY%m+6$TLef$Lr3*^yL{pi@%VQDMQF@jq8g2 zgr50=>i=ZINM2b*uZE+~3-%_!DoIOz05&a|u&)>mu|2e|JXzmJdsT=!$WaxFXIb-w z|HDd%L{%#*%br9l(_SX;CrW@ml1ZoByks`-{^Uflk#U13Dup!lc~kaSKi2X)$O07h zIvKKW-@cuSh1R#P_NG?wvNoee^7&T=Q#d~vwZWPG&hWuI&_!FwWKvR3({Stpj*+1^ ztD8vb0Oz2`d%}N|_NSkIifg6Oo8G>7J5}Tzuexs9-tE;l8=(iM8%75H-7Fk1oSgsI z#=QT;Zc?HI2@5F)G`w|AZxAs;fEPs}cv7D9z+!NrB?>WDF`r3R+e`KQ<0})Hs1i#L z8wpJ(nUY86C)Ab|)qrG<1l?q3JwNmf3qQVZC>`}ComPj=Lr@kmW^TKVWZ>WP ze-uT#U%Ysclypvq*qNB$y6~g?qh4*Z5k&Rv$q$PP$Zts)kl(Ci@IXS+7a%`;Qxd5) z8ho3r6a>1uhSUOSNaLv9{*7n z7-qL_-SYc9F9`OAz#>>JU@+Nzs2#ch9pC1w=!CGc0OvDjli^e*SC3~1H)`d4jDRE} z^QD^D@U#m7((Hv#Mttc^wv$ZXm=ESjOXKq~j`$@xZOtuKU0KnM*CHG9P=%W}Z!Rs) zV?-W5eJz+Mq0{;~S7*@K>I6SI-@$_l)QrfyOWkXbwN|CoK#zuFh@ad|3daQpJ^s}E zSM%R0TAjqUY;R$s^yJ+7`l<7tNS+)T6J_ie`<2L$&ig)XAJ@2?P(!-A6y_0Sc!wi0_FvpbJ?`|opZ@KDTb zw&%bQuRk;~xAwgJMURO5UZ;6a{!++nc4kbw@=F#YuMy={R#?+id1`=_!P+8VE%_&U(e=4!hhp7DBvOnaSPx^XKUTp=e@u_0-bF zXBD^@);2Fbgss6_(dYu;F)k z?WU;T6RgH;qnu@;;A*BEQ5PWxlE6}gm3&iNrcb+dt(XBDfCC=#T@l43aW>yaL0Mkq&jp^woPu}LJ3pR^i z*|(x2`qymK*=zzTf?PUGe%D$aoIe4l0mlfVhQqA)n;Ue@J^hb(46Kj}FV^#dy$P^7 zd(494+ve)ag7^EVaw9e8UcTh^`0Z$f^n-fFVi!?{kfj!iI3 zM{?nS@~&8Dk9CAHXYxxUn!N>!v(8=*hPjeopY-VX&RQyqWo#$m``MBI!pLjZYw0p{ zJ%Q}FmgWomr!;I5#wJy;HwTti2KeKpP$a>4O0RU;9fA}RT!COD?(s*$iJ}zBiJ)o; znoN@4VJ|17N@scN{LOeG$t12&t|c=?PdFnwB9hr1ljN$;Pj>ody&NHg&McbjA<>pJ z>*cUyO}b6I@GF>U;{!pB5XQ;d^ftCH?_B*P*Im_%;gD=j$;M91m|_c%(QeS|Xj4|( zc@bVRQBOubOTJ9Bqs=k~k3rRUNw=zS0j{^JWpTY6}ZALL)! zmA^W@(;ls~cIEFboSQy=d$4jLQ=bY363KX!RU!Hq4aba?(S(iwAXZ<&DAn2IYB4ai zHw2t)`s3ZXB>Zr2C4K7ZT!1W^gaGkq4vWt0wYFa1KgBXVhyOna_NKsk{c)l%l|3mQ z55`X*n#PH`vUUCZjZbfU^yTe`UthZO^wgC{lbg3Qljod;6)`m#DokLyVB7RY3m0!b zyz}Urs}H_dKlh+Ab2d?#<*m(yV!2)|X3Fi_aCK_)#$fYqX8NipwHWs2YjLmL8Dkj* zZxz-WJ3W(Rw^`Ucee>DUh0kfY*8G{)#A0J&xiz~raqQak=Jnd-MmSw#L;y6j2W_#p z9qi4!zQt6|$Ye2`lNc_=kb7FC7adU3<)WZ7q{Mr^@MvEsJ{keR7kgZQPXSQcg z-935r>4n?RF5Y^4{OpbHXekg$5>WtjP}=VDM02gq{LbpRkI&xv{PNw;&)GaGcG5$w+#cIN$rqf{@!T;yyzo1H8 zKoVeI7VP!FM)7gFTr>)TDg5_1y}ayvxHwwam|Hmsqzj07U@nd%&(r}H9h^AwL)tqo~^SB9*vPo#!2AriDXQ*(el( zRFuZSpa7B_D#sMMh@ykc0#;_edbZc$VD$TaTu)J4&*RjK@l+_N+S!9miUlKn7)B-R zE11Tkxs1obf5u!SS1xckIEfy{5xOTo*DLZ%GV=2R6RnA)-8Q^2yW%G?EqEx8qbN8} zQ3ybTQWwT?d!V2XwCs9|ThIkXz0YMM)s#(i5pPM@^JG!N_%DQbpPY>GLxJ4kwY6X1 zKV1MC0|0P_F&{1e&ygYx<3W-6f8*qq{C7aH*$;H>jOYu{K&>K76fS}3gH&@5vnkhPYDMi zKj8eS^nbZE$+F=2rSk6j@As*D!Do*`*ny1NtP9f-Xgli5Pc({Zj)D^4&>Wms`2%$H z0x+S0u*T7N%5voH!54HgUP{i>G-^BYNl!t~;BVo8LL7MTI2e66&#$x#pWMX-oIwok zqD5dS?GCRH80LdH2jIhDep0&t@-vh(FQy&UF8RFt2)`#k?The?Uz9-Y;0FDUVx6I) zHPUtH1=f;SP7TO{$LD7ju4r|mX|toG+$8@M-5;5GuGZ+-m;Wyd_9nop)kHSfMd9!n z9MH^a?*>x705Y)f6HR~^d?Gx;fI17BAOMX~XkbpeZ~=!}6o3FJK&ko-8UqdrMC~9v zi1I-XIrjjhSpkPDxdrIdcH|@l0v9^%EsXy^75{t9QmHw?P=0=~#H;NIT1FBMSOfx4 z3mBm*=m8@*&1dSC!512rM&u_|06zK7H9VT~X$2mtDjapf#ej?Okd~9Q@}B%~VDDa} zf$_DH3&djDN^F4y`oOUi-H%I&F(B5lMsey@pjRRQ@%4pH_HJq zhtgoF*5urm|6KpFU_S{gb)+K5hA@Cktw9E$K$YbDDyl^6P!6!Dx`k`#4$of(=HN!f zp|m=7pg`1XxQjML)uV0riH?RofyN9X)QF;J1@5H_>=A&AIjO=30o3lJ-UxJ9dNhGb zq&NS3_xhiJoGwv3^**i)l64SixyOIC3(%2Kq|^%*p_5VFP*LS7yahK9219;D;Q$w1 zhFh@xK##_PLZ}y7gd0JaPQ)jC20hO3$$u$F)z35^ZOVBbL~larsTzh7ASze#T=3^& zWZ=1APWdYNUsgCUjqZRXZ!SN5c5e`22tWWB0`VwBj+Wf%3A@$-|A~Cy{kgo|jCZ}! z)1p2B<7cU=>>Nj6dL=~Fm|gqwpKIPiFo303A`*(_WmG|f1AM>}SU?ZPLSqgzGp&P2 zaFv5ED*2I9wOF`@Gc*u40y6hfWfT{~H!TPnJXamcDU;i2aoS7OR6LZ%g8ckMfe6Me zFE_+{&3~`9UE{TtEt}vtJ)0$plE9Vc1y&mi1qc-A1nP)Fpt5iS?tvk?PQ_|(zC*|7 zKmz~;cYz5vLM>Rq!Nq)65B>|gC=i`SX?q3oD|ewrD3edVa~BU%n~aOOg&Vny)_FbP zZy`UofBTx4)ZOWy3rrsW-@+7Q`({384}TLhg>-%2~-zZ!kPEwBi>YAw>M z_^-lyP{HWS9Q&e*2H;S!<H2UUaEdzh&BaE9|7uej-Tr_~$PGAZp<1-{{BuY1z&Z<0`yEC)xVZi{sAW01%$;x@BL8jXZD>b{*G zJNynFx3_O)j`+!au|@y$LJ)=jVZ4Ar5@M6*hp^96JsIaMHOAh?mR3wbi^P7hVcNJ> z(tWh4KfE zgXzw&?(xPAqUO6}N_(Y~>&% zvQ>&VyOUx(H>xNvEuCs9Td?uGDP15I3yKblEu}{M63T}|k9@7{X??rb_WfkNj^YLT zV#_uGHHwTpfs*7_vVA5+-f&TgDrqz64!8wyg#j|t&6+)NYASuQX8Gdc;3y*S?qxm@ z|3RBJLi4MO4hsLpuszZ;>z%~-u@$#mDnyI*MlAdBwW+C=vW0|$p*f$4C!J=a5GQDp zut3rmr$`PE)DgM;c44b($-kt}S7B>1IN5X%bZUaKnJDUj-#3$&&yr%qVW9fV-_b>Q zMbI<69jobxk^l2l&B&&bUlKS!UtJEwf2Q7y(M)DlCnO!g>($4;2jcgvgG})hjU^5eBivZk|94J zpeXji*6zVQMzAp^P(;#2+@|@QdA(vF9~9!z9O!@45&GZb_2%r^g{o__;(T~gNlY<5 zeiDHL@gH|9I-kib&e%}E<6|eDCtB{z+Ff4uk1zxfW?zVi?2=wA%h`~PtY!7Mtr`-e z9>HvM+H8W^<{~S$HBd2QDQ7(1d9o#q}xa!bTMeOTRvP=-=u{O#DC}t;5dJ9X(lsl)9FqxpV+!^ z>+YxD+<)?CSMGnYef_if(~k((FU?*mPMoTaP7kuA%zifbp{rwvWEK_xL-uFc54{9C*08SWgw>ekJ;dgMk?5 zXD%6MJ-t0z9jsn1&tDI>w|zcxGrLGtExcb=TR`DAJ5UUTVcYT{h9cP247n;C485P+Aq5dF(sfF@(g zMmT}We+GSqesx9f-BDyeHLs55+hT|4}$NKbde5zr+xLS6_|C z;3T?>sR0M=NaPFw9@6&jL(^1NLJnn#sg~p z3+#kVa`3AfEe7+s7{U|a$9XQ}lkc3NHR&UJmd~ri`#{d`+5h`u3uqYmP#ThhGd$-q zR1N%R{!3p_n}d$Pm0Y0eC{8k1qhx5c58S=*8WtUh|M)5~S70>_K^QjC-Dl36VbeOP zOYZL;ckbM|apMNbph&y4v$I215H?>QKYpBuaiWL`m!-3CFAb!Uf7~u=(_sF+23vN8 zlf&oIrAvTGv^ISK#SZh)?R@f`GXw?$G8?GDoUnXi4(SVOH$lmaaeh@7I1v9;HyxUX zkW};xmM3xb1ZJ~IfxZCje6l}+{}NZvdBU~{zb8ao4FX4T!F-yG;4Yf`1Go8~ZxPl1 zk14n>wm^#Zfcb>eGlpXGtJWqkVn8w?GX%hP#$uWS<`ZzZx2a^TXP(FTohv8h7x`#>cM*1I4ZOoKcYlUA_m9D>dz>LrKX?KHSu2G4$foR^(?mqYKc>+M#Z)vPm{v__-y z#~1#~Nq*u2E_+oWl*}V!hn~lYHl59~TgWblje4c){ZNkXjmkkD5qWe}fr?NaDy8ie6JyHzbBy`_{>}h_hS?dt=UP)m-gfFG6wXCgK z5ZXndQxZDm|M1r?39W)q&)Ui(*+bu3G%y*j2%##?lY*`r<#@YkXF^7Cc1|XNiAUOssha z{LOV-qlUAYw8Z?0v8$(nPn{rz3)y)S(J|Lwg8a~sK)wkeYE z-UqxlgbNTJbf67-?@dsKk|=MwqowXvo7>mSy?4HtnVp@P-PmttV}I*Di6SA01R+rF zcB@rUDisS=S(#byIr*NGCo`uj=82N2n%2hyYG4M(rdcNiJ~7-NAJ^9NZ%IK)P)cVo ztM3n~ofbJHf#NhYzYNTvgwfT*RpZ#Wd3@YDGisZw+UBeFbNgJ?K09ign6OSvnnwqX zm5i=mKu+$o%3M|%pr!d39MFvo8z-mClM_~Mz9b#n^oXrKW*M!UY9&KHp$&Ugu7(^r z%t9DRwwl$}2Mv>xripQj$WNtRek!(^QS11)WqiUsQZ<&-x>!)kn4IId!Jl)aqpEI0f(HQ3}(4rCfD|fydpkK-Mo4`M6noXflIGxT^Dpjl1ip63g z(GUJZA%A%wFjaBXlcr?A?NNFH7FWWi>9@;bW;uJmy*KUZk@k$cXTaN6Pb#PKy7`(Z z6V+-|r(c4$08PFyWL~HkrgQ4NPnLC`KGl5u+)2*UTL~#AbDH_8k%j=$f)P4bGuHFE zk)$f;m1R6{NkK}Gbjm@+WL7g*F^~=xD~2j`rJU6I^w)H5ZFbT%wjY?i9$vo@-8qcz z9mUS=yN9vOo6&`v;mM;wX~AQS7!19AkyCqF9dap@O1VE{9$xd#+z2imN49Q7dE{Io z{JC=wUArEcJ`4@-_~RoEl~wK4_h80)9SXl&8Fef4Ju+w9SX=f?9R(I|g*Put=PetW=`P~Yd0CdF{{CBPa0N>l&i=MYwEQv&7czAeZWMp7q zAQFkVTrRWOtWqgG9#1@xo)`$s<}7NXb7rpe?BVp2lhM0J`CGfGTYG8t`Yjv z*RBrg4py8dy==T>y0z}`So#L?uEV1g_cZVCa6%^6mST1*a;G_5aq6^vXl(4$@uJ?M zQw1|-Z$O(&8wc{Ha@kz1SZdYtI~OZubD>~PXN?^xZ%c=)=JO`99f;_I zX|vI;s}E?a8C^W6!luSX>-_aU*{{DU{SW2=yj(6f8jay_xKgQ1OiYZ9j%G3$uh*+m zso*^Ec)V5r`<$MWZTn_TnW}oy{ib}%P)HjKX>&1SX}cFP=4{FoNIP!L81L_U-8O|( z)v(hiN51Qe)>y%rEm%9G&`h?JHOIsH;;^@#kpFNJc3KqImz+;-xZM#;s%qy&%{x1v zlrrX2%vc%q^;`R1-3>V`0&?}DVQR>zx9ci%Zj)Id!?ah3U8!upOTWtH*Jy?cYVGgu9~v5( zo}Px`1cO1HPN&gmI2jxqY}NlZn~k4jGFix@`r&D4zpk6v4ApBk>Rj1zSIDnT4B6|; zq3e_SZy$vME*13y`Nv}Y%aiDEM!C8X8z@*hCnEjIyTSqO_D-VY@B3e0C43I$^8?@W z6VK>`Z(=Q=0%IL>()^WlEGEV7PQOE@{QJ|m-=)gM)U!jzsR@(XV;I@+TCEDaTit9F zM6K?tEV$#tPP1L6H_I$u6-L3}rmr|^2iRS1q~r5x2G@ORvnG+&!dRjKr9YskOuGxS zuHNo4BfSgP+|a5wn756O8s~>ixTj!un801;9txdtUjGi!*C`iC$rfs z^af!2{r*y^BVZZiZGgip z%B^+ElvcShpi=32>(_(DX(xi#fbs3AvwU_?RFOmg|V?Qv_1GorK9R&u^6hJE9G(-st@M5 zh~G#WYVaTSs3HIB6Xy9vt0iKZ zza4Tqy2U@p)M)y~cD?-z9<4*BwaJYkwN}$RcM>YhyWXlCnnfm^P)Iv-9M-yZiGrS3 z2v0=qi6{rR{3BaF_)nLcz<*!Q=w2XEb<`(KE8}K5@9;|D=It_tkA2&x)c?R#{K-!;=0V-1bLH_K|VpQr-0X)c+`O ziQ8kkLgRrwmMpqm(*H9vGx>ZTh6B9;^YEf>zO{7E`{SRt(HHQSZ{lM)#o~5+AaChX zzkP80n|p=+54pg_@6rP4XpaK_P##5+#FW-sC!l)N2<$i zRo9dZIRgJn_agX_k`n%7YF|t_xF49@_rDeYRH^uer_rZ9)n%_QXRD7GFlq`3jn^($ z{ciX_x*gXIsvcH@NsAN!@=z#*{)gdkopS`?N|(xjc>jXGKmK)^zU%AmpB@CQ zQQPWPM5~s4RQ@A@M4{+WDo=O3k!GWOZ9V4JDu1~jwVM^StafL{0{$H_1=kWWef zA8sbGfB)lIKj8tdj(uO>_jIs-sUN$~J~&p|yRmln_THUivEM!3-Pzg7RVU-2>Yc4Z zt}s}x4A0>N7%#h%vE4`L3QcGJLr-BJp&Xb@@?QA=^r3fPP4NHTQ~iJKwf?7vMchV* zf`Ulo%StElpR4NsSlTc#=X&%k@$HNBohJ!8nn$+%=}DK)qw{Ech~lGPgPAN@bf$)u&J>6$%v{ae~5LzJdQp&~W$WXdsaO>&a-PI(%;>alC7uoexf~ z1uptODSBUDZ@wIzpUmI5RouK)9NozdZf5H1>EcA(V6vA&+Oe8RsU||tJX14IR7}21 zWdCm2iEyF@J1>vi-$K1}pKSGvp2mG62h{xPT(N|jb`)anf? zwMHTD#s4|6E&TsKzKqcAJ5#g#{o8~`t=Ky1$Dgm!D!EN>G#N~0t7iFP= zf8)umF`vrvmpjq0M{{RUadS&KHy>(?e>}KydGG4Zz|Q_~Hd?+uKl|OC)t~OIvA?>z z`rXOOy`%Z<^{Mgku}W=ZrZzlLsrs_<{gV#(??I>mv(8{-*XRrelY#!>yPC;i< zYTZt~SMvt`dwZ!0Bt@eWB49LI4JM1$fY51a6q?19@V|5~Z1(H>i-dUecIN+xHhX>3 z^Xy65XI7U&y5XFuHfSCmH4Rn_#9c@IN=D7Vd>BW7@q39g`^sPc{U3NA{7cA(&)~g; z)#J=TusO%2-}L8vY*Fuwf8vLPZWQ1bADu6*Rx77QoKt&=gE7skZ^AB{_HaG)mnV69 ze|Tr7q}9kYTJALgdk|$XTR;d;p2{cI-tF~)PHl^d>Q`5kVbj$ z`c8auKCwLNKR!%8dzyLnB=hy7^y8cTy9=S(fXnBxxUHs2+OUcLbH?KCw{6~uwdMa* zzFe-LOyrX?BBY#~ZC1IgPvC#MU@?`AuB6^;W57dG{{vIK$xMlOycZ56MBTs@qeTb; z6{J$(d6wgQ#{aW&JC6OE%9#v0nJubn_<#I3S1Y)q|CXLKA#nY zpT2B{zz{)4{J&n)hj>DMr2F3=z3-pGJL4Z<f)QC^W*jUm3dwnaDb;Nos9I?=RQ`g$87m_lz0ivW42eH4O#yqhef91Li7 z?$)e9U$GF;Yjv)q(G#|e-iS>fg)hVZ$hv#uddz5$O$}LVCE@Ccdc|l;!jJ^`wPHXJ z;HPmpQz7q<-}g=79q><$;kpS1gFqd653z^*AO!@f(XoYN|5G+^m*F2)47mj!N|grd zkZQt6)auZ4mcb#nz<*x<{BB62vpl`!Uaq*8_6sA`2%iQdm@GD6-*91z>g7SW0RU+T zg4JlY=^zZ;q3#3!M-BV;zn&)rvHiIO1| zQ8VaRrxqiB33zCXd~8fiajDrYYWov!SH(X}9>7Cy@aJId;^agMh*%5JU#``D8lB%- zMFj2t{B4BzhuMS3XvGE`*iLCrgS|_-QF;UpW3}5#CClP|d9Iv&ey3)2#a`XC26T$8 ztwbcKgO{RCO%^L?Z+(k&PEu%HfUkl-Yjj$TT;RX!_=hI`6Ak=7+4W)xo_h;Wc`N>D zl^WoOlBt^Dm(tNd;8doOHS_EKuM7Tvbb`TRqaZ?Vq`f5*R2_Q5Xfk;{>dgm5OTar& z@~YIvV9M{YI%bc-fA%u`53jiwuZK+z+5EJ1qNGhnRE)u-L=b+JQcNEq4Sqk9*Os^b z<4&)He?l~a!615`O$tTk1XK5YI|COMl}57#fW4s{&yt`m5Z1KLAP}gQ zy&L}HnTGz~@oKM*e`HX!L~wqU>f$ikhF{ZAUDRcl+#0F>_X7SAUhofDx`=;x9f|Wh zw9)TVEZqy4{Q5**tua`9@tEI{o4lTz-Rr*$|DzkegOj4)5uF~+PF4fuj17m6!_4F@ zjVVx+4f4~N0_Cha|MCBKiGL^!<4}lFgXhqzMeH$602}W8kLTODtafN+Xlv+ABLjs} z>AO3rsg!r?V0diE4=4bzR&RpNqRK9$(1SCom{bg}cyCM^?%xfp%{so`cNa!{3-bZS zLZS|#I?!X3(pXi0;!l6zRN%CLO1xw)Fn6*_YRx~ z@&n1IZ;Ku-B;r%%8+>48V_f4_rB4CVoAaPs^Yp+!p73uT42=!>bXwdX2HcmxxNY7T zHI_-rEAx(>N#pLW=Xk@rHLhP=jSUY&m&FsX3k*OsY%6h+l7e+Zp20uHG{i%)f3L)U zuzyz$BGvK0%>(DnO*g|Wrz@txtRWH5*i0fS0EBeFKZYtM40#Y4Xt!bL(8*Dr*Tkj_ zT8F%C`~w6I{Nv{V|EHD*f+s}(8pk<0GT7vEkI%NU>bZ1^t*j!uxqqx3nwGr?~c3{Nu?1X4tu)4nIGzM+#^H(7>O- z&ZVMw3+6UKM3Mjkdxkxd6GSqb%N2^nlikEz*15Es9;w*PX4tJ+E?gp#QafLVc=i%m zRdL#~GG!T?w%ytHG0u1MAd!w6@V&4h?6?Qfu@_5+Of`I=ddZ-^D*h$^|JqSxbt67r zwtTe{{P!pQe}9&Ec^ulBcMQaJj16a+wYK<&a!?|HPoo7beUA1w3km*dm8wg|@R-Kn zR=3B$Qe`|f@XzosS^`mBqUmfvo8?>jX|3fb8izn`lnre1GQXA0Q?fB~T$joKgL$gH+`n;G1 z#1Y9EZfv+^M#JV_%*Lz-4Z6g8kszJHeBrzF2-{Hf90|iya3Q(ye|E3`kx1S?KId6p z4qIbpuTLE?_Qvh9N?0+G(<}{|N=Y5=!M6BEHqrSyBVBUFcD(MixTKHcc5uZUuB(k6 zw04{LH(}sdY!u|2f)H&0y~7RbMrY6k{EF`Nzx4V*Ogl7gzj-He|0K3^Gd{7OOwT9v z(SX$)jCqo!Ov+;?^wXr*>)@_vGkQpg1O`s%H@@tJ3cu_de#q+`@K0?x91eho&L@Xt zm=gfW^$(eY3uz%}yiTI`6OB*`NEcj73L-*v&=z2Ea}PDf_^-zJ7tQQmq?l6Lg$`}c ze5CgX{1**{DRXVeW_7COX3VLIXL=*3q*-nhi_)R|p+MquNSF}im3G*Xun)AWI>HWQAq2^O77>{q>o3U%veQohiyqxIAgPaJT&xI;G zyFhpc#RvcKDScc0Pf?H~lhr1)1o;uIYhR-|69uHW9Pk^MEM~V;xpWe`Qv4@FZ+u>L zE^DZiO@%ytLprBN8O&Nu8ueI#$?J4hn>(2jCL}%+JcoQ}Jm+XL^cx?S&kDc&1giC{ zw#5G(@J|lGJh{c~gA@?14ER5)kdinx5Q#+asiGWE_*|h|4KEWJ;Do;BbBdzj|Br4B zY9_Y=xtN>|+m7Oiev|hz5y8A8o6=it@=QW8y&kUCos#mGYDTB0hsfHPs*(nfaL{HO zEs!Uy0G&Y?0{B0Dn(p<#lsH}^w?mzb8FEQeE@c`>nbHYkvgAA%(?7rIvzg`KpZMo< z{(qHP2KC~l67`ajdVOq21lZ+EVfYWPSyJ;howE)92jYk@`kz)zbbC?VdOZO_l(CVT z5CQ63Z!p*`s<~sY$)``2h<|ts|9_p6(^U8fSmHfSm%23TCK6+|Zl+LSPoGJrB=KM{ zDCvJ1ca$49b40Tt3pmwLZWN^BcRcsPcmMM;{6p^%NdyzjqxLyLJU<@zk$OBcp->1b z1BxYSct{uepDS=Mv^`k7SWXH5+rzriRaY*i(CIqGR5uJ*?LbaHw(cV?z-Lq#WvcbP zc;t-yOX&kMI=KYHX*g1$3Jq^z17r>5D>-3IW?j&ux+?yqR|q}Z*a?gpDp_Z@!W)B{ zpPz@l4$)zH4*xEf5}Tf$S;71-(f*)?OzI56H#N5H7+7KaW1IMY-U@x9|BFHael4qLlp>nps{Y;0X&3;M9 ziw50d>dtfcX95$m)u=ZzFs*&*04=nK+>&uL$<%>2-{}8s%m2YYg~AKU{Rk96K!^-> zwfrnbk<|M7dRhs zyQoRNFVI5cp=Krh&qatgf1&`v0~#1%X+H8{Puk)iRtDzD419wVa)PLS*!N1QA!yjM zupL+yrVwu-d=6g|;)WD-fPFJF3HpD(i)~KYYx5QoxNKk%?K}J#MW@lK#%k8xy`Ua zR964-Rg8G-i}<&iWi|&ITQ6FSb{iNLLaKwwCRbC7ZQZDFVF@Gw0x`Ud&uz2HG0M2T(` zZZ1$EY)=CYdV+1Fe2M=I2Ax%}+FT3`c(48E^W?kD|L|7)GyaR|DQtEk78tT+ z_w@>|6b&0*UnqBD{L5R$KT;v6-7|4Us_?98B;y+J3;g#A)jcugViM16#G%(+jsHt^ zg#Kqp_sFJCp;gaLnVI|w{&P~1`?EU(@vw7ZDD?0~wm)h(CvD(?BDa+{Q zjAy;B0(m&?Y(~Y(w5J%5y(j+X4x=j-HN$W&@}ISPL9I$n*dF1iqCl?i>vXB!E3k7+BIeU>mYY_Tagj-|f$H z_~+9DNMJ8%=DE4K58lbkDZKqed^^}XNPYKezgbv%PXGM#0QetS^@WSpi};tx z35O7!wvvm3k5aGMzk65+$80yZQg?5qtY+mo{5KPCo{(qJ1Q;!%2@sn=As%ZYdlx61 z)rk7a_&);)ooLvIj-mrg{8{^-XZ~HU~9W{3P+BfkJIco?e!5+dipdb?d zi|c-kP0W9Sr7P3)Y}||vu67##E(%C|tJy-^*TnzKd`NAT6%ql9Nn`6`{5xwc8CfI4 zr9|zsdpt}%r%04-cB6czDn`Mu1_z6AbdP^ZgxLiN#~F_PM+#^Ns0!#16a}CnAmm)s z%)4&q@Q?LG*V4+$3VN6$t-y!%$D&jvPd-wN*$eCee=rz)XX82<(*4sf#hDpzeLZ*) z|7b*%BkB>HNHrwk|EH%Vn@j)oNq&1XLQH~Cd2icDfxcIS0F^7<%Y)2jyg1<+?l=DD zSLrVKk6P8|uqlZ(rQKo25t>aJvo+WLnC|Oaeg?lc`M-q!g_|z5obTMQzuwFz3ZMMo z7sW!6vIPF|lg(ZC53UC2!DG~pOV5y=-rb|v@Omfw^Eip<%~r-rfGA1-&n$#=PS!H; zJ8ULvM2vswJpY5!uda;P_pV27A15{sqf_gF{$YpGq6!#$2D4gLpJ5aXtI0IyoPS%D zzy2NAfEI89Y(PE@0lj9Jjr_^9IgsafmV$Hm2lkL3)IQpHV`BqwzRtvZpOHy&PIHEZ zD@id$!2UgRt-Q+%(h3JMgdw7#f2kNHD*~{D#=-<$N-@SqBl=a^SMl4fLJyDvoHOWud@{5P5FxO{pI5RO zTiC}B;I=Sd!CaxoClsy6Ce$lM0ly}_enOFu>9@M)$wuL_*OLZ zCjTL2C<};{%vk9y;2--J{$sT$e|TJ=`-w1yE;o#5obXJhFWoQj@6)c#hQR;-{j&5{ z{PWpy<<_U&OH(0#%;vW0Sv13~>C3p~qbc>uh^dm&x5hs(|CK#zoZ2^Q8P?9iPHZi} zfv2PV30G?>e|`u5jNf0q6WH92y7lt7Q57?&oEqh`XDJ5fUL2Ay6+}A1$PlFl1(JjR zP*4m0O(!=%x@^BLpVsA~YR0&+oCiyUp!^9> zdRO?LW|q&26wpn8@h43CKpObR zlhpVU^f#gZwd)Jfk)-8+e_v%P(YKEOV*KygVPt(VUN4xx+9v)j@z2lW-`x)FEIJAa zozu#+B(LLtmRBs+uzqsasMo3#bQ%Hv*ZA*Y4GBCpG)aQ2oNW^R*H41CZY7*r1^AB| zlwPCuhwn22|8H|GU_>)O4&N8!JDd3L_nF-;yUx?sf&WPOuWz^?K1dn0%6h-Klrv0E$8@fOQNP;!g<1l@dFLeXp&vWgk!-~L<~9x|T8KbS|Gfqx9T)}&xN zqob~!gIKv@+uDz*l(+#~fA4gT1r6T-a>BuJO~SusV<|BhH~#kzLovVZt@x+OSUrdg z4h4?(oHrKT>*LnNVKd7frNSDPdc=5djeiFJmY@dw)2YRSZ{q)&Qmv+wT7)sTYy$tv zUiAOToqm_-R$>KJS<<5a`Bi3Q{Vn{5^zopAe@uWT|3Ux9JqDN4s&Og25&6)5`}yA* z^>MAf;bHtc(@jqnY{M19Kupb?*p$nn??dx5*pu1uVzf@@SC&9q@&)MiKpiux< z*vQ@M)pPjgK~TE9yo@2hk&fx^lWO@6ZBEG>eF~WHG+U(nLHZPM)|IpFzy7g2R5QGR zf6+W+}+ zxIbj<2LEFGOTqKZ*{0sOu`Bjs<29vSxJpPex!f4FaR%%C< zC=R(ATXt=31c%1m6XRZD?P{a0yT_5jm{=U4)7A)$W6BFW2AKGaf0y`A%&m9WO=^eA z9qN56{ts^?m~M2sY-2UaY*Z~R`)FeMRIL~VEQSDoQ2a~9!B&M)U>DOOz^EZb_=~zU zD$n7cW(Bv>($W&-1o8u2!4T+FXTJb(G6SgY#K;UCMl z5#@fq+Zu#0P5MC5GC6F|<}J{nD{X{E(54V%$x~VE`KkROBToOGjFF)_kPJ@c^Leh_>5sQ=mH z_qUz@Q_}yK*f(zVD>S_m zdFxQwkd3MRE-~j1>kOJ%kNa#D_$%q0PT6hw4J3ehls`wL05cPbfO53l(*E=u{<#T= zV*e6~1Mflf|Nf`6K1ls|pG@8IgcSO8?an)m+s=R&otJ%@kctvB0RZA0mC zzEqJFwcP4*&g|)Kor~7@VoiX5DcL6eSu79yXA_RM;-5#B_QIKh=fxu*`k%$A@zT%= zzzv9|ruV9K{s-`H@W{Zw)p8F1Ju-!Y(ox3Nyfq8R!Z>s!rjmwS#yF5QCi|Ilo{Db_ z@4Wax%N0xs0wItrT9ViJcWNyrt%~uKKJAAQRE$=XKV~M>8e4!+inns?i}(kbJPS&t$Q*DB#d?3zCgC5F zMz;bK>x1AQ2Vkw>`_oSYm6B$BE7G)og|CvS(3s_>-BjiwrUtR8IN~a7Q;!`lHC#Y8 zLgToriGLPwg#QdAT|eAr{g+PqU*i8m!-2yc$Mpr*_LOa7!nQbUVf|m$j)Wwm=a~!W z)c?6+(YyOD;-8iP@^7-(&yoLT#v84G2)~5zuycZB2^Iaejj!Am|7N2a{M)=T_|Ig! z`JYLgeRSRZ{7Kr?@c$I!hFa0cJ1C zp?8T^0LZ=m`NG*n{BsMMpRgbJ51JpW!q{n3E0J&k z@&7EvNAl)po5KIY{>%Ny$+mZ5$Q<+I=I?FHeL(dVRiYv-Ts0rzAgT-3Yo)-p@MA_qPthx+y2_=Vh`SR4TGl| z(ekhHpK)FL?;i%i|Eptf2mJHZA?Ksh?!~E4FkyFD4EjD+=u)_KihMvZQqV38n(6;# zc?c=~S;GJ1jsg5DWN-L?7_>q*M6ANqMYux&!GELO6!>Szg=GImoveSS#QwFo6~i6z z?~7=M#vC{9L?1m!9^8p7?u7=IJf417o_rvp%=c^PBEcO51^S@5Zc`xG9?Bo|;>IC} zj#l6@bGR-3Ns(3q2?F^D0{&IJtTZe1xV~2L*(U2(xpW!+iB*6e z5Z`{4#Jajy;9ux}MvI)`e<>;O|EgBZcwZfRI^drdAa|kD{@*0yAMYJxj(1b*8wr2N z5_9+5-*soAI=tm?#Xo*6@Xv^b4~r!9g2Wri=>_~NEpFxTVW@-t$KC35Dp9vA?~_Uy z;t73-1;oO7YusE2E9xbEDx_w_1UCN1#y?d}D*y{ZZxbm2hq=s+a~b|g0fmo24f08W zFgy~$tR|gz!>u%w0Dt%vS8~9hB z$N%?Lf&W|X&iLoG*r9f-Djd`$q6V=?*!8t3uhMAv@o{jfVs=?&gN^Zz4gD`e|6~6O z{m*t*Ti_o@J=V?p)!+GAAnt(>2qH#K@joy6fARka{qKA`{#%kGqhiPO7^qmYb!R!E zSQs_s6I!Amq*)(ZZvI%O0E|vCtXEVntw37>(*gfv3zmY~M+9)I(yf3Gla2<&p|efV zwmdW(GyynN@vkCHxPTB(gtYC=U0#&z-#?zSe;L88R15rzF@0U8)a`A^6!@oy^qui9 zy@|(>eIfpsD81K~52OBs3b~J?ZXS6d|a_PW2t0xEN#zfk{|rLG~d{I zLPdcz(91{`1OX|y=ymUme^NkiBLb*v+5#kqbbv0MSx~IjcXe|P`NyW_aZZo|+5)ol z4(T-Cg?V1f`~LKE9{=ZS|Bp0-!NZP z=K%jhXZSbzl=Od@jZB_-rvDp)^HK2M=>H|74@k;8UD!(2to5pWMh1MoVMUx4m;ytx*6C**RflP zG6JrM0~n?uAQBJz;#`6q{-g<@9T9!i#Ui~7*ybe2kyhs2IF<1K4|xClhhnXw6Y+0S z{Le?ke-H3)Or9*d%})KF690b>{L?M9aWmeZbN&6t=+V5h760ZyqyN{?=>O%F60Nv) z?PKsC(V{7pv~Kiq;G$>z3;N&S(Z}67tBFam)drh#a5KPo_I6I?_MaX67pI)l>pt+m zzhE00(8YWz`hwrv@@xOPwpXQYtY9#R2NF-_Ieps={z;ankR7x){bHzY(nOjdGuRiL zUuIli%n!tg9y+pt2p~IH=p=|)LD-7|u~?P0W#8bE`>p-R%`(zXvIO4vinkC~?#@}UYgp{yQ|f8q$P9h!v?JSxD%WS`)HF zK}s2yi2oTfw&I`uS@6#{Yboeif#2hQ=FCU=(*!gW@tgV||Bt}G@c&HDUTyt%l;OsW zcs%R++YiyhIVW@GpYb27Eux>=H|f=^N7nZm|BcW-3dqp5_bAQzN2yGrJMb$p;$P^0 z;(z8tdbcj+)B9XngHCTZD@&8^%9NAnI4ZWA9s9RBY_HC^t5N0pq@|qFhCE8$r{e$3 z5~6a6>c<==o&n&ss_X9YPij;V4FPflT#4btvH<!VM(he=ekT>2g7RB%rq#j4qopRU#&dtm_2RZCS9aBBncH8kQFpsfZUilU>yKZB7Kg1){GYi-|8Hab@7xWC z2FPfIw?S$GC=`;2fHx)ajeNfAAEZg^#$b9%D&c>6E~Ig43lT#qX0n^DKD$EY&^QCS z!kB$<(KWK=o!s+HANXeu-`Kgr9OJ8 zmJm#yl6*nrlgA}FqM_k&UesEabLW|;@o#@BPfj`n{>A(c+>A#NqHYm^$~4toZ|Gr` zPl*Jz%umR~p69|;7+@~QznB;LG|eXdb6yeuQ^~ql$F0u~U97p6E!kF>=lLkQdKBxt zuN*}e_9CIA#jfjncI;*4JGbr3|KGs>;;~1=_+NtvWfb^t{726h0xp;s?Z=1MuxP~K zFOwMmPI^uJs~p;Dzp0$Fxg2&}_f~n2$*JwH+6E?_#U*!P-Ba51mbc#6xx)40vO7QJ zObyyhKE@FD<|3+eRKs!@ECfcR--pTcRSHfY?&jtuc;g5xn~%OT{z(A<15Ug{1WUt( zXbWfpfb(cYQs4hJW#khA9&WkB5MEUDw1ydt91o&E|5di)CgyeWUdd0oABS zu`beSicS3I{c_?zt668EUw1fb%f(d8vz5v@iWPUI=IOi_%C1=2?$9f%Y4yg8&F@k+ z;~!Z6j`hD*PyAXXoo=UX*;y0Nu*6MQ6L6`<+8)K|epdYuk^jQcO4|qjNB=AB+L4T9 ztm=*i9RZgr=2c|;ivFM?8c_t}%5Xw;*&d3kVo_x>sK^8q{eC4gp9I`WhnZkk5#!f3 z4SMs2-^9n z)dm4)5Gn)*A>uZg0~i$Yg69?yAXow+;+%^#G3UD8nE&uEKM%~#`bJm%t@xK*8Df=_ zt~cfwPh~9=^PY6pJUUIt>uco-Di~`^n}XtPrq{%ODWn4bm5i8tx|-5&&e|T|@;tlm ze|+ML+Hur1AUWj> z%>rL?0@~qO3CEvt^Z)C|Q9FAv-ihv5#W5c0zcIk}&Z!GoKV!HqSVYr;K@Nr`S zQzhpNxNNl;Q#$Bg9=U(K9bo@@-*q@`n8;{G`_-Exh9_ILXFK+z8SUPrYIoAOF>XzV zRSK2fs_zU z5hAEvQ~C45Yy4X^vlZ9+OemkUu|iGIqs+#&g98BDI94(+r7h#UrU#AelU2i1%{Vh? zny!hT>P5q7(ZHPp2^POr5&TSaH}ymC=ci`>je<(}2Wj9RMcOR<2gH9fO->+1yakYB zBcsECJ(vf#2nac$b$}Y}?(QNW*u0P&Jg96iB_slxL<>d3!JnMaW+4}(2J$7bVr+tB zH764N4u|vQQ}5cGueM-K44Cl$NEksBPsb{r(e>8sT|DQb;A7m|PL!tH``d|;MKLr| z!au9#;Yxz}%UYB_DM&GLwUdm&s(TMXJ>m)#Hx1(T_)%^5PA4C#n6;M7^n2F^WJtr#Y$ z(7PO_m{0C9Yb_Q}BAE#XBR-$s<92!7_E^{x4mzVDr&^^j=+ud*o1Ocinl7g)9(6~< zF1_x`aRsf#zw8{H&wM_>68=w*Rr9uwg#Tvxv^c17SUn)es@>VyK^%ZOYM(ETJt9$k z1cZ$of^8@eQiC~6*D$aL|9oyf6C2F|Mlp}WkziRgCG}RhQ zPC^_S=*hDyo&INp2w`c@Zx4h^)@R29L-S1bF7VH*QM{UcKH*as^pQ)G)rUpMbgQ(P~sadU#0=5LAkYT)TxvY74 z(fiF;sb7A|{_*GBvlsmnbM9!0DXg1$KRuC3$IOS)^oh-%oA~cFsWv9UyNlsc%4oM) zYNLhyqlM$U>(}=e2WzEFzi%KDs+CgZLMopPj}I3@;pF5ICHARD?f3 zLQuI#K{Mr#jsND0(CXOTI5tt^SOhdf$O7CR_bEgOR1;qZz2}6iqTboCCJ6Caq(Gl6^IxsZ1jYP6FNzG3eSBv=9QTe5C0~5K`SfI= zKA75CsiE!;4<=_Pa)nIj>jz8xG&GtC#l5kp`(S@!ay0Ymyck7a#|TMupo zoxKMOjwb%yE<{ywSX>RI-H?_x_DYB48+bjU}&M z6jQ?*8*2tNu96G}Ec-=?073P-_m{6qMxE*p&&#z2{s}w4=T90U;O)ZJz`uHFlKI~( z|MhjwYWDo}{hr38y>)ZO?KIJ{M}kgT1a#f^FSi-l_5A5tE*(I>-8)%`hh6=d;7GN9 zY$)}IZ?**f1CHm0kk!P5NJc&W>%pIsZ?OSSS|eL$bn4_U0zxe`+q&r=5 z7bO(B)rIou@!wh=1QTII?X!_d2tVRLR;3MbAj61lb3-&DkqBHC|Lu^TM3z5d$D_A#7OwHBlsGhPH*Vz#FTvZ%*HDKXCcd^d2H>B3wQ=iYacZVOi}32(opL_H zk>&{@zPUSgW2X+`@p-J^|K~374??l%5oQqE6kbFC%p;x@kRiz`xTa9%y2&l$-fqyT zlf_NSm`Q2ZXuf@sW^#t+{BIP88sJW(TX>Kz6i&kbtlN1X59n7MN}d24t4n?D>SW1gq4_UGotD*4FEudY*Sl)w+qk8)}M zFE4J!s{J&AFMhesaegxY%ZqL42>!o&vN}{raGa`VFIM&YTmfScW>`0vfipwJQKZcT z|FQUQWGC$@hD+D|O@oJrBYVxfB`a7o0cc1l0oZ^iG}`($Gl$^$&lhb62gy{w)1%WJ>?aCi7@RB*L|1*&pgxy7 zG5^2asAT^CQa}#=4c5T7-|W{iv9sMUyNCU5PH zA8n2LJ=T!lTPsHY`0_xbW=3e|_dnh0lPPZ8zkZSbNDPHmOo*`FAobFU;t8?np^hjc z;1^9puWE2fJ-F;-z7exQ9<-=cy1whT;vb>dkpjMbplV&3toqzBr7?XcD1u%}*wnr8M6-ZzdB#W zJj5`p52e0)vCZ{oPgV<=V1Lwo|6~DcHPThE8ySjk&P^~ri@~B$+10ScL$QU4ma|d=<-Yzma@AuHZz_(zB!}Q zs?Ov8)tu^Zt+g4Z^dIsy)_@0SRJ@S`#>qBJ$iDSY_W ztZ%Zs6%Aw@24|nep()H*i{nf!c&X+mSv0N1YPK2VyQ_`$zn-T84mExroImAU`0Ho% zZj9+I`Uf{>@%-=aPw=@1l7X)uF1`F_t2UI1CVcUz=kCFV(_z|}pMHF24n|R`X2&Lr z3(E_ch>Onw|GKhc-yQ zL?RQQK&@d9&163_{Nv&S6-WRw&>*)hBG4!^DqRYV6S}goL9OFq{rX-`5U?2<$>dUjiQ)Y1gW0bitiO1)^22vKz+j}FwOS0) z{3n;;pAzA;5zBRH#d2Mz1#Gkeq8D4%3;tu1`ZoA)^aRq?#U!`V6n1BF^F|{AyJX5; z7O9y#_V<~!tDE+fy15WnWmDm~i2_n=)N6bpC#@LXt&OD$_`iEJiU0HFjRl-thX=F6 zBl%Fwd;8W>B;X)>_`Al3Qyg*m(jhoISE5hE<+Bc#GdFjqhY6gr86yGj*hFq=rbZo} z`(*F%Cn^G+kGDS*3eic#37xQQwb^Hhf3V#)Vxdu!Vu~k%=bQK^9BY0qFfimhn6)`v zs?AkLX4q65!{<*FaGSb^L$RO?ei#-`5BXoO{|^4S8=j9RABIq1K^PYcT%3|9x_JCv z8&WV-PQZvzhe`42C{KYbWr7#gosibbnY*QaWn zp)@_$m}43IM<;Zb;lHueor!J(sRcaO1Rzk9&S)`tJ&MJXaG&KA|J5`>5}IlucKvYb z#rM1S9mTI55qc09*EGge%jsy@5B z`sC3vjmWK&h5h|;v>8(%$Lo1ZUZQ_~c-sr0K{Li0m)2(~10jBX}E1%kmK7u%-s z`Qe|>3PW!A_h889IXrklbu)O1&QqFyDjV{@u@+O?OpkYbp{V9?+deR6&yQ$us5d^v zxz8ajvu=W7(Ph9ef#&+}68_s(a`zWo=YMXCf7qP>zgV$d-Q+M$3=8}-1X9llgpfP&6~`hjE9(Rh zhVp(JUmWn`^C5DE&QqiShhbahBfaM{!#^%S(aEJ{C1|m9HD#1Y&RGL5^riUtOdco` z{XWQtAph%$q`mj6qhN8^e7tYZPJ8mB+(l1Q6CIq0oiwp5Xk0h}X`dMXWFDgiUWfEu z6m1GihM8TkG_L{w+Dq`yZSX_M3VyS8PCLa&T%nXl!rBY?r$4+|ArgG9q5n^gra0o% zU0EG0SNl1Jd{uqSeoBbuMk8o*C?Qip^;GyH3`!uX{Dm&@K5Wh8Jf zBtaax0sI3b3IC&`&e^5N?v&xtzJGDWd2-#6pAVGA4MsEUrM1x%;D;MXmNhB;qwr7L zhHxU|a)k;m)F{{K7smd@$yr{|3j90eZS_C5Vl_jbnChyzmLFHB5Nd`3A|2R1UE-er z58~E1;tpC{8!8WkIfneN11vjW?*slsCD6T#pOdf|*bd-F3ZzlAt@U&9jh`R>p$E9f z1wB@4gqItg*rK$k4SEL{IYKkRb+KB|a;U%Td3DpfyX-tV^4`DUEG~p<(>9Y?EF9TT z`$A2U6n{ee8xM+R`qSf|mkRzP+BYNP5!f+9)H)sgU!pkx#G*t!l?7d7OsjJ$F2g_R z0CtwTqbfM3ib#QUkgP_}8eZaSUE&|8j}0c-p)`qf5TlqT0BS_CpK>E$fW9qyctJkO zAKHWSM;am0IUAoD{vpNqM&P$_9D~tHE6;@X=rukoJ^{eb27aVD%ps>vn`&!6F4O7% z`b~7aY`S+6xPQ}CTnvvaxS3)TLjeT~u6VrBtPl=?)SPnEe+d2?UlYLgQB2^9GZQsV zF>wcp76HRY-B(*OM#eOH7c-5@&8_~=mPl~n2-OQmC`)NzN}w*NGzt8?qI>*PsgQhj zoCW;jgnkn$xijiM;I9Zi42NiWz>kv$w-5bXfS(TEZr<{l;a_s`V#lE;B||`(&=^(8 z=N3i*VQpfps~E{k*h4yJUfW9)qJ4jT5s&!PPacNu-w^m8Tk>f%YGY${QB)(IMht3@ z(gZ(s`;Wta^TP^3z!0G_<@ON5<4;pe?lLVzkV!0RPQ`icT@9MbPUF z6MOE|yiMoo#D92FqEe`S1OYjEoz_+Kf9q49_#6!{S-aAl2~vauSE!2abMkrPzgZ6` ziqjT>iTrd1G^SD|XDagL%}6lo`j&y}`(8Zf&mOty=T%C){K2bFo+*|5ccT9< zer7%~^PDWL#4G)>zdVjJ_Xw-Aox^`;GwuuiFV^Zuy@GpR>ewZuGZYFD$&by86cFKm zE~>fvZ2WHVkE?Ke+&eTGy18ik_D(3ktAa( zr!31WR!7XV_8{zXz8?S8`Qswj*uPT;_z9B;ekso2>ge%v$w+vlV)knwm4!c^=nzZ z`eOXsB?-LyWgM8eF+n@iU_x@}$%@5d==|2npx!<83s3$I@z2LK8WbD5F|)_~%aicU zWhb6bD&|*$e^!VnCN(Q#rkQz*Ic%J{8Fbi{7pHE!@Fm3s3I8M8-pq_g=a8waazjw9 z)bvgrhpMyZ=f7&3f^?B|LLu$=^`Kg>o?o`j4I1+a4O91jRQyX*1=5)-p`AaadBu?h zqI$aT((O>Li$DDi@vo3yi-q*tM~RTJ_wO&`6J?V$Be#VAds6(vvLAN&>HVLWA32ESo=&u_CSy2L+uhW`w&xYKo)#UayMWLCe*Xy_Z;4^+pobuNDY_E$(J z5YUWn`;{8i!klHQV#q`_VrrQS@z3p_zv5Ncjuf}U6b;z9OnrmSXWR;cKny2>aK-el zaQkl^|M7t4=WnB1L)zJcDC6I|Y2s0CkpIQq=;EOEFE6A0L9v)M8~A_uFm&&Rb8tRX zUw%FR?Pawo`Tq|$Q+e;T|9qYrh<$*dNAS;>;&encQ8tX%jT)D}vg|RN2}pjO3vzpI zZ~g{u>C~=GJNqkkmrce}Vor||+zzgI%Y!W2+PqPmoJ&Ni1nCaa@pv`0MX%PVT$nJA z=5_rcmDMEsuv7O-JRidrPbTvd60|8TDo5Nt8%8dHZ6CzQeWTcK{_$JKf7GY`!&i~r z5$*hTqz?WSblP^>v2*3J-q{uZz>xRRk?X5le&8WhiPb2>7Pxch9GnUcFL=R2=cH&y z82_@nn=JZ!{_RCF6ZrsY3d6*1ma$F{OCZ&&dV@uq8FLs6BD@lI!+4#y>U5YDvxCNRS`+ap=^vDz zTNCO{EOq}E$VbUbBXgOV0S^!U9PC^?oalV6)5-e*joc0y`>o@jeyeZq2KMXv(HUQ@ zU{$MFUW#3D+0HmzlR-JTDW>m!@i?$OZD+{|seVj)+2dW$oo#!%;4aslnztpz4Su(J zekD>1$^Y^+7Wa1*aNFTq@DmEwZ0HNR6;PnzoYt!B)4R0Ux^r~XH@OpB-eD5&@YYu3 z+`h3HS=kNG?1sj-{iSJ_J)|eTFXUFR*kahL3OeN)x7L|56&F3jJN~)7(CTh@bMt&6 zq`AHkUfK>#Yz2nae2JQ!)xLbXp5dG>6IJ&qJVC#+I0%W@SykBjdX`}8b4*$7o&EMez~G0e9mj{nqIg45{-)hOt0;O z0;?c9-W|`7uYZV6j-$4$ZZ&M;Y>{ zB0g2vt*|J1v>t7|Vk?e2EA#HsO8a!OE1trlJ3sA8583T;lU=QdyJXp@I_y=@N}?!4 zEA$>4DjljX^vE z(^V7n;60#m`X~TnibR?Kk6jMsNCegSn7WYB6pFh1fWDkI)MAFgxbfUxiyJE$L$P2e zmGz~ZwwO?}7yyZ1L@~}sd3aK&)bvCETmUYFvzADl=p9pD^IIrMEg@P`h*5sn< z{(vgvQo5`%lfI9!auCAz*}y-Zd{{j`d~`ngALc{!CjDHWhV5L;{BIrqN_nrxuBfK< zHy2!gI1c~I!}z~FPITHICI0vzdT+-!Rx?MuswVlNW*EU_Gt2V{-PXM0yW63^KYL43 z|N1ELmy_tdb>j`?+_Morw@Hj&zxd9-dC$Msa! zS2EgUSj~!!*du_Sih`Rpb;VW7CEefD|Jb}Ze56IJ-Z#;A%gWhPzjgehk?1q^I+W#< zex+``vFNzF>N;6#zu#SR-CS}qo8Rz&0d9cXpWyeinh^6l&;&rJ@Z`)7nGfe3cUGMp zlDf6z+?cQqW%YocHJw{sG|e=xdIG>0l}R>85Y*A-6!9w45j8ymwXAkHuLZ-S#q&EC zhX=H^jJDXX$wbsqpUUe1w{nX?#twDyyA(7fxwxjB)(+;{CqlBMGc=&3_qmYJq(W+> z5Ak8$E{(buJ%fIDPyACGBta{}#tGY1>*s^t|69jD6@t#hFO7ndV%Y3v1zwBGcAT6L z6_C#=v=>|R2K;)MgvX?l*^De%D8DQnuG>ulU|0=k?2Adg+m|8(B!v|~_GkqlLf8VV z915iI)272};7yBgZs#ID^@r5V28_)Mce0uKjJjUEM(oHDIvj}zdzCZ<$&ea>x;!0K zBA|-;l!%PWDmSC~RbtUPvP3<9e%oh&|Ih5BG(>(mD*%i|V5-xiAV>pi#cNmk z9Ori~vU_YGR;YbRz>#7o8A~p5f|bNYe&PmhlkRVu4rG!S(PZEaM)L!HG`|G?UxBv% z@Spk%{y%)3zITd31n|*|sTF!?T$s`d3`0T?2d##gb=&M*hca;+fCsTW2xwt<>^Qg- zBAruN;I_*WA!YFjwYU@M6KY>3QlLrtEWyu*`hx%WM(f8-0N^!uaM)pQ-X@(ivvuw| ze|AVjf_n3jW+I>K$rt>8u3z>`xw@%=FZl1KmcHEcxz@lJ{C}=r_Di|Csev!}@1~Z% z-1E8Cz!&^~u3z>`xw@%=FZl1KmcHEcxz@lJ{C}=r_Di|Csev!}@1~Z%-1E8Cz!&^~ iu3z>`xw@%=FZl1KmcHEcxz>P$|1ZZcHSoKwf&U*U5ht?% literal 0 HcmV?d00001 diff --git a/samples/lights/romdisk/PLACEHOLDER b/samples/lights/romdisk/PLACEHOLDER new file mode 100644 index 0000000..e69de29 From fd3e7cde6c870da795438a2b79c0efca90dd56a7 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Thu, 14 Nov 2019 21:12:27 +0000 Subject: [PATCH 37/64] Improve the light sample + tweaks --- GL/lighting.c | 2 +- samples/lights/main.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/GL/lighting.c b/GL/lighting.c index 6283350..d75ad65 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -395,7 +395,7 @@ void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex) { const GLfloat k0 = light->constant_attenuation; const GLfloat k1 = light->linear_attenuation; const GLfloat k2 = light->quadratic_attenuation; - const GLfloat att = (light->position[3] == 0) ? 1.0f : 1.0f / k0 + (k1 * VPpliL) + (k2 * VPpliL * VPpliL); + const GLfloat att = (light->position[3] == 0.0f) ? 1.0f : 1.0f / (k0 + (k1 * VPpliL) + (k2 * VPpliL * VPpliL)); const GLfloat spot = 1.0f; // FIXME: Spotlights const GLfloat fi = (ndotVPpli == 0) ? 0 : 1; diff --git a/samples/lights/main.c b/samples/lights/main.c index e02d85a..a0e94fc 100644 --- a/samples/lights/main.c +++ b/samples/lights/main.c @@ -171,6 +171,16 @@ void InitGL(int Width, int Height) glLightfv(GL_LIGHT0, GL_POSITION, position); glDisable(GL_TEXTURE_2D); + + diffuseLight[1] = 1.0f; + + glEnable(GL_LIGHT1); + glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuseLight); + glLightfv(GL_LIGHT1, GL_SPECULAR, specularLight); + + glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 1.0); + glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 4.5 / 100); + glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 75.0f / (100 * 100)); } @@ -189,6 +199,14 @@ void ReSizeGLScene(int Width, int Height) } void DrawCube(float x, float z) { + static float pos = 0.0f; + const static float radius = 30.0f; + + pos += 0.001f; + + GLfloat position[] = { cos(pos) * radius, 15.0f, sin(pos) * radius, 1.0f }; + glLightfv(GL_LIGHT1, GL_POSITION, position); + glPushMatrix(); glTranslatef(x, 0, z); glColor4f(1, 1, 1, 1); From 11cd54bc0b717c06151e499fc2fc7afc3317a387 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 18 Nov 2019 17:39:09 +0000 Subject: [PATCH 38/64] Add partial support for GL_ARB_vertex_type_2_10_10_10_rev and also GL_NORMALIZE --- GL/draw.c | 51 ++++++++++++++++++++++++++++++++++++++++++- GL/immediate.c | 28 +++++++++++++++++++----- GL/private.h | 2 ++ GL/state.c | 13 ++++++++++- include/gl.h | 2 ++ samples/lights/main.c | 2 ++ 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index b1f98ef..bef822f 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "../include/gl.h" #include "../include/glext.h" @@ -102,6 +103,7 @@ static inline GLuint byte_size(GLenum type) { case GL_INT: return sizeof(GLint); case GL_UNSIGNED_INT: return sizeof(GLuint); case GL_DOUBLE: return sizeof(GLdouble); + case GL_UNSIGNED_INT_2_10_10_10_REV: return sizeof(GLuint); case GL_FLOAT: default: return sizeof(GLfloat); } @@ -128,6 +130,32 @@ static void _readVertexData3f3f(const float* input, GLuint count, GLubyte stride } } + +static inline float conv_i10_to_norm_float(int i10) { + struct attr_bits_10 { + signed int x:10; + } val; + + val.x = i10; + + return (2.0F * (float)val.x + 1.0F) * (1.0F / 1023.0F); +} + +// 10:10:10:2REV format +static void _readVertexData1ui3f(const GLuint* input, GLuint count, GLubyte stride, float* output) { + ITERATE(count) { + int inp = *input; + output[0] = conv_i10_to_norm_float((inp) & 0x3ff); + output[1] = conv_i10_to_norm_float(((inp) >> 10) & 0x3ff); + output[2] = conv_i10_to_norm_float(((inp) >> 20) & 0x3ff); + + // fprintf(stderr, "%d -> %f %f %f\n", inp, output[0], output[1], output[2]); + + input = (GLuint*) (((GLubyte*) input) + stride); + output = (GLfloat*) (((GLubyte*) output) + sizeof(VertexExtra)); + } +} + /* VE == VertexExtra */ static void _readVertexData3f3fVE(const float* input, GLuint count, GLubyte stride, float* output) { ITERATE(count) { @@ -748,7 +776,7 @@ static inline void _readNormalData(const GLuint first, const GLuint count, Verte 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)); - if(NORMAL_POINTER.size == 3) { + if(NORMAL_POINTER.size == 3 || NORMAL_POINTER.type == GL_UNSIGNED_INT_2_10_10_10_REV) { switch(NORMAL_POINTER.type) { case GL_DOUBLE: case GL_FLOAT: @@ -766,12 +794,33 @@ static inline void _readNormalData(const GLuint first, const GLuint count, Verte case GL_UNSIGNED_INT: _readVertexData3ui3fVE(nptr, count, nstride, extra->nxyz); break; + case GL_UNSIGNED_INT_2_10_10_10_REV: + _readVertexData1ui3f(nptr, count, nstride, extra->nxyz); + break; default: assert(0 && "Not Implemented"); } } else { assert(0 && "Not Implemented"); } + + if(_glIsNormalizeEnabled()) { + GLubyte* ptr = (GLubyte*) extra->nxyz; + GLfloat l; + ITERATE(count) { + GLfloat* n = (GLfloat*) ptr; + + vec3f_length(n[0], n[1], n[2], l); + + l = 1.0f / l; + + n[0] *= l; + n[1] *= l; + n[2] *= l; + + ptr += sizeof(VertexExtra); + } + } } static inline void _readDiffuseData(const GLuint first, const GLuint count, Vertex* output) { diff --git a/GL/immediate.c b/GL/immediate.c index ae3ad7a..69b6870 100644 --- a/GL/immediate.c +++ b/GL/immediate.c @@ -38,11 +38,11 @@ static AttribPointer NORMAL_ATTRIB; void _glInitImmediateMode(GLuint initial_size) { aligned_vector_init(&VERTICES, sizeof(GLVertexKOS)); aligned_vector_init(&ST_COORDS, sizeof(GLfloat)); - aligned_vector_init(&NORMALS, sizeof(GLfloat)); + aligned_vector_init(&NORMALS, sizeof(GLuint)); aligned_vector_reserve(&VERTICES, initial_size); aligned_vector_reserve(&ST_COORDS, initial_size * 2); - aligned_vector_reserve(&NORMALS, initial_size * 3); + aligned_vector_reserve(&NORMALS, initial_size); VERTEX_ATTRIB.ptr = VERTICES.data + sizeof(uint32_t); VERTEX_ATTRIB.size = 3; @@ -61,8 +61,8 @@ void _glInitImmediateMode(GLuint initial_size) { NORMAL_ATTRIB.ptr = NORMALS.data; NORMAL_ATTRIB.stride = 0; - NORMAL_ATTRIB.type = GL_FLOAT; - NORMAL_ATTRIB.size = 3; + NORMAL_ATTRIB.type = GL_UNSIGNED_INT_2_10_10_10_REV; + NORMAL_ATTRIB.size = 1; ST_ATTRIB.ptr = ST_COORDS.data; ST_ATTRIB.stride = 0; @@ -141,10 +141,25 @@ void APIENTRY glColor3fv(const GLfloat* v) { COLOR[3] = 255; } +static inline uint32_t pack_vertex_attribute_vec3_1ui(float x, float y, float z) { + struct attr_bits_10 { + signed int x:10; + }; + + struct attr_bits_10 xi, yi, zi; + xi.x = x * 511.0f; + yi.x = y * 511.0f; + zi.x = z * 511.0f; + + int ret = (xi.x) | (yi.x << 10) | (zi.x << 20); + + return ret; +} + void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { GLVertexKOS* vert = aligned_vector_extend(&VERTICES, 1); GLfloat* st = aligned_vector_extend(&ST_COORDS, 2); - GLfloat* n = aligned_vector_extend(&NORMALS, 3); + GLuint* n = aligned_vector_extend(&NORMALS, 1); vert->x = x; vert->y = y; @@ -157,8 +172,9 @@ void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { vert->bgra[B8IDX] = COLOR[2]; vert->bgra[A8IDX] = COLOR[3]; + *n = pack_vertex_attribute_vec3_1ui(NORMAL[0], NORMAL[1], NORMAL[2]); + memcpy(st, ST_COORD, sizeof(GLfloat) * 2); - memcpy(n, NORMAL, sizeof(GLfloat) * 3); } void APIENTRY glVertex3fv(const GLfloat* v) { diff --git a/GL/private.h b/GL/private.h index 35d3407..d079269 100644 --- a/GL/private.h +++ b/GL/private.h @@ -316,6 +316,8 @@ GLboolean _glIsLightingEnabled(); GLboolean _glIsLightEnabled(GLubyte light); GLboolean _glIsColorMaterialEnabled(); +GLboolean _glIsNormalizeEnabled(); + GLboolean _glRecalcFastPath(); typedef struct { diff --git a/GL/state.c b/GL/state.c index 239f5d9..e40295b 100644 --- a/GL/state.c +++ b/GL/state.c @@ -32,6 +32,7 @@ static GLboolean SHARED_PALETTE_ENABLED = GL_FALSE; static GLboolean ALPHA_TEST_ENABLED = GL_FALSE; +static GLboolean NORMALIZE_ENABLED = GL_FALSE; GLboolean _glIsSharedTexturePaletteEnabled() { return SHARED_PALETTE_ENABLED; @@ -83,6 +84,10 @@ static GLenum BLEND_SFACTOR = GL_ONE; static GLenum BLEND_DFACTOR = GL_ZERO; static GLboolean BLEND_ENABLED = GL_FALSE; +GLboolean _glIsNormalizeEnabled() { + return NORMALIZE_ENABLED; +} + GLboolean _glIsBlendingEnabled() { return BLEND_ENABLED; } @@ -333,6 +338,9 @@ GLAPI void APIENTRY glEnable(GLenum cap) { case GL_NEARZ_CLIPPING_KOS: _glEnableClipping(GL_TRUE); break; + case GL_NORMALIZE: + NORMALIZE_ENABLED = GL_TRUE; + break; default: break; } @@ -387,6 +395,9 @@ GLAPI void APIENTRY glDisable(GLenum cap) { case GL_NEARZ_CLIPPING_KOS: _glEnableClipping(GL_FALSE); break; + case GL_NORMALIZE: + NORMALIZE_ENABLED = GL_FALSE; + break; default: break; } @@ -671,7 +682,7 @@ const GLubyte *glGetString(GLenum name) { return (const GLubyte*) "1.2 (partial) - GLdc 1.1"; case GL_EXTENSIONS: - return (const GLubyte*) "GL_ARB_framebuffer_object, GL_ARB_multitexture, GL_ARB_texture_rg, GL_EXT_paletted_texture, GL_EXT_shared_texture_palette, GL_KOS_multiple_shared_palette, GL_ARB_vertex_array_bgra"; + return (const GLubyte*) "GL_ARB_framebuffer_object, GL_ARB_multitexture, GL_ARB_texture_rg, GL_EXT_paletted_texture, GL_EXT_shared_texture_palette, GL_KOS_multiple_shared_palette, GL_ARB_vertex_array_bgra, GL_ARB_vertex_type_2_10_10_10_rev"; } return (const GLubyte*) "GL_KOS_ERROR: ENUM Unsupported\n"; diff --git a/include/gl.h b/include/gl.h index a1d2f57..7679abf 100644 --- a/include/gl.h +++ b/include/gl.h @@ -335,6 +335,8 @@ __BEGIN_DECLS #define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 #define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 + #define GL_COLOR_INDEX 0x1900 #define GL_RED 0x1903 #define GL_GREEN 0x1904 diff --git a/samples/lights/main.c b/samples/lights/main.c index a0e94fc..ea867e5 100644 --- a/samples/lights/main.c +++ b/samples/lights/main.c @@ -181,6 +181,8 @@ void InitGL(int Width, int Height) glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 1.0); glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 4.5 / 100); glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 75.0f / (100 * 100)); + + glEnable(GL_NORMALIZE); } From 140eec3d921086124cbdf735e2d935e2b60c831b Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 27 Nov 2019 09:10:10 +0000 Subject: [PATCH 39/64] glNormalPointer should accept GL_INT_2_10_10_10_REV, not GL_UNSIGNED_INT_2_... --- GL/draw.c | 20 ++++++++++++-------- GL/immediate.c | 26 ++++++++++++++------------ include/gl.h | 1 + 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index bef822f..a5823ad 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -104,6 +104,7 @@ static inline GLuint byte_size(GLenum type) { case GL_UNSIGNED_INT: return sizeof(GLuint); case GL_DOUBLE: return sizeof(GLdouble); case GL_UNSIGNED_INT_2_10_10_10_REV: return sizeof(GLuint); + case GL_INT_2_10_10_10_REV: return sizeof(GLint); case GL_FLOAT: default: return sizeof(GLfloat); } @@ -142,7 +143,7 @@ static inline float conv_i10_to_norm_float(int i10) { } // 10:10:10:2REV format -static void _readVertexData1ui3f(const GLuint* input, GLuint count, GLubyte stride, float* output) { +static void _readVertexData1i3f(const GLuint* input, GLuint count, GLubyte stride, float* output) { ITERATE(count) { int inp = *input; output[0] = conv_i10_to_norm_float((inp) & 0x3ff); @@ -774,9 +775,10 @@ static inline void _readNormalData(const GLuint first, const GLuint count, Verte } 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)); - if(NORMAL_POINTER.size == 3 || NORMAL_POINTER.type == GL_UNSIGNED_INT_2_10_10_10_REV) { + if(NORMAL_POINTER.size == 3 || NORMAL_POINTER.type == GL_INT_2_10_10_10_REV) { switch(NORMAL_POINTER.type) { case GL_DOUBLE: case GL_FLOAT: @@ -794,8 +796,8 @@ static inline void _readNormalData(const GLuint first, const GLuint count, Verte case GL_UNSIGNED_INT: _readVertexData3ui3fVE(nptr, count, nstride, extra->nxyz); break; - case GL_UNSIGNED_INT_2_10_10_10_REV: - _readVertexData1ui3f(nptr, count, nstride, extra->nxyz); + case GL_INT_2_10_10_10_REV: + _readVertexData1i3f(nptr, count, nstride, extra->nxyz); break; default: assert(0 && "Not Implemented"); @@ -916,13 +918,13 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei if(FAST_PATH_ENABLED) { /* Copy the pos, uv and color directly in one go */ - const GLfloat* pos = VERTEX_POINTER.ptr; + const GLubyte* pos = VERTEX_POINTER.ptr; Vertex* it = start; ITERATE(count) { it->flags = PVR_CMD_VERTEX; memcpy(it->xyz, pos, FAST_PATH_BYTE_SIZE); it++; - pos += 32 / sizeof(GLfloat); + pos += VERTEX_POINTER.stride; } } else { _readPositionData(first, count, start); @@ -1259,7 +1261,9 @@ static void submitVertices(GLenum mode, GLsizei first, GLuint count, GLenum type profiler_checkpoint("generate"); - light(target); + if(doLighting) { + light(target); + } profiler_checkpoint("light"); @@ -1507,7 +1511,7 @@ void APIENTRY glNormalPointer(GLenum type, GLsizei stride, const GLvoid * poin NORMAL_POINTER.ptr = pointer; NORMAL_POINTER.stride = stride; NORMAL_POINTER.type = type; - NORMAL_POINTER.size = 3; + NORMAL_POINTER.size = (type == GL_INT_2_10_10_10_REV) ? 1 : 3; _glRecalcFastPath(); } diff --git a/GL/immediate.c b/GL/immediate.c index 69b6870..8d13d9c 100644 --- a/GL/immediate.c +++ b/GL/immediate.c @@ -61,7 +61,7 @@ void _glInitImmediateMode(GLuint initial_size) { NORMAL_ATTRIB.ptr = NORMALS.data; NORMAL_ATTRIB.stride = 0; - NORMAL_ATTRIB.type = GL_UNSIGNED_INT_2_10_10_10_REV; + NORMAL_ATTRIB.type = GL_INT_2_10_10_10_REV; NORMAL_ATTRIB.size = 1; ST_ATTRIB.ptr = ST_COORDS.data; @@ -141,19 +141,21 @@ void APIENTRY glColor3fv(const GLfloat* v) { COLOR[3] = 255; } -static inline uint32_t pack_vertex_attribute_vec3_1ui(float x, float y, float z) { - struct attr_bits_10 { - signed int x:10; - }; +static inline uint32_t pack_vertex_attribute_vec3_1i(float x, float y, float z) { + const float w = 0.0f; - struct attr_bits_10 xi, yi, zi; - xi.x = x * 511.0f; - yi.x = y * 511.0f; - zi.x = z * 511.0f; + const uint32_t xs = x < 0; + const uint32_t ys = y < 0; + const uint32_t zs = z < 0; + const uint32_t ws = w < 0; - int ret = (xi.x) | (yi.x << 10) | (zi.x << 20); + uint32_t vi = + ws << 31 | ((uint32_t)(w + (ws << 1)) & 1) << 30 | + zs << 29 | ((uint32_t)(z * 511 + (zs << 9)) & 511) << 20 | + ys << 19 | ((uint32_t)(y * 511 + (ys << 9)) & 511) << 10 | + xs << 9 | ((uint32_t)(x * 511 + (xs << 9)) & 511); - return ret; + return vi; } void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { @@ -172,7 +174,7 @@ void APIENTRY glVertex3f(GLfloat x, GLfloat y, GLfloat z) { vert->bgra[B8IDX] = COLOR[2]; vert->bgra[A8IDX] = COLOR[3]; - *n = pack_vertex_attribute_vec3_1ui(NORMAL[0], NORMAL[1], NORMAL[2]); + *n = pack_vertex_attribute_vec3_1i(NORMAL[0], NORMAL[1], NORMAL[2]); memcpy(st, ST_COORD, sizeof(GLfloat) * 2); } diff --git a/include/gl.h b/include/gl.h index 7679abf..33479c5 100644 --- a/include/gl.h +++ b/include/gl.h @@ -336,6 +336,7 @@ __BEGIN_DECLS #define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_INT_2_10_10_10_REV 0x8D9F #define GL_COLOR_INDEX 0x1900 #define GL_RED 0x1903 From cdfb805dd9daf1dc0b1b8aa21afd4d02c79c9bc8 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 27 Nov 2019 20:08:29 +0000 Subject: [PATCH 40/64] Implement a fast path for glDrawElements --- GL/draw.c | 55 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index a5823ad..eb7f730 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -978,28 +978,51 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei Vertex* vertices = _glSubmissionTargetStart(target); VertexExtra* extras = aligned_vector_at(target->extras, 0); - ITERATE(count) { - j = indexFunc(idx); + if(FAST_PATH_ENABLED) { + typedef struct FastPath { + float xyz[3]; + float uv[2]; + uint32_t argb; + } FastPath; - _readPositionData(j, 1, vertices); - _readDiffuseData(j, 1, vertices); - if(doTexture) _readUVData(j, 1, vertices); - if(doLighting) _readNormalData(j, 1, extras); - if(doTexture && doMultitexture) _readSTData(j, 1, extras); + GLboolean readST = doTexture && doMultitexture; - ++vertices; - ++extras; + ITERATE(count) { + j = indexFunc(idx); - idx += istride; + vertices->flags = PVR_CMD_VERTEX; + + FastPath* srcV = (FastPath*) ((uint8_t*) VERTEX_POINTER.ptr + (VERTEX_POINTER.stride * j)); + FastPath* dst = (FastPath*) &vertices->xyz; + *dst = *srcV; + + if(doLighting) _readNormalData(j, 1, extras); + if(readST) _readSTData(j, 1, extras); + + ++vertices; + ++extras; + + idx += istride; + } + } else { + ITERATE(count) { + j = indexFunc(idx); + vertices->flags = PVR_CMD_VERTEX; + + _readPositionData(j, 1, vertices); + _readDiffuseData(j, 1, vertices); + if(doTexture) _readUVData(j, 1, vertices); + if(doLighting) _readNormalData(j, 1, extras); + if(doTexture && doMultitexture) _readSTData(j, 1, extras); + + ++vertices; + ++extras; + + idx += istride; + } } Vertex* it = _glSubmissionTargetStart(target); - const Vertex* end = _glSubmissionTargetEnd(target); - while(it < end) { - (it++)->flags = PVR_CMD_VERTEX; - } - - it = _glSubmissionTargetStart(target); // Drawing arrays switch(mode) { case GL_TRIANGLES: From e97dd466aa95355810f7d923eddeefc39d7c9b4b Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sat, 30 Nov 2019 10:06:46 +0000 Subject: [PATCH 41/64] Fix depth test functions --- GL/state.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GL/state.c b/GL/state.c index e40295b..738ca88 100644 --- a/GL/state.c +++ b/GL/state.c @@ -62,17 +62,17 @@ static int _calc_pvr_depth_test() { case GL_NEVER: return PVR_DEPTHCMP_NEVER; case GL_LESS: - return PVR_DEPTHCMP_GREATER; + return PVR_DEPTHCMP_GEQUAL; case GL_EQUAL: return PVR_DEPTHCMP_EQUAL; case GL_LEQUAL: - return PVR_DEPTHCMP_GEQUAL; + return PVR_DEPTHCMP_GREATER; case GL_GREATER: - return PVR_DEPTHCMP_LESS; + return PVR_DEPTHCMP_LEQUAL; case GL_NOTEQUAL: return PVR_DEPTHCMP_NOTEQUAL; case GL_GEQUAL: - return PVR_DEPTHCMP_LEQUAL; + return PVR_DEPTHCMP_LESS; break; case GL_ALWAYS: default: From e61369674c44146278774d3f19c22684a4555c37 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sat, 30 Nov 2019 10:07:04 +0000 Subject: [PATCH 42/64] Change where txr.alpha is set --- GL/state.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GL/state.c b/GL/state.c index 738ca88..55e2923 100644 --- a/GL/state.c +++ b/GL/state.c @@ -123,10 +123,8 @@ static int _calcPVRBlendFactor(GLenum factor) { static void _updatePVRBlend(pvr_poly_cxt_t* context) { if(BLEND_ENABLED) { context->gen.alpha = PVR_ALPHA_ENABLE; - context->txr.alpha = PVR_TXRALPHA_ENABLE; } else { context->gen.alpha = PVR_ALPHA_DISABLE; - context->txr.alpha = PVR_TXRALPHA_DISABLE; } context->blend.src = _calcPVRBlendFactor(BLEND_SFACTOR); @@ -166,6 +164,8 @@ void _glUpdatePVRTextureContext(pvr_poly_cxt_t* context, GLshort textureUnit) { return; } + context->txr.alpha = (BLEND_ENABLED) ? PVR_TXRALPHA_ENABLE : PVR_TXRALPHA_DISABLE; + GLuint filter = PVR_FILTER_NEAREST; GLboolean enableMipmaps = GL_FALSE; From 62b53f0fb1d70486714e64aa4516c2da87992aad Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sat, 30 Nov 2019 10:07:23 +0000 Subject: [PATCH 43/64] Simplify swapVertex --- GL/private.h | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/GL/private.h b/GL/private.h index d079269..be91c29 100644 --- a/GL/private.h +++ b/GL/private.h @@ -189,14 +189,9 @@ do { \ #define swapVertex(a, b) \ do { \ - _SWAP32(a->flags, b->flags); \ - _SWAP32(a->xyz[0], b->xyz[0]); \ - _SWAP32(a->xyz[1], b->xyz[1]); \ - _SWAP32(a->xyz[2], b->xyz[2]); \ - _SWAP32(a->uv[0], b->uv[0]); \ - _SWAP32(a->uv[1], b->uv[1]); \ - _SWAP32(a->bgra, b->bgra); \ - _SWAP32(a->w, b->w); \ + Vertex c = *a; \ + *a = *b; \ + *b = c; \ } while(0) /* ClipVertex doesn't have room for these, so we need to parse them From a280dac7784b0ce1e1245ff03017d4c8c8eca575 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sat, 30 Nov 2019 10:07:41 +0000 Subject: [PATCH 44/64] Performance improvements --- GL/draw.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index eb7f730..6d9aba3 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -414,10 +414,10 @@ static void _readVertexData3ubARGB(const GLubyte* input, GLuint count, GLubyte s static void _readVertexData4ubRevARGB(const GLubyte* input, GLuint count, GLubyte stride, GLubyte* output) { ITERATE(count) { - output[0] = input[0]; - output[1] = input[1]; - output[2] = input[2]; - output[3] = input[3]; + output[B8IDX] = input[0]; + output[G8IDX] = input[1]; + output[R8IDX] = input[2]; + output[A8IDX] = input[3]; input += stride; output += sizeof(Vertex); @@ -608,23 +608,22 @@ Vertex* _glSubmissionTargetEnd(SubmissionTarget* target) { } static inline void genTriangles(Vertex* output, GLuint count) { + const GLuint tris = count / 3; Vertex* it = output + 2; - ITERATE(count / 3) { + ITERATE(tris) { it->flags = PVR_CMD_VERTEX_EOL; it += 3; } } static inline void genQuads(Vertex* output, GLuint count) { - Vertex* this = output + 2; - Vertex* next = output + 3; + const GLuint quads = count / 4; + Vertex* final = output + 3; - ITERATE(count / 4) { - swapVertex(this, next); - next->flags = PVR_CMD_VERTEX_EOL; - - this += 4; - next += 4; + ITERATE(quads) { + swapVertex((final - 1), final); + final->flags = PVR_CMD_VERTEX_EOL; + final += 4; } } @@ -982,7 +981,7 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei typedef struct FastPath { float xyz[3]; float uv[2]; - uint32_t argb; + uint8_t bgra[4]; } FastPath; GLboolean readST = doTexture && doMultitexture; @@ -993,7 +992,7 @@ static void generate(SubmissionTarget* target, const GLenum mode, const GLsizei vertices->flags = PVR_CMD_VERTEX; FastPath* srcV = (FastPath*) ((uint8_t*) VERTEX_POINTER.ptr + (VERTEX_POINTER.stride * j)); - FastPath* dst = (FastPath*) &vertices->xyz; + FastPath* dst = (FastPath*) vertices->xyz; *dst = *srcV; if(doLighting) _readNormalData(j, 1, extras); @@ -1426,6 +1425,8 @@ void APIENTRY glEnableClientState(GLenum cap) { default: _glKosThrowError(GL_INVALID_ENUM, __func__); } + + _glRecalcFastPath(); } void APIENTRY glDisableClientState(GLenum cap) { @@ -1449,6 +1450,8 @@ void APIENTRY glDisableClientState(GLenum cap) { default: _glKosThrowError(GL_INVALID_ENUM, __func__); } + + _glRecalcFastPath(); } GLuint _glGetActiveClientTexture() { From b6e9b8c3ff7b99cb8d061ee714fc76bd44049453 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sat, 30 Nov 2019 10:08:43 +0000 Subject: [PATCH 45/64] Fix a glColorPointer call --- samples/multitexture_arrays/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/multitexture_arrays/main.c b/samples/multitexture_arrays/main.c index 27b57cf..58bf860 100644 --- a/samples/multitexture_arrays/main.c +++ b/samples/multitexture_arrays/main.c @@ -67,7 +67,7 @@ void RenderCallback(GLuint texID0, GLuint texID1) { glClientActiveTextureARB(GL_TEXTURE0_ARB); /* Bind the Color Array */ - glColorPointer(1, GL_UNSIGNED_INT, 0, ARGB_ARRAY); + glColorPointer(GL_BGRA, GL_UNSIGNED_BYTE, 0, ARGB_ARRAY); /* Bind the Vertex Array */ glVertexPointer(3, GL_FLOAT, 0, VERTEX_ARRAY); From b2a2e71795072066a9fed59d95248ebce4ef65b6 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 27 Dec 2019 10:36:30 +0000 Subject: [PATCH 46/64] Much faster lighting implementation --- GL/draw.c | 8 +- GL/lighting.c | 247 +++++++++++++++++++++++++------------------------- GL/private.h | 2 +- 3 files changed, 123 insertions(+), 134 deletions(-) diff --git a/GL/draw.c b/GL/draw.c index 6d9aba3..4884a70 100644 --- a/GL/draw.c +++ b/GL/draw.c @@ -1136,14 +1136,8 @@ static void light(SubmissionTarget* target) { _glMatrixLoadNormal(); mat_transform_normal3(extra->nxyz, eye_space->n, target->count, sizeof(VertexExtra), sizeof(EyeSpaceData)); - GLsizei i; EyeSpaceData* ES = aligned_vector_at(eye_space_data, 0); - - for(i = 0; i < target->count; ++i, ++vertex, ++ES) { - /* We ignore diffuse colour when lighting is enabled. If GL_COLOR_MATERIAL is enabled - * then the lighting calculation should possibly take it into account */ - _glCalculateLighting(ES, vertex); - } + _glPerformLighting(vertex, ES, target->count); } static void divide(SubmissionTarget* target) { diff --git a/GL/lighting.c b/GL/lighting.c index d75ad65..933b935 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -281,10 +281,37 @@ static inline float FPOW(float b, float p) { return FEXP(FLOG(b) * p); } -void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex) { +#define LIGHT_COMPONENT(C) { \ + const GLfloat* acm = &MA[C]; \ + const GLfloat* dcm = &MD[C]; \ + const GLfloat* scm = &MS[C]; \ + const GLfloat* scli = &light->specular[C]; \ + const GLfloat* dcli = &light->diffuse[C]; \ + const GLfloat* acli = &light->ambient[C]; \ + const GLfloat* srm = &MATERIAL.exponent; \ + const GLfloat fi = (LdotN == 0) ? 0 : 1; \ + GLfloat component = (*acm * *acli); \ + component += (LdotN * *dcm * *dcli); \ + component += (FPOW((fi * NdotH), *srm) * *scm * *scli); \ + component *= att; \ + component *= spot; \ + final[C] += component; \ +} - /* Before we begin, lets fiddle some pointers if COLOR_MATERIAL - * is enabled */ +static inline float vec3_dot_limited( + const float* x1, const float* y1, const float* z1, + const float* x2, const float* y2, const float* z2) { + + float ret; + vec3f_dot(*x1, *y1, *z1, *x2, *y2, *z2, ret); + return (ret < 0) ? 0 : ret; +} + +void _glPerformLighting(Vertex* vertices, const EyeSpaceData* es, const int32_t count) { + int8_t i; + int32_t j; + + const LightSource* light = NULL; const GLboolean colorMaterial = _glIsColorMaterialEnabled(); const GLboolean isDiffuseCM = isDiffuseColorMaterial(); @@ -293,137 +320,105 @@ void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex) { static GLfloat CM[4]; - if(colorMaterial) { - CM[0] = ((GLfloat) vertex->bgra[R8IDX]) / 255.0f; - CM[1] = ((GLfloat) vertex->bgra[G8IDX]) / 255.0f; - CM[2] = ((GLfloat) vertex->bgra[B8IDX]) / 255.0f; - CM[3] = ((GLfloat) vertex->bgra[A8IDX]) / 255.0f; - } + /* So the DC has 16 floating point registers, that means + * we need to limit the number of floats as much as possible + * to give the compiler a good enough chance to do the right + * thing */ - const GLfloat* MD = (colorMaterial && isDiffuseCM) ? CM : MATERIAL.diffuse; - const GLfloat* MA = (colorMaterial && isAmbientCM) ? CM : MATERIAL.ambient; - const GLfloat* MS = (colorMaterial && isSpecularCM) ? CM : MATERIAL.specular; + Vertex* vertex = vertices; + const EyeSpaceData* data = es; - /* Right.. - * - * global propertie: - * - * acs - Global Ambient - * - * vertex-specific properties: - * - * ecm - Material Emission - * acm - Material Ambient - * dcm - Material Diffuse - * n - Normal - * V - Vertex Position - * VPe - Vector from V to eye point (0, 0, 0, -1) basically negative V - * - * light-specifc properties: - * - * att - Attenution - * acli - Light Ambient - * Ppli - Light Position - * dcli - Light Diffuse - * fi - 1/0 facing light or not - * VPpli - Vector from V to Ppli - * ndotPpli - Dot product between n and Ppli - * hi - - * PpliV - vector from Ppli to V - */ + const static float ONE_OVER_255 = 1.0f / 255.0f; + for(j = 0; j < count; ++j, ++vertex, ++data) { + /* When GL_COLOR_MATERIAL is on, we need to pull out + * the passed in diffuse and use it */ + const GLfloat* MD = MATERIAL.diffuse; + const GLfloat* MA = MATERIAL.ambient; + const GLfloat* MS = MATERIAL.specular; -/* Each colour component is calculated in its own scope - * so that the SH4 float registers don't get flooded */ -#define LIGHT_COMPONENT(C) { \ - const GLfloat acm = MA[C]; \ - const GLfloat dcm = MD[C]; \ - const GLfloat scm = MS[C]; \ - const GLfloat scli = light->specular[C]; \ - const GLfloat dcli = light->diffuse[C]; \ - const GLfloat acli = light->ambient[C]; \ - const GLfloat srm = MATERIAL.exponent; \ -\ - final[C] += (att * spot * ( \ - (acm * acli) + (ndotVPpli * dcm * dcli) + \ - (FPOW((fi * ndothi), srm) * scm * scli) \ - )); \ -} + if(colorMaterial) { + CM[0] = ((GLfloat) vertex->bgra[R8IDX]) * ONE_OVER_255; + CM[1] = ((GLfloat) vertex->bgra[G8IDX]) * ONE_OVER_255; + CM[2] = ((GLfloat) vertex->bgra[B8IDX]) * ONE_OVER_255; + CM[3] = ((GLfloat) vertex->bgra[A8IDX]) * ONE_OVER_255; - const GLfloat* n = ES->n; - const GLfloat* V = ES->xyz; - - GLfloat Vpe [] = {-V[0], -V[1], -V[2]}; - GLfloat VpeL; - vec3f_length(Vpe[0], Vpe[1], Vpe[2], VpeL); - Vpe[0] /= VpeL; - Vpe[1] /= VpeL; - Vpe[2] /= VpeL; - - GLfloat final[4] = { - MATERIAL.emissive[0] + (MA[0] * SCENE_AMBIENT[0]), - MATERIAL.emissive[1] + (MA[1] * SCENE_AMBIENT[1]), - MATERIAL.emissive[2] + (MA[2] * SCENE_AMBIENT[2]), - MD[3] // GL spec says alpha is always from the diffuse - }; - - GLubyte i; - for(i = 0; i < MAX_LIGHTS; ++i) { - if(!_glIsLightEnabled(i)) continue; - - const LightSource* light = &LIGHTS[i]; - - const GLfloat* Ppli = light->position; - GLfloat VPpli [] = { - Ppli[0] - V[0], - Ppli[1] - V[1], - Ppli[2] - V[2] - }; - - GLfloat VPpliL; - vec3f_length(VPpli[0], VPpli[1], VPpli[2], VPpliL); - - VPpli[0] /= VPpliL; - VPpli[1] /= VPpliL; - VPpli[2] /= VPpliL; - - GLfloat ndotVPpli; - - vec3f_dot(n[0], n[1], n[2], VPpli[0], VPpli[1], VPpli[2], ndotVPpli); - ndotVPpli = (ndotVPpli < 0) ? 0 : ndotVPpli; - - const GLfloat k0 = light->constant_attenuation; - const GLfloat k1 = light->linear_attenuation; - const GLfloat k2 = light->quadratic_attenuation; - const GLfloat att = (light->position[3] == 0.0f) ? 1.0f : 1.0f / (k0 + (k1 * VPpliL) + (k2 * VPpliL * VPpliL)); - const GLfloat spot = 1.0f; // FIXME: Spotlights - - const GLfloat fi = (ndotVPpli == 0) ? 0 : 1; - - GLfloat hi [3]; - if(!VIEWER_IN_EYE_COORDINATES) { - // FIXME: Docs show power of T or something? - hi[0] = VPpli[0] + 0; - hi[1] = VPpli[1] + 0; - hi[2] = VPpli[2] + 1; - } else { - hi[0] = VPpli[0] + Vpe[0]; - hi[1] = VPpli[1] + Vpe[1]; - hi[2] = VPpli[2] + Vpe[2]; + MD = (isDiffuseCM) ? CM : MATERIAL.diffuse; + MA = (isAmbientCM) ? CM : MATERIAL.ambient; + MS = (isSpecularCM) ? CM : MATERIAL.specular; } - GLfloat ndothi; - vec3f_dot(n[0], n[1], n[2], hi[0], hi[1], hi[2], ndothi); + float final[3]; - LIGHT_COMPONENT(0); - LIGHT_COMPONENT(1); - LIGHT_COMPONENT(2); + /* Initial, non-light related values */ + final[0] = (SCENE_AMBIENT[0] * MA[0]) + MATERIAL.emissive[0]; + final[1] = (SCENE_AMBIENT[1] * MA[1]) + MATERIAL.emissive[1]; + final[2] = (SCENE_AMBIENT[2] * MA[2]) + MATERIAL.emissive[2]; + final[3] = MD[3]; + + float Vx, Vy, Vz; + Vx = -data->xyz[0]; + Vy = -data->xyz[1]; + Vz = -data->xyz[2]; + vec3f_normalize(Vx, Vy, Vz); + + for(i = 0; i < MAX_LIGHTS; ++i) { + if(!_glIsLightEnabled(i)) continue; + + /* Calc light specific parameters */ + light = &LIGHTS[i]; + + float Lx, Ly, Lz, D; + float Hx, Hy, Hz; + const float* Nx = &data->n[0]; + const float* Ny = &data->n[1]; + const float* Nz = &data->n[2]; + + Lx = light->position[0] - data->xyz[0]; + Ly = light->position[1] - data->xyz[1]; + Lz = light->position[2] - data->xyz[2]; + vec3f_length(Lx, Ly, Lz, D); + + { + /* Normalize L - scoping ensures Llen is temporary */ + const float Llen = 1.0f / D; + Lx *= Llen; + Ly *= Llen; + Lz *= Llen; + } + + Hx = (Lx + Vx); + Hy = (Ly + Vy); + Hz = (Lz + Vz); + vec3f_normalize(Hx, Hy, Hz); + + const float LdotN = vec3_dot_limited( + &Lx, &Ly, &Lz, + Nx, Ny, Nz + ); + + const float NdotH = vec3_dot_limited( + Nx, Ny, Nz, + &Hx, &Hy, &Hz + ); + + const float att = ( + light->position[3] == 0.0f) ? 1.0f : + 1.0f / (light->constant_attenuation + (light->linear_attenuation * D) + (light->quadratic_attenuation * D * D) + ); + + const float spot = 1.0f; + + LIGHT_COMPONENT(0); + LIGHT_COMPONENT(1); + LIGHT_COMPONENT(2); + } + + vertex->bgra[R8IDX] = (GLubyte)(fminf(final[0] * 255.0f, 255.0f)); + vertex->bgra[G8IDX] = (GLubyte)(fminf(final[1] * 255.0f, 255.0f)); + vertex->bgra[B8IDX] = (GLubyte)(fminf(final[2] * 255.0f, 255.0f)); + vertex->bgra[A8IDX] = (GLubyte)(fminf(final[3] * 255.0f, 255.0f)); } +} #undef LIGHT_COMPONENT - - vertex->bgra[R8IDX] = (GLubyte)(fminf(final[0] * 255.0f, 255.0f)); - vertex->bgra[G8IDX] = (GLubyte)(fminf(final[1] * 255.0f, 255.0f)); - vertex->bgra[B8IDX] = (GLubyte)(fminf(final[2] * 255.0f, 255.0f)); - vertex->bgra[A8IDX] = (GLubyte)(fminf(final[3] * 255.0f, 255.0f)); -} diff --git a/GL/private.h b/GL/private.h index be91c29..c441793 100644 --- a/GL/private.h +++ b/GL/private.h @@ -320,7 +320,7 @@ typedef struct { float n[3]; } EyeSpaceData; -extern void _glCalculateLighting(EyeSpaceData* ES, Vertex* vertex); +extern void _glPerformLighting(Vertex* vertices, const EyeSpaceData* es, const int32_t count); unsigned char _glIsClippingEnabled(); void _glEnableClipping(unsigned char v); From eeb95193acfd2f24f4e65e93dcf2e50df3a85563 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Fri, 27 Dec 2019 19:35:18 +0000 Subject: [PATCH 47/64] Fix overflow --- GL/lighting.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GL/lighting.c b/GL/lighting.c index 933b935..1bdcadc 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -348,7 +348,7 @@ void _glPerformLighting(Vertex* vertices, const EyeSpaceData* es, const int32_t MS = (isSpecularCM) ? CM : MATERIAL.specular; } - float final[3]; + float final[4]; /* Initial, non-light related values */ final[0] = (SCENE_AMBIENT[0] * MA[0]) + MATERIAL.emissive[0]; From 3c9f352827fdcacf1a7ef6b82f95bdfd6a2922ae Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 1 Jan 2020 14:12:12 +0000 Subject: [PATCH 48/64] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f761d3a..7efcd83 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ build: GL/version.h $(OBJS) link samples: build $(KOS_MAKE) -C samples all -defaultall: create_kos_link $(OBJS) subdirs linklib samples +defaultall: GL/version.h create_kos_link $(OBJS) subdirs linklib samples include $(KOS_BASE)/addons/Makefile.prefab From 56b8abd758f5fd4fd167b59b31618bc9026b48f0 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 1 Jan 2020 14:13:34 +0000 Subject: [PATCH 49/64] Update Makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 7efcd83..aecb85e 100644 --- a/Makefile +++ b/Makefile @@ -31,5 +31,6 @@ include $(KOS_BASE)/addons/Makefile.prefab # creates the kos link to the headers create_kos_link: + GL/version.h rm -f ../include/GL ln -s ../GLdc/include ../include/GL From 6322728fae8b13b23196494f3d3b0ab6256557fa Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 1 Jan 2020 14:28:25 +0000 Subject: [PATCH 50/64] Update Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index aecb85e..67f3e8f 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ SUBDIRS = KOS_CFLAGS += -ffast-math -Ofast -Iinclude GL/version.h: + rm $@ @echo -e '#pragma once\n#define GLDC_VERSION "$(shell git describe --abbrev=4 --dirty --always --tags)"\n' > $@ link: @@ -25,12 +26,11 @@ build: GL/version.h $(OBJS) link samples: build $(KOS_MAKE) -C samples all -defaultall: GL/version.h create_kos_link $(OBJS) subdirs linklib samples +defaultall: create_kos_link $(OBJS) subdirs linklib samples include $(KOS_BASE)/addons/Makefile.prefab # creates the kos link to the headers create_kos_link: - GL/version.h rm -f ../include/GL ln -s ../GLdc/include ../include/GL From 0819e1e5dfdb372d64d3b02fc4e73e4da40f9cbc Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 1 Jan 2020 14:28:45 +0000 Subject: [PATCH 51/64] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 67f3e8f..677a891 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ SUBDIRS = KOS_CFLAGS += -ffast-math -Ofast -Iinclude GL/version.h: - rm $@ + rm $@ @echo -e '#pragma once\n#define GLDC_VERSION "$(shell git describe --abbrev=4 --dirty --always --tags)"\n' > $@ link: From dafc56c3965f79ef3612ff56ee169521ed7ea289 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 1 Jan 2020 14:30:03 +0000 Subject: [PATCH 52/64] Add new file --- GL/version.h | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 GL/version.h diff --git a/GL/version.h b/GL/version.h new file mode 100644 index 0000000..a151a30 --- /dev/null +++ b/GL/version.h @@ -0,0 +1,2 @@ +#pragma once +#define GLDC_VERSION "1.1-master" From 96057344761fae7cde096fd341646932d506da44 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Wed, 1 Jan 2020 14:31:55 +0000 Subject: [PATCH 53/64] Update Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 677a891..43f7327 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # kos-ports/libgl Makefile # Copyright (C) 2013, 2014 Josh Pearson # Copyright (C) 2014 Lawrence Sebald -# Copyright (C) 2018 Luke Benstead +# Copyright (C) 2020 Luke Benstead TARGET = libGLdc.a OBJS = GL/draw.o GL/flush.o GL/framebuffer.o GL/immediate.o GL/lighting.o GL/state.o GL/texture.o GL/glu.o GL/version.h @@ -14,7 +14,7 @@ SUBDIRS = KOS_CFLAGS += -ffast-math -Ofast -Iinclude GL/version.h: - rm $@ + rm -f $@ @echo -e '#pragma once\n#define GLDC_VERSION "$(shell git describe --abbrev=4 --dirty --always --tags)"\n' > $@ link: From 2178d2f6d78f40438d9c894b1e4c498a4e65fcf9 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Sun, 16 Feb 2020 20:22:13 +0000 Subject: [PATCH 54/64] Improve lighting performance This is (another) first pass at improving the performance of the lighting code. As part of this refactor *we have lost glColorMaterial*. I need to figure out a nice way of implementing it without slowing the common code path. --- GL/lighting.c | 281 ++++++++++++++++++++++++------------------ GL/private.h | 7 +- samples/lights/main.c | 4 +- 3 files changed, 165 insertions(+), 127 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index 1bdcadc..1cb3fd8 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -4,6 +4,10 @@ #include #include "private.h" +/* Lighting will not be calculated if the attenuation + * multiplier ends up less than this value */ +#define ATTENUATION_THRESHOLD 0.01f + static GLfloat SCENE_AMBIENT [] = {0.2, 0.2, 0.2, 1.0}; static GLboolean VIEWER_IN_EYE_COORDINATES = GL_TRUE; static GLenum COLOR_CONTROL = GL_SINGLE_COLOR; @@ -48,8 +52,6 @@ void _glInitLights() { LIGHTS[i].constant_attenuation = 1.0f; LIGHTS[i].linear_attenuation = 0.0f; LIGHTS[i].quadratic_attenuation = 0.0f; - - LIGHTS[i].is_directional = GL_FALSE; } } @@ -112,8 +114,7 @@ void APIENTRY glLightfv(GLenum light, GLenum pname, const GLfloat *params) { _glMatrixLoadModelView(); memcpy(LIGHTS[idx].position, params, sizeof(GLfloat) * 4); - LIGHTS[idx].is_directional = (params[3] == 0.0f) ? GL_TRUE : GL_FALSE; - if(LIGHTS[idx].is_directional) { + if(params[3] == 0.0f) { //FIXME: Do we need to rotate directional lights? } else { mat_trans_single4( @@ -239,19 +240,19 @@ void APIENTRY glColorMaterial(GLenum face, GLenum mode) { COLOR_MATERIAL_MODE = mode; } -static inline GLboolean isDiffuseColorMaterial() { +GL_FORCE_INLINE GLboolean isDiffuseColorMaterial() { return (COLOR_MATERIAL_MODE == GL_DIFFUSE || COLOR_MATERIAL_MODE == GL_AMBIENT_AND_DIFFUSE); } -static inline GLboolean isAmbientColorMaterial() { +GL_FORCE_INLINE GLboolean isAmbientColorMaterial() { return (COLOR_MATERIAL_MODE == GL_AMBIENT || COLOR_MATERIAL_MODE == GL_AMBIENT_AND_DIFFUSE); } -static inline GLboolean isSpecularColorMaterial() { +GL_FORCE_INLINE GLboolean isSpecularColorMaterial() { return (COLOR_MATERIAL_MODE == GL_SPECULAR); } -static inline void initVec3(struct vec3f* v, const GLfloat* src) { +GL_FORCE_INLINE void initVec3(struct vec3f* v, const GLfloat* src) { memcpy(v, src, sizeof(GLfloat) * 3); } @@ -259,7 +260,7 @@ static inline void initVec3(struct vec3f* v, const GLfloat* src) { #define EXP_A 184 #define EXP_C 16249 -static inline float FEXP(float y) { +GL_FORCE_INLINE float FEXP(float y) { union { float d; struct { @@ -271,34 +272,29 @@ static inline float FEXP(float y) { return eco.d; } -static inline float FLOG(float y) { - int *nTemp = (int *)&y; - y = (*nTemp) >> 16; - return (y - EXP_C) / EXP_A; +GL_FORCE_INLINE float MATH_Fast_Divide(float numerator, float denominator) { + __asm__ volatile ("fsrra %[div_denom]\n\t" + "fmul %[div_denom], %[div_denom]\n\t" + "fmul %[div_numer], %[div_denom]\n" + : [div_denom] "+&f" (denominator) + : [div_numer] "f" (numerator) // inputs + : // clobbers + ); + + return denominator; } -static inline float FPOW(float b, float p) { +GL_FORCE_INLINE float FLOG(float y) { + int *nTemp = (int *)&y; + y = (*nTemp) >> 16; + return MATH_Fast_Divide((y - EXP_C), EXP_A); +} + +GL_FORCE_INLINE float FPOW(float b, float p) { return FEXP(FLOG(b) * p); } -#define LIGHT_COMPONENT(C) { \ - const GLfloat* acm = &MA[C]; \ - const GLfloat* dcm = &MD[C]; \ - const GLfloat* scm = &MS[C]; \ - const GLfloat* scli = &light->specular[C]; \ - const GLfloat* dcli = &light->diffuse[C]; \ - const GLfloat* acli = &light->ambient[C]; \ - const GLfloat* srm = &MATERIAL.exponent; \ - const GLfloat fi = (LdotN == 0) ? 0 : 1; \ - GLfloat component = (*acm * *acli); \ - component += (LdotN * *dcm * *dcli); \ - component += (FPOW((fi * NdotH), *srm) * *scm * *scli); \ - component *= att; \ - component *= spot; \ - final[C] += component; \ -} - -static inline float vec3_dot_limited( +GL_FORCE_INLINE float vec3_dot_limited( const float* x1, const float* y1, const float* z1, const float* x2, const float* y2, const float* z2) { @@ -307,117 +303,156 @@ static inline float vec3_dot_limited( return (ret < 0) ? 0 : ret; } +#define _MIN(x, y) (x < y) ? x : y; + +GL_FORCE_INLINE void _glLightVertexDirectional(uint8_t* final, int8_t lid, float LdotN, float NdotH) { + float F; + float FI = (LdotN != 0.0f); + uint8_t FO; + +#define _PROCESS_COMPONENT(T, X) \ + F = (MATERIAL.ambient[X] * LIGHTS[lid].ambient[X]); \ + F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ + F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ + FO = (uint8_t) (F * 255.0f); \ +\ + final[T] += _MIN(FO, final[T] - FO) \ + + _PROCESS_COMPONENT(R8IDX, 0); + _PROCESS_COMPONENT(G8IDX, 1); + _PROCESS_COMPONENT(B8IDX, 2); + +#undef _PROCESS_COMPONENT +} + +GL_FORCE_INLINE void _glLightVertexPoint(uint8_t* final, int8_t lid, float LdotN, float NdotH, float att) { + float F; + float FI = (LdotN != 0.0f); + uint8_t FO; + +#define _PROCESS_COMPONENT(T, X) \ + F = (MATERIAL.ambient[X] * LIGHTS[lid].ambient[X]); \ + F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ + F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ + FO = (uint8_t) (F * att * 255.0f); \ +\ + final[T] += _MIN(FO, final[T] - FO) \ + + _PROCESS_COMPONENT(R8IDX, 0); + _PROCESS_COMPONENT(G8IDX, 1); + _PROCESS_COMPONENT(B8IDX, 2); + +#undef _PROCESS_COMPONENT +} + +GL_FORCE_INLINE float MATH_fsrra(float x) { + __asm__ volatile ("fsrra %[one_div_sqrt]\n" + : [one_div_sqrt] "+f" (x) // outputs, "+" means r/w + : // no inputs + : // no clobbers + ); + + return x; +} + void _glPerformLighting(Vertex* vertices, const EyeSpaceData* es, const int32_t count) { int8_t i; int32_t j; - const LightSource* light = NULL; - - const GLboolean colorMaterial = _glIsColorMaterialEnabled(); - const GLboolean isDiffuseCM = isDiffuseColorMaterial(); - const GLboolean isAmbientCM = isAmbientColorMaterial(); - const GLboolean isSpecularCM = isSpecularColorMaterial(); - - static GLfloat CM[4]; - - /* So the DC has 16 floating point registers, that means - * we need to limit the number of floats as much as possible - * to give the compiler a good enough chance to do the right - * thing */ - Vertex* vertex = vertices; const EyeSpaceData* data = es; - - const static float ONE_OVER_255 = 1.0f / 255.0f; + float base; for(j = 0; j < count; ++j, ++vertex, ++data) { - /* When GL_COLOR_MATERIAL is on, we need to pull out - * the passed in diffuse and use it */ - const GLfloat* MD = MATERIAL.diffuse; - const GLfloat* MA = MATERIAL.ambient; - const GLfloat* MS = MATERIAL.specular; - - if(colorMaterial) { - CM[0] = ((GLfloat) vertex->bgra[R8IDX]) * ONE_OVER_255; - CM[1] = ((GLfloat) vertex->bgra[G8IDX]) * ONE_OVER_255; - CM[2] = ((GLfloat) vertex->bgra[B8IDX]) * ONE_OVER_255; - CM[3] = ((GLfloat) vertex->bgra[A8IDX]) * ONE_OVER_255; - - MD = (isDiffuseCM) ? CM : MATERIAL.diffuse; - MA = (isAmbientCM) ? CM : MATERIAL.ambient; - MS = (isSpecularCM) ? CM : MATERIAL.specular; - } - - float final[4]; - /* Initial, non-light related values */ - final[0] = (SCENE_AMBIENT[0] * MA[0]) + MATERIAL.emissive[0]; - final[1] = (SCENE_AMBIENT[1] * MA[1]) + MATERIAL.emissive[1]; - final[2] = (SCENE_AMBIENT[2] * MA[2]) + MATERIAL.emissive[2]; - final[3] = MD[3]; + base = (SCENE_AMBIENT[0] * MATERIAL.ambient[0]) + MATERIAL.emissive[0]; + vertex->bgra[0] = (uint8_t)(base * 255.0f); - float Vx, Vy, Vz; - Vx = -data->xyz[0]; - Vy = -data->xyz[1]; - Vz = -data->xyz[2]; + base = (SCENE_AMBIENT[1] * MATERIAL.ambient[1]) + MATERIAL.emissive[1]; + vertex->bgra[1] = (uint8_t)(base * 255.0f); + + base = (SCENE_AMBIENT[2] * MATERIAL.ambient[2]) + MATERIAL.emissive[2]; + vertex->bgra[2] = (uint8_t)(base * 255.0f); + vertex->bgra[3] = (uint8_t)(MATERIAL.diffuse[3] * 255.0f); + + float Vx = -data->xyz[0]; + float Vy = -data->xyz[1]; + float Vz = -data->xyz[2]; vec3f_normalize(Vx, Vy, Vz); + const float Nx = data->n[0]; + const float Ny = data->n[1]; + const float Nz = data->n[2]; + for(i = 0; i < MAX_LIGHTS; ++i) { if(!_glIsLightEnabled(i)) continue; - /* Calc light specific parameters */ - light = &LIGHTS[i]; + if(LIGHTS[i].position[3] == 0.0f) { + float Lx = -LIGHTS[i].position[0]; + float Ly = -LIGHTS[i].position[1]; + float Lz = -LIGHTS[i].position[2]; + float Hx = (Lx + Vx); + float Hy = (Ly + Vy); + float Hz = (Lz + Vz); - float Lx, Ly, Lz, D; - float Hx, Hy, Hz; - const float* Nx = &data->n[0]; - const float* Ny = &data->n[1]; - const float* Nz = &data->n[2]; + vec3f_normalize(Lx, Ly, Lz); + vec3f_normalize(Hx, Hy, Hz); - Lx = light->position[0] - data->xyz[0]; - Ly = light->position[1] - data->xyz[1]; - Lz = light->position[2] - data->xyz[2]; - vec3f_length(Lx, Ly, Lz, D); + const float LdotN = vec3_dot_limited( + &Nx, &Ny, &Nz, + &Lx, &Ly, &Lz + ); - { - /* Normalize L - scoping ensures Llen is temporary */ - const float Llen = 1.0f / D; - Lx *= Llen; - Ly *= Llen; - Lz *= Llen; + const float NdotH = vec3_dot_limited( + &Nx, &Ny, &Nz, + &Hx, &Hy, &Hz + ); + + _glLightVertexDirectional( + vertex->bgra, + i, LdotN, NdotH + ); + } else { + float Lx = LIGHTS[i].position[0] - data->xyz[0]; + float Ly = LIGHTS[i].position[1] - data->xyz[1]; + float Lz = LIGHTS[i].position[2] - data->xyz[2]; + float D; + + vec3f_length(Lx, Ly, Lz, D); + + float att = ( + LIGHTS[i].constant_attenuation + ( + LIGHTS[i].linear_attenuation * D + ) + (LIGHTS[i].quadratic_attenuation * D * D) + ); + + att = MATH_fsrra(att * att); + + if(att >= ATTENUATION_THRESHOLD) { + float Hx = (Lx + Vx); + float Hy = (Ly + Vy); + float Hz = (Lz + Vz); + + vec3f_normalize(Lx, Ly, Lz); + vec3f_normalize(Hx, Hy, Hz); + + const float LdotN = vec3_dot_limited( + &Nx, &Ny, &Nz, + &Lx, &Ly, &Lz + ); + + const float NdotH = vec3_dot_limited( + &Nx, &Ny, &Nz, + &Hx, &Hy, &Hz + ); + + _glLightVertexPoint( + vertex->bgra, + i, LdotN, NdotH, att + ); + } } - - Hx = (Lx + Vx); - Hy = (Ly + Vy); - Hz = (Lz + Vz); - vec3f_normalize(Hx, Hy, Hz); - - const float LdotN = vec3_dot_limited( - &Lx, &Ly, &Lz, - Nx, Ny, Nz - ); - - const float NdotH = vec3_dot_limited( - Nx, Ny, Nz, - &Hx, &Hy, &Hz - ); - - const float att = ( - light->position[3] == 0.0f) ? 1.0f : - 1.0f / (light->constant_attenuation + (light->linear_attenuation * D) + (light->quadratic_attenuation * D * D) - ); - - const float spot = 1.0f; - - LIGHT_COMPONENT(0); - LIGHT_COMPONENT(1); - LIGHT_COMPONENT(2); } - - vertex->bgra[R8IDX] = (GLubyte)(fminf(final[0] * 255.0f, 255.0f)); - vertex->bgra[G8IDX] = (GLubyte)(fminf(final[1] * 255.0f, 255.0f)); - vertex->bgra[B8IDX] = (GLubyte)(fminf(final[2] * 255.0f, 255.0f)); - vertex->bgra[A8IDX] = (GLubyte)(fminf(final[3] * 255.0f, 255.0f)); } } diff --git a/GL/private.h b/GL/private.h index c441793..1575bb8 100644 --- a/GL/private.h +++ b/GL/private.h @@ -12,6 +12,8 @@ #include "../containers/aligned_vector.h" #include "../containers/named_array.h" +#define GL_FORCE_INLINE __attribute__((always_inline)) static __inline__ + #define FASTCPY(dst, src, bytes) \ (bytes % 32 == 0) ? sq_cpy(dst, src, bytes) : memcpy(dst, src, bytes); @@ -143,7 +145,9 @@ typedef struct { GLfloat ambient[4]; GLfloat diffuse[4]; GLfloat specular[4]; - GLfloat exponent; + + /* Valid values are 0-128 */ + GLubyte exponent; } Material; typedef struct { @@ -157,7 +161,6 @@ typedef struct { GLfloat diffuse[4]; GLfloat specular[4]; GLfloat ambient[4]; - GLboolean is_directional; } LightSource; typedef struct { diff --git a/samples/lights/main.c b/samples/lights/main.c index ea867e5..c6c2609 100644 --- a/samples/lights/main.c +++ b/samples/lights/main.c @@ -216,14 +216,14 @@ void DrawCube(float x, float z) { glBegin(GL_QUADS); - glNormal3f(0, 0, 1); + glNormal3f(0, 0, -1); glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); - glNormal3f(0, 0, -1); + glNormal3f(0, 0, 1); glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); From 35842ad3006189fa3bceaa0b8a8d0caccd835e32 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 10:12:15 +0000 Subject: [PATCH 55/64] Faster FPOw implementation --- GL/lighting.c | 53 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index 1cb3fd8..97fad62 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -1,6 +1,6 @@ #include #include - +#include #include #include "private.h" @@ -272,26 +272,47 @@ GL_FORCE_INLINE float FEXP(float y) { return eco.d; } -GL_FORCE_INLINE float MATH_Fast_Divide(float numerator, float denominator) { - __asm__ volatile ("fsrra %[div_denom]\n\t" - "fmul %[div_denom], %[div_denom]\n\t" - "fmul %[div_numer], %[div_denom]\n" - : [div_denom] "+&f" (denominator) - : [div_numer] "f" (numerator) // inputs - : // clobbers - ); +/* Inspired by: https://web.archive.org/web/20180423090243/www.dctsystems.co.uk/Software/power.html */ +#define SHIFT23 (1 << 23) +#define INVSHIFT23 (1.0f / SHIFT23) +#define LOGBODGE 0.346607f +#define POWBODGE 0.33971f - return denominator; +GL_FORCE_INLINE float FLOG2(float i) { + float y; + + union { + float f; + int i; + } x; + + x.f = i; + + x.i *= INVSHIFT23; + x.i = x.i - 127; + + y = x.i - floorf(x.i); + y = (y - y * y) * LOGBODGE; + + return x.i + y; } -GL_FORCE_INLINE float FLOG(float y) { - int *nTemp = (int *)&y; - y = (*nTemp) >> 16; - return MATH_Fast_Divide((y - EXP_C), EXP_A); +GL_FORCE_INLINE float FPOW2(float i) { + float y = i - floorf(i); + y = (y - y * y) * POWBODGE; + + union { + float f; + float i; + } x; + + x.f = i + 127 - y; + x.f *= SHIFT23; + return x.i; } -GL_FORCE_INLINE float FPOW(float b, float p) { - return FEXP(FLOG(b) * p); +GL_FORCE_INLINE float FPOW(float a, float b) { + return FPOW2(b * FLOG2(a)); } GL_FORCE_INLINE float vec3_dot_limited( From ab69030b1e57d7203f8d8a226dcaa2ea27b547d1 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 10:21:33 +0000 Subject: [PATCH 56/64] Fix lighting colours --- GL/lighting.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index 97fad62..6f4be94 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -387,14 +387,14 @@ void _glPerformLighting(Vertex* vertices, const EyeSpaceData* es, const int32_t for(j = 0; j < count; ++j, ++vertex, ++data) { /* Initial, non-light related values */ base = (SCENE_AMBIENT[0] * MATERIAL.ambient[0]) + MATERIAL.emissive[0]; - vertex->bgra[0] = (uint8_t)(base * 255.0f); + vertex->bgra[R8IDX] = (uint8_t)(base * 255.0f); base = (SCENE_AMBIENT[1] * MATERIAL.ambient[1]) + MATERIAL.emissive[1]; - vertex->bgra[1] = (uint8_t)(base * 255.0f); + vertex->bgra[G8IDX] = (uint8_t)(base * 255.0f); base = (SCENE_AMBIENT[2] * MATERIAL.ambient[2]) + MATERIAL.emissive[2]; - vertex->bgra[2] = (uint8_t)(base * 255.0f); - vertex->bgra[3] = (uint8_t)(MATERIAL.diffuse[3] * 255.0f); + vertex->bgra[B8IDX] = (uint8_t)(base * 255.0f); + vertex->bgra[A8IDX] = (uint8_t)(MATERIAL.diffuse[3] * 255.0f); float Vx = -data->xyz[0]; float Vy = -data->xyz[1]; From 5183d7b2d40c5323618a236d6655f3269ea99d59 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 10:31:26 +0000 Subject: [PATCH 57/64] Fix strict aliasing issues --- GL/clip.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/GL/clip.c b/GL/clip.c index 772386f..c5a1009 100644 --- a/GL/clip.c +++ b/GL/clip.c @@ -91,8 +91,10 @@ void _glClipTriangle(const Triangle* triangle, const uint8_t visible, Submission const Vertex* vertices = triangle->vertex; const VertexExtra* extras = triangle->extra; + char* bgra = (char*) vertices[2].bgra; + /* Used when flat shading is enabled */ - uint32_t finalColour = *((uint32_t*) vertices[2].bgra); + uint32_t finalColour = *((uint32_t*) bgra); for(i = 0; i < 4; ++i) { uint8_t thisIndex = (i == 3) ? 0 : i; @@ -123,7 +125,8 @@ void _glClipTriangle(const Triangle* triangle, const uint8_t visible, Submission interpolateVec2(ve1->st, ve2->st, t, veNext.st); if(flatShade) { - *((uint32_t*) next.bgra) = finalColour; + char* next_bgra = (char*) next.bgra; + *((uint32_t*) next_bgra) = finalColour; } else { interpolateColour(v1->bgra, v2->bgra, t, next.bgra); } @@ -185,9 +188,19 @@ static inline void markDead(Vertex* vert) { // If we're debugging, wipe out the xyz #ifndef NDEBUG - *((uint32_t*) &vert->xyz[0]) = 0xDEADBEEF; - *((uint32_t*) &vert->xyz[1]) = 0xDEADBEEF; - *((uint32_t*) &vert->xyz[2]) = 0xDEADBEEF; + typedef union { + float* f; + int* i; + } cast; + + cast v1, v2, v3; + v1.f = &vert->xyz[0]; + v2.f = &vert->xyz[1]; + v3.f = &vert->xyz[2]; + + *v1.i = 0xDEADBEEF; + *v2.i = 0xDEADBEEF; + *v3.i = 0xDEADBEEF; #endif } From 6516f0f6f54985a2e7e8b227d7ed979bee566534 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 10:38:30 +0000 Subject: [PATCH 58/64] Prevent overflowing colours --- GL/lighting.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index 6f4be94..a837abd 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -324,7 +324,7 @@ GL_FORCE_INLINE float vec3_dot_limited( return (ret < 0) ? 0 : ret; } -#define _MIN(x, y) (x < y) ? x : y; +#define _MIN(x, y) (x < y) ? x : y GL_FORCE_INLINE void _glLightVertexDirectional(uint8_t* final, int8_t lid, float LdotN, float NdotH) { float F; @@ -337,7 +337,7 @@ GL_FORCE_INLINE void _glLightVertexDirectional(uint8_t* final, int8_t lid, float F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ FO = (uint8_t) (F * 255.0f); \ \ - final[T] += _MIN(FO, final[T] - FO) \ + final[T] += _MIN(FO, final[T] - FO); \ _PROCESS_COMPONENT(R8IDX, 0); _PROCESS_COMPONENT(G8IDX, 1); @@ -357,7 +357,7 @@ GL_FORCE_INLINE void _glLightVertexPoint(uint8_t* final, int8_t lid, float LdotN F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ FO = (uint8_t) (F * att * 255.0f); \ \ - final[T] += _MIN(FO, final[T] - FO) \ + final[T] += _MIN(FO, final[T] - FO); \ _PROCESS_COMPONENT(R8IDX, 0); _PROCESS_COMPONENT(G8IDX, 1); @@ -387,14 +387,14 @@ void _glPerformLighting(Vertex* vertices, const EyeSpaceData* es, const int32_t for(j = 0; j < count; ++j, ++vertex, ++data) { /* Initial, non-light related values */ base = (SCENE_AMBIENT[0] * MATERIAL.ambient[0]) + MATERIAL.emissive[0]; - vertex->bgra[R8IDX] = (uint8_t)(base * 255.0f); + vertex->bgra[R8IDX] = (uint8_t)(_MIN(base * 255.0f, 255.0f)); base = (SCENE_AMBIENT[1] * MATERIAL.ambient[1]) + MATERIAL.emissive[1]; - vertex->bgra[G8IDX] = (uint8_t)(base * 255.0f); + vertex->bgra[G8IDX] = (uint8_t)(_MIN(base * 255.0f, 255.0f)); base = (SCENE_AMBIENT[2] * MATERIAL.ambient[2]) + MATERIAL.emissive[2]; - vertex->bgra[B8IDX] = (uint8_t)(base * 255.0f); - vertex->bgra[A8IDX] = (uint8_t)(MATERIAL.diffuse[3] * 255.0f); + vertex->bgra[B8IDX] = (uint8_t)(_MIN(base * 255.0f, 255.0f)); + vertex->bgra[A8IDX] = (uint8_t)(_MIN(MATERIAL.diffuse[3] * 255.0f, 255.0f)); float Vx = -data->xyz[0]; float Vy = -data->xyz[1]; From 93be3ab75731effe439f0462c131db4105f66bfb Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 19:02:45 +0000 Subject: [PATCH 59/64] Use super-simple floor function --- GL/lighting.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index a837abd..d0bf104 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -278,6 +278,10 @@ GL_FORCE_INLINE float FEXP(float y) { #define LOGBODGE 0.346607f #define POWBODGE 0.33971f +GL_FORCE_INLINE float FFLOOR(float x) { + return (int) x - (x < (int) x); +} + GL_FORCE_INLINE float FLOG2(float i) { float y; @@ -286,19 +290,19 @@ GL_FORCE_INLINE float FLOG2(float i) { int i; } x; - x.f = i; + x.i = i; - x.i *= INVSHIFT23; - x.i = x.i - 127; + x.f *= INVSHIFT23; + x.f = x.f - 127; - y = x.i - floorf(x.i); + y = x.f - FFLOOR(x.f); y = (y - y * y) * LOGBODGE; - return x.i + y; + return x.f + y; } GL_FORCE_INLINE float FPOW2(float i) { - float y = i - floorf(i); + float y = i - FFLOOR(i); y = (y - y * y) * POWBODGE; union { From ab5dc26e3334269edb9e34f393843f8a3f30d550 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 19:05:18 +0000 Subject: [PATCH 60/64] Only calculate specular if there is an exponent --- GL/lighting.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index d0bf104..9f31d0c 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -338,7 +338,8 @@ GL_FORCE_INLINE void _glLightVertexDirectional(uint8_t* final, int8_t lid, float #define _PROCESS_COMPONENT(T, X) \ F = (MATERIAL.ambient[X] * LIGHTS[lid].ambient[X]); \ F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ - F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ + if(MATERIAL.exponent) \ + F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ FO = (uint8_t) (F * 255.0f); \ \ final[T] += _MIN(FO, final[T] - FO); \ @@ -358,7 +359,8 @@ GL_FORCE_INLINE void _glLightVertexPoint(uint8_t* final, int8_t lid, float LdotN #define _PROCESS_COMPONENT(T, X) \ F = (MATERIAL.ambient[X] * LIGHTS[lid].ambient[X]); \ F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ - F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ + if(MATERIAL.exponent) \ + F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ FO = (uint8_t) (F * att * 255.0f); \ \ final[T] += _MIN(FO, final[T] - FO); \ From 29cbdc75b7cbd787ca4c67664dc821e2b416fe19 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 19:05:57 +0000 Subject: [PATCH 61/64] Fix a copy-paste bug (thanks @freakdave) --- GL/lighting.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GL/lighting.c b/GL/lighting.c index 9f31d0c..2ae13d9 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -210,7 +210,7 @@ void APIENTRY glMaterialfv(GLenum face, GLenum pname, const GLfloat *params) { memcpy(MATERIAL.specular, params, sizeof(GLfloat) * 4); break; case GL_EMISSION: - memcpy(MATERIAL.specular, params, sizeof(GLfloat) * 4); + memcpy(MATERIAL.emissive, params, sizeof(GLfloat) * 4); break; case GL_AMBIENT_AND_DIFFUSE: { glMaterialfv(face, GL_AMBIENT, params); From 28f77969eab54acc19b4d58c114870fd132dd97c Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 19:09:21 +0000 Subject: [PATCH 62/64] Avoid overflows --- GL/lighting.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index 2ae13d9..b6dfe42 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -340,9 +340,9 @@ GL_FORCE_INLINE void _glLightVertexDirectional(uint8_t* final, int8_t lid, float F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ if(MATERIAL.exponent) \ F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ - FO = (uint8_t) (F * 255.0f); \ + FO = (uint8_t) (_MIN(F * 255.0f, 255.0f)); \ \ - final[T] += _MIN(FO, final[T] - FO); \ + final[T] += _MIN(FO, 255 - final[T]); \ _PROCESS_COMPONENT(R8IDX, 0); _PROCESS_COMPONENT(G8IDX, 1); @@ -361,9 +361,9 @@ GL_FORCE_INLINE void _glLightVertexPoint(uint8_t* final, int8_t lid, float LdotN F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ if(MATERIAL.exponent) \ F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ - FO = (uint8_t) (F * att * 255.0f); \ + FO = (uint8_t) (_MIN(F * att * 255.0f, 255.0f)); \ \ - final[T] += _MIN(FO, final[T] - FO); \ + final[T] += _MIN(FO, 255 - final[T]); \ _PROCESS_COMPONENT(R8IDX, 0); _PROCESS_COMPONENT(G8IDX, 1); From 4ebd112784a30d4424d27c735104840c61dcf329 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 20:29:12 +0000 Subject: [PATCH 63/64] Attempt to fix specular --- GL/lighting.c | 8 ++++---- GL/private.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GL/lighting.c b/GL/lighting.c index b6dfe42..b462d42 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -338,8 +338,8 @@ GL_FORCE_INLINE void _glLightVertexDirectional(uint8_t* final, int8_t lid, float #define _PROCESS_COMPONENT(T, X) \ F = (MATERIAL.ambient[X] * LIGHTS[lid].ambient[X]); \ F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ - if(MATERIAL.exponent) \ - F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ + FI = (MATERIAL.exponent) ? FPOW(FI * NdotH, MATERIAL.exponent) : 1.0f; \ + F += FI * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ FO = (uint8_t) (_MIN(F * 255.0f, 255.0f)); \ \ final[T] += _MIN(FO, 255 - final[T]); \ @@ -359,8 +359,8 @@ GL_FORCE_INLINE void _glLightVertexPoint(uint8_t* final, int8_t lid, float LdotN #define _PROCESS_COMPONENT(T, X) \ F = (MATERIAL.ambient[X] * LIGHTS[lid].ambient[X]); \ F += (LdotN * MATERIAL.diffuse[X] * LIGHTS[lid].diffuse[X]); \ - if(MATERIAL.exponent) \ - F += FPOW(FI * NdotH, MATERIAL.exponent) * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ + FI = (MATERIAL.exponent) ? FPOW(FI * NdotH, MATERIAL.exponent) : 1.0f; \ + F += FI * MATERIAL.specular[X] * LIGHTS[lid].specular[X]; \ FO = (uint8_t) (_MIN(F * att * 255.0f, 255.0f)); \ \ final[T] += _MIN(FO, 255 - final[T]); \ diff --git a/GL/private.h b/GL/private.h index 1575bb8..71a4e34 100644 --- a/GL/private.h +++ b/GL/private.h @@ -147,7 +147,7 @@ typedef struct { GLfloat specular[4]; /* Valid values are 0-128 */ - GLubyte exponent; + GLfloat exponent; } Material; typedef struct { From 24fe33358c0b99e77b6646db3c4914683a7a58c0 Mon Sep 17 00:00:00 2001 From: Luke Benstead Date: Mon, 17 Feb 2020 20:49:40 +0000 Subject: [PATCH 64/64] Faster floor --- GL/lighting.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GL/lighting.c b/GL/lighting.c index b462d42..2dab278 100644 --- a/GL/lighting.c +++ b/GL/lighting.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "private.h" @@ -278,8 +279,10 @@ GL_FORCE_INLINE float FEXP(float y) { #define LOGBODGE 0.346607f #define POWBODGE 0.33971f +const static float FINT_MAX = (float) INT_MAX; + GL_FORCE_INLINE float FFLOOR(float x) { - return (int) x - (x < (int) x); + return (int)(x + FINT_MAX) - INT_MAX; } GL_FORCE_INLINE float FLOG2(float i) {