#include "private.h"
#include "world.h"
#include "framebuffer.h"
struct _Framebuffer {
int width;
int height;
unsigned int texture_id;
unsigned int renderbuffer_id;
unsigned int framebuffer_id;
};
static void heliInitFramebuffer(
Framebuffer fb,
int width,
int height,
const void *pixels,
int horWrap,
int vertWrap,
int smooth,
int multisample,
int simple
) {
fb->width = width;
fb->height = height;
fb->texture_id = 0;
fb->renderbuffer_id = 0;
fb->framebuffer_id = 0;
unsigned int prev = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, (int*)&prev);
glGenTextures(1, &fb->texture_id);
glBindTexture(GL_TEXTURE_2D, fb->texture_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, smooth ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, smooth ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, horWrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, vertWrap ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.7f);
if (multisample)
heliGLTexImage2DMultisamplePtr(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA, fb->width, fb->height, GL_TRUE);
else
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb->width, fb->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, prev);
if (!simple) {
glGetIntegerv(GL_RENDERBUFFER_BINDING, (int*)&prev);
heliGLGenRenderbuffersPtr(1, &fb->renderbuffer_id);
heliGLBindRenderbufferPtr(GL_RENDERBUFFER, fb->renderbuffer_id);
if (multisample)
heliGLRenderbufferStorageMultisamplePtr(GL_RENDERBUFFER, multisample, GL_STENCIL_INDEX8, fb->width, fb->height);
else
heliGLRenderbufferStoragePtr(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, fb->width, fb->height);
heliGLBindRenderbufferPtr(GL_RENDERBUFFER, prev);
}
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prev);
heliGLGenFramebuffersPtr(1, &fb->framebuffer_id);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, fb->framebuffer_id);
if (fb->renderbuffer_id)
heliGLFramebufferRenderbufferPtr(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->renderbuffer_id);
if (multisample)
heliGLFramebufferTexture2DPtr(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, fb->texture_id, 0);
else
heliGLFramebufferTexture2DPtr(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture_id, 0);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, prev);
}
static void heliDeinitFramebuffer(Framebuffer fb) {
if (fb->framebuffer_id) {
unsigned int id = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&id);
if (id == fb->framebuffer_id) heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, 0);
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&id);
if (id == fb->framebuffer_id) heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, 0);
heliGLDeleteFramebuffersPtr(1, &fb->framebuffer_id);
fb->framebuffer_id = 0;
}
if (fb->renderbuffer_id) {
heliGLDeleteRenderbuffersPtr(1, &fb->renderbuffer_id);
fb->renderbuffer_id = 0;
}
if (fb->texture_id) {
glDeleteTextures(1, &fb->texture_id);
fb->texture_id = 0;
}
fb->width = 0;
fb->height = 0;
}
Framebuffer createFramebufferEx(int width, int height, const void *pixels, int horWrap, int vertWrap, int smooth) {
Framebuffer fb = calloc(1, sizeof(*fb));
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;
}
if (heliGLTexImage2DMultisamplePtr && heliGLRenderbufferStoragePtr) {
for(int multisample = 8; multisample >= 4; multisample /= 2) {
glGetError();
heliInitFramebuffer(fb, width, height, NULL, horWrap, vertWrap, smooth, multisample, FALSE);
if (glGetError() == GL_NO_ERROR) {
if (pixels) {
struct _Framebuffer ffb = {};
heliInitFramebuffer(&ffb, width, height, pixels, FALSE, FALSE, FALSE, 0, TRUE);
unsigned int prevR = 0, prevD = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&prevR);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prevD);
heliGLBindFramebufferPtr(GL_READ_FRAMEBUFFER, ffb.framebuffer_id);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, fb->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);
heliDeinitFramebuffer(&ffb);
}
break;
}
heliDeinitFramebuffer(fb);
}
}
if (!fb->framebuffer_id) {
fprintf(stderr, "helianthus: cannot initialize multisampling for framebuffer\n");
heliDeinitFramebuffer(fb);
heliInitFramebuffer(fb, width, height, pixels, horWrap, vertWrap, smooth, 0, FALSE);
}
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->framebuffer_id) {
for(HeliDrawingState *s = heliDrawingGetStateStack(); s <= ss; ++s) {
if (ss->glState.framebuffer_read_id == framebuffer->framebuffer_id)
ss->glState.framebuffer_read_id = 0;
if (ss->glState.framebuffer_draw_id == framebuffer->framebuffer_id)
ss->glState.framebuffer_draw_id = 0;
}
}
heliDeinitFramebuffer(framebuffer);
free(framebuffer);
}
int framebufferGetWidth(Framebuffer framebuffer)
{ return framebuffer->width; }
int framebufferGetHeight(Framebuffer framebuffer)
{ return framebuffer->height; }
unsigned int framebufferGetGLTexId(Framebuffer framebuffer)
{ return framebuffer->texture_id; }
unsigned int framebufferGetGLId(Framebuffer framebuffer)
{ return framebuffer->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 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
struct _Framebuffer ffb = {};
heliInitFramebuffer(&ffb, vp[2], vp[3], NULL, FALSE, FALSE, FALSE, 0, TRUE);
unsigned int prevR = 0, prevD = 0;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&prevR);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&prevD);
heliGLBindFramebufferPtr(GL_DRAW_FRAMEBUFFER, ffb.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, ffb.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(&ffb);
}
// flip image
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;
}