GLdc/GL/framebuffer.c

360 lines
10 KiB
C
Raw Normal View History

2018-05-20 15:44:16 +00:00
#include <stdio.h>
2019-09-24 20:26:17 +00:00
2018-05-20 15:16:53 +00:00
#include "private.h"
2018-05-20 15:44:16 +00:00
typedef struct {
GLuint index;
GLuint texture_id;
GLboolean is_complete;
/* FIXME: Add OP, TR and PT lists per framebuffer */
2018-05-20 15:44:16 +00:00
} FrameBuffer;
static FrameBuffer* ACTIVE_FRAMEBUFFER = NULL;
2018-05-20 15:16:53 +00:00
static NamedArray FRAMEBUFFERS;
void _glInitFramebuffers() {
2018-05-20 15:44:16 +00:00
named_array_init(&FRAMEBUFFERS, sizeof(FrameBuffer), 32);
// Reserve zero so that it is never given to anyone as an ID!
named_array_reserve(&FRAMEBUFFERS, 0);
2018-05-20 15:44:16 +00:00
}
2018-05-20 15:16:53 +00:00
void _glWipeTextureOnFramebuffers(GLuint texture) {
/* Spec says we don't update inactive framebuffers, they'll presumably just cause
* a GL_INVALID_OPERATION if we try to render to them */
if(ACTIVE_FRAMEBUFFER && ACTIVE_FRAMEBUFFER->texture_id == texture) {
ACTIVE_FRAMEBUFFER->texture_id = 0;
}
}
2018-05-20 15:44:16 +00:00
void APIENTRY glGenFramebuffersEXT(GLsizei n, GLuint* framebuffers) {
TRACE();
while(n--) {
GLuint id = 0;
FrameBuffer* fb = (FrameBuffer*) named_array_alloc(&FRAMEBUFFERS, &id);
fb->index = id;
fb->is_complete = GL_FALSE;
fb->texture_id = 0;
*framebuffers = id;
framebuffers++;
}
2018-05-20 15:16:53 +00:00
}
void APIENTRY glDeleteFramebuffersEXT(GLsizei n, const GLuint* framebuffers) {
2018-05-20 15:44:16 +00:00
TRACE();
while(n--) {
FrameBuffer* fb = (FrameBuffer*) named_array_get(&FRAMEBUFFERS, *framebuffers);
if(fb == ACTIVE_FRAMEBUFFER) {
ACTIVE_FRAMEBUFFER = NULL;
}
2018-05-20 15:16:53 +00:00
2018-05-20 15:44:16 +00:00
named_array_release(&FRAMEBUFFERS, *framebuffers++);
}
2018-05-20 15:16:53 +00:00
}
void APIENTRY glBindFramebufferEXT(GLenum target, GLuint framebuffer) {
_GL_UNUSED(target);
2018-05-20 15:44:16 +00:00
TRACE();
2018-05-20 15:16:53 +00:00
2018-05-20 15:44:16 +00:00
if(framebuffer) {
ACTIVE_FRAMEBUFFER = (FrameBuffer*) named_array_get(&FRAMEBUFFERS, framebuffer);
} else {
ACTIVE_FRAMEBUFFER = NULL;
/* FIXME: This is where we need to submit the lists and then clear them. Binding zero means returning to the
* default framebuffer so we need to render a frame to the texture at that point */
2018-05-20 15:44:16 +00:00
}
2018-05-20 15:16:53 +00:00
}
void APIENTRY glFramebufferTexture2DEXT(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {
_GL_UNUSED(target);
_GL_UNUSED(attachment);
_GL_UNUSED(textarget);
_GL_UNUSED(level);
if(texture != 0 && !glIsTexture(texture)) {
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
2018-05-20 15:16:53 +00:00
if(!ACTIVE_FRAMEBUFFER) {
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
ACTIVE_FRAMEBUFFER->texture_id = texture;
2018-05-20 15:16:53 +00:00
}
GL_FORCE_INLINE GLuint A1555(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (1 << 15);
return (v & MASK) >> 8;
2018-08-09 07:56:43 +00:00
}
GL_FORCE_INLINE GLuint R1555(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (31 << 10);
return (v & MASK) >> 7;
2018-08-09 07:56:43 +00:00
}
GL_FORCE_INLINE GLuint G1555(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (31 << 5);
return (v & MASK) >> 2;
2018-08-09 07:56:43 +00:00
}
GL_FORCE_INLINE GLuint B1555(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (31 << 0);
return (v & MASK) << 3;
2018-08-09 07:56:43 +00:00
}
GL_FORCE_INLINE GLuint A4444(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (0xF << 12);
return (v & MASK) >> 12;
}
GL_FORCE_INLINE GLuint R4444(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (0xF << 8);
return (v & MASK) >> 8;
}
GL_FORCE_INLINE GLuint G4444(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (0xF << 4);
return (v & MASK) >> 4;
}
GL_FORCE_INLINE GLuint B4444(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (0xF << 0);
return (v & MASK) >> 0;
}
GL_FORCE_INLINE GLuint R565(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (31 << 11);
2019-09-25 12:32:13 +00:00
return (v & MASK) >> 8;
2018-08-09 07:56:43 +00:00
}
GL_FORCE_INLINE GLuint G565(GLuint v) {
2019-09-25 12:45:08 +00:00
const GLuint MASK = (63 << 5);
2019-09-25 12:32:13 +00:00
return (v & MASK) >> 3;
2018-08-09 07:56:43 +00:00
}
GL_FORCE_INLINE GLuint B565(GLuint v) {
2018-08-09 07:56:43 +00:00
const GLuint MASK = (31 << 0);
2019-09-25 12:45:08 +00:00
return (v & MASK) << 3;
2018-08-09 07:56:43 +00:00
}
static GL_NO_INSTRUMENT GLboolean _glCalculateAverageTexel(GLuint pvrFormat, const GLubyte* src1, const GLubyte* src2, const GLubyte* src3, const GLubyte* src4, GLubyte* t) {
2018-08-09 07:56:43 +00:00
GLuint a, r, g, b;
GLubyte format = ((pvrFormat & (1 << 27)) | (pvrFormat & (1 << 26))) >> 26;
const GLubyte ARGB1555 = 0;
const GLubyte ARGB4444 = 1;
const GLubyte RGB565 = 2;
2021-04-09 15:24:47 +00:00
if((pvrFormat & GPU_TXRFMT_PAL8BPP) == GPU_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;
GLushort* s4 = (GLushort*) src4;
GLushort* d1 = (GLushort*) t;
2018-08-09 07:56:43 +00:00
r = R565(*s1) + R565(*s2) + R565(*s3) + R565(*s4);
2019-09-24 20:26:17 +00:00
g = G565(*s1) + G565(*s2) + G565(*s3) + G565(*s4);
b = B565(*s1) + B565(*s2) + B565(*s3) + B565(*s4);
2018-08-09 07:56:43 +00:00
r /= 4;
g /= 4;
b /= 4;
2019-09-24 20:26:17 +00:00
*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);
2018-08-09 07:56:43 +00:00
} else {
2022-08-25 20:24:08 +00:00
gl_assert(format == 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);
2018-08-09 07:56:43 +00:00
}
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;
2021-04-09 15:24:47 +00:00
if((pvrFormat & GPU_TXRFMT_PAL8BPP) == GPU_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!? */
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];
2022-08-25 20:24:08 +00:00
gl_assert(s4 < prevData + (lastHeight * lastWidth * stride));
gl_assert(t < thisData + (thisHeight * thisWidth * stride));
_glCalculateAverageTexel(pvrFormat, s1, s2, s3, s4, t);
}
return GL_TRUE;
}
2018-05-20 15:16:53 +00:00
void APIENTRY glGenerateMipmapEXT(GLenum target) {
2018-08-09 07:56:43 +00:00
if(target != GL_TEXTURE_2D) {
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
TextureObject* tex = _glGetBoundTexture();
2018-08-09 07:56:43 +00:00
2020-10-03 23:36:54 +00:00
if(!tex || !tex->data || !tex->mipmapCount) {
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
2020-10-03 23:36:54 +00:00
if(tex->width != tex->height) {
fprintf(stderr, "[GL ERROR] Mipmaps cannot be supported on non-square textures\n");
2018-08-09 07:56:43 +00:00
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
if((tex->color & GPU_TXRFMT_PAL4BPP) == GPU_TXRFMT_PAL4BPP) {
fprintf(stderr, "[GL ERROR] Mipmap generation not supported for 4BPP paletted textures\n");
_glKosThrowError(GL_INVALID_OPERATION, __func__);
return;
}
2021-04-09 15:24:47 +00:00
if((tex->color & GPU_TXRFMT_NONTWIDDLED) == GPU_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__);
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__);
return;
}
if(complete) {
2018-08-09 07:56:43 +00:00
/* Nothing to do */
return;
}
GLuint i;
2018-08-09 07:56:43 +00:00
GLuint prevWidth = tex->width;
GLuint prevHeight = tex->height;
2018-05-20 15:16:53 +00:00
/* Make sure there is room for the mipmap data on the texture object */
_glAllocateSpaceForMipmaps(tex);
for(i = 1; i < _glGetMipmapLevelCount(tex); ++i) {
2018-08-09 07:56:43 +00:00
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;
_glGenerateMipmapTwiddled(tex->color, prevData, thisWidth, thisHeight, thisData);
2018-08-09 07:56:43 +00:00
tex->mipmap |= (1 << i);
2018-08-09 07:56:43 +00:00
prevWidth = thisWidth;
prevHeight = thisHeight;
}
2019-09-24 20:26:17 +00:00
2022-08-25 20:24:08 +00:00
gl_assert(_glIsMipmapComplete(tex));
2018-05-20 15:16:53 +00:00
}
2022-02-01 07:25:54 +00:00
/* generate mipmaps for any image provided by the user and then pass them to OpenGL */
GLAPI GLvoid APIENTRY gluBuild2DMipmaps(GLenum target, GLint internalFormat,
2022-02-01 07:25:54 +00:00
GLsizei width, GLsizei height,
GLenum format, GLenum type, const void *data){
/* 2d texture, level of detail 0 (normal), 3 components (red, green, blue),
2022-02-01 07:25:54 +00:00
width & height of the image, border 0 (normal), rgb color data,
unsigned byte data, and finally the data itself. */
glTexImage2D(GL_TEXTURE_2D, 0, 3, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmapEXT(GL_TEXTURE_2D);
}
2018-05-20 15:16:53 +00:00
GLenum APIENTRY glCheckFramebufferStatusEXT(GLenum target) {
if(target != GL_FRAMEBUFFER_EXT) {
_glKosThrowError(GL_INVALID_ENUM, __func__);
return 0;
}
if(!ACTIVE_FRAMEBUFFER) {
return GL_FRAMEBUFFER_COMPLETE_EXT;
}
if(!ACTIVE_FRAMEBUFFER->texture_id) {
return GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT;
}
2018-05-20 15:16:53 +00:00
return GL_FRAMEBUFFER_COMPLETE_EXT;
2018-05-20 15:16:53 +00:00
}
GLboolean APIENTRY glIsFramebufferEXT(GLuint framebuffer) {
return (named_array_used(&FRAMEBUFFERS, framebuffer)) ? GL_TRUE : GL_FALSE;
2018-05-20 15:16:53 +00:00
}