583 lines
14 KiB
C++
583 lines
14 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2010 Warzone 2100 Project
|
|
|
|
Warzone 2100 is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Warzone 2100 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Warzone 2100; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/** \file
|
|
* Renderer setup and state control routines for 3D rendering.
|
|
*/
|
|
|
|
#include <GLee.h>
|
|
#if defined(__MACOSX__)
|
|
#include <OpenGL/glu.h>
|
|
#else
|
|
#include <GL/glu.h>
|
|
#endif
|
|
#include "lib/framework/frame.h"
|
|
|
|
#include <physfs.h>
|
|
#include "lib/framework/opengl.h"
|
|
|
|
#if defined(WZ_OS_MAC)
|
|
# include <OpenGL/glu.h>
|
|
#else
|
|
# include <GL/glu.h>
|
|
#endif
|
|
|
|
#include "lib/ivis_opengl/pieblitfunc.h"
|
|
#include "lib/ivis_opengl/piestate.h"
|
|
#include "lib/ivis_opengl/piedef.h"
|
|
#include "lib/ivis_opengl/tex.h"
|
|
#include "lib/ivis_opengl/piepalette.h"
|
|
#include "screen.h"
|
|
|
|
/*
|
|
* Global Variables
|
|
*/
|
|
|
|
// Variables for the coloured mouse cursor
|
|
static CURSOR MouseCursor = CURSOR_ARROW;
|
|
static IMAGEFILE* MouseCursors = NULL;
|
|
static uint16_t MouseCursorIDs[CURSOR_MAX];
|
|
static GLuint shaderProgram[SHADER_MAX];
|
|
static GLfloat shaderStretch = 0;
|
|
static GLint locTeam, locStretch, locTCMask, locFog;
|
|
static SHADER_MODE currentShaderMode = SHADER_NONE;
|
|
unsigned int pieStateCount = 0; // Used in pie_GetResetCounts
|
|
static RENDER_STATE rendStates;
|
|
|
|
void rendStatesRendModeHack()
|
|
{
|
|
rendStates.rendMode = REND_ALPHA;
|
|
}
|
|
|
|
/*
|
|
* Source
|
|
*/
|
|
|
|
void pie_SetDefaultStates(void)//Sets all states
|
|
{
|
|
PIELIGHT black;
|
|
|
|
//fog off
|
|
rendStates.fogEnabled = false;// enable fog before renderer
|
|
rendStates.fog = false;//to force reset to false
|
|
pie_SetFogStatus(false);
|
|
black.rgba = 0;
|
|
black.byte.a = 255;
|
|
pie_SetFogColour(black);
|
|
|
|
//depth Buffer on
|
|
pie_SetDepthBufferStatus(DEPTH_CMP_LEQ_WRT_ON);
|
|
|
|
rendStates.rendMode = REND_ALPHA; // to force reset to REND_OPAQUE
|
|
pie_SetRendMode(REND_OPAQUE);
|
|
|
|
//chroma keying on black
|
|
rendStates.keyingOn = false;//to force reset to true
|
|
pie_SetAlphaTest(true);
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// pie_EnableFog(BOOL val)
|
|
//
|
|
// Global enable/disable fog to allow fog to be turned of ingame
|
|
//
|
|
//***************************************************************************
|
|
void pie_EnableFog(BOOL val)
|
|
{
|
|
if (rendStates.fogEnabled != val)
|
|
{
|
|
debug(LOG_FOG, "pie_EnableFog: Setting fog to %s", val ? "ON" : "OFF");
|
|
rendStates.fogEnabled = val;
|
|
if (val == true)
|
|
{
|
|
pie_SetFogColour(WZCOL_FOG);
|
|
}
|
|
else
|
|
{
|
|
PIELIGHT black;
|
|
|
|
black.rgba = 0;
|
|
black.byte.a = 255;
|
|
pie_SetFogColour(black); // clear background to black
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL pie_GetFogEnabled(void)
|
|
{
|
|
return rendStates.fogEnabled;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// pie_SetFogStatus(BOOL val)
|
|
//
|
|
// Toggle fog on and off for rendering objects inside or outside the 3D world
|
|
//
|
|
//***************************************************************************
|
|
BOOL pie_GetFogStatus(void)
|
|
{
|
|
return rendStates.fog;
|
|
}
|
|
|
|
void pie_SetFogColour(PIELIGHT colour)
|
|
{
|
|
rendStates.fogColour = colour;
|
|
}
|
|
|
|
PIELIGHT pie_GetFogColour(void)
|
|
{
|
|
return rendStates.fogColour;
|
|
}
|
|
|
|
// Read shader into text buffer
|
|
static char *readShaderBuf(const char *name)
|
|
{
|
|
PHYSFS_file *fp;
|
|
int filesize;
|
|
char *buffer;
|
|
|
|
fp = PHYSFS_openRead(name);
|
|
debug(LOG_3D, "Reading...[directory: %s] %s", PHYSFS_getRealDir(name), name);
|
|
ASSERT_OR_RETURN(0, fp != NULL, "Could not open %s", name);
|
|
filesize = PHYSFS_fileLength(fp);
|
|
buffer = (char *)malloc(filesize + 1);
|
|
if (buffer)
|
|
{
|
|
PHYSFS_read(fp, buffer, 1, filesize);
|
|
buffer[filesize] = '\0';
|
|
}
|
|
PHYSFS_close(fp);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// Retrieve shader compilation errors
|
|
static void printShaderInfoLog(code_part part, GLuint shader)
|
|
{
|
|
GLint infologLen = 0;
|
|
|
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infologLen);
|
|
if (infologLen > 0)
|
|
{
|
|
GLint charsWritten = 0;
|
|
GLchar *infoLog = (GLchar *)malloc(infologLen);
|
|
|
|
glGetShaderInfoLog(shader, infologLen, &charsWritten, infoLog);
|
|
debug(part, "Shader info log: %s", infoLog);
|
|
free(infoLog);
|
|
}
|
|
}
|
|
|
|
// Retrieve shader linkage errors
|
|
static void printProgramInfoLog(code_part part, GLuint program)
|
|
{
|
|
GLint infologLen = 0;
|
|
|
|
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infologLen);
|
|
if (infologLen > 0)
|
|
{
|
|
GLint charsWritten = 0;
|
|
GLchar *infoLog = (GLchar *)malloc(infologLen);
|
|
|
|
glGetProgramInfoLog(program, infologLen, &charsWritten, infoLog);
|
|
debug(part, "Program info log: %s", infoLog);
|
|
free(infoLog);
|
|
}
|
|
}
|
|
|
|
// Read/compile/link shaders
|
|
static bool loadShaders(GLuint *program, const char *definitions,
|
|
const char *vertexPath, const char *fragmentPath)
|
|
{
|
|
GLint status;
|
|
bool success = true; // Assume overall success
|
|
char *buffer[2];
|
|
|
|
*program = glCreateProgram();
|
|
ASSERT_OR_RETURN(false, definitions != NULL, "Null in preprocessor definitions!");
|
|
ASSERT_OR_RETURN(false, *program, "Could not create shader program!");
|
|
|
|
*buffer = (char *)definitions;
|
|
|
|
if (vertexPath)
|
|
{
|
|
success = false; // Assume failure before reading shader file
|
|
|
|
if ((*(buffer + 1) = readShaderBuf(vertexPath)))
|
|
{
|
|
GLuint shader = glCreateShader(GL_VERTEX_SHADER);
|
|
|
|
glShaderSource(shader, 2, (const char **)buffer, NULL);
|
|
glCompileShader(shader);
|
|
|
|
// Check for compilation errors
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
|
if (!status)
|
|
{
|
|
debug(LOG_ERROR, "Vertex shader compilation has failed [%s]", vertexPath);
|
|
printShaderInfoLog(LOG_ERROR, shader);
|
|
}
|
|
else
|
|
{
|
|
printShaderInfoLog(LOG_3D, shader);
|
|
glAttachShader(*program, shader);
|
|
success = true;
|
|
}
|
|
|
|
free(*(buffer + 1));
|
|
}
|
|
}
|
|
|
|
if (success && fragmentPath)
|
|
{
|
|
success = false; // Assume failure before reading shader file
|
|
|
|
if ((*(buffer + 1) = readShaderBuf(fragmentPath)))
|
|
{
|
|
GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
|
|
glShaderSource(shader, 2, (const char **)buffer, NULL);
|
|
glCompileShader(shader);
|
|
|
|
// Check for compilation errors
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
|
if (!status)
|
|
{
|
|
debug(LOG_ERROR, "Fragment shader compilation has failed [%s]", fragmentPath);
|
|
printShaderInfoLog(LOG_ERROR, shader);
|
|
}
|
|
else
|
|
{
|
|
printShaderInfoLog(LOG_3D, shader);
|
|
glAttachShader(*program, shader);
|
|
success = true;
|
|
}
|
|
|
|
free(*(buffer + 1));
|
|
}
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
glLinkProgram(*program);
|
|
|
|
// Check for linkage errors
|
|
glGetProgramiv(*program, GL_LINK_STATUS, &status);
|
|
if (!status)
|
|
{
|
|
debug(LOG_ERROR, "Shader program linkage has failed [%s, %s]", vertexPath, fragmentPath);
|
|
printProgramInfoLog(LOG_ERROR, *program);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
printProgramInfoLog(LOG_3D, *program);
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Run from screen.c on init. FIXME: do some kind of FreeShaders on failure.
|
|
bool pie_LoadShaders()
|
|
{
|
|
GLuint program;
|
|
bool result;
|
|
|
|
// Try to load some shaders
|
|
shaderProgram[SHADER_NONE] = 0;
|
|
|
|
// TCMask shader for map-placed models with advanced lighting
|
|
debug(LOG_3D, "Loading shader: SHADER_COMPONENT");
|
|
result = loadShaders(&program, "", "shaders/tcmask.vert", "shaders/tcmask.frag");
|
|
ASSERT_OR_RETURN(false, result, "Failed to load component shader");
|
|
shaderProgram[SHADER_COMPONENT] = program;
|
|
|
|
// TCMask shader for buttons with flat lighting
|
|
debug(LOG_3D, "Loading shader: SHADER_BUTTON");
|
|
result = loadShaders(&program, "", "shaders/button.vert", "shaders/button.frag");
|
|
ASSERT_OR_RETURN(false, result, "Failed to load button shader");
|
|
shaderProgram[SHADER_BUTTON] = program;
|
|
|
|
currentShaderMode = SHADER_NONE;
|
|
|
|
return true;
|
|
}
|
|
|
|
void pie_DeactivateShader(void)
|
|
{
|
|
currentShaderMode = SHADER_NONE;
|
|
glUseProgram(0);
|
|
}
|
|
|
|
void pie_SetShaderStretchDepth(float stretch)
|
|
{
|
|
shaderStretch = stretch;
|
|
}
|
|
|
|
void pie_ActivateShader(SHADER_MODE shaderMode, PIELIGHT teamcolour, int maskpage)
|
|
{
|
|
GLfloat colour4f[4];
|
|
|
|
if (shaderMode != currentShaderMode)
|
|
{
|
|
GLint locTex0, locTex1;
|
|
|
|
glUseProgram(shaderProgram[shaderMode]);
|
|
locTex0 = glGetUniformLocation(shaderProgram[shaderMode], "Texture0");
|
|
locTex1 = glGetUniformLocation(shaderProgram[shaderMode], "Texture1");
|
|
locTeam = glGetUniformLocation(shaderProgram[shaderMode], "teamcolour");
|
|
locStretch = glGetUniformLocation(shaderProgram[shaderMode], "stretch");
|
|
locTCMask = glGetUniformLocation(shaderProgram[shaderMode], "tcmask");
|
|
locFog = glGetUniformLocation(shaderProgram[shaderMode], "fogEnabled");
|
|
|
|
// These never change
|
|
glUniform1i(locTex0, 0);
|
|
glUniform1i(locTex1, 1);
|
|
|
|
currentShaderMode = shaderMode;
|
|
}
|
|
|
|
pal_PIELIGHTtoRGBA4f(&colour4f[0], teamcolour);
|
|
glUniform4fv(locTeam, 1, &colour4f[0]);
|
|
glUniform1f(locStretch, shaderStretch);
|
|
glUniform1i(locTCMask, maskpage != iV_TEX_INVALID);
|
|
glUniform1i(locFog, rendStates.fog);
|
|
|
|
if (maskpage != iV_TEX_INVALID)
|
|
{
|
|
glActiveTexture(GL_TEXTURE1);
|
|
pie_SetTexturePage(maskpage);
|
|
}
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
#ifdef _DEBUG
|
|
glErrors();
|
|
#endif
|
|
}
|
|
|
|
void pie_SetDepthBufferStatus(DEPTH_MODE depthMode)
|
|
{
|
|
switch(depthMode)
|
|
{
|
|
case DEPTH_CMP_LEQ_WRT_ON:
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LEQUAL);
|
|
glDepthMask(GL_TRUE);
|
|
break;
|
|
|
|
case DEPTH_CMP_ALWAYS_WRT_ON:
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDepthMask(GL_TRUE);
|
|
break;
|
|
|
|
case DEPTH_CMP_LEQ_WRT_OFF:
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LEQUAL);
|
|
glDepthMask(GL_FALSE);
|
|
break;
|
|
|
|
case DEPTH_CMP_ALWAYS_WRT_OFF:
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDepthMask(GL_FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Set the depth (z) offset
|
|
/// Negative values are closer to the screen
|
|
void pie_SetDepthOffset(float offset)
|
|
{
|
|
if(offset == 0.0f)
|
|
{
|
|
glDisable (GL_POLYGON_OFFSET_FILL);
|
|
}
|
|
else
|
|
{
|
|
glPolygonOffset(offset, offset);
|
|
glEnable (GL_POLYGON_OFFSET_FILL);
|
|
}
|
|
}
|
|
|
|
/// Set the OpenGL fog start and end
|
|
void pie_UpdateFogDistance(float begin, float end)
|
|
{
|
|
glFogf(GL_FOG_START, begin);
|
|
glFogf(GL_FOG_END, end);
|
|
}
|
|
|
|
//
|
|
// pie_SetFogStatus(BOOL val)
|
|
//
|
|
// Toggle fog on and off for rendering objects inside or outside the 3D world
|
|
//
|
|
void pie_SetFogStatus(BOOL val)
|
|
{
|
|
float fog_colour[4];
|
|
|
|
if (rendStates.fogEnabled)
|
|
{
|
|
//fog enabled so toggle if required
|
|
if (rendStates.fog != val)
|
|
{
|
|
rendStates.fog = val;
|
|
if (rendStates.fog) {
|
|
PIELIGHT fog = pie_GetFogColour();
|
|
|
|
fog_colour[0] = fog.byte.r/255.0f;
|
|
fog_colour[1] = fog.byte.g/255.0f;
|
|
fog_colour[2] = fog.byte.b/255.0f;
|
|
fog_colour[3] = fog.byte.a/255.0f;
|
|
|
|
glFogi(GL_FOG_MODE, GL_LINEAR);
|
|
glFogfv(GL_FOG_COLOR, fog_colour);
|
|
glFogf(GL_FOG_DENSITY, 0.35f);
|
|
glHint(GL_FOG_HINT, GL_DONT_CARE);
|
|
glEnable(GL_FOG);
|
|
glClearColor(fog_colour[0], fog_colour[1], fog_colour[2], fog_colour[3]);
|
|
} else {
|
|
glDisable(GL_FOG);
|
|
glClearColor(0, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//fog disabled so turn it off if not off already
|
|
if (rendStates.fog != false)
|
|
{
|
|
rendStates.fog = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Selects a texture page and binds it for the current texture unit
|
|
* \param num a number indicating the texture page to bind. If this is a
|
|
* negative value (doesn't matter what value) it will disable texturing.
|
|
*/
|
|
void pie_SetTexturePage(SDWORD num)
|
|
{
|
|
// Only bind textures when they're not bound already
|
|
if (num != rendStates.texPage)
|
|
{
|
|
switch (num)
|
|
{
|
|
case TEXPAGE_NONE:
|
|
glDisable(GL_TEXTURE_2D);
|
|
break;
|
|
case TEXPAGE_EXTERN:
|
|
// GLC will set the texture, we just need to enable texturing
|
|
glEnable(GL_TEXTURE_2D);
|
|
break;
|
|
default:
|
|
if (rendStates.texPage == TEXPAGE_NONE)
|
|
{
|
|
glEnable(GL_TEXTURE_2D);
|
|
}
|
|
ASSERT_OR_RETURN(, num < iV_TEX_MAX, "Index out of bounds: %d", num);
|
|
glBindTexture(GL_TEXTURE_2D, _TEX_PAGE[num].id);
|
|
}
|
|
rendStates.texPage = num;
|
|
}
|
|
}
|
|
|
|
void pie_SetAlphaTest(BOOL keyingOn)
|
|
{
|
|
if (keyingOn != rendStates.keyingOn)
|
|
{
|
|
rendStates.keyingOn = keyingOn;
|
|
pieStateCount++;
|
|
|
|
if (keyingOn == true) {
|
|
glEnable(GL_ALPHA_TEST);
|
|
glAlphaFunc(GL_GREATER, 0.1f);
|
|
} else {
|
|
glDisable(GL_ALPHA_TEST);
|
|
}
|
|
}
|
|
}
|
|
|
|
void pie_SetRendMode(REND_MODE rendMode)
|
|
{
|
|
if (rendMode != rendStates.rendMode)
|
|
{
|
|
rendStates.rendMode = rendMode;
|
|
switch (rendMode)
|
|
{
|
|
case REND_OPAQUE:
|
|
glDisable(GL_BLEND);
|
|
break;
|
|
|
|
case REND_ALPHA:
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
break;
|
|
|
|
case REND_ADDITIVE:
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
break;
|
|
|
|
case REND_MULTIPLICATIVE:
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(false, "Bad render state");
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
void pie_InitColourMouse(IMAGEFILE* img, const uint16_t cursorIDs[CURSOR_MAX])
|
|
{
|
|
MouseCursors = img;
|
|
memcpy(MouseCursorIDs, cursorIDs, sizeof(MouseCursorIDs));
|
|
}
|
|
|
|
/** Selects the given mouse cursor.
|
|
* \param cursor mouse cursor to render
|
|
* \param coloured wether a coloured or black&white cursor should be used ... NOW UNUSED - FIXME REMOVE IT
|
|
*/
|
|
void pie_SetMouse(CURSOR cursor, bool coloured)
|
|
{
|
|
ASSERT(cursor < CURSOR_MAX, "Attempting to load non-existent cursor: %u", (unsigned int)cursor);
|
|
|
|
MouseCursor = cursor;
|
|
|
|
frameSetCursor(MouseCursor);
|
|
}
|
|
|
|
bool _glerrors(const char *function, const char *file, int line)
|
|
{
|
|
bool ret = false;
|
|
GLenum err = glGetError();
|
|
while (err != GL_NO_ERROR)
|
|
{
|
|
ret = true;
|
|
debug(LOG_ERROR, "OpenGL error in function %s at %s:%u: %s\n", function, file, line, gluErrorString(err));
|
|
err = glGetError();
|
|
}
|
|
return ret;
|
|
}
|