Blob Blame Raw


#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;
}