/* * This file is part of the Colobot: Gold Edition source code * Copyright (C) 2001-2023, Daniel Roux, EPSITEC SA & TerranovaTeam * http://epsitec.ch; http://colobot.info; http://github.com/colobot * * 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://gnu.org/licenses */ #include "graphics/opengl33/glutil.h" #include "graphics/core/renderers.h" #include "graphics/opengl33/gl33_device.h" #include "common/image.h" #include "common/logger.h" #include #include #include #include #include #include // Graphics module namespace namespace Gfx { bool InitializeGLEW() { static bool glewInited = false; if (!glewInited) { glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { GetLogger()->Error("GLEW initialization failed\n"); return false; } glewInited = true; } return true; } std::unique_ptr CreateDevice(const DeviceConfig &config, const std::string& name) { if (name == "default") return std::make_unique(config); else if (name == "opengl") return std::make_unique(config); else if (name == "gl33") return std::make_unique(config); else if (name == "auto") { int version = GetOpenGLVersion(); if (version >= 33) return std::make_unique(config); } return nullptr; } int GetOpenGLVersion() { int major, minor; return GetOpenGLVersion(major, minor); } int GetOpenGLVersion(int &major, int &minor) { const char* version = reinterpret_cast(glGetString(GL_VERSION)); sscanf(version, "%d.%d", &major, &minor); return 10 * major + minor; } bool AreExtensionsSupported(std::string list) { // Extract extensions to find std::vector extensions; std::stringstream stream(list); std::string value; while (true) { stream >> value; if (stream.eof()) break; extensions.push_back(value); } int version = GetOpenGLVersion(); // Use glGetString if (version < 30) { const char* text = reinterpret_cast(glGetString(GL_EXTENSIONS)); std::stringstream stream(text); while (!extensions.empty()) { stream >> value; if (stream.eof()) break; auto result = std::remove(extensions.begin(), extensions.end(), value); if (result != extensions.end()) extensions.erase(result); } } // Use glGetStringi else { int n; glGetIntegerv(GL_NUM_EXTENSIONS, &n); for (int i = 0; i < n; i++) { const char* name = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); value = std::string(name); auto result = std::remove(extensions.begin(), extensions.end(), value); if (result != extensions.end()) extensions.erase(result); if (extensions.empty()) break; } } // Return true if found all required extensions return extensions.empty(); } std::string GetHardwareInfo(bool full) { int glversion = GetOpenGLVersion(); std::stringstream result; // basic hardware information const char* version = reinterpret_cast(glGetString(GL_VERSION)); const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); result << "Hardware information:\n\n"; result << "OpenGL Version:\t\t" << version << '\n'; result << "Hardware Vendor:\t\t" << vendor << '\n'; result << "Renderer:\t\t\t" << renderer << '\n'; // GLSL version if available if (glversion >= 20) { const char* glslVersion = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); result << "Shading Language Version:\t" << glslVersion << '\n'; } if (!full) return result.str(); // extended hardware information int value = 0; result << "\nCapabilities:\n\n"; // texture size glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value); result << "Max Texture Size:\t\t" << value << '\n'; if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) { result << "Anisotropic filtering:\t\tsupported\n"; float level; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &level); result << " Max Level:\t\t" << static_cast(level) << '\n'; } else { result << "Anisotropic filtering:\t\tunsupported\n"; } // multitexturing if (glversion >= 13) { result << "Multitexturing:\t\tsupported\n"; glGetIntegerv(GL_MAX_TEXTURE_UNITS, &value); result << " Max Texture Units:\t\t" << value << '\n'; if (glversion >= 20) { glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &value); result << " Max Texture Image Units:\t" << value << '\n'; } } else { result << "Multitexturing:\t\tunsupported\n"; } // FBO support result << "Framebuffer Object:\t\tsupported\n"; result << " Type:\t\t\tCore/ARB\n"; glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &value); result << " Max Renderbuffer Size:\t" << value << '\n'; glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &value); result << " Max Color Attachments:\t" << value << '\n'; result << "Multisampling:\t\tsupported\n"; glGetIntegerv(GL_MAX_SAMPLES, &value); result << " Max Framebuffer Samples:\t" << value << '\n'; // VBO support result << "VBO:\t\t\tsupported (core)\n"; return result.str(); } int ClearGLErrors() { int result = 0; while (true) { GLint error = glGetError(); if (error == GL_NO_ERROR) break; result++; } return result; } bool CheckGLErrors() { GLint error = glGetError(); bool result = false; while (error != GL_NO_ERROR) { GetLogger()->Error("OpenGL error: %d\n", error); result = true; error = glGetError(); } return result; } GLenum TranslateGfxPrimitive(PrimitiveType type) { GLenum flag = 0; switch (type) { case PrimitiveType::POINTS: flag = GL_POINTS; break; case PrimitiveType::LINES: flag = GL_LINES; break; case PrimitiveType::LINE_STRIP: flag = GL_LINE_STRIP; break; case PrimitiveType::LINE_LOOP: flag = GL_LINE_LOOP; break; case PrimitiveType::TRIANGLES: flag = GL_TRIANGLES; break; case PrimitiveType::TRIANGLE_STRIP: flag = GL_TRIANGLE_STRIP; break; case PrimitiveType::TRIANGLE_FAN: flag = GL_TRIANGLE_FAN; break; default: assert(false); break; } return flag; } bool InPlane(glm::vec3 normal, float originPlane, glm::vec3 center, float radius) { float distance = originPlane + glm::dot(normal, center); if (distance < -radius) return false; return true; } GLenum TranslateType(Type type) { switch (type) { case Type::BYTE: return GL_BYTE; case Type::UBYTE: return GL_UNSIGNED_BYTE; case Type::SHORT: return GL_SHORT; case Type::USHORT: return GL_UNSIGNED_SHORT; case Type::INT: return GL_INT; case Type::UINT: return GL_UNSIGNED_INT; case Type::HALF: return GL_HALF_FLOAT; case Type::FLOAT: return GL_FLOAT; case Type::DOUBLE: return GL_DOUBLE; default: return 0; } } std::string lastShaderError; std::string GetLastShaderError() { return lastShaderError; } std::string LoadSource(const std::string& path) { PHYSFS_file* file = PHYSFS_openRead(path.c_str()); if (file == nullptr) { GetLogger()->Error("Cannot read shader source file\n"); GetLogger()->Error("Missing file \"%s\"\n", path.c_str()); return {}; } size_t len = PHYSFS_fileLength(file); std::string source(len, ' '); size_t length = PHYSFS_read(file, source.data(), 1, len); PHYSFS_close(file); return source; } GLint CreateShader(GLint type, const std::vector& sources) { std::vector parts; for (const auto& source : sources) { parts.push_back(source.c_str()); } GLuint shader = glCreateShader(type); glShaderSource(shader, parts.size(), parts.data(), nullptr); glCompileShader(shader); GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { GLint len; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); auto message = std::make_unique(len + 1); glGetShaderInfoLog(shader, len + 1, nullptr, message.get()); GetLogger()->Error("Shader compilation error occurred!\n%s\n", message.get()); lastShaderError = std::string("Shader compilation error occurred!\n\n") + std::string(message.get()); glDeleteShader(shader); return 0; } return shader; } GLint LoadShader(GLint type, const char* filename) { PHYSFS_file *file = PHYSFS_openRead(filename); if (file == nullptr) { GetLogger()->Error("Cannot read shader source file\n"); GetLogger()->Error("Missing file \"%s\"\n", filename); return 0; } size_t len = PHYSFS_fileLength(file); std::vector source(len + 1); GLchar *sources[] = { source.data() }; size_t length = PHYSFS_readBytes(file, source.data(), len); source[length] = '\0'; PHYSFS_close(file); GLuint shader = glCreateShader(type); glShaderSource(shader, 1, const_cast(sources), nullptr); glCompileShader(shader); GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { GLint len; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); auto message = std::make_unique(len + 1); glGetShaderInfoLog(shader, len + 1, nullptr, message.get()); GetLogger()->Error("Shader compilation error occurred!\n%s\n", message.get()); lastShaderError = std::string("Shader compilation error occurred!\n\n") + std::string(message.get()); glDeleteShader(shader); return 0; } return shader; } GLint LinkProgram(int count, const GLint* shaders) { GLint program = glCreateProgram(); for (int i = 0; i < count; i++) glAttachShader(program, shaders[i]); glLinkProgram(program); for (int i = 0; i < count; i++) glDetachShader(program, shaders[i]); GLint status; glGetProgramiv(program, GL_LINK_STATUS, &status); if (status != GL_TRUE) { GLint len; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); auto message = std::make_unique(len + 1); glGetProgramInfoLog(program, len + 1, nullptr, message.get()); GetLogger()->Error("Shader program linking error occurred!\n%s\n", message.get()); lastShaderError = std::string("Shader program linking error occurred!\n\n") + std::string(message.get()); glDeleteProgram(program); return 0; } return program; } GLint LinkProgram(const std::vector& shaders) { GLint program = glCreateProgram(); for (auto shader : shaders) glAttachShader(program, shader); glLinkProgram(program); for (auto shader : shaders) glDetachShader(program, shader); GLint status; glGetProgramiv(program, GL_LINK_STATUS, &status); if (status != GL_TRUE) { GLint len; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); std::string message(len, ' '); glGetProgramInfoLog(program, len + 1, nullptr, message.data()); GetLogger()->Error("Shader program linking error occurred!\n%s\n", message.c_str()); lastShaderError = std::string("Shader program linking error occurred!\n\n") + std::string(message.c_str()); glDeleteProgram(program); return 0; } return program; } class CGLFrameBufferPixels : public CFrameBufferPixels { public: CGLFrameBufferPixels(std::size_t size) : m_pixels(std::make_unique(size)) {} void* GetPixelsData() override { return static_cast(m_pixels.get()); } private: std::unique_ptr m_pixels; }; std::unique_ptr GetGLFrameBufferPixels(const glm::ivec2& size) { auto pixels = std::make_unique(4 * size.x * size.y); glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, pixels->GetPixelsData()); GLuint* p = static_cast ( pixels->GetPixelsData() ); for (int i = 0; i < size.x * size.y; ++i) p[i] |= 0xFF000000; return pixels; } PreparedTextureData PrepareTextureData(ImageData* imageData, TextureFormat format) { PreparedTextureData texData; bool convert = false; texData.sourceFormat = 0; if (format == TextureFormat::RGB) { texData.sourceFormat = GL_RGB; texData.alpha = false; } else if (format == TextureFormat::BGR) { texData.sourceFormat = GL_BGR; texData.alpha = false; } else if (format == TextureFormat::RGBA) { texData.sourceFormat = GL_RGBA; texData.alpha = true; } else if (format == TextureFormat::BGRA) { texData.sourceFormat = GL_BGRA; texData.alpha = true; } else if (format == TextureFormat::AUTO) { if (imageData->surface->format->BytesPerPixel == 4) { if ((imageData->surface->format->Amask == 0xFF000000) && (imageData->surface->format->Rmask == 0x00FF0000) && (imageData->surface->format->Gmask == 0x0000FF00) && (imageData->surface->format->Bmask == 0x000000FF)) { texData.sourceFormat = GL_BGRA; texData.alpha = true; } else if ((imageData->surface->format->Amask == 0xFF000000) && (imageData->surface->format->Bmask == 0x00FF0000) && (imageData->surface->format->Gmask == 0x0000FF00) && (imageData->surface->format->Rmask == 0x000000FF)) { texData.sourceFormat = GL_RGBA; texData.alpha = true; } else { texData.sourceFormat = GL_RGBA; convert = true; } } else if (imageData->surface->format->BytesPerPixel == 3) { if ((imageData->surface->format->Rmask == 0xFF0000) && (imageData->surface->format->Gmask == 0x00FF00) && (imageData->surface->format->Bmask == 0x0000FF)) { texData.sourceFormat = GL_BGR; texData.alpha = false; } else if ((imageData->surface->format->Bmask == 0xFF0000) && (imageData->surface->format->Gmask == 0x00FF00) && (imageData->surface->format->Rmask == 0x0000FF)) { texData.sourceFormat = GL_RGB; texData.alpha = false; } else { texData.sourceFormat = GL_RGBA; convert = true; } } else { GetLogger()->Error("Unknown data surface format"); assert(false); } } else assert(false); texData.actualSurface = imageData->surface; texData.convertedSurface = nullptr; if (convert) { SDL_PixelFormat format = {}; format.BytesPerPixel = 4; format.BitsPerPixel = 32; format.Aloss = format.Bloss = format.Gloss = format.Rloss = 0; format.Amask = 0xFF000000; format.Ashift = 24; format.Bmask = 0x00FF0000; format.Bshift = 16; format.Gmask = 0x0000FF00; format.Gshift = 8; format.Rmask = 0x000000FF; format.Rshift = 0; format.palette = nullptr; texData.convertedSurface = SDL_ConvertSurface(imageData->surface, &format, SDL_SWSURFACE); if (texData.convertedSurface != nullptr) texData.actualSurface = texData.convertedSurface; } return texData; } } // namespace Gfx