scummvm/engines/grim/gfx_opengl_shaders.cpp
Le Philousophe 462536c287 GRIM: Fix text not displaying
The setUniform function was not called on the active shader which got
deactivated.
2022-07-21 21:16:03 +02:00

2265 lines
72 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Matrix calculations taken from the glm library
// Which is covered by the MIT license
// And has this additional copyright note:
/* Copyright (c) 2005 - 2012 G-Truc Creation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#include "common/endian.h"
#include "common/file.h"
#include "common/str.h"
#include "common/system.h"
#include "common/textconsole.h"
#if defined(USE_OPENGL_SHADERS)
#include "graphics/surface.h"
#include "graphics/opengl/context.h"
#include "engines/grim/actor.h"
#include "engines/grim/bitmap.h"
#include "engines/grim/colormap.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/font.h"
#include "engines/grim/gfx_opengl_shaders.h"
#include "engines/grim/grim.h"
#include "engines/grim/material.h"
#include "engines/grim/model.h"
#include "engines/grim/primitives.h"
#include "engines/grim/set.h"
#include "engines/grim/sprite.h"
namespace Grim {
template<class T>
static T nextHigher2(T k) {
if (k == 0)
return 1;
--k;
for (uint i = 1; i < sizeof(T) * 8; i <<= 1)
k = k | k >> i;
return k + 1;
}
static float textured_quad[] = {
// X , Y , S , T
0.0f, 0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
};
static float textured_quad_centered[] = {
// X , Y , Z , S , T
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
-0.5f, +0.5f, 0.0f, 0.0f, 0.0f,
+0.5f, +0.5f, 0.0f, 1.0f, 0.0f,
+0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
};
static float zero_texVerts[] = { 0.0, 0.0 };
struct GrimVertex {
GrimVertex(const float *verts, const float *texVerts, const float *normals) {
memcpy(_position, verts, 3 * sizeof(float));
memcpy(_texcoord, texVerts, 2 * sizeof(float));
memcpy(_normal, normals, 3 * sizeof(float));
}
float _position[3];
float _texcoord[2];
float _normal[3];
};
struct TextUserData {
OpenGL::Shader * shader;
uint32 characters;
Color color;
GLuint texture;
};
struct FontUserData {
int size;
GLuint texture;
};
struct EMIModelUserData {
OpenGL::Shader *_shader;
OpenGL::Shader *_shaderLights;
uint32 _texCoordsVBO;
uint32 _colorMapVBO;
uint32 _verticesVBO;
uint32 _normalsVBO;
};
struct ModelUserData {
OpenGL::Shader *_shader;
OpenGL::Shader *_shaderLights;
uint32 _meshInfoVBO;
};
struct ShadowUserData {
uint32 _verticesVBO;
uint32 _indicesVBO;
uint32 _numTriangles;
};
Math::Matrix4 makeLookMatrix(const Math::Vector3d& pos, const Math::Vector3d& interest, const Math::Vector3d& up) {
Math::Vector3d f = (interest - pos).getNormalized();
Math::Vector3d u = up.getNormalized();
Math::Vector3d s = Math::Vector3d::crossProduct(f, u).getNormalized();
u = Math::Vector3d::crossProduct(s, f);
Math::Matrix4 look;
look(0, 0) = s.x();
look(1, 0) = s.y();
look(2, 0) = s.z();
look(0, 1) = u.x();
look(1, 1) = u.y();
look(2, 1) = u.z();
look(0, 2) = -f.x();
look(1, 2) = -f.y();
look(2, 2) = -f.z();
look(3, 0) = -Math::Vector3d::dotProduct(s, pos);
look(3, 1) = -Math::Vector3d::dotProduct(u, pos);
look(3, 2) = Math::Vector3d::dotProduct(f, pos);
look.transpose();
return look;
}
Math::Matrix4 makeRotationMatrix(const Math::Angle& angle, Math::Vector3d axis) {
float c = angle.getCosine();
float s = angle.getSine();
axis.normalize();
Math::Vector3d temp = (1.f - c) * axis;
Math::Matrix4 rotate;
rotate(0, 0) = c + temp.x() * axis.x();
rotate(0, 1) = 0 + temp.x() * axis.y() + s * axis.z();
rotate(0, 2) = 0 + temp.x() * axis.z() - s * axis.y();
rotate(0, 3) = 0;
rotate(1, 0) = 0 + temp.y() * axis.x() - s * axis.z();
rotate(1, 1) = c + temp.y() * axis.y();
rotate(1, 2) = 0 + temp.y() * axis.z() + s * axis.x();
rotate(1, 3) = 0;
rotate(2, 0) = 0 + temp.z() * axis.x() + s * axis.y();
rotate(2, 1) = 0 + temp.z() * axis.y() - s * axis.x();
rotate(2, 2) = c + temp.z() * axis.z();
rotate(2, 3) = 0;
rotate(3, 0) = 0;
rotate(3, 1) = 0;
rotate(3, 2) = 0;
rotate(3, 3) = 1;
return rotate;
}
Math::Matrix4 makeFrustumMatrix(double left, double right, double bottom, double top, double nclip, double fclip) {
Math::Matrix4 proj;
proj(0, 0) = (2.0f * nclip) / (right - left);
proj(1, 1) = (2.0f * nclip) / (top - bottom);
proj(2, 0) = (right + left) / (right - left);
proj(2, 1) = (top + bottom) / (top - bottom);
proj(2, 2) = -(fclip + nclip) / (fclip - nclip);
proj(2, 3) = -1.0f;
proj(3, 2) = -(2.0f * fclip * nclip) / (fclip - nclip);
proj(3, 3) = 0.0f;
return proj;
}
GfxBase *CreateGfxOpenGLShader() {
return new GfxOpenGLS();
}
GfxOpenGLS::GfxOpenGLS() {
_smushTexId = 0;
_matrixStack.push(Math::Matrix4());
_fov = -1.0;
_nclip = -1;
_fclip = -1;
_selectedTexture = nullptr;
_emergTexture = 0;
_maxLights = 8;
_lights = new GLSLight[_maxLights];
_lightsEnabled = false;
_hasAmbientLight = false;
_backgroundProgram = nullptr;
_smushProgram = nullptr;
_textProgram = nullptr;
_emergProgram = nullptr;
_actorProgram = nullptr;
_actorLightsProgram = nullptr;
_spriteProgram = nullptr;
_primitiveProgram = nullptr;
_irisProgram = nullptr;
_shadowPlaneProgram = nullptr;
_dimProgram = nullptr;
_dimPlaneProgram = nullptr;
_dimRegionProgram = nullptr;
float div = 6.0f;
_overworldProjMatrix = makeFrustumMatrix(-1.f / div, 1.f / div, -0.75f / div, 0.75f / div, 1.0f / div, 3276.8f);
}
GfxOpenGLS::~GfxOpenGLS() {
releaseMovieFrame();
for (unsigned int i = 0; i < _numSpecialtyTextures; i++) {
destroyTexture(&_specialtyTextures[i]);
}
delete[] _lights;
delete _backgroundProgram;
delete _smushProgram;
delete _textProgram;
delete _emergProgram;
delete _actorProgram;
delete _actorLightsProgram;
delete _spriteProgram;
delete _primitiveProgram;
delete _irisProgram;
delete _shadowPlaneProgram;
delete _dimProgram;
delete _dimPlaneProgram;
delete _dimRegionProgram;
glDeleteTextures(1, &_storedDisplay);
glDeleteTextures(1, &_emergTexture);
}
void GfxOpenGLS::setupZBuffer() {
GLint format = GL_LUMINANCE_ALPHA;
GLenum type = GL_UNSIGNED_BYTE;
float width = _gameWidth;
float height = _gameHeight;
glGenTextures(1, (GLuint *)&_zBufTex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, _zBufTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, format, nextHigher2((int)width), nextHigher2((int)height), 0, format, type, nullptr);
glActiveTexture(GL_TEXTURE0);
_zBufTexCrop = Math::Vector2d(width / nextHigher2((int)width), height / nextHigher2((int)height));
}
void GfxOpenGLS::setupQuadEBO() {
// FIXME: Probably way too big...
unsigned short quad_indices[6 * 1000];
unsigned short start = 0;
for (unsigned short *p = quad_indices; p < &quad_indices[6 * 1000]; p += 6) {
p[0] = p[3] = start++;
p[1] = start++;
p[2] = p[4] = start++;
p[5] = start++;
}
_quadEBO = OpenGL::Shader::createBuffer(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW);
}
void GfxOpenGLS::setupTexturedQuad() {
_smushVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(textured_quad), textured_quad, GL_STATIC_DRAW);
_smushProgram->enableVertexAttribute("position", _smushVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
_smushProgram->enableVertexAttribute("texcoord", _smushVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 2 * sizeof(float));
_emergProgram->enableVertexAttribute("position", _smushVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
_emergProgram->enableVertexAttribute("texcoord", _smushVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 2 * sizeof(float));
if (g_grim->getGameType() == GType_GRIM) {
_backgroundProgram->enableVertexAttribute("position", _smushVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
_backgroundProgram->enableVertexAttribute("texcoord", _smushVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 2 * sizeof(float));
} else {
_dimPlaneProgram->enableVertexAttribute("position", _smushVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
}
}
void GfxOpenGLS::setupTexturedCenteredQuad() {
_spriteVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, sizeof(textured_quad_centered), textured_quad_centered, GL_STATIC_DRAW);
_spriteProgram->enableVertexAttribute("position", _spriteVBO, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
_spriteProgram->enableVertexAttribute("texcoord", _spriteVBO, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 3 * sizeof(float));
_spriteProgram->disableVertexAttribute("color", Math::Vector4d(1.0f, 1.0f, 1.0f, 1.0f));
}
void GfxOpenGLS::setupPrimitives() {
uint32 numVBOs = ARRAYSIZE(_primitiveVBOs);
glGenBuffers(numVBOs, _primitiveVBOs);
_currentPrimitive = 0;
for (uint32 i = 0; i < numVBOs; ++i) {
glBindBuffer(GL_ARRAY_BUFFER, _primitiveVBOs[i]);
glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
}
if (g_grim->getGameType() == GType_MONKEY4)
return;
glGenBuffers(1, &_irisVBO);
glBindBuffer(GL_ARRAY_BUFFER, _irisVBO);
glBufferData(GL_ARRAY_BUFFER, 20 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
_irisProgram->enableVertexAttribute("position", _irisVBO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
glGenBuffers(1, &_dimVBO);
glBindBuffer(GL_ARRAY_BUFFER, _dimVBO);
float points[12] = {
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
};
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), points, GL_DYNAMIC_DRAW);
_dimProgram->enableVertexAttribute("position", _dimVBO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
_dimProgram->enableVertexAttribute("texcoord", _dimVBO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
glGenBuffers(1, &_dimRegionVBO);
glBindBuffer(GL_ARRAY_BUFFER, _dimRegionVBO);
glBufferData(GL_ARRAY_BUFFER, 24 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
_dimRegionProgram->enableVertexAttribute("position", _dimRegionVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
_dimRegionProgram->enableVertexAttribute("texcoord", _dimRegionVBO, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 2 * sizeof(float));
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
GLuint GfxOpenGLS::nextPrimitive() {
GLuint ret = _primitiveVBOs[_currentPrimitive];
_currentPrimitive = (_currentPrimitive + 1) % ARRAYSIZE(_primitiveVBOs);
return ret;
}
void GfxOpenGLS::setupShaders() {
bool isEMI = g_grim->getGameType() == GType_MONKEY4;
static const char* commonAttributes[] = {"position", "texcoord", nullptr};
_backgroundProgram = OpenGL::Shader::fromFiles(isEMI ? "emi_background" : "grim_background", commonAttributes);
_smushProgram = OpenGL::Shader::fromFiles("grim_smush", commonAttributes);
_textProgram = OpenGL::Shader::fromFiles("grim_text", commonAttributes);
_emergProgram = OpenGL::Shader::fromFiles("grim_emerg", commonAttributes);
static const char* actorAttributes[] = {"position", "texcoord", "color", "normal", nullptr};
_actorProgram = OpenGL::Shader::fromFiles(isEMI ? "emi_actor" : "grim_actor", actorAttributes);
_actorLightsProgram = OpenGL::Shader::fromFiles(isEMI ? "emi_actorlights" : "grim_actorlights", actorAttributes);
_spriteProgram = OpenGL::Shader::fromFiles(isEMI ? "emi_sprite" : "grim_actor", actorAttributes);
static const char* primAttributes[] = { "position", nullptr };
_shadowPlaneProgram = OpenGL::Shader::fromFiles("grim_shadowplane", primAttributes);
_primitiveProgram = OpenGL::Shader::fromFiles("grim_primitive", primAttributes);
if (!isEMI) {
_irisProgram = _primitiveProgram->clone();
_dimProgram = OpenGL::Shader::fromFiles("grim_dim", commonAttributes);
_dimRegionProgram = _dimProgram->clone();
} else {
_dimPlaneProgram = OpenGL::Shader::fromFiles("emi_dimplane", primAttributes);
}
setupQuadEBO();
setupTexturedQuad();
setupTexturedCenteredQuad();
setupPrimitives();
if (!isEMI) {
_blastVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, 128 * 16 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
}
}
void GfxOpenGLS::setupScreen(int screenW, int screenH) {
_screenWidth = screenW;
_screenHeight = screenH;
_scaleW = _screenWidth / (float)_gameWidth;
_scaleH = _screenHeight / (float)_gameHeight;
g_system->showMouse(false);
setupZBuffer();
setupShaders();
glViewport(0, 0, _screenWidth, _screenHeight);
glGenTextures(1, &_storedDisplay);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (g_grim->getGameType() == GType_MONKEY4) {
// GL_LEQUAL as glDepthFunc ensures that subsequent drawing attempts for
// the same triangles are not ignored by the depth test.
// That's necessary for EMI where some models have multiple faces which
// refer to the same vertices. The first face is usually using the
// color map and the following are using textures.
glDepthFunc(GL_LEQUAL);
}
}
void GfxOpenGLS::setupCameraFrustum(float fov, float nclip, float fclip) {
if (_fov == fov && _nclip == nclip && _fclip == fclip)
return;
_fov = fov; _nclip = nclip; _fclip = fclip;
float right = nclip * tan(fov / 2 * ((float)M_PI / 180));
float top = right * 0.75;
_projMatrix = makeFrustumMatrix(-right, right, -top, top, nclip, fclip);
}
void GfxOpenGLS::positionCamera(const Math::Vector3d &pos, const Math::Vector3d &interest, float roll) {
Math::Matrix4 viewMatrix = makeRotationMatrix(Math::Angle(roll), Math::Vector3d(0, 0, 1));
Math::Vector3d up_vec(0, 0, 1);
if (pos.x() == interest.x() && pos.y() == interest.y())
up_vec = Math::Vector3d(0, 1, 0);
Math::Matrix4 lookMatrix = makeLookMatrix(pos, interest, up_vec);
_viewMatrix = viewMatrix * lookMatrix;
_viewMatrix.transpose();
}
void GfxOpenGLS::positionCamera(const Math::Vector3d &pos, const Math::Matrix4 &rot) {
Math::Matrix4 projMatrix = _projMatrix;
projMatrix.transpose();
_currentPos = pos;
_currentRot = rot;
Math::Matrix4 invertZ;
invertZ(2, 2) = -1.0f;
Math::Matrix4 viewMatrix = _currentRot;
viewMatrix.transpose();
Math::Matrix4 camPos;
camPos(0, 3) = -_currentPos.x();
camPos(1, 3) = -_currentPos.y();
camPos(2, 3) = -_currentPos.z();
_viewMatrix = invertZ * viewMatrix * camPos;
_mvpMatrix = projMatrix * _viewMatrix;
_viewMatrix.transpose();
}
Math::Matrix4 GfxOpenGLS::getModelView() {
if (g_grim->getGameType() == GType_MONKEY4) {
Math::Matrix4 invertZ;
invertZ(2, 2) = -1.0f;
Math::Matrix4 viewMatrix = _currentRot;
viewMatrix.transpose();
Math::Matrix4 camPos;
camPos(0, 3) = -_currentPos.x();
camPos(1, 3) = -_currentPos.y();
camPos(2, 3) = -_currentPos.z();
Math::Matrix4 modelView = invertZ * viewMatrix * camPos;
return modelView;
} else {
return _mvpMatrix;
}
}
Math::Matrix4 GfxOpenGLS::getProjection() {
Math::Matrix4 proj = _projMatrix;
proj.transpose();
return proj;
}
void GfxOpenGLS::clearScreen() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void GfxOpenGLS::clearDepthBuffer() {
glClear(GL_DEPTH_BUFFER_BIT);
}
void GfxOpenGLS::flipBuffer() {
g_system->updateScreen();
}
void GfxOpenGLS::getScreenBoundingBox(const Mesh *model, int *x1, int *y1, int *x2, int *y2) {
if (_currentShadowArray) {
*x1 = -1;
*y1 = -1;
*x2 = -1;
*y2 = -1;
return;
}
Math::Matrix4 modelMatrix = _currentActor->getFinalMatrix();
Math::Matrix4 mvpMatrix = _mvpMatrix * modelMatrix;
double top = 1000;
double right = -1000;
double left = 1000;
double bottom = -1000;
Math::Vector3d obj;
float *pVertices = nullptr;
for (int i = 0; i < model->_numFaces; i++) {
for (int j = 0; j < model->_faces[i].getNumVertices(); j++) {
pVertices = model->_vertices + 3 * model->_faces[i].getVertex(j);
obj.set(*(pVertices), *(pVertices + 1), *(pVertices + 2));
Math::Vector4d v = Math::Vector4d(obj.x(), obj.y(), obj.z(), 1.0f);
v = mvpMatrix * v;
v /= v.w();
double winX = (1 + v.x()) / 2.0f * _gameWidth;
double winY = (1 + v.y()) / 2.0f * _gameHeight;
if (winX > right)
right = winX;
if (winX < left)
left = winX;
if (winY < top)
top = winY;
if (winY > bottom)
bottom = winY;
}
}
double t = bottom;
bottom = _gameHeight - top;
top = _gameHeight - t;
if (left < 0)
left = 0;
if (right >= _gameWidth)
right = _gameWidth - 1;
if (top < 0)
top = 0;
if (bottom >= _gameHeight)
bottom = _gameHeight - 1;
if (top >= _gameHeight || left >= _gameWidth || bottom < 0 || right < 0) {
*x1 = -1;
*y1 = -1;
*x2 = -1;
*y2 = -1;
return;
}
*x1 = (int)left;
*y1 = (int)(_gameHeight - bottom);
*x2 = (int)right;
*y2 = (int)(_gameHeight - top);
}
void GfxOpenGLS::getScreenBoundingBox(const EMIModel *model, int *x1, int *y1, int *x2, int *y2) {
if (_currentShadowArray) {
*x1 = -1;
*y1 = -1;
*x2 = -1;
*y2 = -1;
return;
}
Math::Matrix4 modelMatrix = _currentActor->getFinalMatrix();
Math::Matrix4 mvpMatrix = _mvpMatrix * modelMatrix;
double top = 1000;
double right = -1000;
double left = 1000;
double bottom = -1000;
for (uint i = 0; i < model->_numFaces; i++) {
uint16 *indices = (uint16 *)model->_faces[i]._indexes;
for (uint j = 0; j < model->_faces[i]._faceLength * 3; j++) {
uint16 index = indices[j];
const Math::Vector3d &dv = model->_drawVertices[index];
Math::Vector4d v = Math::Vector4d(dv.x(), dv.y(), dv.z(), 1.0f);
v = mvpMatrix * v;
v /= v.w();
double winX = (1 + v.x()) / 2.0f * _gameWidth;
double winY = (1 + v.y()) / 2.0f * _gameHeight;
if (winX > right)
right = winX;
if (winX < left)
left = winX;
if (winY < top)
top = winY;
if (winY > bottom)
bottom = winY;
}
}
double t = bottom;
bottom = _gameHeight - top;
top = _gameHeight - t;
if (left < 0)
left = 0;
if (right >= _gameWidth)
right = _gameWidth - 1;
if (top < 0)
top = 0;
if (bottom >= _gameHeight)
bottom = _gameHeight - 1;
if (top >= _gameHeight || left >= _gameWidth || bottom < 0 || right < 0) {
*x1 = -1;
*y1 = -1;
*x2 = -1;
*y2 = -1;
return;
}
*x1 = (int)left;
*y1 = (int)(_gameHeight - bottom);
*x2 = (int)right;
*y2 = (int)(_gameHeight - top);
}
void GfxOpenGLS::getActorScreenBBox(const Actor *actor, Common::Point &p1, Common::Point &p2) {
// Get the actor's bounding box information (describes a 3D box)
Math::Vector3d bboxPos, bboxSize;
actor->getBBoxInfo(bboxPos, bboxSize);
// Translate the bounding box to the actor's position
Math::Matrix4 m = actor->getFinalMatrix();
bboxPos = bboxPos + actor->getWorldPos();
// Set up the camera coordinate system
Math::Matrix4 modelView = _currentRot;
Math::Matrix4 zScale;
zScale.setValue(2, 2, -1.0);
modelView = modelView * zScale;
modelView.transpose();
modelView.translate(-_currentPos);
modelView.transpose();
// Set values outside of the screen range
p1.x = 1000;
p1.y = 1000;
p2.x = -1000;
p2.y = -1000;
// Project all of the points in the 3D bounding box
Math::Vector3d p, projected;
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
for (int z = 0; z < 2; z++) {
Math::Vector3d added(bboxSize.x() * 0.5f * (x * 2 - 1), bboxSize.y() * 0.5f * (y * 2 - 1), bboxSize.z() * 0.5f * (z * 2 - 1));
m.transform(&added, false);
p = bboxPos + added;
Math::Vector4d v = Math::Vector4d(p.x(), p.y(), p.z(), 1.0f);
v = _projMatrix.transform(modelView.transform(v));
if (v.w() == 0.0)
return;
v /= v.w();
double winX = (1 + v.x()) / 2.0f * _gameWidth;
double winY = (1 + v.y()) / 2.0f * _gameHeight;
// Find the points
if (winX < p1.x)
p1.x = winX;
if (winY < p1.y)
p1.y = winY;
if (winX > p2.x)
p2.x = winX;
if (winY > p2.y)
p2.y = winY;
}
}
}
// Swap the p1/p2 y coorindates
int16 tmp = p1.y;
p1.y = 480 - p2.y;
p2.y = 480 - tmp;
}
void GfxOpenGLS::startActorDraw(const Actor *actor) {
_currentActor = actor;
glEnable(GL_DEPTH_TEST);
const Math::Vector3d &pos = actor->getWorldPos();
const Math::Quaternion &quat = actor->getRotationQuat();
//const float scale = actor->getScale();
Math::Matrix4 viewMatrix = _viewMatrix;
viewMatrix.transpose();
OpenGL::Shader *shaders[] = { _spriteProgram, _actorProgram, _actorLightsProgram };
if (g_grim->getGameType() == GType_MONKEY4) {
glEnable(GL_CULL_FACE);
glFrontFace(GL_CW);
if (actor->isInOverworld())
viewMatrix = Math::Matrix4();
Math::Vector4d color(1.0f, 1.0f, 1.0f, actor->getEffectiveAlpha());
const Math::Matrix4 &viewRot = _currentRot;
Math::Matrix4 modelMatrix = actor->getFinalMatrix();
Math::Matrix4 normalMatrix = viewMatrix * modelMatrix;
normalMatrix.invertAffineOrthonormal();
modelMatrix.transpose();
for (int i = 0; i < 3; i++) {
shaders[i]->use();
shaders[i]->setUniform("modelMatrix", modelMatrix);
if (actor->isInOverworld()) {
shaders[i]->setUniform("viewMatrix", viewMatrix);
shaders[i]->setUniform("projMatrix", _overworldProjMatrix);
shaders[i]->setUniform("cameraPos", Math::Vector3d(0,0,0));
} else {
shaders[i]->setUniform("viewMatrix", viewRot);
shaders[i]->setUniform("projMatrix", _projMatrix);
shaders[i]->setUniform("cameraPos", _currentPos);
}
shaders[i]->setUniform("normalMatrix", normalMatrix);
shaders[i]->setUniform("useVertexAlpha", GL_FALSE);
shaders[i]->setUniform("uniformColor", color);
shaders[i]->setUniform1f("alphaRef", 0.0f);
shaders[i]->setUniform1f("meshAlpha", 1.0f);
}
} else {
Math::Matrix4 modelMatrix = quat.toMatrix();
bool hasZBuffer = g_grim->getCurrSet()->getCurrSetup()->_bkgndZBm;
Math::Matrix4 extraMatrix;
_matrixStack.top() = extraMatrix;
modelMatrix.transpose();
modelMatrix.setPosition(pos);
modelMatrix.transpose();
for (int i = 0; i < 3; i++) {
shaders[i]->use();
shaders[i]->setUniform("modelMatrix", modelMatrix);
shaders[i]->setUniform("viewMatrix", _viewMatrix);
shaders[i]->setUniform("projMatrix", _projMatrix);
shaders[i]->setUniform("extraMatrix", extraMatrix);
shaders[i]->setUniform("tex", 0);
shaders[i]->setUniform("texZBuf", 1);
shaders[i]->setUniform("hasZBuffer", hasZBuffer);
shaders[i]->setUniform("texcropZBuf", _zBufTexCrop);
shaders[i]->setUniform("screenSize", Math::Vector2d(_screenWidth, _screenHeight));
shaders[i]->setUniform1f("alphaRef", 0.5f);
}
}
if (_currentShadowArray) {
const Sector *shadowSector = _currentShadowArray->planeList.front().sector;
Math::Vector3d color;
if (g_grim->getGameType() == GType_GRIM) {
color = Math::Vector3d(_shadowColorR, _shadowColorG, _shadowColorB) / 255.f;
} else {
color = Math::Vector3d(_currentShadowArray->color.getRed(), _currentShadowArray->color.getGreen(), _currentShadowArray->color.getBlue()) / 255.f;
}
Math::Vector3d normal = shadowSector->getNormal();
if (!_currentShadowArray->dontNegate)
normal = -normal;
for (int i = 0; i < 3; i++) {
shaders[i]->use();
shaders[i]->setUniform("shadow._active", true);
shaders[i]->setUniform("shadow._color", color);
shaders[i]->setUniform("shadow._light", _currentShadowArray->pos);
shaders[i]->setUniform("shadow._point", shadowSector->getVertices()[0]);
shaders[i]->setUniform("shadow._normal", normal);
}
glDepthMask(GL_FALSE);
glDisable(GL_BLEND);
glEnable(GL_POLYGON_OFFSET_FILL);
}
else {
for (int i = 0; i < 3; i++) {
shaders[i]->use();
shaders[i]->setUniform("shadow._active", false);
}
}
_actorLightsProgram->setUniform("hasAmbient", _hasAmbientLight);
if (_lightsEnabled) {
// Allocate all variables in one chunk
static const unsigned int numUniforms = 4;
static const unsigned int uniformSize = _maxLights * 4;
float *lightsData = new float[numUniforms * uniformSize];
for (int i = 0; i < _maxLights; ++i) {
const GLSLight &l = _lights[i];
// lightsPosition
Math::Vector4d tmp = viewMatrix * l._position;
lightsData[0 * uniformSize + 4 * i + 0] = tmp.x();
lightsData[0 * uniformSize + 4 * i + 1] = tmp.y();
lightsData[0 * uniformSize + 4 * i + 2] = tmp.z();
lightsData[0 * uniformSize + 4 * i + 3] = tmp.w();
// lightsDirection
Math::Vector4d direction = l._direction;
direction.w() = 0.0;
viewMatrix.transformVector(&direction);
direction.w() = l._direction.w();
lightsData[1 * uniformSize + 4 * i + 0] = direction.x();
lightsData[1 * uniformSize + 4 * i + 1] = direction.y();
lightsData[1 * uniformSize + 4 * i + 2] = direction.z();
lightsData[1 * uniformSize + 4 * i + 3] = direction.w();
// lightsColor
lightsData[2 * uniformSize + 4 * i + 0] = l._color.x();
lightsData[2 * uniformSize + 4 * i + 1] = l._color.y();
lightsData[2 * uniformSize + 4 * i + 2] = l._color.z();
lightsData[2 * uniformSize + 4 * i + 3] = l._color.w();
// lightsParams
lightsData[3 * uniformSize + 4 * i + 0] = l._params.x();
lightsData[3 * uniformSize + 4 * i + 1] = l._params.y();
lightsData[3 * uniformSize + 4 * i + 2] = l._params.z();
lightsData[3 * uniformSize + 4 * i + 3] = l._params.w();
}
Common::String uniform;
GLint uniformPos;
uniform = Common::String::format("lightsPosition");
uniformPos = _actorLightsProgram->getUniformLocation(uniform.c_str());
if (uniformPos == -1) {
error("No uniform named '%s'", uniform.c_str());
}
glUniform4fv(uniformPos, _maxLights, &lightsData[0 * uniformSize]);
uniform = Common::String::format("lightsDirection");
uniformPos = _actorLightsProgram->getUniformLocation(uniform.c_str());
if (uniformPos == -1) {
error("No uniform named '%s'", uniform.c_str());
}
glUniform4fv(uniformPos, _maxLights, &lightsData[1 * uniformSize]);
uniform = Common::String::format("lightsColor");
uniformPos = _actorLightsProgram->getUniformLocation(uniform.c_str());
if (uniformPos == -1) {
error("No uniform named '%s'", uniform.c_str());
}
glUniform4fv(uniformPos, _maxLights, &lightsData[2 * uniformSize]);
uniform = Common::String::format("lightsParams");
uniformPos = _actorLightsProgram->getUniformLocation(uniform.c_str());
if (uniformPos == -1) {
error("No uniform named '%s'", uniform.c_str());
}
glUniform4fv(uniformPos, _maxLights, &lightsData[3 * uniformSize]);
delete[] lightsData;
}
}
void GfxOpenGLS::finishActorDraw() {
_currentActor = nullptr;
glDisable(GL_POLYGON_OFFSET_FILL);
if (g_grim->getGameType() == GType_MONKEY4) {
glDisable(GL_CULL_FACE);
}
}
void GfxOpenGLS::setShadow(Shadow *shadow) {
_currentShadowArray = shadow;
}
void GfxOpenGLS::drawShadowPlanes() {
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
glClearStencil(~0);
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, (GLuint)~0);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
if (!_currentShadowArray->userData) {
uint32 numVertices = 0;
uint32 numTriangles = 0;
for (SectorListType::iterator i = _currentShadowArray->planeList.begin(); i != _currentShadowArray->planeList.end(); ++i) {
numVertices += i->sector->getNumVertices();
numTriangles += i->sector->getNumVertices() - 2;
}
float *vertBuf = new float[3 * numVertices];
uint16 *idxBuf = new uint16[3 * numTriangles];
float *vert = vertBuf;
uint16 *idx = idxBuf;
for (SectorListType::iterator i = _currentShadowArray->planeList.begin(); i != _currentShadowArray->planeList.end(); ++i) {
Sector *shadowSector = i->sector;
memcpy(vert, shadowSector->getVertices(), 3 * shadowSector->getNumVertices() * sizeof(float));
uint16 first = (vert - vertBuf) / 3;
for (uint16 j = 2; j < shadowSector->getNumVertices(); ++j) {
*idx++ = first;
*idx++ = first + j - 1;
*idx++ = first + j;
}
vert += 3 * shadowSector->getNumVertices();
}
ShadowUserData *sud = new ShadowUserData;
_currentShadowArray->userData = sud;
sud->_numTriangles = numTriangles;
sud->_verticesVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, 3 * numVertices * sizeof(float), vertBuf, GL_STATIC_DRAW);
sud->_indicesVBO = OpenGL::Shader::createBuffer(GL_ELEMENT_ARRAY_BUFFER, 3 * numTriangles * sizeof(uint16), idxBuf, GL_STATIC_DRAW);
delete[] vertBuf;
delete[] idxBuf;
}
const ShadowUserData *sud = (ShadowUserData *)_currentShadowArray->userData;
_shadowPlaneProgram->use();
_shadowPlaneProgram->setUniform("projMatrix", _projMatrix);
_shadowPlaneProgram->setUniform("viewMatrix", _viewMatrix);
glBindBuffer(GL_ARRAY_BUFFER, sud->_verticesVBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sud->_indicesVBO);
const uint32 attribPos = _shadowPlaneProgram->getAttribute("position")._idx;
glEnableVertexAttribArray(attribPos);
glVertexAttribPointer(attribPos, 3, GL_FLOAT, GL_TRUE, 3 * sizeof(float), nullptr);
glDrawElements(GL_TRIANGLES, 3 * sud->_numTriangles, GL_UNSIGNED_SHORT, nullptr);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_EQUAL, 1, (GLuint)~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
}
void GfxOpenGLS::setShadowMode() {
GfxBase::setShadowMode();
}
void GfxOpenGLS::clearShadowMode() {
GfxBase::clearShadowMode();
glDisable(GL_STENCIL_TEST);
glDepthMask(GL_TRUE);
}
bool GfxOpenGLS::isShadowModeActive() {
return false;
}
void GfxOpenGLS::setShadowColor(byte r, byte g, byte b) {
_shadowColorR = r;
_shadowColorG = g;
_shadowColorB = b;
}
void GfxOpenGLS::getShadowColor(byte *r, byte *g, byte *b) {
*r = _shadowColorR;
*g = _shadowColorG;
*b = _shadowColorB;
}
void GfxOpenGLS::destroyShadow(Shadow *shadow) {
ShadowUserData *sud = static_cast<ShadowUserData *>(shadow->userData);
if (sud) {
OpenGL::Shader::freeBuffer(sud->_verticesVBO);
OpenGL::Shader::freeBuffer(sud->_indicesVBO);
delete sud;
}
shadow->userData = nullptr;
}
void GfxOpenGLS::set3DMode() {
}
void GfxOpenGLS::translateViewpointStart() {
_matrixStack.push(_matrixStack.top());
}
void GfxOpenGLS::translateViewpoint(const Math::Vector3d &vec) {
Math::Matrix4 temp;
temp.setPosition(vec);
temp.transpose();
_matrixStack.top() = temp * _matrixStack.top();
}
void GfxOpenGLS::rotateViewpoint(const Math::Angle &angle, const Math::Vector3d &axis_) {
Math::Matrix4 temp = makeRotationMatrix(angle, axis_) * _matrixStack.top();
_matrixStack.top() = temp;
}
void GfxOpenGLS::rotateViewpoint(const Math::Matrix4 &rot) {
Math::Matrix4 temp = rot * _matrixStack.top();
_matrixStack.top() = temp;
}
void GfxOpenGLS::translateViewpointFinish() {
_matrixStack.pop();
}
void GfxOpenGLS::updateEMIModel(const EMIModel* model) {
const EMIModelUserData *mud = (const EMIModelUserData *)model->_userData;
glBindBuffer(GL_ARRAY_BUFFER, mud->_verticesVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, model->_numVertices * 3 * sizeof(float), model->_drawVertices);
glBindBuffer(GL_ARRAY_BUFFER, mud->_normalsVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, model->_numVertices * 3 * sizeof(float), model->_drawNormals);
}
void GfxOpenGLS::drawEMIModelFace(const EMIModel* model, const EMIMeshFace* face) {
if (face->_flags & EMIMeshFace::kAlphaBlend || face->_flags & EMIMeshFace::kUnknownBlend)
glEnable(GL_BLEND);
const EMIModelUserData *mud = (const EMIModelUserData *)model->_userData;
OpenGL::Shader *actorShader;
if ((face->_flags & EMIMeshFace::kNoLighting) ? false : _lightsEnabled)
actorShader = mud->_shaderLights;
else
actorShader = mud->_shader;
actorShader->use();
bool textured = face->_hasTexture && !_currentShadowArray;
actorShader->setUniform("textured", textured ? GL_TRUE : GL_FALSE);
actorShader->setUniform("useVertexAlpha", _selectedTexture->_hasAlpha);
actorShader->setUniform1f("meshAlpha", (model->_meshAlphaMode == Actor::AlphaReplace) ? model->_meshAlpha : 1.0f);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, face->_indicesEBO);
glDrawElements(GL_TRIANGLES, 3 * face->_faceLength, GL_UNSIGNED_SHORT, nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void GfxOpenGLS::drawMesh(const Mesh *mesh) {
const ModelUserData *mud = (const ModelUserData *)mesh->_userData;
if (!mud)
return;
OpenGL::Shader *actorShader;
if (_lightsEnabled && !isShadowModeActive())
actorShader = mud->_shaderLights;
else
actorShader = mud->_shader;
actorShader->use();
actorShader->setUniform("extraMatrix", _matrixStack.top());
const Material *curMaterial = nullptr;
for (int i = 0; i < mesh->_numFaces;) {
const MeshFace *face = &mesh->_faces[i];
curMaterial = face->getMaterial();
curMaterial->select();
int faces = 0;
for (; i < mesh->_numFaces; ++i) {
if (mesh->_faces[i].getMaterial() != curMaterial)
break;
faces += 3 * (mesh->_faces[i].getNumVertices() - 2);
}
bool textured = face->hasTexture() && !_currentShadowArray;
actorShader->setUniform("textured", textured ? GL_TRUE : GL_FALSE);
actorShader->setUniform("texScale", Math::Vector2d(_selectedTexture->_width, _selectedTexture->_height));
glDrawArrays(GL_TRIANGLES, *(int *)face->_userData, faces);
}
}
void GfxOpenGLS::drawDimPlane() {
if (_dimLevel == 0.0f)
return;
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
_dimPlaneProgram->use();
_dimPlaneProgram->setUniform1f("dim", _dimLevel);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadEBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
}
void GfxOpenGLS::drawModelFace(const Mesh *mesh, const MeshFace *face) {
}
void GfxOpenGLS::drawSprite(const Sprite *sprite) {
if (g_grim->getGameType() == GType_MONKEY4) {
glDepthMask(GL_TRUE);
} else {
glDepthMask(GL_FALSE);
}
if (sprite->_flags1 & Sprite::BlendAdditive) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
} else {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
// FIXME: depth test does not work yet because final z coordinates
// for Sprites and actor textures are inconsistently calculated
if (sprite->_flags2 & Sprite::DepthTest || _currentActor->isInOverworld()) {
glEnable(GL_DEPTH_TEST);
} else {
glDisable(GL_DEPTH_TEST);
}
_spriteProgram->use();
Math::Matrix4 rotateMatrix;
rotateMatrix.buildAroundZ(_currentActor->getYaw());
Math::Matrix4 extraMatrix;
extraMatrix.setPosition(sprite->_pos);
extraMatrix(0, 0) = sprite->_width;
extraMatrix(1, 1) = sprite->_height;
extraMatrix = rotateMatrix * extraMatrix;
extraMatrix.transpose();
_spriteProgram->setUniform("extraMatrix", extraMatrix);
_spriteProgram->setUniform("textured", GL_TRUE);
if (g_grim->getGameType() == GType_GRIM) {
_spriteProgram->setUniform1f("alphaRef", 0.5f);
} else if (sprite->_flags2 & Sprite::AlphaTest) {
_spriteProgram->setUniform1f("alphaRef", 0.1f);
} else {
_spriteProgram->setUniform1f("alphaRef", 0.0f);
}
// FIXME: Currently vertex-specific colors are not supported for sprites.
// It is unknown at this time if this is really needed anywhere.
Math::Vector4d color(sprite->_red[0] / 255.0f, sprite->_green[0] / 255.0f, sprite->_blue[0] / 255.0f, sprite->_alpha[0] / 255.0f);
_spriteProgram->setUniform("uniformColor", color);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadEBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void GfxOpenGLS::enableLights() {
_lightsEnabled = true;
}
void GfxOpenGLS::disableLights() {
_lightsEnabled = false;
}
void GfxOpenGLS::setupLight(Grim::Light *light, int lightId) {
_lightsEnabled = true;
if (lightId >= _maxLights) {
return;
}
// Disable previous lights.
if (lightId == 0) {
_hasAmbientLight = false;
for (int id = 0; id < _maxLights; ++id)
_lights[id]._color.w() = 0.0;
}
Math::Vector4d &lightColor = _lights[lightId]._color;
Math::Vector4d &lightPos = _lights[lightId]._position;
Math::Vector4d &lightDir = _lights[lightId]._direction;
Math::Vector4d &lightParams = _lights[lightId]._params;
lightColor.x() = (float)light->_color.getRed();
lightColor.y() = (float)light->_color.getGreen();
lightColor.z() = (float)light->_color.getBlue();
lightColor.w() = light->_scaledintensity;
if (light->_type == Grim::Light::Omni) {
lightPos = Math::Vector4d(light->_pos.x(), light->_pos.y(), light->_pos.z(), 1.0f);
lightDir = Math::Vector4d(0.0f, 0.0f, 0.0f, -1.0f);
lightParams = Math::Vector4d(light->_falloffNear, light->_falloffFar, 0.0f, 0.0f);
} else if (light->_type == Grim::Light::Direct) {
lightPos = Math::Vector4d(-light->_dir.x(), -light->_dir.y(), -light->_dir.z(), 0.0f);
lightDir = Math::Vector4d(0.0f, 0.0f, 0.0f, -1.0f);
} else if (light->_type == Grim::Light::Spot) {
lightPos = Math::Vector4d(light->_pos.x(), light->_pos.y(), light->_pos.z(), 1.0f);
lightDir = Math::Vector4d(light->_dir.x(), light->_dir.y(), light->_dir.z(), 1.0f);
lightParams = Math::Vector4d(light->_falloffNear, light->_falloffFar, light->_cospenumbraangle, light->_cosumbraangle);
} else if (light->_type == Grim::Light::Ambient) {
lightPos = Math::Vector4d(0.0f, 0.0f, 0.0f, -1.0f);
lightDir = Math::Vector4d(0.0f, 0.0f, 0.0f, -1.0f);
_hasAmbientLight = true;
}
}
void GfxOpenGLS::turnOffLight(int lightId) {
if (lightId >= _maxLights) {
return;
}
_lights[lightId]._color = Math::Vector4d(0.0f, 0.0f, 0.0f, 0.0f);
_lights[lightId]._position = Math::Vector4d(0.0f, 0.0f, 0.0f, 0.0f);
_lights[lightId]._direction = Math::Vector4d(0.0f, 0.0f, 0.0f, 0.0f);
}
void GfxOpenGLS::createTexture(Texture *texture, const uint8 *data, const CMap *cmap, bool clamp) {
texture->_texture = new GLuint[1];
glGenTextures(1, (GLuint *)texture->_texture);
char *texdata = new char[texture->_width * texture->_height * 4];
char *texdatapos = texdata;
if (cmap != nullptr) { // EMI doesn't have colour-maps
int bytes = 4;
for (int y = 0; y < texture->_height; y++) {
for (int x = 0; x < texture->_width; x++) {
uint8 col = *(const uint8 *)(data);
if (col == 0) {
memset(texdatapos, 0, bytes); // transparent
if (!texture->_hasAlpha) {
texdatapos[3] = '\xff'; // fully opaque
}
} else {
memcpy(texdatapos, cmap->_colors + 3 * (col), 3);
texdatapos[3] = '\xff'; // fully opaque
}
texdatapos += bytes;
data++;
}
}
} else {
memcpy(texdata, data, texture->_width * texture->_height * texture->_bpp);
}
GLuint *textures = (GLuint *)texture->_texture;
glBindTexture(GL_TEXTURE_2D, textures[0]);
// Remove darkened lines in EMI intro
if (g_grim->getGameType() == GType_MONKEY4 && clamp) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->_width, texture->_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texdata);
delete[] texdata;
}
void GfxOpenGLS::selectTexture(const Texture *texture) {
GLuint *textures = (GLuint *)texture->_texture;
glBindTexture(GL_TEXTURE_2D, textures[0]);
if (texture->_hasAlpha && g_grim->getGameType() == GType_MONKEY4) {
glEnable(GL_BLEND);
}
_selectedTexture = const_cast<Texture *>(texture);
}
void GfxOpenGLS::destroyTexture(Texture *texture) {
GLuint *textures = static_cast<GLuint *>(texture->_texture);
if (textures) {
glDeleteTextures(1, textures);
delete[] textures;
}
}
void GfxOpenGLS::createBitmap(BitmapData *bitmap) {
if (bitmap->_format != 1) {
for (int pic = 0; pic < bitmap->_numImages; pic++) {
uint16 *zbufPtr = reinterpret_cast<uint16 *>(const_cast<void *>(bitmap->getImageData(pic).getPixels()));
for (int i = 0; i < (bitmap->_width * bitmap->_height); i++) {
uint16 val = READ_LE_UINT16(zbufPtr + i);
// fix the value if it is incorrectly set to the bitmap transparency color
if (val == 0xf81f) {
val = 0;
}
// This is later read as a LA pair when filling texture
// with L being used as the LSB in fragment shader
zbufPtr[i] = TO_LE_16(0xffff - ((uint32)val) * 0x10000 / 100 / (0x10000 - val));
}
}
}
bitmap->_hasTransparency = false;
if (bitmap->_format == 1) {
bitmap->_numTex = 1;
GLuint *textures = new GLuint[bitmap->_numTex * bitmap->_numImages];
bitmap->_texIds = textures;
glGenTextures(bitmap->_numTex * bitmap->_numImages, textures);
byte *texData = nullptr;
const byte *texOut = nullptr;
GLint format = GL_RGBA;
GLint type = GL_UNSIGNED_BYTE;
int bytes = 4;
glPixelStorei(GL_UNPACK_ALIGNMENT, bytes);
for (int pic = 0; pic < bitmap->_numImages; pic++) {
if (bitmap->_format == 1 && bitmap->_bpp == 16 && bitmap->_colorFormat != BM_RGB1555) {
if (texData == nullptr)
texData = new byte[bytes * bitmap->_width * bitmap->_height];
// Convert data to 32-bit RGBA format
byte *texDataPtr = texData;
const uint16 *bitmapData = reinterpret_cast<const uint16 *>(bitmap->getImageData(pic).getPixels());
for (int i = 0; i < bitmap->_width * bitmap->_height; i++, texDataPtr += bytes, bitmapData++) {
uint16 pixel = *bitmapData;
int r = pixel >> 11;
texDataPtr[0] = (r << 3) | (r >> 2);
int g = (pixel >> 5) & 0x3f;
texDataPtr[1] = (g << 2) | (g >> 4);
int b = pixel & 0x1f;
texDataPtr[2] = (b << 3) | (b >> 2);
if (pixel == 0xf81f) { // transparent
texDataPtr[3] = 0;
bitmap->_hasTransparency = true;
} else {
texDataPtr[3] = 255;
}
}
texOut = texData;
} else if (bitmap->_format == 1 && bitmap->_colorFormat == BM_RGB1555) {
bitmap->convertToColorFormat(pic, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
texOut = (const byte *)bitmap->getImageData(pic).getPixels();
} else {
texOut = (const byte *)bitmap->getImageData(pic).getPixels();
}
int actualWidth = nextHigher2(bitmap->_width);
int actualHeight = nextHigher2(bitmap->_height);
glBindTexture(GL_TEXTURE_2D, textures[bitmap->_numTex * pic]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, format, actualWidth, actualHeight, 0, format, type, nullptr);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap->_width, bitmap->_height, format, type, texOut);
}
if (texData)
delete[] texData;
bitmap->freeData();
OpenGL::Shader *shader = _backgroundProgram->clone();
bitmap->_userData = shader;
if (g_grim->getGameType() == GType_MONKEY4) {
GLuint vbo = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, bitmap->_numCoords * 4 * sizeof(float), bitmap->_texc, GL_STATIC_DRAW);
shader->enableVertexAttribute("position", vbo, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
shader->enableVertexAttribute("texcoord", vbo, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 2*sizeof(float));
}
} else {
bitmap->_numTex = 0;
bitmap->_texIds = nullptr;
bitmap->_userData = nullptr;
}
}
void GfxOpenGLS::drawBitmap(const Bitmap *bitmap, int dx, int dy, uint32 layer) {
if (g_grim->getGameType() == GType_MONKEY4 && bitmap->_data && bitmap->_data->_texc) {
BitmapData *data = bitmap->_data;
OpenGL::Shader *shader = (OpenGL::Shader *)data->_userData;
GLuint *textures = (GLuint *)bitmap->getTexIds();
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
shader->use();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadEBO);
assert(layer < data->_numLayers);
uint32 offset = data->_layers[layer]._offset;
for (uint32 i = offset; i < offset + data->_layers[layer]._numImages; ++i) {
glBindTexture(GL_TEXTURE_2D, textures[data->_verts[i]._texid]);
unsigned short startVertex = data->_verts[i]._pos / 4 * 6;
unsigned short numVertices = data->_verts[i]._verts / 4 * 6;
glDrawElements(GL_TRIANGLES, numVertices, GL_UNSIGNED_SHORT, (void *)(startVertex * sizeof(unsigned short)));
}
return;
}
int format = bitmap->getFormat();
if ((format == 1 && !_renderBitmaps) || (format == 5 && !_renderZBitmaps)) {
return;
}
if (format == 1) {
GLuint *textures = (GLuint *)bitmap->getTexIds();
if (bitmap->getFormat() == 1 && bitmap->getHasTransparency()) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
} else {
glDisable(GL_BLEND);
}
OpenGL::Shader *shader = (OpenGL::Shader *)bitmap->_data->_userData;
shader->use();
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadEBO);
int cur_tex_idx = bitmap->getNumTex() * (bitmap->getActiveImage() - 1);
glBindTexture(GL_TEXTURE_2D, textures[cur_tex_idx]);
float width = bitmap->getWidth();
float height = bitmap->getHeight();
shader->setUniform("offsetXY", Math::Vector2d(float(dx) / _gameWidth, float(dy) / _gameHeight));
shader->setUniform("sizeWH", Math::Vector2d(width / _gameWidth, height / _gameHeight));
shader->setUniform("texcrop", Math::Vector2d(width / nextHigher2((int)width), height / nextHigher2((int)height)));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
} else {
// Only draw the manual zbuffer when enabled
if (bitmap->getActiveImage() - 1 < bitmap->getNumImages()) {
drawDepthBitmap(bitmap->getId(), dx, dy, bitmap->getWidth(), bitmap->getHeight(),
(char *)const_cast<void *>(bitmap->getData(bitmap->getActiveImage() - 1).getPixels()));
} else {
warning("zbuffer image has index out of bounds! %d/%d", bitmap->getActiveImage(), bitmap->getNumImages());
}
return;
}
}
void GfxOpenGLS::drawDepthBitmap(int bitmapId, int x, int y, int w, int h, char *data) {
static int prevId = -1;
static int prevX = -1, prevY = -1;
static int prevW = -1, prevH = -1;
static char *prevData = nullptr;
// Sometimes the data pointer is reused by the allocator between bitmaps
// Use the bitmap ID to ensure we don't prevent an expected update
if (bitmapId == prevId && prevX == x && prevY == y && prevW == w && prevH == h && data == prevData) {
return;
}
prevId = bitmapId;
prevX = x;
prevY = y;
prevW = w;
prevH = h;
prevData = data;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, _zBufTex);
glPixelStorei(GL_UNPACK_ALIGNMENT, 2); // 16 bit Z depth bitmap
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glActiveTexture(GL_TEXTURE0);
}
void GfxOpenGLS::destroyBitmap(BitmapData *bitmap) {
GLuint *textures = (GLuint *)bitmap->_texIds;
if (textures) {
glDeleteTextures(bitmap->_numTex * bitmap->_numImages, textures);
delete[] textures;
bitmap->_texIds = nullptr;
}
OpenGL::Shader *shader = (OpenGL::Shader *)bitmap->_userData;
if (g_grim->getGameType() == GType_MONKEY4) {
glDeleteBuffers(1, &shader->getAttributeAt(0)._vbo);
}
delete shader;
if (bitmap->_format != 1) {
bitmap->freeData();
}
}
void GfxOpenGLS::createFont(Font *font) {
const byte *bitmapData = font->getFontData();
uint dataSize = font->getDataSize();
uint8 bpp = 4;
uint8 charsWide = 16;
uint8 charsHigh = 16;
byte *texDataPtr = new byte[dataSize * bpp];
byte *data = texDataPtr;
for (uint i = 0; i < dataSize; i++, texDataPtr += bpp, bitmapData++) {
byte pixel = *bitmapData;
if (pixel == 0x00) {
texDataPtr[0] = texDataPtr[1] = texDataPtr[2] = texDataPtr[3] = 0;
} else if (pixel == 0x80) {
texDataPtr[0] = texDataPtr[1] = texDataPtr[2] = 0;
texDataPtr[3] = 255;
} else if (pixel == 0xFF) {
texDataPtr[0] = texDataPtr[1] = texDataPtr[2] = texDataPtr[3] = 255;
}
}
int size = 0;
for (int i = 0; i < 256; ++i) {
int width = font->getCharBitmapWidth(i), height = font->getCharBitmapHeight(i);
int m = MAX(width, height);
if (m > size)
size = m;
}
assert(size < 64);
if (size < 8)
size = 8;
if (size < 16)
size = 16;
else if (size < 32)
size = 32;
else if (size < 64)
size = 64;
uint arraySize = size * size * bpp * charsWide * charsHigh;
byte *temp = new byte[arraySize]();
FontUserData *userData = new FontUserData;
font->setUserData(userData);
userData->texture = 0;
userData->size = size;
GLuint *texture = &(userData->texture);
glGenTextures(1, texture);
for (int i = 0, row = 0; i < 256; ++i) {
int width = font->getCharBitmapWidth(i), height = font->getCharBitmapHeight(i);
int32 d = font->getCharOffset(i);
for (int x = 0; x < height; ++x) {
// a is the offset to get to the correct row.
// b is the offset to get to the correct line in the character.
// c is the offset of the character from the start of the row.
uint a = row * size * size * bpp * charsHigh;
uint b = x * size * charsWide * bpp;
uint c = 0;
if (i != 0)
c = ((i - 1) % 16) * size * bpp;
uint pos = a + b + c;
uint pos2 = d * bpp + x * width * bpp;
assert(pos + width * bpp <= arraySize);
assert(pos2 + width * bpp <= dataSize * bpp);
memcpy(temp + pos, data + pos2, width * bpp);
}
if (i != 0 && i % charsWide == 0)
++row;
}
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size * charsWide, size * charsHigh, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp);
delete[] data;
delete[] temp;
}
void GfxOpenGLS::destroyFont(Font *font) {
const FontUserData *data = (const FontUserData *)font->getUserData();
if (data) {
glDeleteTextures(1, &(data->texture));
delete data;
}
}
void GfxOpenGLS::createTextObject(TextObject *text) {
const Color &color = text->getFGColor();
const Font *font = text->getFont();
const FontUserData *userData = (const FontUserData *)font->getUserData();
if (!userData)
error("Could not get font userdata");
float sizeW = float(userData->size) / _gameWidth;
float sizeH = float(userData->size) / _gameHeight;
const Common::String *lines = text->getLines();
int numLines = text->getNumLines();
int numCharacters = 0;
for (int j = 0; j < numLines; ++j) {
numCharacters += lines[j].size();
}
float *bufData = new float[numCharacters * 16];
float *cur = bufData;
for (int j = 0; j < numLines; ++j) {
const Common::String &line = lines[j];
int x = text->getLineX(j);
int y = text->getLineY(j);
for (uint i = 0; i < line.size(); ++i) {
uint8 character = line[i];
float w = y + font->getCharStartingLine(character);
if (g_grim->getGameType() == GType_GRIM)
w += font->getBaseOffsetY();
float z = x + font->getCharStartingCol(character);
z /= _gameWidth;
w /= _gameHeight;
float width = 1 / 16.f;
float cx = ((character - 1) % 16) / 16.0f;
float cy = ((character - 1) / 16) / 16.0f;
float charData[] = {
z, w, cx, cy,
z + sizeW, w, cx + width, cy,
z + sizeW, w + sizeH, cx + width, cy + width,
z, w + sizeH, cx, cy + width
};
memcpy(cur, charData, 16 * sizeof(float));
cur += 16;
x += font->getCharKernedWidth(character);
}
}
GLuint vbo;
if (text->isBlastDraw()) {
vbo = _blastVBO;
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, numCharacters * 16 * sizeof(float), bufData);
} else {
vbo = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, numCharacters * 16 * sizeof(float), bufData, GL_STATIC_DRAW);
}
OpenGL::Shader * textShader = _textProgram->clone();
glBindBuffer(GL_ARRAY_BUFFER, vbo);
textShader->enableVertexAttribute("position", vbo, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
textShader->enableVertexAttribute("texcoord", vbo, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 2 * sizeof(float));
TextUserData * td = new TextUserData;
td->characters = numCharacters;
td->shader = textShader;
td->color = color;
td->texture = userData->texture;
text->setUserData(td);
delete[] bufData;
}
void GfxOpenGLS::drawTextObject(const TextObject *text) {
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
const TextUserData * td = (const TextUserData *) text->getUserData();
assert(td);
td->shader->use();
Math::Vector3d colors(float(td->color.getRed()) / 255.0f,
float(td->color.getGreen()) / 255.0f,
float(td->color.getBlue()) / 255.0f);
td->shader->setUniform("color", colors);
glBindTexture(GL_TEXTURE_2D, td->texture);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadEBO);
glDrawElements(GL_TRIANGLES, td->characters * 6, GL_UNSIGNED_SHORT, nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glEnable(GL_DEPTH_TEST);
}
void GfxOpenGLS::destroyTextObject(TextObject *text) {
const TextUserData * td = (const TextUserData *) text->getUserData();
if (!text->isBlastDraw()) {
glDeleteBuffers(1, &td->shader->getAttributeAt(0)._vbo);
}
text->setUserData(nullptr);
delete td->shader;
delete td;
}
void GfxOpenGLS::storeDisplay() {
glBindTexture(GL_TEXTURE_2D, _storedDisplay);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _screenWidth, _screenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, _screenWidth, _screenHeight, 0);
}
void GfxOpenGLS::copyStoredToDisplay() {
if (!_dimProgram)
return;
_dimProgram->use();
_dimProgram->setUniform("scaleWH", Math::Vector2d(1.f, 1.f));
_dimProgram->setUniform("tex", 0);
glBindTexture(GL_TEXTURE_2D, _storedDisplay);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDrawArrays(GL_TRIANGLES, 0, 6);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
}
void GfxOpenGLS::dimScreen() {
}
void GfxOpenGLS::dimRegion(int xin, int yReal, int w, int h, float level) {
xin = (int)(xin * _scaleW);
yReal = (int)(yReal * _scaleH);
w = (int)(w * _scaleW);
h = (int)(h * _scaleH);
int yin = _screenHeight - yReal - h;
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, xin, yin, w, h, 0);
glBindBuffer(GL_ARRAY_BUFFER, _dimRegionVBO);
float width = w;
float height = h;
float x = xin;
float y = yin;
float points[24] = {
x, y, 0.0f, 0.0f,
x + width, y, 1.0f, 0.0f,
x + width, y + height, 1.0f, 1.0f,
x + width, y + height, 1.0f, 1.0f,
x, y + height, 0.0f, 1.0f,
x, y, 0.0f, 0.0f,
};
glBufferSubData(GL_ARRAY_BUFFER, 0, 24 * sizeof(float), points);
_dimRegionProgram->use();
_dimRegionProgram->setUniform("scaleWH", Math::Vector2d(1.f / _screenWidth, 1.f / _screenHeight));
_dimRegionProgram->setUniform("tex", 0);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDrawArrays(GL_TRIANGLES, 0, 6);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDeleteTextures(1, &texture);
}
void GfxOpenGLS::irisAroundRegion(int x1, int y1, int x2, int y2) {
_irisProgram->use();
_irisProgram->setUniform("color", Math::Vector3d(0.0f, 0.0f, 0.0f));
_irisProgram->setUniform("scaleWH", Math::Vector2d(1.f / _gameWidth, 1.f / _gameHeight));
float fx1 = x1;
float fx2 = x2;
float fy1 = y1;
float fy2 = y2;
float width = _screenWidth;
float height = _screenHeight;
float points[20] = {
0.0f, 0.0f,
0.0f, fy1,
width, 0.0f,
fx2, fy1,
width, height,
fx2, fy2,
0.0f, height,
fx1, fy2,
0.0f, fy1,
fx1, fy1
};
glBindBuffer(GL_ARRAY_BUFFER, _irisVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, 20 * sizeof(float), points);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 10);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
}
void GfxOpenGLS::drawEmergString(int x, int y, const char *text, const Color &fgColor) {
if (!*text)
return;
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, _emergTexture);
_emergProgram->use();
Math::Vector3d colors(float(fgColor.getRed()) / 255.0f,
float(fgColor.getGreen()) / 255.0f,
float(fgColor.getBlue()) / 255.0f);
_emergProgram->setUniform("color", colors);
_emergProgram->setUniform("sizeWH", Math::Vector2d(float(8) / _gameWidth, float(16) / _gameHeight));
_emergProgram->setUniform("texScale", Math::Vector2d(float(8) / 128, float(16) / 128));
for (; *text; ++text, x+=10) {
int blockcol = *text & 0xf;
int blockrow = *text / 16;
_emergProgram->setUniform("offsetXY", Math::Vector2d(float(x) / _gameWidth, float(y) / _gameHeight));
_emergProgram->setUniform("texOffsetXY", Math::Vector2d(float(blockcol * 8) / 128, float(blockrow * 16) / 128));
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
}
void GfxOpenGLS::loadEmergFont() {
uint8 *atlas = new uint8[128 * 128]();
for (int c = 32; c < 128; ++c) {
int blockrow = c / 16;
int blockcol = c & 0xf;
for (int row = 0; row < 13; ++row) {
int base = 128 * (16 * blockrow + row) + 8 * blockcol;
uint8 val = Font::emerFont[c - 32][row];
atlas[base + 0] = (val & 0x80) ? 255 : 0;
atlas[base + 1] = (val & 0x40) ? 255 : 0;
atlas[base + 2] = (val & 0x20) ? 255 : 0;
atlas[base + 3] = (val & 0x10) ? 255 : 0;
atlas[base + 4] = (val & 0x08) ? 255 : 0;
atlas[base + 5] = (val & 0x04) ? 255 : 0;
atlas[base + 6] = (val & 0x02) ? 255 : 0;
atlas[base + 7] = (val & 0x01) ? 255 : 0;
}
}
glGenTextures(1, &_emergTexture);
glBindTexture(GL_TEXTURE_2D, _emergTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 128, 128, 0, GL_ALPHA, GL_UNSIGNED_BYTE, atlas);
delete[] atlas;
}
void GfxOpenGLS::drawGenericPrimitive(const float *vertices, uint32 numVertices, const PrimitiveObject *primitive) {
const Color color(primitive->getColor());
const Math::Vector3d colorV =
Math::Vector3d(color.getRed(), color.getGreen(), color.getBlue()) / 255.f;
GLuint prim = nextPrimitive();
glBindBuffer(GL_ARRAY_BUFFER, prim);
glBufferSubData(GL_ARRAY_BUFFER, 0, numVertices * sizeof(float), vertices);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
_primitiveProgram->enableVertexAttribute("position", prim, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
_primitiveProgram->use(true);
_primitiveProgram->setUniform("color", colorV);
_primitiveProgram->setUniform("scaleWH", Math::Vector2d(1.f / _gameWidth, 1.f / _gameHeight));
switch (primitive->getType()) {
case PrimitiveObject::RectangleType:
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
break;
case PrimitiveObject::LineType:
glDrawArrays(GL_LINES, 0, 2);
break;
case PrimitiveObject::PolygonType:
glDrawArrays(GL_LINES, 0, 4);
break;
default:
// Impossible
break;
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
}
void GfxOpenGLS::drawRectangle(const PrimitiveObject *primitive) {
float x1 = primitive->getP1().x * _scaleW;
float y1 = primitive->getP1().y * _scaleH;
float x2 = primitive->getP2().x * _scaleW;
float y2 = primitive->getP2().y * _scaleH;
if (primitive->isFilled()) {
float data[] = { x1, y1, x2 + 1, y1, x1, y2 + 1, x2 + 1, y2 + 1 };
drawGenericPrimitive(data, 8, primitive);
} else {
float top[] = { x1, y1, x2 + 1, y1, x1, y1 + 1, x2 + 1, y1 + 1 };
float right[] = { x2, y1, x2 + 1, y1, x2, y2 + 1, x2 + 1, y2 + 1 };
float bottom[] = { x1, y2, x2 + 1, y2, x1, y2 + 1, x2 + 1, y2 + 1 };
float left[] = { x1, y1, x1 + 1, y1, x1, y2 + 1, x1 + 1, y2 + 1 };
drawGenericPrimitive(top, 8, primitive);
drawGenericPrimitive(right, 8, primitive);
drawGenericPrimitive(bottom, 8, primitive);
drawGenericPrimitive(left, 8, primitive);
}
}
void GfxOpenGLS::drawLine(const PrimitiveObject *primitive) {
float x1 = primitive->getP1().x * _scaleW;
float y1 = primitive->getP1().y * _scaleH;
float x2 = primitive->getP2().x * _scaleW;
float y2 = primitive->getP2().y * _scaleH;
float data[] = { x1, y1, x2, y2 };
drawGenericPrimitive(data, 4, primitive);
}
void GfxOpenGLS::drawPolygon(const PrimitiveObject *primitive) {
float x1 = primitive->getP1().x * _scaleW;
float y1 = primitive->getP1().y * _scaleH;
float x2 = primitive->getP2().x * _scaleW;
float y2 = primitive->getP2().y * _scaleH;
float x3 = primitive->getP3().x * _scaleW;
float y3 = primitive->getP3().y * _scaleH;
float x4 = primitive->getP4().x * _scaleW;
float y4 = primitive->getP4().y * _scaleH;
const float data[] = { x1, y1, x2 + 1, y2 + 1, x3, y3 + 1, x4 + 1, y4 };
drawGenericPrimitive(data, 8, primitive);
}
void GfxOpenGLS::prepareMovieFrame(Graphics::Surface* frame) {
int width = frame->w;
int height = frame->h;
const byte *bitmap = (const byte *)frame->getPixels();
GLenum frameType, frameFormat;
// GLES2 support is needed here, so:
// - frameFormat GL_BGRA is not supported, so use GL_RGBA
// - no format conversion, so same format is used for internal storage, so swizzle in shader
// - GL_UNSIGNED_INT_8_8_8_8[_REV] do not exist, so use _BYTE and fix
// endianness in shader.
if (frame->format == Graphics::PixelFormat(4, 8, 8, 8, 0, 8, 16, 24, 0) || frame->format == Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0)) {
// frame->format: GBRA
// read in little endian: {A, R, G, B}, swap: {B, G, R, A}, swizzle: {R, G, B, A}
// read in big endian: {B, G, R, A}, swizzle: {R, G, B, A}
frameType = GL_UNSIGNED_BYTE;
frameFormat = GL_RGBA;
_smushSwizzle = true;
#ifdef SCUMM_LITTLE_ENDIAN
_smushSwap = true;
#else
_smushSwap = false;
#endif
} else if (frame->format == Graphics::PixelFormat(4, 8, 8, 8, 0, 16, 8, 0, 0) || frame->format == Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24)) {
// frame->format: ARGB
// read in little endian: {B, G, R, A}, swizzle: {R, G, B, A}
// read in big endian: {A, R, G, B}, swap: {B, G, R, A}, swizzle: {R, G, B, A}
frameType = GL_UNSIGNED_BYTE;
frameFormat = GL_RGBA;
_smushSwizzle = true;
#ifdef SCUMM_LITTLE_ENDIAN
_smushSwap = false;
#else
_smushSwap = true;
#endif
} else if (frame->format == Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)) {
frameType = GL_UNSIGNED_SHORT_5_6_5;
frameFormat = GL_RGB;
_smushSwizzle = false;
_smushSwap = false;
} else {
error("Unknown pixelformat: Bpp: %d RBits: %d GBits: %d BBits: %d ABits: %d RShift: %d GShift: %d BShift: %d AShift: %d",
frame->format.bytesPerPixel,
-(frame->format.rLoss - 8),
-(frame->format.gLoss - 8),
-(frame->format.bLoss - 8),
-(frame->format.aLoss - 8),
frame->format.rShift,
frame->format.gShift,
frame->format.bShift,
frame->format.aShift);
}
// create texture
if (_smushTexId == 0) {
glGenTextures(1, &_smushTexId);
}
glBindTexture(GL_TEXTURE_2D, _smushTexId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, frameFormat, nextHigher2(width), nextHigher2(height), 0, frameFormat, frameType, nullptr);
glPixelStorei(GL_UNPACK_ALIGNMENT, frame->format.bytesPerPixel);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, frameFormat, frameType, bitmap);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
_smushWidth = (int)(width);
_smushHeight = (int)(height);
}
void GfxOpenGLS::drawMovieFrame(int offsetX, int offsetY) {
_smushProgram->use();
glDisable(GL_DEPTH_TEST);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadEBO);
_smushProgram->setUniform("texcrop", Math::Vector2d(float(_smushWidth) / nextHigher2(_smushWidth), float(_smushHeight) / nextHigher2(_smushHeight)));
_smushProgram->setUniform("scale", Math::Vector2d(float(_smushWidth)/ float(_gameWidth), float(_smushHeight) / float(_gameHeight)));
_smushProgram->setUniform("offset", Math::Vector2d(float(offsetX) / float(_gameWidth), float(offsetY) / float(_gameHeight)));
_smushProgram->setUniform("swap", _smushSwap);
_smushProgram->setUniform("swizzle", _smushSwizzle);
glBindTexture(GL_TEXTURE_2D, _smushTexId);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glEnable(GL_DEPTH_TEST);
}
void GfxOpenGLS::releaseMovieFrame() {
if (_smushTexId > 0) {
glDeleteTextures(1, &_smushTexId);
_smushTexId = 0;
}
}
const char *GfxOpenGLS::getVideoDeviceName() {
return "OpenGLS Renderer";
}
void GfxOpenGLS::renderBitmaps(bool render) {
}
void GfxOpenGLS::renderZBitmaps(bool render) {
}
void GfxOpenGLS::createEMIModel(EMIModel *model) {
EMIModelUserData *mud = new EMIModelUserData;
model->_userData = mud;
mud->_verticesVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 3 * sizeof(float), model->_vertices, GL_STREAM_DRAW);
mud->_normalsVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 3 * sizeof(float), model->_normals, GL_STREAM_DRAW);
mud->_texCoordsVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 2 * sizeof(float), model->_texVerts, GL_STATIC_DRAW);
mud->_colorMapVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 4 * sizeof(byte), model->_colorMap, GL_STATIC_DRAW);
OpenGL::Shader * actorShader = _actorProgram->clone();
actorShader->enableVertexAttribute("position", mud->_verticesVBO, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
actorShader->enableVertexAttribute("normal", mud->_normalsVBO, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
actorShader->enableVertexAttribute("texcoord", mud->_texCoordsVBO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
actorShader->enableVertexAttribute("color", mud->_colorMapVBO, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 * sizeof(byte), 0);
mud->_shader = actorShader;
actorShader = _actorLightsProgram->clone();
actorShader->enableVertexAttribute("position", mud->_verticesVBO, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
actorShader->enableVertexAttribute("normal", mud->_normalsVBO, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
actorShader->enableVertexAttribute("texcoord", mud->_texCoordsVBO, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
actorShader->enableVertexAttribute("color", mud->_colorMapVBO, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 * sizeof(byte), 0);
mud->_shaderLights = actorShader;
for (uint32 i = 0; i < model->_numFaces; ++i) {
EMIMeshFace * face = &model->_faces[i];
face->_indicesEBO = OpenGL::Shader::createBuffer(GL_ELEMENT_ARRAY_BUFFER, face->_faceLength * 3 * sizeof(uint16), face->_indexes, GL_STATIC_DRAW);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void GfxOpenGLS::destroyEMIModel(EMIModel *model) {
for (uint32 i = 0; i < model->_numFaces; ++i) {
EMIMeshFace *face = &model->_faces[i];
OpenGL::Shader::freeBuffer(face->_indicesEBO);
face->_indicesEBO = 0;
}
EMIModelUserData *mud = static_cast<EMIModelUserData *>(model->_userData);
if (mud) {
OpenGL::Shader::freeBuffer(mud->_verticesVBO);
OpenGL::Shader::freeBuffer(mud->_normalsVBO);
OpenGL::Shader::freeBuffer(mud->_texCoordsVBO);
OpenGL::Shader::freeBuffer(mud->_colorMapVBO);
delete mud->_shader;
delete mud;
}
model->_userData = nullptr;
}
void GfxOpenGLS::createMesh(Mesh *mesh) {
Common::Array<GrimVertex> meshInfo;
meshInfo.reserve(mesh->_numVertices * 5);
for (int i = 0; i < mesh->_numFaces; ++i) {
MeshFace *face = &mesh->_faces[i];
face->_userData = new uint32;
*(uint32 *)face->_userData = meshInfo.size();
if (face->getNumVertices() < 3)
continue;
#define VERT(j) (&mesh->_vertices[3 * face->getVertex(j)])
#define TEXVERT(j) (face->hasTexture() ? &mesh->_textureVerts[2 * face->getTextureVertex(j)] : zero_texVerts)
#define NORMAL(j) (&mesh->_vertNormals[3 * face->getVertex(j)])
for (int j = 2; j < face->getNumVertices(); ++j) {
meshInfo.push_back(GrimVertex(VERT(0), TEXVERT(0), NORMAL(0)));
meshInfo.push_back(GrimVertex(VERT(j - 1), TEXVERT(j - 1), NORMAL(j - 1)));
meshInfo.push_back(GrimVertex(VERT(j), TEXVERT(j), NORMAL(j)));
}
#undef VERT
#undef TEXVERT
#undef NORMAL
}
if (meshInfo.empty()) {
mesh->_userData = nullptr;
return;
}
ModelUserData *mud = new ModelUserData;
mesh->_userData = mud;
mud->_meshInfoVBO = OpenGL::Shader::createBuffer(GL_ARRAY_BUFFER, meshInfo.size() * sizeof(GrimVertex), &meshInfo[0], GL_STATIC_DRAW);
OpenGL::Shader *actorShader = _actorProgram->clone();
actorShader->enableVertexAttribute("position", mud->_meshInfoVBO, 3, GL_FLOAT, GL_FALSE, sizeof(GrimVertex), 0);
actorShader->enableVertexAttribute("texcoord", mud->_meshInfoVBO, 2, GL_FLOAT, GL_FALSE, sizeof(GrimVertex), 3 * sizeof(float));
actorShader->enableVertexAttribute("normal", mud->_meshInfoVBO, 3, GL_FLOAT, GL_FALSE, sizeof(GrimVertex), 5 * sizeof(float));
actorShader->disableVertexAttribute("color", Math::Vector4d(1.f, 1.f, 1.f, 1.f));
mud->_shader = actorShader;
actorShader = _actorLightsProgram->clone();
actorShader->enableVertexAttribute("position", mud->_meshInfoVBO, 3, GL_FLOAT, GL_FALSE, sizeof(GrimVertex), 0);
actorShader->enableVertexAttribute("texcoord", mud->_meshInfoVBO, 2, GL_FLOAT, GL_FALSE, sizeof(GrimVertex), 3 * sizeof(float));
actorShader->enableVertexAttribute("normal", mud->_meshInfoVBO, 3, GL_FLOAT, GL_FALSE, sizeof(GrimVertex), 5 * sizeof(float));
actorShader->disableVertexAttribute("color", Math::Vector4d(1.f, 1.f, 1.f, 1.f));
mud->_shaderLights = actorShader;
}
void GfxOpenGLS::destroyMesh(const Mesh *mesh) {
ModelUserData *mud = static_cast<ModelUserData *>(mesh->_userData);
for (int i = 0; i < mesh->_numFaces; ++i) {
MeshFace *face = &mesh->_faces[i];
if (face->_userData) {
uint32 *data = static_cast<uint32 *>(face->_userData);
delete data;
}
}
if (!mud)
return;
delete mud->_shader;
delete mud;
}
static void readPixels(int x, int y, int width, int height, byte *buffer) {
byte *p = buffer;
for (int i = y; i < y + height; i++) {
glReadPixels(x, 479 - i, width, 1, GL_RGBA, GL_UNSIGNED_BYTE, p);
p += width * 4;
}
}
Bitmap *GfxOpenGLS::getScreenshot(int w, int h, bool useStored) {
Graphics::Surface src;
src.create(_screenWidth, _screenHeight, Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24));
Bitmap *bmp;
if (useStored) {
if (OpenGLContext.type == OpenGL::kContextGLES2) {
GLuint frameBuffer;
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _storedDisplay, 0);
readPixels(0, 0, _screenWidth, _screenHeight, (uint8 *)src.getPixels());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &frameBuffer);
#if !USE_FORCED_GLES2
} else {
glBindTexture(GL_TEXTURE_2D, _storedDisplay);
char *buffer = new char[_screenWidth * _screenHeight * 4];
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
byte *rawBuf = (byte *)src.getPixels();
for (int i = 0; i < _screenHeight; i++) {
memcpy(&(rawBuf[(_screenHeight - i - 1) * _screenWidth * 4]), &buffer[4 * _screenWidth * i], _screenWidth * 4);
}
delete[] buffer;
#endif
}
} else {
readPixels(0, 0, _screenWidth, _screenHeight, (uint8 *)src.getPixels());
}
bmp = createScreenshotBitmap(&src, w, h, true);
src.free();
return bmp;
}
void GfxOpenGLS::createSpecialtyTextureFromScreen(uint id, uint8 *data, int x, int y, int width, int height) {
readPixels(x, y, width, height, data);
createSpecialtyTexture(id, data, width, height);
}
void GfxOpenGLS::setBlendMode(bool additive) {
if (additive) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
} else {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
}
}
#endif