/* vid_sdl.c - SDL vid component Copyright (C) 2018 a1batross 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. */ #if !XASH_DEDICATED #include #include #include "common.h" #include "client.h" #include "vid_common.h" #include "platform/sdl/events.h" static vidmode_t *vidmodes = NULL; static int num_vidmodes = 0; static void GL_SetupAttributes( void ); struct { int prev_width, prev_height; } sdlState = { 640, 480 }; struct { #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Renderer *renderer; SDL_Texture *tex; #endif int width, height; SDL_Surface *surf; SDL_Surface *win; } sw; qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) { sw.width = width; sw.height = height; #if SDL_VERSION_ATLEAST(2, 0, 0) if( sw.renderer ) { unsigned int format = SDL_GetWindowPixelFormat( host.hWnd ); SDL_RenderSetLogicalSize(sw.renderer, refState.width, refState.height); if( sw.tex ) SDL_DestroyTexture( sw.tex ); // guess if( format == SDL_PIXELFORMAT_UNKNOWN ) { if( refState.desktopBitsPixel == 16 ) format = SDL_PIXELFORMAT_RGB565; else format = SDL_PIXELFORMAT_RGBA8888; } // we can only copy fast 16 or 32 bits // SDL_Renderer does not allow zero-copy, so 24 bits will be ineffective if( !( SDL_BYTESPERPIXEL(format) == 2 || SDL_BYTESPERPIXEL(format) == 4 ) ) format = SDL_PIXELFORMAT_RGBA8888; sw.tex = SDL_CreateTexture(sw.renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height); // fallback if( !sw.tex && format != SDL_PIXELFORMAT_RGBA8888 ) { format = SDL_PIXELFORMAT_RGBA8888; sw.tex = SDL_CreateTexture(sw.renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height); } if( !sw.tex ) { SDL_DestroyRenderer( sw.renderer ); sw.renderer = NULL; } else { void *pixels; int pitch; if( !SDL_LockTexture(sw.tex, NULL, &pixels, &pitch ) ) { int bits; uint amask; // lock successfull, release SDL_UnlockTexture(sw.tex); // enough for building blitter tables SDL_PixelFormatEnumToMasks( format, &bits, r, g, b, &amask ); *bpp = SDL_BYTESPERPIXEL(format); *stride = pitch / *bpp; return true; } // fallback to surf SDL_DestroyTexture(sw.tex); sw.tex = NULL; SDL_DestroyRenderer(sw.renderer); sw.renderer = NULL; } } #endif #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( !sw.renderer ) { sw.win = SDL_GetWindowSurface( host.hWnd ); #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) { sw.win = SDL_GetVideoSurface(); #endif // sdl will create renderer if hw framebuffer unavailiable, so cannot fallback here // if it is failed, it is not possible to draw with SDL in REF_SOFTWARE mode if( !sw.win ) { Sys_Warn("failed to initialize software output, try enable sw_glblit"); return false; } *bpp = sw.win->format->BytesPerPixel; *r = sw.win->format->Rmask; *g = sw.win->format->Gmask; *b = sw.win->format->Bmask; *stride = sw.win->pitch / sw.win->format->BytesPerPixel; /// TODO: check somehow if ref_soft can handle native format #if 0 { sw.surf = SDL_CreateRGBSurfaceWithFormat( 0, width, height, 16, SDL_PIXELFORMAT_RGB565 ); if( !sw.surf ) Sys_Error(SDL_GetError()); } #endif } // we can't create ref_soft buffer return false; } void *SW_LockBuffer( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( sw.renderer ) { void *pixels; int stride; if( SDL_LockTexture(sw.tex, NULL, &pixels, &stride ) < 0 ) Sys_Error( "%s: %s", __func__, SDL_GetError( )); return pixels; } // ensure it not changed (do we really need this?) sw.win = SDL_GetWindowSurface( host.hWnd ); //if( !sw.win ) //SDL_GetWindowSurface( host.hWnd ); #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) sw.win = SDL_GetVideoSurface(); #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) // prevent buffer overrun if( !sw.win || sw.win->w < sw.width || sw.win->h < sw.height ) return NULL; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( sw.surf ) { SDL_LockSurface( sw.surf ); return sw.surf->pixels; } else #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) { // real window pixels (x11 shm region, dma buffer, etc) // or SDL_Renderer texture if not supported SDL_LockSurface( sw.win ); return sw.win->pixels; } } void SW_UnlockBuffer( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( sw.renderer ) { SDL_Rect src, dst; src.x = src.y = 0; src.w = sw.width; src.h = sw.height; dst = src; SDL_UnlockTexture(sw.tex); SDL_SetTextureBlendMode(sw.tex, SDL_BLENDMODE_NONE); SDL_RenderCopy(sw.renderer, sw.tex, &src, &dst); SDL_RenderPresent(sw.renderer); return; //Con_Printf("%s\n", SDL_GetError()); } // blit if blitting surface availiable if( sw.surf ) { SDL_Rect src, dst; src.x = src.y = 0; src.w = sw.width; src.h = sw.height; dst = src; SDL_UnlockSurface( sw.surf ); SDL_BlitSurface( sw.surf, &src, sw.win, &dst ); return; } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) // already blitted SDL_UnlockSurface( sw.win ); #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_UpdateWindowSurface( host.hWnd ); #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_Flip( host.hWnd ); #endif } int R_MaxVideoModes( void ) { return num_vidmodes; } vidmode_t *R_GetVideoMode( int num ) { if( !vidmodes || num < 0 || num >= R_MaxVideoModes() ) { return NULL; } return vidmodes + num; } static void R_InitVideoModes( void ) { char buf[MAX_VA_STRING]; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) int displayIndex = 0; // TODO: handle multiple displays somehow int i, modes; num_vidmodes = 0; modes = SDL_GetNumDisplayModes( displayIndex ); if( !modes ) return; vidmodes = Mem_Malloc( host.mempool, modes * sizeof( vidmode_t ) ); for( i = 0; i < modes; i++ ) { int j; SDL_DisplayMode mode; if( SDL_GetDisplayMode( displayIndex, i, &mode ) < 0 ) { Msg( "SDL_GetDisplayMode: %s\n", SDL_GetError() ); continue; } if( mode.w < VID_MIN_WIDTH || mode.h < VID_MIN_HEIGHT ) continue; for( j = 0; j < num_vidmodes; j++ ) { if( mode.w == vidmodes[j].width && mode.h == vidmodes[j].height ) { break; } } if( j != num_vidmodes ) continue; vidmodes[num_vidmodes].width = mode.w; vidmodes[num_vidmodes].height = mode.h; Q_snprintf( buf, sizeof( buf ), "%ix%i", mode.w, mode.h ); vidmodes[num_vidmodes].desc = copystring( buf ); num_vidmodes++; } #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_Rect **modes; int len = 0, i = 0, j; modes = SDL_ListModes( NULL, SDL_FULLSCREEN ); if( !modes || modes == (void*)-1 ) return; for( len = 0; modes[len]; len++ ); vidmodes = Mem_Malloc( host.mempool, len * sizeof( vidmode_t ) ); // from smallest to largest for( ; i < len; i++ ) { SDL_Rect *mode = modes[len - i - 1]; for( j = 0; j < num_vidmodes; j++ ) { if( mode->w == vidmodes[j].width && mode->h == vidmodes[j].height ) { break; } } if( j != num_vidmodes ) continue; vidmodes[num_vidmodes].width = mode->w; vidmodes[num_vidmodes].height = mode->h; Q_snprintf( buf, sizeof( buf ), "%ix%i", mode->w, mode->h ); vidmodes[num_vidmodes].desc = copystring( buf ); num_vidmodes++; } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } static void R_FreeVideoModes( void ) { int i; if( !vidmodes ) return; for( i = 0; i < num_vidmodes; i++ ) Mem_Free( (char*)vidmodes[i].desc ); Mem_Free( vidmodes ); vidmodes = NULL; } #if XASH_WIN32 typedef enum _XASH_DPI_AWARENESS { XASH_DPI_UNAWARE = 0, XASH_SYSTEM_DPI_AWARE = 1, XASH_PER_MONITOR_DPI_AWARE = 2 } XASH_DPI_AWARENESS; static void WIN_SetDPIAwareness( void ) { HMODULE hModule; HRESULT ( __stdcall *pSetProcessDpiAwareness )( XASH_DPI_AWARENESS ); BOOL ( __stdcall *pSetProcessDPIAware )( void ); BOOL bSuccess = FALSE; if( ( hModule = LoadLibrary( "shcore.dll" ) ) ) { if( ( pSetProcessDpiAwareness = (void*)GetProcAddress( hModule, "SetProcessDpiAwareness" ) ) ) { // I hope SDL don't handle WM_DPICHANGED message HRESULT hResult = pSetProcessDpiAwareness( XASH_SYSTEM_DPI_AWARE ); if( hResult == S_OK ) { Con_Reportf( "SetDPIAwareness: Success\n" ); bSuccess = TRUE; } else if( hResult == E_INVALIDARG ) Con_Reportf( "SetDPIAwareness: Invalid argument\n" ); else if( hResult == E_ACCESSDENIED ) Con_Reportf( "SetDPIAwareness: Access Denied\n" ); } else Con_Reportf( "SetDPIAwareness: Can't get SetProcessDpiAwareness\n" ); FreeLibrary( hModule ); } else Con_Reportf( "SetDPIAwareness: Can't load shcore.dll\n" ); if( !bSuccess ) { Con_Reportf( "SetDPIAwareness: Trying SetProcessDPIAware...\n" ); if( ( hModule = LoadLibrary( "user32.dll" ) ) ) { if( ( pSetProcessDPIAware = ( void* )GetProcAddress( hModule, "SetProcessDPIAware" ) ) ) { // I hope SDL don't handle WM_DPICHANGED message BOOL hResult = pSetProcessDPIAware(); if( hResult ) { Con_Reportf( "SetDPIAwareness: Success\n" ); bSuccess = TRUE; } else Con_Reportf( "SetDPIAwareness: fail\n" ); } else Con_Reportf( "SetDPIAwareness: Can't get SetProcessDPIAware\n" ); FreeLibrary( hModule ); } else Con_Reportf( "SetDPIAwareness: Can't load user32.dll\n" ); } } #include static qboolean WIN_SetWindowIcon( HICON ico ) { SDL_SysWMinfo wminfo; SDL_VERSION( &wminfo.version ); if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) == SDL_TRUE ) { SendMessage( wminfo.info.win.window, WM_SETICON, ICON_SMALL, (LONG_PTR)ico ); SendMessage( wminfo.info.win.window, WM_SETICON, ICON_BIG, (LONG_PTR)ico ); return true; } Con_Reportf( S_ERROR "%s: %s", __func__, SDL_GetError( )); return false; } #endif /* ================= GL_GetProcAddress ================= */ void *GL_GetProcAddress( const char *name ) { void *func = SDL_GL_GetProcAddress( name ); #if !SDL_VERSION_ATLEAST( 2, 0, 6 ) && XASH_POSIX if( !func && Sys_CheckParm( "-egl" )) { /* * SDL2 has broken SDL_GL_GetProcAddress until this commit if using egl: * https://github.com/libsdl-org/SDL/commit/466ba57d42d244e80357e9ad3011c50af30ed225 * so call eglGetProcAddress directly * */ static void *(*peglGetProcAddress)( const char * ); if( !peglGetProcAddress ) { void *lib = dlopen( "libEGL.so", RTLD_NOW ); if( lib ) *(void**)&peglGetProcAddress = dlsym( lib, "eglGetProcAddress" ); } if( peglGetProcAddress ) func = peglGetProcAddress( name ); } #endif #if XASH_PSVITA // try to find in main module if( !func ) { func = dlsym( NULL, name ); } #endif if( !func ) { Con_Reportf( S_ERROR "GL_GetProcAddress failed for %s\n", name ); } return func; } /* =============== GL_UpdateSwapInterval =============== */ void GL_UpdateSwapInterval( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) // disable VSync while level is loading if( cls.state < ca_active ) { SDL_GL_SetSwapInterval( 0 ); SetBits( gl_vsync.flags, FCVAR_CHANGED ); } else if( FBitSet( gl_vsync.flags, FCVAR_CHANGED )) { ClearBits( gl_vsync.flags, FCVAR_CHANGED ); if( SDL_GL_SetSwapInterval( gl_vsync.value ) < 0 ) Con_Reportf( S_ERROR "SDL_GL_SetSwapInterval: %s\n", SDL_GetError( )); } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } /* ================= GL_DeleteContext always return false ================= */ qboolean GL_DeleteContext( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( glw_state.context ) { SDL_GL_DeleteContext(glw_state.context); glw_state.context = NULL; } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) return false; } /* ================= GL_CreateContext ================= */ static qboolean GL_CreateContext( void ) { #if SDL_VERSION_ATLEAST(2, 0, 0) if( ( glw_state.context = SDL_GL_CreateContext( host.hWnd ) ) == NULL) { Con_Reportf( S_ERROR "GL_CreateContext: %s\n", SDL_GetError()); return GL_DeleteContext(); } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) return true; } /* ================= GL_UpdateContext ================= */ static qboolean GL_UpdateContext( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( SDL_GL_MakeCurrent( host.hWnd, glw_state.context ) < 0 ) { Con_Reportf( S_ERROR "GL_UpdateContext: %s\n", SDL_GetError()); return GL_DeleteContext(); } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) return true; } void VID_SaveWindowSize( int width, int height, qboolean maximized ) { int render_w = width, render_h = height; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( glw_state.context_type != REF_SOFTWARE ) SDL_GL_GetDrawableSize( host.hWnd, &render_w, &render_h ); else SDL_RenderSetLogicalSize( sw.renderer, width, height ); #endif VID_SetDisplayTransform( &render_w, &render_h ); R_SaveVideoMode( width, height, render_w, render_h, maximized ); } static qboolean VID_SetScreenResolution( int width, int height, window_mode_t window_mode ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DisplayMode got; Uint32 wndFlags = 0; #if !XASH_APPLE if( vid_highdpi.value ) SetBits( wndFlags, SDL_WINDOW_ALLOW_HIGHDPI ); #endif SDL_SetWindowBordered( host.hWnd, SDL_FALSE ); if( window_mode == WINDOW_MODE_BORDERLESS ) { if( SDL_GetDesktopDisplayMode( 0, &got ) < 0 ) { Con_Printf( S_ERROR "%s: SDL_GetDesktopDisplayMode: %s", __func__, SDL_GetError( )); return false; } if( SDL_SetWindowFullscreen( host.hWnd, SDL_WINDOW_FULLSCREEN_DESKTOP ) < 0 ) { Con_Printf( S_ERROR "%s: SDL_SetWindowFullscreen (borderless): %s", __func__, SDL_GetError( )); return false; } } else if( window_mode == WINDOW_MODE_FULLSCREEN ) { SDL_DisplayMode want = { 0 }; want.w = width; want.h = height; if( SDL_GetClosestDisplayMode( 0, &want, &got ) == NULL ) { Con_Printf( S_ERROR "%s: SDL_GetClosestDisplayMode: %s", __func__, SDL_GetError( )); return false; } if( got.w != want.w || got.h != want.h ) Con_Reportf( S_NOTE "Got closest display mode: %ix%i@%i\n", got.w, got.h, got.refresh_rate ); if( SDL_SetWindowDisplayMode( host.hWnd, &got ) < 0 ) { Con_Printf( S_ERROR "%s: SDL_SetWindowDisplayMode: %s", __func__, SDL_GetError( )); return false; } if( SDL_SetWindowFullscreen( host.hWnd, SDL_WINDOW_FULLSCREEN ) < 0 ) { Con_Printf( S_ERROR "%s: SDL_SetWindowFullscreen (fullscreen): %s", __func__, SDL_GetError( )); return false; } } SDL_SetWindowSize( host.hWnd, got.w, got.h ); VID_SaveWindowSize( got.w, got.h, true ); #else VID_SaveWindowSize( width, height, true ); #endif return true; } void VID_RestoreScreenResolution( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) switch((window_mode_t)vid_fullscreen.value ) { case WINDOW_MODE_WINDOWED: SDL_SetWindowBordered( host.hWnd, SDL_TRUE ); break; default: SDL_MinimizeWindow( host.hWnd ); SDL_SetWindowFullscreen( host.hWnd, 0 ); break; } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } #if SDL_VERSION_ATLEAST( 2, 0, 0 ) static void VID_SetWindowIcon( SDL_Window *hWnd ) { rgbdata_t *icon = NULL; char iconpath[MAX_STRING]; const char *localIcoPath; #if XASH_WIN32 // ICO support only for Win32 if(( localIcoPath = FS_GetDiskPath( GI->iconpath, true ))) { HICON ico = (HICON)LoadImage( NULL, localIcoPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); if( ico && WIN_SetWindowIcon( ico )) return; } #endif // _WIN32 && !XASH_64BIT Q_strncpy( iconpath, GI->iconpath, sizeof( iconpath )); COM_ReplaceExtension( iconpath, ".tga", sizeof( iconpath )); icon = FS_LoadImage( iconpath, NULL, 0 ); if( icon ) { SDL_Surface *surface = SDL_CreateRGBSurfaceFrom( icon->buffer, icon->width, icon->height, 32, 4 * icon->width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); if( surface ) { SDL_SetWindowIcon( host.hWnd, surface ); SDL_FreeSurface( surface ); FS_FreeImage( icon ); return; } FS_FreeImage( icon ); } #if XASH_WIN32 // ICO support only for Win32 WIN_SetWindowIcon( LoadIcon( host.hInst, MAKEINTRESOURCE( 101 ))); #endif } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) static qboolean VID_CreateWindowWithSafeGL( const char *wndname, int xpos, int ypos, int w, int h, uint32_t flags ) { while( glw_state.safe >= SAFE_NO && glw_state.safe < SAFE_LAST ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) host.hWnd = SDL_CreateWindow( wndname, xpos, ypos, w, h, flags ); #else host.hWnd = sw.surf = SDL_SetVideoMode( width, height, 16, flags ); #endif // we have window, exit loop if( host.hWnd ) break; Con_Reportf( S_ERROR "VID_CreateWindow: couldn't create '%s' with safegl level %d: %s\n", wndname, glw_state.safe, SDL_GetError()); glw_state.safe++; if( !gl_msaa_samples.value && glw_state.safe == SAFE_NOMSAA ) glw_state.safe++; // no need to skip msaa, if we already disabled it GL_SetupAttributes(); // re-choose attributes // try again create window } // window creation has failed... if( glw_state.safe >= SAFE_LAST ) return false; return true; } /* ================= VID_CreateWindow ================= */ qboolean VID_CreateWindow( int width, int height, window_mode_t window_mode ) { string wndname; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) qboolean maximized = vid_maximized.value != 0.0f; Uint32 wndFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_MOUSE_FOCUS; int xpos, ypos; Q_strncpy( wndname, GI->title, sizeof( wndname )); if( vid_highdpi.value ) SetBits( wndFlags, SDL_WINDOW_ALLOW_HIGHDPI ); switch (glw_state.context_type) { case REF_GL: wndFlags |= SDL_WINDOW_OPENGL; break; case REF_VULKAN: wndFlags |= SDL_WINDOW_VULKAN; break; } #if !XASH_MOBILE_PLATFORM if( window_mode == WINDOW_MODE_WINDOWED ) { SDL_Rect r; SetBits( wndFlags, SDL_WINDOW_RESIZABLE ); if( maximized ) SetBits( wndFlags, SDL_WINDOW_MAXIMIZED ); #if SDL_VERSION_ATLEAST( 2, 0, 5 ) if( SDL_GetDisplayUsableBounds( 0, &r ) < 0 && SDL_GetDisplayBounds( 0, &r ) < 0 ) #else if( SDL_GetDisplayBounds( 0, &r ) < 0 ) #endif { Con_Reportf( S_ERROR "VID_CreateWindow: SDL_GetDisplayBounds failed: %s\n", SDL_GetError( )); xpos = SDL_WINDOWPOS_CENTERED; ypos = SDL_WINDOWPOS_CENTERED; } else { xpos = window_xpos.value; ypos = window_ypos.value; // don't create window outside of usable display space if( xpos < r.x || xpos + width > r.x + r.w ) xpos = SDL_WINDOWPOS_CENTERED; if( ypos < r.y || ypos + height > r.y + r.h ) ypos = SDL_WINDOWPOS_CENTERED; } } else { if( window_mode == WINDOW_MODE_FULLSCREEN ) // need input grab only in true fullscreen mode SetBits( wndFlags, SDL_WINDOW_FULLSCREEN | SDL_WINDOW_INPUT_GRABBED ); else SetBits( wndFlags, SDL_WINDOW_FULLSCREEN_DESKTOP ); SetBits( wndFlags, SDL_WINDOW_BORDERLESS ); xpos = ypos = 0; } #else SetBits( wndFlags, SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_INPUT_GRABBED ); xpos = ypos = SDL_WINDOWPOS_UNDEFINED; #endif if( !VID_CreateWindowWithSafeGL( wndname, xpos, ypos, width, height, wndFlags )) return false; // update window size if it was maximized, just in case if( FBitSet( SDL_GetWindowFlags( host.hWnd ), SDL_WINDOW_MAXIMIZED|SDL_WINDOW_FULLSCREEN_DESKTOP ) != 0 ) SDL_GetWindowSize( host.hWnd, &width, &height ); #if !XASH_MOBILE_PLATFORM if( window_mode != WINDOW_MODE_WINDOWED ) { if( !VID_SetScreenResolution( width, height, window_mode )) return false; } else VID_RestoreScreenResolution(); #endif VID_SetWindowIcon( host.hWnd ); SDL_ShowWindow( host.hWnd ); if( glw_state.context_type == REF_SOFTWARE ) { int sdl_renderer = -2; char cmd[64]; if( Sys_GetParmFromCmdLine("-sdl_renderer", cmd ) ) sdl_renderer = Q_atoi( cmd ); if( sdl_renderer >= -1 ) { sw.renderer = SDL_CreateRenderer( host.hWnd, sdl_renderer, 0 ); if( !sw.renderer ) Con_Printf( S_ERROR "failed to create SDL renderer: %s\n", SDL_GetError() ); else { SDL_RendererInfo info; SDL_GetRendererInfo( sw.renderer, &info ); Con_Printf( "SDL_Renderer %s initialized\n", info.name ); } } } else if( glw_state.context_type == REF_GL ) { if( !glw_state.initialized ) { while( !GL_CreateContext( )) { glw_state.safe++; if(glw_state.safe > SAFE_DONTCARE) return false; GL_SetupAttributes(); // re-choose attributes } VID_StartupGamma(); } if( !GL_UpdateContext( )) return false; } #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) Uint32 flags = 0; if( window_mode != WINDOW_MODE_WINDOWED ) SetBits( flags, SDL_FULLSCREEN|SDL_HWSURFACE ); if( glw_state.context_type == REF_SOFTWARE ) { // flags |= SDL_ASYNCBLIT; } else { flags |= SDL_OPENGL; } if( !VID_CreateWindowWithSafeGL( wndname, xpos, ypos, width, height, wndFlags )) return false; #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) VID_SaveWindowSize( width, height, maximized ); return true; } /* ================= VID_DestroyWindow ================= */ void VID_DestroyWindow( void ) { GL_DeleteContext(); VID_RestoreScreenResolution(); if( host.hWnd ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DestroyWindow ( host.hWnd ); #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) host.hWnd = NULL; } if( refState.fullScreen ) { refState.fullScreen = false; } } /* ================== GL_SetupAttributes ================== */ static void GL_SetupAttributes( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_GL_ResetAttributes(); #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) ref.dllFuncs.GL_SetupAttributes( glw_state.safe ); } void GL_SwapBuffers( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_GL_SwapWindow( host.hWnd ); #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_Flip( host.hWnd ); #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } int GL_SetAttribute( int attr, int val ) { switch( attr ) { #define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_SetAttribute( SDL_##name, val ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); #if SDL_VERSION_ATLEAST( 2, 0, 0 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_EGL ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE ); case REF_GL_CONTEXT_PROFILE_MASK: #ifdef SDL_HINT_OPENGL_ES_DRIVER if( val == REF_GL_CONTEXT_PROFILE_ES ) { SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); SDL_SetHint( "SDL_VIDEO_X11_FORCE_EGL", "1" ); } #endif // SDL_HINT_OPENGL_ES_DRIVER return SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, val ); #endif #if SDL_VERSION_ATLEAST( 2, 0, 4 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR ); #endif #if SDL_VERSION_ATLEAST( 2, 0, 6 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR ); #endif #undef MAP_REF_API_ATTRIBUTE_TO_SDL } return -1; } int GL_GetAttribute( int attr, int *val ) { switch( attr ) { #define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_GetAttribute( SDL_##name, val ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); #if SDL_VERSION_ATLEAST( 2, 0, 0 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_EGL ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_FLAGS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK ); #endif #if SDL_VERSION_ATLEAST( 2, 0, 4 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR ); #endif #if SDL_VERSION_ATLEAST( 2, 0, 6 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RESET_NOTIFICATION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_NO_ERROR ); #endif #undef MAP_REF_API_ATTRIBUTE_TO_SDL } return 0; } #ifndef EGL_LIB #define EGL_LIB NULL #endif int XVK_GetInstanceExtensions( unsigned int count, const char **pNames ) { if (!SDL_Vulkan_GetInstanceExtensions(host.hWnd, &count, pNames)) { Con_Reportf( S_ERROR "Couldn't get Vulkan extensions: %s\n", SDL_GetError()); return -1; } return (int)count; } void *XVK_GetVkGetInstanceProcAddr( void ) { return SDL_Vulkan_GetVkGetInstanceProcAddr(); } VkSurfaceKHR XVK_CreateSurface( VkInstance instance ) { VkSurfaceKHR surface; if (!SDL_Vulkan_CreateSurface(host.hWnd, instance, &surface)) { Con_Reportf( S_ERROR "Couldn't create Vulkan surface: %s\n", SDL_GetError()); return 0; } return surface; } /* ================== R_Init_Video ================== */ qboolean R_Init_Video( const int type ) { string safe; qboolean retval; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DisplayMode displayMode; SDL_GetCurrentDisplayMode(0, &displayMode); refState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format ); #else refState.desktopBitsPixel = 16; #endif #ifdef SDL_HINT_QTWAYLAND_WINDOW_FLAGS SDL_SetHint( SDL_HINT_QTWAYLAND_WINDOW_FLAGS, "OverridesSystemGestures" ); #endif #ifdef SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION SDL_SetHint( SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION, "landscape" ); #endif #if SDL_VERSION_ATLEAST( 2, 0, 0 ) && !XASH_WIN32 SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "1" ); SDL_SetHint( "SDL_VIDEO_X11_XVIDMODE", "1" ); if( Sys_CheckParm( "-egl" ) ) SDL_SetHint( "SDL_VIDEO_X11_FORCE_EGL", "1" ); #endif // must be initialized before creating window #if XASH_WIN32 WIN_SetDPIAwareness(); #endif glw_state.context_type = type; switch( type ) { case REF_SOFTWARE: break; case REF_GL: if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", safe ) ) glw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE ); // refdll can request some attributes GL_SetupAttributes( ); if( SDL_GL_LoadLibrary( EGL_LIB ) < 0 ) { Con_Reportf( S_ERROR "Couldn't initialize OpenGL: %s\n", SDL_GetError()); return false; } break; case REF_VULKAN: break; default: Host_Error( "Can't initialize unknown context type %d!\n", type ); break; } if( !(retval = VID_SetMode()) ) { return retval; } switch( type ) { case REF_GL: // refdll also can check extensions ref.dllFuncs.GL_InitExtensions(); break; case REF_SOFTWARE: default: break; } R_InitVideoModes(); host.renderinfo_changed = false; return true; } rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DisplayMode displayMode; if( SDL_GetCurrentDisplayMode( 0, &displayMode ) < 0 ) { Con_Printf( S_ERROR "SDL_GetCurrentDisplayMode: %s", SDL_GetError( )); return rserr_invalid_mode; } // check our desktop attributes refState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format ); if( window_mode == WINDOW_MODE_BORDERLESS ) { width = displayMode.w; height = displayMode.h; } #endif refState.fullScreen = window_mode != WINDOW_MODE_WINDOWED; Con_Reportf( "R_ChangeDisplaySettings: Setting video mode to %dx%d %s\n", width, height, refState.fullScreen ? "fullscreen" : "windowed" ); if( !host.hWnd ) { if( !VID_CreateWindow( width, height, window_mode )) return rserr_invalid_mode; } else if( refState.fullScreen ) { if( !VID_SetScreenResolution( width, height, window_mode )) return rserr_invalid_fullscreen; } else { VID_RestoreScreenResolution(); #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( SDL_SetWindowFullscreen( host.hWnd, 0 ) < 0 ) { Con_Printf( S_ERROR "SDL_SetWindowFullscreen: %s", SDL_GetError( )); return rserr_invalid_fullscreen; } #if SDL_VERSION_ATLEAST( 2, 0, 5 ) SDL_SetWindowResizable( host.hWnd, SDL_TRUE ); #endif SDL_SetWindowBordered( host.hWnd, SDL_TRUE ); SDL_SetWindowSize( host.hWnd, width, height ); #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) VID_SaveWindowSize( width, height, true ); } return rserr_ok; } /* ================== VID_SetMode Set the described video mode ================== */ qboolean VID_SetMode( void ) { int iScreenWidth, iScreenHeight; rserr_t err; window_mode_t window_mode; iScreenWidth = Cvar_VariableInteger( "width" ); iScreenHeight = Cvar_VariableInteger( "height" ); if( iScreenWidth < VID_MIN_WIDTH || iScreenHeight < VID_MIN_HEIGHT ) // trying to get resolution automatically by default { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) #if !defined( DEFAULT_MODE_WIDTH ) || !defined( DEFAULT_MODE_HEIGHT ) SDL_DisplayMode mode; SDL_GetDesktopDisplayMode( 0, &mode ); iScreenWidth = mode.w; iScreenHeight = mode.h; #else iScreenWidth = DEFAULT_MODE_WIDTH; iScreenHeight = DEFAULT_MODE_HEIGHT; #endif #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) iScreenWidth = 320; iScreenHeight = 240; #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } if( !FBitSet( vid_fullscreen.flags, FCVAR_CHANGED )) Cvar_DirectSet( &vid_fullscreen, DEFAULT_FULLSCREEN ); else ClearBits( vid_fullscreen.flags, FCVAR_CHANGED ); SetBits( gl_vsync.flags, FCVAR_CHANGED ); window_mode = bound( 0, vid_fullscreen.value, WINDOW_MODE_COUNT - 1 ); if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, window_mode )) == rserr_ok ) { sdlState.prev_width = iScreenWidth; sdlState.prev_height = iScreenHeight; } else { if( err == rserr_invalid_fullscreen ) { Cvar_DirectSet( &vid_fullscreen, "0" ); Con_Reportf( S_ERROR "VID_SetMode: fullscreen unavailable in this mode\n" ); Sys_Warn("fullscreen unavailable in this mode!"); if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, WINDOW_MODE_WINDOWED )) == rserr_ok ) return true; } else if( err == rserr_invalid_mode ) { Con_Reportf( S_ERROR "VID_SetMode: invalid mode\n" ); Sys_Warn( "invalid mode, engine will run in %dx%d", sdlState.prev_width, sdlState.prev_height ); } // try setting it back to something safe if(( err = R_ChangeDisplaySettings( sdlState.prev_width, sdlState.prev_height, WINDOW_MODE_WINDOWED )) != rserr_ok ) { Con_Reportf( S_ERROR "VID_SetMode: could not revert to safe mode\n" ); Sys_Warn("could not revert to safe mode!"); return false; } } return true; } /* ================== R_Free_Video ================== */ void R_Free_Video( void ) { GL_DeleteContext (); VID_DestroyWindow (); R_FreeVideoModes(); ref.dllFuncs.GL_ClearExtensions(); #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_VideoQuit(); #endif } #endif // XASH_DEDICATED