////////////////////////////////////////////////////////////////////////////////
// RenderToTexture.cpp
// Manages rendering to texture for OpenGL
// Copyright Björn Ganster
// Developed since 2020
////////////////////////////////////////////////////////////////////////////////

#include <GL/glew.h>

#include "BGBase.h"
#include "RenderToTexture.h"

#define FILE_NUMBER FILE_NO_RENDERTOTEXTURE_CPP

// Default constructor
RenderToTexture::RenderToTexture (GLenum _format, const char* newname)
: Texture(16, 16, _format, newname)
{
	const GLubyte* versionString = glGetString(GL_VERSION);
	double glVersion = BGCPString(versionString, CP_ASCII).toDouble();
	mCanRenderToTexture = (glVersion > 3.19);

	if (mCanRenderToTexture) {
		glGenFramebuffers(1, &mFrameBufferName);
		glGenTextures(1, &mRenderedTextureName);
		glGenRenderbuffers(1, &mDepthBufferName);
	}
}

// Destructor
RenderToTexture::~RenderToTexture()
{
	if (mCanRenderToTexture) {
		glDeleteFramebuffers(1, &mFrameBufferName);
		glDeleteRenderbuffers(1, &mDepthBufferName);
	}
}

// Prepare for next frame
// based on www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture
// todo: obtain the handles in the constructor and free them using glDeleteFramebuffers in the destructor
void RenderToTexture::prepare(TextureDim sizex, TextureDim sizey, bool zBuffer)
{
	SET_SCOPE;

	if (mCanRenderToTexture) {
		resize(sizex, sizey, format);
		glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferName);
		glBindTexture(GL_TEXTURE_2D, mRenderedTextureName);
		glTexImage2D (GL_TEXTURE_2D, 0, 3, (unsigned int) potWidth, (unsigned int) potHeight, 
			0, format, GL_UNSIGNED_BYTE, 0);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

		if (zBuffer) {
			SET_OP;
			glBindRenderbuffer(GL_RENDERBUFFER, mDepthBufferName);
			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, (unsigned int) potWidth, (unsigned int) potHeight);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mDepthBufferName);
			glEnable(GL_DEPTH_TEST);
		} else {
			SET_OP;
			glDisable(GL_DEPTH_TEST);
		}

		SET_OP;
		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mRenderedTextureName, 0);
		SET_OP;
		glViewport(0, 0, (unsigned int) potWidth, (unsigned int) potHeight);
	}

	RETURN;
}

// Call this to stop rendering to texture
// todo: pass textures in as pointers for the framebuffer and depth buffer, then obtain those buffers for textures whose pointer is not null ...
void RenderToTexture::stopRenderingToTexture(bool getDepthBuffer)
{
	if (mCanRenderToTexture) {
		glFinish();
		glBindTexture(GL_TEXTURE_2D, mRenderedTextureName);
		if (getDepthBuffer) {
			// Try to obtain depth buffer
			size_t depthTexelsSize = sizeof(float) * width * height;
			float* depthTexels = (float*)traceAlloc(depthTexelsSize, "DepthBufferTexels", DepthBufferTexelsID);
			glBindBuffer(GL_COPY_READ_BUFFER, mDepthBufferName);
			glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, depthTexels);
			float min = depthTexels[0];
			float max = depthTexels[0];
			for (size_t i = 1; i < width * height; ++i) {
				min = std::min(min, depthTexels[i]);
				max = std::max(min, depthTexels[i]);
			}
			for (TextureDim v = 0; v < height; ++v) {
				for (TextureDim u = 0; u < width; ++u) {
					uint8_t value = (uint8_t)(255.0 * (texels[v*width+u]-min) / (max-min));
					setRGB(u, v, value, value, value);
				}
			}
			traceFree(depthTexels, DepthBufferTexelsID, depthTexelsSize);
		} else {
			// Copy the color buffer instead
			glGetTexImage(GL_TEXTURE_2D, 0, format, GL_UNSIGNED_BYTE, texels);
		}

		// Switch back to normal rendering
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}
}

// Query if rendering to texture is possible
bool RenderToTexture::canRenderToTexture() const
{
	return mCanRenderToTexture;
}
