From 45afd86a424bacdfd02a208f13b5a6718ed92842 Mon Sep 17 00:00:00 2001 From: Le Philousophe Date: Sat, 17 May 2025 16:44:08 +0200 Subject: [PATCH] BACKENDS: OPENGL: Add 3D rendering support --- backends/graphics/opengl/opengl-graphics.cpp | 219 +++++++-- backends/graphics/opengl/opengl-graphics.h | 13 + backends/graphics/opengl/renderer3d.cpp | 430 ++++++++++++++++++ backends/graphics/opengl/renderer3d.h | 96 ++++ .../graphics/openglsdl/openglsdl-graphics.cpp | 4 + backends/module.mk | 1 + 6 files changed, 733 insertions(+), 30 deletions(-) create mode 100644 backends/graphics/opengl/renderer3d.cpp create mode 100644 backends/graphics/opengl/renderer3d.h 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 \