<commit_before>/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Daemon source code.

Daemon source code 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 2 of the License,
or (at your option) any later version.

Daemon source code 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 Daemon source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "qcommon/q_shared.h" // Include before SDL.h due to M_PI issue...
#include <SDL.h>

#ifdef USE_SMP
#include <SDL_thread.h>
#endif

#include "renderer/tr_local.h"

#include "sdl_icon.h"
#include "SDL_syswm.h"
#include "framework/CommandSystem.h"
#include "framework/CvarSystem.h"

static Log::Logger logger("glconfig", "", Log::Level::NOTICE);

static Cvar::Modified<Cvar::Cvar<bool>> r_noBorder(
	"r_noBorder", "draw window without border", Cvar::ARCHIVE, false);
static Cvar::Modified<Cvar::Range<Cvar::Cvar<int>>> r_swapInterval(
	"r_swapInterval", "enable vsync on every Nth frame, negative for apdative", Cvar::ARCHIVE, 0, -5, 5 );

SDL_Window *window = nullptr;
static SDL_GLContext glContext = nullptr;

#ifdef USE_SMP
static void GLimp_SetCurrentContext( bool enable )
{
	if ( enable )
	{
		SDL_GL_MakeCurrent( window, glContext );
	}
	else
	{
		SDL_GL_MakeCurrent( window, nullptr );
	}
}

/*
===========================================================

SMP acceleration

===========================================================
*/

/*
 * I have no idea if this will even work...most platforms don't offer
 * thread-safe OpenGL libraries.
 */

static SDL_mutex  *smpMutex = nullptr;
static SDL_cond   *renderCommandsEvent = nullptr;
static SDL_cond   *renderCompletedEvent = nullptr;
static void ( *renderThreadFunction )() = nullptr;
static SDL_Thread *renderThread = nullptr;

/*
===============
GLimp_RenderThreadWrapper
===============
*/
ALIGN_STACK_FOR_MINGW static int GLimp_RenderThreadWrapper( void* )
{
	// These printfs cause race conditions which mess up the console output
	logger.Notice( "Render thread starting\n" );

	renderThreadFunction();

	GLimp_SetCurrentContext( false );

	logger.Notice( "Render thread terminating\n" );

	return 0;
}

/*
===============
GLimp_SpawnRenderThread
===============
*/
bool GLimp_SpawnRenderThread( void ( *function )() )
{
	static bool warned = false;

	if ( !warned )
	{
		logger.Warn( "You enable r_smp at your own risk!\n" );
		warned = true;
	}

	if ( renderThread != nullptr ) /* hopefully just a zombie at this point... */
	{
		logger.Notice( "Already a render thread? Trying to clean it up...\n" );
		GLimp_ShutdownRenderThread();
	}

	smpMutex = SDL_CreateMutex();

	if ( smpMutex == nullptr )
	{
		logger.Notice( "smpMutex creation failed: %s\n", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	renderCommandsEvent = SDL_CreateCond();

	if ( renderCommandsEvent == nullptr )
	{
		logger.Notice( "renderCommandsEvent creation failed: %s\n", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	renderCompletedEvent = SDL_CreateCond();

	if ( renderCompletedEvent == nullptr )
	{
		logger.Notice( "renderCompletedEvent creation failed: %s\n", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	renderThreadFunction = function;
	renderThread = SDL_CreateThread( GLimp_RenderThreadWrapper, "render thread", nullptr );

	if ( renderThread == nullptr )
	{
		logger.Notice("SDL_CreateThread() returned %s", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	return true;
}

/*
===============
GLimp_ShutdownRenderThread
===============
*/
void GLimp_ShutdownRenderThread()
{
	if ( renderThread != nullptr )
	{
		GLimp_WakeRenderer( nullptr );
		SDL_WaitThread( renderThread, nullptr );
		renderThread = nullptr;
		glConfig.smpActive = false;
	}

	if ( smpMutex != nullptr )
	{
		SDL_DestroyMutex( smpMutex );
		smpMutex = nullptr;
	}

	if ( renderCommandsEvent != nullptr )
	{
		SDL_DestroyCond( renderCommandsEvent );
		renderCommandsEvent = nullptr;
	}

	if ( renderCompletedEvent != nullptr )
	{
		SDL_DestroyCond( renderCompletedEvent );
		renderCompletedEvent = nullptr;
	}

	renderThreadFunction = nullptr;
}

static volatile void     *smpData = nullptr;
static volatile bool smpDataReady;

/*
===============
GLimp_RendererSleep
===============
*/
void           *GLimp_RendererSleep()
{
	void *data = nullptr;

	GLimp_SetCurrentContext( false );

	SDL_LockMutex( smpMutex );
	{
		smpData = nullptr;
		smpDataReady = false;

		// after this, the front end can exit GLimp_FrontEndSleep
		SDL_CondSignal( renderCompletedEvent );

		while ( !smpDataReady )
		{
			SDL_CondWait( renderCommandsEvent, smpMutex );
		}

		data = ( void * ) smpData;
	}
	SDL_UnlockMutex( smpMutex );

	GLimp_SetCurrentContext( true );

	return data;
}

/*
===============
GLimp_FrontEndSleep
===============
*/
void GLimp_FrontEndSleep()
{
	SDL_LockMutex( smpMutex );
	{
		while ( smpData )
		{
			SDL_CondWait( renderCompletedEvent, smpMutex );
		}
	}
	SDL_UnlockMutex( smpMutex );
}

/*
===============
GLimp_SyncRenderThread
===============
*/
void GLimp_SyncRenderThread()
{
	GLimp_FrontEndSleep();

	GLimp_SetCurrentContext( true );
}

/*
===============
GLimp_WakeRenderer
===============
*/
void GLimp_WakeRenderer( void *data )
{
	GLimp_SetCurrentContext( false );

	SDL_LockMutex( smpMutex );
	{
		ASSERT(smpData == nullptr);
		smpData = data;
		smpDataReady = true;

		// after this, the renderer can continue through GLimp_RendererSleep
		SDL_CondSignal( renderCommandsEvent );
	}
	SDL_UnlockMutex( smpMutex );
}

#else

// No SMP - stubs
void GLimp_RenderThreadWrapper( void* )
{
}

bool GLimp_SpawnRenderThread( void ( * )() )
{
	logger.Warn("SMP support was disabled at compile time" );
	return false;
}

void GLimp_ShutdownRenderThread()
{
}

void *GLimp_RendererSleep()
{
	return nullptr;
}

void GLimp_FrontEndSleep()
{
}

void GLimp_SyncRenderThread()
{
}

void GLimp_WakeRenderer( void* )
{
}

#endif

enum class rserr_t
{
  RSERR_OK,

  RSERR_INVALID_FULLSCREEN,
  RSERR_INVALID_MODE,
  RSERR_MISSING_GL,
  RSERR_OLD_GL,

  RSERR_UNKNOWN
};

cvar_t                     *r_allowResize; // make window resizable
cvar_t                     *r_centerWindow;
cvar_t                     *r_displayIndex;
cvar_t                     *r_sdlDriver;

static void GLimp_DestroyContextIfExists();
static void GLimp_DestroyWindowIfExists();

/*
===============
GLimp_Shutdown
===============
*/
void GLimp_Shutdown()
{
	logger.Debug("Shutting down OpenGL subsystem" );

	ri.IN_Shutdown();

#if defined( USE_SMP )

	if ( renderThread != nullptr )
	{
		logger.Notice( "Destroying renderer thread...\n" );
		GLimp_ShutdownRenderThread();
	}

#endif

	GLimp_DestroyContextIfExists();
	GLimp_DestroyWindowIfExists();

	SDL_QuitSubSystem( SDL_INIT_VIDEO );

	memset( &glConfig, 0, sizeof( glConfig ) );
	memset( &glState, 0, sizeof( glState ) );
}

static void GLimp_Minimize()
{
	SDL_MinimizeWindow( window );
}

/*
===============
GLimp_CompareModes
===============
*/
static int GLimp_CompareModes( const void *a, const void *b )
{
	const float ASPECT_EPSILON = 0.001f;
	SDL_Rect    *modeA = ( SDL_Rect * ) a;
	SDL_Rect    *modeB = ( SDL_Rect * ) b;
	float       aspectA = ( float ) modeA->w / ( float ) modeA->h;
	float       aspectB = ( float ) modeB->w / ( float ) modeB->h;
	int         areaA = modeA->w * modeA->h;
	int         areaB = modeB->w * modeB->h;
	float       aspectDiffA = fabs( aspectA - displayAspect );
	float       aspectDiffB = fabs( aspectB - displayAspect );
	float       aspectDiffsDiff = aspectDiffA - aspectDiffB;

	if ( aspectDiffsDiff > ASPECT_EPSILON )
	{
		return 1;
	}
	else if ( aspectDiffsDiff < -ASPECT_EPSILON )
	{
		return -1;
	}
	else
	{
		return areaA - areaB;
	}
}

/*
===============
GLimp_DetectAvailableModes
===============
*/
static bool GLimp_DetectAvailableModes()
{
	char     buf[ MAX_STRING_CHARS ] = { 0 };
	SDL_Rect modes[ 128 ];
	int      numModes = 0;
	int      i;
	SDL_DisplayMode windowMode;
	int      display;

	display = SDL_GetWindowDisplayIndex( window );

	if ( SDL_GetWindowDisplayMode( window, &windowMode ) < 0 )
	{
		logger.Warn("Couldn't get window display mode: %s", SDL_GetError() );
		return false;
	}

	for ( i = 0; i < SDL_GetNumDisplayModes( display ); i++ )
	{
		SDL_DisplayMode mode;

		if ( SDL_GetDisplayMode( display, i, &mode ) < 0 )
		{
			continue;
		}

		if ( !mode.w || !mode.h )
		{
			logger.Notice("Display supports any resolution" );
			return true;
		}

		if ( windowMode.format != mode.format || windowMode.refresh_rate != mode.refresh_rate )
		{
			continue;
		}

		modes[ numModes ].w = mode.w;
		modes[ numModes ].h = mode.h;
		numModes++;
	}

	if ( numModes > 1 )
	{
		qsort( modes, numModes, sizeof( SDL_Rect ), GLimp_CompareModes );
	}

	for ( i = 0; i < numModes; i++ )
	{
		const char *newModeString = va( "%ux%u ", modes[ i ].w, modes[ i ].h );

		if ( strlen( newModeString ) < sizeof( buf ) - strlen( buf ) )
		{
			Q_strcat( buf, sizeof( buf ), newModeString );
		}
		else
		{
			logger.Warn("Skipping mode %ux%x, buffer too small", modes[ i ].w, modes[ i ].h );
		}
	}

	if ( *buf )
	{
		logger.Notice("Available modes: '%s'", buf );
		ri.Cvar_Set( "r_availableModes", buf );
	}

	return true;
}

enum class glProfile {
	UNDEFINED = 0,
	COMPATIBILITY = 1,
	CORE = 2,
};

struct glConfiguration {
	int major;
	int minor;
	glProfile profile;
	int colorBits;
};

static bool operator!=(const glConfiguration& c1, const glConfiguration& c2) {
	return c1.major != c2.major
		|| c1.minor != c2.minor
		|| c1.profile != c2.profile
		|| c1.colorBits != c2.colorBits;
}

static const char* GLimp_getProfileName( glProfile profile )
{
	ASSERT(profile != glProfile::UNDEFINED);
	return profile == glProfile::CORE ? "core" : "compatibility";
}

static void GLimp_SetAttributes( const glConfiguration &configuration )
{
	int perChannelColorBits = configuration.colorBits == 24 ? 8 : 4;

	SDL_GL_SetAttribute( SDL_GL_RED_SIZE, perChannelColorBits );
	SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, perChannelColorBits );
	SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, perChannelColorBits );
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

	if ( !r_glAllowSoftware->integer )
	{
		SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 );
	}

	SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, configuration.major );
	SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, configuration.minor );

	if ( configuration.profile == glProfile::CORE )
	{
		SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
	}
	else
	{
		SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY );
	}

	if ( r_glDebugProfile->integer )
	{
		SDL_GL_SetAttribute( SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG );
	}
}

static bool GLimp_CreateWindow( bool fullscreen, bool bordered, const glConfiguration &configuration )
{
	/* The requested attributes should be set before creating
	an OpenGL window.

	-- http://wiki.libsdl.org/SDL_GL_SetAttribute */
	GLimp_SetAttributes( configuration );

	Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL;

	if ( r_allowResize->integer )
	{
		flags |= SDL_WINDOW_RESIZABLE;
	}

	SDL_Surface *icon = nullptr;

	icon = SDL_CreateRGBSurfaceFrom( ( void * ) CLIENT_WINDOW_ICON.pixel_data,
		CLIENT_WINDOW_ICON.width,
		CLIENT_WINDOW_ICON.height,
		CLIENT_WINDOW_ICON.bytes_per_pixel * 8,
		CLIENT_WINDOW_ICON.bytes_per_pixel * CLIENT_WINDOW_ICON.width,
#ifdef Q3_LITTLE_ENDIAN
		0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
#else
		0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF
#endif
	);

	const char *windowType = nullptr;

	if ( fullscreen )
	{
		flags |= SDL_WINDOW_FULLSCREEN;
		windowType = "fullscreen";
	}

	/* We need to set borderless flag even when fullscreen
	because otherwise when disabling fullscreen the window
	will be bordered while the borderless option is enabled. */

	if ( !bordered )
	{
		flags |= SDL_WINDOW_BORDERLESS;

		/* Don't tell fullscreen window is borderless,
		it's meaningless. */
		if ( ! fullscreen )
		{
			windowType = "borderless";
		}
	}

	int x, y;
	if ( r_centerWindow->integer )
	{
		// center window on specified display
		x = SDL_WINDOWPOS_CENTERED_DISPLAY( r_displayIndex->integer );
		y = SDL_WINDOWPOS_CENTERED_DISPLAY( r_displayIndex->integer );
	}
	else
	{
		x = SDL_WINDOWPOS_UNDEFINED_DISPLAY( r_displayIndex->integer );
		y = SDL_WINDOWPOS_UNDEFINED_DISPLAY( r_displayIndex->integer );
	}

	window = SDL_CreateWindow( CLIENT_WINDOW_TITLE, x, y, glConfig.vidWidth, glConfig.vidHeight, flags );

	if ( window )
	{
		int w, h;
		SDL_GetWindowPosition( window, &x, &y );
		SDL_GetWindowSize( window, &w, &h );
		logger.Debug( "SDL %s%swindow created at %d,%d with %d×%d size",
			windowType ? windowType : "",
			windowType ? " ": "",
			x, y, w, h );
	}
	else
	{
		logger.Warn( "SDL %d×%d %s%swindow not created",
			glConfig.vidWidth, glConfig.vidHeight,
			windowType ? windowType : "",
			windowType ? " ": "" );
		logger.Warn("SDL_CreateWindow failed: %s", SDL_GetError() );
		return false;
	}

	SDL_SetWindowIcon( window, icon );

	SDL_FreeSurface( icon );

	return true;
}

static void GLimp_DestroyContextIfExists()
{
	if ( glContext != nullptr )
	{
		SDL_GL_DeleteContext( glContext );
		glContext = nullptr;
	}
}

static void GLimp_DestroyWindowIfExists()
{
	// Do not let orphaned context alive.
	GLimp_DestroyContextIfExists();

	if ( window != nullptr )
	{
		int x, y, w, h;
		SDL_GetWindowPosition( window, &x, &y );
		SDL_GetWindowSize( window, &w, &h );
		logger.Debug("Destroying %d×%d SDL window at %d,%d", w, h, x, y );
		SDL_DestroyWindow( window );
		window = nullptr;
	}
}

static bool GLimp_CreateContext( const glConfiguration &configuration )
{
	GLimp_DestroyContextIfExists();
	glContext = SDL_GL_CreateContext( window );

	const char* profileName = GLimp_getProfileName( configuration.profile );
	if ( glContext != nullptr )
	{
		logger.Debug( "Valid context: %d-bit OpenGL %d.%d %s",
			configuration.colorBits,
			configuration.major,
			configuration.minor,
			profileName );
	}
	else
	{
		logger.Debug( "Invalid context: %d-bit OpenGL %d.%d %s",
			configuration.colorBits,
			configuration.major,
			configuration.minor,
			profileName );
	}

	return glContext != nullptr;
}

/* GLimp_DestroyWindowIfExists checks if window exists before
destroying it so we can call GLimp_RecreateWindowWhenChange even
if no window is created yet.

It is assumed width, height and other things like that are unchanged,
given vid_restart is called when changing those and then destroying
the window before calling this. */
static bool GLimp_RecreateWindowWhenChange( const bool fullscreen, const bool bordered, const glConfiguration &configuration )
{
	static bool currentFullscreen = false;
	static bool currentBordered = false;
	static glConfiguration currentConfiguration = {};

	if ( window == nullptr
		|| configuration != currentConfiguration )
	{
		currentConfiguration = configuration;

		GLimp_DestroyWindowIfExists();

		if ( !GLimp_CreateWindow( fullscreen, bordered, configuration ) )
		{
			return false;
		}
	}

	if ( fullscreen != currentFullscreen )
	{
		Uint32 flags = fullscreen ? SDL_WINDOW_FULLSCREEN : 0;
		int sdlToggled = SDL_SetWindowFullscreen( window, flags );

		if ( sdlToggled < 0 )
		{
			GLimp_DestroyWindowIfExists();

			if ( !GLimp_CreateWindow( fullscreen, bordered, configuration ) )
			{
				return false;
			}

			const char* windowType = fullscreen ? "fullscreen" : "windowed";
			logger.Debug( "SDL window recreated as %s.", windowType );
		}
		else
		{
			const char* windowType = fullscreen ? "fullscreen" : "windowed";
			logger.Debug( "SDL window set as %s.", windowType );
		}
	}

	if ( bordered != currentBordered )
	{
		SDL_bool sdlBordered = bordered ? SDL_TRUE : SDL_FALSE;

		SDL_SetWindowBordered( window, sdlBordered );

		const char* windowType = bordered ? "bordered" : "borderless";
		logger.Debug( "SDL window set as %s.", windowType );
	}

	currentFullscreen = fullscreen;
	currentBordered = bordered;

	return true;
}

static rserr_t GLimp_SetModeAndResolution( const int mode )
{
	SDL_DisplayMode desktopMode;

	if ( SDL_GetDesktopDisplayMode( r_displayIndex->integer, &desktopMode ) == 0 )
	{
		displayAspect = ( float ) desktopMode.w / ( float ) desktopMode.h;
	}
	else
	{
		memset( &desktopMode, 0, sizeof( SDL_DisplayMode ) );
		displayAspect = 1.333f;
		logger.Warn("Cannot determine display aspect, assuming %.3f: %s", displayAspect, SDL_GetError() );
	}

	logger.Notice("Display aspect: %.3f", displayAspect );

	if ( mode == -2 )
	{
		// use desktop video resolution
		if ( desktopMode.h > 0 )
		{
			glConfig.vidWidth = desktopMode.w;
			glConfig.vidHeight = desktopMode.h;
		}
		else
		{
			glConfig.vidWidth = 640;
			glConfig.vidHeight = 480;
			logger.Warn("Cannot determine display resolution, assuming %dx%d", glConfig.vidWidth, glConfig.vidHeight );
		}

		logger.Notice("Display resolution: %dx%d", glConfig.vidWidth, glConfig.vidHeight);
	}
	else if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, mode ) )
	{
		logger.Notice("Invalid mode %d", mode );
		return rserr_t::RSERR_INVALID_MODE;
	}

	logger.Notice("...setting mode %d: %d×%d", mode, glConfig.vidWidth, glConfig.vidHeight );

	return rserr_t::RSERR_OK;
}

static rserr_t GLimp_ValidateBestContext(
	const int GLEWmajor, glConfiguration &bestValidatedConfiguration, glConfiguration& extendedValidationConfiguration )
{
	/* We iterate known OpenGL versions from highest to lowest,
	iterating core profiles first, then compatibility profile,
	iterating 24-bit display first, then 16-bit.

	Once we get a core profile working, we stop and attempt
	to use 3.2 core whatever the highest version we validated.
	The 3.2 is the oldest version with core profile, meaning
	every extension not in 3.2 code are expected to be loaded
	explicitely. This may make extension loading more predictable.

	For debugging and knowledge purpose we log the best supported
	version even if we're not gonna use it.

	User can request explicit version or profile with related cvars.

	We test down to the lowest known OpenGL version so we can provide
	useful and precise error message when an OpenGL version is too low.

	The idea of going from high to low is to make the engine load fast
	on actual hardware likely to support highest core profile version,
	then spend more time on more rarest configuration. The highest
	loading time affects hardware and related drivers that can't run
	then engine anyway.

	For known OpenGL version,
	see https://en.wikipedia.org/wiki/OpenGL#Version_history

	For information about core, compatibility and forward profiles,
	see https://www.khronos.org/opengl/wiki/OpenGL_Context */

	struct {
		int major;
		int minor;
		glProfile profile;
		bool testByDefault;
	} glSupportArray[] {
		{ 4, 6, glProfile::CORE, false },
		{ 4, 5, glProfile::CORE, false },
		{ 4, 4, glProfile::CORE, false },
		{ 4, 3, glProfile::CORE, false },
		{ 4, 2, glProfile::CORE, false },
		{ 4, 1, glProfile::CORE, false },
		{ 4, 0, glProfile::CORE, false },
		{ 3, 3, glProfile::CORE, false },
		{ 3, 2, glProfile::CORE, true },
		{ 3, 1, glProfile::COMPATIBILITY, false },
		{ 3, 0, glProfile::COMPATIBILITY, false },
		{ 2, 1, glProfile::COMPATIBILITY, true },
		{ 2, 0, glProfile::COMPATIBILITY, true },
		{ 1, 5, glProfile::COMPATIBILITY, true },
		{ 1, 4, glProfile::COMPATIBILITY, true },
		{ 1, 3, glProfile::COMPATIBILITY, true },
		{ 1, 2, glProfile::COMPATIBILITY, true },
		{ 1, 1, glProfile::COMPATIBILITY, true },
		{ 1, 0, glProfile::COMPATIBILITY, true },
	};

	logger.Debug( "Validating best OpenGL context." );

	bool needHighestExtended = !!r_glExtendedValidation->integer;
	for ( int colorBits : {24, 16} )
	{
		for ( auto& row : glSupportArray )
		{
			if ( GLEWmajor < 2 && row.profile == glProfile::CORE )
			{
				// GLEW version < 2.0.0 doesn't support OpenGL core profiles.
				continue;
			}

			if ( !needHighestExtended && !row.testByDefault )
			{
				continue;
			}

			glConfiguration testConfiguration;
			testConfiguration.major = row.major;
			testConfiguration.minor = row.minor;
			testConfiguration.profile = row.profile;
			testConfiguration.colorBits = colorBits;

			if ( !GLimp_RecreateWindowWhenChange( false, false, testConfiguration ) )
			{
				return rserr_t::RSERR_INVALID_MODE;
			}

			if ( GLimp_CreateContext( testConfiguration ) )
			{
				if ( needHighestExtended )
				{
					needHighestExtended = false;
					extendedValidationConfiguration = testConfiguration;
				}

				if ( row.testByDefault )
				{
					bestValidatedConfiguration = testConfiguration;
					return rserr_t::RSERR_OK;
				}
			}
		}
	}

	if ( bestValidatedConfiguration.major == 0 )
	{

		return rserr_t::RSERR_MISSING_GL;
	}

	return rserr_t::RSERR_OLD_GL;
}

static glConfiguration GLimp_ApplyCustomOptions( const int GLEWmajor, const glConfiguration &bestConfiguration )
{
	glConfiguration customConfiguration = {};

	if ( bestConfiguration.profile == glProfile::CORE && !Q_stricmp( r_glProfile->string, "compat" ) )
	{
		logger.Debug( "Compatibility profile is forced by r_glProfile" );

		customConfiguration.profile = glProfile::COMPATIBILITY;
	}

	if ( bestConfiguration.profile == glProfile::COMPATIBILITY && !Q_stricmp( r_glProfile->string, "core" ) )
	{
		if ( GLEWmajor < 2 )
		{
			// GLEW version < 2.0.0 doesn't support OpenGL core profiles.
			logger.Debug( "Core profile is ignored from r_glProfile" );
		}
		else
		{
			logger.Debug( "Core profile is forced by r_glProfile" );

			customConfiguration.profile = glProfile::CORE;
		}
	}

	customConfiguration.major = std::max( 0, r_glMajorVersion->integer );
	customConfiguration.minor = std::max( 0, r_glMinorVersion->integer );

	if ( customConfiguration.major == 0 )
	{
		customConfiguration.major = bestConfiguration.major;
		customConfiguration.minor = bestConfiguration.minor;
	}
	else if ( customConfiguration.major == 1 )
	{
		logger.Warn( "OpenGL %d.%d is not supported, trying %d.%d instead",
			customConfiguration.major,
			customConfiguration.minor,
			bestConfiguration.major,
			bestConfiguration.minor );

		customConfiguration.major = bestConfiguration.major;
		customConfiguration.minor = bestConfiguration.minor;
	}
	else
	{
		if ( customConfiguration.major == 3
			&& customConfiguration.minor < 2
			&& customConfiguration.profile == glProfile::UNDEFINED )
		{
			customConfiguration.profile = glProfile::COMPATIBILITY;
		}
		else if ( customConfiguration.major == 2 )
		{
			if ( customConfiguration.profile == glProfile::UNDEFINED )
			{
				customConfiguration.profile = glProfile::COMPATIBILITY;
			}

			if ( customConfiguration.minor == 0 )
			{
				logger.Warn( "OpenGL 2.0 is not supported, trying 2.1 instead" );

				customConfiguration.minor = 1;
			}
		}

		logger.Debug( "GL version %d.%d is forced by r_glMajorVersion and r_glMinorVersion",
			customConfiguration.major,
			customConfiguration.minor );
	}

	if ( customConfiguration.profile == glProfile::UNDEFINED )
	{
		customConfiguration.profile = bestConfiguration.profile;
	}

	customConfiguration.colorBits = std::max( 0, r_colorbits->integer );

	if ( customConfiguration.colorBits == 0 )
	{
		customConfiguration.colorBits = bestConfiguration.colorBits;
	}
	else
	{
		if ( customConfiguration.colorBits != bestConfiguration.colorBits )
		{
			logger.Debug( "Color framebuffer bitness %d is forced by r_colorbits",
				customConfiguration.colorBits );
		}
	}

	return customConfiguration;
}

static std::string ContextDescription( const glConfiguration& configuration )
{
	return Str::Format( "%d-bit OpenGL %d.%d %s",
		configuration.colorBits,
		configuration.major,
		configuration.minor,
		GLimp_getProfileName( configuration.profile ) );
}

static bool CreateWindowAndContext(
	bool fullscreen, bool bordered,
	Str::StringRef contextAdjective,
	const glConfiguration& customConfiguration)
{
	if ( !GLimp_RecreateWindowWhenChange( fullscreen, bordered, customConfiguration ) )
	{
		logger.Warn( "Failed to create window for %s context - %s",
			contextAdjective,
			ContextDescription( customConfiguration ) );
		return false;
	}

	if ( !GLimp_CreateContext( customConfiguration ) )
	{
		logger.Warn( "Failed to initialize %s context - %s",
			contextAdjective,
			ContextDescription( customConfiguration ) );
		logger.Warn( "SDL_GL_CreateContext failed: %s", SDL_GetError() );
		return false;
	}

	logger.Notice( "Using %s context - %s",
		contextAdjective,
		ContextDescription( customConfiguration ) );
	return true;
}

static void GLimp_RegisterConfiguration( const glConfiguration& highestConfiguration, const glConfiguration &requestedConfiguration )
{
	glConfig2.glHighestMajor = highestConfiguration.major;
	glConfig2.glHighestMinor = highestConfiguration.minor;

	glConfig2.glRequestedMajor = requestedConfiguration.major;
	glConfig2.glRequestedMinor = requestedConfiguration.minor;

	// FIXME: this is missing all the checks and fallbacks that occur when the cvar is modified?
	SDL_GL_SetSwapInterval( r_swapInterval.Get() );

	{
		/* Make sure we don't silence any useful error that would
		already have happened. */
		GL_CheckErrors();

		// Check if we have a core profile.
		int profileBit;
		glGetIntegerv( GL_CONTEXT_PROFILE_MASK, &profileBit );

		/* OpenGL implementations not supporting core profile like the ones only
		implementing OpenGL version older than 3.2 may raise a GL_INVALID_ENUM
		error while implementations supporting core profile may not raise the
		error when forcing an OpenGL version older than 3.2, so we better want
		to catch and silence this expected error. */

		if ( glGetError() != GL_NO_ERROR )
		{
			glConfig2.glCoreProfile = false;
		}
		else
		{
			glConfig2.glCoreProfile = ( profileBit == GL_CONTEXT_CORE_PROFILE_BIT );
		}

		glProfile providedProfile = glConfig2.glCoreProfile ? glProfile::CORE : glProfile::COMPATIBILITY ;
		const char *providedProfileName = GLimp_getProfileName( providedProfile );

		if ( providedProfile != requestedConfiguration.profile )
		{
			const char *requestedProfileName = GLimp_getProfileName( requestedConfiguration.profile );

			logger.Warn( "Provided OpenGL %s profile is not the same as requested %s profile.",
				providedProfileName,
				requestedProfileName );
		}
		else
		{
			logger.Debug( "Provided OpenGL context uses %s profile.",
			providedProfileName );
		}
	}

	{
		int providedRedChannelColorBits;
		SDL_GL_GetAttribute( SDL_GL_RED_SIZE, &providedRedChannelColorBits );

		glConfig.colorBits = providedRedChannelColorBits == 8 ? 24 : 16;

		if ( requestedConfiguration.colorBits != glConfig.colorBits )
		{
			logger.Warn( "Provided OpenGL %d-bit channel depth is not the same as requested %d-bit depth.",
				glConfig.colorBits, requestedConfiguration.colorBits );
		}
		else
		{
			logger.Debug( "Provided OpenGL context uses %d-bit channel depth.", glConfig.colorBits );
		}
	}

	if ( requestedConfiguration.profile == glProfile::CORE )
	{
		// Check if context is forward compatible.
		int contextFlags;
		glGetIntegerv( GL_CONTEXT_FLAGS, &contextFlags );

		glConfig2.glForwardCompatibleContext = contextFlags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT;

		if ( glConfig2.glForwardCompatibleContext )
		{
			logger.Debug( "Provided OpenGL core context is forward compatible." );
		}
		else
		{
			logger.Debug( "Provided OpenGL core context is not forward compatible." );
		}
	}
	else
	{
		glConfig2.glForwardCompatibleContext = false;
	}

	// alphaBits was used by legacy renderer, do we need it for anything?
	// int alphaBits = std::max( 0, r_alphabits->integer );

	/* FIXME: It looks like MSAA was only implemented in legacy renderer.
	int samples = std::max( 0, r_ext_multisample->integer ); */

	{
		int GLmajor, GLminor;
		sscanf( ( const char * ) glGetString( GL_VERSION ), "%d.%d", &GLmajor, &GLminor );

		glConfig2.glMajor = GLmajor;
		glConfig2.glMinor = GLminor;
	}

	/* FIXME: a duplicate of this is done in GLimp_Init, also storing it
	for gfxinfo command. */
	const char *glstring = ( char * ) glGetString( GL_RENDERER );
	logger.Notice("OpenGL Renderer: %s", glstring );
}

static void GLimp_DrawWindowContent()
{
	// Fill window with a dark grey (#141414) background.
	glClearColor( 0.08f, 0.08f, 0.08f, 1.0f );
	glClear( GL_COLOR_BUFFER_BIT );
	GLimp_EndFrame();
}

static rserr_t GLimp_CheckOpenGLVersion( const glConfiguration &requestedConfiguration )
{
	if ( glConfig2.glMajor != requestedConfiguration.major
		|| glConfig2.glMinor != requestedConfiguration.minor )
	{
		logger.Warn( "Provided OpenGL %d.%d is not the same as requested %d.%d version",
			glConfig2.glMajor,
			glConfig2.glMinor,
			requestedConfiguration.major,
			requestedConfiguration.minor );
	}
	else
	{
		logger.Debug( "Provided OpenGL %d.%d version.",
			glConfig2.glMajor,
			glConfig2.glMinor );
	}

	if ( glConfig2.glMajor < 2 || ( glConfig2.glMajor == 2 && glConfig2.glMinor < 1 ) )
	{
		GLimp_DestroyWindowIfExists();

		// Missing shader support, there is no OpenGL 1.x renderer anymore.
		return rserr_t::RSERR_OLD_GL;
	}

	if ( glConfig2.glMajor < 3 || ( glConfig2.glMajor == 3 && glConfig2.glMinor < 2 ) )
	{
		// Shaders are supported, but not all OpenGL 3.x features
		logger.Notice("Using GL3 Renderer in OpenGL 2.x mode..." );
	}
	else
	{
		logger.Notice("Using GL3 Renderer in OpenGL 3.x mode..." );
		glConfig.driverType = glDriverType_t::GLDRV_OPENGL3;
	}

	return rserr_t::RSERR_OK;
}

static void GLimp_CheckGLEW( const glConfiguration &requestedConfiguration )
{
	GLenum glewResult = glewInit();

#ifdef GLEW_ERROR_NO_GLX_DISPLAY
	if ( glewResult != GLEW_OK && glewResult != GLEW_ERROR_NO_GLX_DISPLAY )
#else
	if ( glewResult != GLEW_OK )
#endif
	{
		// glewInit failed, something is seriously wrong

		GLimp_DestroyWindowIfExists();

		const char* requestedProfileName = GLimp_getProfileName( requestedConfiguration.profile );

		Sys::Error( "GLEW initialization failed: %s.\n\n"
			"Engine successfully created\n"
			"%d-bit OpenGL %d.%d %d context,\n"
			"This is a GLEW issue.",
			glewGetErrorString( glewResult ),
			requestedConfiguration.colorBits,
			requestedConfiguration.major,
			requestedConfiguration.minor,
			requestedProfileName );
	}
}

/*
===============
GLimp_SetMode<commit_msg>Use function for string describing OpenGL context<commit_after>/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Daemon source code.

Daemon source code 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 2 of the License,
or (at your option) any later version.

Daemon source code 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 Daemon source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "qcommon/q_shared.h" // Include before SDL.h due to M_PI issue...
#include <SDL.h>

#ifdef USE_SMP
#include <SDL_thread.h>
#endif

#include "renderer/tr_local.h"

#include "sdl_icon.h"
#include "SDL_syswm.h"
#include "framework/CommandSystem.h"
#include "framework/CvarSystem.h"

static Log::Logger logger("glconfig", "", Log::Level::NOTICE);

static Cvar::Modified<Cvar::Cvar<bool>> r_noBorder(
	"r_noBorder", "draw window without border", Cvar::ARCHIVE, false);
static Cvar::Modified<Cvar::Range<Cvar::Cvar<int>>> r_swapInterval(
	"r_swapInterval", "enable vsync on every Nth frame, negative for apdative", Cvar::ARCHIVE, 0, -5, 5 );

SDL_Window *window = nullptr;
static SDL_GLContext glContext = nullptr;

#ifdef USE_SMP
static void GLimp_SetCurrentContext( bool enable )
{
	if ( enable )
	{
		SDL_GL_MakeCurrent( window, glContext );
	}
	else
	{
		SDL_GL_MakeCurrent( window, nullptr );
	}
}

/*
===========================================================

SMP acceleration

===========================================================
*/

/*
 * I have no idea if this will even work...most platforms don't offer
 * thread-safe OpenGL libraries.
 */

static SDL_mutex  *smpMutex = nullptr;
static SDL_cond   *renderCommandsEvent = nullptr;
static SDL_cond   *renderCompletedEvent = nullptr;
static void ( *renderThreadFunction )() = nullptr;
static SDL_Thread *renderThread = nullptr;

/*
===============
GLimp_RenderThreadWrapper
===============
*/
ALIGN_STACK_FOR_MINGW static int GLimp_RenderThreadWrapper( void* )
{
	// These printfs cause race conditions which mess up the console output
	logger.Notice( "Render thread starting\n" );

	renderThreadFunction();

	GLimp_SetCurrentContext( false );

	logger.Notice( "Render thread terminating\n" );

	return 0;
}

/*
===============
GLimp_SpawnRenderThread
===============
*/
bool GLimp_SpawnRenderThread( void ( *function )() )
{
	static bool warned = false;

	if ( !warned )
	{
		logger.Warn( "You enable r_smp at your own risk!\n" );
		warned = true;
	}

	if ( renderThread != nullptr ) /* hopefully just a zombie at this point... */
	{
		logger.Notice( "Already a render thread? Trying to clean it up...\n" );
		GLimp_ShutdownRenderThread();
	}

	smpMutex = SDL_CreateMutex();

	if ( smpMutex == nullptr )
	{
		logger.Notice( "smpMutex creation failed: %s\n", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	renderCommandsEvent = SDL_CreateCond();

	if ( renderCommandsEvent == nullptr )
	{
		logger.Notice( "renderCommandsEvent creation failed: %s\n", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	renderCompletedEvent = SDL_CreateCond();

	if ( renderCompletedEvent == nullptr )
	{
		logger.Notice( "renderCompletedEvent creation failed: %s\n", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	renderThreadFunction = function;
	renderThread = SDL_CreateThread( GLimp_RenderThreadWrapper, "render thread", nullptr );

	if ( renderThread == nullptr )
	{
		logger.Notice("SDL_CreateThread() returned %s", SDL_GetError() );
		GLimp_ShutdownRenderThread();
		return false;
	}

	return true;
}

/*
===============
GLimp_ShutdownRenderThread
===============
*/
void GLimp_ShutdownRenderThread()
{
	if ( renderThread != nullptr )
	{
		GLimp_WakeRenderer( nullptr );
		SDL_WaitThread( renderThread, nullptr );
		renderThread = nullptr;
		glConfig.smpActive = false;
	}

	if ( smpMutex != nullptr )
	{
		SDL_DestroyMutex( smpMutex );
		smpMutex = nullptr;
	}

	if ( renderCommandsEvent != nullptr )
	{
		SDL_DestroyCond( renderCommandsEvent );
		renderCommandsEvent = nullptr;
	}

	if ( renderCompletedEvent != nullptr )
	{
		SDL_DestroyCond( renderCompletedEvent );
		renderCompletedEvent = nullptr;
	}

	renderThreadFunction = nullptr;
}

static volatile void     *smpData = nullptr;
static volatile bool smpDataReady;

/*
===============
GLimp_RendererSleep
===============
*/
void           *GLimp_RendererSleep()
{
	void *data = nullptr;

	GLimp_SetCurrentContext( false );

	SDL_LockMutex( smpMutex );
	{
		smpData = nullptr;
		smpDataReady = false;

		// after this, the front end can exit GLimp_FrontEndSleep
		SDL_CondSignal( renderCompletedEvent );

		while ( !smpDataReady )
		{
			SDL_CondWait( renderCommandsEvent, smpMutex );
		}

		data = ( void * ) smpData;
	}
	SDL_UnlockMutex( smpMutex );

	GLimp_SetCurrentContext( true );

	return data;
}

/*
===============
GLimp_FrontEndSleep
===============
*/
void GLimp_FrontEndSleep()
{
	SDL_LockMutex( smpMutex );
	{
		while ( smpData )
		{
			SDL_CondWait( renderCompletedEvent, smpMutex );
		}
	}
	SDL_UnlockMutex( smpMutex );
}

/*
===============
GLimp_SyncRenderThread
===============
*/
void GLimp_SyncRenderThread()
{
	GLimp_FrontEndSleep();

	GLimp_SetCurrentContext( true );
}

/*
===============
GLimp_WakeRenderer
===============
*/
void GLimp_WakeRenderer( void *data )
{
	GLimp_SetCurrentContext( false );

	SDL_LockMutex( smpMutex );
	{
		ASSERT(smpData == nullptr);
		smpData = data;
		smpDataReady = true;

		// after this, the renderer can continue through GLimp_RendererSleep
		SDL_CondSignal( renderCommandsEvent );
	}
	SDL_UnlockMutex( smpMutex );
}

#else

// No SMP - stubs
void GLimp_RenderThreadWrapper( void* )
{
}

bool GLimp_SpawnRenderThread( void ( * )() )
{
	logger.Warn("SMP support was disabled at compile time" );
	return false;
}

void GLimp_ShutdownRenderThread()
{
}

void *GLimp_RendererSleep()
{
	return nullptr;
}

void GLimp_FrontEndSleep()
{
}

void GLimp_SyncRenderThread()
{
}

void GLimp_WakeRenderer( void* )
{
}

#endif

enum class rserr_t
{
  RSERR_OK,

  RSERR_INVALID_FULLSCREEN,
  RSERR_INVALID_MODE,
  RSERR_MISSING_GL,
  RSERR_OLD_GL,

  RSERR_UNKNOWN
};

cvar_t                     *r_allowResize; // make window resizable
cvar_t                     *r_centerWindow;
cvar_t                     *r_displayIndex;
cvar_t                     *r_sdlDriver;

static void GLimp_DestroyContextIfExists();
static void GLimp_DestroyWindowIfExists();

/*
===============
GLimp_Shutdown
===============
*/
void GLimp_Shutdown()
{
	logger.Debug("Shutting down OpenGL subsystem" );

	ri.IN_Shutdown();

#if defined( USE_SMP )

	if ( renderThread != nullptr )
	{
		logger.Notice( "Destroying renderer thread...\n" );
		GLimp_ShutdownRenderThread();
	}

#endif

	GLimp_DestroyContextIfExists();
	GLimp_DestroyWindowIfExists();

	SDL_QuitSubSystem( SDL_INIT_VIDEO );

	memset( &glConfig, 0, sizeof( glConfig ) );
	memset( &glState, 0, sizeof( glState ) );
}

static void GLimp_Minimize()
{
	SDL_MinimizeWindow( window );
}

/*
===============
GLimp_CompareModes
===============
*/
static int GLimp_CompareModes( const void *a, const void *b )
{
	const float ASPECT_EPSILON = 0.001f;
	SDL_Rect    *modeA = ( SDL_Rect * ) a;
	SDL_Rect    *modeB = ( SDL_Rect * ) b;
	float       aspectA = ( float ) modeA->w / ( float ) modeA->h;
	float       aspectB = ( float ) modeB->w / ( float ) modeB->h;
	int         areaA = modeA->w * modeA->h;
	int         areaB = modeB->w * modeB->h;
	float       aspectDiffA = fabs( aspectA - displayAspect );
	float       aspectDiffB = fabs( aspectB - displayAspect );
	float       aspectDiffsDiff = aspectDiffA - aspectDiffB;

	if ( aspectDiffsDiff > ASPECT_EPSILON )
	{
		return 1;
	}
	else if ( aspectDiffsDiff < -ASPECT_EPSILON )
	{
		return -1;
	}
	else
	{
		return areaA - areaB;
	}
}

/*
===============
GLimp_DetectAvailableModes
===============
*/
static bool GLimp_DetectAvailableModes()
{
	char     buf[ MAX_STRING_CHARS ] = { 0 };
	SDL_Rect modes[ 128 ];
	int      numModes = 0;
	int      i;
	SDL_DisplayMode windowMode;
	int      display;

	display = SDL_GetWindowDisplayIndex( window );

	if ( SDL_GetWindowDisplayMode( window, &windowMode ) < 0 )
	{
		logger.Warn("Couldn't get window display mode: %s", SDL_GetError() );
		return false;
	}

	for ( i = 0; i < SDL_GetNumDisplayModes( display ); i++ )
	{
		SDL_DisplayMode mode;

		if ( SDL_GetDisplayMode( display, i, &mode ) < 0 )
		{
			continue;
		}

		if ( !mode.w || !mode.h )
		{
			logger.Notice("Display supports any resolution" );
			return true;
		}

		if ( windowMode.format != mode.format || windowMode.refresh_rate != mode.refresh_rate )
		{
			continue;
		}

		modes[ numModes ].w = mode.w;
		modes[ numModes ].h = mode.h;
		numModes++;
	}

	if ( numModes > 1 )
	{
		qsort( modes, numModes, sizeof( SDL_Rect ), GLimp_CompareModes );
	}

	for ( i = 0; i < numModes; i++ )
	{
		const char *newModeString = va( "%ux%u ", modes[ i ].w, modes[ i ].h );

		if ( strlen( newModeString ) < sizeof( buf ) - strlen( buf ) )
		{
			Q_strcat( buf, sizeof( buf ), newModeString );
		}
		else
		{
			logger.Warn("Skipping mode %ux%x, buffer too small", modes[ i ].w, modes[ i ].h );
		}
	}

	if ( *buf )
	{
		logger.Notice("Available modes: '%s'", buf );
		ri.Cvar_Set( "r_availableModes", buf );
	}

	return true;
}

enum class glProfile {
	UNDEFINED = 0,
	COMPATIBILITY = 1,
	CORE = 2,
};

struct glConfiguration {
	int major;
	int minor;
	glProfile profile;
	int colorBits;
};

static bool operator!=(const glConfiguration& c1, const glConfiguration& c2) {
	return c1.major != c2.major
		|| c1.minor != c2.minor
		|| c1.profile != c2.profile
		|| c1.colorBits != c2.colorBits;
}

static const char* GLimp_getProfileName( glProfile profile )
{
	ASSERT(profile != glProfile::UNDEFINED);
	return profile == glProfile::CORE ? "core" : "compatibility";
}

static std::string ContextDescription( const glConfiguration& configuration )
{
	return Str::Format( "%d-bit OpenGL %d.%d %s",
		configuration.colorBits,
		configuration.major,
		configuration.minor,
		GLimp_getProfileName( configuration.profile ) );
}

static void GLimp_SetAttributes( const glConfiguration &configuration )
{
	int perChannelColorBits = configuration.colorBits == 24 ? 8 : 4;

	SDL_GL_SetAttribute( SDL_GL_RED_SIZE, perChannelColorBits );
	SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, perChannelColorBits );
	SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, perChannelColorBits );
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

	if ( !r_glAllowSoftware->integer )
	{
		SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 );
	}

	SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, configuration.major );
	SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, configuration.minor );

	if ( configuration.profile == glProfile::CORE )
	{
		SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
	}
	else
	{
		SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY );
	}

	if ( r_glDebugProfile->integer )
	{
		SDL_GL_SetAttribute( SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG );
	}
}

static bool GLimp_CreateWindow( bool fullscreen, bool bordered, const glConfiguration &configuration )
{
	/* The requested attributes should be set before creating
	an OpenGL window.

	-- http://wiki.libsdl.org/SDL_GL_SetAttribute */
	GLimp_SetAttributes( configuration );

	Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL;

	if ( r_allowResize->integer )
	{
		flags |= SDL_WINDOW_RESIZABLE;
	}

	SDL_Surface *icon = nullptr;

	icon = SDL_CreateRGBSurfaceFrom( ( void * ) CLIENT_WINDOW_ICON.pixel_data,
		CLIENT_WINDOW_ICON.width,
		CLIENT_WINDOW_ICON.height,
		CLIENT_WINDOW_ICON.bytes_per_pixel * 8,
		CLIENT_WINDOW_ICON.bytes_per_pixel * CLIENT_WINDOW_ICON.width,
#ifdef Q3_LITTLE_ENDIAN
		0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
#else
		0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF
#endif
	);

	const char *windowType = nullptr;

	if ( fullscreen )
	{
		flags |= SDL_WINDOW_FULLSCREEN;
		windowType = "fullscreen";
	}

	/* We need to set borderless flag even when fullscreen
	because otherwise when disabling fullscreen the window
	will be bordered while the borderless option is enabled. */

	if ( !bordered )
	{
		flags |= SDL_WINDOW_BORDERLESS;

		/* Don't tell fullscreen window is borderless,
		it's meaningless. */
		if ( ! fullscreen )
		{
			windowType = "borderless";
		}
	}

	int x, y;
	if ( r_centerWindow->integer )
	{
		// center window on specified display
		x = SDL_WINDOWPOS_CENTERED_DISPLAY( r_displayIndex->integer );
		y = SDL_WINDOWPOS_CENTERED_DISPLAY( r_displayIndex->integer );
	}
	else
	{
		x = SDL_WINDOWPOS_UNDEFINED_DISPLAY( r_displayIndex->integer );
		y = SDL_WINDOWPOS_UNDEFINED_DISPLAY( r_displayIndex->integer );
	}

	window = SDL_CreateWindow( CLIENT_WINDOW_TITLE, x, y, glConfig.vidWidth, glConfig.vidHeight, flags );

	if ( window )
	{
		int w, h;
		SDL_GetWindowPosition( window, &x, &y );
		SDL_GetWindowSize( window, &w, &h );
		logger.Debug( "SDL %s%swindow created at %d,%d with %d×%d size",
			windowType ? windowType : "",
			windowType ? " ": "",
			x, y, w, h );
	}
	else
	{
		logger.Warn( "SDL %d×%d %s%swindow not created",
			glConfig.vidWidth, glConfig.vidHeight,
			windowType ? windowType : "",
			windowType ? " ": "" );
		logger.Warn("SDL_CreateWindow failed: %s", SDL_GetError() );
		return false;
	}

	SDL_SetWindowIcon( window, icon );

	SDL_FreeSurface( icon );

	return true;
}

static void GLimp_DestroyContextIfExists()
{
	if ( glContext != nullptr )
	{
		SDL_GL_DeleteContext( glContext );
		glContext = nullptr;
	}
}

static void GLimp_DestroyWindowIfExists()
{
	// Do not let orphaned context alive.
	GLimp_DestroyContextIfExists();

	if ( window != nullptr )
	{
		int x, y, w, h;
		SDL_GetWindowPosition( window, &x, &y );
		SDL_GetWindowSize( window, &w, &h );
		logger.Debug("Destroying %d×%d SDL window at %d,%d", w, h, x, y );
		SDL_DestroyWindow( window );
		window = nullptr;
	}
}

static bool GLimp_CreateContext( const glConfiguration &configuration )
{
	GLimp_DestroyContextIfExists();
	glContext = SDL_GL_CreateContext( window );

	if ( glContext != nullptr )
	{
		logger.Debug( "Valid context: %s", ContextDescription( configuration ) );
	}
	else
	{
		logger.Debug( "Invalid context: %s", ContextDescription( configuration ) );
	}

	return glContext != nullptr;
}

/* GLimp_DestroyWindowIfExists checks if window exists before
destroying it so we can call GLimp_RecreateWindowWhenChange even
if no window is created yet.

It is assumed width, height and other things like that are unchanged,
given vid_restart is called when changing those and then destroying
the window before calling this. */
static bool GLimp_RecreateWindowWhenChange( const bool fullscreen, const bool bordered, const glConfiguration &configuration )
{
	static bool currentFullscreen = false;
	static bool currentBordered = false;
	static glConfiguration currentConfiguration = {};

	if ( window == nullptr
		|| configuration != currentConfiguration )
	{
		currentConfiguration = configuration;

		GLimp_DestroyWindowIfExists();

		if ( !GLimp_CreateWindow( fullscreen, bordered, configuration ) )
		{
			return false;
		}
	}

	if ( fullscreen != currentFullscreen )
	{
		Uint32 flags = fullscreen ? SDL_WINDOW_FULLSCREEN : 0;
		int sdlToggled = SDL_SetWindowFullscreen( window, flags );

		if ( sdlToggled < 0 )
		{
			GLimp_DestroyWindowIfExists();

			if ( !GLimp_CreateWindow( fullscreen, bordered, configuration ) )
			{
				return false;
			}

			const char* windowType = fullscreen ? "fullscreen" : "windowed";
			logger.Debug( "SDL window recreated as %s.", windowType );
		}
		else
		{
			const char* windowType = fullscreen ? "fullscreen" : "windowed";
			logger.Debug( "SDL window set as %s.", windowType );
		}
	}

	if ( bordered != currentBordered )
	{
		SDL_bool sdlBordered = bordered ? SDL_TRUE : SDL_FALSE;

		SDL_SetWindowBordered( window, sdlBordered );

		const char* windowType = bordered ? "bordered" : "borderless";
		logger.Debug( "SDL window set as %s.", windowType );
	}

	currentFullscreen = fullscreen;
	currentBordered = bordered;

	return true;
}

static rserr_t GLimp_SetModeAndResolution( const int mode )
{
	SDL_DisplayMode desktopMode;

	if ( SDL_GetDesktopDisplayMode( r_displayIndex->integer, &desktopMode ) == 0 )
	{
		displayAspect = ( float ) desktopMode.w / ( float ) desktopMode.h;
	}
	else
	{
		memset( &desktopMode, 0, sizeof( SDL_DisplayMode ) );
		displayAspect = 1.333f;
		logger.Warn("Cannot determine display aspect, assuming %.3f: %s", displayAspect, SDL_GetError() );
	}

	logger.Notice("Display aspect: %.3f", displayAspect );

	if ( mode == -2 )
	{
		// use desktop video resolution
		if ( desktopMode.h > 0 )
		{
			glConfig.vidWidth = desktopMode.w;
			glConfig.vidHeight = desktopMode.h;
		}
		else
		{
			glConfig.vidWidth = 640;
			glConfig.vidHeight = 480;
			logger.Warn("Cannot determine display resolution, assuming %dx%d", glConfig.vidWidth, glConfig.vidHeight );
		}

		logger.Notice("Display resolution: %dx%d", glConfig.vidWidth, glConfig.vidHeight);
	}
	else if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, mode ) )
	{
		logger.Notice("Invalid mode %d", mode );
		return rserr_t::RSERR_INVALID_MODE;
	}

	logger.Notice("...setting mode %d: %d×%d", mode, glConfig.vidWidth, glConfig.vidHeight );

	return rserr_t::RSERR_OK;
}

static rserr_t GLimp_ValidateBestContext(
	const int GLEWmajor, glConfiguration &bestValidatedConfiguration, glConfiguration& extendedValidationConfiguration )
{
	/* We iterate known OpenGL versions from highest to lowest,
	iterating core profiles first, then compatibility profile,
	iterating 24-bit display first, then 16-bit.

	Once we get a core profile working, we stop and attempt
	to use 3.2 core whatever the highest version we validated.
	The 3.2 is the oldest version with core profile, meaning
	every extension not in 3.2 code are expected to be loaded
	explicitely. This may make extension loading more predictable.

	For debugging and knowledge purpose we log the best supported
	version even if we're not gonna use it.

	User can request explicit version or profile with related cvars.

	We test down to the lowest known OpenGL version so we can provide
	useful and precise error message when an OpenGL version is too low.

	The idea of going from high to low is to make the engine load fast
	on actual hardware likely to support highest core profile version,
	then spend more time on more rarest configuration. The highest
	loading time affects hardware and related drivers that can't run
	then engine anyway.

	For known OpenGL version,
	see https://en.wikipedia.org/wiki/OpenGL#Version_history

	For information about core, compatibility and forward profiles,
	see https://www.khronos.org/opengl/wiki/OpenGL_Context */

	struct {
		int major;
		int minor;
		glProfile profile;
		bool testByDefault;
	} glSupportArray[] {
		{ 4, 6, glProfile::CORE, false },
		{ 4, 5, glProfile::CORE, false },
		{ 4, 4, glProfile::CORE, false },
		{ 4, 3, glProfile::CORE, false },
		{ 4, 2, glProfile::CORE, false },
		{ 4, 1, glProfile::CORE, false },
		{ 4, 0, glProfile::CORE, false },
		{ 3, 3, glProfile::CORE, false },
		{ 3, 2, glProfile::CORE, true },
		{ 3, 1, glProfile::COMPATIBILITY, false },
		{ 3, 0, glProfile::COMPATIBILITY, false },
		{ 2, 1, glProfile::COMPATIBILITY, true },
		{ 2, 0, glProfile::COMPATIBILITY, true },
		{ 1, 5, glProfile::COMPATIBILITY, true },
		{ 1, 4, glProfile::COMPATIBILITY, true },
		{ 1, 3, glProfile::COMPATIBILITY, true },
		{ 1, 2, glProfile::COMPATIBILITY, true },
		{ 1, 1, glProfile::COMPATIBILITY, true },
		{ 1, 0, glProfile::COMPATIBILITY, true },
	};

	logger.Debug( "Validating best OpenGL context." );

	bool needHighestExtended = !!r_glExtendedValidation->integer;
	for ( int colorBits : {24, 16} )
	{
		for ( auto& row : glSupportArray )
		{
			if ( GLEWmajor < 2 && row.profile == glProfile::CORE )
			{
				// GLEW version < 2.0.0 doesn't support OpenGL core profiles.
				continue;
			}

			if ( !needHighestExtended && !row.testByDefault )
			{
				continue;
			}

			glConfiguration testConfiguration;
			testConfiguration.major = row.major;
			testConfiguration.minor = row.minor;
			testConfiguration.profile = row.profile;
			testConfiguration.colorBits = colorBits;

			if ( !GLimp_RecreateWindowWhenChange( false, false, testConfiguration ) )
			{
				return rserr_t::RSERR_INVALID_MODE;
			}

			if ( GLimp_CreateContext( testConfiguration ) )
			{
				if ( needHighestExtended )
				{
					needHighestExtended = false;
					extendedValidationConfiguration = testConfiguration;
				}

				if ( row.testByDefault )
				{
					bestValidatedConfiguration = testConfiguration;
					return rserr_t::RSERR_OK;
				}
			}
		}
	}

	if ( bestValidatedConfiguration.major == 0 )
	{

		return rserr_t::RSERR_MISSING_GL;
	}

	return rserr_t::RSERR_OLD_GL;
}

static glConfiguration GLimp_ApplyCustomOptions( const int GLEWmajor, const glConfiguration &bestConfiguration )
{
	glConfiguration customConfiguration = {};

	if ( bestConfiguration.profile == glProfile::CORE && !Q_stricmp( r_glProfile->string, "compat" ) )
	{
		logger.Debug( "Compatibility profile is forced by r_glProfile" );

		customConfiguration.profile = glProfile::COMPATIBILITY;
	}

	if ( bestConfiguration.profile == glProfile::COMPATIBILITY && !Q_stricmp( r_glProfile->string, "core" ) )
	{
		if ( GLEWmajor < 2 )
		{
			// GLEW version < 2.0.0 doesn't support OpenGL core profiles.
			logger.Debug( "Core profile is ignored from r_glProfile" );
		}
		else
		{
			logger.Debug( "Core profile is forced by r_glProfile" );

			customConfiguration.profile = glProfile::CORE;
		}
	}

	customConfiguration.major = std::max( 0, r_glMajorVersion->integer );
	customConfiguration.minor = std::max( 0, r_glMinorVersion->integer );

	if ( customConfiguration.major == 0 )
	{
		customConfiguration.major = bestConfiguration.major;
		customConfiguration.minor = bestConfiguration.minor;
	}
	else if ( customConfiguration.major == 1 )
	{
		logger.Warn( "OpenGL %d.%d is not supported, trying %d.%d instead",
			customConfiguration.major,
			customConfiguration.minor,
			bestConfiguration.major,
			bestConfiguration.minor );

		customConfiguration.major = bestConfiguration.major;
		customConfiguration.minor = bestConfiguration.minor;
	}
	else
	{
		if ( customConfiguration.major == 3
			&& customConfiguration.minor < 2
			&& customConfiguration.profile == glProfile::UNDEFINED )
		{
			customConfiguration.profile = glProfile::COMPATIBILITY;
		}
		else if ( customConfiguration.major == 2 )
		{
			if ( customConfiguration.profile == glProfile::UNDEFINED )
			{
				customConfiguration.profile = glProfile::COMPATIBILITY;
			}

			if ( customConfiguration.minor == 0 )
			{
				logger.Warn( "OpenGL 2.0 is not supported, trying 2.1 instead" );

				customConfiguration.minor = 1;
			}
		}

		logger.Debug( "GL version %d.%d is forced by r_glMajorVersion and r_glMinorVersion",
			customConfiguration.major,
			customConfiguration.minor );
	}

	if ( customConfiguration.profile == glProfile::UNDEFINED )
	{
		customConfiguration.profile = bestConfiguration.profile;
	}

	customConfiguration.colorBits = std::max( 0, r_colorbits->integer );

	if ( customConfiguration.colorBits == 0 )
	{
		customConfiguration.colorBits = bestConfiguration.colorBits;
	}
	else
	{
		if ( customConfiguration.colorBits != bestConfiguration.colorBits )
		{
			logger.Debug( "Color framebuffer bitness %d is forced by r_colorbits",
				customConfiguration.colorBits );
		}
	}

	return customConfiguration;
}

static bool CreateWindowAndContext(
	bool fullscreen, bool bordered,
	Str::StringRef contextAdjective,
	const glConfiguration& customConfiguration)
{
	if ( !GLimp_RecreateWindowWhenChange( fullscreen, bordered, customConfiguration ) )
	{
		logger.Warn( "Failed to create window for %s context - %s",
			contextAdjective,
			ContextDescription( customConfiguration ) );
		return false;
	}

	if ( !GLimp_CreateContext( customConfiguration ) )
	{
		logger.Warn( "Failed to initialize %s context - %s",
			contextAdjective,
			ContextDescription( customConfiguration ) );
		logger.Warn( "SDL_GL_CreateContext failed: %s", SDL_GetError() );
		return false;
	}

	logger.Notice( "Using %s context - %s",
		contextAdjective,
		ContextDescription( customConfiguration ) );
	return true;
}

static void GLimp_RegisterConfiguration( const glConfiguration& highestConfiguration, const glConfiguration &requestedConfiguration )
{
	glConfig2.glHighestMajor = highestConfiguration.major;
	glConfig2.glHighestMinor = highestConfiguration.minor;

	glConfig2.glRequestedMajor = requestedConfiguration.major;
	glConfig2.glRequestedMinor = requestedConfiguration.minor;

	// FIXME: this is missing all the checks and fallbacks that occur when the cvar is modified?
	SDL_GL_SetSwapInterval( r_swapInterval.Get() );

	{
		/* Make sure we don't silence any useful error that would
		already have happened. */
		GL_CheckErrors();

		// Check if we have a core profile.
		int profileBit;
		glGetIntegerv( GL_CONTEXT_PROFILE_MASK, &profileBit );

		/* OpenGL implementations not supporting core profile like the ones only
		implementing OpenGL version older than 3.2 may raise a GL_INVALID_ENUM
		error while implementations supporting core profile may not raise the
		error when forcing an OpenGL version older than 3.2, so we better want
		to catch and silence this expected error. */

		if ( glGetError() != GL_NO_ERROR )
		{
			glConfig2.glCoreProfile = false;
		}
		else
		{
			glConfig2.glCoreProfile = ( profileBit == GL_CONTEXT_CORE_PROFILE_BIT );
		}

		glProfile providedProfile = glConfig2.glCoreProfile ? glProfile::CORE : glProfile::COMPATIBILITY ;
		const char *providedProfileName = GLimp_getProfileName( providedProfile );

		if ( providedProfile != requestedConfiguration.profile )
		{
			const char *requestedProfileName = GLimp_getProfileName( requestedConfiguration.profile );

			logger.Warn( "Provided OpenGL %s profile is not the same as requested %s profile.",
				providedProfileName,
				requestedProfileName );
		}
		else
		{
			logger.Debug( "Provided OpenGL context uses %s profile.",
			providedProfileName );
		}
	}

	{
		int providedRedChannelColorBits;
		SDL_GL_GetAttribute( SDL_GL_RED_SIZE, &providedRedChannelColorBits );

		glConfig.colorBits = providedRedChannelColorBits == 8 ? 24 : 16;

		if ( requestedConfiguration.colorBits != glConfig.colorBits )
		{
			logger.Warn( "Provided OpenGL %d-bit channel depth is not the same as requested %d-bit depth.",
				glConfig.colorBits, requestedConfiguration.colorBits );
		}
		else
		{
			logger.Debug( "Provided OpenGL context uses %d-bit channel depth.", glConfig.colorBits );
		}
	}

	if ( requestedConfiguration.profile == glProfile::CORE )
	{
		// Check if context is forward compatible.
		int contextFlags;
		glGetIntegerv( GL_CONTEXT_FLAGS, &contextFlags );

		glConfig2.glForwardCompatibleContext = contextFlags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT;

		if ( glConfig2.glForwardCompatibleContext )
		{
			logger.Debug( "Provided OpenGL core context is forward compatible." );
		}
		else
		{
			logger.Debug( "Provided OpenGL core context is not forward compatible." );
		}
	}
	else
	{
		glConfig2.glForwardCompatibleContext = false;
	}

	// alphaBits was used by legacy renderer, do we need it for anything?
	// int alphaBits = std::max( 0, r_alphabits->integer );

	/* FIXME: It looks like MSAA was only implemented in legacy renderer.
	int samples = std::max( 0, r_ext_multisample->integer ); */

	{
		int GLmajor, GLminor;
		sscanf( ( const char * ) glGetString( GL_VERSION ), "%d.%d", &GLmajor, &GLminor );

		glConfig2.glMajor = GLmajor;
		glConfig2.glMinor = GLminor;
	}

	/* FIXME: a duplicate of this is done in GLimp_Init, also storing it
	for gfxinfo command. */
	const char *glstring = ( char * ) glGetString( GL_RENDERER );
	logger.Notice("OpenGL Renderer: %s", glstring );
}

static void GLimp_DrawWindowContent()
{
	// Fill window with a dark grey (#141414) background.
	glClearColor( 0.08f, 0.08f, 0.08f, 1.0f );
	glClear( GL_COLOR_BUFFER_BIT );
	GLimp_EndFrame();
}

static rserr_t GLimp_CheckOpenGLVersion( const glConfiguration &requestedConfiguration )
{
	if ( glConfig2.glMajor != requestedConfiguration.major
		|| glConfig2.glMinor != requestedConfiguration.minor )
	{
		logger.Warn( "Provided OpenGL %d.%d is not the same as requested %d.%d version",
			glConfig2.glMajor,
			glConfig2.glMinor,
			requestedConfiguration.major,
			requestedConfiguration.minor );
	}
	else
	{
		logger.Debug( "Provided OpenGL %d.%d version.",
			glConfig2.glMajor,
			glConfig2.glMinor );
	}

	if ( glConfig2.glMajor < 2 || ( glConfig2.glMajor == 2 && glConfig2.glMinor < 1 ) )
	{
		GLimp_DestroyWindowIfExists();

		// Missing shader support, there is no OpenGL 1.x renderer anymore.
		return rserr_t::RSERR_OLD_GL;
	}

	if ( glConfig2.glMajor < 3 || ( glConfig2.glMajor == 3 && glConfig2.glMinor < 2 ) )
	{
		// Shaders are supported, but not all OpenGL 3.x features
		logger.Notice("Using GL3 Renderer in OpenGL 2.x mode..." );
	}
	else
	{
		logger.Notice("Using GL3 Renderer in OpenGL 3.x mode..." );
		glConfig.driverType = glDriverType_t::GLDRV_OPENGL3;
	}

	return rserr_t::RSERR_OK;
}

static void GLimp_CheckGLEW( const glConfiguration &requestedConfiguration )
{
	GLenum glewResult = glewInit();

#ifdef GLEW_ERROR_NO_GLX_DISPLAY
	if ( glewResult != GLEW_OK && glewResult != GLEW_ERROR_NO_GLX_DISPLAY )
#else
	if ( glewResult != GLEW_OK )
#endif
	{
		// glewInit failed, something is seriously wrong

		GLimp_DestroyWindowIfExists();

		Sys::Error( "GLEW initialization failed: %s.\n\n"
			"Engine successfully created\n"
			"%s context,\n"
			"This is a GLEW issue.",
			glewGetErrorString( glewResult ),
			ContextDescription( requestedConfiguration ) );
	}
}

/*
===============
GLimp_SetMode
{'fraction_non_alphanumeric': 0.12334953124233053, 'fraction_numerical': 0.009227899671133364, 'mean_word_length': 4.229742448874818, 'pattern_counts': {'":': 4, '<': 49, '<?xml version=': 0, '>': 73, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1181384', 'n_tokens_mistral': 22105, 'n_tokens_neox': 19796, 'n_words': 5088}