openclonk/src/graphics/C4DrawGL.cpp

1031 lines
33 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* OpenGL implementation of NewGfx */
#include "C4Include.h"
#include "graphics/C4DrawGL.h"
#include "graphics/C4Surface.h"
#include "platform/C4Window.h"
#include "landscape/fow/C4FoWRegion.h"
#include "lib/C4Rect.h"
#include "config/C4Config.h"
#include "platform/C4App.h"
#include "lib/StdColors.h"
#ifndef USE_CONSOLE
// MSVC doesn't define M_PI in math.h unless requested
#ifdef _MSC_VER
#define _USE_MATH_DEFINES
#endif /* _MSC_VER */
#include <stdio.h>
#include <math.h>
namespace
{
const char *MsgSourceToStr(GLenum source)
{
switch (source)
{
case GL_DEBUG_SOURCE_API_ARB: return "API";
case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: return "window system";
case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: return "shader compiler";
case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: return "third party";
case GL_DEBUG_SOURCE_APPLICATION_ARB: return "application";
case GL_DEBUG_SOURCE_OTHER_ARB: return "other";
default: return "<unknown>";
}
}
const char *MsgTypeToStr(GLenum type)
{
switch (type)
{
case GL_DEBUG_TYPE_ERROR_ARB: return "error";
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: return "deprecation warning";
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: return "undefined behavior warning";
case GL_DEBUG_TYPE_PORTABILITY_ARB: return "portability warning";
case GL_DEBUG_TYPE_PERFORMANCE_ARB: return "performance warning";
case GL_DEBUG_TYPE_OTHER_ARB: return "other message";
default: return "unknown message";
}
}
const char *MsgSeverityToStr(GLenum severity)
{
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH_ARB: return "high";
case GL_DEBUG_SEVERITY_MEDIUM_ARB: return "medium";
case GL_DEBUG_SEVERITY_LOW_ARB: return "low";
#ifdef GL_DEBUG_SEVERITY_NOTIFICATION
case GL_DEBUG_SEVERITY_NOTIFICATION: return "notification";
#endif
default: return "<unknown>";
}
}
#ifdef GLDEBUGPROCARB_USERPARAM_IS_CONST
#define USERPARAM_CONST const
#else
#define USERPARAM_CONST
#endif
void GLAPIENTRY OpenGLDebugProc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* message, USERPARAM_CONST void* userParam)
{
const char *msg_source = MsgSourceToStr(source);
const char *msg_type = MsgTypeToStr(type);
const char *msg_severity = MsgSeverityToStr(severity);
LogSilentF(" gl: %s severity %s %s: %s", msg_severity, msg_source, msg_type, message);
#ifdef USE_WIN32_WINDOWS
if (IsDebuggerPresent() && severity == GL_DEBUG_SEVERITY_HIGH_ARB)
BREAKPOINT_HERE;
#endif
}
}
#undef USERPARAM_CONST
CStdGL::CStdGL():
pMainCtx(0), CurrentVBO(0), NextVAOID(VAOIDs.end())
{
GenericVBOs[0] = 0;
Default();
// global ptr
pGL = this;
lines_tex = 0;
}
CStdGL::~CStdGL()
{
Clear();
pGL=NULL;
}
void CStdGL::Clear()
{
NoPrimaryClipper();
// cannot unlock TexMgr here or we can't preserve textures across GL reinitialization as required when changing multisampling
InvalidateDeviceObjects();
NoPrimaryClipper();
RenderTarget = NULL;
// Clear all shaders
SpriteShader.Clear();
SpriteShaderMod2.Clear();
SpriteShaderBase.Clear();
SpriteShaderBaseMod2.Clear();
SpriteShaderBaseOverlay.Clear();
SpriteShaderBaseOverlayMod2.Clear();
SpriteShaderLight.Clear();
SpriteShaderLightMod2.Clear();
SpriteShaderLightBase.Clear();
SpriteShaderLightBaseMod2.Clear();
SpriteShaderLightBaseOverlay.Clear();
SpriteShaderLightBaseOverlayMod2.Clear();
SpriteShaderLightBaseNormal.Clear();
SpriteShaderLightBaseNormalMod2.Clear();
SpriteShaderLightBaseNormalOverlay.Clear();
SpriteShaderLightBaseNormalOverlayMod2.Clear();
// clear context
if (pCurrCtx) pCurrCtx->Deselect();
pMainCtx=0;
C4Draw::Clear();
}
void CStdGL::FillBG(DWORD dwClr)
{
if (!pCurrCtx) return;
glClearColor((float)GetRedValue(dwClr)/255.0f, (float)GetGreenValue(dwClr)/255.0f, (float)GetBlueValue(dwClr)/255.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
bool CStdGL::UpdateClipper()
{
// no render target? do nothing
if (!RenderTarget || !Active) return true;
// negative/zero?
C4Rect clipRect = GetClipRect();
if (clipRect.Wdt<=0 || clipRect.Hgt<=0)
{
ClipAll=true;
return true;
}
ClipAll=false;
// set it
glViewport(clipRect.x, RenderTarget->Hgt-clipRect.y-clipRect.Hgt, clipRect.Wdt, clipRect.Hgt);
ProjectionMatrix = StdProjectionMatrix::Orthographic(clipRect.x, clipRect.x + clipRect.Wdt, clipRect.y + clipRect.Hgt, clipRect.y);
return true;
}
bool CStdGL::PrepareRendering(C4Surface * sfcToSurface)
{
// call from gfx thread only!
if (!pApp || !pApp->AssertMainThread()) return false;
// not ready?
if (!Active)
return false;
// target?
if (!sfcToSurface) return false;
// target locked?
if (sfcToSurface->Locked) return false;
// target is already set as render target?
if (sfcToSurface != RenderTarget)
{
// target is a render-target?
if (!sfcToSurface->IsRenderTarget()) return false;
// context
if (sfcToSurface->pCtx && sfcToSurface->pCtx != pCurrCtx)
if (!sfcToSurface->pCtx->Select()) return false;
// set target
RenderTarget=sfcToSurface;
// new target has different size; needs other clipping rect
UpdateClipper();
}
// done
return true;
}
bool CStdGL::PrepareSpriteShader(C4Shader& shader, const char* name, int ssc, C4GroupSet* pGroups, const char* const* additionalDefines, const char* const* additionalSlices)
{
const char* uniformNames[C4SSU_Count + 1];
uniformNames[C4SSU_ProjectionMatrix] = "projectionMatrix";
uniformNames[C4SSU_ModelViewMatrix] = "modelviewMatrix";
uniformNames[C4SSU_NormalMatrix] = "normalMatrix";
uniformNames[C4SSU_ClrMod] = "clrMod";
uniformNames[C4SSU_Gamma] = "gamma";
uniformNames[C4SSU_BaseTex] = "baseTex";
uniformNames[C4SSU_OverlayTex] = "overlayTex";
uniformNames[C4SSU_OverlayClr] = "overlayClr";
uniformNames[C4SSU_LightTex] = "lightTex";
uniformNames[C4SSU_LightTransform] = "lightTransform";
uniformNames[C4SSU_NormalTex] = "normalTex";
uniformNames[C4SSU_AmbientTex] = "ambientTex";
uniformNames[C4SSU_AmbientTransform] = "ambientTransform";
uniformNames[C4SSU_AmbientBrightness] = "ambientBrightness";
uniformNames[C4SSU_MaterialAmbient] = "materialAmbient"; // unused
uniformNames[C4SSU_MaterialDiffuse] = "materialDiffuse"; // unused
uniformNames[C4SSU_MaterialSpecular] = "materialSpecular"; // unused
uniformNames[C4SSU_MaterialEmission] = "materialEmission"; // unused
uniformNames[C4SSU_MaterialShininess] = "materialShininess"; // unused
uniformNames[C4SSU_Bones] = "bones"; // unused
uniformNames[C4SSU_CullMode] = "cullMode"; // unused
uniformNames[C4SSU_FrameCounter] = "frameCounter";
uniformNames[C4SSU_Count] = NULL;
const char* attributeNames[C4SSA_Count + 1];
attributeNames[C4SSA_Position] = "oc_Position";
attributeNames[C4SSA_Normal] = "oc_Normal"; // unused
attributeNames[C4SSA_TexCoord] = "oc_TexCoord"; // only used if C4SSC_Base is set
attributeNames[C4SSA_Color] = "oc_Color";
attributeNames[C4SSA_BoneIndices0] = "oc_BoneIndices0"; // unused
attributeNames[C4SSA_BoneIndices1] = "oc_BoneIndices1"; // unused
attributeNames[C4SSA_BoneWeights0] = "oc_BoneWeights0"; // unused
attributeNames[C4SSA_BoneWeights1] = "oc_BoneWeights1"; // unused
attributeNames[C4SSA_Count] = NULL;
// Clear previous content
shader.Clear();
shader.ClearSlices();
// Start with #defines
shader.AddDefine("OPENCLONK");
shader.AddDefine("OC_SPRITE");
if (ssc & C4SSC_MOD2) shader.AddDefine("OC_CLRMOD_MOD2");
if (ssc & C4SSC_NORMAL) shader.AddDefine("OC_WITH_NORMALMAP");
if (ssc & C4SSC_LIGHT) shader.AddDefine("OC_DYNAMIC_LIGHT");
if (ssc & C4SSC_BASE) shader.AddDefine("OC_HAVE_BASE");
if (ssc & C4SSC_OVERLAY) shader.AddDefine("OC_HAVE_OVERLAY");
if (additionalDefines)
for (const char* const* define = additionalDefines; *define != NULL; ++define)
shader.AddDefine(*define);
// Then load slices for fragment and vertex shader
shader.LoadVertexSlices(pGroups, "SpriteVertexShader.glsl");
shader.LoadFragmentSlices(pGroups, "CommonShader.glsl");
shader.LoadFragmentSlices(pGroups, "ObjectShader.glsl");
if (additionalSlices)
for (const char* const* slice = additionalSlices; *slice != NULL; ++slice)
shader.LoadFragmentSlices(pGroups, *slice);
if (!shader.Init(name, uniformNames, attributeNames))
{
shader.ClearSlices();
return false;
}
return true;
}
void CStdGL::ObjectLabel(uint32_t identifier, uint32_t name, int32_t length, const char * label)
{
#ifdef GL_KHR_debug
if (glObjectLabel)
glObjectLabel(identifier, name, length, label);
#endif
}
CStdGLCtx *CStdGL::CreateContext(C4Window * pWindow, C4AbstractApp *pApp)
{
// safety
if (!pWindow) return NULL;
// create it
CStdGLCtx *pCtx = new CStdGLCtx();
bool first_ctx = !pMainCtx;
if (first_ctx)
{
pMainCtx = pCtx;
LogF(" gl: Create first %scontext...", Config.Graphics.DebugOpenGL ? "debug " : "");
}
bool success = pCtx->Init(pWindow, pApp);
if (Config.Graphics.DebugOpenGL && glDebugMessageCallbackARB)
{
if (first_ctx) Log(" gl: Setting OpenGLDebugProc callback");
glDebugMessageCallbackARB(&OpenGLDebugProc, nullptr);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
#ifdef GL_KHR_debug
if (GLEW_KHR_debug)
glEnable(GL_DEBUG_OUTPUT);
#endif
}
// First context: Log some information about hardware/drivers
// Must log after context creation to get valid results
if (first_ctx)
{
const char *gl_vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
const char *gl_renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
const char *gl_version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
LogF("GL %s on %s (%s)", gl_version ? gl_version : "", gl_renderer ? gl_renderer : "", gl_vendor ? gl_vendor : "");
if (Config.Graphics.DebugOpenGL)
{
// Dump extension list
if (glGetStringi)
{
GLint gl_extension_count = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &gl_extension_count);
if (gl_extension_count == 0)
{
LogSilentF("No available extensions.");
}
else
{
LogSilentF("%d available extensions:", gl_extension_count);
for (GLint i = 0; i < gl_extension_count; ++i)
{
const char *gl_extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
LogSilentF(" %4d: %s", i, gl_extension);
}
}
}
else
{
const char *gl_extensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
LogSilentF("GLExt: %s", gl_extensions ? gl_extensions : "");
}
}
}
if (!success)
{
delete pCtx; Error(" gl: Error creating secondary context!"); return NULL;
}
// creation selected the new context - switch back to previous context
RenderTarget = NULL;
pCurrCtx = NULL;
// done
return pCtx;
}
void CStdGL::SetupMultiBlt(C4ShaderCall& call, const C4BltTransform* pTransform, GLuint baseTex, GLuint overlayTex, GLuint normalTex, DWORD dwOverlayModClr, StdProjectionMatrix* out_modelview)
{
// Initialize multi blit shader.
int iAdditive = dwBlitMode & C4GFXBLIT_ADDITIVE;
glBlendFunc(GL_SRC_ALPHA, iAdditive ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA);
call.Start();
// Upload uniforms
const DWORD dwModClr = BlitModulated ? BlitModulateClr : 0xffffffff;
const float fMod[4] = {
((dwModClr >> 16) & 0xff) / 255.0f,
((dwModClr >> 8) & 0xff) / 255.0f,
((dwModClr ) & 0xff) / 255.0f,
((dwModClr >> 24) & 0xff) / 255.0f
};
call.SetUniform4fv(C4SSU_ClrMod, 1, fMod);
call.SetUniform3fv(C4SSU_Gamma, 1, gammaOut);
if(baseTex != 0)
{
call.AllocTexUnit(C4SSU_BaseTex);
glBindTexture(GL_TEXTURE_2D, baseTex);
}
if(overlayTex != 0)
{
call.AllocTexUnit(C4SSU_OverlayTex);
glBindTexture(GL_TEXTURE_2D, overlayTex);
const float fOverlayModClr[4] = {
((dwOverlayModClr >> 16) & 0xff) / 255.0f,
((dwOverlayModClr >> 8) & 0xff) / 255.0f,
((dwOverlayModClr ) & 0xff) / 255.0f,
((dwOverlayModClr >> 24) & 0xff) / 255.0f
};
call.SetUniform4fv(C4SSU_OverlayClr, 1, fOverlayModClr);
}
if(pFoW != NULL && normalTex != 0)
{
call.AllocTexUnit(C4SSU_NormalTex);
glBindTexture(GL_TEXTURE_2D, normalTex);
}
if(pFoW != NULL)
{
const C4Rect OutRect = GetOutRect();
const C4Rect ClipRect = GetClipRect();
const FLOAT_RECT vpRect = pFoW->getViewportRegion();
// Dynamic Light
call.AllocTexUnit(C4SSU_LightTex);
glBindTexture(GL_TEXTURE_2D, pFoW->getSurfaceName());
float lightTransform[6];
pFoW->GetFragTransform(ClipRect, OutRect, lightTransform);
call.SetUniformMatrix2x3fv(C4SSU_LightTransform, 1, lightTransform);
// Ambient Light
call.AllocTexUnit(C4SSU_AmbientTex);
glBindTexture(GL_TEXTURE_2D, pFoW->getFoW()->Ambient.Tex);
call.SetUniform1f(C4SSU_AmbientBrightness, pFoW->getFoW()->Ambient.GetBrightness());
float ambientTransform[6];
pFoW->getFoW()->Ambient.GetFragTransform(vpRect, ClipRect, OutRect, ambientTransform);
call.SetUniformMatrix2x3fv(C4SSU_AmbientTransform, 1, ambientTransform);
}
call.SetUniform1f(C4SSU_CullMode, 0.0f);
// The primary reason we use a 4x4 matrix for the modelview matrix is that
// that the C4BltTransform pTransform parameter can have projection components
// (see SetObjDrawTransform2). Still, for sprites the situation is a bit
// unsatisfactory because there's no distinction between modelview and projection
// components in the BltTransform. Object rotation is part of the BltTransform
// for sprites, which should be part of the modelview matrix, so that lighting
// is correct for rotated sprites. This is much more common than projection
// components in the BltTransform, and therefore we turn the BltTransform into
// the modelview matrix and not the projection matrix.
StdProjectionMatrix default_modelview = StdProjectionMatrix::Identity();
StdProjectionMatrix& modelview = out_modelview ? *out_modelview : default_modelview;
// Apply zoom and transform
Translate(modelview, ZoomX, ZoomY, 0.0f);
// Scale Z as well so that we don't distort normals.
Scale(modelview, Zoom, Zoom, Zoom);
Translate(modelview, -ZoomX, -ZoomY, 0.0f);
if(pTransform)
{
float sz = 1.0f;
if (pFoW != NULL && normalTex != 0)
{
// Decompose scale factors and scale Z accordingly to X and Y, again to avoid distorting normals
// We could instead work around this by using the projection matrix, but then for object rotations (SetR)
// the normals would not be correct.
const float sx = sqrt(pTransform->mat[0]*pTransform->mat[0] + pTransform->mat[1]*pTransform->mat[1]);
const float sy = sqrt(pTransform->mat[3]*pTransform->mat[3] + pTransform->mat[4]*pTransform->mat[4]);
sz = sqrt(sx * sy);
}
// Multiply modelview matrix with transform
StdProjectionMatrix transform;
transform(0, 0) = pTransform->mat[0];
transform(0, 1) = pTransform->mat[1];
transform(0, 2) = 0.0f;
transform(0, 3) = pTransform->mat[2];
transform(1, 0) = pTransform->mat[3];
transform(1, 1) = pTransform->mat[4];
transform(1, 2) = 0.0f;
transform(1, 3) = pTransform->mat[5];
transform(2, 0) = 0.0f;
transform(2, 1) = 0.0f;
transform(2, 2) = sz;
transform(2, 3) = 0.0f;
transform(3, 0) = pTransform->mat[6];
transform(3, 1) = pTransform->mat[7];
transform(3, 2) = 0.0f;
transform(3, 3) = pTransform->mat[8];
modelview *= transform;
}
call.SetUniformMatrix4x4(C4SSU_ProjectionMatrix, ProjectionMatrix);
call.SetUniformMatrix4x4(C4SSU_ModelViewMatrix, modelview);
if (pFoW != NULL && normalTex != 0)
call.SetUniformMatrix3x3Transpose(C4SSU_NormalMatrix, StdMeshMatrix::Inverse(StdProjectionMatrix::Upper3x4(modelview)));
}
void CStdGL::PerformMultiPix(C4Surface* sfcTarget, const C4BltVertex* vertices, unsigned int n_vertices, C4ShaderCall* shader_call)
{
// Draw on pixel center:
StdProjectionMatrix transform = StdProjectionMatrix::Translate(0.5f, 0.5f, 0.0f);
// This is a workaround. Instead of submitting the whole vertex array to the GL, we do it
// in batches of 256 vertices. The intel graphics driver on Linux crashes with
// significantly larger arrays, such as 400. It's not clear to me why, maybe POINT drawing
// is just not very well tested.
const unsigned int BATCH_SIZE = 256;
// Feed the vertices to the GL
if (!shader_call)
{
C4ShaderCall call(GetSpriteShader(false, false, false));
SetupMultiBlt(call, NULL, 0, 0, 0, 0, &transform);
for(unsigned int i = 0; i < n_vertices; i += BATCH_SIZE)
PerformMultiBlt(sfcTarget, OP_POINTS, &vertices[i], std::min(n_vertices - i, BATCH_SIZE), false, &call);
}
else
{
SetupMultiBlt(*shader_call, NULL, 0, 0, 0, 0, &transform);
for(unsigned int i = 0; i < n_vertices; i += BATCH_SIZE)
PerformMultiBlt(sfcTarget, OP_POINTS, &vertices[i], std::min(n_vertices - i, BATCH_SIZE), false, shader_call);
}
}
void CStdGL::PerformMultiLines(C4Surface* sfcTarget, const C4BltVertex* vertices, unsigned int n_vertices, float width, C4ShaderCall* shader_call)
{
// In a first step, we transform the lines array to a triangle array, so that we can draw
// the lines with some thickness.
// In principle, this step could be easily (and probably much more efficiently) performed
// by a geometry shader as well, however that would require OpenGL 3.2.
C4BltVertex* tri_vertices = new C4BltVertex[n_vertices * 3];
for(unsigned int i = 0; i < n_vertices; i += 2)
{
const float x1 = vertices[i].ftx;
const float y1 = vertices[i].fty;
const float x2 = vertices[i+1].ftx;
const float y2 = vertices[i+1].fty;
float offx = y1 - y2;
float offy = x2 - x1;
float l = sqrtf(offx * offx + offy * offy);
// avoid division by zero
l += 0.000000005f;
offx *= width/l;
offy *= width/l;
tri_vertices[3*i + 0].ftx = x1 + offx; tri_vertices[3*i + 0].fty = y1 + offy;
tri_vertices[3*i + 1].ftx = x1 - offx; tri_vertices[3*i + 1].fty = y1 - offy;
tri_vertices[3*i + 2].ftx = x2 - offx; tri_vertices[3*i + 2].fty = y2 - offy;
tri_vertices[3*i + 3].ftx = x2 + offx; tri_vertices[3*i + 3].fty = y2 + offy;
for(int j = 0; j < 4; ++j)
{
tri_vertices[3*i + 0].color[j] = vertices[i].color[j];
tri_vertices[3*i + 1].color[j] = vertices[i].color[j];
tri_vertices[3*i + 2].color[j] = vertices[i + 1].color[j];
tri_vertices[3*i + 3].color[j] = vertices[i + 1].color[j];
}
tri_vertices[3*i + 0].tx = 0.f; tri_vertices[3*i + 0].ty = 0.f;
tri_vertices[3*i + 1].tx = 0.f; tri_vertices[3*i + 1].ty = 2.f;
tri_vertices[3*i + 2].tx = 1.f; tri_vertices[3*i + 2].ty = 2.f;
tri_vertices[3*i + 3].tx = 1.f; tri_vertices[3*i + 3].ty = 0.f;
tri_vertices[3*i + 4] = tri_vertices[3*i + 2]; // duplicate vertex
tri_vertices[3*i + 5] = tri_vertices[3*i + 0]; // duplicate vertex
}
// Then, feed the vertices to the GL
if (!shader_call)
{
C4ShaderCall call(GetSpriteShader(true, false, false));
SetupMultiBlt(call, NULL, lines_tex, 0, 0, 0, NULL);
PerformMultiBlt(sfcTarget, OP_TRIANGLES, tri_vertices, n_vertices * 3, true, &call);
}
else
{
SetupMultiBlt(*shader_call, NULL, lines_tex, 0, 0, 0, NULL);
PerformMultiBlt(sfcTarget, OP_TRIANGLES, tri_vertices, n_vertices * 3, true, shader_call);
}
delete[] tri_vertices;
}
void CStdGL::PerformMultiTris(C4Surface* sfcTarget, const C4BltVertex* vertices, unsigned int n_vertices, const C4BltTransform* pTransform, C4TexRef* pTex, C4TexRef* pOverlay, C4TexRef* pNormal, DWORD dwOverlayModClr, C4ShaderCall* shader_call)
{
// Feed the vertices to the GL
if (!shader_call)
{
C4ShaderCall call(GetSpriteShader(pTex != NULL, pOverlay != NULL, pNormal != NULL));
SetupMultiBlt(call, pTransform, pTex ? pTex->texName : 0, pOverlay ? pOverlay->texName : 0, pNormal ? pNormal->texName : 0, dwOverlayModClr, NULL);
PerformMultiBlt(sfcTarget, OP_TRIANGLES, vertices, n_vertices, pTex != NULL, &call);
}
else
{
SetupMultiBlt(*shader_call, pTransform, pTex ? pTex->texName : 0, pOverlay ? pOverlay->texName : 0, pNormal ? pNormal->texName : 0, dwOverlayModClr, NULL);
PerformMultiBlt(sfcTarget, OP_TRIANGLES, vertices, n_vertices, pTex != NULL, shader_call);
}
}
void CStdGL::PerformMultiBlt(C4Surface* sfcTarget, DrawOperation op, const C4BltVertex* vertices, unsigned int n_vertices, bool has_tex, C4ShaderCall* shader_call)
{
// Only direct rendering
assert(sfcTarget->IsRenderTarget());
if(!PrepareRendering(sfcTarget)) return;
// Select a buffer
const unsigned int vbo_index = CurrentVBO;
CurrentVBO = (CurrentVBO + 1) % N_GENERIC_VBOS;
// Upload data into the buffer, resize buffer if necessary
glBindBuffer(GL_ARRAY_BUFFER, GenericVBOs[vbo_index]);
if (GenericVBOSizes[vbo_index] < n_vertices)
{
GenericVBOSizes[vbo_index] = n_vertices;
glBufferData(GL_ARRAY_BUFFER, n_vertices * sizeof(C4BltVertex), vertices, GL_STREAM_DRAW);
}
else
{
glBufferSubData(GL_ARRAY_BUFFER, 0, n_vertices * sizeof(C4BltVertex), vertices);
}
// Choose the VAO that corresponds to the chosen VBO. Also, use one
// that supplies texture coordinates if we have texturing enabled.
GLuint vao;
const unsigned int vao_index = vbo_index + (has_tex ? N_GENERIC_VBOS : 0);
const unsigned int vao_id = GenericVAOs[vao_index];
const bool has_vao = GetVAO(vao_id, vao);
glBindVertexArray(vao);
if (!has_vao)
{
// Initialize VAO for this context
const GLuint position = shader_call->GetAttribute(C4SSA_Position);
const GLuint color = shader_call->GetAttribute(C4SSA_Color);
const GLuint texcoord = has_tex ? shader_call->GetAttribute(C4SSA_TexCoord) : 0;
glEnableVertexAttribArray(position);
glEnableVertexAttribArray(color);
if (has_tex)
glEnableVertexAttribArray(texcoord);
glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, sizeof(C4BltVertex), reinterpret_cast<const uint8_t*>(offsetof(C4BltVertex, ftx)));
glVertexAttribPointer(color, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(C4BltVertex), reinterpret_cast<const uint8_t*>(offsetof(C4BltVertex, color)));
if (has_tex)
glVertexAttribPointer(texcoord, 2, GL_FLOAT, GL_FALSE, sizeof(C4BltVertex), reinterpret_cast<const uint8_t*>(offsetof(C4BltVertex, tx)));
}
switch (op)
{
case OP_POINTS:
glDrawArrays(GL_POINTS, 0, n_vertices);
break;
case OP_TRIANGLES:
glDrawArrays(GL_TRIANGLES, 0, n_vertices);
break;
default:
assert(false);
break;
}
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
C4Shader* CStdGL::GetSpriteShader(bool haveBase, bool haveOverlay, bool haveNormal)
{
int ssc = 0;
if(dwBlitMode & C4GFXBLIT_MOD2) ssc |= C4SSC_MOD2;
if(haveBase) ssc |= C4SSC_BASE;
if(haveBase && haveOverlay) ssc |= C4SSC_OVERLAY;
if(pFoW != NULL) ssc |= C4SSC_LIGHT;
if(pFoW != NULL && haveBase && haveNormal) ssc |= C4SSC_NORMAL;
return GetSpriteShader(ssc);
}
C4Shader* CStdGL::GetSpriteShader(int ssc)
{
C4Shader* shaders[16] = {
&SpriteShader,
&SpriteShaderMod2,
&SpriteShaderBase,
&SpriteShaderBaseMod2,
&SpriteShaderBaseOverlay,
&SpriteShaderBaseOverlayMod2,
&SpriteShaderLight,
&SpriteShaderLightMod2,
&SpriteShaderLightBase,
&SpriteShaderLightBaseMod2,
&SpriteShaderLightBaseOverlay,
&SpriteShaderLightBaseOverlayMod2,
&SpriteShaderLightBaseNormal,
&SpriteShaderLightBaseNormalMod2,
&SpriteShaderLightBaseNormalOverlay,
&SpriteShaderLightBaseNormalOverlayMod2,
};
int index = 0;
if(ssc & C4SSC_LIGHT) index += 6;
if(ssc & C4SSC_BASE)
{
index += 2;
if(ssc & C4SSC_OVERLAY)
index += 2;
if( (ssc & C4SSC_NORMAL) && (ssc & C4SSC_LIGHT))
index += 4;
}
if(ssc & C4SSC_MOD2)
index += 1;
assert(index < 16);
return shaders[index];
}
bool CStdGL::InitShaders(C4GroupSet* pGroups)
{
// Create sprite blitting shaders
if(!PrepareSpriteShader(SpriteShader, "sprite", 0, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderMod2, "spriteMod2", C4SSC_MOD2, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderBase, "spriteBase", C4SSC_BASE, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderBaseMod2, "spriteBaseMod2", C4SSC_MOD2 | C4SSC_BASE, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderBaseOverlay, "spriteBaseOverlay", C4SSC_BASE | C4SSC_OVERLAY, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderBaseOverlayMod2, "spriteBaseOverlayMod2", C4SSC_MOD2 | C4SSC_BASE | C4SSC_OVERLAY, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLight, "spriteLight", C4SSC_LIGHT, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightMod2, "spriteLightMod2", C4SSC_LIGHT | C4SSC_MOD2, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBase, "spriteLightBase", C4SSC_LIGHT | C4SSC_BASE, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBaseMod2, "spriteLightBaseMod2", C4SSC_LIGHT | C4SSC_BASE | C4SSC_MOD2, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBaseOverlay, "spriteLightBaseOverlay", C4SSC_LIGHT | C4SSC_BASE | C4SSC_OVERLAY, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBaseOverlayMod2, "spriteLightBaseOverlayMod2", C4SSC_LIGHT | C4SSC_BASE | C4SSC_OVERLAY | C4SSC_MOD2, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBaseNormal, "spriteLightBaseNormal", C4SSC_LIGHT | C4SSC_BASE | C4SSC_NORMAL, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBaseNormalMod2, "spriteLightBaseNormalMod2", C4SSC_LIGHT | C4SSC_BASE | C4SSC_NORMAL | C4SSC_MOD2, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBaseNormalOverlay, "spriteLightBaseNormalOverlay", C4SSC_LIGHT | C4SSC_BASE | C4SSC_OVERLAY | C4SSC_NORMAL, pGroups, NULL, NULL))
return false;
if(!PrepareSpriteShader(SpriteShaderLightBaseNormalOverlayMod2, "spriteLightBaseNormalOverlayMod2", C4SSC_LIGHT | C4SSC_BASE | C4SSC_OVERLAY | C4SSC_NORMAL | C4SSC_MOD2, pGroups, NULL, NULL))
return false;
return true;
}
bool CStdGL::RestoreDeviceObjects()
{
assert(pMainCtx);
// delete any previous objects
InvalidateDeviceObjects();
// set states
Active = pMainCtx->Select();
RenderTarget = pApp->pWindow->pSurface;
// lines texture
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &lines_tex);
glBindTexture(GL_TEXTURE_2D, lines_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
static const char * linedata = "\xff\xff\xff\x00\xff\xff\xff\xff";
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 2, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, linedata);
MaxTexSize = 64;
GLint s = 0;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &s);
if (s>0) MaxTexSize = s;
// Generic VBOs
glGenBuffers(N_GENERIC_VBOS, GenericVBOs);
for (unsigned int i = 0; i < N_GENERIC_VBOS; ++i)
{
GenericVBOSizes[i] = GENERIC_VBO_SIZE;
glBindBuffer(GL_ARRAY_BUFFER, GenericVBOs[i]);
glBufferData(GL_ARRAY_BUFFER, GenericVBOSizes[i] * sizeof(C4BltVertex), NULL, GL_STREAM_DRAW);
GenericVAOs[i] = GenVAOID();
GenericVAOs[i + N_GENERIC_VBOS] = GenVAOID();
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
// reset blit states
dwBlitMode = 0;
// done
return Active;
}
bool CStdGL::InvalidateDeviceObjects()
{
bool fSuccess=true;
// deactivate
Active=false;
// invalidate font objects
// invalidate primary surfaces
if (lines_tex)
{
glDeleteTextures(1, &lines_tex);
lines_tex = 0;
}
// invalidate generic VBOs
if (GenericVBOs[0] != 0)
{
glDeleteBuffers(N_GENERIC_VBOS, GenericVBOs);
GenericVBOs[0] = 0;
CurrentVBO = 0;
for (unsigned int i = 0; i < N_GENERIC_VBOS * 2; ++i)
FreeVAOID(GenericVAOs[i]);
}
// invalidate shaders
// TODO: We don't do this here because we cannot re-validate them in
// RestoreDeviceObjects. This should be refactored.
/*SpriteShader.Clear();
SpriteShaderMod2.Clear();
SpriteShaderBase.Clear();
SpriteShaderBaseMod2.Clear();
SpriteShaderBaseOverlay.Clear();
SpriteShaderBaseOverlayMod2.Clear();
SpriteShaderLight.Clear();
SpriteShaderLightMod2.Clear();
SpriteShaderLightBase.Clear();
SpriteShaderLightBaseMod2.Clear();
SpriteShaderLightBaseOverlay.Clear();
SpriteShaderLightBaseOverlayMod2.Clear();
SpriteShaderLightBaseNormal.Clear();
SpriteShaderLightBaseNormalMod2.Clear();
SpriteShaderLightBaseNormalOverlay.Clear();
SpriteShaderLightBaseNormalOverlayMod2.Clear();*/
return fSuccess;
}
bool CStdGL::Error(const char *szMsg)
{
#ifdef USE_WIN32_WINDOWS
DWORD err = GetLastError();
#endif
bool r = C4Draw::Error(szMsg);
#ifdef USE_WIN32_WINDOWS
wchar_t * lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
LogF(" gl: GetLastError() = %d - %s", err, StdStrBuf(lpMsgBuf).getData());
LocalFree(lpMsgBuf);
#endif
LogF(" gl: %s", glGetString(GL_VENDOR));
LogF(" gl: %s", glGetString(GL_RENDERER));
LogF(" gl: %s", glGetString(GL_VERSION));
LogF(" gl: %s", glGetString(GL_EXTENSIONS));
return r;
}
const char* CStdGL::GLErrorString(GLenum code)
{
switch (code)
{
case GL_NO_ERROR: return "No error";
case GL_INVALID_ENUM: return "An unacceptable value is specified for an enumerated argument";
case GL_INVALID_VALUE: return "A numeric argument is out of range";
case GL_INVALID_OPERATION: return "The specified operation is not allowed in the current state";
case GL_INVALID_FRAMEBUFFER_OPERATION: return "The framebuffer object is not complete";
case GL_OUT_OF_MEMORY: return "There is not enough memory left to execute the command";
case GL_STACK_UNDERFLOW: return "An attempt has been made to perform an operation that would cause an internal stack to underflow";
case GL_STACK_OVERFLOW: return "An attempt has been made to perform an operation that would cause an internal stack to overflow";
default: assert(false); return "";
}
}
bool CStdGL::CheckGLError(const char *szAtOp)
{
GLenum err = glGetError();
if (!err) return true;
LogF("GL error with %s: %d - %s", szAtOp, err, GLErrorString(err));
return false;
}
CStdGL *pGL=NULL;
bool CStdGL::OnResolutionChanged(unsigned int iXRes, unsigned int iYRes)
{
// Re-create primary clipper to adapt to new size.
CreatePrimaryClipper(iXRes, iYRes);
RestoreDeviceObjects();
return true;
}
void CStdGL::Default()
{
C4Draw::Default();
pCurrCtx = NULL;
iPixelFormat=0;
sfcFmt=0;
Workarounds.LowMaxVertexUniformCount = false;
}
unsigned int CStdGL::GenVAOID()
{
// Generate a new VAO ID. Make them sequential so that the actual
// VAOs in the context can be simply maintained with a lookup table.
unsigned int id;
if (NextVAOID == VAOIDs.begin())
{
// Insert at the beginning
id = 1;
}
else
{
// Insert at the end, or somewhere in the middle
std::set<unsigned int>::iterator iter = NextVAOID;
--iter;
id = *iter + 1;
}
// Actually insert the ID
#ifdef NDEBUG
std::set<unsigned int>::iterator inserted_iter = VAOIDs.insert(NextVAOID, id);
#else
std::pair<std::set<unsigned int>::iterator, bool> inserted = VAOIDs.insert(id);
assert(inserted.second == true);
std::set<unsigned int>::iterator inserted_iter = inserted.first;
#endif
// Update next VAO ID: increment iterator until we find a gap
// in the sequence.
NextVAOID = inserted_iter;
unsigned int prev_id = id;
++NextVAOID;
while(NextVAOID != VAOIDs.end() && prev_id + 1 == *NextVAOID)
{
prev_id = *NextVAOID;
++NextVAOID;
}
return id;
}
void CStdGL::FreeVAOID(unsigned int vaoid)
{
std::set<unsigned int>::iterator iter = VAOIDs.find(vaoid);
assert(iter != VAOIDs.end());
// Delete this VAO in the current context
if (pCurrCtx)
{
if (vaoid < pCurrCtx->hVAOs.size() && pCurrCtx->hVAOs[vaoid] != 0)
{
glDeleteVertexArrays(1, &pCurrCtx->hVAOs[vaoid]);
pCurrCtx->hVAOs[vaoid] = 0;
}
}
// For all other contexts, mark it to be deleted as soon as we select
// that context. Otherwise we would need to do a lot of context
// switching at this point.
for (std::list<CStdGLCtx*>::iterator iter = CStdGLCtx::contexts.begin(); iter != CStdGLCtx::contexts.end(); ++iter)
{
CStdGLCtx* ctx = *iter;
if (ctx != pCurrCtx && vaoid < ctx->hVAOs.size() && ctx->hVAOs[vaoid] != 0)
if (std::find(ctx->VAOsToBeDeleted.begin(), ctx->VAOsToBeDeleted.end(), vaoid) == ctx->VAOsToBeDeleted.end())
ctx->VAOsToBeDeleted.push_back(vaoid);
}
// Delete the VAO ID from our list of VAO IDs in use
// If the Next VAO ID is 1, then no matter what we delete we don't need
// to update anything. If it is not at the beginning, then move it to the
// gap we just created if it was at a higher place, to make sure we keep
// the numbers as sequential as possible.
unsigned int nextVaoID = 1;
if (NextVAOID != VAOIDs.begin())
{
std::set<unsigned int>::iterator next_vao_iter = NextVAOID;
--next_vao_iter;
nextVaoID = *next_vao_iter + 1;
}
assert(vaoid != nextVaoID);
if (vaoid < nextVaoID || iter == NextVAOID)
NextVAOID = VAOIDs.erase(iter);
else
VAOIDs.erase(iter);
}
bool CStdGL::GetVAO(unsigned int vaoid, GLuint& vao)
{
assert(pCurrCtx != NULL);
if (vaoid >= pCurrCtx->hVAOs.size())
{
// Resize the VAO array so that all generated VAO IDs fit
// in it, and not only the one requested in this call.
// We hope to get away with fewer reallocations this way.
assert(VAOIDs.find(vaoid) != VAOIDs.end());
std::set<unsigned int>::iterator iter = VAOIDs.end();
--iter;
pCurrCtx->hVAOs.resize(*iter + 1);
}
if (pCurrCtx->hVAOs[vaoid] == 0)
{
glGenVertexArrays(1, &pCurrCtx->hVAOs[vaoid]);
vao = pCurrCtx->hVAOs[vaoid];
return false;
}
vao = pCurrCtx->hVAOs[vaoid];
return true;
}
#endif // USE_CONSOLE