Files
scummvm-cursorfix/backends/graphics/opengl/renderer3d.cpp
2026-02-02 04:50:13 +01:00

485 lines
14 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/>.
*
*/
#include "common/scummsys.h"
#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
#include "backends/graphics/opengl/renderer3d.h"
#include "backends/graphics/opengl/pipelines/pipeline.h"
#include "common/system.h"
#include "common/textconsole.h"
namespace OpenGL {
static void setupRenderbufferStorage(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height) {
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
if (samples > 1) {
glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
return;
}
#endif
glRenderbufferStorage(target, internalformat, width, height);
}
// This constructor must not depend on any existing GL context
Renderer3D::Renderer3D() :
_texture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, false),
_renderBuffers{0, 0, 0}, _frameBuffers{0, 0}, _renderToFrameBuffer(false),
_samples(0), _stackLevel(0), _inOverlay(false),
_pendingScreenChangeWidth(-1), _pendingScreenChangeHeight(-1) {
_texture.enableLinearFiltering(true);
_texture.setFlip(true);
}
void Renderer3D::destroy() {
while (_stackLevel) {
enter3D();
}
if (_frameBuffers[0]) {
// Check that we did allocated some framebuffer before destroying them
// This avoids to call glDeleteFramebuffers and glDeleteRenderbuffers
// on platforms not supporting it
glDeleteFramebuffers(ARRAYSIZE(_frameBuffers), _frameBuffers);
glDeleteRenderbuffers(ARRAYSIZE(_renderBuffers), _renderBuffers);
memset(_renderBuffers, 0, sizeof(_renderBuffers));
memset(_frameBuffers, 0, sizeof(_frameBuffers));
}
_texture.destroy();
}
void Renderer3D::initSize(uint w, uint h, int samples, bool renderToFrameBuffer) {
_samples = samples;
_renderToFrameBuffer = renderToFrameBuffer;
if (!renderToFrameBuffer) {
destroy();
_texture.setSize(0, 0);
return;
}
_texture.setSize(w, h);
recreate();
}
void Renderer3D::resize(uint w, uint h) {
assert(!_stackLevel);
if (!_renderToFrameBuffer) {
return;
}
if (_inOverlay) {
// While the (GUI) overlay is active, the game doesn't renders
// So, instead of loosing the contents of the FBO because of a resize,
// just delay it to when we close the GUI.
_pendingScreenChangeWidth = w;
_pendingScreenChangeHeight = h;
return;
}
_texture.setSize(w, h);
setup();
// Rebind the framebuffer
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
if (_frameBuffers[1]) {
// We are using multisampling
glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBuffers[1]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _frameBuffers[0]);
} else if (_frameBuffers[0])
#endif
{
// Draw on framebuffer if one was setup
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffers[0]);
}
}
void Renderer3D::recreate() {
destroy();
if (!_renderToFrameBuffer) {
// No framebuffer was requested
return;
}
// A 1x antialiasing is not an antialiasing
if (_samples > 1) {
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
if (!OpenGLContext.framebufferObjectMultisampleSupported) {
warning("The current OpenGL context does not support multisample framebuffer objects");
_samples = 0;
}
if (_samples > OpenGLContext.multisampleMaxSamples) {
warning("Requested anti-aliasing with '%d' samples, but the current OpenGL context supports '%d' samples at most",
_samples, OpenGLContext.multisampleMaxSamples);
_samples = OpenGLContext.multisampleMaxSamples;
}
#else
warning("multisample framebuffer objects support is not compiled in");
_samples = 0;
#endif
} else {
_samples = 0;
}
setup();
// Context got destroyed
_stackLevel = 0;
}
void Renderer3D::setup() {
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
const bool multiSample = _samples > 1;
#else
const bool multiSample = false;
#endif
const uint w = _texture.getLogicalWidth();
const uint h = _texture.getLogicalHeight();
if (!_texture.getGLTexture()) {
_texture.create();
}
glBindTexture(GL_TEXTURE_2D, 0);
if (!_frameBuffers[0]) {
glGenFramebuffers(multiSample ? 2 : 1, _frameBuffers);
glGenRenderbuffers(multiSample ? 3 : 2, _renderBuffers);
}
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffers[multiSample ? 1 : 0]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture.getGLTexture(), 0);
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
if (multiSample) {
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffers[0]);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffers[2]);
setupRenderbufferStorage(GL_RENDERBUFFER, _samples, GL_RGBA8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffers[2]);
}
#endif
#ifdef EMSCRIPTEN
// See https://www.khronos.org/registry/webgl/specs/latest/1.0/#FBO_ATTACHMENTS
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffers[0]);
setupRenderbufferStorage(GL_RENDERBUFFER, _samples, GL_DEPTH_STENCIL, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _renderBuffers[0]);
#else
if (OpenGLContext.packedDepthStencilSupported) {
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffers[0]);
setupRenderbufferStorage(GL_RENDERBUFFER, _samples, GL_DEPTH24_STENCIL8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffers[0]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _renderBuffers[0]);
} else {
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffers[0]);
setupRenderbufferStorage(GL_RENDERBUFFER, _samples, OpenGLContext.OESDepth24 ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffers[0]);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffers[1]);
setupRenderbufferStorage(GL_RENDERBUFFER, _samples, GL_STENCIL_INDEX8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _renderBuffers[1]);
}
#endif
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
error("Framebuffer is not complete! status: %d", status);
}
if (multiSample) {
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffers[1]);
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
error("Target framebuffer is not complete! status: %d", status);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Renderer3D::leave3D() {
#if !USE_FORCED_GLES2
if (OpenGLContext.type == kContextGL) {
// Save current state (only available on OpenGL)
glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT |
GL_LIGHTING_BIT | GL_PIXEL_MODE_BIT | GL_SCISSOR_BIT |
GL_TEXTURE_BIT | GL_TRANSFORM_BIT | GL_VIEWPORT_BIT);
glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT | GL_CLIENT_VERTEX_ARRAY_BIT);
// prepare view
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glMatrixMode(GL_TEXTURE);
glPushMatrix();
} else
#endif
{
// Save context by ourselves
#define CTX_STATE(gl_param) _save ## gl_param = glIsEnabled(gl_param)
#define CTX_BOOLEAN(gl_param) glGetBooleanv(gl_param, &_save ## gl_param)
#define CTX_INTEGER(gl_param, count) glGetIntegerv(gl_param, _save ## gl_param)
CTX_STATE(GL_BLEND);
CTX_STATE(GL_CULL_FACE);
CTX_STATE(GL_DEPTH_TEST);
CTX_STATE(GL_DITHER);
CTX_STATE(GL_POLYGON_OFFSET_FILL);
CTX_STATE(GL_SCISSOR_TEST);
CTX_STATE(GL_STENCIL_TEST);
CTX_BOOLEAN(GL_DEPTH_WRITEMASK);
CTX_INTEGER(GL_BLEND_SRC_RGB, 1);
CTX_INTEGER(GL_BLEND_DST_RGB, 1);
CTX_INTEGER(GL_BLEND_SRC_ALPHA, 1);
CTX_INTEGER(GL_BLEND_DST_ALPHA, 1);
CTX_INTEGER(GL_SCISSOR_BOX, 4);
CTX_INTEGER(GL_VIEWPORT, 4);
#undef CTX_INTEGER
#undef CTX_BOOLEAN
#undef CTX_STATE
}
_stackLevel++;
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
if (_frameBuffers[1]) {
// Frambuffer blit is impacted by scissor test, disable it
glDisable(GL_SCISSOR_TEST);
// We are using multisampling
glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBuffers[0]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _frameBuffers[1]);
const uint w = _texture.getLogicalWidth();
const uint h = _texture.getLogicalHeight();
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
} else if (_frameBuffers[0])
#endif
{
// Don't mess with the framebuffer if one was setup
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
void Renderer3D::enter3D() {
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
if (_frameBuffers[1]) {
// We are using multisampling
glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBuffers[1]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _frameBuffers[0]);
} else if (_frameBuffers[0])
#endif
{
// Draw on framebuffer if one was setup
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffers[0]);
}
glBindTexture(GL_TEXTURE_2D, 0);
Pipeline::disable();
if (_stackLevel) {
#if !USE_FORCED_GLES2
if (OpenGLContext.type == kContextGL) {
glMatrixMode(GL_TEXTURE);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopClientAttrib();
glPopAttrib();
} else
#endif
{
#define CTX_STATE(gl_param) _save ## gl_param ? glEnable(gl_param) : glDisable(gl_param)
CTX_STATE(GL_BLEND);
CTX_STATE(GL_CULL_FACE);
CTX_STATE(GL_DEPTH_TEST);
CTX_STATE(GL_DITHER);
CTX_STATE(GL_POLYGON_OFFSET_FILL);
CTX_STATE(GL_SCISSOR_TEST);
CTX_STATE(GL_STENCIL_TEST);
glDepthMask(_saveGL_DEPTH_WRITEMASK);
glBlendFuncSeparate(_saveGL_BLEND_SRC_RGB[0], _saveGL_BLEND_DST_RGB[0],
_saveGL_BLEND_SRC_ALPHA[0], _saveGL_BLEND_DST_ALPHA[0]);
glScissor(_saveGL_SCISSOR_BOX[0], _saveGL_SCISSOR_BOX[1],
_saveGL_SCISSOR_BOX[2], _saveGL_SCISSOR_BOX[3]);
glViewport(_saveGL_VIEWPORT[0], _saveGL_VIEWPORT[1],
_saveGL_VIEWPORT[2], _saveGL_VIEWPORT[3]);
#undef CTX_STATE
}
_stackLevel--;
} else {
// 3D engine just starts, make sure the state is clean
glDisable(GL_BLEND);
if (OpenGLContext.imagingSupported) {
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ZERO);
}
glDisable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glDisable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_DITHER);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(0.f, 0.f);
glDisable(GL_SCISSOR_TEST);
glScissor(0, 0, g_system->getWidth(), g_system->getHeight());
glDisable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 0, -1u);
glStencilMask(-1u);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glLineWidth(1.f);
glViewport(0, 0, g_system->getWidth(), g_system->getHeight());
#if !USE_FORCED_GLES2
if (OpenGLContext.type == kContextGL) {
glDisable(GL_ALPHA_TEST);
glAlphaFunc(GL_ALWAYS, 0);
glDisable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glDisable(GL_FOG);
glDisable(GL_LIGHTING);
glDisable(GL_LINE_SMOOTH);
glEnable(GL_MULTISAMPLE);
glDisable(GL_NORMALIZE);
glDisable(GL_POLYGON_OFFSET_FILL);
glDisable(GL_POLYGON_STIPPLE);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_TEXTURE_1D);
glDisable(GL_TEXTURE_2D);
glDisable(GL_TEXTURE_3D);
glDisable(GL_TEXTURE_CUBE_MAP);
glDisable(GL_TEXTURE_GEN_Q);
glDisable(GL_TEXTURE_GEN_R);
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_EDGE_FLAG_ARRAY);
glDisableClientState(GL_FOG_COORD_ARRAY);
glDisableClientState(GL_INDEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
// The others targets are not modified by engines
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_DONT_CARE);
glLogicOp(GL_COPY);
glPointSize(1.f);
glShadeModel(GL_SMOOTH);
}
#endif
}
}
void Renderer3D::presentBuffer() {
#if !USE_FORCED_GLES2 || defined(USE_GLAD)
if (!_frameBuffers[1]) {
// We are not using multisampling: contents are readily available
// The engine just has to read from the FBO or the backbuffer
return;
}
assert(_stackLevel == 0);
bool saveScissorTest = glIsEnabled(GL_SCISSOR_TEST);
// Frambuffer blit is impacted by scissor test, disable it
glDisable(GL_SCISSOR_TEST);
// Swap the framebuffers and blit
glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBuffers[0]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _frameBuffers[1]);
const uint w = _texture.getLogicalWidth();
const uint h = _texture.getLogicalHeight();
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// Put back things as they were before
glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBuffers[1]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _frameBuffers[0]);
saveScissorTest ? glEnable(GL_SCISSOR_TEST) : glDisable(GL_SCISSOR_TEST);
#endif
}
void Renderer3D::showOverlay(uint w, uint h) {
_inOverlay = true;
if (_frameBuffers[0]) {
// We have a framebuffer: the texture already contains an image
return;
}
_texture.create();
_texture.setSize(w, h);
Graphics::Surface background;
background.create(w, h, Texture::getRGBAPixelFormat());
glReadPixels(0, 0, background.w, background.h, GL_RGBA, GL_UNSIGNED_BYTE, background.getPixels());
_texture.updateArea(Common::Rect(w, h), background);
background.free();
}
void Renderer3D::hideOverlay() {
_inOverlay = false;
if (!_frameBuffers[0]) {
// We don't have a framebuffer: destroy the texture we used to store the background
_texture.destroy();
return;
}
// We have a framebuffer: resize the screen if we have a pending change
if (_pendingScreenChangeWidth >= 0 && _pendingScreenChangeHeight >= 0) {
resize(_pendingScreenChangeWidth, _pendingScreenChangeHeight);
_pendingScreenChangeWidth = -1;
_pendingScreenChangeHeight = -1;
}
}
} // End of namespace OpenGL
#endif