Files
starruler-linux/source/game/render/gl_driver.cpp
T
2018-07-17 14:15:37 +02:00

1436 lines
37 KiB
C++

#include "compat/misc.h"
#include "compat/gl.h"
#include "render/driver.h"
#include "render/gl_driver.h"
#include "render/gl_mesh.h"
#include "render/gl_shader.h"
#include "render/gl_texture.h"
#include "render/lighting.h"
#include "render/camera.h"
#include "render/gl_framebuffer.h"
#include "render/vertexBuffer.h"
#include "util/mesh_generation.h"
#include "main/references.h"
#include "main/tick.h"
#include "main/logging.h"
#include "matrix.h"
#include "frustum.h"
#include <algorithm>
extern char lockText[1024];
bool glDirectStateAccess = false;
namespace render {
unsigned drawCalls = 0;
extern const RenderMesh* lastRenderedMesh;
float* shaderUniforms = 0;
bool alphaTest = false, blend = false;
static inline void setRenderFuncs(BaseMaterial mat, bool intermediate) {
switch(mat) {
case MAT_Solid:
if(alphaTest) {
glDisable(GL_ALPHA_TEST);
alphaTest = false;
}
if(blend) {
glDisable(GL_BLEND);
blend = false;
}
break;
case MAT_Add:
if(alphaTest) {
glDisable(GL_ALPHA_TEST);
alphaTest = false;
}
if(!blend) {
glEnable(GL_BLEND);
blend = true;
}
glBlendFuncSeparate(
GL_ONE, GL_ONE,
GL_ONE, GL_ZERO);
break;
case MAT_Alpha:
case MAT_Font:
if(!alphaTest) {
glEnable(GL_ALPHA_TEST);
alphaTest = true;
}
if(!blend) {
glEnable(GL_BLEND);
blend = true;
}
if(intermediate) {
glBlendFuncSeparate(
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
else {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
break;
case MAT_Overlay:
if(!alphaTest) {
glEnable(GL_ALPHA_TEST);
alphaTest = true;
}
if(!blend) {
glEnable(GL_BLEND);
blend = true;
}
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
break;
}
}
Texture* RenderDriver::createTexture() {
return new GLTexture();
}
Texture* RenderDriver::createCubemap() {
return new GLCubeMap();
}
bool isIntelCard = false;
class GLDriver : public RenderDriver {
public:
const RenderState* pLastRenderState;
unsigned textureStage;
unsigned char activeType[RENDER_MAX_TEXTURES];
const RenderState* skybox;
const RenderMesh* skyboxMesh;
bool cleared;
bool intermediateBlend;
vec2i screenSize, frameSize;
double fov;
double zNear, zFar;
std::stack<recti> viewportClips;
frustum viewFrustum;
vec3d cam_right,
bb_up_p_right, bb_up_m_right;
quaterniond BBFacingRot;
Colorf diffuse, specular;
float shininess;
RenderState state_3d;
RenderState state_2d;
float invView[9];
vec3f lightPosition[2];
vec2f screenLight[2];
double lightRadius[2];
bool lightActive[2];
bool isPrepared;
GLDriver() : textureStage(GL_TEXTURE0), pLastRenderState(0), skybox(0), skyboxMesh(0),
cleared(false), intermediateBlend(false), fov(50.0), zNear(1.0), zFar(240000.0)
{
state_2d.lighting = false;
state_2d.depthTest = DT_NoDepthTest;
state_2d.baseMat = MAT_Alpha;
state_2d.culling = FC_None;
}
~GLDriver() {
}
const RenderState* getLastRenderState() const override {
return pLastRenderState ? pLastRenderState : &activeRenderState;
}
void reportErrors(const char* context = nullptr) const override {
GLenum err = glGetError();
while(err != GL_NO_ERROR) {
error("Error %s: %d (0x%04x)", context ? context : "in OpenGL", err, err);
err = glGetError();
}
}
const frustum& getViewFrustum() const {
return viewFrustum;
}
void getInverseView(float* mat3) const override {
memcpy(mat3, invView, 9 * sizeof(float));
}
void setCameraData(Camera& camera) {
cam_pos = camera.getPosition();
cam_facing = camera.getFacing();
cam_up = camera.getUp();
cam_right = camera.getRight();
bb_up_p_right = cam_up - cam_right;
bb_up_m_right = cam_up + cam_right;
auto rot = camera.getRotation();
Matrix mat = rot.toMatrix();
vec3d f = cam_facing;
vec3d s = f.cross(cam_up);
vec3d u = s.cross(f);
//s0 s1 s2 0
//u0 u1 u2 0
//-f0 -f1 -f2 0
//0 0 0 1
invView[0] = s.x;
invView[1] = u.x;
invView[2] = -f.x;
invView[3] = s.y;
invView[4] = u.y;
invView[5] = -f.y;
invView[6] = s.z;
invView[7] = u.z;
invView[8] = -f.z;
auto yaw = quaterniond::fromAxisAngle(vec3d::up(-1.0), atan2(cam_facing.z, cam_facing.x));
auto pitch = quaterniond::fromAxisAngle(vec3d::right(), asin(-cam_facing.y));
BBFacingRot = yaw * pitch;
viewFrustum = frustum(camera.screenToRay(0,0), camera.screenToRay(1,0), camera.screenToRay(0,1), camera.screenToRay(1,1));
}
void setTextureStage(unsigned stage) {
if(stage != textureStage) {
glActiveTexture(stage);
textureStage = stage;
}
}
void setTransformation(const Matrix& matrix) override {
glPushMatrix();
Matrix temp(matrix);
temp[12] -= cam_pos.x;
temp[13] -= cam_pos.y;
temp[14] -= cam_pos.z;
glMultMatrixd(temp.m);
}
void setTransformationAbs(const Matrix& matrix) override {
glPushMatrix();
glMultMatrixd(matrix.m);
}
void setTransformationIdentity() override {
glPushMatrix();
glLoadIdentity();
}
void setBBTransform(vec3d pos, double width, double rot) override {
glPushMatrix();
Matrix m;
//BBFacingRot.toTransform(m, pos - cam_pos, vec3d(width * 0.5));
(quaterniond::fromAxisAngle(cam_facing, -rot) * BBFacingRot).toTransform(m, pos - cam_pos, vec3d(width * 0.5));
glMultMatrixd(m.m);
}
void resetTransformation() override {
glPopMatrix();
}
void setDefaultRenderState() {
activeRenderState = RenderState();
pLastRenderState = 0;
lastRenderedMesh = 0;
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glAlphaFunc(GL_GREATER, 1.f/255.f);
setRenderFuncs(MAT_Solid, false);
glEnable(GL_LIGHTING);
glDisable(GL_NORMALIZE);
glUseProgram(0);
for(int i = 0; i < RENDER_MAX_TEXTURES; ++i) {
glActiveTexture(GL_TEXTURE0 + i);
glDisable(GL_TEXTURE_2D);
glDisable(GL_TEXTURE_CUBE_MAP);
}
glActiveTexture(GL_TEXTURE0);
textureStage = GL_TEXTURE0;
diffuse = Colorf(1.f, 1.f, 1.f, 1.f);
specular = Colorf(1.f, 1.f, 1.f, 1.f);
shininess = 8.f;
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (const GLfloat*)&diffuse);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (const GLfloat*)&specular);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
}
void set2DRenderState() {
switchToRenderState(state_2d);
}
void switchToRenderState(const RenderState& state) {
if(state.constant && pLastRenderState == &state) {
if(state.shader && !state.shader->constant) {
if(shaderUniforms)
state.shader->loadDynamicVars(shaderUniforms);
else
state.shader->updateDynamicVars();
}
return;
}
//Front/Back face culling
if(state.culling != activeRenderState.culling) {
if(state.culling == FC_None) {
glDisable(GL_CULL_FACE);
}
else {
if(activeRenderState.culling == FC_None)
glEnable(GL_CULL_FACE);
switch(state.culling) {
case FC_Front:
glCullFace(GL_FRONT); break;
case FC_Back:
glCullFace(GL_BACK); break;
case FC_Both:
glCullFace(GL_FRONT_AND_BACK); break;
NO_DEFAULT
}
}
}
//Depth write
if(state.depthWrite != activeRenderState.depthWrite)
glDepthMask(state.depthWrite ? GL_TRUE : GL_FALSE);
//Depth test
if(state.depthTest != activeRenderState.depthTest) {
if(state.depthTest == DT_NoDepthTest)
glDisable(GL_DEPTH_TEST);
else {
if(activeRenderState.depthTest == DT_NoDepthTest)
glEnable(GL_DEPTH_TEST);
switch(state.depthTest) {
case DT_Never:
glDepthFunc(GL_NEVER); break;
case DT_Less:
glDepthFunc(GL_LESS); break;
case DT_Equal:
glDepthFunc(GL_EQUAL); break;
case DT_LessEqual:
glDepthFunc(GL_LEQUAL); break;
case DT_Greater:
glDepthFunc(GL_GREATER); break;
case DT_NotEqual:
glDepthFunc(GL_NOTEQUAL); break;
case DT_GreaterEqual:
glDepthFunc(GL_GEQUAL); break;
//case DT_Always:
// glDepthFunc(GL_ALWAYS); break;
NO_DEFAULT
}
}
}
//Texture states
for(int i = 0; i < RENDER_MAX_TEXTURES; ++i) {
if(activeRenderState.textures[i] == state.textures[i])
continue;
Texture* tex = state.textures[i];
if(glDirectStateAccess) {
if(tex == 0) {
if(activeType[i] == TT_2D)
glDisablei(GL_TEXTURE_2D, i);
else
glDisablei(GL_TEXTURE_CUBE_MAP, i);
}
else {
auto type = tex->type;
GLenum glType;
if(type == TT_2D)
glType = GL_TEXTURE_2D;
else
glType = GL_TEXTURE_CUBE_MAP;
if(activeRenderState.textures[i] == 0) {
glEnablei(glType, i);
activeType[i] = type;
}
else if(activeType[i] != type) {
if(type == TT_2D)
glDisablei(GL_TEXTURE_CUBE_MAP, i);
else
glDisablei(GL_TEXTURE_2D, i);
glEnablei(glType, i);
activeType[i] = type;
}
auto texunit = GL_TEXTURE0 + i;
glBindMultiTextureEXT(texunit, glType, tex->getID());
//Wrapping settings
bool changed = false;
if(state.wrapHorizontal != tex->prevRenderState.wrapHorizontal) {
GLint mode;
switch(state.wrapHorizontal) {
case TW_Repeat:
mode = GL_REPEAT; break;
case TW_Clamp:
mode = GL_CLAMP; break;
case TW_ClampEdge:
mode = GL_CLAMP_TO_EDGE; break;
case TW_Mirror:
mode = GL_MIRRORED_REPEAT; break;
NO_DEFAULT
}
glMultiTexParameteriEXT(texunit, glType, GL_TEXTURE_WRAP_S, mode);
changed = true;
}
if(state.wrapVertical != tex->prevRenderState.wrapVertical) {
GLint mode;
switch(state.wrapVertical) {
case TW_Repeat:
mode = GL_REPEAT; break;
case TW_Clamp:
mode = GL_CLAMP; break;
case TW_ClampEdge:
mode = GL_CLAMP_TO_EDGE; break;
case TW_Mirror:
mode = GL_MIRRORED_REPEAT; break;
NO_DEFAULT
}
glMultiTexParameteriEXT(texunit, glType, GL_TEXTURE_WRAP_T, mode);
changed = true;
}
//Mipmap settings
if(state.filterMin != tex->prevRenderState.filterMin) {
GLint mode;
switch(state.filterMin) {
case TF_Nearest:
mode = tex->hasMipMaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST; break;
case TF_Linear:
mode = tex->hasMipMaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; break;
NO_DEFAULT
}
glMultiTexParameteriEXT(texunit, glType, GL_TEXTURE_MIN_FILTER, mode);
changed = true;
}
if(state.filterMag != tex->prevRenderState.filterMag) {
GLint mode;
switch(state.filterMag) {
case TF_Nearest:
mode = tex->hasMipMaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST; break;
case TF_Linear:
mode = tex->hasMipMaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; break;
NO_DEFAULT
}
glMultiTexParameteriEXT(texunit, glType, GL_TEXTURE_MAG_FILTER, mode);
changed = true;
}
//Update the previous render state
if(changed)
tex->prevRenderState = state;
}
}
else {
setTextureStage(GL_TEXTURE0 + i);
if(tex == 0) {
if(activeType[i] == TT_2D)
glDisable(GL_TEXTURE_2D);
else
glDisable(GL_TEXTURE_CUBE_MAP);
}
else {
auto type = tex->type;
GLenum glType;
if(type == TT_2D)
glType = GL_TEXTURE_2D;
else
glType = GL_TEXTURE_CUBE_MAP;
if(activeRenderState.textures[i] == 0) {
glEnable(glType);
activeType[i] = type;
}
else if(activeType[i] != type) {
if(type == TT_2D)
glDisable(GL_TEXTURE_CUBE_MAP);
else
glDisable(GL_TEXTURE_2D);
glEnable(glType);
activeType[i] = type;
}
//Bind the texture
tex->bind();
//Wrapping settings
bool changed = false;
if(state.wrapHorizontal != tex->prevRenderState.wrapHorizontal) {
GLint mode;
switch(state.wrapHorizontal) {
case TW_Repeat:
mode = GL_REPEAT; break;
case TW_Clamp:
mode = GL_CLAMP; break;
case TW_ClampEdge:
mode = GL_CLAMP_TO_EDGE; break;
case TW_Mirror:
mode = GL_MIRRORED_REPEAT; break;
NO_DEFAULT
}
glTexParameteri(glType, GL_TEXTURE_WRAP_S, mode);
changed = true;
}
if(state.wrapVertical != tex->prevRenderState.wrapVertical) {
GLint mode;
switch(state.wrapVertical) {
case TW_Repeat:
mode = GL_REPEAT; break;
case TW_Clamp:
mode = GL_CLAMP; break;
case TW_ClampEdge:
mode = GL_CLAMP_TO_EDGE; break;
case TW_Mirror:
mode = GL_MIRRORED_REPEAT; break;
NO_DEFAULT
}
glTexParameteri(glType, GL_TEXTURE_WRAP_T, mode);
changed = true;
}
//Mipmap settings
if(state.filterMin != tex->prevRenderState.filterMin) {
GLint mode;
switch(state.filterMin) {
case TF_Nearest:
mode = tex->hasMipMaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST; break;
case TF_Linear:
mode = tex->hasMipMaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; break;
NO_DEFAULT
}
glTexParameteri(glType, GL_TEXTURE_MIN_FILTER, mode);
changed = true;
}
if(state.filterMag != tex->prevRenderState.filterMag) {
GLint mode;
switch(state.filterMag) {
case TF_Nearest:
mode = tex->hasMipMaps ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST; break;
case TF_Linear:
mode = tex->hasMipMaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; break;
NO_DEFAULT
}
glTexParameteri(glType, GL_TEXTURE_MAG_FILTER, mode);
changed = true;
}
//Update the previous render state
if(changed)
tex->prevRenderState = state;
}
}
}
if(state.normalizeNormals != activeRenderState.normalizeNormals) {
if(state.normalizeNormals)
glEnable(GL_RESCALE_NORMAL);
else
glDisable(GL_RESCALE_NORMAL);
}
if(state.drawMode != activeRenderState.drawMode) {
switch(state.drawMode) {
case DM_Line:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
break;
case DM_Fill:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
}
}
if(state.baseMat != activeRenderState.baseMat) {
setRenderFuncs(state.baseMat, intermediateBlend);
}
if(!state.shader && (state.lighting != activeRenderState.lighting || activeRenderState.shader)) {
if(state.lighting)
glEnable(GL_LIGHTING);
else
glDisable(GL_LIGHTING);
}
if(state.lighting) {
//Lighting model material settings
if(state.diffuse != diffuse) {
diffuse = state.diffuse;
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, (GLfloat*)&state.diffuse);
}
if(state.specular != specular) {
specular = state.specular;
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat*)&state.specular);
}
if(state.shininess != shininess) {
shininess = state.shininess;
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, state.shininess);
}
}
//Shader and shader variables
if(state.shader == 0 && activeRenderState.shader != 0)
glUseProgram(0);
activeRenderState = state;
pLastRenderState = &state;
//The shader bind can inspect the current material
if(state.shader)
state.shader->bind(shaderUniforms);
}
void getBillboardVecs(vec3d& upLeft, vec3d& upRight, double rotation) const {
if(rotation == 0) {
upLeft = bb_up_m_right;
upRight = bb_up_p_right;
}
else {
double st = sin(rotation), ct = cos(rotation);
upLeft = (bb_up_m_right * ct) - (bb_up_p_right * st);
upRight = (bb_up_p_right * ct) + (bb_up_m_right * st);
}
}
void getBillboardVecs(const vec3d& from, vec3d& upLeft, vec3d& upRight, double rotation) const {
vec3d toward = (from - cam_pos).normalize();
vec3d right = cam_up.cross(toward).normalize();
vec3d up = toward.cross(right);
vec3d ul = up - right;
vec3d ur = up + right;
if(rotation == 0) {
upLeft = ul;
upRight = ur;
}
else {
double st = sin(rotation), ct = cos(rotation);
upLeft = (ul * ct) - (ur * st);
upRight = (ur * ct) + (ul * st);
}
}
void drawBillboard(vec3d center, double width) {
center -= cam_pos;
width *= 0.5;
vec3d UpMinRight, UpPlusRight;
getBillboardVecs(UpMinRight, UpPlusRight, 0);
UpMinRight *= width;
UpPlusRight *= width;
auto& mat = *pLastRenderState;
auto* buffer = VertexBufferTCV::fetch(&mat);
auto* verts = buffer->request(1, PT_Quads);
Color col = Color();
verts[0].set(vec3f(center + UpMinRight), vec2f(0,0), col);
verts[1].set(vec3f(center + UpPlusRight), vec2f(1,0), col);
verts[2].set(vec3f(center - UpMinRight), vec2f(1,1), col);
verts[3].set(vec3f(center - UpPlusRight), vec2f(0,1), col);
}
void drawBillboard(vec3d center, double width, const RenderState& mat, double rotation, Color* color) {
center -= cam_pos;
width *= 0.5;
vec3d UpMinRight, UpPlusRight;
getBillboardVecs(UpMinRight, UpPlusRight, rotation);
UpMinRight *= width;
UpPlusRight *= width;
auto* buffer = VertexBufferTCV::fetch(&mat);
auto* verts = buffer->request(1, PT_Quads);
Color col = color ? *color : Color();
verts[0].set(vec3f(center + UpMinRight), vec2f(0,0), col);
verts[1].set(vec3f(center + UpPlusRight), vec2f(1,0), col);
verts[2].set(vec3f(center - UpMinRight), vec2f(1,1), col);
verts[3].set(vec3f(center - UpPlusRight), vec2f(0,1), col);
}
void drawBillboard( vec3d center, double width, const RenderState& mat, const recti& source, Color* color) {
rectf texCoords;
Texture* tex = mat.textures[0];
if(tex) {
texCoords.topLeft.x = (float)source.topLeft.x / tex->size.width;
texCoords.topLeft.y = (float)source.topLeft.y / tex->size.height;
texCoords.botRight.x = (float)source.botRight.x / tex->size.width;
texCoords.botRight.y = (float)source.botRight.y / tex->size.height;
}
center -= cam_pos;
width *= 0.5;
auto UpMinRight = bb_up_m_right * width, UpPlusRight = bb_up_p_right * width;
Color col = color ? *color : Color();
auto* buffer = VertexBufferTCV::fetch(&mat);
auto* verts = buffer->request(1, PT_Quads);
verts[0].set(vec3f(center + UpMinRight), texCoords.topLeft, col);
verts[1].set(vec3f(center + UpPlusRight), texCoords.getTopRight(), col);
verts[2].set(vec3f(center - UpMinRight), texCoords.botRight, col);
verts[3].set(vec3f(center - UpPlusRight), texCoords.getBotLeft(), col);
}
void drawBillboard( vec3d center, double width, const RenderState& mat, const recti& source, double rotation, Color color) override {
rectf texCoords;
Texture* tex = mat.textures[0];
if(tex) {
texCoords.topLeft.x = (float)source.topLeft.x / tex->size.width;
texCoords.topLeft.y = (float)source.topLeft.y / tex->size.height;
texCoords.botRight.x = (float)source.botRight.x / tex->size.width;
texCoords.botRight.y = (float)source.botRight.y / tex->size.height;
}
center -= cam_pos;
width *= 0.5;
vec3d UpMinRight, UpPlusRight;
getBillboardVecs(UpMinRight, UpPlusRight, rotation);
UpMinRight *= width;
UpPlusRight *= width;
auto* buffer = VertexBufferTCV::fetch(&mat);
auto* verts = buffer->request(1, PT_Quads);
verts[0].set(vec3f(center + UpMinRight), texCoords.topLeft, color);
verts[1].set(vec3f(center + UpPlusRight), texCoords.getTopRight(), color);
verts[2].set(vec3f(center - UpMinRight), texCoords.botRight, color);
verts[3].set(vec3f(center - UpPlusRight), texCoords.getBotLeft(), color);
if(!mat.constant)
buffer->draw();
}
void drawLine(line3dd line, Color start, Color end) {
auto* buffer = VertexBufferTCV::fetch(pLastRenderState);
auto* verts = buffer->request(1, PT_Lines);
verts[0].set(vec3f(line.start - cam_pos), vec2f(), start);
verts[1].set(vec3f(line.end - cam_pos), vec2f(1,0), start);
if(!pLastRenderState->constant)
buffer->draw();
}
virtual void drawQuad(
const RenderState* mat,
const vec2<float>* vertices,
const vec2<float>* textureCoords,
const Color* color = 0)
{
auto* buffer = VertexBufferTCV::fetch(mat);
auto* verts = buffer->request(1, PT_Quads);
Color col = color ? *color : Color();
for(unsigned i = 0; i < 4; ++i) {
auto& v = verts[i];
v.uv = textureCoords[i];
v.col = col;
v.pos = vec3f(vertices[i].x, vertices[i].y, 0);
}
if(!mat->constant)
buffer->draw();
}
void drawQuad(const vec3d* vertices,
const vec2<float>* textureCoords,
const Color* colors)
{
auto* buffer = VertexBufferTCV::fetch(pLastRenderState);
auto* verts = buffer->request(1, PT_Quads);
for(unsigned i = 0; i < 4; ++i) {
auto& v = verts[i];
v.uv = textureCoords[i];
v.col = colors[i];
v.pos = vec3f(vertices[i] - cam_pos);
}
if(!pLastRenderState->constant)
buffer->draw();
}
void drawQuad(const vec2<float>* vertices,
const vec2<float>* textureCoords,
const Color* colors)
{
auto* buffer = VertexBufferTCV::fetch(pLastRenderState);
auto* verts = buffer->request(1, PT_Quads);
for(unsigned i = 0; i < 4; ++i) {
auto& v = verts[i];
v.uv = textureCoords[i];
v.col = colors ? colors[i] : Color();
v.pos = vec3f(vertices[i].x, vertices[i].y, 0);
}
if(!pLastRenderState->constant)
buffer->draw();
}
void drawRectangle(const recti& rect, const Color& color) {
auto* buffer = VertexBufferTCV::fetch(&state_2d);
auto* verts = buffer->request(1, PT_Quads);
auto& tl = verts[0];
tl.uv.set(0,0);
tl.col = color;
tl.pos = vec3f(vec3i(rect.topLeft.x, rect.topLeft.y, 0));
auto& tr = verts[1];
tr.uv.set(0,0);
tr.col = color;
tr.pos = vec3f(vec3i(rect.botRight.x, rect.topLeft.y, 0));
auto& br = verts[2];
br.uv.set(0,0);
br.col = color;
br.pos = vec3f(vec3i(rect.botRight.x, rect.botRight.y, 0));
auto& bl = verts[3];
bl.uv.set(0,0);
bl.col = color;
bl.pos = vec3f(vec3i(rect.topLeft.x, rect.botRight.y, 0));
}
void drawRectangle(const recti& rectangle, const RenderState* mat, Color color, const recti* clip = 0) {
if(clip && !clip->overlaps(rectangle))
return;
if(!mat)
mat = &state_2d;
recti rect = rectangle;
rectf uv(0,0,1,1);
if(clip && !clip->isRectInside(rect)) {
rect = clip->clipAgainst(rect);
uv.topLeft.x = (float)(rect.topLeft.x - rectangle.topLeft.x) / (float)rectangle.getWidth();
uv.topLeft.y = (float)(rect.topLeft.y - rectangle.topLeft.y) / (float)rectangle.getHeight();
uv.botRight.x = 1.f - (float)(rectangle.botRight.x - rect.botRight.x) / (float)rectangle.getWidth();
uv.botRight.y = 1.f - (float)(rectangle.botRight.y - rect.botRight.y) / (float)rectangle.getHeight();
}
auto* buffer = VertexBufferTCV::fetch(mat);
auto* verts = buffer->request(1, PT_Quads);
auto& tl = verts[0];
tl.uv = uv.topLeft;
tl.col = color;
tl.pos = vec3f(vec3i(rect.topLeft.x, rect.topLeft.y, 0));
auto& tr = verts[1];
tr.uv.set(uv.botRight.x,uv.topLeft.y);
tr.col = color;
tr.pos = vec3f(vec3i(rect.botRight.x, rect.topLeft.y, 0));
auto& br = verts[2];
br.uv = uv.botRight;
br.col = color;
br.pos = vec3f(vec3i(rect.botRight.x, rect.botRight.y, 0));
auto& bl = verts[3];
bl.uv.set(uv.topLeft.x,uv.botRight.y);
bl.col = color;
bl.pos = vec3f(vec3i(rect.topLeft.x, rect.botRight.y, 0));
if(!mat->constant)
buffer->draw();
}
void drawRectangle(recti rect, const RenderState* mat, const recti* src,
const Color* color, const recti* clip) {
//Store source rect
recti source;
if(src)
source = *src;
//Clipping
if(clip) {
if(!clip->overlaps(rect))
return;
if(!clip->isRectInside(rect)) {
recti clipped = clip->clipAgainst(rect);
if(src) {
source = source.clipProportional(rect, clipped);
}
else if(mat && mat->textures[0]) {
source = recti(vec2i(0, 0), mat->textures[0]->size);
source = source.clipProportional(rect, clipped);
src = &source;
}
rect = clipped;
}
}
if(!mat)
mat = &state_2d;
rectf uv(0,0,1.f,1.f);
if(mat && src && mat->textures[0]) {
Texture* tex = mat->textures[0];
uv.topLeft.x = (float)source.topLeft.x / tex->size.width;
uv.topLeft.y = (float)source.topLeft.y / tex->size.height;
uv.botRight.x = (float)source.botRight.x / tex->size.width;
uv.botRight.y = (float)source.botRight.y / tex->size.height;
}
auto* buffer = VertexBufferTCV::fetch(mat);
auto* verts = buffer->request(1, PT_Quads);
auto& tl = verts[0];
tl.uv = uv.topLeft;
tl.col = color ? color[0] : Color();
tl.pos = vec3f(vec3i(rect.topLeft.x, rect.topLeft.y, 0));
auto& tr = verts[1];
tr.uv.set(uv.botRight.x,uv.topLeft.y);
tr.col = color ? color[1] : Color();
tr.pos = vec3f(vec3i(rect.botRight.x, rect.topLeft.y, 0));
auto& br = verts[2];
br.uv = uv.botRight;
br.col = color ? color[2] : Color();
br.pos = vec3f(vec3i(rect.botRight.x, rect.botRight.y, 0));
auto& bl = verts[3];
bl.uv.set(uv.topLeft.x,uv.botRight.y);
bl.col = color ? color[3] : Color();
bl.pos = vec3f(vec3i(rect.topLeft.x, rect.botRight.y, 0));
if(!mat->constant)
buffer->draw();
}
void drawRectangle(recti rect, const RenderState* mat, const recti* src,
const Color* color, const recti* clip, double rotation) {
if(rotation == 0.0) {
drawRectangle(rect, mat, src, color, clip);
return;
}
//Store source rect
recti source;
if(src)
source = *src;
//Clipping
if(clip) {
if(!clip->overlaps(rect))
return;
if(!clip->isRectInside(rect)) {
recti clipped = clip->clipAgainst(rect);
if(src) {
source = source.clipProportional(rect, clipped);
}
else if(mat && mat->textures[0]) {
source = recti(vec2i(0, 0), mat->textures[0]->size);
source = source.clipProportional(rect, clipped);
src = &source;
}
rect = clipped;
}
}
//Render the correct material
if(!mat)
mat = &state_2d;
//Compute texture coordinates
rectf uv(0,0,1.f,1.f);
if(mat && src && mat->textures[0]) {
Texture* tex = mat->textures[0];
uv.topLeft.x = (float)source.topLeft.x / tex->size.width;
uv.topLeft.y = (float)source.topLeft.y / tex->size.height;
uv.botRight.x = (float)source.botRight.x / tex->size.width;
uv.botRight.y = (float)source.botRight.y / tex->size.height;
}
//Handle rotations
rectf frect = rectf(rect);
vec2f center = vec2f(frect.getCenter());
rectf preRot = rectf(frect.topLeft - center, frect.botRight - center);
vec2f pos;
auto* buffer = VertexBufferTCV::fetch(mat);
auto* verts = buffer->request(1, PT_Quads);
auto& tl = verts[0];
tl.uv = uv.topLeft;
tl.col = color ? color[0] : Color();
pos = preRot.topLeft.rotated(rotation) + center;
tl.pos = vec3f(pos.x, pos.y, 0);
auto& tr = verts[1];
tr.uv = uv.getTopRight();
tr.col = color ? color[1] : Color();
pos = preRot.getTopRight().rotated(rotation) + center;
tr.pos = vec3f(pos.x, pos.y, 0);
auto& br = verts[2];
br.uv = uv.botRight;
br.col = color ? color[3] : Color();
pos = preRot.botRight.rotated(rotation) + center;
br.pos = vec3f(pos.x, pos.y, 0);
auto& bl = verts[3];
bl.uv = uv.getBotLeft();
bl.col = color ? color[2] : Color();
pos = preRot.getBotLeft().rotated(rotation) + center;
bl.pos = vec3f(pos.x, pos.y, 0);
if(!mat->constant)
buffer->draw();
}
RenderMesh* createMesh(const Mesh& mesh) {
return createGLMesh(mesh);
}
Shader* createShader() {
return createGLShader();
}
ShaderProgram* createShaderProgram(const char* vertex_shader, const char* fragment_shader) {
return createGLShaderProgram(vertex_shader, fragment_shader);
}
Texture* createTexture(Image& image, bool mipmap = true, bool cachePixels = false) {
return new GLTexture(image, mipmap, cachePixels);
}
Texture* createRenderTarget(const vec2i& size) {
return new glFrameBuffer(size);
}
void clear(unsigned flags) {
//If we need to clear the depth buffer, we need to enable the depth buffer for writing
if(flags & GL_DEPTH_BUFFER_BIT) {
if(!activeRenderState.depthWrite) {
glDepthMask(GL_TRUE);
activeRenderState.depthWrite = true;
pLastRenderState = 0;
}
}
glClear(flags);
#ifdef _DEBUG
reportErrors("Clearing");
#endif
}
void setRenderTarget(Texture* texture, bool intermediate = false) {
renderVertexBuffers();
glFrameBuffer* frame = dynamic_cast<glFrameBuffer*>(texture);
if(!frame) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0,0,screenSize.x,screenSize.y);
frameSize = screenSize;
}
else {
frame->setAsTarget();
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
frameSize = frame->size;
}
if(intermediate != intermediateBlend)
setRenderFuncs(activeRenderState.baseMat, intermediate);
intermediateBlend = intermediate;
}
Image* getScreen(int x, int y, int w, int h) {
Image* img = new Image(w, h, FMT_RGB);
glReadBuffer(GL_BACK);
glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, img->rgb);
return img;
}
bool init() {
//Check for sufficient opengl version
glewInit();
if(!GLEW_VERSION_2_1) {
fprintf(stderr, "Error: Requires OpenGL 2.1\n");
return false;
}
//Initialize state
setDefaultRenderState();
//Lighting
float lightCol[4] = {2.5f, 2.5f, 2.5f, 1.f};
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightCol);
float lightSpec[4] = {1, 1, 1, 1.f};
glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpec);
glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1.f/(500.f*500.f));
glEnable(GL_LIGHT0);
float deadCol[4] = {0,0,0,1};
glLightfv(GL_LIGHT1, GL_DIFFUSE, deadCol);
glLightfv(GL_LIGHT1, GL_SPECULAR, deadCol);
glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 1.f/(500.f*500.f));
glEnable(GL_LIGHT1);
//System defaults
float globalAmbient[4] = {0.175f, 0.175f, 0.175f, 0.f};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, globalAmbient);
if(GLEW_ARB_seamless_cube_map)
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
if(GLEW_EXT_direct_state_access)
glDirectStateAccess = true;
//Support strange sizes of image
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
//Report vendor
const char* vendorString = (const char*)glGetString(GL_VENDOR);
const char* rendererString = (const char*)glGetString(GL_RENDERER);
const char* versionString = (const char*)glGetString(GL_VERSION);
isIntelCard = vendorString != nullptr && std::string(vendorString) == "Intel";
print("OpenGL vendor '%s', renderer '%s'", vendorString, rendererString);
print(" version '%s'", versionString);
if(isIntelCard)
print("-- Using Intel mode. (%d)", GLEW_ARB_texture_storage);
return true;
}
void setScreenSize(int w, int h) {
screenSize.x = w;
screenSize.y = h;
frameSize = screenSize;
}
void setFOV(double FOV) {
fov = FOV;
}
void setNearFarPlanes(double near, double far) {
zNear = near;
zFar = far;
}
void clearRenderPrepared() {
isPrepared = false;
}
bool isRenderPrepared() {
return isPrepared;
}
void prepareRender3D(Camera& camera, const recti* clip) {
//Cache camera calculations
setCameraData(camera);
cleared = true;
isPrepared = true;
if(clip)
pushScreenClip(*clip);
//Draw 3D
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
double aspect = ((double)screenSize.x)/((double)screenSize.y);
gluPerspective(fov, aspect, zNear, zFar);
camera.setRenderConstraints(zNear, zFar, fov, aspect, (double)screenSize.x, (double)screenSize.y);
glMatrixMode(GL_MODELVIEW);
{
vec3d pos, at, lookDir, up;
camera.toLookAt(pos, at, up);
lookDir = (at - pos).normalized();
glLoadIdentity();
gluLookAt(0,0,0, lookDir.x, lookDir.y, lookDir.z, up.x, up.y, up.z);
{ //Setup lights in the scene
vec3f camPosf(float(pos.x),float(pos.y),float(pos.z));
vec3f lightOffset = camPosf * -1.f;
glDisable(GL_LIGHT0);
light::LightSource* sources[2];
unsigned lightCount = light::findNearestLights(camPosf,sources,2);
unsigned lightIndex = 0;
lightActive[0] = false;
lightActive[1] = false;
while(lightIndex < lightCount) {
lightPosition[lightIndex] = sources[lightIndex]->getPosition();
lightRadius[lightIndex] = sources[lightIndex]->getRadius();
vec2i onScreen = camera.screenPos(vec3d(lightPosition[lightIndex]));
screenLight[lightIndex] = vec2f((float)onScreen.x / (float)screenSize.x, 1.f - (float)onScreen.y / (float)screenSize.y);
lightActive[lightIndex] = true;
sources[lightIndex]->enable(lightIndex, lightOffset);
}
}
//Render Skybox
if(skybox) {
clear(GL_DEPTH_BUFFER_BIT);
switchToRenderState(*skybox);
if(skyboxMesh == 0) {
Mesh* mesh = generateSphereMesh(64,32);
skyboxMesh = createGLMesh( *mesh );
delete mesh;
}
glPushMatrix();
glScaled((zNear + zFar) * 0.5, (zNear + zFar) * 0.5, (zNear + zFar) * 0.5);
skyboxMesh->render();
glPopMatrix();
}
else {
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
}
}
void renderWorld() {
rootNode._render(*this);
scene::renderingNode = nullptr;
renderVertexBuffers();
popScreenClip();
}
void prepareRender2D() {
isPrepared = true;
if(!cleared)
clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
else
clear(GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,screenSize.x,screenSize.y,0,-4096.0,4096.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
light::resetLights();
switchToRenderState(state_2d);
if(!intermediateBlend)
cleared = false;
}
void drawFPSGraph(const recti& location) {
double max_time = 0.03333333;
foreach(frame, frames)
if(*frame > max_time)
max_time = *frame;
float x = (float)location.topLeft.width, yOff = (float)location.topLeft.y, height = (float)location.getHeight();
static render::RenderState rs;
rs.lighting = false;
rs.culling = render::FC_None;
rs.depthTest = render::DT_NoDepthTest;
switchToRenderState(rs);
auto* buffer = VertexBufferTCV::fetch(&rs);
auto* v = buffer->request(1, PT_Lines);
v[0].col = v[1].col = Color(0,0,196);
float yFrameLine = floor( yOff + height * (float)(1.0 - (0.0166667 / max_time)) );
v[0].pos = vec3f(x,yFrameLine,0);
v[1].pos = vec3f((float)location.botRight.x,yFrameLine,0);
glColor3ub(0,255,0);
if(frames.size() > 1) {
v = buffer->request((unsigned)frames.size() - 1, PT_LineStrip);
for(auto frame = frames.begin(), end = frames.end(); frame != end; ++frame, ++v) {
v->col = Colorf((float)(*frame/0.033333), 1.f - (float)(*frame/0.0333333), 0);
v->pos = vec3f(x, yOff + height * (1.f - (float)(*frame/max_time)), 0 );
x += (float)location.getWidth() / (float)max_frames;
}
}
glColor3ub(255,255,255);
}
void setSkybox(const RenderState* mat) {
skybox = mat;
}
void setSkyboxMesh(const RenderMesh* mesh) {
skyboxMesh = mesh;
}
void setScissor(const recti& clip) const {
double x = (double)clip.topLeft.x / (double)screenSize.width;
double y = (double)(screenSize.height - clip.botRight.y) / (double)screenSize.height;
double w = clip.getWidth() / (double)screenSize.width;
double h = clip.getHeight() / (double)screenSize.height;
glScissor((int)(x * frameSize.x), (int)(y * frameSize.y), (int)(w * frameSize.x), (int)(h * frameSize.y));
}
void pushScreenClip(const recti& box) override {
if(viewportClips.empty())
glEnable(GL_SCISSOR_TEST);
viewportClips.push(box);
setScissor(box);
}
void popScreenClip() override {
if(viewportClips.empty())
return;
viewportClips.pop();
if(viewportClips.empty())
glDisable(GL_SCISSOR_TEST);
else
setScissor(viewportClips.top());
}
};
RenderDriver* createGLDriver() {
return new GLDriver();
}
};
void shader_tex_size(float* out,unsigned short n,void* args) {
vec2f* sizes = (vec2f*)out;
auto* texs = ((render::GLDriver*)devices.render)->activeRenderState.textures;
unsigned* texIndices = (unsigned*)args;
for(unsigned short i = 0; i < n; ++i) {
unsigned index = texIndices[i];
if(index >= RENDER_MAX_TEXTURES)
new(sizes+i) vec2f(0);
else if(render::Texture* tex = texs[index])
new(sizes+i) vec2f(tex->size);
else
new(sizes+i) vec2f(0);
}
}
void shader_light_radius(float* out,unsigned short n,void* args) {
auto* radii = ((render::GLDriver*)devices.render)->lightRadius;
unsigned* indices = (unsigned*)args;
for(unsigned short i = 0; i < n; ++i) {
unsigned index = indices[i];
if(index < 2)
out[i] = radii[index];
else
out[i] = 0.f;
}
}
void shader_light_position(float* out,unsigned short n,void* args) {
auto* positions = ((render::GLDriver*)devices.render)->lightPosition;
unsigned* indices = (unsigned*)args;
for(unsigned short i = 0; i < n; ++i) {
unsigned index = indices[i];
if(index < 2) {
out[i*3+0] = positions[index].x;
out[i*3+1] = positions[index].y;
out[i*3+2] = positions[index].z;
}
else {
out[i*3+0] = 0.f;
out[i*3+1] = 0.f;
out[i*3+2] = 0.f;
}
}
}
void shader_light_screen(float* out,unsigned short n,void* args) {
auto* positions = ((render::GLDriver*)devices.render)->screenLight;
unsigned* indices = (unsigned*)args;
for(unsigned short i = 0; i < n; ++i) {
unsigned index = indices[i];
if(index < 2) {
out[i*2+0] = positions[index].x;
out[i*2+1] = positions[index].y;
}
else {
out[i*2+0] = 0.f;
out[i*2+1] = 0.f;
}
}
}
void shader_light_active(float* out,unsigned short n,void* args) {
auto* active = ((render::GLDriver*)devices.render)->lightActive;
unsigned* indices = (unsigned*)args;
for(unsigned short i = 0; i < n; ++i) {
unsigned index = indices[i];
if(index < 2)
out[i] = active[index] ? 1.f : 0.f;
else
out[i] = 0.f;
}
}
void setShaderLightRadius(unsigned index, double radius) {
auto* active = ((render::GLDriver*)devices.render)->lightRadius;
if(index < 2)
active[index] = radius;
}