/*
 * Copyright © 2013 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

/**
 * \file piglit-fbo.cpp
 *
 * This file defines the functions which can be utilized to develop
 * new piglit test cases. These functions initialize a framebuffer
 * object based on paramaters passed.
 */
#include "piglit-fbo.h"
using namespace piglit_util_fbo;

FboConfig::FboConfig(int num_samples, int width, int height)
	: num_samples(num_samples),
	  num_rb_attachments(1),
	  num_tex_attachments(0),
	  width(width),
	  height(height),
	  layers(0),
	  use_rect(num_samples == 0),
	  attachment_layer(0),
	  combine_depth_stencil(true),
	  color_format(GL_RGBA),
	  color_internalformat(GL_RGBA),
	  depth_internalformat(GL_DEPTH_COMPONENT24),
	  stencil_internalformat(GL_STENCIL_INDEX8)
{
	memset(rb_attachment, 0, PIGLIT_MAX_COLOR_ATTACHMENTS * sizeof(GLuint));
	memset(tex_attachment, 0, PIGLIT_MAX_COLOR_ATTACHMENTS * sizeof(GLuint));

	/* Set default values for single renderbuffer and texture attachment. */
	rb_attachment[0] = GL_COLOR_ATTACHMENT0;
	tex_attachment[0] = GL_COLOR_ATTACHMENT0;
}

Fbo::Fbo()
	: config(0, 0, 0), /* will be overwritten on first call to setup() */
	  handle(0),
	  depth_rb(0),
	  stencil_rb(0),
	  gl_objects_generated(false)
{
	memset(color_tex, 0, PIGLIT_MAX_COLOR_ATTACHMENTS * sizeof(GLuint));
	memset(color_rb, 0, PIGLIT_MAX_COLOR_ATTACHMENTS * sizeof(GLuint));
}

void
Fbo::generate_gl_objects(void)
{
	GLint max_color_attachments;
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_color_attachments);
	glGenFramebuffers(1, &handle);
	glGenTextures(max_color_attachments, color_tex);
	glGenRenderbuffers(max_color_attachments, color_rb);
	glGenRenderbuffers(1, &depth_rb);
	glGenRenderbuffers(1, &stencil_rb);
	gl_objects_generated = true;
}

void
Fbo::attach_color_renderbuffer(const FboConfig &config, int index)
{
	glBindRenderbuffer(GL_RENDERBUFFER, color_rb[index]);
	glRenderbufferStorageMultisample(GL_RENDERBUFFER,
					 config.num_samples,
					 config.color_internalformat,
					 config.width,
					 config.height);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,
				  config.rb_attachment[index],
				  GL_RENDERBUFFER, color_rb[index]);
}

void
Fbo::attach_color_texture(const FboConfig &config, int index)
{
	GLenum target = config.use_rect ? GL_TEXTURE_RECTANGLE : GL_TEXTURE_2D;
	glBindTexture(target, color_tex[index]);
	glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(target,
		     0 /* level */,
		     config.color_internalformat,
		     config.width,
		     config.height,
		     0 /* border */,
		     config.color_format /* format */,
		     GL_BYTE /* type */,
		     NULL /* data */);
	glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
			       config.tex_attachment[index],
			       target,
			       color_tex[index],
			       0 /* level */);
}

void
Fbo::attach_multisample_color_texture(const FboConfig &config, int index)
{
	if (config.layers == 0) {
		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, color_tex[index]);
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
					config.num_samples,
					config.color_internalformat,
					config.width,
					config.height,
					GL_TRUE /* fixed sample locations */);
		glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
				       config.tex_attachment[index],
				       GL_TEXTURE_2D_MULTISAMPLE,
				       color_tex[index],
				       0 /* level */);
	} else {
		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, color_tex[index]);
		glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
					config.num_samples,
					config.color_internalformat,
					config.width,
					config.height,
					config.layers,
					GL_TRUE /* fixed sample locations */);
		glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER,
					  config.tex_attachment[index],
					  color_tex[index],
					  0 /* level */,
					  config.attachment_layer);
	}
}

void
Fbo::set_samples(int num_samples)
{
	FboConfig new_config = this->config;
	new_config.num_samples = num_samples;
	setup(new_config);
}

/**
 * Modify the state of the framebuffer object to reflect the state in
 * new_config.  if the resulting framebuffer is incomplete, terminate
 * the test.
 */
void
Fbo::setup(const FboConfig &new_config)
{
	GLint max_attachments;
	GLint requested_attachments = new_config.num_rb_attachments +
		new_config.num_tex_attachments;
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_attachments);

	if (requested_attachments > max_attachments) {
		printf("Number of color attachments are not supported by the"
		       " implementation.\nattachments requested = %d,"
		       " max attachments supported = %d\n",
		       requested_attachments, max_attachments);
		piglit_report_result(PIGLIT_SKIP);
	}

	if (!try_setup(new_config)) {
		printf("Framebuffer not complete\n");
		piglit_report_result(PIGLIT_SKIP);
	}
}

/**
 * Modify the state of the framebuffer object to reflect the state in
 * config.  Return true if the resulting framebuffer is complete,
 * false otherwise.
 */
bool
Fbo::try_setup(const FboConfig &new_config)
{
	this->config = new_config;

	if (!gl_objects_generated)
		generate_gl_objects();

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, handle);

	/* Color buffer */
	if (config.color_internalformat != GL_NONE) {

		/* Attach renderbuffers as color attachments */
		for (int i = 0; i < config.num_rb_attachments; i++)
			attach_color_renderbuffer(new_config, i);

		if (config.num_samples == 0) {

			/* Attach textures as color attachments */
			piglit_require_extension("GL_ARB_texture_rectangle");
			for (int i = 0; i < config.num_tex_attachments; i++)
				attach_color_texture(new_config, i);

		} else {

			/* Attach multisample textures as color attachments */
			piglit_require_extension("GL_ARB_texture_multisample");
			for (int i = 0; i < config.num_tex_attachments; i++)
				attach_multisample_color_texture(new_config, i);
		}
	}

	/* Depth/stencil buffer(s) */
	if (config.combine_depth_stencil) {
		glBindRenderbuffer(GL_RENDERBUFFER, depth_rb);
		glRenderbufferStorageMultisample(GL_RENDERBUFFER,
						 config.num_samples,
						 GL_DEPTH_STENCIL,
						 config.width,
						 config.height);
		glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,
					  GL_DEPTH_STENCIL_ATTACHMENT,
					  GL_RENDERBUFFER, depth_rb);
	} else {
		if (config.stencil_internalformat != GL_NONE) {
			glBindRenderbuffer(GL_RENDERBUFFER, stencil_rb);
			glRenderbufferStorageMultisample(GL_RENDERBUFFER,
							 config.num_samples,
							 config.stencil_internalformat,
							 config.width,
							 config.height);
			glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,
						  GL_STENCIL_ATTACHMENT,
						  GL_RENDERBUFFER, stencil_rb);
		}

		if (config.depth_internalformat != GL_NONE) {
			glBindRenderbuffer(GL_RENDERBUFFER, depth_rb);
			glRenderbufferStorageMultisample(GL_RENDERBUFFER,
							 config.num_samples,
							 config.depth_internalformat,
							 config.width,
							 config.height);
			glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER,
						  GL_DEPTH_ATTACHMENT,
						  GL_RENDERBUFFER, depth_rb);
		}
	}

	bool success = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER)
		== GL_FRAMEBUFFER_COMPLETE;

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, piglit_winsys_fbo);

	return success;
}

void
Fbo::set_viewport()
{
	glViewport(0, 0, config.width, config.height);
}
