#include "window/detail/SDLWindow.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "debug/Logger.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Texture.hpp" #include "settings.hpp" #include "util/platform.hpp" #include "window/Window.hpp" #include "window/detail/SDLInput.hpp" static debug::Logger logger("window"); static std::unordered_set supported_gl_extensions; static void init_gl_extensions_list() { GLint numExtensions = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); for (GLint i = 0; i < numExtensions; ++i) { const char *ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); if (ext) { supported_gl_extensions.insert(ext); } } } static bool is_gl_extension_supported(const char *extension) { if (!extension || !*extension) { return false; } return supported_gl_extensions.find(extension) != supported_gl_extensions.end(); } static const char *gl_error_name(int error) { switch (error) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; } return "UNKNOWN"; } static const char *gl_severity_name(int severity) { switch (severity) { case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; } return "UNKNOWN"; } static void GLAPIENTRY gl_message_callback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam ) { if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { return; } if (!ENGINE_DEBUG_BUILD && severity != GL_DEBUG_SEVERITY_HIGH) { return; } logger.warning() << "GL:" << gl_error_name(type) << ":" << gl_severity_name(severity) << ": " << message; } static bool initialize_gl(int width, int height) { glewExperimental = GL_TRUE; GLenum glewErr = glewInit(); if (glewErr != GLEW_OK) { if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY) { // see issue #240 logger.warning() << "glewInit() returned GLEW_ERROR_NO_GLX_DISPLAY; ignored"; } else { logger.error() << "failed to initialize GLEW:\n" << glewGetErrorString(glewErr); return false; } } init_gl_extensions_list(); #ifndef __APPLE__ if (is_gl_extension_supported("GL_KHR_debug")) { glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(gl_message_callback, nullptr); } #endif glViewport(0, 0, width, height); glClearColor(0.0f, 0.0f, 0.0f, 1); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLint maxTextureSize[1] {static_cast(Texture::MAX_RESOLUTION)}; glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxTextureSize); if (maxTextureSize[0] > 0) { Texture::MAX_RESOLUTION = maxTextureSize[0]; logger.info() << "max texture size is " << Texture::MAX_RESOLUTION; } const GLubyte *vendor = glGetString(GL_VENDOR); const GLubyte *renderer = glGetString(GL_RENDERER); logger.info() << "GL Vendor: " << reinterpret_cast(vendor); logger.info() << "GL Renderer: " << reinterpret_cast(renderer); logger.info() << "SDL: " << SDL_GetVersion(); return true; } SDLWindow::SDLWindow(DisplaySettings *settings, std::string title) noexcept { if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { logger.error() << "failed to initialize SDL: " << SDL_GetError(); isSuccessfull = false; return; } SDL_WindowFlags flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; if (settings->windowMode.get() == static_cast(WindowMode::FULLSCREEN)) { flags |= SDL_WINDOW_FULLSCREEN; } window = SDL_CreateWindow( title.c_str(), settings->width.get(), settings->height.get(), flags ); if (!window) { logger.error() << "failed to create SDL Window: " << SDL_GetError(); isSuccessfull = false; return; } int width = 0; int height = 0; if (!SDL_GetWindowSize(window, &width, &height)) { logger.error() << "failed to get window size: " << SDL_GetError(); isSuccessfull = false; return; } size = {width, height}; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); #ifdef __APPLE__ SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE ); SDL_GL_SetAttribute( SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG ); #endif SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, settings->samples.get()); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); context = SDL_GL_CreateContext(window); if (!context) { logger.error() << "failed to create GL context: " << SDL_GetError(); isSuccessfull = false; return; } if (!initialize_gl(width, height)) { logger.error() << "failed to init GL: " << SDL_GetError(); return; } if (!SDL_GL_SetSwapInterval(1)) { logger.error() << "failed to set vsync: " << SDL_GetError(); isSuccessfull = false; return; } renderer = SDL_CreateRenderer(window, "vc_main"); float scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); logger.info() << "monitor content scale: " << scale; } SDLWindow::~SDLWindow() { if (window) { SDL_DestroyWindow(window); } if (context) { if (!SDL_GL_DestroyContext(context)) { logger.error() << "Cant destroy gl context: " << SDL_GetError(); } } } void SDLWindow::swapBuffers() noexcept { if (framerate > 0) { auto elapsedTime = time() - prevSwap; auto frameTime = 1.0 / framerate; if (elapsedTime < frameTime) { platform::sleep( static_cast((frameTime - elapsedTime) * 1000) ); } prevSwap = time(); } if (!SDL_GL_SwapWindow(window)) [[unlikely]] { // C++20 needed logger.error() << "Cant swap buffer: " << SDL_GetError(); } resetScissor(); } bool SDLWindow::isMaximized() const { return (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED) != 0; } bool SDLWindow::isFocused() const { Uint32 flags = SDL_GetWindowFlags(window); return (flags & SDL_WINDOW_INPUT_FOCUS) != 0 || (flags & SDL_WINDOW_MOUSE_FOCUS) != 0; } bool SDLWindow::isIconified() const { return (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) != 0; } bool SDLWindow::isShouldClose() const { return toClose; } void SDLWindow::setShouldClose(bool flag) { toClose = flag; } void SDLWindow::setCursor(CursorShape shape) { // so it method called each frame, we always recreate cursor. // Refactor it if (cursor) { SDL_DestroyCursor(cursor); cursor = nullptr; } switch (shape) { case CursorShape::ARROW: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_DEFAULT ); break; case CursorShape::TEXT: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_TEXT ); break; case CursorShape::CROSSHAIR: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_CROSSHAIR ); break; case CursorShape::POINTER: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_POINTER ); break; case CursorShape::EW_RESIZE: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_EW_RESIZE ); break; case CursorShape::NS_RESIZE: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_NS_RESIZE ); break; case CursorShape::NWSE_RESIZE: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_NWSE_RESIZE ); break; case CursorShape::NESW_RESIZE: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_NESW_RESIZE ); break; case CursorShape::ALL_RESIZE: // ?? cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_NWSE_RESIZE ); break; case CursorShape::NOT_ALLOWED: cursor = SDL_CreateSystemCursor( SDL_SystemCursor::SDL_SYSTEM_CURSOR_NOT_ALLOWED ); break; } if (cursor) { SDL_SetCursor(cursor); } } void SDLWindow::setMode(WindowMode mode) { // WindowMode::WINDOWED by default have the next options as disbled if (!SDL_SetWindowFullscreen(window, mode == WindowMode::FULLSCREEN)) { logger.error() << "Cant toggle fullscreen window: " << SDL_GetError(); } if (!SDL_SetWindowBordered(window, mode == WindowMode::BORDERLESS)) { logger.error() << "Cant toggle bordered window: " << SDL_GetError(); } this->mode = mode; } WindowMode SDLWindow::getMode() const { return mode; } void SDLWindow::setIcon(const ImageData *image) { if (image == nullptr) { logger.error() << "Image is nullptr"; return; } SDL_Surface *iconSurface = SDL_CreateSurface( image->getWidth(), image->getHeight(), SDL_PIXELFORMAT_RGBA32 ); if (!iconSurface) { logger.error() << "Failed to create surface for app icon: " << SDL_GetError(); return; } memcpy( iconSurface->pixels, image->getData(), image->getWidth() * image->getHeight() * 4 ); if (!SDL_SetWindowIcon(window, iconSurface)) { logger.error() << "Failed to set icon: " << SDL_GetError(); } SDL_DestroySurface(iconSurface); } void SDLWindow::pushScissor(glm::vec4 area) { if (scissorStack.empty()) { glEnable(GL_SCISSOR_TEST); } scissorStack.push(scissorArea); area.z += glm::ceil(area.x); area.w += glm::ceil(area.y); area.x = glm::max(area.x, scissorArea.x); area.y = glm::max(area.y, scissorArea.y); area.z = glm::min(area.z, scissorArea.z); area.w = glm::min(area.w, scissorArea.w); if (area.z < 0.0f || area.w < 0.0f) { glScissor(0, 0, 0, 0); } else { glScissor( area.x, size.y - area.w, std::max(0, static_cast(glm::ceil(area.z - area.x))), std::max(0, static_cast(glm::ceil(area.w - area.y))) ); } scissorArea = area; } void SDLWindow::resetScissor() { scissorArea = glm::vec4(0.0f, 0.0f, size.x, size.y); scissorStack = std::stack(); glDisable(GL_SCISSOR_TEST); } void SDLWindow::popScissor() { if (scissorStack.empty()) { logger.warning() << "extra Window::popScissor call"; return; } glm::vec4 area = scissorStack.top(); scissorStack.pop(); if (area.z < 0.0f || area.w < 0.0f) { glScissor(0, 0, 0, 0); } else { glScissor( area.x, size.y - area.w, std::max(0, static_cast(area.z - area.x)), std::max(0, static_cast(area.w - area.y)) ); } if (scissorStack.empty()) { glDisable(GL_SCISSOR_TEST); } scissorArea = area; } double SDLWindow::time() { return static_cast(SDL_GetTicksNS()) / 1'000'000'000; } void SDLWindow::setFramerate(int framerate) { if (!SDL_GL_SetSwapInterval(framerate == -1)) { logger.error() << "Failed to set framerate: " << SDL_GetError(); } this->framerate = framerate; } // todo: move somewhere // Possible do with SDL_RenderReadPixels std::unique_ptr SDLWindow::takeScreenshot() { glBindFramebuffer(GL_FRAMEBUFFER, 0); auto data = std::make_unique(size.x * size.y * 3); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, size.x, size.y, GL_RGB, GL_UNSIGNED_BYTE, data.get()); return std::make_unique( ImageFormat::rgb888, size.x, size.y, data.release() ); } [[nodiscard]] bool SDLWindow::isValid() const { return isSuccessfull; } [[nodiscard]] SDL_Window *SDLWindow::getSdlWindow() const { return window; } std::tuple, std::unique_ptr> Window::initialize( DisplaySettings *settings, std::string title ) { auto window = std::make_unique(settings, title); if (!window->isValid()) { return {nullptr, nullptr}; } auto input = std::make_unique(*window); return {std::move(window), std::move(input)}; } void display::clear() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void display::clearDepth() { glClear(GL_DEPTH_BUFFER_BIT); } void display::setBgColor(glm::vec3 color) { glClearColor(color.r, color.g, color.b, 1.0f); } void display::setBgColor(glm::vec4 color) { glClearColor(color.r, color.g, color.b, color.a); }