diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index d1efb1e2cc8..d08167c3258 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -66,6 +66,10 @@
#include "backends/graphics/opengl/pipelines/libretro/parser.h"
#endif
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+#include "backends/graphics/opengl/renderer3d.h"
+#endif
+
namespace OpenGL {
OpenGLGraphicsManager::OpenGLGraphicsManager()
@@ -80,6 +84,9 @@ OpenGLGraphicsManager::OpenGLGraphicsManager()
#if !USE_FORCED_GLES
, _libretroPipeline(nullptr)
#endif
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ , _renderer3d(nullptr)
+#endif
#ifdef USE_OSD
, _osdMessageChangeRequest(false), _osdMessageAlpha(0), _osdMessageFadeStartTime(0), _osdMessageSurface(nullptr),
_osdIconSurface(nullptr)
@@ -94,6 +101,9 @@ OpenGLGraphicsManager::OpenGLGraphicsManager()
OpenGLGraphicsManager::~OpenGLGraphicsManager() {
delete _gameScreen;
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ delete _renderer3d;
+#endif
delete _overlay;
delete _cursor;
delete _cursorMask;
@@ -121,6 +131,15 @@ bool OpenGLGraphicsManager::hasFeature(OSystem::Feature f) const {
case OSystem::kFeatureScalers:
#endif
return true;
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ case OSystem::kFeatureOpenGLForGame:
+ // No 3D for GLES
+ return OpenGLContext.type != kContextGLES;
+#endif
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ case OSystem::kFeatureShadersForGame:
+ return OpenGLContext.enginesShadersSupported;
+#endif
#if !USE_FORCED_GLES
case OSystem::kFeatureShaders:
@@ -193,9 +212,17 @@ int OpenGLGraphicsManager::getDefaultGraphicsMode() const {
bool OpenGLGraphicsManager::setGraphicsMode(int mode, uint flags) {
assert(_transactionMode != kTransactionNone);
+ if (flags & OSystem::kGfxModeRender3d) {
+ // In 3D, mode is not used
+ _currentState.graphicsMode = GFX_OPENGL;
+ _currentState.flags = flags;
+ return true;
+ }
+
switch (mode) {
case GFX_OPENGL:
_currentState.graphicsMode = mode;
+ _currentState.flags = flags;
return true;
default:
@@ -390,7 +417,8 @@ OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() {
bool setupNewGameScreen = false;
if ( _oldState.gameWidth != _currentState.gameWidth
- || _oldState.gameHeight != _currentState.gameHeight) {
+ || _oldState.gameHeight != _currentState.gameHeight
+ || _oldState.flags != _currentState.flags) {
setupNewGameScreen = true;
}
@@ -415,6 +443,36 @@ OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() {
}
#endif
+ const bool render3d = _currentState.flags & OSystem::kGfxModeRender3d;
+ assert(!render3d || OpenGLContext.type != kContextGLES);
+ const int antialiasing = render3d ? ConfMan.getInt("antialiasing") : 0;
+
+ // 2D engines always render on a texture that can be stretched
+ const bool engineSupportsArbitraryResolutions = !g_engine || !render3d || g_engine->hasFeature(Engine::kSupportsArbitraryResolutions);
+ // Prefer to render on framebuffer if antialiasing is requested: this avoids recreating the window
+ // This is only needed for 3D rendering
+ const bool renderToFrameBuffer = render3d && (!engineSupportsArbitraryResolutions
+#if !USE_FORCED_GLES
+ || _libretroPipeline
+#endif
+ || (antialiasing && OpenGLContext.framebufferObjectMultisampleSupported)) &&
+ OpenGLContext.framebufferObjectSupported;
+
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ // Create the 3D renderer first to allow the platform specific code know we are in 3D
+ // The creation doesn't depend on any context existing
+ if (render3d && !_renderer3d) {
+ _renderer3d = new Renderer3D();
+ } else if (!render3d) {
+ delete _renderer3d;
+ _renderer3d = nullptr;
+ }
+#else
+ if (render3d) {
+ error("3D rendering is not available");
+ }
+#endif
+
do {
const uint desiredAspect = getDesiredGameAspectRatio();
const uint requestedWidth = _currentState.gameWidth;
@@ -430,7 +488,8 @@ OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() {
#else
Graphics::PixelFormat::createFormatCLUT8(),
#endif
- true, 0)
+ renderToFrameBuffer || engineSupportsArbitraryResolutions,
+ renderToFrameBuffer ? 0 : antialiasing)
|| !(shaderOK = loadShader(_currentState.shader))
// HACK: This is really nasty but we don't have any guarantees of
// a context existing before, which means we don't know the maximum
@@ -507,35 +566,42 @@ OSystem::TransactionError OpenGLGraphicsManager::endGFXTransaction() {
delete _gameScreen;
_gameScreen = nullptr;
- bool wantScaler = _currentState.scaleFactor > 1;
+ if (render3d) {
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ _renderer3d->initSize(_currentState.gameWidth, _currentState.gameHeight, antialiasing, renderToFrameBuffer);
+ _renderer3d->enter3D();
+#endif
+ } else {
+ const bool wantScaler = _currentState.scaleFactor > 1;
#ifdef USE_RGB_COLOR
- _gameScreen = createSurface(_currentState.gameFormat, false, wantScaler);
+ _gameScreen = createSurface(_currentState.gameFormat, false, wantScaler);
#else
- _gameScreen = createSurface(Graphics::PixelFormat::createFormatCLUT8(), false, wantScaler);
+ _gameScreen = createSurface(Graphics::PixelFormat::createFormatCLUT8(), false, wantScaler);
#endif
- assert(_gameScreen);
- if (_gameScreen->hasPalette()) {
- _gameScreen->setPalette(0, 256, _gamePalette);
- }
+ assert(_gameScreen);
+ if (_gameScreen->hasPalette()) {
+ _gameScreen->setPalette(0, 256, _gamePalette);
+ }
#ifdef USE_SCALERS
- if (wantScaler) {
- _gameScreen->setScaler(_currentState.scalerIndex, _currentState.scaleFactor);
- }
+ if (wantScaler) {
+ _gameScreen->setScaler(_currentState.scalerIndex, _currentState.scaleFactor);
+ }
#endif
- _gameScreen->allocate(_currentState.gameWidth, _currentState.gameHeight);
- // We fill the screen to all black or index 0 for CLUT8.
+ _gameScreen->allocate(_currentState.gameWidth, _currentState.gameHeight);
+ // We fill the screen to all black or index 0 for CLUT8.
#ifdef USE_RGB_COLOR
- if (_currentState.gameFormat.bytesPerPixel == 1) {
- _gameScreen->fill(0);
- } else {
- _gameScreen->fill(_gameScreen->getSurface()->format.RGBToColor(0, 0, 0));
- }
+ if (_currentState.gameFormat.bytesPerPixel == 1) {
+ _gameScreen->fill(0);
+ } else {
+ _gameScreen->fill(_gameScreen->getSurface()->format.RGBToColor(0, 0, 0));
+ }
#else
- _gameScreen->fill(0);
+ _gameScreen->fill(0);
#endif
+ }
}
// Update our display area and cursor scaling. This makes sure we pick up
@@ -582,14 +648,17 @@ int16 OpenGLGraphicsManager::getHeight() const {
}
void OpenGLGraphicsManager::copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) {
+ assert(_gameScreen);
_gameScreen->copyRectToTexture(x, y, w, h, buf, pitch);
}
void OpenGLGraphicsManager::fillScreen(uint32 col) {
+ assert(_gameScreen);
_gameScreen->fill(col);
}
void OpenGLGraphicsManager::fillScreen(const Common::Rect &r, uint32 col) {
+ assert(_gameScreen);
_gameScreen->fill(r, col);
}
@@ -638,7 +707,11 @@ void OpenGLGraphicsManager::updateScreen() {
rotatedHeight = _windowWidth;
}
- if (!_gameScreen || !_pipeline) {
+ if ((!_gameScreen
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ && !_renderer3d
+#endif
+ ) || !_pipeline) {
return;
}
@@ -659,8 +732,11 @@ void OpenGLGraphicsManager::updateScreen() {
// We only update the screen when there actually have been any changes.
if ( !_forceRedraw
- && !_cursorNeedsRedraw
- && !_gameScreen->isDirty()
+ && !_cursorNeedsRedraw
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ && !_renderer3d
+#endif
+ && !_gameScreen->isDirty() // if _renderer3d is nullptr, _gameScreen is not null
#if !USE_FORCED_GLES
&& !(_libretroPipeline && _libretroPipeline->isAnimated())
#endif
@@ -673,8 +749,17 @@ void OpenGLGraphicsManager::updateScreen() {
return;
}
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (_renderer3d) {
+ _renderer3d->leave3D();
+ }
+#endif
+
// Update changes to textures.
- _gameScreen->updateGLTexture();
+ if (_gameScreen) {
+ _gameScreen->updateGLTexture();
+ }
+
if (_cursorVisible && _cursor) {
_cursor->updateGLTexture();
}
@@ -691,8 +776,13 @@ void OpenGLGraphicsManager::updateScreen() {
_pipeline->activate();
- // Clear the screen buffer.
- GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
+ // Clear the screen buffer (when the engine has not drawn on it directly).
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (!_renderer3d || _renderer3d->hasTexture())
+#endif
+ {
+ GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
+ }
if (!_overlayVisible) {
// The scissor test is enabled to:
@@ -708,7 +798,14 @@ void OpenGLGraphicsManager::updateScreen() {
_targetBuffer->enableBlend(Framebuffer::kBlendModeOpaque);
// First step: Draw the (virtual) game screen.
- _pipeline->drawTexture(_gameScreen->getGLTexture(), _gameDrawRect.left, _gameDrawRect.top, _gameDrawRect.width(), _gameDrawRect.height());
+ if (_gameScreen) {
+ _pipeline->drawTexture(_gameScreen->getGLTexture(), _gameDrawRect.left, _gameDrawRect.top, _gameDrawRect.width(), _gameDrawRect.height());
+ }
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ else if (_renderer3d && _renderer3d->hasTexture()) {
+ _pipeline->drawTexture(_renderer3d->getGLTexture(), _gameDrawRect.left, _gameDrawRect.top, _gameDrawRect.width(), _gameDrawRect.height());
+ }
+#endif
// Second step: Draw the cursor if necessary and we are not in GUI and it
#if !USE_FORCED_GLES
@@ -800,13 +897,21 @@ void OpenGLGraphicsManager::updateScreen() {
_cursorNeedsRedraw = false;
_forceRedraw = false;
refreshScreen();
+
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (_renderer3d) {
+ _renderer3d->enter3D();
+ }
+#endif
}
Graphics::Surface *OpenGLGraphicsManager::lockScreen() {
+ assert(_gameScreen);
return _gameScreen->getSurface();
}
void OpenGLGraphicsManager::unlockScreen() {
+ assert(_gameScreen);
_gameScreen->flagDirty();
}
@@ -832,6 +937,24 @@ int16 OpenGLGraphicsManager::getOverlayHeight() const {
}
}
+void OpenGLGraphicsManager::showOverlay(bool inGUI) {
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (!_overlayVisible && _renderer3d) {
+ _renderer3d->showOverlay(getOverlayWidth(), getOverlayHeight());
+ }
+#endif
+ WindowedGraphicsManager::showOverlay(inGUI);
+}
+
+void OpenGLGraphicsManager::hideOverlay() {
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (_overlayVisible && _renderer3d) {
+ _renderer3d->hideOverlay();
+ }
+#endif
+ WindowedGraphicsManager::hideOverlay();
+}
+
Graphics::PixelFormat OpenGLGraphicsManager::getOverlayFormat() const {
return _overlay->getFormat();
}
@@ -1251,6 +1374,7 @@ void OpenGLGraphicsManager::displayActivityIconOnOSD(const Graphics::Surface *ic
}
void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) {
+ assert(_gameScreen);
assert(_gameScreen->hasPalette());
memcpy(_gamePalette + start * 3, colors, num * 3);
@@ -1261,6 +1385,7 @@ void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num)
}
void OpenGLGraphicsManager::grabPalette(byte *colors, uint start, uint num) const {
+ assert(_gameScreen);
assert(_gameScreen->hasPalette());
memcpy(colors, _gamePalette + start * 3, num * 3);
@@ -1403,6 +1528,12 @@ void OpenGLGraphicsManager::notifyContextCreate(ContextType type,
_gameScreen->recreate();
}
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (_renderer3d) {
+ _renderer3d->recreate();
+ }
+#endif
+
if (_overlay) {
_overlay->recreate();
}
@@ -1431,6 +1562,12 @@ void OpenGLGraphicsManager::notifyContextDestroy() {
_gameScreen->destroy();
}
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (_renderer3d) {
+ _renderer3d->destroy();
+ }
+#endif
+
if (_overlay) {
_overlay->destroy();
}
@@ -1632,15 +1769,32 @@ int OpenGLGraphicsManager::getGameRenderScale() const {
}
void OpenGLGraphicsManager::recalculateDisplayAreas() {
- if (!_gameScreen) {
+ if (!_gameScreen
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ && !_renderer3d
+#endif
+ ) {
return;
}
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ const bool engineSupportsArbitraryResolutions = !g_engine || g_engine->hasFeature(Engine::kSupportsArbitraryResolutions);
+ if (_renderer3d && engineSupportsArbitraryResolutions) {
+ _currentState.gameWidth = getOverlayWidth();
+ _currentState.gameHeight = getOverlayHeight();
+ _renderer3d->resize(_currentState.gameWidth, _currentState.gameHeight);
+ }
+#endif
+
WindowedGraphicsManager::recalculateDisplayAreas();
#if !USE_FORCED_GLES
if (_libretroPipeline) {
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ const Texture &gameScreenTexture = _gameScreen ? _gameScreen->getGLTexture() : _renderer3d->getGLTexture();
+#else
const Texture &gameScreenTexture = _gameScreen->getGLTexture();
+#endif
_libretroPipeline->setDisplaySizes(gameScreenTexture.getLogicalWidth(), gameScreenTexture.getLogicalHeight(),
_gameDrawRect);
}
@@ -1691,7 +1845,11 @@ void OpenGLGraphicsManager::updateCursorPalette() {
}
void OpenGLGraphicsManager::recalculateCursorScaling() {
- if (!_cursor || !_gameScreen) {
+ if (!_cursor || (!_gameScreen
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ && !_renderer3d
+#endif
+ )) {
return;
}
@@ -1706,7 +1864,8 @@ void OpenGLGraphicsManager::recalculateCursorScaling() {
// In case scaling is actually enabled we will scale the cursor according
// to the game screen.
- if (!_cursorDontScale) {
+ // In 3D mode, there is no scaling
+ if (!_cursorDontScale && _gameScreen) {
const frac_t screenScaleFactorX = intToFrac(_gameDrawRect.width()) / _gameScreen->getWidth();
const frac_t screenScaleFactorY = intToFrac(_gameDrawRect.height()) / _gameScreen->getHeight();
diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h
index 39110f3aa96..aec17af63f5 100644
--- a/backends/graphics/opengl/opengl-graphics.h
+++ b/backends/graphics/opengl/opengl-graphics.h
@@ -49,6 +49,9 @@ class Pipeline;
#if !USE_FORCED_GLES
class LibRetroPipeline;
#endif
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+class Renderer3D;
+#endif
enum {
GFX_OPENGL = 0
@@ -115,6 +118,8 @@ public:
int16 getOverlayWidth() const override;
int16 getOverlayHeight() const override;
+ void showOverlay(bool inGUI) override;
+ void hideOverlay() override;
Graphics::PixelFormat getOverlayFormat() const override;
@@ -196,6 +201,7 @@ protected:
#endif
bool aspectRatioCorrection;
int graphicsMode;
+ uint flags;
bool filtering;
uint scalerIndex;
@@ -354,6 +360,13 @@ protected:
*/
Surface *_gameScreen;
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ /**
+ * The rendering helper for 3D games.
+ */
+ Renderer3D *_renderer3d;
+#endif
+
/**
* The game palette if in CLUT8 mode.
*/
diff --git a/backends/graphics/opengl/renderer3d.cpp b/backends/graphics/opengl/renderer3d.cpp
new file mode 100644
index 00000000000..4a75574e63d
--- /dev/null
+++ b/backends/graphics/opengl/renderer3d.cpp
@@ -0,0 +1,430 @@
+/* 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 .
+ *
+ */
+
+#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);
+}
+
+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, true);
+ 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, true);
+ setup();
+
+ // Rebind the framebuffer
+ if (_frameBuffers[1]) {
+ // We are using multisampling
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBuffers[1]);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _frameBuffers[0]);
+ } else if (_frameBuffers[0]) {
+ // 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() {
+ const bool multiSample = _samples > 1;
+ 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 (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]);
+ }
+
+#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 (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 {
+ // 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 (_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]) {
+ // Don't mess with the framebuffer if one was setup
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+}
+
+void Renderer3D::enter3D() {
+ if (_frameBuffers[1]) {
+ // We are using multisampling
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBuffers[1]);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _frameBuffers[0]);
+ } else if (_frameBuffers[0]) {
+ // Draw on framebuffer if one was setup
+ glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffers[0]);
+ }
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ Pipeline::disable();
+
+ if (_stackLevel) {
+ if (OpenGLContext.type == kContextGL) {
+ glMatrixMode(GL_TEXTURE);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glPopClientAttrib();
+ glPopAttrib();
+ } else {
+#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);
+ 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 (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);
+ }
+ }
+}
+
+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, true);
+ 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
diff --git a/backends/graphics/opengl/renderer3d.h b/backends/graphics/opengl/renderer3d.h
new file mode 100644
index 00000000000..df8491b8832
--- /dev/null
+++ b/backends/graphics/opengl/renderer3d.h
@@ -0,0 +1,96 @@
+/* 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 .
+ *
+ */
+
+#ifndef BACKENDS_GRAPHICS_OPENGL_RENDERER3D_H
+#define BACKENDS_GRAPHICS_OPENGL_RENDERER3D_H
+
+#include "common/scummsys.h"
+
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+
+#include "graphics/opengl/texture.h"
+
+namespace OpenGL {
+
+class Renderer3D {
+public:
+ Renderer3D();
+ ~Renderer3D() { destroy(); }
+
+ void initSize(uint w, uint h, int samples, bool renderToFrameBuffer);
+ void resize(uint w, uint h);
+ void recreate();
+ void destroy();
+
+ void leave3D();
+ void enter3D();
+
+ void showOverlay(uint w, uint h);
+ void hideOverlay();
+
+ const Texture &getGLTexture() const { return _texture; }
+ bool hasTexture() const { return _texture.getGLTexture() != 0; }
+
+protected:
+ void setup();
+
+ int _stackLevel;
+ bool _inOverlay;
+ int _pendingScreenChangeWidth;
+ int _pendingScreenChangeHeight;
+
+ bool _renderToFrameBuffer;
+ int _samples;
+ Texture _texture;
+ GLuint _renderBuffers[3];
+ GLuint _frameBuffers[2];
+
+#define CTX_STATE(gl_param) GLboolean _save ## gl_param
+#define CTX_BOOLEAN(gl_param) GLboolean _save ## gl_param
+#define CTX_INTEGER(gl_param, count) GLint _save ## gl_param[count]
+
+ 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
+};
+
+} // End of namespace OpenGL
+
+#endif // defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+
+#endif
diff --git a/backends/graphics/openglsdl/openglsdl-graphics.cpp b/backends/graphics/openglsdl/openglsdl-graphics.cpp
index d6a3e3474ab..784efffd9e3 100644
--- a/backends/graphics/openglsdl/openglsdl-graphics.cpp
+++ b/backends/graphics/openglsdl/openglsdl-graphics.cpp
@@ -789,7 +789,11 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
height = _desiredFullscreenHeight;
flags |= SDL_FULLSCREEN;
}
+#if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS)
+ if (_resizable && !_renderer3d) {
+#else
if (_resizable) {
+#endif
// In SDL1.2, resizing the window may invalidate the context
// This would kill all the engine objects
flags |= SDL_RESIZABLE;
diff --git a/backends/module.mk b/backends/module.mk
index ca6a5dd4c0c..e624395c3f9 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -176,6 +176,7 @@ ifdef USE_OPENGL
MODULE_OBJS += \
graphics/opengl/framebuffer.o \
graphics/opengl/opengl-graphics.o \
+ graphics/opengl/renderer3d.o \
graphics/opengl/shader.o \
graphics/opengl/texture.o \
graphics/opengl/pipelines/clut8.o \