/*  $Id: canvas_opengl.cpp,v 1.22 2004/05/07 21:24:52 harry Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

#include "Display/display_precomp.h"
#include "canvas_opengl.h"
#include "API/Core/System/error.h"
#include "API/Display/pixel_buffer.h"
#include "API/Display/display.h"
#include "API/Display/display_window.h"
#include "API/Display/graphic_context.h"
#include "API/GL/opengl_state.h"
#include "API/GL/opengl_wrap.h"
#ifdef WIN32
#include "WGL/display_window_opengl.h"
#else
#include "GLX/display_window_opengl.h"
#endif
#include "graphic_context_opengl.h"
#include "surface_target_opengl.h"

/////////////////////////////////////////////////////////////////////////////
// CL_Canvas_OpenGL construction:

CL_Canvas_OpenGL::CL_Canvas_OpenGL()
{
	CL_OpenGLState state(CL_Display::get_current_window()->get_gc());
	state.set_active();

	selected_surface = 0;
	gc = 0;
	pbuffer = 0;
	pbuffer_context = 0;

#ifdef WIN32
	pbuffer_dc = 0;

	if (wglCreatePbufferARB == 0)
	{
		wglCreatePbufferARB = (ptr_wglCreatePbufferARB) wglGetProcAddress("wglCreatePbufferARB");
		wglCreatePbufferARB = (ptr_wglCreatePbufferARB) wglGetProcAddress("wglCreatePbufferARB");
		wglGetPbufferDCARB = (ptr_wglGetPbufferDCARB) wglGetProcAddress("wglGetPbufferDCARB");
		wglReleasePbufferDCARB = (ptr_wglReleasePbufferDCARB) wglGetProcAddress("wglReleasePbufferDCARB");
		wglDestroyPbufferARB = (ptr_wglDestroyPbufferARB) wglGetProcAddress("wglDestroyPbufferARB");
		wglQueryPbufferARB = (ptr_wglQueryPbufferARB) wglGetProcAddress("wglQueryPbufferARB");
	}
#else
	if (glXCreateGLXPbufferSGIX == 0)
	{
		glXCreateGLXPbufferSGIX = (ptr_glXCreateGLXPbufferSGIX) CL_OpenGL::get_proc_address("glXCreateGLXPbufferSGIX");
		glXDestroyGLXPbufferSGIX = (ptr_glXDestroyGLXPbufferSGIX) CL_OpenGL::get_proc_address("glXDestroyGLXPbufferSGIX");
		glXQueryGLXPbufferSGIX = (ptr_glXQueryGLXPbufferSGIX) CL_OpenGL::get_proc_address("glXQueryGLXPbufferSGIX");
		glXSelectEventSGIX = (ptr_glXSelectEventSGIX) CL_OpenGL::get_proc_address("glXSelectEventSGIX");
		glXGetSelectedEventSGIX = (ptr_glXGetSelectedEventSGIX) CL_OpenGL::get_proc_address("glXGetSelectedEventSGIX");

		glXChooseFBConfigSGIX = (ptr_glXChooseFBConfigSGIX) CL_OpenGL::get_proc_address("glXChooseFBConfigSGIX");
		glXCreateContextWithConfigSGIX = (ptr_glXCreateContextWithConfigSGIX) CL_OpenGL::get_proc_address("glXCreateContextWithConfigSGIX");
	}
#endif

	pbuffer_changed = false;
	texture_changed = true;
}

CL_Canvas_OpenGL::~CL_Canvas_OpenGL()
{
	delete gc;

	CL_OpenGLState state(CL_Display::get_current_window()->get_gc());
	state.set_active();
#ifdef WIN32
	if (pbuffer_context) wglDeleteContext(pbuffer_context);
	if (pbuffer_dc) wglReleasePbufferDCARB(pbuffer, pbuffer_dc);
	if (pbuffer) wglDestroyPbufferARB(pbuffer);
#else
	if (pbuffer_context) glXDestroyContext(CL_DisplayWindow_OpenGL::get_display(), pbuffer_context);
	if (pbuffer) glXDestroyGLXPbufferSGIX(CL_DisplayWindow_OpenGL::get_display(), pbuffer);
#endif
}

/////////////////////////////////////////////////////////////////////////////
// CL_Canvas_OpenGL attributes:

CL_GraphicContext *CL_Canvas_OpenGL::get_gc()
{
	if (selected_surface == 0) return 0;

	if (gc == 0)
	{
		// Create working pbuffer:
#ifdef WIN32
		if (wglCreatePbufferARB == 0)
		{
			throw CL_Error("WGL_ARB_pbuffer OpenGL extension not supported by this card");
		}

		int attribList[1] = { 0 };

		PIXELFORMATDESCRIPTOR pfd =
		{
			sizeof(PIXELFORMATDESCRIPTOR),  // size of this pfd 
			1,                              // version number
			// PFD_DRAW_TO_WINDOW |            // support window
			PFD_SUPPORT_OPENGL //|            // support OpenGL
			//PFD_DOUBLEBUFFER |              // double buffered
			//PFD_DEPTH_DONTCARE
			,             // do you care about a zbuffer?
			PFD_TYPE_RGBA,                  // RGBA type
			24,                             // 24-bit color depth
			0, 0, 0, 0, 0, 0,               // color bits ignored
			8,                              // no alpha buffer
			0,                              // shift bit ignored
			0,                              // no accumulation buffer
			0, 0, 0, 0,                     // accum bits ignored
			0,                              // z-buffer
			0,                              // no stencil buffer
			0,                              // no auxiliary buffer
			PFD_MAIN_PLANE,                 // main layer
			0,                              // reserved
			0, 0, 0                         // layer masks ignored
		};

		int pixelformat = ChoosePixelFormat(wglGetCurrentDC(), &pfd);

//		int pixelformat = GetPixelFormat(wglGetCurrentDC());

		pbuffer = wglCreatePbufferARB(
			wglGetCurrentDC(),
			pixelformat,
			selected_surface->surface_size.width,
			selected_surface->surface_size.height,
			attribList);
		pbuffer_dc = wglGetPbufferDCARB(pbuffer);
		pbuffer_context = wglCreateContext(pbuffer_dc);

		if (!CL_DisplayWindow_OpenGL::opengl_contexts.empty())
		{
			wglShareLists(CL_DisplayWindow_OpenGL::opengl_contexts.back(), pbuffer_context);
		}
#else
		if (glXCreateGLXPbufferSGIX == 0)
		{
			throw CL_Error("GLX_SGIX_pbuffer OpenGL extension not supported by this card");
		}

		int configAttribs[] =
		{
//			GLX_DOUBLEBUFFER,        False,
			GLX_RED_SIZE,            8,
			GLX_GREEN_SIZE,          8,
			GLX_BLUE_SIZE,           8,
			GLX_ALPHA_SIZE,          8,
			GLX_DRAWABLE_TYPE_SGIX,  GLX_PBUFFER_BIT_SGIX,
			(int) None
		};

		int num_configs_returned = 0;

		GLXFBConfig *config = glXChooseFBConfigSGIX(
			CL_DisplayWindow_OpenGL::get_display(),
			DefaultScreen(CL_DisplayWindow_OpenGL::get_display()),
			configAttribs,
			&num_configs_returned);

		if (num_configs_returned == 0 || config == 0)
		{
			throw CL_Error("glXChooseFBConfigSGIX could not find any suitable pbuffer pixel formats on this card!");
		}

		int pbufAttribs[] = {
			GLX_PRESERVED_CONTENTS, True,
			(int) None };

		pbuffer = glXCreateGLXPbufferSGIX(
			CL_DisplayWindow_OpenGL::get_display(),
			*config,
			selected_surface->surface_size.width,
			selected_surface->surface_size.height,
			pbufAttribs);

		pbuffer_context = glXCreateContextWithConfigSGIX(
			CL_DisplayWindow_OpenGL::get_display(),
			config[0],
			GLX_RGBA_TYPE,
			CL_DisplayWindow_OpenGL::share_context,
			True);
#endif

		// Create GC interface for it:
		gc = new CL_GraphicContext(new CL_GraphicContext_OpenGL(this));
		gc->set_cliprect(CL_Rect(0, 0, selected_surface->surface_size.width, selected_surface->surface_size.height));

		// Download surface to pbuffer:
		pbuffer_changed = false;
		texture_changed = true;
		sync_pbuffer();
	}

	return gc;
}

CL_PixelBuffer CL_Canvas_OpenGL::get_pixeldata(const CL_Rect &area)
{
	return CL_PixelBuffer();
}

int CL_Canvas_OpenGL::get_width() const
{
	if (selected_surface) return selected_surface->get_width();
	return 0;
}
	
int CL_Canvas_OpenGL::get_height() const
{
	if (selected_surface) return selected_surface->get_height();
	return 0;
}
	
/////////////////////////////////////////////////////////////////////////////
// CL_Canvas_OpenGL operations:

void CL_Canvas_OpenGL::set_pixeldata(const CL_Point &dest, const CL_Rect &src, const CL_PixelBuffer &data)
{
}

void CL_Canvas_OpenGL::select_surface(CL_Surface_Generic *surface)
{
	if (selected_surface)
	{
		sync_texture();
		selected_surface->selected_canvas = 0;
	}
	if (surface == 0)
	{
		selected_surface = 0;
		pbuffer_changed = false;
		texture_changed = false;
	}
	else
	{
		selected_surface = (CL_Surface_Target_OpenGL *) surface->target;
		selected_surface->selected_canvas = this;
		pbuffer_changed = false;
		texture_changed = true;
		// alloc_pbuffer();
	}
}
	
void CL_Canvas_OpenGL::sync_surface()
{
	sync_texture();
}

void CL_Canvas_OpenGL::sync_pbuffer()
{
	if (texture_changed && gc != 0)
	{
		CL_OpenGLState state(gc);
		state.set_active();

		// Setup down/up 2d projection matrix:
		clMatrixMode(CL_PROJECTION);
		clLoadIdentity();

		CLdouble viewport[4];
		clGetDoublev(CL_VIEWPORT, viewport);

		CLdouble width = viewport[2];
		CLdouble height = viewport[3];
		gluOrtho2D(0.0, width, 0.0, height);

		clMatrixMode(CL_MODELVIEW);
		clLoadIdentity();

		// Copy texture to pbuffer:
		clEnable(CL_TEXTURE_2D);
		clBindTexture(CL_TEXTURE_2D, selected_surface->handle);
		clMatrixMode(CL_TEXTURE);
		CLdouble scale_matrix[16];
		memset(scale_matrix, 0, sizeof(CLdouble)*16);
		scale_matrix[0] = 1.0f/float(selected_surface->texture_size.width);
		scale_matrix[5] = 1.0f/float(selected_surface->texture_size.height);
		scale_matrix[10] = 1.0f;
		scale_matrix[15] = 1.0f;
		clLoadMatrixd(scale_matrix);
		clMatrixMode(CL_MODELVIEW);

		clBegin(CL_QUADS);
		clColor3f(1.0f, 1.0f, 1.0f);
		clTexCoord2f(float(selected_surface->surface_size.width), 0.0f);
		clVertex2f(float(selected_surface->surface_size.width), 0.0f);
		clTexCoord2f(float(selected_surface->surface_size.width), float(selected_surface->surface_size.height));
		clVertex2f(float(selected_surface->surface_size.width), float(selected_surface->surface_size.height));
		clTexCoord2f(0.0f, float(selected_surface->surface_size.height));
		clVertex2f(0.0f, float(selected_surface->surface_size.height));
		clTexCoord2f(0.0f, 0.0f);
		clVertex2f(0.0f, 0.0f);
		clEnd();
	}
	texture_changed = false;
}

void CL_Canvas_OpenGL::sync_texture()
{
	if (pbuffer_changed && gc != 0)
	{
		CL_OpenGLState state(gc);
		state.set_active();

		// Copy pbuffer to texture:
		clEnable(CL_TEXTURE_2D);
		clBindTexture(CL_TEXTURE_2D, selected_surface->handle);
		clCopyTexSubImage2D(
			CL_TEXTURE_2D,
			0,
			0, 0,
			0, 0,
			selected_surface->surface_size.width, selected_surface->surface_size.height);
	}
	pbuffer_changed = false;
}

void CL_Canvas_OpenGL::set_pbuffer_modified()
{
	pbuffer_changed = true;
}

/////////////////////////////////////////////////////////////////////////////
// CL_Canvas_OpenGL implementation:

#ifdef WIN32
CL_Canvas_OpenGL::ptr_wglCreatePbufferARB CL_Canvas_OpenGL::wglCreatePbufferARB = 0;

CL_Canvas_OpenGL::ptr_wglGetPbufferDCARB CL_Canvas_OpenGL::wglGetPbufferDCARB = 0;

CL_Canvas_OpenGL::ptr_wglReleasePbufferDCARB CL_Canvas_OpenGL::wglReleasePbufferDCARB = 0;

CL_Canvas_OpenGL::ptr_wglDestroyPbufferARB CL_Canvas_OpenGL::wglDestroyPbufferARB = 0;

CL_Canvas_OpenGL::ptr_wglQueryPbufferARB CL_Canvas_OpenGL::wglQueryPbufferARB = 0;
#else
CL_Canvas_OpenGL::ptr_glXCreateGLXPbufferSGIX CL_Canvas_OpenGL::glXCreateGLXPbufferSGIX = 0;

CL_Canvas_OpenGL::ptr_glXDestroyGLXPbufferSGIX CL_Canvas_OpenGL::glXDestroyGLXPbufferSGIX = 0;

CL_Canvas_OpenGL::ptr_glXQueryGLXPbufferSGIX CL_Canvas_OpenGL::glXQueryGLXPbufferSGIX = 0;

CL_Canvas_OpenGL::ptr_glXSelectEventSGIX CL_Canvas_OpenGL::glXSelectEventSGIX = 0;

CL_Canvas_OpenGL::ptr_glXGetSelectedEventSGIX CL_Canvas_OpenGL::glXGetSelectedEventSGIX = 0;

CL_Canvas_OpenGL::ptr_glXChooseFBConfigSGIX CL_Canvas_OpenGL::glXChooseFBConfigSGIX = 0;
	
CL_Canvas_OpenGL::ptr_glXCreateContextWithConfigSGIX CL_Canvas_OpenGL::glXCreateContextWithConfigSGIX = 0;
#endif
