#include "private.h"
#include "framebuffer.h"
#include "drawing.h"
HeliArray heliDrawingFramebuffersToFlush;
typedef struct _HeliFramebufferDesc {
unsigned int texture_id;
unsigned int renderbuffer_id;
unsigned int framebuffer_id;
} HeliFramebufferDesc;
struct _Framebuffer {
int width;
int height;
HeliFramebufferDesc base;
HeliFramebufferDesc multisample;
};
static void heliInitFramebuffer(
HeliFramebufferDesc *fbd,
int width,
int height,
const void *pixels,
int horWrap,
int vertWrap,
int smooth,
int multisample,
int simple
) {
fbd->texture_id = 0;
fbd->renderbuffer_id = 0;
fbd->framebuffer_id = 0;
unsigned int texTargetBinding = multisample ? GL_TEXTURE_BINDING_2D_MULTISAMPLE : GL_TEXTURE_BINDING_2D;
unsigned int texTarget = multisample ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
unsigned int prev = 0;
glGetIntegerv(texTargetBinding, (int*)&prev);
glGenTextures(1, &fbd->texture_id);
glBindTexture(texTarget, fbd->texture_id);
if (multisample) {
heliGLTexImage2DMultisamplePtr(texTarget, multisample, GL_RGBA, width, height, GL_TRUE);
} else {
glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, smooth ? GL_LINEAR : GL_NEAREST);
glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, smooth ? GL_LINEAR : GL_NEAREST);
glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, horWrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, vertWrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexImage2D(texTarget, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
glBindTexture(texTarget, prev);
if (!simple) {
glGetIntegerv(GL_RENDERBUFFER_BINDING, (int*)&prev);
heliGLGenRenderbuffersPtr(1, &fbd->renderbuffer_id);
heliGLBindRenderbufferPtr(GL_RENDERBUFFER, fbd->renderbuffer_id);
if (multisample) heliGLRenderbufferStorageMultisamplePtr(GL_RENDERBUFFER, multisample, GL_DEPTH24_STENCIL8, width, height);
else heliGLRenderbufferStoragePtr(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
heliGLBindRenderbufferPtr(GL_RENDERBUFFER, prev);
}
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prev);
heliGLGenFramebuffersPtr(1, &fbd->framebuffer_id);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, fbd->framebuffer_id);
if (fbd->renderbuffer_id)
heliGLFramebufferRenderbufferPtr(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbd->renderbuffer_id);
heliGLFramebufferTexture2DPtr(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texTarget, fbd->texture_id, 0);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prev);
}
static void heliDeinitFramebuffer(HeliFramebufferDesc *fbd) {
if (fbd->framebuffer_id) {
unsigned int id = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&id);
if (id == fbd->framebuffer_id) heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, 0);
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&id);
if (id == fbd->framebuffer_id) heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, 0);
heliGLDeleteFramebuffersPtr(1, &fbd->framebuffer_id);
fbd->framebuffer_id = 0;
}
if (fbd->renderbuffer_id) {
heliGLDeleteRenderbuffersPtr(1, &fbd->renderbuffer_id);
fbd->renderbuffer_id = 0;
}
if (fbd->texture_id) {
glDeleteTextures(1, &fbd->texture_id);
fbd->texture_id = 0;
}
}
Framebuffer createFramebufferEx(int width, int height, const void *pixels, int horWrap, int vertWrap, int smooth) {
Framebuffer fb = calloc(1, sizeof(*fb));
heliObjectRegister(fb, (HeliFreeCallback)&framebufferDestroy);
if (width <= 0 || height <= 0) {
fprintf(stderr, "helianthus: cannot create framebuffer, bad size: %dx%d\n", width, height);
return fb;
}
if (!heliGLGenFramebuffersPtr || !heliGLGenRenderbuffersPtr) {
fprintf(stderr, "helianthus: cannot create framebuffer, seems OpenGL version is too low\n");
return fb;
}
fb->width = width;
fb->height = height;
heliInitFramebuffer(&fb->base, width, height, pixels, horWrap, vertWrap, smooth, 0, FALSE);
unsigned int err = 0;
glGetError();
if (heliGLTexImage2DMultisamplePtr && heliGLRenderbufferStorageMultisamplePtr) {
for(int multisample = 8; multisample >= 4; multisample /= 2) {
glGetError();
heliInitFramebuffer(&fb->multisample, width, height, NULL, FALSE, FALSE, FALSE, multisample, FALSE);
err = glGetError();
if (err == GL_NO_ERROR) {
if (pixels) {
unsigned int prevR = 0, prevD = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&prevR);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prevD);
heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, fb->base.framebuffer_id);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, fb->multisample.framebuffer_id);
heliGLBlitFramebufferPtr(0, 0, fb->width, fb->height, 0, 0, fb->width, fb->height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, prevR);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prevD);
}
break;
}
heliDeinitFramebuffer(&fb->multisample);
}
}
unsigned int prevD = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prevD);
if (!fb->multisample.framebuffer_id) {
fprintf(stderr, "helianthus: cannot initialize multisampling for framebuffer, OpenGL error code: 0x%04x\n", err);
} else {
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, fb->multisample.framebuffer_id);
err = heliGLCheckFramebufferStatusPtr(GL_DRAW_FRAMEBUFFER);
if (err != GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "helianthus: multisampling framebuffer in incomplete, status: 0x%04x, OpenGL error code: 0x%04x\n", err, glGetError());
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prevD);
heliDeinitFramebuffer(&fb->multisample);
}
}
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, fb->multisample.framebuffer_id);
err = heliGLCheckFramebufferStatusPtr(GL_DRAW_FRAMEBUFFER);
if (err != GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "helianthus: base framebuffer in incomplete, status: 0x%04x, OpenGL error code: 0x%04x", err, glGetError());
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prevD);
heliDeinitFramebuffer(&fb->base);
}
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prevD);
return fb;
}
Framebuffer createFramebuffer(int width, int height)
{ return createFramebufferEx(width, height, NULL, FALSE, FALSE, TRUE); }
Framebuffer createFramebufferFromFile(const char *path) {
int width = 0;
int height = 0;
unsigned char *pixels = NULL;
imageLoad(path, &width, &height, &pixels);
return createFramebufferEx(width, height, pixels, FALSE, FALSE, TRUE);
}
void framebufferDestroy(Framebuffer framebuffer) {
HeliDrawingState *ss = heliDrawingGetState();
if (framebuffer->base.texture_id)
heliUIntRemove(&heliDrawingFramebuffersToFlush, framebuffer->base.texture_id);
if (framebuffer->base.framebuffer_id) {
for(HeliDrawingState *s = heliDrawingGetStateStack(); s <= ss; ++s) {
if (ss->glState.framebuffer_read_id == framebuffer->base.framebuffer_id)
ss->glState.framebuffer_read_id = 0;
if (ss->glState.framebuffer_draw_id == framebuffer->base.framebuffer_id)
ss->glState.framebuffer_draw_id = 0;
}
}
if (framebuffer->multisample.framebuffer_id) {
for(HeliDrawingState *s = heliDrawingGetStateStack(); s <= ss; ++s) {
if (ss->glState.framebuffer_read_id == framebuffer->multisample.framebuffer_id)
ss->glState.framebuffer_read_id = 0;
if (ss->glState.framebuffer_draw_id == framebuffer->multisample.framebuffer_id)
ss->glState.framebuffer_draw_id = 0;
}
}
heliDeinitFramebuffer(&framebuffer->multisample);
heliDeinitFramebuffer(&framebuffer->base);
free(framebuffer);
}
void framebufferFlush(Framebuffer framebuffer) {
if (framebuffer->base.texture_id)
heliUIntRemove(&heliDrawingFramebuffersToFlush, framebuffer->base.texture_id);
if (framebuffer->base.framebuffer_id && framebuffer->multisample.framebuffer_id) {
unsigned int prevR = 0, prevD = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&prevR);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prevD);
heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, framebuffer->multisample.framebuffer_id);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, framebuffer->base.framebuffer_id);
heliGLBlitFramebufferPtr(
0, 0, framebuffer->width, framebuffer->height,
0, 0, framebuffer->width, framebuffer->height,
GL_COLOR_BUFFER_BIT, GL_NEAREST );
heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, prevR);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prevD);
}
}
int framebufferGetWidth(Framebuffer framebuffer)
{ return framebuffer->width; }
int framebufferGetHeight(Framebuffer framebuffer)
{ return framebuffer->height; }
unsigned int framebufferGetGLTexId(Framebuffer framebuffer)
{ return framebuffer->base.texture_id; }
unsigned int framebufferGetGLId(Framebuffer framebuffer) {
return framebuffer->multisample.framebuffer_id
? framebuffer->multisample.framebuffer_id
: framebuffer->base.framebuffer_id;
}
int imageFromViewport(int *outWidth, int *outHeight, unsigned char **outPixels) {
*outWidth = 0;
*outHeight = 0;
*outPixels = NULL;
int vp[4] = {};
glGetIntegerv(GL_VIEWPORT, vp);
if (vp[2] <= 0 || vp[3] <= 0) return FALSE;
unsigned int prevR = 0, prevD = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prevR);
unsigned char *buffer = malloc(vp[2]*vp[3]*4);
glGetError();
glReadPixels(vp[0], vp[1], vp[2], vp[3], GL_RGBA, GL_UNSIGNED_BYTE, buffer);
if (glGetError() != GL_NO_ERROR) {
if (!heliGLBindFramebufferPtr || !heliGLBlitFramebufferPtr) {
fprintf(stderr, "helianthus: cannot read viewport pixels\n");
free(buffer);
return FALSE;
}
// blit multisample buffer
HeliFramebufferDesc fbd = {};
heliInitFramebuffer(&fbd, vp[2], vp[3], NULL, FALSE, FALSE, FALSE, 0, TRUE);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prevD);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, fbd.framebuffer_id);
heliGLBlitFramebufferPtr(vp[0], vp[1], vp[2], vp[3], 0, 0, vp[2], vp[3], GL_COLOR_BUFFER_BIT, GL_NEAREST);
heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, fbd.framebuffer_id);
glReadPixels(0, 0, vp[2], vp[3], GL_RGBA, GL_UNSIGNED_BYTE, buffer);
heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, prevR);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prevD);
heliDeinitFramebuffer(&fbd);
}
// flip image
if (!prevR) {
size_t rowSize = vp[2]*4;
unsigned char *row = malloc(rowSize);
for(int i = vp[3]/2 - 1; i >= 0; --i) {
unsigned char *r0 = buffer + rowSize*i;
unsigned char *r1 = buffer + rowSize*(vp[3]-i-1);
memcpy(row, r0, rowSize);
memcpy(r0, r1, rowSize);
memcpy(r1, row, rowSize);
}
}
*outWidth = vp[2];
*outHeight = vp[3];
*outPixels = buffer;
return TRUE;
}