Blob Blame Raw


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