scummvm/engines/grim/gfx_opengl_shaders.cpp
Le Philousophe ed3e83a26d GRIM: Don't prevent Z-buffer update when bitmap has changed
The previous chack was essentialy based on data pointer being different
between successive updates.
Sometimes the memory allocator reuses the same memory space for two
different bitmaps and the z-buffer isn't updated.
2022-04-16 14:54:53 +02:00

2282 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::ShaderGL * shader;
uint32 characters;
Color color;
GLuint texture;
};
struct FontUserData {
int size;
GLuint texture;
};
struct EMIModelUserData {
OpenGL::ShaderGL *_shader;
OpenGL::ShaderGL *_shaderLights;
uint32 _texCoordsVBO;
uint32 _colorMapVBO;
uint32 _verticesVBO;
uint32 _normalsVBO;
};
struct ModelUserData {
OpenGL::ShaderGL *_shader;
OpenGL::ShaderGL *_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::ShaderGL::createBuffer(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW);
}
void GfxOpenGLS::setupTexturedQuad() {
_smushVBO = OpenGL::ShaderGL::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::ShaderGL::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::ShaderGL::fromFiles(isEMI ? "emi_background" : "grim_background", commonAttributes);
_smushProgram = OpenGL::ShaderGL::fromFiles("grim_smush", commonAttributes);
_textProgram = OpenGL::ShaderGL::fromFiles("grim_text", commonAttributes);
_emergProgram = OpenGL::ShaderGL::fromFiles("grim_emerg", commonAttributes);
static const char* actorAttributes[] = {"position", "texcoord", "color", "normal", nullptr};
_actorProgram = OpenGL::ShaderGL::fromFiles(isEMI ? "emi_actor" : "grim_actor", actorAttributes);
_actorLightsProgram = OpenGL::ShaderGL::fromFiles(isEMI ? "emi_actorlights" : "grim_actorlights", actorAttributes);
_spriteProgram = OpenGL::ShaderGL::fromFiles(isEMI ? "emi_sprite" : "grim_actor", actorAttributes);
static const char* primAttributes[] = { "position", nullptr };
_shadowPlaneProgram = OpenGL::ShaderGL::fromFiles("grim_shadowplane", primAttributes);
_primitiveProgram = OpenGL::ShaderGL::fromFiles("grim_primitive", primAttributes);
if (!isEMI) {
_irisProgram = _primitiveProgram->clone();
_dimProgram = OpenGL::ShaderGL::fromFiles("grim_dim", commonAttributes);
_dimRegionProgram = _dimProgram->clone();
} else {
_dimPlaneProgram = OpenGL::ShaderGL::fromFiles("emi_dimplane", primAttributes);
}
setupQuadEBO();
setupTexturedQuad();
setupTexturedCenteredQuad();
setupPrimitives();
if (!isEMI) {
_blastVBO = OpenGL::ShaderGL::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::ShaderGL *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::ShaderGL::createBuffer(GL_ARRAY_BUFFER, 3 * numVertices * sizeof(float), vertBuf, GL_STATIC_DRAW);
sud->_indicesVBO = OpenGL::ShaderGL::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::ShaderGL::freeBuffer(sud->_verticesVBO);
OpenGL::ShaderGL::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::ShaderGL *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::ShaderGL *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::ShaderGL *shader = _backgroundProgram->clone();
bitmap->_userData = shader;
if (g_grim->getGameType() == GType_MONKEY4) {
GLuint vbo = OpenGL::ShaderGL::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::ShaderGL *shader = (OpenGL::ShaderGL *)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::ShaderGL *shader = (OpenGL::ShaderGL *)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::ShaderGL *shader = (OpenGL::ShaderGL *)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::ShaderGL::createBuffer(GL_ARRAY_BUFFER, numCharacters * 16 * sizeof(float), bufData, GL_STATIC_DRAW);
}
OpenGL::ShaderGL * 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);
_textProgram->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::ShaderGL::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 3 * sizeof(float), model->_vertices, GL_STREAM_DRAW);
mud->_normalsVBO = OpenGL::ShaderGL::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 3 * sizeof(float), model->_normals, GL_STREAM_DRAW);
mud->_texCoordsVBO = OpenGL::ShaderGL::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 2 * sizeof(float), model->_texVerts, GL_STATIC_DRAW);
mud->_colorMapVBO = OpenGL::ShaderGL::createBuffer(GL_ARRAY_BUFFER, model->_numVertices * 4 * sizeof(byte), model->_colorMap, GL_STATIC_DRAW);
OpenGL::ShaderGL * 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::ShaderGL::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::ShaderGL::freeBuffer(face->_indicesEBO);
face->_indicesEBO = 0;
}
EMIModelUserData *mud = static_cast<EMIModelUserData *>(model->_userData);
if (mud) {
OpenGL::ShaderGL::freeBuffer(mud->_verticesVBO);
OpenGL::ShaderGL::freeBuffer(mud->_normalsVBO);
OpenGL::ShaderGL::freeBuffer(mud->_texCoordsVBO);
OpenGL::ShaderGL::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::ShaderGL::createBuffer(GL_ARRAY_BUFFER, meshInfo.size() * sizeof(GrimVertex), &meshInfo[0], GL_STATIC_DRAW);
OpenGL::ShaderGL *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::kOGLContextGLES2) {
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