// // #include "SDLGraphicsOperations.h" #ifdef WITH_VULKAN #include "backends/imgui_impl_vulkan.h" #endif #include "cmdline/cmdline.h" #include #include "backends/imgui_impl_sdl3.h" #include "backends/imgui_impl_opengl3.h" namespace { void setOGLProperties(const os::ViewPortProperties& props) { SDL_GL_ResetAttributes(); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, props.pixel_format.red_size); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, props.pixel_format.green_size); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, props.pixel_format.blue_size); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, props.pixel_format.depth_size); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, props.pixel_format.stencil_size); #ifndef USE_OPENGL_ES SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); #else SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 0); #endif // disabled due to issues with implementation; may be re-enabled in future SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); mprintf((" Requested SDL Pixel values = R: %d, G: %d, B: %d, depth: %d, stencil: %d, double-buffer: %d, FSAA: %d\n", props.pixel_format.red_size, props.pixel_format.green_size, props.pixel_format.blue_size, props.pixel_format.depth_size, props.pixel_format.stencil_size, 1, props.pixel_format.multi_samples)); SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, props.gl_attributes.major_version); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, props.gl_attributes.minor_version); #ifdef USE_OPENGL_ES SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); #endif int profile; switch (props.gl_attributes.profile) { case os::OpenGLProfile::Core: profile = SDL_GL_CONTEXT_PROFILE_CORE; break; case os::OpenGLProfile::Compatibility: profile = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; break; case os::OpenGLProfile::ES: profile = SDL_GL_CONTEXT_PROFILE_ES; break; default: UNREACHABLE("Unhandled profile value %d!", static_cast(props.gl_attributes.profile)); return; } SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile); int flags = 0; if (props.gl_attributes.flags[os::OpenGLContextFlags::Debug]) { flags |= SDL_GL_CONTEXT_DEBUG_FLAG; } if (props.gl_attributes.flags[os::OpenGLContextFlags::ForwardCompatible]) { flags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG; } SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, flags); } } class SDLOpenGLContext: public os::OpenGLContext { SDL_GLContext _glCtx; public: SDLOpenGLContext(SDL_GLContext sdl_gl_context) : _glCtx(sdl_gl_context) { } ~SDLOpenGLContext() override { SDL_GL_DestroyContext(_glCtx); } os::OpenGLLoadProc getLoaderFunction() override { return reinterpret_cast(SDL_GL_GetProcAddress); } void makeCurrent(SDL_Window* window) { SDL_GL_MakeCurrent(window, _glCtx); } bool setSwapInterval(int status) override { return SDL_GL_SetSwapInterval(status); } }; class SDLWindowViewPort: public os::Viewport { SDL_Window* _window; os::ViewPortProperties _props; public: SDLWindowViewPort(SDL_Window* window, const os::ViewPortProperties& props) : _window(window), _props(props) { Assertion(window != nullptr, "Invalid window specified"); } ~SDLWindowViewPort() override { SDL_DestroyWindow(_window); _window = nullptr; } const os::ViewPortProperties& getProps() const { return _props; } SDL_Window* toSDLWindow() override { return _window; } std::pair getSize() override { int width, height; SDL_GetWindowSizeInPixels(_window, &width, &height); return std::make_pair(width, height); } void swapBuffers() override { SDL_GL_SwapWindow(_window); } void setState(os::ViewportState state) override { switch (state) { case os::ViewportState::Windowed: SDL_SetWindowFullscreen(_window, false); SDL_SetWindowBordered(_window, true); break; case os::ViewportState::Borderless: SDL_SetWindowFullscreen(_window, false); SDL_SetWindowBordered(_window, false); break; case os::ViewportState::Fullscreen: { SDL_DisplayMode target; int width, height; if (SDL_GetWindowSizeInPixels(_window, &width, &height)) { if (SDL_GetClosestFullscreenDisplayMode(SDL_GetDisplayForWindow(_window), width, height, 0.0f, true, &target)) { SDL_SetWindowFullscreenMode(_window, &target); } } // NOTE: This can be buggy if the mode failed to set since FSO // doesn't account for a difference between assumed window size // and actual window size. This can present as screen anomalies // such as distortion, mirroring, or flickering. SDL_SetWindowFullscreen(_window, true); break; } default: UNREACHABLE("Invalid window state %d!", static_cast(state)); break; } } void minimize() override { // lets not minimize if we are in windowed mode if (!(SDL_GetWindowFlags(_window) & SDL_WINDOW_FULLSCREEN)) { return; } SDL_MinimizeWindow(_window); } void restore() override { SDL_RestoreWindow(_window); } }; SDLGraphicsOperations::SDLGraphicsOperations() { mprintf((" Initializing SDL video...\n")); #ifdef SCP_UNIX // Slight hack to make Mesa advertise S3TC support without libtxc_dxtn setenv("force_s3tc_enable", "true", 1); #endif if ( !SDL_InitSubSystem(SDL_INIT_VIDEO) ) { Error(LOCATION, "Couldn't init SDL video: %s", SDL_GetError()); return; } } SDLGraphicsOperations::~SDLGraphicsOperations() { // make sure imgui stuff is initialized before trying to shut it down if (ImGui::GetCurrentContext()) { if (ImGui::GetIO().BackendPlatformUserData) { ImGui_ImplSDL3_Shutdown(); } if ( ImGui::GetIO().BackendRendererUserData ) { switch (gr_screen.mode) { case GraphicsAPI::OpenGL: ImGui_ImplOpenGL3_Shutdown(); break; case GraphicsAPI::Vulkan: #ifdef WITH_VULKAN ImGui_ImplVulkan_Shutdown(); #endif break; default: break; } } } SDL_QuitSubSystem(SDL_INIT_VIDEO); } std::unique_ptr SDLGraphicsOperations::createViewport(const os::ViewPortProperties& props) { uint32_t windowflags = 0; if (props.enable_opengl) { windowflags |= SDL_WINDOW_OPENGL; setOGLProperties(props); } if (props.enable_vulkan) { windowflags |= SDL_WINDOW_VULKAN; } if (props.flags[os::ViewPortFlags::Borderless]) { windowflags |= SDL_WINDOW_BORDERLESS; } if (props.flags[os::ViewPortFlags::Fullscreen]) { // don't set window flag here since we need to alter the display mode // first and that can only be done after the window is created // // windowflags |= SDL_WINDOW_FULLSCREEN; } if (props.flags[os::ViewPortFlags::Resizeable]) { windowflags |= SDL_WINDOW_RESIZABLE; } if (props.flags[os::ViewPortFlags::Capture_Mouse]) { windowflags |= SDL_WINDOW_MOUSE_GRABBED; } #ifdef __ANDROID__ SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1"); SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); os_android_touch_overlay_init(); #endif SDL_Rect bounds; if ( !SDL_GetDisplayBounds(props.display, &bounds) ) { mprintf(("Failed to get display bounds: %s\n", SDL_GetError())); return nullptr; } int x; int y; uint32_t width = props.width; uint32_t height = props.height; if (Cmdline_window_res) { width = Cmdline_window_res->first; height = Cmdline_window_res->second; } if (bounds.w == (int)width && bounds.h == (int)height) { // If we have the same size as the desktop we explicitly specify 0,0 to make sure that the window borders aren't hidden mprintf(("SDL: Creating window at %d,%d because window has same size as desktop.\n", bounds.x, bounds.y)); x = bounds.x; y = bounds.y; } else { x = SDL_WINDOWPOS_CENTERED_DISPLAY(props.display); y = SDL_WINDOWPOS_CENTERED_DISPLAY(props.display); } SDL_Window* window = SDL_CreateWindow(props.title.c_str(), width, height, windowflags); if (window == nullptr) { mprintf(("Failed to create SDL Window: %s\n", SDL_GetError())); return nullptr; } // switch to fullscreen if we should if (props.flags[os::ViewPortFlags::Fullscreen]) { SDL_DisplayMode target; if (SDL_GetClosestFullscreenDisplayMode(props.display, width, height, 0.0f, true, &target)) { SDL_SetWindowFullscreenMode(window, &target); } // NOTE: This can be buggy if the mode failed to set since FSO doesn't // account for a difference between assumed window size and actual window // size. This can present as screen anomalies such as distortion, mirroring, // or flickering. SDL_SetWindowFullscreen(window, true); } SDL_SetWindowPosition(window, x, y); SDL_RaiseWindow(window); return std::unique_ptr(new SDLWindowViewPort(window, props)); } void SDLGraphicsOperations::makeOpenGLContextCurrent(os::Viewport* view, os::OpenGLContext* ctx) { if (view == nullptr && ctx == nullptr) { SDL_GL_MakeCurrent(nullptr, nullptr); return; } Assertion(view != nullptr, "Both viewport of context must be valid at this point!"); Assertion(ctx != nullptr, "Both viewport of context must be valid at this point!"); auto sdlCtx = reinterpret_cast(ctx); sdlCtx->makeCurrent(view->toSDLWindow()); } std::unique_ptr SDLGraphicsOperations::createOpenGLContext(os::Viewport* viewport, const os::OpenGLContextAttributes& gl_attrs) { auto sdlViewport = reinterpret_cast(viewport); auto props = sdlViewport->getProps(); props.gl_attributes = gl_attrs; setOGLProperties(props); auto ctx = SDL_GL_CreateContext(viewport->toSDLWindow()); if (ctx == nullptr) { mprintf(("Could not create OpenGL Context: %s\n", SDL_GetError())); return nullptr; } int r, g, b, depth, stencil, db, fsaa_samples; SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &r); SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &g); SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &b); SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depth); SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &db); SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencil); SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fsaa_samples); mprintf((" Actual SDL Video values = R: %d, G: %d, B: %d, depth: %d, stencil: %d, double-buffer: %d, FSAA: %d\n", r, g, b, depth, stencil, db, fsaa_samples)); Assertion(ImGui::GetCurrentContext() != nullptr, "Can't use ImGui without a valid context!"); ImGui_ImplSDL3_InitForOpenGL(viewport->toSDLWindow(), ctx); #ifndef USE_OPENGL_ES ImGui_ImplOpenGL3_Init(); #else ImGui_ImplOpenGL3_Init("#version 300 es"); #endif return std::unique_ptr(new SDLOpenGLContext(ctx)); }