diff options
author | Michael Lutz <michi@icosahedron.de> | 2021-01-16 16:43:04 +0100 |
---|---|---|
committer | Michael Lutz <michi@icosahedron.de> | 2021-02-22 22:16:07 +0100 |
commit | ef478ade649add1ab1706370ff53bcb0c32798d1 (patch) | |
tree | bf39b20030319377edec1b654fe4e8c7ba4594a3 /src/video | |
parent | af4d32357cc4cea6878c61c366378477e62915d0 (diff) | |
download | openttd-ef478ade649add1ab1706370ff53bcb0c32798d1.tar.xz |
Add: [Win32] Video driver that uses OpenGL to transfer the video buffer to the screen.
Diffstat (limited to 'src/video')
-rw-r--r-- | src/video/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/video/opengl.cpp | 222 | ||||
-rw-r--r-- | src/video/opengl.h | 49 | ||||
-rw-r--r-- | src/video/win32_v.cpp | 153 | ||||
-rw-r--r-- | src/video/win32_v.h | 48 |
5 files changed, 475 insertions, 3 deletions
diff --git a/src/video/CMakeLists.txt b/src/video/CMakeLists.txt index 02b65d4be..f0f31dac4 100644 --- a/src/video/CMakeLists.txt +++ b/src/video/CMakeLists.txt @@ -8,6 +8,12 @@ if(NOT OPTION_DEDICATED) ) add_files( + opengl.cpp + opengl.h + CONDITION OPENGL_FOUND + ) + + add_files( sdl_v.cpp sdl_v.h CONDITION SDL_FOUND diff --git a/src/video/opengl.cpp b/src/video/opengl.cpp new file mode 100644 index 000000000..4d759e60f --- /dev/null +++ b/src/video/opengl.cpp @@ -0,0 +1,222 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>. + */ + +/** @file opengl_v.cpp OpenGL video driver support. */ + +#include "../stdafx.h" + +#if defined(_WIN32) +# include <windows.h> +#endif + +#if defined(__APPLE__) +# include <OpenGL/gl3.h> +#else +# include <GL/gl.h> +#endif +#include "../3rdparty/opengl/glext.h" + +#include "opengl.h" +#include "../core/mem_func.hpp" +#include "../gfx_func.h" +#include "../debug.h" + +#include "../safeguards.h" + + +/* static */ OpenGLBackend *OpenGLBackend::instance = nullptr; + +/** + * Find a substring in a string made of space delimited elements. The substring + * has to match the complete element, partial matches don't count. + * @param string List of space delimited elements. + * @param substring Substring to find. + * @return Pointer to the start of the match or nullptr if the substring is not present. + */ +static const char *FindStringInExtensionList(const char *string, const char *substring) +{ + while (1) { + /* Is the extension string present at all? */ + const char *pos = strstr(string, substring); + if (pos == nullptr) break; + + /* Is this a real match, i.e. are the chars before and after the matched string + * indeed spaces (or the start or end of the string, respectively)? */ + const char *end = pos + strlen(substring); + if ((pos == string || pos[-1] == ' ') && (*end == ' ' || *end == '\0')) return pos; + + /* False hit, try again for the remaining string. */ + string = end; + } + + return nullptr; +} + +/** + * Check if an OpenGL extension is supported by the current context. + * @param extension The extension string to test. + * @return True if the extension is supported, false if not. + */ +static bool IsOpenGLExtensionSupported(const char *extension) +{ + return FindStringInExtensionList((const char *)glGetString(GL_EXTENSIONS), extension) != nullptr; +} + +static byte _gl_major_ver = 0; ///< Major OpenGL version. +static byte _gl_minor_ver = 0; ///< Minor OpenGL version. + +/** + * Check if the current OpenGL version is equal or higher than a given one. + * @param major Minimal major version. + * @param minor Minimal minor version. + * @pre OpenGL was initialized. + * @return True if the OpenGL version is equal or higher than the requested one. + */ +static bool IsOpenGLVersionAtLeast(byte major, byte minor) +{ + return (_gl_major_ver > major) || (_gl_major_ver == major && _gl_minor_ver >= minor); +} + + +/** + * Create and initialize the singleton back-end class. + */ +/* static */ const char *OpenGLBackend::Create() +{ + if (OpenGLBackend::instance != nullptr) OpenGLBackend::Destroy(); + + OpenGLBackend::instance = new OpenGLBackend(); + return OpenGLBackend::instance->Init(); +} + +/** + * Free resources and destroy singleton back-end class. + */ +/* static */ void OpenGLBackend::Destroy() +{ + delete OpenGLBackend::instance; + OpenGLBackend::instance = nullptr; +} + +/** + * Construct OpenGL back-end class. + */ +OpenGLBackend::OpenGLBackend() +{ +} + +/** + * Free allocated resources. + */ +OpenGLBackend::~OpenGLBackend() +{ + glDeleteTextures(1, &this->vid_texture); + free(this->vid_buffer); +} + +/** + * Check for the needed OpenGL functionality and allocate all resources. + * @return Error string or nullptr if successful. + */ +const char *OpenGLBackend::Init() +{ + /* Always query the supported OpenGL version as the current context might have changed. */ + const char *ver = (const char *)glGetString(GL_VERSION); + const char *vend = (const char *)glGetString(GL_VENDOR); + const char *renderer = (const char *)glGetString(GL_RENDERER); + + if (ver == nullptr || vend == nullptr || renderer == nullptr) return "OpenGL not supported"; + + DEBUG(driver, 1, "OpenGL driver: %s - %s (%s)", vend, renderer, ver); + + const char *minor = strchr(ver, '.'); + _gl_major_ver = atoi(ver); + _gl_minor_ver = minor != nullptr ? atoi(minor + 1) : 0; + + /* OpenGL 1.3 is the absolute minimum. */ + if (!IsOpenGLVersionAtLeast(1, 3)) return "OpenGL version >= 1.3 required"; + /* Check for non-power-of-two texture support. */ + if (!IsOpenGLVersionAtLeast(2, 0) && !IsOpenGLExtensionSupported("GL_ARB_texture_non_power_of_two")) return "Non-power-of-two textures not supported"; + + /* Setup video buffer texture. */ + glGenTextures(1, &this->vid_texture); + glBindTexture(GL_TEXTURE_2D, this->vid_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + if (glGetError() != GL_NO_ERROR) return "Can't generate video buffer texture"; + + /* Prime vertex array with a full-screen quad. */ + static const float vert_array[] = { 1.f, -1.f, 1.f, 1.f, -1.f, -1.f, -1.f, 1.f }; + static const float tex_array[] = { 1.f, 1.f, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f }; + + glVertexPointer(2, GL_FLOAT, 0, vert_array); + glTexCoordPointer(2, GL_FLOAT, 0, tex_array); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + (void)glGetError(); // Clear errors. + + return nullptr; +} + +/** + * Change the size of the drawing window and allocate matching resources. + * @param w New width of the window. + * @param h New height of the window. + * @param force Recreate resources even if size didn't change. + * @param False if nothing had to be done, true otherwise. + */ +bool OpenGLBackend::Resize(int w, int h, bool force) +{ + if (!force && _screen.width == w && _screen.height == h && this->vid_buffer != nullptr) return false; + + glViewport(0, 0, w, h); + + /* Re-allocate video buffer texture. */ + free(this->vid_buffer); + this->vid_buffer = CallocT<uint32>(w * h); // 32bpp + + glBindTexture(GL_TEXTURE_2D, this->vid_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + + /* Set new viewport. */ + _screen.height = h; + _screen.width = w; + _screen.pitch = w; + _screen.dst_ptr = this->GetVideoBuffer(); + + return true; +} + +/** + * Render video buffer to the screen. + */ +void OpenGLBackend::Paint() +{ + assert(this->vid_buffer != nullptr); + + glClear(GL_COLOR_BUFFER_BIT); + + /* Update changed rect of the video buffer texture. */ + glBindTexture(GL_TEXTURE_2D, this->vid_texture); + glPixelStorei(GL_UNPACK_ROW_LENGTH, _screen.pitch); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _screen.width, _screen.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, this->vid_buffer); + + /* Blit video buffer to screen. */ + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + diff --git a/src/video/opengl.h b/src/video/opengl.h new file mode 100644 index 000000000..9db1be5f5 --- /dev/null +++ b/src/video/opengl.h @@ -0,0 +1,49 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD 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, version 2. + * OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>. + */ + +/** @file opengl.h OpenGL video driver support. */ + +#ifndef VIDEO_OPENGL_H +#define VIDEO_OPENGL_H + +#include "../core/alloc_type.hpp" + +/** Platform-independent back-end singleton class for OpenGL video drivers. */ +class OpenGLBackend : public ZeroedMemoryAllocator { +private: + static OpenGLBackend *instance; ///< Singleton instance pointer. + + void *vid_buffer; ///< Pointer to the memory used for the video driver to draw to. + GLuint vid_texture; ///< Texture handle for the video buffer texture. + + OpenGLBackend(); + ~OpenGLBackend(); + + const char *Init(); + +public: + /** Get singleton instance of this class. */ + static inline OpenGLBackend *Get() + { + return OpenGLBackend::instance; + } + static const char *Create(); + static void Destroy(); + + bool Resize(int w, int h, bool force = false); + void Paint(); + + /** + * Get a pointer to the memory for the video driver to draw to. + * @return Pointer to draw on. + */ + void *GetVideoBuffer() { return this->vid_buffer; } +}; + +#endif /* VIDEO_OPENGL_H */ diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp index 7ebac069d..c97dccf44 100644 --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -1114,6 +1114,7 @@ float VideoDriver_Win32Base::GetDPIScale() bool VideoDriver_Win32Base::LockVideoBuffer() { if (_draw_threaded) this->draw_lock.lock(); + _screen.dst_ptr = _wnd.buffer_bits; return true; } @@ -1319,3 +1320,155 @@ void VideoDriver_Win32GDI::PaintThread() return _fooctr++; } #endif + +#ifdef WITH_OPENGL + +#include <GL/gl.h> +#include "../3rdparty/opengl/glext.h" +#include "opengl.h" + +#ifndef PFD_SUPPORT_COMPOSITION +# define PFD_SUPPORT_COMPOSITION 0x00008000 +#endif + +static FVideoDriver_Win32OpenGL iFVideoDriver_Win32OpenGL; + +const char *VideoDriver_Win32OpenGL::Start(const StringList ¶m) +{ + if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 32) return "Only 32bpp blitters supported"; + + Dimension old_res = _cur_resolution; // Save current screen resolution in case of errors, as MakeWindow invalidates it. + + this->Initialize(); + this->MakeWindow(_fullscreen); + + /* Create and initialize OpenGL context. */ + const char *err = this->AllocateContext(); + if (err != nullptr) { + this->Stop(); + _cur_resolution = old_res; + return err; + } + + this->ClientSizeChanged(_wnd.width, _wnd.height); + + _draw_threaded = false; + MarkWholeScreenDirty(); + + return nullptr; +} + +void VideoDriver_Win32OpenGL::Stop() +{ + this->DestroyContext(); + this->VideoDriver_Win32Base::Stop(); +} + +void VideoDriver_Win32OpenGL::DestroyContext() +{ + OpenGLBackend::Destroy(); + + wglMakeCurrent(nullptr, nullptr); + if (this->gl_rc != nullptr) { + wglDeleteContext(this->gl_rc); + this->gl_rc = nullptr; + } + if (this->dc != nullptr) { + ReleaseDC(this->main_wnd, this->dc); + this->dc = nullptr; + } +} + +const char *VideoDriver_Win32OpenGL::AllocateContext() +{ + PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), // Size of this struct. + 1, // Version of this struct. + PFD_DRAW_TO_WINDOW | // Require window support. + PFD_SUPPORT_OPENGL | // Require OpenGL support. + PFD_DOUBLEBUFFER | // Use double buffering. + PFD_DEPTH_DONTCARE, + PFD_TYPE_RGBA, // Request RGBA format. + 24, // 24 bpp (excluding alpha). + 0, 0, 0, 0, 0, 0, 0, 0, // Colour bits and shift ignored. + 0, 0, 0, 0, 0, // No accumulation buffer. + 0, 0, // No depth/stencil buffer. + 0, // No aux buffers. + PFD_MAIN_PLANE, // Main layer. + 0, 0, 0, 0 // Ignored/reserved. + }; + + if (IsWindowsVistaOrGreater()) pfd.dwFlags |= PFD_SUPPORT_COMPOSITION; // Make OpenTTD compatible with Aero. + + this->dc = GetDC(this->main_wnd); + + /* Choose a suitable pixel format. */ + int format = ChoosePixelFormat(this->dc, &pfd); + if (format == 0) return "No suitable pixel format found"; + if (!SetPixelFormat(this->dc, format, &pfd)) return "Can't set pixel format"; + + /* Create OpenGL device context. */ + this->gl_rc = wglCreateContext(this->dc); + if (this->gl_rc == 0) return "Can't create OpenGL context"; + if (!wglMakeCurrent(this->dc, this->gl_rc)) return "Can't active GL context"; + + return OpenGLBackend::Create(); +} + +bool VideoDriver_Win32OpenGL::ToggleFullscreen(bool full_screen) +{ + this->DestroyContext(); + bool res = this->VideoDriver_Win32Base::ToggleFullscreen(full_screen); + res &= this->AllocateContext() == nullptr; + this->ClientSizeChanged(_wnd.width, _wnd.height); + return res; +} + +bool VideoDriver_Win32OpenGL::AfterBlitterChange() +{ + assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0); + this->ClientSizeChanged(_wnd.width, _wnd.height); + return true; +} + +bool VideoDriver_Win32OpenGL::AllocateBackingStore(int w, int h, bool force) +{ + if (!force && w == _screen.width && h == _screen.height) return false; + + _wnd.width = w = std::max(w, 64); + _wnd.height = h = std::max(h, 64); + + if (this->gl_rc == nullptr) return false; + + bool res = OpenGLBackend::Get()->Resize(w, h); + _wnd.buffer_bits = OpenGLBackend::Get()->GetVideoBuffer(); + return res; +} + +void VideoDriver_Win32OpenGL::Paint() +{ + PerformanceMeasurer framerate(PFE_VIDEO); + + if (_cur_palette.count_dirty != 0) { + Blitter *blitter = BlitterFactory::GetCurrentBlitter(); + + switch (blitter->UsePaletteAnimation()) { + case Blitter::PALETTE_ANIMATION_BLITTER: + blitter->PaletteAnimate(_local_palette); + break; + + case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND: + case Blitter::PALETTE_ANIMATION_NONE: + break; + + default: + NOT_REACHED(); + } + _cur_palette.count_dirty = 0; + } + + OpenGLBackend::Get()->Paint(); + SwapBuffers(this->dc); +} + +#endif /* WITH_OPENGL */ diff --git a/src/video/win32_v.h b/src/video/win32_v.h index b50f4e7a5..1840bbe86 100644 --- a/src/video/win32_v.h +++ b/src/video/win32_v.h @@ -48,8 +48,10 @@ protected: void Initialize(); bool MakeWindow(bool full_screen); - virtual uint8 GetFullscreenBpp(); + void ClientSizeChanged(int w, int h); + /** Get screen depth to use for fullscreen mode. */ + virtual uint8 GetFullscreenBpp(); /** (Re-)create the backing store. */ virtual bool AllocateBackingStore(int w, int h, bool force = false) = 0; /** Palette of the window has changed. */ @@ -58,8 +60,6 @@ protected: private: std::unique_lock<std::recursive_mutex> draw_lock; - void ClientSizeChanged(int w, int h); - static void PaintThreadThunk(VideoDriver_Win32Base *drv); friend LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); @@ -102,4 +102,46 @@ public: Driver *CreateInstance() const override { return new VideoDriver_Win32GDI(); } }; +#ifdef WITH_OPENGL + +/** The OpenGL video driver for windows. */ +class VideoDriver_Win32OpenGL : public VideoDriver_Win32Base { +public: + VideoDriver_Win32OpenGL() : dc(nullptr), gl_rc(nullptr) {} + + const char *Start(const StringList ¶m) override; + + void Stop() override; + + bool ToggleFullscreen(bool fullscreen) override; + + bool AfterBlitterChange() override; + + const char *GetName() const override { return "win32-opengl"; } + +protected: + HDC dc; ///< Window device context. + HGLRC gl_rc; ///< OpenGL context. + + uint8 GetFullscreenBpp() override { return 32; } // OpenGL is always 32 bpp. + + void Paint() override; + void PaintThread() override {} + + bool AllocateBackingStore(int w, int h, bool force = false) override; + void PaletteChanged(HWND hWnd) override {} + + const char *AllocateContext(); + void DestroyContext(); +}; + +/** The factory for Windows' OpenGL video driver. */ +class FVideoDriver_Win32OpenGL : public DriverFactoryBase { +public: + FVideoDriver_Win32OpenGL() : DriverFactoryBase(Driver::DT_VIDEO, 9, "win32-opengl", "Win32 OpenGL Video Driver") {} + /* virtual */ Driver *CreateInstance() const override { return new VideoDriver_Win32OpenGL(); } +}; + +#endif /* WITH_OPENGL */ + #endif /* VIDEO_WIN32_H */ |