From d14782fb37fc2283b2043e200a75c234fe298204 Mon Sep 17 00:00:00 2001 From: arookas Date: Wed, 31 Aug 2016 22:04:22 -0400 Subject: [PATCH 01/44] Added Thing_Damage3 function It acts as a simple wrapper around P_DamageMobj which can damage a single actor, but can also set the actor inflicting the damage. It returns the amount of damage actually done, or -1 if the damaging was cancelled. --- src/p_acs.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index a2b5275cb..edabca784 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -4450,6 +4450,7 @@ enum EACSFunctions */ ACSF_CheckClass = 200, + ACSF_Thing_Damage3, // [arookas] // ZDaemon ACSF_GetTeamScore = 19620, // (int team) @@ -6035,6 +6036,15 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) const char *clsname = FBehavior::StaticLookupString(args[0]); return !!PClass::FindActor(clsname); } + + case ACSF_Thing_Damage3: // [arookas] wrapper around P_DamageMobj + { + // (target, ptr_select1, inflictor, ptr_select2, amount, damagetype) + AActor* target = COPY_AAPTR(SingleActorFromTID(args[0], activator), args[1]); + AActor* inflictor = COPY_AAPTR(SingleActorFromTID(args[2], activator), args[3]); + FName damagetype(FBehavior::StaticLookupString(args[5])); + return P_DamageMobj(target, inflictor, inflictor, args[4], damagetype); + } default: break; From d7b5bdc0f7e68b45fe2f76f8da35b61459e441dc Mon Sep 17 00:00:00 2001 From: arookas Date: Fri, 2 Sep 2016 23:04:12 -0400 Subject: [PATCH 02/44] Renamed Thing_Damage3 to DamageActor --- src/p_acs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index edabca784..0157fda75 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -4450,7 +4450,7 @@ enum EACSFunctions */ ACSF_CheckClass = 200, - ACSF_Thing_Damage3, // [arookas] + ACSF_DamageActor, // [arookas] // ZDaemon ACSF_GetTeamScore = 19620, // (int team) @@ -6037,7 +6037,7 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) return !!PClass::FindActor(clsname); } - case ACSF_Thing_Damage3: // [arookas] wrapper around P_DamageMobj + case ACSF_DamageActor: // [arookas] wrapper around P_DamageMobj { // (target, ptr_select1, inflictor, ptr_select2, amount, damagetype) AActor* target = COPY_AAPTR(SingleActorFromTID(args[0], activator), args[1]); From 5770e5dfaf0711619f3bb1f17fdd89065f98983d Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 3 Sep 2016 12:00:08 +0200 Subject: [PATCH 03/44] - split up m_specialpaths.cpp to be a separate file for each operating system. The reason for this is that the macOS version uses a deprecated API and in order to correct this, the file needs to be compiled as Objective-C++ which requires a different extension. --- src/CMakeLists.txt | 14 +- src/posix/osx/i_specialpaths.mm | 187 +++++++++ src/posix/unix/i_specialpaths.cpp | 199 ++++++++++ .../i_specialpaths.cpp} | 360 ++---------------- 4 files changed, 429 insertions(+), 331 deletions(-) create mode 100644 src/posix/osx/i_specialpaths.mm create mode 100644 src/posix/unix/i_specialpaths.cpp rename src/{m_specialpaths.cpp => win32/i_specialpaths.cpp} (50%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f6caa2f5d..51548afdd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -635,6 +635,7 @@ set( PLAT_WIN32_SOURCES win32/i_main.cpp win32/i_movie.cpp win32/i_system.cpp + win32/i_specialpaths.cpp win32/st_start.cpp win32/win32video.cpp ) set( PLAT_POSIX_SOURCES @@ -652,8 +653,11 @@ set( PLAT_SDL_SOURCES posix/sdl/i_timer.cpp posix/sdl/sdlvideo.cpp posix/sdl/st_start.cpp ) +set( PLAT_UNIX_SOURCES + posix/unix/i_specialpaths.cpp ) set( PLAT_OSX_SOURCES posix/osx/iwadpicker_cocoa.mm + posix/osx/i_specialpaths.mm posix/osx/zdoom.icns ) set( PLAT_COCOA_SOURCES posix/cocoa/critsec.cpp @@ -670,7 +674,7 @@ set( PLAT_COCOA_SOURCES if( WIN32 ) set( SYSTEM_SOURCES_DIR win32 ) set( SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ) - set( OTHER_SYSTEM_SOURCES ${PLAT_POSIX_SOURCES} ${PLAT_SDL_SOURCES} ${PLAT_OSX_SOURCES} ${PLAT_COCOA_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_POSIX_SOURCES} ${PLAT_SDL_SOURCES} ${PLAT_OSX_SOURCES} ${PLAT_COCOA_SOURCES} ${PLAT_UNIX_SOURCES} ) if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) # CMake is not set up to compile and link rc files with GCC. :( @@ -685,12 +689,12 @@ elseif( APPLE ) if( OSX_COCOA_BACKEND ) set( SYSTEM_SOURCES_DIR posix posix/cocoa ) set( SYSTEM_SOURCES ${PLAT_COCOA_SOURCES} ) - set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_SDL_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_SDL_SOURCES} ${PLAT_UNIX_SOURCES} ) else() set( SYSTEM_SOURCES_DIR posix posix/sdl ) set( SYSTEM_SOURCES ${PLAT_SDL_SOURCES} ) set( PLAT_OSX_SOURCES ${PLAT_OSX_SOURCES} posix/sdl/i_system.mm ) - set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_COCOA_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_COCOA_SOURCES} ${PLAT_UNIX_SOURCES} ) endif() set( SYSTEM_SOURCES ${SYSTEM_SOURCES} ${PLAT_POSIX_SOURCES} ${PLAT_OSX_SOURCES} "${FMOD_LIBRARY}" ) @@ -700,7 +704,7 @@ elseif( APPLE ) set_source_files_properties( posix/osx/iwadpicker_cocoa.mm PROPERTIES COMPILE_FLAGS -fobjc-exceptions ) else() set( SYSTEM_SOURCES_DIR posix posix/sdl ) - set( SYSTEM_SOURCES ${PLAT_POSIX_SOURCES} ${PLAT_SDL_SOURCES} ) + set( SYSTEM_SOURCES ${PLAT_POSIX_SOURCES} ${PLAT_SDL_SOURCES} ${PLAT_UNIX_SOURCES} ) set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_OSX_SOURCES} ${PLAT_COCOA_SOURCES} ) endif() @@ -1246,7 +1250,6 @@ add_executable( zdoom WIN32 MACOSX_BUNDLE ${FASTMATH_SOURCES} ${PCH_SOURCES} x86.cpp - m_specialpaths.cpp strnatcmp.c zstring.cpp math/asin.c @@ -1410,6 +1413,7 @@ source_group("Resource Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r source_group("POSIX Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/.+") source_group("Cocoa Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/cocoa/.+") source_group("OS X Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/osx/.+") +source_group("Unix Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/unix/.+") source_group("SDL Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/sdl/.+") source_group("SFMT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sfmt/.+") source_group("Shared Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_shared/.+") diff --git a/src/posix/osx/i_specialpaths.mm b/src/posix/osx/i_specialpaths.mm new file mode 100644 index 000000000..843fb82d6 --- /dev/null +++ b/src/posix/osx/i_specialpaths.mm @@ -0,0 +1,187 @@ +/* +** i_specialpaths.mm +** Gets special system folders where data should be stored. (macOS version) +** +**--------------------------------------------------------------------------- +** Copyright 2013-2016 Randy Heit +** Copyright 2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include + +#include "cmdlib.h" +#include "m_misc.h" +#include "version.h" // for GAMENAME + +//=========================================================================== +// +// M_GetCachePath macOS +// +// Returns the path for cache GL nodes. +// +//=========================================================================== + +FString M_GetCachePath(bool create) +{ + FString path; + + char pathstr[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kUserDomain, kApplicationSupportFolderType, create ? kCreateFolder : 0, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)pathstr, PATH_MAX)) + { + path = pathstr; + } + else + { + path = progdir; + } + path += "/zdoom/cache"; + return path; +} + +//=========================================================================== +// +// M_GetAutoexecPath macOS +// +// Returns the expected location of autoexec.cfg. +// +//=========================================================================== + +FString M_GetAutoexecPath() +{ + FString path; + + char cpath[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kUserDomain, kDocumentsFolderType, kCreateFolder, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) + { + path << cpath << "/" GAME_DIR "/autoexec.cfg"; + } + return path; +} + +//=========================================================================== +// +// M_GetCajunPath macOS +// +// Returns the location of the Cajun Bot definitions. +// +//=========================================================================== + +FString M_GetCajunPath(const char *botfilename) +{ + FString path; + + // Just copies the Windows code. Should this be more Mac-specific? + path << progdir << "zcajun/" << botfilename; + if (!FileExists(path)) + { + path = ""; + } + return path; +} + +//=========================================================================== +// +// M_GetConfigPath macOS +// +// Returns the path to the config file. On Windows, this can vary for reading +// vs writing. i.e. If $PROGDIR/zdoom-.ini does not exist, it will try +// to read from $PROGDIR/zdoom.ini, but it will never write to zdoom.ini. +// +//=========================================================================== + +FString M_GetConfigPath(bool for_reading) +{ + char cpath[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kUserDomain, kPreferencesFolderType, kCreateFolder, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) + { + FString path; + path << cpath << "/" GAMENAMELOWERCASE ".ini"; + return path; + } + // Ungh. + return GAMENAMELOWERCASE ".ini"; +} + +//=========================================================================== +// +// M_GetScreenshotsPath macOS +// +// Returns the path to the default screenshots directory. +// +//=========================================================================== + +FString M_GetScreenshotsPath() +{ + FString path; + char cpath[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kUserDomain, kDocumentsFolderType, kCreateFolder, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) + { + path << cpath << "/" GAME_DIR "/Screenshots/"; + } + else + { + path = "~/"; + } + return path; +} + +//=========================================================================== +// +// M_GetSavegamesPath macOS +// +// Returns the path to the default save games directory. +// +//=========================================================================== + +FString M_GetSavegamesPath() +{ + FString path; + char cpath[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kUserDomain, kDocumentsFolderType, kCreateFolder, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) + { + path << cpath << "/" GAME_DIR "/Savegames/"; + } + return path; +} + diff --git a/src/posix/unix/i_specialpaths.cpp b/src/posix/unix/i_specialpaths.cpp new file mode 100644 index 000000000..6a17e1318 --- /dev/null +++ b/src/posix/unix/i_specialpaths.cpp @@ -0,0 +1,199 @@ +/* +** i_specialpaths.cpp +** Gets special system folders where data should be stored. (Unix version) +** +**--------------------------------------------------------------------------- +** Copyright 2013-2016 Randy Heit +** Copyright 2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#include +#include "i_system.h" + +#include "version.h" // for GAMENAME + + +FString GetUserFile (const char *file) +{ + FString path; + struct stat info; + + path = NicePath("~/" GAME_DIR "/"); + + if (stat (path, &info) == -1) + { + struct stat extrainfo; + + // Sanity check for ~/.config + FString configPath = NicePath("~/.config/"); + if (stat (configPath, &extrainfo) == -1) + { + if (mkdir (configPath, S_IRUSR | S_IWUSR | S_IXUSR) == -1) + { + I_FatalError ("Failed to create ~/.config directory:\n%s", strerror(errno)); + } + } + else if (!S_ISDIR(extrainfo.st_mode)) + { + I_FatalError ("~/.config must be a directory"); + } + + // This can be removed after a release or two + // Transfer the old zdoom directory to the new location + bool moved = false; + FString oldpath = NicePath("~/." GAMENAMELOWERCASE "/"); + if (stat (oldpath, &extrainfo) != -1) + { + if (rename(oldpath, path) == -1) + { + I_Error ("Failed to move old " GAMENAMELOWERCASE " directory (%s) to new location (%s).", + oldpath.GetChars(), path.GetChars()); + } + else + moved = true; + } + + if (!moved && mkdir (path, S_IRUSR | S_IWUSR | S_IXUSR) == -1) + { + I_FatalError ("Failed to create %s directory:\n%s", + path.GetChars(), strerror (errno)); + } + } + else + { + if (!S_ISDIR(info.st_mode)) + { + I_FatalError ("%s must be a directory", path.GetChars()); + } + } + path += file; + return path; +} + +//=========================================================================== +// +// M_GetCachePath Unix +// +// Returns the path for cache GL nodes. +// +//=========================================================================== + +FString M_GetCachePath(bool create) +{ + // Don't use GAME_DIR and such so that ZDoom and its child ports can + // share the node cache. + FString path = NicePath("~/.config/zdoom/cache"); + if (create) + { + CreatePath(path); + } + return path; +} + +//=========================================================================== +// +// M_GetAutoexecPath Unix +// +// Returns the expected location of autoexec.cfg. +// +//=========================================================================== + +FString M_GetAutoexecPath() +{ + return GetUserFile("autoexec.cfg"); +} + +//=========================================================================== +// +// M_GetCajunPath Unix +// +// Returns the location of the Cajun Bot definitions. +// +//=========================================================================== + +FString M_GetCajunPath(const char *botfilename) +{ + FString path; + + // Check first in ~/.config/zdoom/botfilename. + path = GetUserFile(botfilename); + if (!FileExists(path)) + { + // Then check in SHARE_DIR/botfilename. + path = SHARE_DIR; + path << botfilename; + if (!FileExists(path)) + { + path = ""; + } + } + return path; +} + +//=========================================================================== +// +// M_GetConfigPath Unix +// +// Returns the path to the config file. On Windows, this can vary for reading +// vs writing. i.e. If $PROGDIR/zdoom-.ini does not exist, it will try +// to read from $PROGDIR/zdoom.ini, but it will never write to zdoom.ini. +// +//=========================================================================== + +FString M_GetConfigPath(bool for_reading) +{ + return GetUserFile(GAMENAMELOWERCASE ".ini"); +} + +//=========================================================================== +// +// M_GetScreenshotsPath Unix +// +// Returns the path to the default screenshots directory. +// +//=========================================================================== + +FString M_GetScreenshotsPath() +{ + return NicePath("~/" GAME_DIR "/screenshots/"); +} + +//=========================================================================== +// +// M_GetSavegamesPath Unix +// +// Returns the path to the default save games directory. +// +//=========================================================================== + +FString M_GetSavegamesPath() +{ + return NicePath("~/" GAME_DIR); +} diff --git a/src/m_specialpaths.cpp b/src/win32/i_specialpaths.cpp similarity index 50% rename from src/m_specialpaths.cpp rename to src/win32/i_specialpaths.cpp index abfb5db8f..ed8dc2ee6 100644 --- a/src/m_specialpaths.cpp +++ b/src/win32/i_specialpaths.cpp @@ -1,27 +1,46 @@ -#ifdef __APPLE__ -#include -#endif +/* +** i_specialpaths.cpp +** Gets special system folders where data should be stored. (Windows version) +** +**--------------------------------------------------------------------------- +** Copyright 2013-2016 Randy Heit +** Copyright 2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ -#ifdef _WIN32 #include #include #include #define USE_WINDOWS_DWORD -#endif #include "cmdlib.h" #include "m_misc.h" - -#if !defined(__APPLE__) && !defined(_WIN32) -#include -#include -#include "i_system.h" -#endif - #include "version.h" // for GAMENAME - -#if defined(_WIN32) - #include "i_system.h" typedef HRESULT (WINAPI *GKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR *); @@ -323,314 +342,3 @@ FString M_GetSavegamesPath() } return path; } - -#elif defined(__APPLE__) - -//=========================================================================== -// -// M_GetCachePath Mac OS X -// -// Returns the path for cache GL nodes. -// -//=========================================================================== - -FString M_GetCachePath(bool create) -{ - FString path; - - char pathstr[PATH_MAX]; - FSRef folder; - - if (noErr == FSFindFolder(kUserDomain, kApplicationSupportFolderType, create ? kCreateFolder : 0, &folder) && - noErr == FSRefMakePath(&folder, (UInt8*)pathstr, PATH_MAX)) - { - path = pathstr; - } - else - { - path = progdir; - } - path += "/zdoom/cache"; - return path; -} - -//=========================================================================== -// -// M_GetAutoexecPath Mac OS X -// -// Returns the expected location of autoexec.cfg. -// -//=========================================================================== - -FString M_GetAutoexecPath() -{ - FString path; - - char cpath[PATH_MAX]; - FSRef folder; - - if (noErr == FSFindFolder(kUserDomain, kDocumentsFolderType, kCreateFolder, &folder) && - noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) - { - path << cpath << "/" GAME_DIR "/autoexec.cfg"; - } - return path; -} - -//=========================================================================== -// -// M_GetCajunPath Mac OS X -// -// Returns the location of the Cajun Bot definitions. -// -//=========================================================================== - -FString M_GetCajunPath(const char *botfilename) -{ - FString path; - - // Just copies the Windows code. Should this be more Mac-specific? - path << progdir << "zcajun/" << botfilename; - if (!FileExists(path)) - { - path = ""; - } - return path; -} - -//=========================================================================== -// -// M_GetConfigPath Mac OS X -// -// Returns the path to the config file. On Windows, this can vary for reading -// vs writing. i.e. If $PROGDIR/zdoom-.ini does not exist, it will try -// to read from $PROGDIR/zdoom.ini, but it will never write to zdoom.ini. -// -//=========================================================================== - -FString M_GetConfigPath(bool for_reading) -{ - char cpath[PATH_MAX]; - FSRef folder; - - if (noErr == FSFindFolder(kUserDomain, kPreferencesFolderType, kCreateFolder, &folder) && - noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) - { - FString path; - path << cpath << "/" GAMENAMELOWERCASE ".ini"; - return path; - } - // Ungh. - return GAMENAMELOWERCASE ".ini"; -} - -//=========================================================================== -// -// M_GetScreenshotsPath Mac OS X -// -// Returns the path to the default screenshots directory. -// -//=========================================================================== - -FString M_GetScreenshotsPath() -{ - FString path; - char cpath[PATH_MAX]; - FSRef folder; - - if (noErr == FSFindFolder(kUserDomain, kDocumentsFolderType, kCreateFolder, &folder) && - noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) - { - path << cpath << "/" GAME_DIR "/Screenshots/"; - } - else - { - path = "~/"; - } - return path; -} - -//=========================================================================== -// -// M_GetSavegamesPath Mac OS X -// -// Returns the path to the default save games directory. -// -//=========================================================================== - -FString M_GetSavegamesPath() -{ - FString path; - char cpath[PATH_MAX]; - FSRef folder; - - if (noErr == FSFindFolder(kUserDomain, kDocumentsFolderType, kCreateFolder, &folder) && - noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) - { - path << cpath << "/" GAME_DIR "/Savegames/"; - } - return path; -} - -#else // Linux, et al. - - -FString GetUserFile (const char *file) -{ - FString path; - struct stat info; - - path = NicePath("~/" GAME_DIR "/"); - - if (stat (path, &info) == -1) - { - struct stat extrainfo; - - // Sanity check for ~/.config - FString configPath = NicePath("~/.config/"); - if (stat (configPath, &extrainfo) == -1) - { - if (mkdir (configPath, S_IRUSR | S_IWUSR | S_IXUSR) == -1) - { - I_FatalError ("Failed to create ~/.config directory:\n%s", strerror(errno)); - } - } - else if (!S_ISDIR(extrainfo.st_mode)) - { - I_FatalError ("~/.config must be a directory"); - } - - // This can be removed after a release or two - // Transfer the old zdoom directory to the new location - bool moved = false; - FString oldpath = NicePath("~/." GAMENAMELOWERCASE "/"); - if (stat (oldpath, &extrainfo) != -1) - { - if (rename(oldpath, path) == -1) - { - I_Error ("Failed to move old " GAMENAMELOWERCASE " directory (%s) to new location (%s).", - oldpath.GetChars(), path.GetChars()); - } - else - moved = true; - } - - if (!moved && mkdir (path, S_IRUSR | S_IWUSR | S_IXUSR) == -1) - { - I_FatalError ("Failed to create %s directory:\n%s", - path.GetChars(), strerror (errno)); - } - } - else - { - if (!S_ISDIR(info.st_mode)) - { - I_FatalError ("%s must be a directory", path.GetChars()); - } - } - path += file; - return path; -} - -//=========================================================================== -// -// M_GetCachePath Unix -// -// Returns the path for cache GL nodes. -// -//=========================================================================== - -FString M_GetCachePath(bool create) -{ - // Don't use GAME_DIR and such so that ZDoom and its child ports can - // share the node cache. - FString path = NicePath("~/.config/zdoom/cache"); - if (create) - { - CreatePath(path); - } - return path; -} - -//=========================================================================== -// -// M_GetAutoexecPath Unix -// -// Returns the expected location of autoexec.cfg. -// -//=========================================================================== - -FString M_GetAutoexecPath() -{ - return GetUserFile("autoexec.cfg"); -} - -//=========================================================================== -// -// M_GetCajunPath Unix -// -// Returns the location of the Cajun Bot definitions. -// -//=========================================================================== - -FString M_GetCajunPath(const char *botfilename) -{ - FString path; - - // Check first in ~/.config/zdoom/botfilename. - path = GetUserFile(botfilename); - if (!FileExists(path)) - { - // Then check in SHARE_DIR/botfilename. - path = SHARE_DIR; - path << botfilename; - if (!FileExists(path)) - { - path = ""; - } - } - return path; -} - -//=========================================================================== -// -// M_GetConfigPath Unix -// -// Returns the path to the config file. On Windows, this can vary for reading -// vs writing. i.e. If $PROGDIR/zdoom-.ini does not exist, it will try -// to read from $PROGDIR/zdoom.ini, but it will never write to zdoom.ini. -// -//=========================================================================== - -FString M_GetConfigPath(bool for_reading) -{ - return GetUserFile(GAMENAMELOWERCASE ".ini"); -} - -//=========================================================================== -// -// M_GetScreenshotsPath Unix -// -// Returns the path to the default screenshots directory. -// -//=========================================================================== - -FString M_GetScreenshotsPath() -{ - return NicePath("~/" GAME_DIR "/screenshots/"); -} - -//=========================================================================== -// -// M_GetSavegamesPath Unix -// -// Returns the path to the default save games directory. -// -//=========================================================================== - -FString M_GetSavegamesPath() -{ - return NicePath("~/" GAME_DIR); -} - -#endif From aece9aaa58bed5864fde2e7a84976e0afaf5288f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 3 Sep 2016 14:01:51 +0200 Subject: [PATCH 04/44] - added xBRZ texture scaler after clearing the licensing conditions. A screenshot of the confirmation that this is ok has been added to the 'licenses' folder. --- docs/licenses/xBRZ.jpg | Bin 0 -> 60099 bytes src/CMakeLists.txt | 6 +++- src/gl/data/gl_vertexbuffer.h | 10 ++++++ src/gl/scene/gl_scene.cpp | 2 -- src/gl/shaders/gl_shader.h | 3 +- src/gl/textures/gl_hqresize.cpp | 62 +++++++++++++++++++++++++++++--- wadsrc/static/menudef.z | 6 ++++ 7 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 docs/licenses/xBRZ.jpg diff --git a/docs/licenses/xBRZ.jpg b/docs/licenses/xBRZ.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab8335391b0e35541662e9664ad53c31aaed0c51 GIT binary patch literal 60099 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 z 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 9) -#else - if (self < 0 || self > 6) -#endif + if (self < 0 || self > 16) + { self = 0; + } + #ifndef HAVE_MMX + // This is to allow the menu option to work properly so that these filters can be skipped while cycling through them. + if (self == 7) self = 10; + if (self == 8) self = 10; + if (self == 9) self = 6; + #endif GLRenderer->FlushTextures(); } @@ -242,6 +248,42 @@ static unsigned char *hqNxHelper( void (*hqNxFunction) ( unsigned*, unsigned*, i } + +static unsigned char *xbrzHelper( void (*xbrzFunction) ( size_t, const uint32_t*, uint32_t*, int, int, xbrz::ColorFormat, const xbrz::ScalerCfg&, int, int ), + const int N, + unsigned char *inputBuffer, + const int inWidth, + const int inHeight, + int &outWidth, + int &outHeight ) +{ + outWidth = N * inWidth; + outHeight = N *inHeight; + + unsigned char * newBuffer = new unsigned char[outWidth*outHeight*4]; + xbrzFunction(N, reinterpret_cast(inputBuffer), reinterpret_cast(newBuffer), inWidth, inHeight, xbrz::ARGB, xbrz::ScalerCfg(), 0, std::numeric_limits::max()); + delete[] inputBuffer; + return newBuffer; +} + +static unsigned char *xbrzoldHelper( void (*xbrzFunction) ( size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz_old::ScalerCfg& cfg, int yFirst, int yLast ), + const int N, + unsigned char *inputBuffer, + const int inWidth, + const int inHeight, + int &outWidth, + int &outHeight ) +{ + outWidth = N * inWidth; + outHeight = N *inHeight; + + unsigned char * newBuffer = new unsigned char[outWidth*outHeight*4]; + xbrzFunction(N, reinterpret_cast(inputBuffer), reinterpret_cast(newBuffer), inWidth, inHeight, xbrz_old::ScalerCfg(), 0, std::numeric_limits::max()); + delete[] inputBuffer; + return newBuffer; +} + + //=========================================================================== // // [BB] Upsamples the texture in inputBuffer, frees inputBuffer and returns @@ -322,6 +364,16 @@ unsigned char *gl_CreateUpsampledTextureBuffer ( const FTexture *inputTexture, u case 9: return hqNxAsmHelper( &HQnX_asm::hq4x_32, 4, inputBuffer, inWidth, inHeight, outWidth, outHeight ); #endif + case 10: + case 11: + case 12: + return xbrzHelper(xbrz::scale, type - 8, inputBuffer, inWidth, inHeight, outWidth, outHeight ); + + case 13: + case 14: + case 15: + return xbrzoldHelper(xbrz_old::scale, type - 11, inputBuffer, inWidth, inHeight, outWidth, outHeight ); + } } return inputBuffer; diff --git a/wadsrc/static/menudef.z b/wadsrc/static/menudef.z index 2386b1076..86e2d5dc0 100644 --- a/wadsrc/static/menudef.z +++ b/wadsrc/static/menudef.z @@ -134,6 +134,12 @@ OptionValue "HqResizeModes" 7, "$OPTVAL_HQ2XMMX" 8, "$OPTVAL_HQ3XMMX" 9, "$OPTVAL_HQ4XMMX" + 10, "xBRZ 2x" + 11, "xBRZ 3x" + 12, "xBRZ 4x" + 13, "xBRZ_old 2x" + 14, "xBRZ_old 3x" + 15, "xBRZ_old 4x" } OptionValue "FogMode" From 4a80f8e4ed0bfff6e56bfef8bc1f302599253958 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 3 Sep 2016 16:54:17 +0200 Subject: [PATCH 05/44] - fixed: Camera textures and savegame pictures should not be drawn with a Stereo3D mode. --- src/gl/scene/gl_scene.cpp | 4 ++-- src/gl/stereo3d/gl_stereo3d.h | 1 + src/gl/stereo3d/gl_stereo_cvars.cpp | 7 +++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 82b63aa00..b4dd95d50 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -822,7 +822,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo // Render (potentially) multiple views for stereo 3d float viewShift[3]; - const s3d::Stereo3DMode& stereo3dMode = s3d::Stereo3DMode::getCurrentMode(); + const s3d::Stereo3DMode& stereo3dMode = mainview && toscreen? s3d::Stereo3DMode::getCurrentMode() : s3d::Stereo3DMode::getMonoMode(); stereo3dMode.SetUp(); for (int eye_ix = 0; eye_ix < stereo3dMode.eye_count(); ++eye_ix) { @@ -1313,7 +1313,7 @@ void FGLInterface::RenderTextureView (FCanvasTexture *tex, AActor *Viewpoint, in gl_fixedcolormap=CM_DEFAULT; gl_RenderState.SetFixedColormap(CM_DEFAULT); - bool usefb = gl_usefb || gltex->GetWidth() > screen->GetWidth() || gltex->GetHeight() > screen->GetHeight(); + bool usefb = !gl.legacyMode || gl_usefb || gltex->GetWidth() > screen->GetWidth() || gltex->GetHeight() > screen->GetHeight(); if (!usefb) { glFlush(); diff --git a/src/gl/stereo3d/gl_stereo3d.h b/src/gl/stereo3d/gl_stereo3d.h index 303f18825..c56cc078f 100644 --- a/src/gl/stereo3d/gl_stereo3d.h +++ b/src/gl/stereo3d/gl_stereo3d.h @@ -74,6 +74,7 @@ class Stereo3DMode public: /* static methods for managing the selected stereoscopic view state */ static const Stereo3DMode& getCurrentMode(); + static const Stereo3DMode& getMonoMode(); Stereo3DMode(); virtual ~Stereo3DMode(); diff --git a/src/gl/stereo3d/gl_stereo_cvars.cpp b/src/gl/stereo3d/gl_stereo_cvars.cpp index e7d08df41..897b28088 100644 --- a/src/gl/stereo3d/gl_stereo_cvars.cpp +++ b/src/gl/stereo3d/gl_stereo_cvars.cpp @@ -105,5 +105,12 @@ const Stereo3DMode& Stereo3DMode::getCurrentMode() return *currentStereo3DMode; } +const Stereo3DMode& Stereo3DMode::getMonoMode() +{ + setCurrentMode(MonoView::getInstance()); + return *currentStereo3DMode; +} + + } /* namespace s3d */ From 3ae2e77512acdc65b89d958345fa2fb73633d794 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 3 Sep 2016 16:55:16 +0200 Subject: [PATCH 06/44] - added xBRZ files. --- src/gl/xbr/xbrz.cpp | 1229 ++++++++++++++++++++++++++++++ src/gl/xbr/xbrz.h | 102 +++ src/gl/xbr/xbrz_config.h | 45 ++ src/gl/xbr/xbrz_config_old.h | 45 ++ src/gl/xbr/xbrz_old.cpp | 1365 ++++++++++++++++++++++++++++++++++ src/gl/xbr/xbrz_old.h | 92 +++ 6 files changed, 2878 insertions(+) create mode 100644 src/gl/xbr/xbrz.cpp create mode 100644 src/gl/xbr/xbrz.h create mode 100644 src/gl/xbr/xbrz_config.h create mode 100644 src/gl/xbr/xbrz_config_old.h create mode 100644 src/gl/xbr/xbrz_old.cpp create mode 100644 src/gl/xbr/xbrz_old.h diff --git a/src/gl/xbr/xbrz.cpp b/src/gl/xbr/xbrz.cpp new file mode 100644 index 000000000..b26d4bbd3 --- /dev/null +++ b/src/gl/xbr/xbrz.cpp @@ -0,0 +1,1229 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// * * +// * An explicit permission was granted to use xBRZ in combination with ZDoom * +// * and derived projects as long as it is used for non-commercial purposes. * +// * * +// * Backported to C++98 by Alexey Lysiuk * +// **************************************************************************** + +#include "xbrz.h" + +#include +#include +#include +#include + +#if __cplusplus <= 199711 +#define static_assert(VAL, MSG) static_assertion(); +template struct static_assertion; +template<> struct static_assertion {}; +#endif // __cplusplus <= 199711 + +namespace +{ +template inline +unsigned char getByte(uint32_t val) { return static_cast((val >> (8 * N)) & 0xff); } + +inline unsigned char getAlpha(uint32_t pix) { return getByte<3>(pix); } +inline unsigned char getRed (uint32_t pix) { return getByte<2>(pix); } +inline unsigned char getGreen(uint32_t pix) { return getByte<1>(pix); } +inline unsigned char getBlue (uint32_t pix) { return getByte<0>(pix); } + +inline uint32_t makePixel( unsigned char r, unsigned char g, unsigned char b) { return (r << 16) | (g << 8) | b; } +inline uint32_t makePixel(unsigned char a, unsigned char r, unsigned char g, unsigned char b) { return (a << 24) | (r << 16) | (g << 8) | b; } + + +template inline +uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color with opacity M / N over opaque background: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending +{ + static_assert(0 < M && M < N && N <= 1000, ""); + +#define calcColor(colFront, colBack) \ + (((colFront) * M + (colBack) * (N - M)) / N) + + return makePixel(calcColor(getRed (pixFront), getRed (pixBack)), + calcColor(getGreen(pixFront), getGreen(pixBack)), + calcColor(getBlue (pixFront), getBlue (pixBack))); + +#undef calcColor +} + + +template inline +uint32_t gradientARGB(uint32_t pixFront, uint32_t pixBack) //find intermediate color between two colors with alpha channels (=> NO alpha blending!!!) +{ + static_assert(0 < M && M < N && N <= 1000, ""); + + const unsigned int weightFront = getAlpha(pixFront) * M; + const unsigned int weightBack = getAlpha(pixBack) * (N - M); + const unsigned int weightSum = weightFront + weightBack; + if (weightSum == 0) + return 0; + +#define calcColor(colFront, colBack) \ + static_cast(((colFront) * weightFront + (colBack) * weightBack) / weightSum) + + return makePixel(static_cast(weightSum / N), + calcColor(getRed (pixFront), getRed (pixBack)), + calcColor(getGreen(pixFront), getGreen(pixBack)), + calcColor(getBlue (pixFront), getBlue (pixBack))); + +#undef calcColor +} + + +//inline +//double fastSqrt(double n) +//{ +// __asm //speeds up xBRZ by about 9% compared to std::sqrt which internally uses the same assembler instructions but adds some "fluff" +// { +// fld n +// fsqrt +// } +//} +// + + +uint32_t* byteAdvance( uint32_t* ptr, int bytes) { return reinterpret_cast< uint32_t*>(reinterpret_cast< char*>(ptr) + bytes); } +const uint32_t* byteAdvance(const uint32_t* ptr, int bytes) { return reinterpret_cast(reinterpret_cast(ptr) + bytes); } + + +//fill block with the given color +inline +void fillBlock(uint32_t* trg, int pitch, uint32_t col, int blockWidth, int blockHeight) +{ + //for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + // std::fill(trg, trg + blockWidth, col); + + for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + for (int x = 0; x < blockWidth; ++x) + trg[x] = col; +} + +inline +void fillBlock(uint32_t* trg, int pitch, uint32_t col, int n) { fillBlock(trg, pitch, col, n, n); } + + +#ifdef _MSC_VER + #define FORCE_INLINE __forceinline +#elif defined __GNUC__ + #define FORCE_INLINE __attribute__((always_inline)) inline +#else + #define FORCE_INLINE inline +#endif + + +enum RotationDegree //clock-wise +{ + ROT_0, + ROT_90, + ROT_180, + ROT_270 +}; + +//calculate input matrix coordinates after rotation at compile time +template +struct MatrixRotation; + +template +struct MatrixRotation +{ + static const size_t I_old = I; + static const size_t J_old = J; +}; + +template //(i, j) = (row, col) indices, N = size of (square) matrix +struct MatrixRotation +{ + static const size_t I_old = N - 1 - MatrixRotation(rotDeg - 1), I, J, N>::J_old; //old coordinates before rotation! + static const size_t J_old = MatrixRotation(rotDeg - 1), I, J, N>::I_old; // +}; + + +template +class OutputMatrix +{ +public: + OutputMatrix(uint32_t* out, int outWidth) : //access matrix area, top-left at position "out" for image with given width + out_(out), + outWidth_(outWidth) {} + + template + uint32_t& ref() const + { + static const size_t I_old = MatrixRotation::I_old; + static const size_t J_old = MatrixRotation::J_old; + return *(out_ + J_old + I_old * outWidth_); + } + +private: + uint32_t* out_; + const int outWidth_; +}; + + +template inline +T square(T value) { return value * value; } + + + +inline +double distRGB(uint32_t pix1, uint32_t pix2) +{ + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + //euklidean RGB distance + return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff)); +} + + +inline +double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight) +{ + //http://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion + //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first! + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //we may delay division by 255 to after matrix multiplication + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); // + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double! + + //const double k_b = 0.0722; //ITU-R BT.709 conversion + //const double k_r = 0.2126; // + const double k_b = 0.0593; //ITU-R BT.2020 conversion + const double k_r = 0.2627; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + //we skip division by 255 to have similar range like other distance functions + return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r)); +} + + +struct DistYCbCrBuffer //30% perf boost compared to distYCbCr()! +{ +public: + static double dist(uint32_t pix1, uint32_t pix2) + { +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif + static const DistYCbCrBuffer inst; + return inst.distImpl(pix1, pix2); + } + +private: + DistYCbCrBuffer() : buffer(256 * 256 * 256) + { + for (uint32_t i = 0; i < 256 * 256 * 256; ++i) //startup time: 114 ms on Intel Core i5 (four cores) + { + const int r_diff = getByte<2>(i) * 2 - 255; + const int g_diff = getByte<1>(i) * 2 - 255; + const int b_diff = getByte<0>(i) * 2 - 255; + + const double k_b = 0.0593; //ITU-R BT.2020 conversion + const double k_r = 0.2627; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + buffer[i] = static_cast(std::sqrt(square(y) + square(c_b) + square(c_r))); + } + } + + double distImpl(uint32_t pix1, uint32_t pix2) const + { + //if (pix1 == pix2) -> 8% perf degradation! + // return 0; + //if (pix1 > pix2) + // std::swap(pix1, pix2); -> 30% perf degradation!!! + + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + return buffer[(((r_diff + 255) / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte + (((g_diff + 255) / 2) << 8) | + (( b_diff + 255) / 2)]; + } + + std::vector buffer; //consumes 64 MB memory; using double is only 2% faster, but takes 128 MB +}; + + +enum BlendType +{ + BLEND_NONE = 0, + BLEND_NORMAL, //a normal indication to blend + BLEND_DOMINANT, //a strong indication to blend + //attention: BlendType must fit into the value range of 2 bit!!! +}; + +struct BlendResult +{ + BlendType + /**/blend_f, blend_g, + /**/blend_j, blend_k; +}; + + +struct Kernel_4x4 //kernel for preprocessing step +{ + uint32_t + /**/a, b, c, d, + /**/e, f, g, h, + /**/i, j, k, l, + /**/m, n, o, p; +}; + +/* +input kernel area naming convention: +----------------- +| A | B | C | D | +----|---|---|---| +| E | F | G | H | //evaluate the four corners between F, G, J, K +----|---|---|---| //input pixel is at position F +| I | J | K | L | +----|---|---|---| +| M | N | O | P | +----------------- +*/ +template +FORCE_INLINE //detect blend direction +BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz::ScalerCfg& cfg) //result: F, G, J, K corners of "GradientType" +{ + BlendResult result = {}; + + if ((ker.f == ker.g && + ker.j == ker.k) || + (ker.f == ker.j && + ker.g == ker.k)) + return result; + +#define dist(pix1, pix2) \ + ColorDistance::dist((pix1), (pix2), cfg.luminanceWeight) + + const int weight = 4; + double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + weight * dist(ker.j, ker.g); + double fk = dist(ker.e, ker.j) + dist(ker.j, ker.o) + dist(ker.b, ker.g) + dist(ker.g, ker.l) + weight * dist(ker.f, ker.k); + +#undef dist + + if (jg < fk) //test sample: 70% of values max(jg, fk) / min(jg, fk) are between 1.1 and 3.7 with median being 1.8 + { + const bool dominantGradient = cfg.dominantDirectionThreshold * jg < fk; + if (ker.f != ker.g && ker.f != ker.j) + result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.k != ker.j && ker.k != ker.g) + result.blend_k = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + else if (fk < jg) + { + const bool dominantGradient = cfg.dominantDirectionThreshold * fk < jg; + if (ker.j != ker.f && ker.j != ker.k) + result.blend_j = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.g != ker.f && ker.g != ker.k) + result.blend_g = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + return result; +} + +struct Kernel_3x3 +{ + uint32_t + /**/a, b, c, + /**/d, e, f, + /**/g, h, i; +}; + +#define DEF_GETTER(x) template uint32_t inline get_##x(const Kernel_3x3& ker) { return ker.x; } +//we cannot and NEED NOT write "ker.##x" since ## concatenates preprocessor tokens but "." is not a token +DEF_GETTER(a) DEF_GETTER(b) DEF_GETTER(c) +DEF_GETTER(d) DEF_GETTER(e) DEF_GETTER(f) +DEF_GETTER(g) DEF_GETTER(h) DEF_GETTER(i) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, g) DEF_GETTER(b, d) DEF_GETTER(c, a) +DEF_GETTER(d, h) DEF_GETTER(e, e) DEF_GETTER(f, b) +DEF_GETTER(g, i) DEF_GETTER(h, f) DEF_GETTER(i, c) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, i) DEF_GETTER(b, h) DEF_GETTER(c, g) +DEF_GETTER(d, f) DEF_GETTER(e, e) DEF_GETTER(f, d) +DEF_GETTER(g, c) DEF_GETTER(h, b) DEF_GETTER(i, a) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, c) DEF_GETTER(b, f) DEF_GETTER(c, i) +DEF_GETTER(d, b) DEF_GETTER(e, e) DEF_GETTER(f, h) +DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g) +#undef DEF_GETTER + + +//compress four blend types into a single byte +inline BlendType getTopL (unsigned char b) { return static_cast(0x3 & b); } +inline BlendType getTopR (unsigned char b) { return static_cast(0x3 & (b >> 2)); } +inline BlendType getBottomR(unsigned char b) { return static_cast(0x3 & (b >> 4)); } +inline BlendType getBottomL(unsigned char b) { return static_cast(0x3 & (b >> 6)); } + +inline void setTopL (unsigned char& b, BlendType bt) { b |= bt; } //buffer is assumed to be initialized before preprocessing! +inline void setTopR (unsigned char& b, BlendType bt) { b |= (bt << 2); } +inline void setBottomR(unsigned char& b, BlendType bt) { b |= (bt << 4); } +inline void setBottomL(unsigned char& b, BlendType bt) { b |= (bt << 6); } + +inline bool blendingNeeded(unsigned char b) { return b != 0; } + +template inline +unsigned char rotateBlendInfo(unsigned char b) { return b; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 2) | (b >> 6)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 4) | (b >> 4)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 6) | (b >> 2)) & 0xff; } + + +/* +input kernel area naming convention: +------------- +| A | B | C | +----|---|---| +| D | E | F | //input pixel is at position E +----|---|---| +| G | H | I | +------------- +*/ +template +FORCE_INLINE //perf: quite worth it! +void blendPixel(const Kernel_3x3& ker, + uint32_t* target, int trgWidth, + unsigned char blendInfo, //result of preprocessing all four corners of pixel "e" + const xbrz::ScalerCfg& cfg) +{ +#define a get_a(ker) +#define b get_b(ker) +#define c get_c(ker) +#define d get_d(ker) +#define e get_e(ker) +#define f get_f(ker) +#define g get_g(ker) +#define h get_h(ker) +#define i get_i(ker) + + const unsigned char blend = rotateBlendInfo(blendInfo); + + if (getBottomR(blend) >= BLEND_NORMAL) + { + struct LineBlend + { + static bool Eval(const Kernel_3x3& ker, const xbrz::ScalerCfg& cfg, const unsigned char blend) + { + if (getBottomR(blend) >= BLEND_DOMINANT) + return true; + +#define eq(pix1, pix2) \ + (ColorDistance::dist((pix1), (pix2), cfg.luminanceWeight) < cfg.equalColorTolerance) + + //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes + if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90 degree corners + return false; + if (getBottomL(blend) != BLEND_NONE && !eq(e, c)) + return false; + + //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes") + if (!eq(e, i) && eq(g, h) && eq(h , i) && eq(i, f) && eq(f, c)) + return false; + +#undef eq + + return true; + } + }; + + const bool doLineBlend = LineBlend::Eval(ker, cfg, blend); + +#define dist(pix1, pix2) \ + ColorDistance::dist((pix1), (pix2), cfg.luminanceWeight) + + const uint32_t px = dist(e, f) <= dist(e, h) ? f : h; //choose most similar color + + OutputMatrix out(target, trgWidth); + + if (doLineBlend) + { + const double fg = dist(f, g); //test sample: 70% of values max(fg, hc) / min(fg, hc) are between 1.1 and 3.7 with median being 1.9 + const double hc = dist(h, c); // + + const bool haveShallowLine = cfg.steepDirectionThreshold * fg <= hc && e != g && d != g; + const bool haveSteepLine = cfg.steepDirectionThreshold * hc <= fg && e != c && b != c; + + if (haveShallowLine) + { + if (haveSteepLine) + Scaler::blendLineSteepAndShallow(px, out); + else + Scaler::blendLineShallow(px, out); + } + else + { + if (haveSteepLine) + Scaler::blendLineSteep(px, out); + else + Scaler::blendLineDiagonal(px,out); + } + } + else + Scaler::blendCorner(px, out); + } + +#undef dist + +#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h +#undef i +} + + +template //scaler policy: see "Scaler2x" reference implementation +void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) +{ + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || srcWidth <= 0) + return; + + const int trgWidth = srcWidth * Scaler::scale; + + //"use" space at the end of the image as temporary buffer for "on the fly preprocessing": we even could use larger area of + //"sizeof(uint32_t) * srcWidth * (yLast - yFirst)" bytes without risk of accidental overwriting before accessing + const int bufferSize = srcWidth; + unsigned char* preProcBuffer = reinterpret_cast(trg + yLast * Scaler::scale * trgWidth) - bufferSize; + std::fill(preProcBuffer, preProcBuffer + bufferSize, 0); + static_assert(BLEND_NONE == 0, ""); + + //initialize preprocessing buffer for first row of current stripe: detect upper left and right corner blending + //this cannot be optimized for adjacent processing stripes; we must not allow for a memory race condition! + if (yFirst > 0) + { + const int y = yFirst - 1; + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + for (int x = 0; x < srcWidth; ++x) + { + const int x_m1 = std::max(x - 1, 0); + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + Kernel_4x4 ker = {}; //perf: initialization is negligible + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + ker.d = s_m1[x_p2]; + + ker.e = s_0[x_m1]; + ker.f = s_0[x]; + ker.g = s_0[x_p1]; + ker.h = s_0[x_p2]; + + ker.i = s_p1[x_m1]; + ker.j = s_p1[x]; + ker.k = s_p1[x_p1]; + ker.l = s_p1[x_p2]; + + ker.m = s_p2[x_m1]; + ker.n = s_p2[x]; + ker.o = s_p2[x_p1]; + ker.p = s_p2[x_p2]; + + const BlendResult res = preProcessCorners(ker, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //input pixel is at position F + | J | K | + --------- + */ + setTopR(preProcBuffer[x], res.blend_j); + + if (x + 1 < bufferSize) + setTopL(preProcBuffer[x + 1], res.blend_k); + } + } + //------------------------------------------------------------------------------------ + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* out = trg + Scaler::scale * y * trgWidth; //consider MT "striped" access + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + unsigned char blend_xy1 = 0; //corner blending for current (x, y + 1) position + + for (int x = 0; x < srcWidth; ++x, out += Scaler::scale) + { + //all those bounds checks have only insignificant impact on performance! + const int x_m1 = std::max(x - 1, 0); //perf: prefer array indexing to additional pointers! + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + Kernel_4x4 ker4 = {}; //perf: initialization is negligible + + ker4.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker4.b = s_m1[x]; + ker4.c = s_m1[x_p1]; + ker4.d = s_m1[x_p2]; + + ker4.e = s_0[x_m1]; + ker4.f = s_0[x]; + ker4.g = s_0[x_p1]; + ker4.h = s_0[x_p2]; + + ker4.i = s_p1[x_m1]; + ker4.j = s_p1[x]; + ker4.k = s_p1[x_p1]; + ker4.l = s_p1[x_p2]; + + ker4.m = s_p2[x_m1]; + ker4.n = s_p2[x]; + ker4.o = s_p2[x_p1]; + ker4.p = s_p2[x_p2]; + + //evaluate the four corners on bottom-right of current pixel + unsigned char blend_xy = 0; //for current (x, y) position + { + const BlendResult res = preProcessCorners(ker4, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //current input pixel is at position F + | J | K | + --------- + */ + blend_xy = preProcBuffer[x]; + setBottomR(blend_xy, res.blend_f); //all four corners of (x, y) have been determined at this point due to processing sequence! + + setTopR(blend_xy1, res.blend_j); //set 2nd known corner for (x, y + 1) + preProcBuffer[x] = blend_xy1; //store on current buffer position for use on next row + + blend_xy1 = 0; + setTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column + + if (x + 1 < bufferSize) //set 3rd known corner for (x + 1, y) + setBottomL(preProcBuffer[x + 1], res.blend_g); + } + + //fill block of size scale * scale with the given color + fillBlock(out, trgWidth * sizeof(uint32_t), ker4.f, Scaler::scale); //place *after* preprocessing step, to not overwrite the results while processing the the last pixel! + + //blend four corners of current pixel + if (blendingNeeded(blend_xy)) //good 5% perf-improvement + { + Kernel_3x3 ker3 = {}; //perf: initialization is negligible + + ker3.a = ker4.a; + ker3.b = ker4.b; + ker3.c = ker4.c; + + ker3.d = ker4.e; + ker3.e = ker4.f; + ker3.f = ker4.g; + + ker3.g = ker4.i; + ker3.h = ker4.j; + ker3.i = ker4.k; + + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + } + } + } +} + +//------------------------------------------------------------------------------------ + +template +struct Scaler2x : public ColorGradient +{ + static const int scale = 2; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<1, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 1>(), col); + alphaGrad<5, 6>(out.template ref<1, 1>(), col); //[!] fixes 7/8 used in xBR + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref<1, 1>(), col); + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<21, 100>(out.template ref<1, 1>(), col); //exact: 1 - pi/4 = 0.2146018366 + } +}; + + +template +struct Scaler3x : public ColorGradient +{ + static const int scale = 3; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + out.template ref<2, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<2, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 2>(), col); + alphaGrad<3, 4>(out.template ref<2, 1>(), col); + alphaGrad<3, 4>(out.template ref<1, 2>(), col); + out.template ref<2, 2>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 8>(out.template ref<1, 2>(), col); //conflict with other rotations for this odd scale + alphaGrad<1, 8>(out.template ref<2, 1>(), col); + alphaGrad<7, 8>(out.template ref<2, 2>(), col); // + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<45, 100>(out.template ref<2, 2>(), col); //exact: 0.4545939598 + //alphaGrad<7, 256>(out.template ref<2, 1>(), col); //0.02826017254 -> negligible + avoid conflicts with other rotations for this odd scale + //alphaGrad<7, 256>(out.template ref<1, 2>(), col); //0.02826017254 + } +}; + + +template +struct Scaler4x : public ColorGradient +{ + static const int scale = 4; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<3, 4>(out.template ref<3, 1>(), col); + alphaGrad<3, 4>(out.template ref<1, 3>(), col); + alphaGrad<1, 4>(out.template ref<3, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 3>(), col); + + alphaGrad<1, 3>(out.template ref<2, 2>(), col); //[!] fixes 1/4 used in xBR + + out.template ref<3, 3>() = col; + out.template ref<3, 2>() = col; + out.template ref<2, 3>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<68, 100>(out.template ref<3, 3>(), col); //exact: 0.6848532563 + alphaGrad< 9, 100>(out.template ref<3, 2>(), col); //0.08677704501 + alphaGrad< 9, 100>(out.template ref<2, 3>(), col); //0.08677704501 + } +}; + + +template +struct Scaler5x : public ColorGradient +{ + static const int scale = 5; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<4, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + alphaGrad<2, 3>(out.template ref<3, 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 8>(out.template ref(), col); //conflict with other rotations for this odd scale + alphaGrad<1, 8>(out.template ref(), col); + alphaGrad<1, 8>(out.template ref(), col); // + + alphaGrad<7, 8>(out.template ref<4, 3>(), col); + alphaGrad<7, 8>(out.template ref<3, 4>(), col); + + out.template ref<4, 4>() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<86, 100>(out.template ref<4, 4>(), col); //exact: 0.8631434088 + alphaGrad<23, 100>(out.template ref<4, 3>(), col); //0.2306749731 + alphaGrad<23, 100>(out.template ref<3, 4>(), col); //0.2306749731 + //alphaGrad<1, 64>(out.template ref<4, 2>(), col); //0.01676812367 -> negligible + avoid conflicts with other rotations for this odd scale + //alphaGrad<1, 64>(out.template ref<2, 4>(), col); //0.01676812367 + } +}; + + +template +struct Scaler6x : public ColorGradient +{ + static const int scale = 6; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<5, scale - 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<97, 100>(out.template ref<5, 5>(), col); //exact: 0.9711013910 + alphaGrad<42, 100>(out.template ref<4, 5>(), col); //0.4236372243 + alphaGrad<42, 100>(out.template ref<5, 4>(), col); //0.4236372243 + alphaGrad< 6, 100>(out.template ref<5, 3>(), col); //0.05652034508 + alphaGrad< 6, 100>(out.template ref<3, 5>(), col); //0.05652034508 + } +}; + +//------------------------------------------------------------------------------------ + +struct ColorDistanceRGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + return DistYCbCrBuffer::dist(pix1, pix2); + + //if (pix1 == pix2) //about 4% perf boost + // return 0; + //return distYCbCr(pix1, pix2, luminanceWeight); + } +}; + +struct ColorDistanceARGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + const double a1 = getAlpha(pix1) / 255.0 ; + const double a2 = getAlpha(pix2) / 255.0 ; + /* + Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1] + + 1. if a1 = a2, distance should be: a1 * distYCbCr() + 2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255 + 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() + */ + + //return std::min(a1, a2) * DistYCbCrBuffer::dist(pix1, pix2) + 255 * abs(a1 - a2); + //=> following code is 15% faster: + const double d = DistYCbCrBuffer::dist(pix1, pix2); + if (a1 < a2) + return a1 * d + 255 * (a2 - a1); + else + return a2 * d + 255 * (a1 - a2); + + //alternative? return std::sqrt(a1 * a2 * square(DistYCbCrBuffer::dist(pix1, pix2)) + square(255 * (a1 - a2))); + } +}; + + +struct ColorGradientRGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientRGB(pixFront, pixBack); + } +}; + +struct ColorGradientARGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientARGB(pixFront, pixBack); + } +}; +} + + +void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, ColorFormat colFmt, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) +{ + switch (colFmt) + { + case ARGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceARGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + + case RGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceRGB>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + } + assert(false); +} + + +bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance) +{ + switch (colFmt) + { + case ARGB: + return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + + case RGB: + return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + } + assert(false); + return false; +} + + +void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, + uint32_t* trg, int trgWidth, int trgHeight, int trgPitch, + SliceType st, int yFirst, int yLast) +{ + if (srcPitch < srcWidth * static_cast(sizeof(uint32_t)) || + trgPitch < trgWidth * static_cast(sizeof(uint32_t))) + { + assert(false); + return; + } + + switch (st) + { + case NN_SCALE_SLICE_SOURCE: + //nearest-neighbor (going over source image - fast for upscaling, since source is read only once + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + //mathematically: ySrc = floor(srcHeight * yTrg / trgHeight) + // => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight + + //keep within for loop to support MT input slices! + const int yTrg_first = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight) + const int yTrg_last = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight) + const int blockHeight = yTrg_last - yTrg_first; + + if (blockHeight > 0) + { + const uint32_t* srcLine = byteAdvance(src, y * srcPitch); + uint32_t* trgLine = byteAdvance(trg, yTrg_first * trgPitch); + int xTrg_first = 0; + + for (int x = 0; x < srcWidth; ++x) + { + int xTrg_last = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth; + const int blockWidth = xTrg_last - xTrg_first; + if (blockWidth > 0) + { + xTrg_first = xTrg_last; + fillBlock(trgLine, trgPitch, srcLine[x], blockWidth, blockHeight); + trgLine += blockWidth; + } + } + } + } + break; + + case NN_SCALE_SLICE_TARGET: + //nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* trgLine = byteAdvance(trg, y * trgPitch); + const int ySrc = srcHeight * y / trgHeight; + const uint32_t* srcLine = byteAdvance(src, ySrc * srcPitch); + for (int x = 0; x < trgWidth; ++x) + { + const int xSrc = srcWidth * x / trgWidth; + trgLine[x] = srcLine[xSrc]; + } + } + break; + } +} diff --git a/src/gl/xbr/xbrz.h b/src/gl/xbr/xbrz.h new file mode 100644 index 000000000..c641429e5 --- /dev/null +++ b/src/gl/xbr/xbrz.h @@ -0,0 +1,102 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// * * +// * An explicit permission was granted to use xBRZ in combination with ZDoom * +// * and derived projects as long as it is used for non-commercial purposes. * +// * * +// * Backported to C++98 by Alexey Lysiuk * +// **************************************************************************** + +#ifndef XBRZ_HEADER_3847894708239054 +#define XBRZ_HEADER_3847894708239054 + +#include //size_t +#include //uint32_t +#include +#include "xbrz_config.h" + +namespace xbrz +{ +/* +------------------------------------------------------------------------- +| xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | +------------------------------------------------------------------------- +using a modified approach of xBR: +http://board.byuu.org/viewtopic.php?f=10&t=2248 +- new rule set preserving small image features +- highly optimized for performance +- support alpha channel +- support multithreading +- support 64-bit architectures +- support processing image slices +- support scaling up to 6xBRZ +*/ + +enum ColorFormat //from high bits -> low bits, 8 bit per channel +{ + RGB, //8 bit for each red, green, blue, upper 8 bits unused + ARGB, //including alpha channel, BGRA byte order on little-endian machines +}; + +/* +-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only +-> support for source/target pitch in bytes! +-> if your emulator changes only a few image slices during each cycle (e.g. DOSBox) then there's no need to run xBRZ on the complete image: + Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis) + Caveat: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition + in the target image data if you are using multiple threads for processing each enlarged slice! + +THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap! + - there is a minor inefficiency for the first row of a slice, so avoid processing single rows only; suggestion: process 8-16 rows at least +*/ +#ifdef max +#undef max +#endif +void scale(size_t factor, //valid range: 2 - 6 + const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, + ColorFormat colFmt, + const ScalerCfg& cfg = ScalerCfg(), + int yFirst = 0, int yLast = std::numeric_limits::max()); //slice of source image + +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + uint32_t* trg, int trgWidth, int trgHeight); + +enum SliceType +{ + NN_SCALE_SLICE_SOURCE, + NN_SCALE_SLICE_TARGET, +}; +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, //pitch in bytes! + uint32_t* trg, int trgWidth, int trgHeight, int trgPitch, + SliceType st, int yFirst, int yLast); + +//parameter tuning +bool equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance); + + + + + +//########################### implementation ########################### +inline +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + uint32_t* trg, int trgWidth, int trgHeight) +{ + nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + NN_SCALE_SLICE_TARGET, 0, trgHeight); +} +} + +#endif diff --git a/src/gl/xbr/xbrz_config.h b/src/gl/xbr/xbrz_config.h new file mode 100644 index 000000000..28e9e9044 --- /dev/null +++ b/src/gl/xbr/xbrz_config.h @@ -0,0 +1,45 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// * * +// * An explicit permission was granted to use xBRZ in combination with ZDoom * +// * and derived projects as long as it is used for non-commercial purposes. * +// * * +// * Backported to C++98 by Alexey Lysiuk * +// **************************************************************************** + +#ifndef XBRZ_CONFIG_HEADER_284578425345 +#define XBRZ_CONFIG_HEADER_284578425345 + +//do NOT include any headers here! used by xBRZ_dll!!! + +namespace xbrz +{ +struct ScalerCfg +{ + ScalerCfg() : + luminanceWeight(1), + equalColorTolerance(30), + dominantDirectionThreshold(3.6), + steepDirectionThreshold(2.2), + newTestAttribute(0) {} + + double luminanceWeight; + double equalColorTolerance; + double dominantDirectionThreshold; + double steepDirectionThreshold; + double newTestAttribute; //unused; test new parameters +}; +} + +#endif \ No newline at end of file diff --git a/src/gl/xbr/xbrz_config_old.h b/src/gl/xbr/xbrz_config_old.h new file mode 100644 index 000000000..480af7976 --- /dev/null +++ b/src/gl/xbr/xbrz_config_old.h @@ -0,0 +1,45 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// * * +// * An explicit permission was granted to use xBRZ in combination with ZDoom * +// * and derived projects as long as it is used for non-commercial purposes. * +// * * +// * Backported to C++98 by Alexey Lysiuk * +// **************************************************************************** + +#ifndef __XBRZ_CONFIG_OLD_HEADER_INCLUDED__ +#define __XBRZ_CONFIG_OLD_HEADER_INCLUDED__ + +//do NOT include any headers here! used by xBRZ_dll!!! + +namespace xbrz_old +{ +struct ScalerCfg +{ + ScalerCfg() : + luminanceWeight_(1), + equalColorTolerance_(30), + dominantDirectionThreshold(3.6), + steepDirectionThreshold(2.2), + newTestAttribute_(0) {} + + double luminanceWeight_; + double equalColorTolerance_; + double dominantDirectionThreshold; + double steepDirectionThreshold; + double newTestAttribute_; //unused; test new parameters +}; +} + +#endif \ No newline at end of file diff --git a/src/gl/xbr/xbrz_old.cpp b/src/gl/xbr/xbrz_old.cpp new file mode 100644 index 000000000..07527cb95 --- /dev/null +++ b/src/gl/xbr/xbrz_old.cpp @@ -0,0 +1,1365 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// * * +// * An explicit permission was granted to use xBRZ in combination with ZDoom * +// * and derived projects as long as it is used for non-commercial purposes. * +// * * +// * Backported to C++98 by Alexey Lysiuk * +// **************************************************************************** + +#include "xbrz_old.h" + +#include +#include +#include + +#if __cplusplus > 199711 +#define XBRZ_CXX11 +#endif // __cplusplus > 199711 + +namespace +{ +template inline +unsigned char getByte(uint32_t val) { return static_cast((val >> (8 * N)) & 0xff); } + +inline unsigned char getRed (uint32_t val) { return getByte<2>(val); } +inline unsigned char getGreen(uint32_t val) { return getByte<1>(val); } +inline unsigned char getBlue (uint32_t val) { return getByte<0>(val); } + +template inline +T abs(T value) +{ +#ifdef XBRZ_CXX11 + static_assert(std::numeric_limits::is_signed, ""); +#endif // XBRZ_CXX11 + return value < 0 ? -value : value; +} + +const uint32_t redMask = 0xff0000; +const uint32_t greenMask = 0x00ff00; +const uint32_t blueMask = 0x0000ff; + +template inline +void alphaBlend(uint32_t& dst, uint32_t col) //blend color over destination with opacity N / M +{ +#ifdef XBRZ_CXX11 + static_assert(N < 256, "possible overflow of (col & redMask) * N"); + static_assert(M < 256, "possible overflow of (col & redMask ) * N + (dst & redMask ) * (M - N)"); + static_assert(0 < N && N < M, ""); +#endif // XBRZ_CXX11 + + static const uint32_t ALPHA_MASK = 0xFF000000; + static const uint32_t ALPHA_SHIFT = 24; + + static const uint32_t FULL_OPAQUE = 0xFF; + + const uint32_t colAlpha = col >> ALPHA_SHIFT; + const uint32_t dstAlpha = dst >> ALPHA_SHIFT; + + // Overflow is ignored intentionally! + + const uint32_t alpha = (FULL_OPAQUE == colAlpha && FULL_OPAQUE == dstAlpha) + ? ALPHA_MASK + : ALPHA_MASK & ((colAlpha * N + dstAlpha * (M - N)) / M << ALPHA_SHIFT); + + dst = (redMask & ((col & redMask ) * N + (dst & redMask ) * (M - N)) / M) | //this works because 8 upper bits are free + (greenMask & ((col & greenMask) * N + (dst & greenMask) * (M - N)) / M) | + (blueMask & ((col & blueMask ) * N + (dst & blueMask ) * (M - N)) / M) | + alpha; +} + + +//inline +//double fastSqrt(double n) +//{ +// __asm //speeds up xBRZ by about 9% compared to std::sqrt +// { +// fld n +// fsqrt +// } +//} +// + + +inline +uint32_t alphaBlend2(uint32_t pix1, uint32_t pix2, double alpha) +{ + return (redMask & static_cast((pix1 & redMask ) * alpha + (pix2 & redMask ) * (1 - alpha))) | + (greenMask & static_cast((pix1 & greenMask) * alpha + (pix2 & greenMask) * (1 - alpha))) | + (blueMask & static_cast((pix1 & blueMask ) * alpha + (pix2 & blueMask ) * (1 - alpha))); +} + + +uint32_t* byteAdvance( uint32_t* ptr, int bytes) { return reinterpret_cast< uint32_t*>(reinterpret_cast< char*>(ptr) + bytes); } +const uint32_t* byteAdvance(const uint32_t* ptr, int bytes) { return reinterpret_cast(reinterpret_cast(ptr) + bytes); } + + +//fill block with the given color +inline +void fillBlock(uint32_t* trg, int pitch, uint32_t col, int blockWidth, int blockHeight) +{ + //for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + // std::fill(trg, trg + blockWidth, col); + + for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + for (int x = 0; x < blockWidth; ++x) + trg[x] = col; +} + +inline +void fillBlock(uint32_t* trg, int pitch, uint32_t col, int n) { fillBlock(trg, pitch, col, n, n); } + + +#ifdef _MSC_VER +#define FORCE_INLINE __forceinline +#elif defined __GNUC__ +#define FORCE_INLINE __attribute__((always_inline)) inline +#else +#define FORCE_INLINE inline +#endif + + +enum RotationDegree //clock-wise +{ + ROT_0, + ROT_90, + ROT_180, + ROT_270 +}; + +//calculate input matrix coordinates after rotation at compile time +template +struct MatrixRotation; + +template +struct MatrixRotation +{ + static const size_t I_old = I; + static const size_t J_old = J; +}; + +template //(i, j) = (row, col) indices, N = size of (square) matrix +struct MatrixRotation +{ + static const size_t I_old = N - 1 - MatrixRotation(rotDeg - 1), I, J, N>::J_old; //old coordinates before rotation! + static const size_t J_old = MatrixRotation(rotDeg - 1), I, J, N>::I_old; // +}; + + +template +class OutputMatrix +{ +public: + OutputMatrix(uint32_t* out, int outWidth) : //access matrix area, top-left at position "out" for image with given width + out_(out), + outWidth_(outWidth) {} + + template + uint32_t& ref() const + { + static const size_t I_old = MatrixRotation::I_old; + static const size_t J_old = MatrixRotation::J_old; + return *(out_ + J_old + I_old * outWidth_); + } + +private: + uint32_t* out_; + const int outWidth_; +}; + + +template inline +T square(T value) { return value * value; } + + +/* +inline +void rgbtoLuv(uint32_t c, double& L, double& u, double& v) +{ + //http://www.easyrgb.com/index.php?X=MATH&H=02#text2 + double r = getRed (c) / 255.0; + double g = getGreen(c) / 255.0; + double b = getBlue (c) / 255.0; + + if ( r > 0.04045 ) + r = std::pow(( ( r + 0.055 ) / 1.055 ) , 2.4); + else + r /= 12.92; + if ( g > 0.04045 ) + g = std::pow(( ( g + 0.055 ) / 1.055 ) , 2.4); + else + g /= 12.92; + if ( b > 0.04045 ) + b = std::pow(( ( b + 0.055 ) / 1.055 ) , 2.4); + else + b /= 12.92; + + r *= 100; + g *= 100; + b *= 100; + + double x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b; + double y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b; + double z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b; + //--------------------- + double var_U = 4 * x / ( x + 15 * y + 3 * z ); + double var_V = 9 * y / ( x + 15 * y + 3 * z ); + double var_Y = y / 100; + + if ( var_Y > 0.008856 ) var_Y = std::pow(var_Y , 1.0/3 ); + else var_Y = 7.787 * var_Y + 16.0 / 116; + + const double ref_X = 95.047; //Observer= 2 degree, Illuminant= D65 + const double ref_Y = 100.000; + const double ref_Z = 108.883; + + const double ref_U = ( 4 * ref_X ) / ( ref_X + ( 15 * ref_Y ) + ( 3 * ref_Z ) ); + const double ref_V = ( 9 * ref_Y ) / ( ref_X + ( 15 * ref_Y ) + ( 3 * ref_Z ) ); + + L = ( 116 * var_Y ) - 16; + u = 13 * L * ( var_U - ref_U ); + v = 13 * L * ( var_V - ref_V ); +} +*/ + +inline +void rgbtoLab(uint32_t c, unsigned char& L, signed char& A, signed char& B) +{ + //code: http://www.easyrgb.com/index.php?X=MATH + //test: http://www.workwithcolor.com/color-converter-01.htm + //------RGB to XYZ------ + double r = getRed (c) / 255.0; + double g = getGreen(c) / 255.0; + double b = getBlue (c) / 255.0; + + r = r > 0.04045 ? std::pow(( r + 0.055 ) / 1.055, 2.4) : r / 12.92; + r = g > 0.04045 ? std::pow(( g + 0.055 ) / 1.055, 2.4) : g / 12.92; + r = b > 0.04045 ? std::pow(( b + 0.055 ) / 1.055, 2.4) : b / 12.92; + + r *= 100; + g *= 100; + b *= 100; + + double x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b; + double y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b; + double z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b; + //------XYZ to Lab------ + const double refX = 95.047; // + const double refY = 100.000; //Observer= 2 degree, Illuminant= D65 + const double refZ = 108.883; // + double var_X = x / refX; + double var_Y = y / refY; + double var_Z = z / refZ; + + var_X = var_X > 0.008856 ? std::pow(var_X, 1.0 / 3) : 7.787 * var_X + 4.0 / 29; + var_Y = var_Y > 0.008856 ? std::pow(var_Y, 1.0 / 3) : 7.787 * var_Y + 4.0 / 29; + var_Z = var_Z > 0.008856 ? std::pow(var_Z, 1.0 / 3) : 7.787 * var_Z + 4.0 / 29; + + L = static_cast(116 * var_Y - 16); + A = static_cast< signed char>(500 * (var_X - var_Y)); + B = static_cast< signed char>(200 * (var_Y - var_Z)); +}; + + +inline +double distLAB(uint32_t pix1, uint32_t pix2) +{ + unsigned char L1 = 0; //[0, 100] + signed char a1 = 0; //[-128, 127] + signed char b1 = 0; //[-128, 127] + rgbtoLab(pix1, L1, a1, b1); + + unsigned char L2 = 0; + signed char a2 = 0; + signed char b2 = 0; + rgbtoLab(pix2, L2, a2, b2); + + //----------------------------- + //http://www.easyrgb.com/index.php?X=DELT + + //Delta E/CIE76 + return std::sqrt(square(1.0 * L1 - L2) + + square(1.0 * a1 - a2) + + square(1.0 * b1 - b2)); +} + + +/* +inline +void rgbtoHsl(uint32_t c, double& h, double& s, double& l) +{ + //http://www.easyrgb.com/index.php?X=MATH&H=18#text18 + const int r = getRed (c); + const int g = getGreen(c); + const int b = getBlue (c); + + const int varMin = numeric::min(r, g, b); + const int varMax = numeric::max(r, g, b); + const int delMax = varMax - varMin; + + l = (varMax + varMin) / 2.0 / 255.0; + + if (delMax == 0) //gray, no chroma... + { + h = 0; + s = 0; + } + else + { + s = l < 0.5 ? + delMax / (1.0 * varMax + varMin) : + delMax / (2.0 * 255 - varMax - varMin); + + double delR = ((varMax - r) / 6.0 + delMax / 2.0) / delMax; + double delG = ((varMax - g) / 6.0 + delMax / 2.0) / delMax; + double delB = ((varMax - b) / 6.0 + delMax / 2.0) / delMax; + + if (r == varMax) + h = delB - delG; + else if (g == varMax) + h = 1 / 3.0 + delR - delB; + else if (b == varMax) + h = 2 / 3.0 + delG - delR; + + if (h < 0) + h += 1; + if (h > 1) + h -= 1; + } +} + +inline +double distHSL(uint32_t pix1, uint32_t pix2, double lightningWeight) +{ + double h1 = 0; + double s1 = 0; + double l1 = 0; + rgbtoHsl(pix1, h1, s1, l1); + double h2 = 0; + double s2 = 0; + double l2 = 0; + rgbtoHsl(pix2, h2, s2, l2); + + //HSL is in cylindric coordinatates where L represents height, S radius, H angle, + //however we interpret the cylinder as a bi-conic solid with top/bottom radius 0, middle radius 1 + assert(0 <= h1 && h1 <= 1); + assert(0 <= h2 && h2 <= 1); + + double r1 = l1 < 0.5 ? + l1 * 2 : + 2 - l1 * 2; + + double x1 = r1 * s1 * std::cos(h1 * 2 * numeric::pi); + double y1 = r1 * s1 * std::sin(h1 * 2 * numeric::pi); + double z1 = l1; + + double r2 = l2 < 0.5 ? + l2 * 2 : + 2 - l2 * 2; + + double x2 = r2 * s2 * std::cos(h2 * 2 * numeric::pi); + double y2 = r2 * s2 * std::sin(h2 * 2 * numeric::pi); + double z2 = l2; + + return 255 * std::sqrt(square(x1 - x2) + square(y1 - y2) + square(lightningWeight * (z1 - z2))); +} +*/ + + +inline +double distRGB(uint32_t pix1, uint32_t pix2) +{ + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + //euklidean RGB distance + return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff)); +} + + +inline +double distNonLinearRGB(uint32_t pix1, uint32_t pix2) +{ + //non-linear rgb: http://www.compuphase.com/cmetric.htm + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + const double r_avg = (static_cast(getRed(pix1)) + getRed(pix2)) / 2; + return std::sqrt((2 + r_avg / 255) * square(r_diff) + 4 * square(g_diff) + (2 + (255 - r_avg) / 255) * square(b_diff)); +} + + +inline +double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight) +{ + //http://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion + //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first! + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //we may delay division by 255 to after matrix multiplication + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); // + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double! + + const double k_b = 0.0722; //ITU-R BT.709 conversion + const double k_r = 0.2126; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + //we skip division by 255 to have similar range like other distance functions + return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r)); +} + + +inline +double distYUV(uint32_t pix1, uint32_t pix2, double luminanceWeight) +{ + //perf: it's not worthwhile to buffer the YUV-conversion, the direct code is faster by ~ 6% + //since RGB -> YUV conversion is essentially a matrix multiplication, we can calculate the RGB diff before the conversion (distributive property) + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + //http://en.wikipedia.org/wiki/YUV#Conversion_to.2Ffrom_RGB + const double w_b = 0.114; + const double w_r = 0.299; + const double w_g = 1 - w_r - w_b; + + const double u_max = 0.436; + const double v_max = 0.615; + + const double scale_u = u_max / (1 - w_b); + const double scale_v = v_max / (1 - w_r); + + double y = w_r * r_diff + w_g * g_diff + w_b * b_diff;//value range: 255 * [-1, 1] + double u = scale_u * (b_diff - y); //value range: 255 * 2 * u_max * [-1, 1] + double v = scale_v * (r_diff - y); //value range: 255 * 2 * v_max * [-1, 1] + +#ifndef NDEBUG + const double eps = 0.5; +#endif + assert(std::abs(y) <= 255 + eps); + assert(std::abs(u) <= 255 * 2 * u_max + eps); + assert(std::abs(v) <= 255 * 2 * v_max + eps); + + return std::sqrt(square(luminanceWeight * y) + square(u) + square(v)); +} + + +inline +double colorDist(uint32_t pix1, uint32_t pix2, double luminanceWeight) +{ + if (pix1 == pix2) //about 8% perf boost + return 0; + + //return distHSL(pix1, pix2, luminanceWeight); + //return distRGB(pix1, pix2); + //return distLAB(pix1, pix2); + //return distNonLinearRGB(pix1, pix2); + //return distYUV(pix1, pix2, luminanceWeight); + + return distYCbCr(pix1, pix2, luminanceWeight); +} + + +enum BlendType +{ + BLEND_NONE = 0, + BLEND_NORMAL, //a normal indication to blend + BLEND_DOMINANT, //a strong indication to blend + //attention: BlendType must fit into the value range of 2 bit!!! +}; + +struct BlendResult +{ + BlendType + /**/blend_f, blend_g, + /**/blend_j, blend_k; +}; + + +struct Kernel_4x4 //kernel for preprocessing step +{ + uint32_t + /**/a, b, c, d, + /**/e, f, g, h, + /**/i, j, k, l, + /**/m, n, o, p; +}; + +/* +input kernel area naming convention: +----------------- +| A | B | C | D | +----|---|---|---| +| E | F | G | H | //evalute the four corners between F, G, J, K +----|---|---|---| //input pixel is at position F +| I | J | K | L | +----|---|---|---| +| M | N | O | P | +----------------- +*/ +FORCE_INLINE //detect blend direction +BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz_old::ScalerCfg& cfg) //result: F, G, J, K corners of "GradientType" +{ + BlendResult result = {}; + + if ((ker.f == ker.g && + ker.j == ker.k) || + (ker.f == ker.j && + ker.g == ker.k)) + return result; + +#ifdef XBRZ_CXX11 + auto dist = [&](uint32_t col1, uint32_t col2) { return colorDist(col1, col2, cfg.luminanceWeight_); }; +#else // !XBRZ_CXX11 +#define dist(C1, C2) colorDist((C1), (C2), cfg.luminanceWeight_) +#endif // XBRZ_CXX11 + + const int weight = 4; + double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + weight * dist(ker.j, ker.g); + double fk = dist(ker.e, ker.j) + dist(ker.j, ker.o) + dist(ker.b, ker.g) + dist(ker.g, ker.l) + weight * dist(ker.f, ker.k); + +#ifndef XBRZ_CXX11 +#undef dist +#endif // !XBRZ_CXX11 + + if (jg < fk) //test sample: 70% of values max(jg, fk) / min(jg, fk) are between 1.1 and 3.7 with median being 1.8 + { + const bool dominantGradient = cfg.dominantDirectionThreshold * jg < fk; + if (ker.f != ker.g && ker.f != ker.j) + result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.k != ker.j && ker.k != ker.g) + result.blend_k = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + else if (fk < jg) + { + const bool dominantGradient = cfg.dominantDirectionThreshold * fk < jg; + if (ker.j != ker.f && ker.j != ker.k) + result.blend_j = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.g != ker.f && ker.g != ker.k) + result.blend_g = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + return result; +} + +struct Kernel_3x3 +{ + uint32_t + /**/a, b, c, + /**/d, e, f, + /**/g, h, i; +}; + +#define DEF_GETTER(x) template uint32_t inline get_##x(const Kernel_3x3& ker) { return ker.x; } +//we cannot and NEED NOT write "ker.##x" since ## concatenates preprocessor tokens but "." is not a token +DEF_GETTER(a) DEF_GETTER(b) DEF_GETTER(c) +DEF_GETTER(d) DEF_GETTER(e) DEF_GETTER(f) +DEF_GETTER(g) DEF_GETTER(h) DEF_GETTER(i) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, g) DEF_GETTER(b, d) DEF_GETTER(c, a) +DEF_GETTER(d, h) DEF_GETTER(e, e) DEF_GETTER(f, b) +DEF_GETTER(g, i) DEF_GETTER(h, f) DEF_GETTER(i, c) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, i) DEF_GETTER(b, h) DEF_GETTER(c, g) +DEF_GETTER(d, f) DEF_GETTER(e, e) DEF_GETTER(f, d) +DEF_GETTER(g, c) DEF_GETTER(h, b) DEF_GETTER(i, a) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, c) DEF_GETTER(b, f) DEF_GETTER(c, i) +DEF_GETTER(d, b) DEF_GETTER(e, e) DEF_GETTER(f, h) +DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g) +#undef DEF_GETTER + + +//compress four blend types into a single byte +inline BlendType getTopL (unsigned char b) { return static_cast(0x3 & b); } +inline BlendType getTopR (unsigned char b) { return static_cast(0x3 & (b >> 2)); } +inline BlendType getBottomR(unsigned char b) { return static_cast(0x3 & (b >> 4)); } +inline BlendType getBottomL(unsigned char b) { return static_cast(0x3 & (b >> 6)); } + +inline void setTopL (unsigned char& b, BlendType bt) { b |= bt; } //buffer is assumed to be initialized before preprocessing! +inline void setTopR (unsigned char& b, BlendType bt) { b |= (bt << 2); } +inline void setBottomR(unsigned char& b, BlendType bt) { b |= (bt << 4); } +inline void setBottomL(unsigned char& b, BlendType bt) { b |= (bt << 6); } + +inline bool blendingNeeded(unsigned char b) { return b != 0; } + +template inline +unsigned char rotateBlendInfo(unsigned char b) { return b; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 2) | (b >> 6)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 4) | (b >> 4)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 6) | (b >> 2)) & 0xff; } + + +#ifndef NDEBUG +int debugPixelX = -1; +int debugPixelY = 84; +bool breakIntoDebugger = false; +#endif + +#define a get_a(ker) +#define b get_b(ker) +#define c get_c(ker) +#define d get_d(ker) +#define e get_e(ker) +#define f get_f(ker) +#define g get_g(ker) +#define h get_h(ker) +#define i get_i(ker) + +#ifndef XBRZ_CXX11 + +template +bool doLineBlend(const Kernel_3x3& ker, const xbrz_old::ScalerCfg& cfg, const unsigned char blend) +{ + if (getBottomR(blend) >= BLEND_DOMINANT) + return true; + +#define eq(C1, C2) (colorDist((C1), (C2), cfg.luminanceWeight_) < cfg.equalColorTolerance_) + + //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes + if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90 degree corners + return false; + if (getBottomL(blend) != BLEND_NONE && !eq(e, c)) + return false; + + //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes") + if (eq(g, h) && eq(h , i) && eq(i, f) && eq(f, c) && !eq(e, i)) + return false; + +#undef eq + + return true; +}; + +#endif // !XBRZ_CXX11 + +/* +input kernel area naming convention: +------------- +| A | B | C | +----|---|---| +| D | E | F | //input pixel is at position E +----|---|---| +| G | H | I | +------------- +*/ +template +FORCE_INLINE //perf: quite worth it! +void scalePixel(const Kernel_3x3& ker, + uint32_t* target, int trgWidth, + unsigned char blendInfo, //result of preprocessing all four corners of pixel "e" + const xbrz_old::ScalerCfg& cfg) +{ +#ifndef NDEBUG + if (breakIntoDebugger) +#ifdef _MSC_VER + __debugbreak(); //__asm int 3; +#else // !_MSC_VER + __builtin_trap(); +#endif // _MSC_VER +#endif + + const unsigned char blend = rotateBlendInfo(blendInfo); + + if (getBottomR(blend) >= BLEND_NORMAL) + { +#ifdef XBRZ_CXX11 + auto eq = [&](uint32_t col1, uint32_t col2) { return colorDist(col1, col2, cfg.luminanceWeight_) < cfg.equalColorTolerance_; }; + auto dist = [&](uint32_t col1, uint32_t col2) { return colorDist(col1, col2, cfg.luminanceWeight_); }; + + const bool doLineBlend = [&]() -> bool + { + if (getBottomR(blend) >= BLEND_DOMINANT) + return true; + + //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes + if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90 degree corners + return false; + if (getBottomL(blend) != BLEND_NONE && !eq(e, c)) + return false; + + //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes") + if (eq(g, h) && eq(h , i) && eq(i, f) && eq(f, c) && !eq(e, i)) + return false; + + return true; + }(); +#else // !XBRZ_CXX11 +#define dist(C1, C2) colorDist((C1), (C2), cfg.luminanceWeight_) +#endif // XBRZ_CXX11 + + const uint32_t px = dist(e, f) <= dist(e, h) ? f : h; //choose most similar color + + OutputMatrix out(target, trgWidth); + +#ifdef XBRZ_CXX11 + if (doLineBlend) +#else // !XBRZ_CXX11 + if (doLineBlend(ker, cfg, blend)) +#endif // XBRZ_CXX11 + { + const double fg = dist(f, g); //test sample: 70% of values max(fg, hc) / min(fg, hc) are between 1.1 and 3.7 with median being 1.9 + const double hc = dist(h, c); // + + const bool haveShallowLine = cfg.steepDirectionThreshold * fg <= hc && e != g && d != g; + const bool haveSteepLine = cfg.steepDirectionThreshold * hc <= fg && e != c && b != c; + + if (haveShallowLine) + { + if (haveSteepLine) + Scaler::blendLineSteepAndShallow(px, out); + else + Scaler::blendLineShallow(px, out); + } + else + { + if (haveSteepLine) + Scaler::blendLineSteep(px, out); + else + Scaler::blendLineDiagonal(px,out); + } + } + else + Scaler::blendCorner(px, out); + } + +#ifndef XBRZ_CXX11 +#undef dist +#endif // XBRZ_CXX11 + +#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h +#undef i +} + + +template //scaler policy: see "Scaler2x" reference implementation +void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz_old::ScalerCfg& cfg, int yFirst, int yLast) +{ + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || srcWidth <= 0) + return; + + const int trgWidth = srcWidth * Scaler::scale; + + //"use" space at the end of the image as temporary buffer for "on the fly preprocessing": we even could use larger area of + //"sizeof(uint32_t) * srcWidth * (yLast - yFirst)" bytes without risk of accidental overwriting before accessing + const int bufferSize = srcWidth; + unsigned char* preProcBuffer = reinterpret_cast(trg + yLast * Scaler::scale * trgWidth) - bufferSize; + std::fill(preProcBuffer, preProcBuffer + bufferSize, 0); +#ifdef XBRZ_CXX11 + static_assert(BLEND_NONE == 0, ""); +#endif // XBRZ_CXX11 + + //initialize preprocessing buffer for first row: detect upper left and right corner blending + //this cannot be optimized for adjacent processing stripes; we must not allow for a memory race condition! + if (yFirst > 0) + { + const int y = yFirst - 1; + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + for (int x = 0; x < srcWidth; ++x) + { + const int x_m1 = std::max(x - 1, 0); + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + Kernel_4x4 ker = {}; //perf: initialization is negligable + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + ker.d = s_m1[x_p2]; + + ker.e = s_0[x_m1]; + ker.f = s_0[x]; + ker.g = s_0[x_p1]; + ker.h = s_0[x_p2]; + + ker.i = s_p1[x_m1]; + ker.j = s_p1[x]; + ker.k = s_p1[x_p1]; + ker.l = s_p1[x_p2]; + + ker.m = s_p2[x_m1]; + ker.n = s_p2[x]; + ker.o = s_p2[x_p1]; + ker.p = s_p2[x_p2]; + + const BlendResult res = preProcessCorners(ker, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //input pixel is at position F + | J | K | + --------- + */ + setTopR(preProcBuffer[x], res.blend_j); + + if (x + 1 < srcWidth) + setTopL(preProcBuffer[x + 1], res.blend_k); + } + } + //------------------------------------------------------------------------------------ + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* out = trg + Scaler::scale * y * trgWidth; //consider MT "striped" access + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + unsigned char blend_xy1 = 0; //corner blending for current (x, y + 1) position + + for (int x = 0; x < srcWidth; ++x, out += Scaler::scale) + { +#ifndef NDEBUG + breakIntoDebugger = debugPixelX == x && debugPixelY == y; +#endif + //all those bounds checks have only insignificant impact on performance! + const int x_m1 = std::max(x - 1, 0); //perf: prefer array indexing to additional pointers! + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + //evaluate the four corners on bottom-right of current pixel + unsigned char blend_xy = 0; //for current (x, y) position + { + Kernel_4x4 ker = {}; //perf: initialization is negligable + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + ker.d = s_m1[x_p2]; + + ker.e = s_0[x_m1]; + ker.f = s_0[x]; + ker.g = s_0[x_p1]; + ker.h = s_0[x_p2]; + + ker.i = s_p1[x_m1]; + ker.j = s_p1[x]; + ker.k = s_p1[x_p1]; + ker.l = s_p1[x_p2]; + + ker.m = s_p2[x_m1]; + ker.n = s_p2[x]; + ker.o = s_p2[x_p1]; + ker.p = s_p2[x_p2]; + + const BlendResult res = preProcessCorners(ker, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //current input pixel is at position F + | J | K | + --------- + */ + blend_xy = preProcBuffer[x]; + setBottomR(blend_xy, res.blend_f); //all four corners of (x, y) have been determined at this point due to processing sequence! + + setTopR(blend_xy1, res.blend_j); //set 2nd known corner for (x, y + 1) + preProcBuffer[x] = blend_xy1; //store on current buffer position for use on next row + + blend_xy1 = 0; + setTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column + + if (x + 1 < srcWidth) //set 3rd known corner for (x + 1, y) + setBottomL(preProcBuffer[x + 1], res.blend_g); + } + + //fill block of size scale * scale with the given color + fillBlock(out, trgWidth * sizeof(uint32_t), s_0[x], Scaler::scale); //place *after* preprocessing step, to not overwrite the results while processing the the last pixel! + + //blend four corners of current pixel + if (blendingNeeded(blend_xy)) //good 20% perf-improvement + { + Kernel_3x3 ker = {}; //perf: initialization is negligable + + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + + ker.d = s_0[x_m1]; + ker.e = s_0[x]; + ker.f = s_0[x_p1]; + + ker.g = s_p1[x_m1]; + ker.h = s_p1[x]; + ker.i = s_p1[x_p1]; + + scalePixel(ker, out, trgWidth, blend_xy, cfg); + scalePixel(ker, out, trgWidth, blend_xy, cfg); + scalePixel(ker, out, trgWidth, blend_xy, cfg); + scalePixel(ker, out, trgWidth, blend_xy, cfg); + } + } + } +} + + +struct Scaler2x +{ + static const int scale = 2; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<1, 0>(), col); + alphaBlend<1, 4>(out.template ref<0, 1>(), col); + alphaBlend<5, 6>(out.template ref<1, 1>(), col); //[!] fixes 7/8 used in xBR + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 2>(out.template ref<1, 1>(), col); + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<21, 100>(out.template ref<1, 1>(), col); //exact: 1 - pi/4 = 0.2146018366 + } +}; + + +struct Scaler3x +{ + static const int scale = 3; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + out.template ref<2, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<2, 0>(), col); + alphaBlend<1, 4>(out.template ref<0, 2>(), col); + alphaBlend<3, 4>(out.template ref<2, 1>(), col); + alphaBlend<3, 4>(out.template ref<1, 2>(), col); + out.template ref<2, 2>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 8>(out.template ref<1, 2>(), col); + alphaBlend<1, 8>(out.template ref<2, 1>(), col); + alphaBlend<7, 8>(out.template ref<2, 2>(), col); + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<45, 100>(out.template ref<2, 2>(), col); //exact: 0.4545939598 + //alphaBlend<14, 1000>(out.template ref<2, 1>(), col); //0.01413008627 -> negligable + //alphaBlend<14, 1000>(out.template ref<1, 2>(), col); //0.01413008627 + } +}; + + +struct Scaler4x +{ + static const int scale = 4; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<3, 4>(out.template ref<3, 1>(), col); + alphaBlend<3, 4>(out.template ref<1, 3>(), col); + alphaBlend<1, 4>(out.template ref<3, 0>(), col); + alphaBlend<1, 4>(out.template ref<0, 3>(), col); + alphaBlend<1, 3>(out.template ref<2, 2>(), col); //[!] fixes 1/4 used in xBR + out.template ref<3, 3>() = out.template ref<3, 2>() = out.template ref<2, 3>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 2>(out.template ref(), col); + alphaBlend<1, 2>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<68, 100>(out.template ref<3, 3>(), col); //exact: 0.6848532563 + alphaBlend< 9, 100>(out.template ref<3, 2>(), col); //0.08677704501 + alphaBlend< 9, 100>(out.template ref<2, 3>(), col); //0.08677704501 + } +}; + + +struct Scaler5x +{ + static const int scale = 5; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<4, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + + out.template ref() = col; + out.template ref() = col; + + out.template ref<4, scale - 1>() = col; + + alphaBlend<2, 3>(out.template ref<3, 3>(), col); + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 8>(out.template ref(), col); + alphaBlend<1, 8>(out.template ref(), col); + alphaBlend<1, 8>(out.template ref(), col); + + alphaBlend<7, 8>(out.template ref<4, 3>(), col); + alphaBlend<7, 8>(out.template ref<3, 4>(), col); + + out.template ref<4, 4>() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<86, 100>(out.template ref<4, 4>(), col); //exact: 0.8631434088 + alphaBlend<23, 100>(out.template ref<4, 3>(), col); //0.2306749731 + alphaBlend<23, 100>(out.template ref<3, 4>(), col); //0.2306749731 + //alphaBlend<8, 1000>(out.template ref<4, 2>(), col); //0.008384061834 -> negligable + //alphaBlend<8, 1000>(out.template ref<2, 4>(), col); //0.008384061834 + } +}; +} + + +struct Scaler6x +{ + static const int scale = 6; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + alphaBlend<3, 4>(out.template ref<5, scale - 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 2>(out.template ref(), col); + alphaBlend<1, 2>(out.template ref(), col); + alphaBlend<1, 2>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<97, 100>(out.template ref<5, 5>(), col); //exact: 0.9711013910 + alphaBlend<42, 100>(out.template ref<4, 5>(), col); //0.4236372243 + alphaBlend<42, 100>(out.template ref<5, 4>(), col); //0.4236372243 + alphaBlend< 6, 100>(out.template ref<5, 3>(), col); //0.05652034508 + alphaBlend< 6, 100>(out.template ref<3, 5>(), col); //0.05652034508 + } +}; + + +void xbrz_old::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz_old::ScalerCfg& cfg, int yFirst, int yLast) +{ + switch (factor) + { + case 2: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + assert(false); +} + + +bool xbrz_old::equalColor(uint32_t col1, uint32_t col2, double luminanceWeight, double equalColorTolerance) +{ + return colorDist(col1, col2, luminanceWeight) < equalColorTolerance; +} + + +void xbrz_old::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, + uint32_t* trg, int trgWidth, int trgHeight, int trgPitch, + SliceType st, int yFirst, int yLast) +{ + if (srcPitch < srcWidth * static_cast(sizeof(uint32_t)) || + trgPitch < trgWidth * static_cast(sizeof(uint32_t))) + { + assert(false); + return; + } + + switch (st) + { + case NN_SCALE_SLICE_SOURCE: + //nearest-neighbor (going over source image - fast for upscaling, since source is read only once + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + //mathematically: ySrc = floor(srcHeight * yTrg / trgHeight) + // => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight + + //keep within for loop to support MT input slices! + const int yTrg_first = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight) + const int yTrg_last = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight) + const int blockHeight = yTrg_last - yTrg_first; + + if (blockHeight > 0) + { + const uint32_t* srcLine = byteAdvance(src, y * srcPitch); + uint32_t* trgLine = byteAdvance(trg, yTrg_first * trgPitch); + int xTrg_first = 0; + + for (int x = 0; x < srcWidth; ++x) + { + int xTrg_last = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth; + const int blockWidth = xTrg_last - xTrg_first; + if (blockWidth > 0) + { + xTrg_first = xTrg_last; + fillBlock(trgLine, trgPitch, srcLine[x], blockWidth, blockHeight); + trgLine += blockWidth; + } + } + } + } + break; + + case NN_SCALE_SLICE_TARGET: + //nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* trgLine = byteAdvance(trg, y * trgPitch); + const int ySrc = srcHeight * y / trgHeight; + const uint32_t* srcLine = byteAdvance(src, ySrc * srcPitch); + for (int x = 0; x < trgWidth; ++x) + { + const int xSrc = srcWidth * x / trgWidth; + trgLine[x] = srcLine[xSrc]; + } + } + break; + } +} diff --git a/src/gl/xbr/xbrz_old.h b/src/gl/xbr/xbrz_old.h new file mode 100644 index 000000000..c93a1480a --- /dev/null +++ b/src/gl/xbr/xbrz_old.h @@ -0,0 +1,92 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// * * +// * An explicit permission was granted to use xBRZ in combination with ZDoom * +// * and derived projects as long as it is used for non-commercial purposes. * +// * * +// * Backported to C++98 by Alexey Lysiuk * +// **************************************************************************** + +#ifndef __XBRZ_OLD_HEADER_INCLUDED__ +#define __XBRZ_OLD_HEADER_INCLUDED__ + +#include //size_t +#include //uint32_t +#include +#include "xbrz_config_old.h" + +namespace xbrz_old +{ +/* +------------------------------------------------------------------------- +| xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | +------------------------------------------------------------------------- +using a modified approach of xBR: +http://board.byuu.org/viewtopic.php?f=10&t=2248 +- new rule set preserving small image features +- support multithreading +- support 64 bit architectures +- support processing image slices +*/ + +/* +-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only +-> color format: ARGB (BGRA byte order), alpha channel unused +-> support for source/target pitch in bytes! +-> if your emulator changes only a few image slices during each cycle (e.g. Dosbox) then there's no need to run xBRZ on the complete image: + Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis) + Caveat: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition + if you are using multiple threads for processing each enlarged slice! + +THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap! + - there is a minor inefficiency for the first row of a slice, so avoid processing single rows only + + +*/ +void scale(size_t factor, //valid range: 2 - 5 + const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, + const ScalerCfg& cfg = ScalerCfg(), + int yFirst = 0, int yLast = std::numeric_limits::max()); //slice of source image + +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + uint32_t* trg, int trgWidth, int trgHeight); + +enum SliceType +{ + NN_SCALE_SLICE_SOURCE, + NN_SCALE_SLICE_TARGET, +}; +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, //pitch in bytes! + uint32_t* trg, int trgWidth, int trgHeight, int trgPitch, + SliceType st, int yFirst, int yLast); + +//parameter tuning +bool equalColor(uint32_t col1, uint32_t col2, double luminanceWeight, double equalColorTolerance); + + + + + +//########################### implementation ########################### +inline +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + uint32_t* trg, int trgWidth, int trgHeight) +{ + nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + NN_SCALE_SLICE_TARGET, 0, trgHeight); +} +} + +#endif From f31346968f447f396f190fd42cf1bfcd6162b09e Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 3 Sep 2016 17:29:28 +0200 Subject: [PATCH 07/44] - added missing #include. --- src/posix/unix/i_specialpaths.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/posix/unix/i_specialpaths.cpp b/src/posix/unix/i_specialpaths.cpp index 6a17e1318..5dedba057 100644 --- a/src/posix/unix/i_specialpaths.cpp +++ b/src/posix/unix/i_specialpaths.cpp @@ -36,6 +36,7 @@ #include #include #include "i_system.h" +#include "cmdlib.h" #include "version.h" // for GAMENAME From 2ed4208a1b4ef65b339e543a4d05acc78988e329 Mon Sep 17 00:00:00 2001 From: Blue-Shadow Date: Sat, 3 Sep 2016 18:55:19 +0300 Subject: [PATCH 08/44] Added IfCVarInt SBARINFO command --- src/g_shared/sbarinfo_commands.cpp | 77 +++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index b4a011510..ba83ce1c6 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -3464,6 +3464,78 @@ class CommandIfWaterLevel : public SBarInfoNegatableFlowControl //////////////////////////////////////////////////////////////////////////////// +class CommandIfCVarInt : public SBarInfoNegatableFlowControl +{ + public: + CommandIfCVarInt(SBarInfo *script) : SBarInfoNegatableFlowControl(script), + equalcomp(false) + { + } + + void ParseNegatable(FScanner &sc, bool fullScreenOffsets) + { + if(!sc.CheckToken(TK_StringConst)) + { + sc.MustGetToken(TK_Identifier); + } + + cvarname = sc.String; + cvar = FindCVar(cvarname, nullptr); + + if (cvar != nullptr) + { + ECVarType cvartype = cvar->GetRealType(); + + if (cvartype == CVAR_Bool || cvartype == CVAR_Int) + { + sc.MustGetToken(','); + sc.MustGetToken(TK_IntConst); + value = sc.Number; + + if (sc.CheckToken(',')) + { + sc.MustGetToken(TK_Identifier); + + if(sc.Compare("equal")) + { + equalcomp = true; + } + } + } + else + { + sc.ScriptError("Type mismatch: console variable '%s' is not of type 'bool' or 'int'.", cvarname); + } + } + else + { + sc.ScriptError("Unknown console variable '%s'.", cvarname); + } + } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged); + + bool result = false; + cvar = GetCVar(statusBar->CPlayer->mo, cvarname); + + if (cvar != nullptr) + { + int cvarvalue = cvar->GetGenericRep(CVAR_Int).Int; + result = equalcomp ? cvarvalue == value : cvarvalue >= value; + } + + SetTruth(result, block, statusBar); + } + protected: + FString cvarname; + FBaseCVar *cvar; + int value; + bool equalcomp; +}; + +//////////////////////////////////////////////////////////////////////////////// + static const char *SBarInfoCommandNames[] = { "drawimage", "drawnumber", "drawswitchableimage", @@ -3474,7 +3546,7 @@ static const char *SBarInfoCommandNames[] = "isselected", "usesammo", "usessecondaryammo", "hasweaponpiece", "inventorybarnotvisible", "weaponammo", "ininventory", "alpha", "ifhealth", - "ifinvulnerable", "ifwaterlevel", + "ifinvulnerable", "ifwaterlevel", "ifcvarint", NULL }; @@ -3488,7 +3560,7 @@ enum SBarInfoCommands SBARINFO_ISSELECTED, SBARINFO_USESAMMO, SBARINFO_USESSECONDARYAMMO, SBARINFO_HASWEAPONPIECE, SBARINFO_INVENTORYBARNOTVISIBLE, SBARINFO_WEAPONAMMO, SBARINFO_ININVENTORY, SBARINFO_ALPHA, SBARINFO_IFHEALTH, - SBARINFO_IFINVULNERABLE, SBARINFO_IFWATERLEVEL, + SBARINFO_IFINVULNERABLE, SBARINFO_IFWATERLEVEL, SBARINFO_IFCVARINT, }; SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc) @@ -3524,6 +3596,7 @@ SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc) case SBARINFO_IFHEALTH: return new CommandIfHealth(script); case SBARINFO_IFINVULNERABLE: return new CommandIfInvulnerable(script); case SBARINFO_IFWATERLEVEL: return new CommandIfWaterLevel(script); + case SBARINFO_IFCVARINT: return new CommandIfCVarInt(script); } sc.ScriptError("Unknown command '%s'.\n", sc.String); From 217601f3385d771ed657bff023b98d3ef8a7724f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 01:46:29 +0200 Subject: [PATCH 09/44] - fixed: FPortal::ClearScreen may not use the 2D drawing code anymore. 2D calls are accumulated and then executed all at once at the end of the frame, but this one needs to be interleaved with the 3D rendering. It now uses the quad drawer to fill the portal with blackness. --- src/gl/scene/gl_portal.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp index cd3efb9e3..b613032ea 100644 --- a/src/gl/scene/gl_portal.cpp +++ b/src/gl/scene/gl_portal.cpp @@ -56,6 +56,7 @@ #include "gl/renderer/gl_lightdata.h" #include "gl/renderer/gl_renderer.h" #include "gl/renderer/gl_renderstate.h" +#include "gl/renderer/gl_quaddrawer.h" #include "gl/dynlights/gl_glow.h" #include "gl/data/gl_data.h" #include "gl/data/gl_vertexbuffer.h" @@ -128,8 +129,21 @@ void GLPortal::ClearScreen() bool multi = !!glIsEnabled(GL_MULTISAMPLE); gl_MatrixStack.Push(gl_RenderState.mViewMatrix); gl_MatrixStack.Push(gl_RenderState.mProjectionMatrix); - screen->Begin2D(false); - screen->Dim(0, 1.f, 0, 0, SCREENWIDTH, SCREENHEIGHT); + + gl_RenderState.mViewMatrix.loadIdentity(); + gl_RenderState.mProjectionMatrix.ortho(0, SCREENWIDTH, SCREENHEIGHT, 0, -1.0f, 1.0f); + gl_RenderState.ApplyMatrices(); + + glDisable(GL_MULTISAMPLE); + glDisable(GL_DEPTH_TEST); + + FQuadDrawer qd; + qd.Set(0, 0, 0, 0, 0, 0); + qd.Set(1, 0, SCREENHEIGHT, 0, 0, 0); + qd.Set(2, SCREENWIDTH, SCREENHEIGHT, 0, 0, 0); + qd.Set(3, SCREENWIDTH, 0, 0, 0, 0); + qd.Render(GL_TRIANGLE_FAN); + glEnable(GL_DEPTH_TEST); gl_MatrixStack.Pop(gl_RenderState.mProjectionMatrix); gl_MatrixStack.Pop(gl_RenderState.mViewMatrix); @@ -137,7 +151,6 @@ void GLPortal::ClearScreen() if (multi) glEnable(GL_MULTISAMPLE); } - //----------------------------------------------------------------------------- // // DrawPortalStencil From dc39a006dc3d58182d2dba83a4ac50258799c9f6 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Sun, 4 Sep 2016 02:37:59 +0200 Subject: [PATCH 10/44] Fix palette tonemap precision and compile error on Intel --- src/gl/renderer/gl_postprocess.cpp | 2 +- wadsrc/static/shaders/glsl/tonemap.fp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index 77ee5cd79..645bc3fe1 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -257,7 +257,7 @@ void FGLRenderer::BindTonemapPalette(int texunit) { for (int b = 0; b < 64; b++) { - PalEntry color = GPalette.BaseColors[ColorMatcher.Pick((r << 2) | (r >> 1), (g << 2) | (g >> 1), (b << 2) | (b >> 1))]; + PalEntry color = GPalette.BaseColors[ColorMatcher.Pick((r << 2) | (r >> 4), (g << 2) | (g >> 4), (b << 2) | (b >> 4))]; int index = ((r * 64 + g) * 64 + b) * 4; lut[index] = color.r; lut[index + 1] = color.g; diff --git a/wadsrc/static/shaders/glsl/tonemap.fp b/wadsrc/static/shaders/glsl/tonemap.fp index c33349a38..d6574b295 100644 --- a/wadsrc/static/shaders/glsl/tonemap.fp +++ b/wadsrc/static/shaders/glsl/tonemap.fp @@ -69,15 +69,15 @@ uniform sampler2D PaletteLUT; vec3 Tonemap(vec3 color) { - ivec3 c = ivec3(clamp(color.rgb, vec3(0.0), vec3(1.0)) * 255.0 + 0.5); - int index = ((c.r >> 2) * 64 + (c.g >> 2)) * 64 + (c.b >> 2); + ivec3 c = ivec3(clamp(color.rgb, vec3(0.0), vec3(1.0)) * 63.0 + 0.5); + int index = (c.r * 64 + c.g) * 64 + c.b; int tx = index % 512; int ty = index / 512; return texelFetch(PaletteLUT, ivec2(tx, ty), 0).rgb; } #else -#error "Tonemap mode define is missing" +#error Tonemap mode define is missing #endif void main() From 5f02e08c8e133f93d9e9e72e004e662d15ef8b7d Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Sun, 4 Sep 2016 03:15:50 +0200 Subject: [PATCH 11/44] Fix minimize crash --- src/gl/renderer/gl_postprocess.cpp | 2 ++ src/gl/renderer/gl_renderer.cpp | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index 645bc3fe1..e6ca7faad 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -444,6 +444,8 @@ void FGLRenderer::ClearBorders() int clientWidth = framebuffer->GetClientWidth(); int clientHeight = framebuffer->GetClientHeight(); + if (clientWidth == 0 || clientHeight == 0) + return; glViewport(0, 0, clientWidth, clientHeight); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); diff --git a/src/gl/renderer/gl_renderer.cpp b/src/gl/renderer/gl_renderer.cpp index 6cd38390f..f9d627d93 100644 --- a/src/gl/renderer/gl_renderer.cpp +++ b/src/gl/renderer/gl_renderer.cpp @@ -231,6 +231,13 @@ void FGLRenderer::SetOutputViewport(GL_IRECT *bounds) // Back buffer letterbox for the final output int clientWidth = framebuffer->GetClientWidth(); int clientHeight = framebuffer->GetClientHeight(); + if (clientWidth == 0 || clientHeight == 0) + { + // When window is minimized there may not be any client area. + // Pretend to the rest of the render code that we just have a very small window. + clientWidth = 160; + clientHeight = 120; + } int screenWidth = framebuffer->GetWidth(); int screenHeight = framebuffer->GetHeight(); float scale = MIN(clientWidth / (float)screenWidth, clientHeight / (float)screenHeight); From 527703ae8c2612a63925e4e7296df49f9ea3af22 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Sun, 4 Sep 2016 03:21:47 +0200 Subject: [PATCH 12/44] Fix missing flash if multisampling was on and no post processing effects active --- src/gl/scene/gl_scene.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index b4dd95d50..23d7c7c80 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -856,7 +856,11 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo TonemapScene(); ColormapScene(); LensDistortScene(); - DrawBlend(viewsector); // This should be done after postprocessing, not before. + + // This should be done after postprocessing, not before. + mBuffers->BindCurrentFB(); + glViewport(mScreenViewport.left, mScreenViewport.top, mScreenViewport.width, mScreenViewport.height); + DrawBlend(viewsector); } mDrawingScene2D = false; eye->TearDown(); From 77ac3bb2655ec922d62159ea93db73aaae8701e3 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 08:33:19 +0200 Subject: [PATCH 13/44] - fixed angle range checks in A_CheckIfTargetInLOS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fixed point version had a mostly useless check that excluded ANGLE_MAX, this got incorrectly converted to floating point. Note that this version will clamp the angle to 360°, not merely overflow like it did with the fixed point code --- src/thingdef/thingdef_codeptr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 1f24a35c7..83db1fdaa 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -4501,7 +4501,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) else { target = viewport; viewport = self; } } - if (fov > 0 && (fov < 360.)) + fov = MIN(fov, 360.); + + if (fov > 0) { DAngle an = absangle(viewport->AngleTo(target), viewport->Angles.Yaw); From 677efb73bce2b178b90781afbba1f9b3912f2206 Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 4 Sep 2016 10:02:09 +0300 Subject: [PATCH 14/44] Fixed compilation with GCC/Clang No longer aborts with error: cannot pass object of non-trivial type 'FString' through variadic method; call will abort at runtime --- src/g_shared/sbarinfo_commands.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index ba83ce1c6..a9fac2ee1 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -3504,12 +3504,12 @@ class CommandIfCVarInt : public SBarInfoNegatableFlowControl } else { - sc.ScriptError("Type mismatch: console variable '%s' is not of type 'bool' or 'int'.", cvarname); + sc.ScriptError("Type mismatch: console variable '%s' is not of type 'bool' or 'int'.", cvarname.GetChars()); } } else { - sc.ScriptError("Unknown console variable '%s'.", cvarname); + sc.ScriptError("Unknown console variable '%s'.", cvarname.GetChars()); } } void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) From eff03d13f0e4b54c7ac1fe0f942f1bc3580c7ddf Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 10:22:59 +0200 Subject: [PATCH 15/44] - fixed last commit. --- src/thingdef/thingdef_codeptr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 83db1fdaa..d520a0b9d 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -76,6 +76,7 @@ #include "d_player.h" #include "p_maputl.h" #include "p_spec.h" +#include "templates.h" #include "math/cmath.h" AActor *SingleActorFromTID(int tid, AActor *defactor); @@ -4501,7 +4502,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) else { target = viewport; viewport = self; } } - fov = MIN(fov, 360.); + fov = MIN(fov, 360.); if (fov > 0) { From 954ac8ce5e607e7811a91717e0e1cfe5126d820a Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 10:27:41 +0200 Subject: [PATCH 16/44] - removed DirectX setup from CMakeLists for Visual Studio For VS 2015 this is no longer needed, the DX headers and libraries are part of the Windows SDK and do not need to be looked for explicitly. --- src/CMakeLists.txt | 84 ++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51548afdd..190a9560a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,49 +114,59 @@ if( WIN32 ) set( FMOD_LIB_PATH_SUFFIXES PATH_SUFFIXES lib ) set( NASM_NAMES nasmw nasm ) - find_path( D3D_INCLUDE_DIR d3d9.h - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Include ) - if( NOT D3D_INCLUDE_DIR ) - message( SEND_ERROR "Could not find DirectX 9 header files" ) - else() - include_directories( ${D3D_INCLUDE_DIR} ) - endif() + if( NOT MSVC ) + find_path( D3D_INCLUDE_DIR d3d9.h + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Include ) + if( NOT D3D_INCLUDE_DIR ) + message( SEND_ERROR "Could not find DirectX 9 header files" ) + else() + include_directories( ${D3D_INCLUDE_DIR} ) + endif() - find_path( XINPUT_INCLUDE_DIR xinput.h - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Include ) - if( NOT XINPUT_INCLUDE_DIR ) - message( WARNING "Could not find xinput.h. XInput will be disabled." ) - add_definitions( -DNO_XINPUT ) + find_path( XINPUT_INCLUDE_DIR xinput.h + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Include ) + if( NOT XINPUT_INCLUDE_DIR ) + message( WARNING "Could not find xinput.h. XInput will be disabled." ) + add_definitions( -DNO_XINPUT ) + else() + include_directories( ${XINPUT_INCLUDE_DIR} ) + endif() + + find_library( DX_dxguid_LIBRARY dxguid + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Lib Lib/${XBITS} ) + find_library( DX_dinput8_LIBRARY dinput8 + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Lib Lib/${XBITS} ) + + set( DX_LIBS_FOUND YES ) + if( NOT DX_dxguid_LIBRARY ) + set( DX_LIBS_FOUND NO ) + endif() + if( NOT DX_dinput8_LIBRARY ) + set( DX_LIBS_FOUND NO ) + endif() + + if( NOT DX_LIBS_FOUND ) + message( FATAL_ERROR "Could not find DirectX 9 libraries" ) + endif() + + set( DX_LIBS + "${DX_dxguid_LIBRARY}" + "${DX_dinput8_LIBRARY}" + ) else() - include_directories( ${XINPUT_INCLUDE_DIR} ) + set( DX_LIBS + dxguid + dinput8 + ) endif() - find_library( DX_dxguid_LIBRARY dxguid - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Lib Lib/${XBITS} ) - find_library( DX_dinput8_LIBRARY dinput8 - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Lib Lib/${XBITS} ) - - set( DX_LIBS_FOUND YES ) - if( NOT DX_dxguid_LIBRARY ) - set( DX_LIBS_FOUND NO ) - endif() - if( NOT DX_dinput8_LIBRARY ) - set( DX_LIBS_FOUND NO ) - endif() - - if( NOT DX_LIBS_FOUND ) - message( FATAL_ERROR "Could not find DirectX 9 libraries" ) - endif() - - set( ZDOOM_LIBS + set( ZDOOM_LIBS ${DX_LIBS} wsock32 winmm - "${DX_dxguid_LIBRARY}" - "${DX_dinput8_LIBRARY}" ole32 user32 gdi32 From e7856ce1e354da13f2b5ffd9795b0bfb1586e37e Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 12:35:26 +0200 Subject: [PATCH 17/44] - removed unused forceadditive parameter from gl_GetLight. - restricted gl_lights_additive to legacy code and removed menu entry for this. For modern hardware this setting is completely pointless, it offers no advantage and degrades visual quality. Its only reason for existence was that drawing additive lights with textures is a lot faster, and that's all it's being used for now. --- src/gl/compatibility/gl_20.cpp | 4 +++- src/gl/dynlights/a_dynlight.cpp | 1 - src/gl/dynlights/gl_dynlight.h | 2 +- src/gl/dynlights/gl_dynlight1.cpp | 9 ++------- src/gl/scene/gl_flats.cpp | 2 +- src/gl/scene/gl_walls_draw.cpp | 2 +- src/gl/system/gl_cvars.h | 1 - wadsrc/static/menudef.z | 1 - 8 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/gl/compatibility/gl_20.cpp b/src/gl/compatibility/gl_20.cpp index fdda99130..60dd9ecdb 100644 --- a/src/gl/compatibility/gl_20.cpp +++ b/src/gl/compatibility/gl_20.cpp @@ -57,6 +57,8 @@ #include "gl/data/gl_vertexbuffer.h" +CVAR(Bool, gl_lights_additive, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + //========================================================================== // // Do some tinkering with the menus so that certain options only appear @@ -478,7 +480,7 @@ bool GLWall::PutWallCompat(int passflag) if (sub->lighthead == nullptr) return false; } - bool foggy = !gl_isBlack(Colormap.FadeColor) || (level.flags&LEVEL_HASFADETABLE) || gl_lights_additive; + bool foggy = gl_CheckFog(&Colormap, lightlevel) || (level.flags&LEVEL_HASFADETABLE) || gl_lights_additive; bool masked = passflag == 2 && gltexture->isMasked(); int list = list_indices[masked][foggy]; diff --git a/src/gl/dynlights/a_dynlight.cpp b/src/gl/dynlights/a_dynlight.cpp index ef96fdd3e..e90bbdc80 100644 --- a/src/gl/dynlights/a_dynlight.cpp +++ b/src/gl/dynlights/a_dynlight.cpp @@ -58,7 +58,6 @@ #include "gl/utility/gl_templates.h" EXTERN_CVAR (Float, gl_lights_size); -EXTERN_CVAR (Bool, gl_lights_additive); EXTERN_CVAR(Int, vid_renderer) diff --git a/src/gl/dynlights/gl_dynlight.h b/src/gl/dynlights/gl_dynlight.h index 513245be5..6b6e40c2d 100644 --- a/src/gl/dynlights/gl_dynlight.h +++ b/src/gl/dynlights/gl_dynlight.h @@ -184,7 +184,7 @@ struct FDynLightData -bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, bool forceadditive, FDynLightData &data); +bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FDynLightData &data); void gl_UploadLights(FDynLightData &data); diff --git a/src/gl/dynlights/gl_dynlight1.cpp b/src/gl/dynlights/gl_dynlight1.cpp index 361b94618..eb3c45f9c 100644 --- a/src/gl/dynlights/gl_dynlight1.cpp +++ b/src/gl/dynlights/gl_dynlight1.cpp @@ -74,18 +74,13 @@ CVAR (Float, gl_lights_intensity, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Float, gl_lights_size, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_sprites, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_particles, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); -CUSTOM_CVAR (Bool, gl_lights_additive, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) -{ - gl_DeleteAllAttachedLights(); - gl_RecreateAllAttachedLights(); -} //========================================================================== // // Sets up the parameters to render one dynamic light onto one plane // //========================================================================== -bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, bool forceadditive, FDynLightData &ldata) +bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FDynLightData &ldata) { int i = 0; @@ -103,7 +98,7 @@ bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, bo float cs; - if (gl_lights_additive || light->flags4&MF4_ADDITIVE || forceadditive) + if (light->IsAdditive()) { cs = 0.2f; i = 2; diff --git a/src/gl/scene/gl_flats.cpp b/src/gl/scene/gl_flats.cpp index 1a3fe994b..9e9a73e5d 100644 --- a/src/gl/scene/gl_flats.cpp +++ b/src/gl/scene/gl_flats.cpp @@ -154,7 +154,7 @@ void GLFlat::SetupSubsectorLights(int pass, subsector_t * sub, int *dli) } p.Set(plane.plane); - gl_GetLight(sub->sector->PortalGroup, p, light, false, false, lightdata); + gl_GetLight(sub->sector->PortalGroup, p, light, false, lightdata); node = node->nextLight; } diff --git a/src/gl/scene/gl_walls_draw.cpp b/src/gl/scene/gl_walls_draw.cpp index 40145abc4..9599b8fa3 100644 --- a/src/gl/scene/gl_walls_draw.cpp +++ b/src/gl/scene/gl_walls_draw.cpp @@ -158,7 +158,7 @@ void GLWall::SetupLights() } if (outcnt[0]!=4 && outcnt[1]!=4 && outcnt[2]!=4 && outcnt[3]!=4) { - gl_GetLight(seg->frontsector->PortalGroup, p, node->lightsource, true, false, lightdata); + gl_GetLight(seg->frontsector->PortalGroup, p, node->lightsource, true, lightdata); } } } diff --git a/src/gl/system/gl_cvars.h b/src/gl/system/gl_cvars.h index 0c31f53a8..da1febe3c 100644 --- a/src/gl/system/gl_cvars.h +++ b/src/gl/system/gl_cvars.h @@ -26,7 +26,6 @@ EXTERN_CVAR (Bool, gl_attachedlights); EXTERN_CVAR (Bool, gl_lights_checkside); EXTERN_CVAR (Float, gl_lights_intensity); EXTERN_CVAR (Float, gl_lights_size); -EXTERN_CVAR (Bool, gl_lights_additive); EXTERN_CVAR (Bool, gl_light_sprites); EXTERN_CVAR (Bool, gl_light_particles); diff --git a/wadsrc/static/menudef.z b/wadsrc/static/menudef.z index 86e2d5dc0..abc7cfacc 100644 --- a/wadsrc/static/menudef.z +++ b/wadsrc/static/menudef.z @@ -198,7 +198,6 @@ OptionMenu "GLLightOptions" Option "$GLLIGHTMNU_CLIPLIGHTS", gl_lights_checkside, "YesNo" Option "$GLLIGHTMNU_LIGHTSPRITES", gl_light_sprites, "YesNo" Option "$GLLIGHTMNU_LIGHTPARTICLES", gl_light_particles, "YesNo" - Option "$GLLIGHTMNU_FORCEADDITIVE", gl_lights_additive, "YesNo" Slider "$GLLIGHTMNU_LIGHTINTENSITY", gl_lights_intensity, 0.0, 1.0, 0.1 Slider "$GLLIGHTMNU_LIGHTSIZE", gl_lights_size, 0.0, 2.0, 0.1 } From 8b01a88b76f815833f77b3b7a7fcc47159d471e0 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 12:45:09 +0200 Subject: [PATCH 18/44] - removed gl_lights_size and gl_lights_intensity. Both of these were inherited from ZDoomGL and in terms of light design in maps it makes absolutely no sense to have them user configurable. They should have been removed 11 years ago. --- src/gl/compatibility/gl_20.cpp | 8 ++++---- src/gl/dynlights/a_dynlight.cpp | 3 +-- src/gl/dynlights/gl_dynlight.cpp | 4 +--- src/gl/dynlights/gl_dynlight1.cpp | 10 ++++------ src/gl/scene/gl_spritelight.cpp | 8 ++++---- src/gl/scene/gl_walls_draw.cpp | 2 +- src/gl/system/gl_cvars.h | 2 -- wadsrc/static/menudef.z | 2 -- 8 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/gl/compatibility/gl_20.cpp b/src/gl/compatibility/gl_20.cpp index 60dd9ecdb..e192b213e 100644 --- a/src/gl/compatibility/gl_20.cpp +++ b/src/gl/compatibility/gl_20.cpp @@ -386,7 +386,7 @@ bool gl_SetupLight(int group, Plane & p, ADynamicLight * light, Vector & nearPt, DVector3 lpos = light->PosRelative(group); float dist = fabsf(p.DistToPoint(lpos.X, lpos.Z, lpos.Y)); - float radius = (light->GetRadius() * gl_lights_size); + float radius = light->GetRadius(); if (radius <= 0.f) return false; if (dist > radius) return false; @@ -417,9 +417,9 @@ bool gl_SetupLight(int group, Plane & p, ADynamicLight * light, Vector & nearPt, float cs = 1.0f - (dist / radius); if (additive) cs *= 0.2f; // otherwise the light gets too strong. - float r = light->GetRed() / 255.0f * cs * gl_lights_intensity; - float g = light->GetGreen() / 255.0f * cs * gl_lights_intensity; - float b = light->GetBlue() / 255.0f * cs * gl_lights_intensity; + float r = light->GetRed() / 255.0f * cs; + float g = light->GetGreen() / 255.0f * cs; + float b = light->GetBlue() / 255.0f * cs; if (light->IsSubtractive()) { diff --git a/src/gl/dynlights/a_dynlight.cpp b/src/gl/dynlights/a_dynlight.cpp index e90bbdc80..ac929e7c0 100644 --- a/src/gl/dynlights/a_dynlight.cpp +++ b/src/gl/dynlights/a_dynlight.cpp @@ -57,7 +57,6 @@ #include "gl/utility/gl_convert.h" #include "gl/utility/gl_templates.h" -EXTERN_CVAR (Float, gl_lights_size); EXTERN_CVAR(Int, vid_renderer) @@ -378,7 +377,7 @@ void ADynamicLight::UpdateLocation() { intensity = m_currentRadius; } - radius = intensity * 2.0f * gl_lights_size; + radius = intensity * 2.0f; if (X() != oldx || Y() != oldy || radius != oldradius) { diff --git a/src/gl/dynlights/gl_dynlight.cpp b/src/gl/dynlights/gl_dynlight.cpp index e7d876b2a..b12290ab6 100644 --- a/src/gl/dynlights/gl_dynlight.cpp +++ b/src/gl/dynlights/gl_dynlight.cpp @@ -61,8 +61,6 @@ #include "gl/utility/gl_clock.h" #include "gl/utility/gl_convert.h" -EXTERN_CVAR (Float, gl_lights_intensity); -EXTERN_CVAR (Float, gl_lights_size); int ScriptDepth; void gl_InitGlow(FScanner &sc); void gl_ParseBrightmap(FScanner &sc, int); @@ -175,7 +173,7 @@ void FLightDefaults::ApplyProperties(ADynamicLight * light) const light->Angles.Yaw.Degrees = m_Param; light->SetOffset(m_Pos); light->halo = m_halo; - for (int a = 0; a < 3; a++) light->args[a] = clamp((int)(m_Args[a] * gl_lights_intensity), 0, 255); + for (int a = 0; a < 3; a++) light->args[a] = clamp((int)(m_Args[a]), 0, 255); light->m_Radius[0] = int(m_Args[LIGHT_INTENSITY]); light->m_Radius[1] = int(m_Args[LIGHT_SECONDARY_INTENSITY]); light->flags4 &= ~(MF4_ADDITIVE | MF4_SUBTRACTIVE | MF4_DONTLIGHTSELF); diff --git a/src/gl/dynlights/gl_dynlight1.cpp b/src/gl/dynlights/gl_dynlight1.cpp index eb3c45f9c..89cdd1ecb 100644 --- a/src/gl/dynlights/gl_dynlight1.cpp +++ b/src/gl/dynlights/gl_dynlight1.cpp @@ -70,8 +70,6 @@ CUSTOM_CVAR (Bool, gl_lights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOIN CVAR (Bool, gl_attachedlights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_lights_checkside, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); -CVAR (Float, gl_lights_intensity, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); -CVAR (Float, gl_lights_size, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_sprites, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_light_particles, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); @@ -87,7 +85,7 @@ bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FD DVector3 pos = light->PosRelative(group); float dist = fabsf(p.DistToPoint(pos.X, pos.Z, pos.Y)); - float radius = (light->GetRadius() * gl_lights_size); + float radius = (light->GetRadius()); if (radius <= 0.f) return false; if (dist > radius) return false; @@ -108,9 +106,9 @@ bool gl_GetLight(int group, Plane & p, ADynamicLight * light, bool checkside, FD cs = 1.0f; } - float r = light->GetRed() / 255.0f * cs * gl_lights_intensity; - float g = light->GetGreen() / 255.0f * cs * gl_lights_intensity; - float b = light->GetBlue() / 255.0f * cs * gl_lights_intensity; + float r = light->GetRed() / 255.0f * cs; + float g = light->GetGreen() / 255.0f * cs; + float b = light->GetBlue() / 255.0f * cs; if (light->IsSubtractive()) { diff --git a/src/gl/scene/gl_spritelight.cpp b/src/gl/scene/gl_spritelight.cpp index b25fc30f0..329459bd1 100644 --- a/src/gl/scene/gl_spritelight.cpp +++ b/src/gl/scene/gl_spritelight.cpp @@ -96,7 +96,7 @@ void gl_SetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t * dist = FVector3(x - light->X(), y - light->Y(), z - light->Z()).LengthSquared(); } - radius = light->GetRadius() * gl_lights_size; + radius = light->GetRadius(); if (dist < radius * radius) { @@ -106,9 +106,9 @@ void gl_SetDynSpriteLight(AActor *self, float x, float y, float z, subsector_t * if (frac > 0) { - lr = light->GetRed() / 255.0f * gl_lights_intensity; - lg = light->GetGreen() / 255.0f * gl_lights_intensity; - lb = light->GetBlue() / 255.0f * gl_lights_intensity; + lr = light->GetRed() / 255.0f; + lg = light->GetGreen() / 255.0f; + lb = light->GetBlue() / 255.0f; if (light->IsSubtractive()) { float bright = FVector3(lr, lg, lb).Length(); diff --git a/src/gl/scene/gl_walls_draw.cpp b/src/gl/scene/gl_walls_draw.cpp index 9599b8fa3..9f521ad11 100644 --- a/src/gl/scene/gl_walls_draw.cpp +++ b/src/gl/scene/gl_walls_draw.cpp @@ -124,7 +124,7 @@ void GLWall::SetupLights() float y = node->lightsource->Y(); float z = node->lightsource->Z(); float dist = fabsf(p.DistToPoint(x, z, y)); - float radius = (node->lightsource->GetRadius() * gl_lights_size); + float radius = node->lightsource->GetRadius(); float scale = 1.0f / ((2.f * radius) - dist); if (radius > 0.f && dist < radius) diff --git a/src/gl/system/gl_cvars.h b/src/gl/system/gl_cvars.h index da1febe3c..4cda3657d 100644 --- a/src/gl/system/gl_cvars.h +++ b/src/gl/system/gl_cvars.h @@ -24,8 +24,6 @@ EXTERN_CVAR(Int, gl_weaponlight) EXTERN_CVAR (Bool, gl_lights); EXTERN_CVAR (Bool, gl_attachedlights); EXTERN_CVAR (Bool, gl_lights_checkside); -EXTERN_CVAR (Float, gl_lights_intensity); -EXTERN_CVAR (Float, gl_lights_size); EXTERN_CVAR (Bool, gl_light_sprites); EXTERN_CVAR (Bool, gl_light_particles); diff --git a/wadsrc/static/menudef.z b/wadsrc/static/menudef.z index abc7cfacc..8879d6170 100644 --- a/wadsrc/static/menudef.z +++ b/wadsrc/static/menudef.z @@ -198,8 +198,6 @@ OptionMenu "GLLightOptions" Option "$GLLIGHTMNU_CLIPLIGHTS", gl_lights_checkside, "YesNo" Option "$GLLIGHTMNU_LIGHTSPRITES", gl_light_sprites, "YesNo" Option "$GLLIGHTMNU_LIGHTPARTICLES", gl_light_particles, "YesNo" - Slider "$GLLIGHTMNU_LIGHTINTENSITY", gl_lights_intensity, 0.0, 1.0, 0.1 - Slider "$GLLIGHTMNU_LIGHTSIZE", gl_lights_size, 0.0, 2.0, 0.1 } OptionMenu "GLPrefOptions" From 95bedac6ca8240e010bb9ff49168fdd4abde06e7 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 13:14:14 +0200 Subject: [PATCH 19/44] - inlined FHardwareTexture::GetTexDimension. --- src/gl/textures/gl_hwtexture.cpp | 12 ------------ src/gl/textures/gl_hwtexture.h | 7 ++++++- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/gl/textures/gl_hwtexture.cpp b/src/gl/textures/gl_hwtexture.cpp index 73c4e7b36..25dc989e7 100644 --- a/src/gl/textures/gl_hwtexture.cpp +++ b/src/gl/textures/gl_hwtexture.cpp @@ -64,18 +64,6 @@ extern int TexFormat[]; //=========================================================================== unsigned int FHardwareTexture::lastbound[FHardwareTexture::MAX_TEXTURES]; -//=========================================================================== -// -// STATIC - Gets the maximum size of hardware textures -// -//=========================================================================== -int FHardwareTexture::GetTexDimension(int value) -{ - if (value > gl.max_texturesize) return gl.max_texturesize; - return value; -} - - //=========================================================================== // // Quick'n dirty image rescaling. diff --git a/src/gl/textures/gl_hwtexture.h b/src/gl/textures/gl_hwtexture.h index 4c00af272..96eff0264 100644 --- a/src/gl/textures/gl_hwtexture.h +++ b/src/gl/textures/gl_hwtexture.h @@ -10,6 +10,7 @@ #define DIRECT_PALETTE -2 #include "tarray.h" +#include "gl/system/gl_interface.h" class FCanvasTexture; class AActor; @@ -49,7 +50,11 @@ public: static unsigned int lastbound[MAX_TEXTURES]; - static int GetTexDimension(int value); + static int GetTexDimension(int value) + { + if (value > gl.max_texturesize) return gl.max_texturesize; + return value; + } static void InitGlobalState() { for (int i = 0; i < MAX_TEXTURES; i++) lastbound[i] = 0; } From d2ead39bccb393f8496e4ba9176f464ec5b4bf3f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 4 Sep 2016 14:16:05 +0200 Subject: [PATCH 20/44] - force framebuffers for camera textures on GL 3+ hardware. With all the postprocessing stuff added I don't think it's ok to use the screenbuffer for this anymore. - disable framebuffers for camera textures in legacy mode entirely. This depends on a GL_DEPTH24_STENCIL8 surface which most of these old chipsets do not support, and I really see no point to invest any work here. The worst that can happen is that oversized camera textures won't be processed, which, due to general performance issues, might even be a good thing. --- src/gl/scene/gl_scene.cpp | 25 ++++++------------------- wadsrc/static/language.enu | 4 ---- wadsrc/static/menudef.z | 1 - 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 23d7c7c80..8a2764829 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -1317,29 +1317,16 @@ void FGLInterface::RenderTextureView (FCanvasTexture *tex, AActor *Viewpoint, in gl_fixedcolormap=CM_DEFAULT; gl_RenderState.SetFixedColormap(CM_DEFAULT); - bool usefb = !gl.legacyMode || gl_usefb || gltex->GetWidth() > screen->GetWidth() || gltex->GetHeight() > screen->GetHeight(); - if (!usefb) + if (gl.legacyMode) { + // In legacy mode, fail if the requested texture is too large. + if (gltex->GetWidth() > screen->GetWidth() || gltex->GetHeight() > screen->GetHeight()) return; glFlush(); } else { -#if defined(_WIN32) && (defined(_MSC_VER) || defined(__INTEL_COMPILER)) - __try -#endif - { - GLRenderer->StartOffscreen(); - gltex->BindToFrameBuffer(); - } -#if defined(_WIN32) && (defined(_MSC_VER) || defined(__INTEL_COMPILER)) - __except(1) - { - usefb = false; - gl_usefb = false; - GLRenderer->EndOffscreen(); - glFlush(); - } -#endif + GLRenderer->StartOffscreen(); + gltex->BindToFrameBuffer(); } GL_IRECT bounds; @@ -1349,7 +1336,7 @@ void FGLInterface::RenderTextureView (FCanvasTexture *tex, AActor *Viewpoint, in GLRenderer->RenderViewpoint(Viewpoint, &bounds, FOV, (float)width/height, (float)width/height, false, false); - if (!usefb) + if (gl.legacyMode) { glFlush(); gl_RenderState.SetMaterial(gltex, 0, 0, -1, false); diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index d2da033d7..c7d19fd92 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2594,7 +2594,6 @@ GLTEXMNU_RESIZETEX = "Resize textures"; GLTEXMNU_RESIZESPR = "Resize sprites"; GLTEXMNU_RESIZEFNT = "Resize fonts"; GLTEXMNU_PRECACHETEX = "Precache GL textures"; -GLTEXMNU_CAMTEXOFFSCR = "Camera textures offscreen"; GLTEXMNU_TRIMSPREDGE = "Trim sprite edges"; GLTEXMNU_SORTDRAWLIST = "Sort draw lists by texture"; @@ -2605,9 +2604,6 @@ GLLIGHTMNU_LIGHTDEFS = "Enable light definitions"; GLLIGHTMNU_CLIPLIGHTS = "Clip lights"; GLLIGHTMNU_LIGHTSPRITES = "Lights affect sprites"; GLLIGHTMNU_LIGHTPARTICLES = "Lights affect particles"; -GLLIGHTMNU_FORCEADDITIVE = "Force additive lighting"; -GLLIGHTMNU_LIGHTINTENSITY = "Light intensity"; -GLLIGHTMNU_LIGHTSIZE = "Light size"; // OpenGL Preferences GLPREFMNU_TITLE = "OPENGL PREFERENCES"; diff --git a/wadsrc/static/menudef.z b/wadsrc/static/menudef.z index 8879d6170..980cdacc3 100644 --- a/wadsrc/static/menudef.z +++ b/wadsrc/static/menudef.z @@ -185,7 +185,6 @@ OptionMenu "GLTextureGLOptions" Option "$GLTEXMNU_RESIZESPR", gl_texture_hqresize_sprites, "OnOff" Option "$GLTEXMNU_RESIZEFNT", gl_texture_hqresize_fonts, "OnOff" Option "$GLTEXMNU_PRECACHETEX", gl_precache, "YesNo" - Option "$GLTEXMNU_CAMTEXOFFSCR", gl_usefb, "OnOff" Option "$GLTEXMNU_TRIMSPREDGE", gl_trimsprites, "OnOff" Option "$GLTEXMNU_SORTDRAWLIST", gl_sort_textures, "YesNo" } From 108dcf122ae02d0354742081e9f67aa791831f7d Mon Sep 17 00:00:00 2001 From: "alexey.lysiuk" Date: Sun, 4 Sep 2016 15:23:29 +0300 Subject: [PATCH 21/44] Updated support for legacy renderer in Cocoa backend Added fallback to legacy profile when creation of pixel format for core context failed Added handling of -glversion command line switch --- src/posix/cocoa/i_video.mm | 71 ++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/src/posix/cocoa/i_video.mm b/src/posix/cocoa/i_video.mm index f455a5f9f..624e8ad07 100644 --- a/src/posix/cocoa/i_video.mm +++ b/src/posix/cocoa/i_video.mm @@ -459,23 +459,14 @@ CocoaWindow* CreateCocoaWindow(const NSUInteger styleMask) return window; } -} // unnamed namespace - - -// --------------------------------------------------------------------------- - - -CocoaVideo::CocoaVideo() -: m_window(CreateCocoaWindow(STYLE_MASK_WINDOWED)) -, m_width(-1) -, m_height(-1) -, m_fullscreen(false) -, m_hiDPI(false) +enum OpenGLProfile { - memset(&m_modeIterator, 0, sizeof m_modeIterator); - - // Set attributes for OpenGL context + Core, + Legacy +}; +NSOpenGLPixelFormat* CreatePixelFormat(const OpenGLProfile profile) +{ NSOpenGLPixelFormatAttribute attributes[16]; size_t i = 0; @@ -492,17 +483,59 @@ CocoaVideo::CocoaVideo() attributes[i++] = NSOpenGLPFAAllowOfflineRenderers; } - if (NSAppKitVersionNumber >= AppKit10_7) + if (NSAppKitVersionNumber >= AppKit10_7 && OpenGLProfile::Core == profile) { + NSOpenGLPixelFormatAttribute profile = NSOpenGLProfileVersion3_2Core; + const char* const glversion = Args->CheckValue("-glversion"); + + if (nullptr != glversion) + { + const double version = strtod(glversion, nullptr) + 0.01; + if (version < 3.2) + { + profile = NSOpenGLProfileVersionLegacy; + } + } + attributes[i++] = NSOpenGLPFAOpenGLProfile; - attributes[i++] = NSOpenGLProfileVersion3_2Core; + attributes[i++] = profile; } attributes[i] = NSOpenGLPixelFormatAttribute(0); - // Create OpenGL context and view + return [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; +} - NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +CocoaVideo::CocoaVideo() +: m_window(CreateCocoaWindow(STYLE_MASK_WINDOWED)) +, m_width(-1) +, m_height(-1) +, m_fullscreen(false) +, m_hiDPI(false) +{ + memset(&m_modeIterator, 0, sizeof m_modeIterator); + + // Create OpenGL pixel format + + NSOpenGLPixelFormat* pixelFormat = CreatePixelFormat(OpenGLProfile::Core); + + if (nil == pixelFormat) + { + pixelFormat = CreatePixelFormat(OpenGLProfile::Legacy); + + if (nil == pixelFormat) + { + I_FatalError("Cannot OpenGL create pixel format, graphics hardware is not supported"); + } + } + + // Create OpenGL context and view const NSRect contentRect = [m_window contentRectForFrameRect:[m_window frame]]; NSOpenGLView* glView = [[CocoaView alloc] initWithFrame:contentRect From 74ede7bb4ee92aa5ad14494f689fc81e75f60d05 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 5 Sep 2016 09:07:34 +0200 Subject: [PATCH 22/44] - fixed: The DrawBlend call in the postprocessing case was using the global 'viewsector' variable directly. As with the Stereo3D stuff, this cannot be done because recursive processing of portals will change it. Instead the local copy has to be used here, just like the EndDrawScene call already did. --- src/gl/scene/gl_scene.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 8a2764829..48d4fb7ff 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -771,7 +771,7 @@ void FGLRenderer::SetFixedColormap (player_t *player) sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, float fov, float ratio, float fovratio, bool mainview, bool toscreen) { - sector_t * retval; + sector_t * lviewsector; mSceneClearColor[0] = 0.0f; mSceneClearColor[1] = 0.0f; mSceneClearColor[2] = 0.0f; @@ -818,7 +818,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo } // 'viewsector' will not survive the rendering so it cannot be used anymore below. - retval = viewsector; + lviewsector = viewsector; // Render (potentially) multiple views for stereo 3d float viewShift[3]; @@ -848,7 +848,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo clipper.SafeAddClipRangeRealAngles(ViewAngle.BAMs() + a1, ViewAngle.BAMs() - a1); ProcessScene(toscreen); - if (mainview && toscreen) EndDrawScene(retval); // do not call this for camera textures. + if (mainview && toscreen) EndDrawScene(lviewsector); // do not call this for camera textures. if (mainview && FGLRenderBuffers::IsEnabled()) { mBuffers->BlitSceneToTexture(); @@ -860,7 +860,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo // This should be done after postprocessing, not before. mBuffers->BindCurrentFB(); glViewport(mScreenViewport.left, mScreenViewport.top, mScreenViewport.width, mScreenViewport.height); - DrawBlend(viewsector); + DrawBlend(lviewsector); } mDrawingScene2D = false; eye->TearDown(); @@ -869,7 +869,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo gl_frameCount++; // This counter must be increased right before the interpolations are restored. interpolator.RestoreInterpolations (); - return retval; + return lviewsector; } //----------------------------------------------------------------------------- From 3ce25bc348a84c43ae4b15f8c7d8cff908da668b Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 6 Sep 2016 08:41:13 +0200 Subject: [PATCH 23/44] - fixed: FxPreIncrDecr depended on undefined compiler behavior. It could only work with right to left function argument processing, but with left to right it failed because the ParseExpressionA call altered sc.TokenType. Note that with register-based arguments on 64 bit platforms this is a very critical issue! --- src/thingdef/thingdef_exp.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/thingdef/thingdef_exp.cpp b/src/thingdef/thingdef_exp.cpp index 991e9b947..cad6198f0 100644 --- a/src/thingdef/thingdef_exp.cpp +++ b/src/thingdef/thingdef_exp.cpp @@ -309,7 +309,8 @@ static FxExpression *ParseExpressionC (FScanner &sc, PClassActor *cls) static FxExpression *ParseExpressionB (FScanner &sc, PClassActor *cls) { sc.GetToken(); - switch(sc.TokenType) + int token = sc.TokenType; + switch(token) { case '~': return new FxUnaryNotBitwise(ParseExpressionA (sc, cls)); @@ -325,7 +326,7 @@ static FxExpression *ParseExpressionB (FScanner &sc, PClassActor *cls) case TK_Incr: case TK_Decr: - return new FxPreIncrDecr(ParseExpressionA(sc, cls), sc.TokenType); + return new FxPreIncrDecr(ParseExpressionA(sc, cls), token); default: sc.UnGet(); From a5c0f9a5487fa51c36c4f9fd90788fbbb0be6482 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 6 Sep 2016 11:58:22 +0200 Subject: [PATCH 24/44] - use the predefined fullscreen vertices for clearing a portal instead of using the quad drawer. --- src/gl/scene/gl_portal.cpp | 7 +------ src/gl/textures/gl_material.h | 2 ++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp index b613032ea..bd60713b6 100644 --- a/src/gl/scene/gl_portal.cpp +++ b/src/gl/scene/gl_portal.cpp @@ -137,12 +137,7 @@ void GLPortal::ClearScreen() glDisable(GL_MULTISAMPLE); glDisable(GL_DEPTH_TEST); - FQuadDrawer qd; - qd.Set(0, 0, 0, 0, 0, 0); - qd.Set(1, 0, SCREENHEIGHT, 0, 0, 0); - qd.Set(2, SCREENWIDTH, SCREENHEIGHT, 0, 0, 0); - qd.Set(3, SCREENWIDTH, 0, 0, 0, 0); - qd.Render(GL_TRIANGLE_FAN); + glDrawArrays(GL_TRIANGLE_STRIP, FFlatVertexBuffer::FULLSCREEN_INDEX, 4); glEnable(GL_DEPTH_TEST); gl_MatrixStack.Pop(gl_RenderState.mProjectionMatrix); diff --git a/src/gl/textures/gl_material.h b/src/gl/textures/gl_material.h index d44a845f6..ee42a66d0 100644 --- a/src/gl/textures/gl_material.h +++ b/src/gl/textures/gl_material.h @@ -267,3 +267,5 @@ public: }; #endif + + From a36d89405afe1d50946d8a8a1c8b5c69994eac6d Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 6 Sep 2016 12:22:00 +0200 Subject: [PATCH 25/44] - separated the coordinate calculation from GLSprite::Draw. --- src/gl/scene/gl_sprite.cpp | 285 +++++++++++++++++++------------------ src/gl/scene/gl_wall.h | 1 + 2 files changed, 149 insertions(+), 137 deletions(-) diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp index 8a8c89c80..2def85ef5 100644 --- a/src/gl/scene/gl_sprite.cpp +++ b/src/gl/scene/gl_sprite.cpp @@ -110,17 +110,157 @@ CVAR(Bool, gl_nolayer, false, 0) static const float LARGE_VALUE = 1e19f; + //========================================================================== // // // //========================================================================== + +void GLSprite::CalculateVertices(FVector3 *v) +{ + // [BB] Billboard stuff + const bool drawWithXYBillboard = ((particle && gl_billboard_particles) || (!(actor && actor->renderflags & RF_FORCEYBILLBOARD) + //&& GLRenderer->mViewActor != NULL + && (gl_billboard_mode == 1 || (actor && actor->renderflags & RF_FORCEXYBILLBOARD)))); + + const bool drawBillboardFacingCamera = gl_billboard_faces_camera; + // [Nash] has +ROLLSPRITE + const bool drawRollSpriteActor = (actor != nullptr && actor->renderflags & RF_ROLLSPRITE); + + + // [fgsfds] check sprite type mask + DWORD spritetype = (DWORD)-1; + if (actor != nullptr) spritetype = actor->renderflags & RF_SPRITETYPEMASK; + + // [Nash] is a flat sprite + const bool isFlatSprite = (actor != nullptr) && (spritetype == RF_WALLSPRITE || spritetype == RF_FLATSPRITE); + const bool dontFlip = (actor != nullptr) && (actor->renderflags & RF_DONTFLIP); + const bool useOffsets = (actor != nullptr) && !(actor->renderflags & RF_ROLLCENTER); + + // [Nash] check for special sprite drawing modes + if (drawWithXYBillboard || drawBillboardFacingCamera || drawRollSpriteActor || isFlatSprite) + { + // Compute center of sprite + float xcenter = (x1 + x2)*0.5; + float ycenter = (y1 + y2)*0.5; + float zcenter = (z1 + z2)*0.5; + float xx = -xcenter + x; + float zz = -zcenter + z; + float yy = -ycenter + y; + Matrix3x4 mat; + mat.MakeIdentity(); + mat.Translate(xcenter, zcenter, ycenter); // move to sprite center + + // Order of rotations matters. Perform yaw rotation (Y, face camera) before pitch (X, tilt up/down). + if (drawBillboardFacingCamera && !isFlatSprite) + { + // [CMB] Rotate relative to camera XY position, not just camera direction, + // which is nicer in VR + float xrel = xcenter - ViewPos.X; + float yrel = ycenter - ViewPos.Y; + float absAngleDeg = RAD2DEG(atan2(-yrel, xrel)); + float counterRotationDeg = 270. - GLRenderer->mAngles.Yaw.Degrees; // counteracts existing sprite rotation + float relAngleDeg = counterRotationDeg + absAngleDeg; + + mat.Rotate(0, 1, 0, relAngleDeg); + } + + // [fgsfds] calculate yaw vectors + float yawvecX = 0, yawvecY = 0, rollDegrees = 0; + float angleRad = (270. - GLRenderer->mAngles.Yaw).Radians(); + if (actor) rollDegrees = actor->Angles.Roll.Degrees; + if (isFlatSprite) + { + yawvecX = actor->Angles.Yaw.Cos(); + yawvecY = actor->Angles.Yaw.Sin(); + } + + // [MC] This is the only thing that I changed in Nash's submission which + // was constantly applying roll to everything. That was wrong. Flat sprites + // with roll literally look like paper thing space ships trying to swerve. + // However, it does well with wall sprites. + // Also, renamed FLOORSPRITE to FLATSPRITE because that's technically incorrect. + // I plan on adding proper FLOORSPRITEs which can actually curve along sloped + // 3D floors later... if possible. + + // Here we need some form of priority in order to work. + if (spritetype == RF_FLATSPRITE) + { + float pitchDegrees = -actor->Angles.Pitch.Degrees; + DVector3 apos = { x, y, z }; + DVector3 diff = ViewPos - apos; + DAngle angto = diff.Angle(); + + angto = deltaangle(actor->Angles.Yaw, angto); + + bool noFlipSprite = (!dontFlip || (fabs(angto) < 90.)); + mat.Rotate(0, 1, 0, (noFlipSprite) ? 0 : 180); + + mat.Rotate(-yawvecY, 0, yawvecX, (noFlipSprite) ? -pitchDegrees : pitchDegrees); + if (drawRollSpriteActor) + { + if (useOffsets) mat.Translate(xx, zz, yy); + mat.Rotate(yawvecX, 0, yawvecY, (noFlipSprite) ? -rollDegrees : rollDegrees); + if (useOffsets) mat.Translate(-xx, -zz, -yy); + } + } + // [fgsfds] Rotate the sprite about the sight vector (roll) + else if (spritetype == RF_WALLSPRITE) + { + mat.Rotate(0, 1, 0, 0); + if (drawRollSpriteActor) + { + if (useOffsets) mat.Translate(xx, zz, yy); + mat.Rotate(yawvecX, 0, yawvecY, rollDegrees); + if (useOffsets) mat.Translate(-xx, -zz, -yy); + } + } + else if (drawRollSpriteActor) + { + if (useOffsets) mat.Translate(xx, zz, yy); + if (drawWithXYBillboard) + { + mat.Rotate(-sin(angleRad), 0, cos(angleRad), -GLRenderer->mAngles.Pitch.Degrees); + } + mat.Rotate(cos(angleRad), 0, sin(angleRad), rollDegrees); + if (useOffsets) mat.Translate(-xx, -zz, -yy); + } + + // apply the transform + else if (drawWithXYBillboard) + { + // Rotate the sprite about the vector starting at the center of the sprite + // triangle strip and with direction orthogonal to where the player is looking + // in the x/y plane. + mat.Rotate(-sin(angleRad), 0, cos(angleRad), -GLRenderer->mAngles.Pitch.Degrees); + } + + mat.Translate(-xcenter, -zcenter, -ycenter); // retreat from sprite center + v[0] = mat * FVector3(x1, z1, y1); + v[1] = mat * FVector3(x2, z1, y2); + v[2] = mat * FVector3(x1, z2, y1); + v[3] = mat * FVector3(x2, z2, y2); + } + else // traditional "Y" billboard mode + { + v[0] = FVector3(x1, z1, y1); + v[1] = FVector3(x2, z1, y2); + v[2] = FVector3(x1, z2, y1); + v[3] = FVector3(x2, z2, y2); + } +} + +//========================================================================== +// +// +// +//========================================================================== + void GLSprite::Draw(int pass) { if (pass == GLPASS_DECALS || pass == GLPASS_LIGHTSONLY) return; - - bool additivefog = false; bool foglayer = false; int rel = fullbright? 0 : getExtraLight(); @@ -264,147 +404,18 @@ void GLSprite::Draw(int pass) if (!modelframe) { - // [BB] Billboard stuff - const bool drawWithXYBillboard = ((particle && gl_billboard_particles) || (!(actor && actor->renderflags & RF_FORCEYBILLBOARD) - //&& GLRenderer->mViewActor != NULL - && (gl_billboard_mode == 1 || (actor && actor->renderflags & RF_FORCEXYBILLBOARD)))); - - const bool drawBillboardFacingCamera = gl_billboard_faces_camera; - // [Nash] has +ROLLSPRITE - const bool drawRollSpriteActor = (actor != nullptr && actor->renderflags & RF_ROLLSPRITE); gl_RenderState.Apply(); - FVector3 v1; - FVector3 v2; - FVector3 v3; - FVector3 v4; + FVector3 v[4]; - // [fgsfds] check sprite type mask - DWORD spritetype = (DWORD)-1; - if (actor != nullptr) spritetype = actor->renderflags & RF_SPRITETYPEMASK; - - // [Nash] is a flat sprite - const bool isFlatSprite = (actor != nullptr) && (spritetype == RF_WALLSPRITE || spritetype == RF_FLATSPRITE); - const bool dontFlip = (actor != nullptr) && (actor->renderflags & RF_DONTFLIP); - const bool useOffsets = (actor != nullptr) && !(actor->renderflags & RF_ROLLCENTER); - - // [Nash] check for special sprite drawing modes - if (drawWithXYBillboard || drawBillboardFacingCamera || drawRollSpriteActor || isFlatSprite) - { - // Compute center of sprite - float xcenter = (x1 + x2)*0.5; - float ycenter = (y1 + y2)*0.5; - float zcenter = (z1 + z2)*0.5; - float xx = -xcenter + x; - float zz = -zcenter + z; - float yy = -ycenter + y; - Matrix3x4 mat; - mat.MakeIdentity(); - mat.Translate(xcenter, zcenter, ycenter); // move to sprite center + CalculateVertices(v); - // Order of rotations matters. Perform yaw rotation (Y, face camera) before pitch (X, tilt up/down). - if (drawBillboardFacingCamera && !isFlatSprite) - { - // [CMB] Rotate relative to camera XY position, not just camera direction, - // which is nicer in VR - float xrel = xcenter - ViewPos.X; - float yrel = ycenter - ViewPos.Y; - float absAngleDeg = RAD2DEG(atan2(-yrel, xrel)); - float counterRotationDeg = 270. - GLRenderer->mAngles.Yaw.Degrees; // counteracts existing sprite rotation - float relAngleDeg = counterRotationDeg + absAngleDeg; - - mat.Rotate(0, 1, 0, relAngleDeg); - } - - // [fgsfds] calculate yaw vectors - float yawvecX = 0, yawvecY = 0, rollDegrees = 0; - float angleRad = (270. - GLRenderer->mAngles.Yaw).Radians(); - if (actor) rollDegrees = actor->Angles.Roll.Degrees; - if (isFlatSprite) - { - yawvecX = actor->Angles.Yaw.Cos(); - yawvecY = actor->Angles.Yaw.Sin(); - } - - // [MC] This is the only thing that I changed in Nash's submission which - // was constantly applying roll to everything. That was wrong. Flat sprites - // with roll literally look like paper thing space ships trying to swerve. - // However, it does well with wall sprites. - // Also, renamed FLOORSPRITE to FLATSPRITE because that's technically incorrect. - // I plan on adding proper FLOORSPRITEs which can actually curve along sloped - // 3D floors later... if possible. - - // Here we need some form of priority in order to work. - if (spritetype == RF_FLATSPRITE) - { - float pitchDegrees = -actor->Angles.Pitch.Degrees; - DVector3 apos = { x, y, z }; - DVector3 diff = ViewPos - apos; - DAngle angto = diff.Angle(); - - angto = deltaangle(actor->Angles.Yaw, angto); - - bool noFlipSprite = (!dontFlip || (fabs(angto) < 90.)); - mat.Rotate(0, 1, 0, (noFlipSprite) ? 0 : 180); - - mat.Rotate(-yawvecY, 0, yawvecX, (noFlipSprite) ? -pitchDegrees : pitchDegrees); - if (drawRollSpriteActor) - { - if (useOffsets) mat.Translate(xx, zz, yy); - mat.Rotate(yawvecX, 0, yawvecY, (noFlipSprite) ? -rollDegrees : rollDegrees); - if (useOffsets) mat.Translate(-xx, -zz, -yy); - } - } - // [fgsfds] Rotate the sprite about the sight vector (roll) - else if (spritetype == RF_WALLSPRITE) - { - mat.Rotate(0, 1, 0, 0); - if (drawRollSpriteActor) - { - if (useOffsets) mat.Translate(xx, zz, yy); - mat.Rotate(yawvecX, 0, yawvecY, rollDegrees); - if (useOffsets) mat.Translate(-xx, -zz, -yy); - } - } - else if (drawRollSpriteActor) - { - if (useOffsets) mat.Translate(xx, zz, yy); - if (drawWithXYBillboard) - { - mat.Rotate(-sin(angleRad), 0, cos(angleRad), -GLRenderer->mAngles.Pitch.Degrees); - } - mat.Rotate(cos(angleRad), 0, sin(angleRad), rollDegrees); - if (useOffsets) mat.Translate(-xx, -zz, -yy); - } - - // apply the transform - else if (drawWithXYBillboard) - { - // Rotate the sprite about the vector starting at the center of the sprite - // triangle strip and with direction orthogonal to where the player is looking - // in the x/y plane. - mat.Rotate(-sin(angleRad), 0, cos(angleRad), -GLRenderer->mAngles.Pitch.Degrees); - } - - mat.Translate(-xcenter, -zcenter, -ycenter); // retreat from sprite center - v1 = mat * FVector3(x1, z1, y1); - v2 = mat * FVector3(x2, z1, y2); - v3 = mat * FVector3(x1, z2, y1); - v4 = mat * FVector3(x2, z2, y2); - } - else // traditional "Y" billboard mode - { - v1 = FVector3(x1, z1, y1); - v2 = FVector3(x2, z1, y2); - v3 = FVector3(x1, z2, y1); - v4 = FVector3(x2, z2, y2); - } FQuadDrawer qd; - qd.Set(0, v1[0], v1[1], v1[2], ul, vt); - qd.Set(1, v2[0], v2[1], v2[2], ur, vt); - qd.Set(2, v3[0], v3[1], v3[2], ul, vb); - qd.Set(3, v4[0], v4[1], v4[2], ur, vb); + qd.Set(0, v[0][0], v[0][1], v[0][2], ul, vt); + qd.Set(1, v[1][0], v[1][1], v[1][2], ur, vt); + qd.Set(2, v[2][0], v[2][1], v[2][2], ul, vb); + qd.Set(3, v[3][0], v[3][1], v[3][2], ur, vb); qd.Render(GL_TRIANGLE_STRIP); if (foglayer) diff --git a/src/gl/scene/gl_wall.h b/src/gl/scene/gl_wall.h index ae7014c7d..32dc6ee5b 100644 --- a/src/gl/scene/gl_wall.h +++ b/src/gl/scene/gl_wall.h @@ -371,6 +371,7 @@ public: void SplitSprite(sector_t * frontsector, bool translucent); void SetLowerParam(); void PerformSpriteClipAdjustment(AActor *thing, const DVector2 &thingpos, float spriteheight); + void CalculateVertices(FVector3 *v); public: From bfe34f4dc7f86d2ce56ccd2b3243bf310f2e162d Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 6 Sep 2016 21:39:13 +0200 Subject: [PATCH 26/44] - cleanup. --- src/gl/scene/gl_sprite.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp index 2def85ef5..dba76d15d 100644 --- a/src/gl/scene/gl_sprite.cpp +++ b/src/gl/scene/gl_sprite.cpp @@ -226,8 +226,6 @@ void GLSprite::CalculateVertices(FVector3 *v) mat.Rotate(cos(angleRad), 0, sin(angleRad), rollDegrees); if (useOffsets) mat.Translate(-xx, -zz, -yy); } - - // apply the transform else if (drawWithXYBillboard) { // Rotate the sprite about the vector starting at the center of the sprite @@ -407,7 +405,6 @@ void GLSprite::Draw(int pass) gl_RenderState.Apply(); FVector3 v[4]; - CalculateVertices(v); From 181e5dc4db824f815cdaf6ff8f37a6191e11d914 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 6 Sep 2016 22:18:47 +0200 Subject: [PATCH 27/44] - fixed floor clipping of sprites. --- src/gl/scene/gl_sprite.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp index dba76d15d..07f397d6d 100644 --- a/src/gl/scene/gl_sprite.cpp +++ b/src/gl/scene/gl_sprite.cpp @@ -711,7 +711,7 @@ void GLSprite::Process(AActor* thing, sector_t * sector, int thruportal) x = thingpos.X; z = thingpos.Z; y = thingpos.Y; - if (spritetype == RF_FLATSPRITE) z -= thing->Floorclip; + if (spritetype != RF_FLATSPRITE) z -= thing->Floorclip; // [RH] Make floatbobbing a renderer-only effect. if (thing->flags2 & MF2_FLOATBOB) From 5a3147407ec1c08e29d701061d2436edac40af4b Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 6 Sep 2016 22:34:59 +0200 Subject: [PATCH 28/44] - fixed floatification error in A_MaulerTorpedoWave. --- src/g_strife/a_strifeweapons.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_strife/a_strifeweapons.cpp b/src/g_strife/a_strifeweapons.cpp index f81ca79f7..0a753e961 100644 --- a/src/g_strife/a_strifeweapons.cpp +++ b/src/g_strife/a_strifeweapons.cpp @@ -552,7 +552,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_MaulerTorpedoWave) // If the torpedo hit the ceiling, it should still spawn the wave savedz = self->Z(); - if (wavedef && self->ceilingz < wavedef->Top()) + if (wavedef && self->ceilingz < self->Z() + wavedef->height) { self->SetZ(self->ceilingz - wavedef->Height); } From f536523fbda2b7ef67febe71f50565479a8902f0 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 6 Sep 2016 22:36:53 +0200 Subject: [PATCH 29/44] - It's Height, not height... --- src/g_strife/a_strifeweapons.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_strife/a_strifeweapons.cpp b/src/g_strife/a_strifeweapons.cpp index 0a753e961..24e7fb5c7 100644 --- a/src/g_strife/a_strifeweapons.cpp +++ b/src/g_strife/a_strifeweapons.cpp @@ -552,7 +552,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_MaulerTorpedoWave) // If the torpedo hit the ceiling, it should still spawn the wave savedz = self->Z(); - if (wavedef && self->ceilingz < self->Z() + wavedef->height) + if (wavedef && self->ceilingz < self->Z() + wavedef->Height) { self->SetZ(self->ceilingz - wavedef->Height); } From 2e8aa53e6ad0fb35728ffa2aff0227444f467966 Mon Sep 17 00:00:00 2001 From: yqco Date: Wed, 24 Aug 2016 04:21:46 -0600 Subject: [PATCH 30/44] Add SetActorFlag ACS function int SetActorFlag(int tid, str flagname, bool value); - Mimics DECORATE's A_ChangeFlag - Returns number of actors affected (number of things with the flag) - Affects activator if TID is 0 # Conflicts: # src/p_acs.cpp --- src/p_acs.cpp | 29 ++++++++++ src/thingdef/thingdef.h | 1 + src/thingdef/thingdef_codeptr.cpp | 85 +--------------------------- src/thingdef/thingdef_properties.cpp | 65 +++++++++++++++++++++ 4 files changed, 96 insertions(+), 84 deletions(-) diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 0157fda75..67ae37771 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -4451,6 +4451,7 @@ enum EACSFunctions ACSF_CheckClass = 200, ACSF_DamageActor, // [arookas] + ACSF_SetActorFlag, // ZDaemon ACSF_GetTeamScore = 19620, // (int team) @@ -6046,6 +6047,34 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) return P_DamageMobj(target, inflictor, inflictor, args[4], damagetype); } + case ACSF_SetActorFlag: + { + int tid = args[0]; + FString flagname = FBehavior::StaticLookupString(args[1]); + bool flagvalue = !!args[2]; + int count = 0; // Return value; number of actors affected + if (tid == 0) + { + if (ModActorFlag(activator, flagname, flagvalue)) + { + ++count; + } + } + else + { + FActorIterator it(tid); + while ((actor = it.Next()) != nullptr) + { + // Don't log errors when affecting many actors because things might share a TID but not share the flag + if (ModActorFlag(actor, flagname, flagvalue, false)) + { + ++count; + } + } + } + return count; + } + default: break; } diff --git a/src/thingdef/thingdef.h b/src/thingdef/thingdef.h index 296383a3d..3a419b112 100644 --- a/src/thingdef/thingdef.h +++ b/src/thingdef/thingdef.h @@ -30,6 +30,7 @@ void HandleDeprecatedFlags(AActor *defaults, PClassActor *info, bool set, int in bool CheckDeprecatedFlags(const AActor *actor, PClassActor *info, int index); const char *GetFlagName(unsigned int flagnum, int flagoffset); void ModActorFlag(AActor *actor, FFlagDef *fd, bool set); +bool ModActorFlag(AActor *actor, FString &flagname, bool set, bool printerror = true); INTBOOL CheckActorFlag(const AActor *actor, FFlagDef *fd); INTBOOL CheckActorFlag(const AActor *owner, const char *flagname, bool printerror = true); diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index d520a0b9d..8c2a020c7 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -4681,90 +4681,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) PARAM_STRING (flagname); PARAM_BOOL (value); - const char *dot = strchr(flagname, '.'); - FFlagDef *fd; - PClassActor *cls = self->GetClass(); - - if (dot != NULL) - { - FString part1(flagname.GetChars(), dot - flagname); - fd = FindFlag(cls, part1, dot + 1); - } - else - { - fd = FindFlag(cls, flagname, NULL); - } - - if (fd != NULL) - { - bool kill_before, kill_after; - INTBOOL item_before, item_after; - INTBOOL secret_before, secret_after; - - kill_before = self->CountsAsKill(); - item_before = self->flags & MF_COUNTITEM; - secret_before = self->flags5 & MF5_COUNTSECRET; - - if (fd->structoffset == -1) - { - HandleDeprecatedFlags(self, cls, value, fd->flagbit); - } - else - { - ActorFlags *flagp = (ActorFlags*) (((char*)self) + fd->structoffset); - - // If these 2 flags get changed we need to update the blockmap and sector links. - bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); - - if (linkchange) self->UnlinkFromWorld(); - ModActorFlag(self, fd, value); - if (linkchange) self->LinkToWorld(); - } - kill_after = self->CountsAsKill(); - item_after = self->flags & MF_COUNTITEM; - secret_after = self->flags5 & MF5_COUNTSECRET; - // Was this monster previously worth a kill but no longer is? - // Or vice versa? - if (kill_before != kill_after) - { - if (kill_after) - { // It counts as a kill now. - level.total_monsters++; - } - else - { // It no longer counts as a kill. - level.total_monsters--; - } - } - // same for items - if (item_before != item_after) - { - if (item_after) - { // It counts as an item now. - level.total_items++; - } - else - { // It no longer counts as an item - level.total_items--; - } - } - // and secretd - if (secret_before != secret_after) - { - if (secret_after) - { // It counts as an secret now. - level.total_secrets++; - } - else - { // It no longer counts as an secret - level.total_secrets--; - } - } - } - else - { - Printf("Unknown flag '%s' in '%s'\n", flagname.GetChars(), cls->TypeName.GetChars()); - } + ModActorFlag(self, flagname, value); return 0; } diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index 372f444cd..f6ad511af 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -162,6 +162,71 @@ void ModActorFlag(AActor *actor, FFlagDef *fd, bool set) #endif } +//========================================================================== +// +// Finds a flag by name and sets or clears it +// +// Returns true if the flag was found for the actor; else returns false +// +//========================================================================== + +bool ModActorFlag(AActor *actor, FString &flagname, bool set, bool printerror) +{ + bool found = false; + + if (actor != NULL) + { + const char *dot = strchr(flagname, '.'); + FFlagDef *fd; + PClassActor *cls = actor->GetClass(); + + if (dot != NULL) + { + FString part1(flagname.GetChars(), dot - flagname); + fd = FindFlag(cls, part1, dot + 1); + } + else + { + fd = FindFlag(cls, flagname, NULL); + } + + if (fd != NULL) + { + found = true; + + if (actor->CountsAsKill() && actor->health > 0) --level.total_monsters; + if (actor->flags & MF_COUNTITEM) --level.total_items; + if (actor->flags5 & MF5_COUNTSECRET) --level.total_secrets; + + if (fd->structoffset == -1) + { + HandleDeprecatedFlags(actor, cls, set, fd->flagbit); + } + else + { + ActorFlags *flagp = (ActorFlags*)(((char*)actor) + fd->structoffset); + + // If these 2 flags get changed we need to update the blockmap and sector links. + bool linkchange = flagp == &actor->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); + + if (linkchange) actor->UnlinkFromWorld(); + ModActorFlag(actor, fd, set); + if (linkchange) actor->LinkToWorld(); + } + + if (actor->CountsAsKill() && actor->health > 0) ++level.total_monsters; + if (actor->flags & MF_COUNTITEM) ++level.total_items; + if (actor->flags5 & MF5_COUNTSECRET) ++level.total_secrets; + } + else if (printerror) + { + DPrintf(DMSG_ERROR, "ACS/DECORATE: '%s' is not a flag in '%s'\n", flagname.GetChars(), cls->TypeName.GetChars()); + } + } + + return found; +} + //========================================================================== // // Returns whether an actor flag is true or not. From ce13b5c6e1731ea04a28c9708470e189593d0e76 Mon Sep 17 00:00:00 2001 From: Major Cooke Date: Sun, 4 Sep 2016 08:53:20 -0500 Subject: [PATCH 31/44] Enhanced FastProjectile trails. - Trails now copy pitch, and set the projectile as the target. - Added GETOWNER flag. Using it sets the owner of the fast projectile as the target instead, if it has an owner. --- src/actor.h | 2 +- src/g_shared/a_fastprojectile.cpp | 8 +++++++- src/thingdef/thingdef_data.cpp | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/actor.h b/src/actor.h index b895d1e2a..522405dd6 100644 --- a/src/actor.h +++ b/src/actor.h @@ -283,7 +283,7 @@ enum ActorFlag4 enum ActorFlag5 { MF5_DONTDRAIN = 0x00000001, // cannot be drained health from. - /* FREE SLOT 0x00000002*/ + MF5_GETOWNER = 0x00000002, MF5_NODROPOFF = 0x00000004, // cannot drop off under any circumstances. MF5_NOFORWARDFALL = 0x00000008, // Does not make any actor fall forward by being damaged by this MF5_COUNTSECRET = 0x00000010, // From Doom 64: actor acts like a secret diff --git a/src/g_shared/a_fastprojectile.cpp b/src/g_shared/a_fastprojectile.cpp index 8c7d63b74..c1253f966 100644 --- a/src/g_shared/a_fastprojectile.cpp +++ b/src/g_shared/a_fastprojectile.cpp @@ -166,8 +166,14 @@ void AFastProjectile::Effect() if (trail != NULL) { AActor *act = Spawn (trail, PosAtZ(hitz), ALLOW_REPLACE); - if (act != NULL) + if (act != nullptr) { + if ((flags5 & MF5_GETOWNER) && (target != nullptr)) + act->target = target; + else + act->target = this; + + act->Angles.Pitch = Angles.Pitch; act->Angles.Yaw = Angles.Yaw; } } diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index 8fceca8ee..756c56a1a 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -182,6 +182,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(MF4, BOSSDEATH, AActor, flags4), DEFINE_FLAG(MF5, DONTDRAIN, AActor, flags5), + DEFINE_FLAG(MF5, GETOWNER, AActor, flags5), DEFINE_FLAG(MF5, NODROPOFF, AActor, flags5), DEFINE_FLAG(MF5, NOFORWARDFALL, AActor, flags5), DEFINE_FLAG(MF5, COUNTSECRET, AActor, flags5), From 043ada24da842dd5e6085422f1b957851794767a Mon Sep 17 00:00:00 2001 From: Major Cooke Date: Sun, 4 Sep 2016 16:49:57 -0500 Subject: [PATCH 32/44] Wave quakes now stack. --- src/g_shared/a_quake.cpp | 67 +++++++++++++++++++---------------- src/g_shared/a_sharedglobal.h | 5 ++- src/r_utility.cpp | 18 +++++----- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/g_shared/a_quake.cpp b/src/g_shared/a_quake.cpp index 74c416468..aae006ca7 100644 --- a/src/g_shared/a_quake.cpp +++ b/src/g_shared/a_quake.cpp @@ -161,7 +161,7 @@ double DEarthquake::GetModWave(double waveMultiplier) const // //========================================================================== -double DEarthquake::GetModIntensity(double intensity) const +double DEarthquake::GetModIntensity(double intensity, bool fake) const { assert(m_CountdownStart >= m_Countdown); @@ -195,7 +195,7 @@ double DEarthquake::GetModIntensity(double intensity) const } scalar = (scalar > divider) ? divider : scalar; - if (m_Flags & QF_FULLINTENSITY) + if (!fake && (m_Flags & QF_FULLINTENSITY)) { scalar *= 2; } @@ -273,64 +273,69 @@ int DEarthquake::StaticGetQuakeIntensities(AActor *victim, FQuakeJiggers &jigger DEarthquake *quake; int count = 0; - while ( (quake = iterator.Next()) != NULL) + while ( (quake = iterator.Next()) != nullptr) { - if (quake->m_Spot != NULL) + if (quake->m_Spot != nullptr) { - double dist = quake->m_Spot->Distance2D (victim, true); + const double dist = quake->m_Spot->Distance2D(victim, true); if (dist < quake->m_TremorRadius) { - const double falloff = quake->GetFalloff(dist); - const double rfalloff = (quake->m_RollIntensity != 0) ? falloff : 0.; ++count; - double x = quake->GetModIntensity(quake->m_Intensity.X); - double y = quake->GetModIntensity(quake->m_Intensity.Y); - double z = quake->GetModIntensity(quake->m_Intensity.Z); - double r = quake->GetModIntensity(quake->m_RollIntensity); + const double falloff = quake->GetFalloff(dist); + const double r = quake->GetModIntensity(quake->m_RollIntensity); + const double strength = quake->GetModIntensity(1.0, true); + DVector3 intensity; + intensity.X = quake->GetModIntensity(quake->m_Intensity.X); + intensity.Y = quake->GetModIntensity(quake->m_Intensity.Y); + intensity.Z = quake->GetModIntensity(quake->m_Intensity.Z); if (!(quake->m_Flags & QF_WAVE)) { jiggers.Falloff = MAX(falloff, jiggers.Falloff); - jiggers.RFalloff = MAX(rfalloff, jiggers.RFalloff); - jiggers.RollIntensity = MAX(r, jiggers.RollIntensity); + jiggers.RollIntensity = MAX(r, jiggers.RollIntensity) * jiggers.Falloff; + + intensity *= jiggers.Falloff; if (quake->m_Flags & QF_RELATIVE) { - jiggers.RelIntensity.X = MAX(x, jiggers.RelIntensity.X); - jiggers.RelIntensity.Y = MAX(y, jiggers.RelIntensity.Y); - jiggers.RelIntensity.Z = MAX(z, jiggers.RelIntensity.Z); + jiggers.RelIntensity.X = MAX(intensity.X, jiggers.RelIntensity.X); + jiggers.RelIntensity.Y = MAX(intensity.Y, jiggers.RelIntensity.Y); + jiggers.RelIntensity.Z = MAX(intensity.Z, jiggers.RelIntensity.Z); } else { - jiggers.Intensity.X = MAX(x, jiggers.Intensity.X); - jiggers.Intensity.Y = MAX(y, jiggers.Intensity.Y); - jiggers.Intensity.Z = MAX(z, jiggers.Intensity.Z); + jiggers.Intensity.X = MAX(intensity.X, jiggers.Intensity.X); + jiggers.Intensity.Y = MAX(intensity.Y, jiggers.Intensity.Y); + jiggers.Intensity.Z = MAX(intensity.Z, jiggers.Intensity.Z); } } else { - jiggers.WFalloff = MAX(falloff, jiggers.WFalloff); - jiggers.RWFalloff = MAX(rfalloff, jiggers.RWFalloff); - jiggers.RollWave = r * quake->GetModWave(quake->m_RollWave); - double mx = x * quake->GetModWave(quake->m_WaveSpeed.X); - double my = y * quake->GetModWave(quake->m_WaveSpeed.Y); - double mz = z * quake->GetModWave(quake->m_WaveSpeed.Z); + jiggers.Falloff = MAX(falloff, jiggers.Falloff); + jiggers.RollWave = r * quake->GetModWave(quake->m_RollWave) * jiggers.Falloff * strength; + + + intensity.X *= quake->GetModWave(quake->m_WaveSpeed.X); + intensity.Y *= quake->GetModWave(quake->m_WaveSpeed.Y); + intensity.Z *= quake->GetModWave(quake->m_WaveSpeed.Z); + intensity *= strength * jiggers.Falloff; // [RH] This only gives effect to the last sine quake. I would // prefer if some way was found to make multiples coexist // peacefully, but just summing them together is undesirable // because they could cancel each other out depending on their // relative phases. + + // [MC] Now does so. And they stack rather well. I'm a little + // surprised at how easy it was. + + if (quake->m_Flags & QF_RELATIVE) { - jiggers.RelOffset.X = mx; - jiggers.RelOffset.Y = my; - jiggers.RelOffset.Z = mz; + jiggers.RelOffset += intensity; } else { - jiggers.Offset.X = mx; - jiggers.Offset.Y = my; - jiggers.Offset.Z = mz; + jiggers.Offset += intensity; } } } diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h index f873ab0b7..314061f04 100644 --- a/src/g_shared/a_sharedglobal.h +++ b/src/g_shared/a_sharedglobal.h @@ -153,7 +153,7 @@ struct FQuakeJiggers DVector3 RelIntensity; DVector3 Offset; DVector3 RelOffset; - double Falloff, WFalloff, RFalloff, RWFalloff; + double Falloff; double RollIntensity, RollWave; }; @@ -180,8 +180,7 @@ public: int m_Highpoint, m_MiniCount; double m_RollIntensity, m_RollWave; - - double GetModIntensity(double intensity) const; + double GetModIntensity(double intensity, bool fake = false) const; double GetModWave(double waveMultiplier) const; double GetFalloff(double dist) const; diff --git a/src/r_utility.cpp b/src/r_utility.cpp index 71d3f2376..00ac91c97 100644 --- a/src/r_utility.cpp +++ b/src/r_utility.cpp @@ -656,7 +656,7 @@ void R_AddInterpolationPoint(const DVector3a &vec) // //========================================================================== -static double QuakePower(double factor, double intensity, double offset, double falloff, double wfalloff) +static double QuakePower(double factor, double intensity, double offset) { double randumb; if (intensity == 0) @@ -667,7 +667,7 @@ static double QuakePower(double factor, double intensity, double offset, double { randumb = pr_torchflicker.GenRand_Real2() * (intensity * 2) - intensity; } - return factor * (wfalloff * offset + falloff * randumb); + return factor * (offset + randumb); } //========================================================================== @@ -797,36 +797,36 @@ void R_SetupFrame (AActor *actor) if (jiggers.RollIntensity != 0 || jiggers.RollWave != 0) { - ViewRoll += QuakePower(quakefactor, jiggers.RollIntensity, jiggers.RollWave, jiggers.RFalloff, jiggers.RWFalloff); + ViewRoll += QuakePower(quakefactor, jiggers.RollIntensity, jiggers.RollWave); } if (jiggers.RelIntensity.X != 0 || jiggers.RelOffset.X != 0) { an = camera->Angles.Yaw; - double power = QuakePower(quakefactor, jiggers.RelIntensity.X, jiggers.RelOffset.X, jiggers.Falloff, jiggers.WFalloff); + double power = QuakePower(quakefactor, jiggers.RelIntensity.X, jiggers.RelOffset.X); ViewPos += an.ToVector(power); } if (jiggers.RelIntensity.Y != 0 || jiggers.RelOffset.Y != 0) { an = camera->Angles.Yaw + 90; - double power = QuakePower(quakefactor, jiggers.RelIntensity.Y, jiggers.RelOffset.Y, jiggers.Falloff, jiggers.WFalloff); + double power = QuakePower(quakefactor, jiggers.RelIntensity.Y, jiggers.RelOffset.Y); ViewPos += an.ToVector(power); } // FIXME: Relative Z is not relative if (jiggers.RelIntensity.Z != 0 || jiggers.RelOffset.Z != 0) { - ViewPos.Z += QuakePower(quakefactor, jiggers.RelIntensity.Z, jiggers.RelOffset.Z, jiggers.Falloff, jiggers.WFalloff); + ViewPos.Z += QuakePower(quakefactor, jiggers.RelIntensity.Z, jiggers.RelOffset.Z); } if (jiggers.Intensity.X != 0 || jiggers.Offset.X != 0) { - ViewPos.X += QuakePower(quakefactor, jiggers.Intensity.X, jiggers.Offset.X, jiggers.Falloff, jiggers.WFalloff); + ViewPos.X += QuakePower(quakefactor, jiggers.Intensity.X, jiggers.Offset.X); } if (jiggers.Intensity.Y != 0 || jiggers.Offset.Y != 0) { - ViewPos.Y += QuakePower(quakefactor, jiggers.Intensity.Y, jiggers.Offset.Y, jiggers.Falloff, jiggers.WFalloff); + ViewPos.Y += QuakePower(quakefactor, jiggers.Intensity.Y, jiggers.Offset.Y); } if (jiggers.Intensity.Z != 0 || jiggers.Offset.Z != 0) { - ViewPos.Z += QuakePower(quakefactor, jiggers.Intensity.Z, jiggers.Offset.Z, jiggers.Falloff, jiggers.WFalloff); + ViewPos.Z += QuakePower(quakefactor, jiggers.Intensity.Z, jiggers.Offset.Z); } } } From 01e9d351b48cb48e94d9b4b90c5c799eda2378f9 Mon Sep 17 00:00:00 2001 From: Major Cooke Date: Wed, 7 Sep 2016 11:24:04 -0500 Subject: [PATCH 33/44] - Don't pass flags directly from A_Explode to P_RadiusAttack. XF_EXPLICITDAMAGETYPE would cause explosions to deal no damage otherwise. --- src/thingdef/thingdef_codeptr.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 8c2a020c7..642f132af 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1422,7 +1422,11 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) damagetype = self->DamageType; } - int count = P_RadiusAttack (self, self->target, damage, distance, damagetype, flags, fulldmgdistance); + int pflags = 0; + if (flags & XF_HURTSOURCE) pflags |= RADF_HURTSOURCE; + if (flags & XF_NOTMISSILE) pflags |= RADF_SOURCEISSPOT; + + int count = P_RadiusAttack (self, self->target, damage, distance, damagetype, pflags, fulldmgdistance); P_CheckSplash(self, distance); if (alert && self->target != NULL && self->target->player != NULL) { From 6414e013544218f5450510078f27383087ae4b8f Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Tue, 6 Sep 2016 19:48:14 +0200 Subject: [PATCH 34/44] Add uiscale slider controlling what scale the On setting uses for hud_scale, hud_althudscale and con_scaletext --- src/c_console.cpp | 61 +++++++++++------------ src/ct_chat.cpp | 32 +++++-------- src/g_shared/hudmessages.cpp | 93 +++++++++--------------------------- src/g_shared/sbarinfo.cpp | 10 +++- src/g_shared/shared_hud.cpp | 42 +++++++++------- src/g_shared/shared_sbar.cpp | 38 ++++++--------- src/v_draw.cpp | 10 +++- src/v_video.h | 2 + wadsrc/static/language.enu | 1 + wadsrc/static/menudef.txt | 3 ++ 10 files changed, 125 insertions(+), 167 deletions(-) diff --git a/src/c_console.cpp b/src/c_console.cpp index 850a29455..026266359 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -159,12 +159,24 @@ static int HistSize; CVAR (Float, con_notifytime, 3.f, CVAR_ARCHIVE) CVAR (Bool, con_centernotify, false, CVAR_ARCHIVE) -CUSTOM_CVAR (Int, con_scaletext, 0, CVAR_ARCHIVE) // Scale notify text at high resolutions? +CUSTOM_CVAR (Int, con_scaletext, 1, CVAR_ARCHIVE) // Scale notify text at high resolutions? { if (self < 0) self = 0; if (self > 3) self = 3; } +int con_uiscale() +{ + switch (con_scaletext) + { + default: + case 0: return 1; + case 1: return uiscale; + case 2: return 2; + case 3: return 4; + } +} + CUSTOM_CVAR(Float, con_alpha, 0.75f, CVAR_ARCHIVE) { if (self < 0.f) self = 0.f; @@ -493,13 +505,13 @@ void C_AddNotifyString (int printlevel, const char *source) return; } - switch (con_scaletext) + if (con_uiscale() == 0) { - default: - case 0: width = DisplayWidth; break; - case 1: width = DisplayWidth / CleanXfac; break; - case 2: width = DisplayWidth / 2; break; - case 3: width = DisplayWidth / 4; break; + width = DisplayWidth / CleanXfac; + } + else + { + width = DisplayWidth / con_uiscale(); } if (addtype == APPENDLINE && NotifyStrings[NUMNOTIFIES-1].PrintLevel == printlevel) @@ -721,7 +733,7 @@ static void C_DrawNotifyText () canskip = true; lineadv = SmallFont->GetHeight (); - if (con_scaletext == 1) + if (con_uiscale() == 0) { lineadv *= CleanYfac; } @@ -755,7 +767,7 @@ static void C_DrawNotifyText () else color = PrintColors[NotifyStrings[i].PrintLevel]; - if (con_scaletext == 1) + if (con_uiscale() == 0) { if (!center) screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text, @@ -766,7 +778,7 @@ static void C_DrawNotifyText () line, NotifyStrings[i].Text, DTA_CleanNoMove, true, DTA_AlphaF, alpha, TAG_DONE); } - else if (con_scaletext == 0) + else if (con_uiscale() == 1) { if (!center) screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text, @@ -777,37 +789,20 @@ static void C_DrawNotifyText () line, NotifyStrings[i].Text, DTA_AlphaF, alpha, TAG_DONE); } - else if (con_scaletext == 3) - { - if (!center) - screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text, - DTA_VirtualWidth, screen->GetWidth() / 4, - DTA_VirtualHeight, screen->GetHeight() / 4, - DTA_KeepRatio, true, - DTA_AlphaF, alpha, TAG_DONE); - else - screen->DrawText (SmallFont, color, (screen->GetWidth() / 4 - - SmallFont->StringWidth (NotifyStrings[i].Text))/4, - line, NotifyStrings[i].Text, - DTA_VirtualWidth, screen->GetWidth() / 4, - DTA_VirtualHeight, screen->GetHeight() / 4, - DTA_KeepRatio, true, - DTA_AlphaF, alpha, TAG_DONE); - } else { if (!center) screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text, - DTA_VirtualWidth, screen->GetWidth() / 2, - DTA_VirtualHeight, screen->GetHeight() / 2, + DTA_VirtualWidth, screen->GetWidth() / con_uiscale(), + DTA_VirtualHeight, screen->GetHeight() / con_uiscale(), DTA_KeepRatio, true, DTA_AlphaF, alpha, TAG_DONE); else - screen->DrawText (SmallFont, color, (screen->GetWidth() / 2 - - SmallFont->StringWidth (NotifyStrings[i].Text))/2, + screen->DrawText (SmallFont, color, (screen->GetWidth() / con_uiscale() - + SmallFont->StringWidth (NotifyStrings[i].Text))/ con_uiscale(), line, NotifyStrings[i].Text, - DTA_VirtualWidth, screen->GetWidth() / 2, - DTA_VirtualHeight, screen->GetHeight() / 2, + DTA_VirtualWidth, screen->GetWidth() / con_uiscale(), + DTA_VirtualHeight, screen->GetHeight() / con_uiscale(), DTA_KeepRatio, true, DTA_AlphaF, alpha, TAG_DONE); } diff --git a/src/ct_chat.cpp b/src/ct_chat.cpp index b053d8bd8..7fd36d0ab 100644 --- a/src/ct_chat.cpp +++ b/src/ct_chat.cpp @@ -46,6 +46,8 @@ EXTERN_CVAR (Bool, sb_cooperative_enable) EXTERN_CVAR (Bool, sb_deathmatch_enable) EXTERN_CVAR (Bool, sb_teamdeathmatch_enable) +int con_uiscale(); + // Public data void CT_Init (); @@ -224,7 +226,7 @@ void CT_Drawer (void) int i, x, scalex, y, promptwidth; y = (viewactive || gamestate != GS_LEVEL) ? -10 : -30; - if (con_scaletext == 1) + if (con_uiscale() == 0) { scalex = CleanXfac; y *= CleanYfac; @@ -235,25 +237,17 @@ void CT_Drawer (void) } int screen_width, screen_height, st_y; - switch (con_scaletext) + if (con_uiscale() == 0) { - default: - case 0: - case 1: screen_width = SCREENWIDTH; screen_height = SCREENHEIGHT; st_y = ST_Y; - break; - case 2: - screen_width = SCREENWIDTH / 2; - screen_height = SCREENHEIGHT / 2; - st_y = ST_Y / 2; - break; - case 3: - screen_width = SCREENWIDTH / 4; - screen_height = SCREENHEIGHT / 4; - st_y = ST_Y / 4; - break; + } + else + { + screen_width = SCREENWIDTH / con_uiscale(); + screen_height = SCREENHEIGHT / con_uiscale(); + st_y = ST_Y / con_uiscale(); } y += ((SCREENHEIGHT == viewheight && viewactive) || gamestate != GS_LEVEL) ? screen_height : st_y; @@ -280,10 +274,10 @@ void CT_Drawer (void) // draw the prompt, text, and cursor ChatQueue[len] = SmallFont->GetCursor(); ChatQueue[len+1] = '\0'; - if (con_scaletext < 2) + if (con_uiscale() < 2) { - screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, DTA_CleanNoMove, *con_scaletext, TAG_DONE); - screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), DTA_CleanNoMove, *con_scaletext, TAG_DONE); + screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, DTA_CleanNoMove, con_uiscale() == 0, TAG_DONE); + screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), DTA_CleanNoMove, con_uiscale() == 0, TAG_DONE); } else { diff --git a/src/g_shared/hudmessages.cpp b/src/g_shared/hudmessages.cpp index 9f13d3d9e..628c8a5ee 100644 --- a/src/g_shared/hudmessages.cpp +++ b/src/g_shared/hudmessages.cpp @@ -41,7 +41,8 @@ #include "doomstat.h" #include "farchive.h" -EXTERN_CVAR (Int, con_scaletext) +EXTERN_CVAR(Int, con_scaletext) +int con_uiscale(); IMPLEMENT_POINTY_CLASS (DHUDMessage) DECLARE_POINTER(Next) @@ -260,13 +261,10 @@ void DHUDMessage::ResetText (const char *text) } else { - switch (con_scaletext) + switch (con_uiscale()) { - default: - case 0: width = SCREENWIDTH; break; - case 1: width = SCREENWIDTH / CleanXfac; break; - case 2: width = SCREENWIDTH / 2; break; - case 3: width = SCREENWIDTH / 4; break; + case 0: width = SCREENWIDTH / CleanXfac; break; + default: width = SCREENWIDTH / con_uiscale(); break; } } @@ -332,7 +330,7 @@ void DHUDMessage::Draw (int bottom, int visibility) int screen_width = SCREENWIDTH; int screen_height = SCREENHEIGHT; - if (HUDWidth == 0 && con_scaletext==1) + if (HUDWidth == 0 && con_uiscale() == 0) { clean = true; xscale = CleanXfac; @@ -341,17 +339,11 @@ void DHUDMessage::Draw (int bottom, int visibility) else { xscale = yscale = 1; - if (HUDWidth==0 && con_scaletext==2) + if (HUDWidth == 0) { - screen_width/=2; - screen_height/=2; - bottom/=2; - } - else if (HUDWidth==0 && con_scaletext==3) - { - screen_width/=4; - screen_height/=4; - bottom/=4; + screen_width /= con_uiscale(); + screen_height /= con_uiscale(); + bottom /= con_uiscale(); } } @@ -453,7 +445,7 @@ void DHUDMessage::DoDraw (int linenum, int x, int y, bool clean, int hudheight) { if (hudheight == 0) { - if (con_scaletext <= 1) + if (con_uiscale() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -461,21 +453,11 @@ void DHUDMessage::DoDraw (int linenum, int x, int y, bool clean, int hudheight) DTA_RenderStyle, Style, TAG_DONE); } - else if (con_scaletext == 3) - { - screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/4, - DTA_VirtualHeight, SCREENHEIGHT/4, - DTA_AlphaF, Alpha, - DTA_RenderStyle, Style, - DTA_KeepRatio, true, - TAG_DONE); - } else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/2, - DTA_VirtualHeight, SCREENHEIGHT/2, + DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), + DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), DTA_AlphaF, Alpha, DTA_RenderStyle, Style, DTA_KeepRatio, true, @@ -566,7 +548,7 @@ void DHUDMessageFadeOut::DoDraw (int linenum, int x, int y, bool clean, int hudh float trans = float(Alpha * -(Tics - FadeOutTics) / FadeOutTics); if (hudheight == 0) { - if (con_scaletext <= 1) + if (con_uiscale() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -574,21 +556,11 @@ void DHUDMessageFadeOut::DoDraw (int linenum, int x, int y, bool clean, int hudh DTA_RenderStyle, Style, TAG_DONE); } - else if (con_scaletext == 3) - { - screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/4, - DTA_VirtualHeight, SCREENHEIGHT/4, - DTA_AlphaF, trans, - DTA_RenderStyle, Style, - DTA_KeepRatio, true, - TAG_DONE); - } else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/2, - DTA_VirtualHeight, SCREENHEIGHT/2, + DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), + DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), DTA_AlphaF, trans, DTA_RenderStyle, Style, DTA_KeepRatio, true, @@ -676,7 +648,7 @@ void DHUDMessageFadeInOut::DoDraw (int linenum, int x, int y, bool clean, int hu float trans = float(Alpha * Tics / FadeInTics); if (hudheight == 0) { - if (con_scaletext <= 1) + if (con_uiscale() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -684,21 +656,11 @@ void DHUDMessageFadeInOut::DoDraw (int linenum, int x, int y, bool clean, int hu DTA_RenderStyle, Style, TAG_DONE); } - else if (con_scaletext == 3) - { - screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/4, - DTA_VirtualHeight, SCREENHEIGHT/4, - DTA_AlphaF, trans, - DTA_RenderStyle, Style, - DTA_KeepRatio, true, - TAG_DONE); - } else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/2, - DTA_VirtualHeight, SCREENHEIGHT/2, + DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), + DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), DTA_AlphaF, trans, DTA_RenderStyle, Style, DTA_KeepRatio, true, @@ -864,7 +826,7 @@ void DHUDMessageTypeOnFadeOut::DoDraw (int linenum, int x, int y, bool clean, in { if (hudheight == 0) { - if (con_scaletext <= 1) + if (con_uiscale() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -873,22 +835,11 @@ void DHUDMessageTypeOnFadeOut::DoDraw (int linenum, int x, int y, bool clean, in DTA_RenderStyle, Style, TAG_DONE); } - else if (con_scaletext == 3) - { - screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/4, - DTA_VirtualHeight, SCREENHEIGHT/4, - DTA_KeepRatio, true, - DTA_TextLen, LineVisible, - DTA_AlphaF, Alpha, - DTA_RenderStyle, Style, - TAG_DONE); - } else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH/2, - DTA_VirtualHeight, SCREENHEIGHT/2, + DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), + DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), DTA_KeepRatio, true, DTA_TextLen, LineVisible, DTA_AlphaF, Alpha, diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 0136b4f25..42f28173c 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -1013,7 +1013,15 @@ public: void ScreenSizeChanged() { Super::ScreenSizeChanged(); - V_CalcCleanFacs(script->resW, script->resH, SCREENWIDTH, SCREENHEIGHT, &script->cleanX, &script->cleanY); + if (uiscale > 0) + { + script->cleanX = uiscale; + script->cleanY = uiscale; + } + else + { + V_CalcCleanFacs(script->resW, script->resH, SCREENWIDTH, SCREENHEIGHT, &script->cleanX, &script->cleanY); + } } void Draw (EHudState state) diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp index cddc86ee4..26504cb7d 100644 --- a/src/g_shared/shared_hud.cpp +++ b/src/g_shared/shared_hud.cpp @@ -66,7 +66,7 @@ EXTERN_CVAR (Int, screenblocks) EXTERN_CVAR (Bool, am_showtime) EXTERN_CVAR (Bool, am_showtotaltime) -CVAR(Int,hud_althudscale, 2, CVAR_ARCHIVE) // Scale the hud to 640x400? +CVAR(Int,hud_althudscale, 4, CVAR_ARCHIVE) // Scale the hud to 640x400? CVAR(Bool,hud_althud, false, CVAR_ARCHIVE) // Enable/Disable the alternate HUD // These are intentionally not the same as in the automap! @@ -118,7 +118,7 @@ static int hudwidth, hudheight; // current width/height for HUD display static int statspace; DVector2 AM_GetPosition(); - +int con_uiscale(); FTextureID GetHUDIcon(PClassInventory *cls) { @@ -886,22 +886,15 @@ static void DrawCoordinates(player_t * CPlayer) } int vwidth, vheight; - switch (con_scaletext) + if (con_uiscale() == 0) { - default: - case 0: - vwidth = SCREENWIDTH; - vheight = SCREENHEIGHT; - break; - case 1: - case 2: - vwidth = SCREENWIDTH/2; - vheight = SCREENHEIGHT/2; - break; - case 3: - vwidth = SCREENWIDTH/4; - vheight = SCREENHEIGHT/4; - break; + vwidth = SCREENWIDTH / 2; + vheight = SCREENHEIGHT / 2; + } + else + { + vwidth = SCREENWIDTH / con_uiscale(); + vheight = SCREENHEIGHT / con_uiscale(); } int xpos = vwidth - SmallFont->StringWidth("X: -00000")-6; @@ -1090,7 +1083,20 @@ void DrawHUD() if (hud_althudscale && SCREENWIDTH>640) { hudwidth=SCREENWIDTH/2; - if (hud_althudscale == 3) + if (hud_althudscale == 4) + { + if (uiscale == 0) + { + hudwidth = CleanWidth; + hudheight = CleanHeight; + } + else + { + hudwidth = SCREENWIDTH / uiscale; + hudheight = SCREENHEIGHT / uiscale; + } + } + else if (hud_althudscale == 3) { hudwidth = SCREENWIDTH / 4; hudheight = SCREENHEIGHT / 4; diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index c3b1fd262..6f495581f 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -74,6 +74,8 @@ EXTERN_CVAR (Bool, am_showtotaltime) EXTERN_CVAR (Bool, noisedebug) EXTERN_CVAR (Int, con_scaletext) +int con_uiscale(); + DBaseStatusBar *StatusBar; extern int setblocks; @@ -1240,17 +1242,17 @@ void DBaseStatusBar::Draw (EHudState state) int xpos; int y; - if (con_scaletext == 0) + if (con_uiscale() == 1) { vwidth = SCREENWIDTH; vheight = SCREENHEIGHT; xpos = vwidth - 80; y = ::ST_Y - height; } - else if (con_scaletext == 3) + else if (con_uiscale() > 1) { - vwidth = SCREENWIDTH/4; - vheight = SCREENHEIGHT/4; + vwidth = SCREENWIDTH / con_uiscale(); + vheight = SCREENHEIGHT / con_uiscale(); xpos = vwidth - SmallFont->StringWidth("X: -00000")-6; y = ::ST_Y/4 - height; } @@ -1264,9 +1266,9 @@ void DBaseStatusBar::Draw (EHudState state) if (gameinfo.gametype == GAME_Strife) { - if (con_scaletext == 0) + if (con_uiscale() == 1) y -= height * 4; - else if (con_scaletext == 3) + else if (con_uiscale() > 3) y -= height; else y -= height * 2; @@ -1400,27 +1402,15 @@ void DBaseStatusBar::DrawLog () if (CPlayer->LogText.IsNotEmpty()) { // This uses the same scaling as regular HUD messages - switch (con_scaletext) + if (con_uiscale() == 0) { - default: - hudwidth = SCREENWIDTH; - hudheight = SCREENHEIGHT; - break; - - case 1: hudwidth = SCREENWIDTH / CleanXfac; hudheight = SCREENHEIGHT / CleanYfac; - break; - - case 2: - hudwidth = SCREENWIDTH / 2; - hudheight = SCREENHEIGHT / 2; - break; - - case 3: - hudwidth = SCREENWIDTH / 4; - hudheight = SCREENHEIGHT / 4; - break; + } + else + { + hudwidth = SCREENWIDTH / con_uiscale(); + hudheight = SCREENHEIGHT / con_uiscale(); } int linelen = hudwidth<640? Scale(hudwidth,9,10)-40 : 560; diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 89023d4bd..dada0cd57 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -62,6 +62,14 @@ #include "colormatcher.h" #include "r_data/colormaps.h" +CUSTOM_CVAR(Int, uiscale, 2, CVAR_ARCHIVE | CVAR_NOINITCALL) +{ + if (StatusBar != NULL) + { + StatusBar->ScreenSizeChanged(); + } +} + // [RH] Stretch values to make a 320x200 image best fit the screen // without using fractional steppings int CleanXfac, CleanYfac; @@ -75,7 +83,7 @@ int CleanXfac_1, CleanYfac_1, CleanWidth_1, CleanHeight_1; // FillSimplePoly uses this extern "C" short spanend[MAXHEIGHT]; -CVAR (Bool, hud_scale, false, CVAR_ARCHIVE); +CVAR (Bool, hud_scale, true, CVAR_ARCHIVE); // For routines that take RGB colors, cache the previous lookup in case there // are several repetitions with the same color. diff --git a/src/v_video.h b/src/v_video.h index 7091c518d..4f0a78675 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -522,4 +522,6 @@ inline bool Is54Aspect(int ratio) { return ratio == 4; } +EXTERN_CVAR(Int, uiscale); + #endif // __V_VIDEO_H__ diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index ed224cb57..d337f0cbf 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1807,6 +1807,7 @@ DSPLYMNU_STILLBOB = "View bob amount while not moving"; HUDMNU_TITLE = "HUD Options"; HUDMNU_ALTHUD = "Alternative HUD"; HUDMNU_MESSAGE = "Message Options"; +HUDMNU_UISCALE = "User interface scale"; HUDMNU_CROSSHAIR = "Default Crosshair"; HUDMNU_FORCECROSSHAIR = "Force default crosshair"; HUDMNU_GROWCROSSHAIR = "Grow crosshair when picking up items"; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index ae8cfa0bb..f814b6709 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -755,6 +755,8 @@ OptionMenu "HUDOptions" Submenu "$HUDMNU_ALTHUD", "AltHudOptions" Submenu "$HUDMNU_MESSAGE", "MessageOptions" StaticText " " + Slider "$HUDMNU_UISCALE", "uiscale", 0.0, 8.0, 1.0, 0 + StaticText " " Option "$HUDMNU_CROSSHAIR", "crosshair", "Crosshairs" Option "$HUDMNU_FORCECROSSHAIR", "crosshairforce", "OnOff" Option "$HUDMNU_GROWCROSSHAIR", "crosshairgrow", "OnOff" @@ -789,6 +791,7 @@ OptionValue "AMCoordinates" OptionValue "AltHUDScale" { 0, "$OPTVAL_OFF" + 4, "$OPTVAL_ON" 1, "$OPTVAL_SCALETO640X400" 2, "$OPTVAL_PIXELDOUBLE" 3, "$OPTVAL_PIXELQUADRUPLE" From d2f8fc63fc5d6935fbaf89317cdf458a6bb54ea3 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Tue, 6 Sep 2016 21:35:57 +0200 Subject: [PATCH 35/44] Scale the console itself as specified by con_scaletext --- src/c_console.cpp | 109 +++++++++++++++++++++++++++++++++++++--------- src/v_video.cpp | 17 ++++++-- 2 files changed, 102 insertions(+), 24 deletions(-) diff --git a/src/c_console.cpp b/src/c_console.cpp index 026266359..bdb9fb559 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -846,9 +846,13 @@ void C_DrawConsole (bool hw2d) static int oldbottom = 0; int lines, left, offset; + int textScale = con_uiscale(); + if (textScale == 0) + textScale = CleanXfac; + left = LEFTMARGIN; - lines = (ConBottom-ConFont->GetHeight()*2)/ConFont->GetHeight(); - if (-ConFont->GetHeight() + lines*ConFont->GetHeight() > ConBottom - ConFont->GetHeight()*7/2) + lines = (ConBottom/textScale-ConFont->GetHeight()*2)/ConFont->GetHeight(); + if (-ConFont->GetHeight() + lines*ConFont->GetHeight() > ConBottom/textScale - ConFont->GetHeight()*7/2) { offset = -ConFont->GetHeight()/2; lines--; @@ -894,16 +898,26 @@ void C_DrawConsole (bool hw2d) if (ConBottom >= 12) { - screen->DrawText (ConFont, CR_ORANGE, SCREENWIDTH - 8 - - ConFont->StringWidth (GetVersionString()), - ConBottom - ConFont->GetHeight() - 4, - GetVersionString(), TAG_DONE); + if (textScale == 1) + screen->DrawText (ConFont, CR_ORANGE, SCREENWIDTH - 8 - + ConFont->StringWidth (GetVersionString()), + ConBottom / textScale - ConFont->GetHeight() - 4, + GetVersionString(), TAG_DONE); + else + screen->DrawText(ConFont, CR_ORANGE, SCREENWIDTH / textScale - 8 - + ConFont->StringWidth(GetVersionString()), + ConBottom / textScale - ConFont->GetHeight() - 4, + GetVersionString(), + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); + if (TickerMax) { char tickstr[256]; - const int tickerY = ConBottom - ConFont->GetHeight() - 4; + const int tickerY = ConBottom / textScale - ConFont->GetHeight() - 4; size_t i; - int tickend = ConCols - SCREENWIDTH / 90 - 6; + int tickend = ConCols / textScale - SCREENWIDTH / textScale / 90 - 6; int tickbegin = 0; if (TickerLabel) @@ -926,11 +940,23 @@ void C_DrawConsole (bool hw2d) { tickstr[tickend+3] = 0; } - screen->DrawText (ConFont, CR_BROWN, LEFTMARGIN, tickerY, tickstr, TAG_DONE); + if (textScale == 1) + screen->DrawText (ConFont, CR_BROWN, LEFTMARGIN, tickerY, tickstr, TAG_DONE); + else + screen->DrawText (ConFont, CR_BROWN, LEFTMARGIN, tickerY, tickstr, + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); // Draw the marker i = LEFTMARGIN+5+tickbegin*8 + Scale (TickerAt, (SDWORD)(tickend - tickbegin)*8, TickerMax); - screen->DrawChar (ConFont, CR_ORANGE, (int)i, tickerY, 0x13, TAG_DONE); + if (textScale == 1) + screen->DrawChar (ConFont, CR_ORANGE, (int)i, tickerY, 0x13, TAG_DONE); + else + screen->DrawChar(ConFont, CR_ORANGE, (int)i, tickerY, 0x13, + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); TickerVisible = true; } @@ -966,18 +992,28 @@ void C_DrawConsole (bool hw2d) if (lines > 0) { // No more enqueuing because adding new text to the console won't touch the actual print data. - conbuffer->FormatText(ConFont, ConWidth); + conbuffer->FormatText(ConFont, ConWidth / textScale); unsigned int consolelines = conbuffer->GetFormattedLineCount(); FBrokenLines **blines = conbuffer->GetLines(); FBrokenLines **printline = blines + consolelines - 1 - RowAdjust; - int bottomline = ConBottom - ConFont->GetHeight()*2 - 4; + int bottomline = ConBottom / textScale - ConFont->GetHeight()*2 - 4; ConsoleDrawing = true; for(FBrokenLines **p = printline; p >= blines && lines > 0; p--, lines--) { - screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), (*p)->Text, TAG_DONE); + if (textScale == 1) + { + screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), (*p)->Text, TAG_DONE); + } + else + { + screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), (*p)->Text, + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); + } } ConsoleDrawing = false; @@ -992,21 +1028,52 @@ void C_DrawConsole (bool hw2d) FString command((char *)&CmdLine[2+CmdLine[259]]); int cursorpos = CmdLine[1] - CmdLine[259]; - screen->DrawChar (ConFont, CR_ORANGE, left, bottomline, '\x1c', TAG_DONE); - screen->DrawText (ConFont, CR_ORANGE, left + ConFont->GetCharWidth(0x1c), bottomline, - command, TAG_DONE); - - if (cursoron) + if (textScale == 1) { - screen->DrawChar (ConFont, CR_YELLOW, left + ConFont->GetCharWidth(0x1c) + cursorpos * ConFont->GetCharWidth(0xb), - bottomline, '\xb', TAG_DONE); + screen->DrawChar(ConFont, CR_ORANGE, left, bottomline, '\x1c', TAG_DONE); + screen->DrawText(ConFont, CR_ORANGE, left + ConFont->GetCharWidth(0x1c), bottomline, + command, TAG_DONE); + + if (cursoron) + { + screen->DrawChar(ConFont, CR_YELLOW, left + ConFont->GetCharWidth(0x1c) + cursorpos * ConFont->GetCharWidth(0xb), + bottomline, '\xb', TAG_DONE); + } + } + else + { + screen->DrawChar(ConFont, CR_ORANGE, left, bottomline, '\x1c', + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); + + screen->DrawText(ConFont, CR_ORANGE, left + ConFont->GetCharWidth(0x1c), bottomline, + command, + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); + + if (cursoron) + { + screen->DrawChar(ConFont, CR_YELLOW, left + ConFont->GetCharWidth(0x1c) + cursorpos * ConFont->GetCharWidth(0xb), + bottomline, '\xb', + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); + } } } if (RowAdjust && ConBottom >= ConFont->GetHeight()*7/2) { // Indicate that the view has been scrolled up (10) // and if we can scroll no further (12) - screen->DrawChar (ConFont, CR_GREEN, 0, bottomline, RowAdjust == conbuffer->GetFormattedLineCount() ? 12 : 10, TAG_DONE); + if (textScale == 1) + screen->DrawChar (ConFont, CR_GREEN, 0, bottomline, RowAdjust == conbuffer->GetFormattedLineCount() ? 12 : 10, TAG_DONE); + else + screen->DrawChar(ConFont, CR_GREEN, 0, bottomline, RowAdjust == conbuffer->GetFormattedLineCount() ? 12 : 10, + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); } } } diff --git a/src/v_video.cpp b/src/v_video.cpp index 0ee065bbe..e1997d581 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -65,6 +65,7 @@ #include "menu/menu.h" #include "r_data/voxels.h" +int con_uiscale(); FRenderer *Renderer; @@ -857,10 +858,20 @@ void DFrameBuffer::DrawRateStuff () int chars; int rate_x; + int textScale = con_uiscale(); + if (textScale == 0) + textScale = CleanXfac; + chars = mysnprintf (fpsbuff, countof(fpsbuff), "%2u ms (%3u fps)", howlong, LastCount); - rate_x = Width - ConFont->StringWidth(&fpsbuff[0]); - Clear (rate_x, 0, Width, ConFont->GetHeight(), GPalette.BlackIndex, 0); - DrawText (ConFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0], TAG_DONE); + rate_x = Width / textScale - ConFont->StringWidth(&fpsbuff[0]); + Clear (rate_x * textScale, 0, Width, ConFont->GetHeight() * textScale, GPalette.BlackIndex, 0); + if (textScale == 1) + DrawText (ConFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0], TAG_DONE); + else + DrawText (ConFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0], + DTA_VirtualWidth, screen->GetWidth() / textScale, + DTA_VirtualHeight, screen->GetHeight() / textScale, + DTA_KeepRatio, true, TAG_DONE); DWORD thisSec = ms/1000; if (LastSec < thisSec) From e794e59cd2798e8fbb36dce3dcad4a18b72dd3bd Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 7 Sep 2016 11:34:49 +0200 Subject: [PATCH 36/44] Add con_scale for scaling just the console --- src/c_console.cpp | 39 ++++++++++++++++++++++++------------ src/ct_chat.cpp | 18 ++++++++--------- src/g_shared/hudmessages.cpp | 38 +++++++++++++++++------------------ src/g_shared/shared_hud.cpp | 8 ++++---- src/g_shared/shared_sbar.cpp | 20 +++++++++--------- src/v_video.cpp | 4 ++-- wadsrc/static/language.enu | 2 ++ wadsrc/static/menudef.txt | 10 +++++++++ 8 files changed, 82 insertions(+), 57 deletions(-) diff --git a/src/c_console.cpp b/src/c_console.cpp index bdb9fb559..08f850675 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -165,7 +165,20 @@ CUSTOM_CVAR (Int, con_scaletext, 1, CVAR_ARCHIVE) // Scale notify text at high if (self > 3) self = 3; } -int con_uiscale() +CUSTOM_CVAR(Int, con_scale, 0, CVAR_ARCHIVE) +{ + if (self < 0) self = 0; +} + +int active_con_scale() +{ + if (con_scale == 0) + return uiscale; + else + return con_scale; +} + +int active_con_scaletext() { switch (con_scaletext) { @@ -505,13 +518,13 @@ void C_AddNotifyString (int printlevel, const char *source) return; } - if (con_uiscale() == 0) + if (active_con_scaletext() == 0) { width = DisplayWidth / CleanXfac; } else { - width = DisplayWidth / con_uiscale(); + width = DisplayWidth / active_con_scaletext(); } if (addtype == APPENDLINE && NotifyStrings[NUMNOTIFIES-1].PrintLevel == printlevel) @@ -733,7 +746,7 @@ static void C_DrawNotifyText () canskip = true; lineadv = SmallFont->GetHeight (); - if (con_uiscale() == 0) + if (active_con_scaletext() == 0) { lineadv *= CleanYfac; } @@ -767,7 +780,7 @@ static void C_DrawNotifyText () else color = PrintColors[NotifyStrings[i].PrintLevel]; - if (con_uiscale() == 0) + if (active_con_scaletext() == 0) { if (!center) screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text, @@ -778,7 +791,7 @@ static void C_DrawNotifyText () line, NotifyStrings[i].Text, DTA_CleanNoMove, true, DTA_AlphaF, alpha, TAG_DONE); } - else if (con_uiscale() == 1) + else if (active_con_scaletext() == 1) { if (!center) screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text, @@ -793,16 +806,16 @@ static void C_DrawNotifyText () { if (!center) screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text, - DTA_VirtualWidth, screen->GetWidth() / con_uiscale(), - DTA_VirtualHeight, screen->GetHeight() / con_uiscale(), + DTA_VirtualWidth, screen->GetWidth() / active_con_scaletext(), + DTA_VirtualHeight, screen->GetHeight() / active_con_scaletext(), DTA_KeepRatio, true, DTA_AlphaF, alpha, TAG_DONE); else - screen->DrawText (SmallFont, color, (screen->GetWidth() / con_uiscale() - - SmallFont->StringWidth (NotifyStrings[i].Text))/ con_uiscale(), + screen->DrawText (SmallFont, color, (screen->GetWidth() / active_con_scaletext() - + SmallFont->StringWidth (NotifyStrings[i].Text))/ active_con_scaletext(), line, NotifyStrings[i].Text, - DTA_VirtualWidth, screen->GetWidth() / con_uiscale(), - DTA_VirtualHeight, screen->GetHeight() / con_uiscale(), + DTA_VirtualWidth, screen->GetWidth() / active_con_scaletext(), + DTA_VirtualHeight, screen->GetHeight() / active_con_scaletext(), DTA_KeepRatio, true, DTA_AlphaF, alpha, TAG_DONE); } @@ -846,7 +859,7 @@ void C_DrawConsole (bool hw2d) static int oldbottom = 0; int lines, left, offset; - int textScale = con_uiscale(); + int textScale = active_con_scale(); if (textScale == 0) textScale = CleanXfac; diff --git a/src/ct_chat.cpp b/src/ct_chat.cpp index 7fd36d0ab..3c0994a3b 100644 --- a/src/ct_chat.cpp +++ b/src/ct_chat.cpp @@ -46,7 +46,7 @@ EXTERN_CVAR (Bool, sb_cooperative_enable) EXTERN_CVAR (Bool, sb_deathmatch_enable) EXTERN_CVAR (Bool, sb_teamdeathmatch_enable) -int con_uiscale(); +int active_con_scaletext(); // Public data @@ -226,7 +226,7 @@ void CT_Drawer (void) int i, x, scalex, y, promptwidth; y = (viewactive || gamestate != GS_LEVEL) ? -10 : -30; - if (con_uiscale() == 0) + if (active_con_scaletext() == 0) { scalex = CleanXfac; y *= CleanYfac; @@ -237,7 +237,7 @@ void CT_Drawer (void) } int screen_width, screen_height, st_y; - if (con_uiscale() == 0) + if (active_con_scaletext() == 0) { screen_width = SCREENWIDTH; screen_height = SCREENHEIGHT; @@ -245,9 +245,9 @@ void CT_Drawer (void) } else { - screen_width = SCREENWIDTH / con_uiscale(); - screen_height = SCREENHEIGHT / con_uiscale(); - st_y = ST_Y / con_uiscale(); + screen_width = SCREENWIDTH / active_con_scaletext(); + screen_height = SCREENHEIGHT / active_con_scaletext(); + st_y = ST_Y / active_con_scaletext(); } y += ((SCREENHEIGHT == viewheight && viewactive) || gamestate != GS_LEVEL) ? screen_height : st_y; @@ -274,10 +274,10 @@ void CT_Drawer (void) // draw the prompt, text, and cursor ChatQueue[len] = SmallFont->GetCursor(); ChatQueue[len+1] = '\0'; - if (con_uiscale() < 2) + if (active_con_scaletext() < 2) { - screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, DTA_CleanNoMove, con_uiscale() == 0, TAG_DONE); - screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), DTA_CleanNoMove, con_uiscale() == 0, TAG_DONE); + screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, DTA_CleanNoMove, active_con_scaletext() == 0, TAG_DONE); + screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), DTA_CleanNoMove, active_con_scaletext() == 0, TAG_DONE); } else { diff --git a/src/g_shared/hudmessages.cpp b/src/g_shared/hudmessages.cpp index 628c8a5ee..32a8d2de5 100644 --- a/src/g_shared/hudmessages.cpp +++ b/src/g_shared/hudmessages.cpp @@ -42,7 +42,7 @@ #include "farchive.h" EXTERN_CVAR(Int, con_scaletext) -int con_uiscale(); +int active_con_scaletext(); IMPLEMENT_POINTY_CLASS (DHUDMessage) DECLARE_POINTER(Next) @@ -261,10 +261,10 @@ void DHUDMessage::ResetText (const char *text) } else { - switch (con_uiscale()) + switch (active_con_scaletext()) { case 0: width = SCREENWIDTH / CleanXfac; break; - default: width = SCREENWIDTH / con_uiscale(); break; + default: width = SCREENWIDTH / active_con_scaletext(); break; } } @@ -330,7 +330,7 @@ void DHUDMessage::Draw (int bottom, int visibility) int screen_width = SCREENWIDTH; int screen_height = SCREENHEIGHT; - if (HUDWidth == 0 && con_uiscale() == 0) + if (HUDWidth == 0 && active_con_scaletext() == 0) { clean = true; xscale = CleanXfac; @@ -341,9 +341,9 @@ void DHUDMessage::Draw (int bottom, int visibility) xscale = yscale = 1; if (HUDWidth == 0) { - screen_width /= con_uiscale(); - screen_height /= con_uiscale(); - bottom /= con_uiscale(); + screen_width /= active_con_scaletext(); + screen_height /= active_con_scaletext(); + bottom /= active_con_scaletext(); } } @@ -445,7 +445,7 @@ void DHUDMessage::DoDraw (int linenum, int x, int y, bool clean, int hudheight) { if (hudheight == 0) { - if (con_uiscale() <= 1) + if (active_con_scaletext() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -456,8 +456,8 @@ void DHUDMessage::DoDraw (int linenum, int x, int y, bool clean, int hudheight) else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), - DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), + DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(), + DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(), DTA_AlphaF, Alpha, DTA_RenderStyle, Style, DTA_KeepRatio, true, @@ -548,7 +548,7 @@ void DHUDMessageFadeOut::DoDraw (int linenum, int x, int y, bool clean, int hudh float trans = float(Alpha * -(Tics - FadeOutTics) / FadeOutTics); if (hudheight == 0) { - if (con_uiscale() <= 1) + if (active_con_scaletext() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -559,8 +559,8 @@ void DHUDMessageFadeOut::DoDraw (int linenum, int x, int y, bool clean, int hudh else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), - DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), + DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(), + DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(), DTA_AlphaF, trans, DTA_RenderStyle, Style, DTA_KeepRatio, true, @@ -648,7 +648,7 @@ void DHUDMessageFadeInOut::DoDraw (int linenum, int x, int y, bool clean, int hu float trans = float(Alpha * Tics / FadeInTics); if (hudheight == 0) { - if (con_uiscale() <= 1) + if (active_con_scaletext() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -659,8 +659,8 @@ void DHUDMessageFadeInOut::DoDraw (int linenum, int x, int y, bool clean, int hu else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), - DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), + DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(), + DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(), DTA_AlphaF, trans, DTA_RenderStyle, Style, DTA_KeepRatio, true, @@ -826,7 +826,7 @@ void DHUDMessageTypeOnFadeOut::DoDraw (int linenum, int x, int y, bool clean, in { if (hudheight == 0) { - if (con_uiscale() <= 1) + if (active_con_scaletext() <= 1) { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, DTA_CleanNoMove, clean, @@ -838,8 +838,8 @@ void DHUDMessageTypeOnFadeOut::DoDraw (int linenum, int x, int y, bool clean, in else { screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text, - DTA_VirtualWidth, SCREENWIDTH / con_uiscale(), - DTA_VirtualHeight, SCREENHEIGHT / con_uiscale(), + DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(), + DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(), DTA_KeepRatio, true, DTA_TextLen, LineVisible, DTA_AlphaF, Alpha, diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp index 26504cb7d..25a88cb35 100644 --- a/src/g_shared/shared_hud.cpp +++ b/src/g_shared/shared_hud.cpp @@ -118,7 +118,7 @@ static int hudwidth, hudheight; // current width/height for HUD display static int statspace; DVector2 AM_GetPosition(); -int con_uiscale(); +int active_con_scaletext(); FTextureID GetHUDIcon(PClassInventory *cls) { @@ -886,15 +886,15 @@ static void DrawCoordinates(player_t * CPlayer) } int vwidth, vheight; - if (con_uiscale() == 0) + if (active_con_scaletext() == 0) { vwidth = SCREENWIDTH / 2; vheight = SCREENHEIGHT / 2; } else { - vwidth = SCREENWIDTH / con_uiscale(); - vheight = SCREENHEIGHT / con_uiscale(); + vwidth = SCREENWIDTH / active_con_scaletext(); + vheight = SCREENHEIGHT / active_con_scaletext(); } int xpos = vwidth - SmallFont->StringWidth("X: -00000")-6; diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index 6f495581f..4631a99aa 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -74,7 +74,7 @@ EXTERN_CVAR (Bool, am_showtotaltime) EXTERN_CVAR (Bool, noisedebug) EXTERN_CVAR (Int, con_scaletext) -int con_uiscale(); +int active_con_scaletext(); DBaseStatusBar *StatusBar; @@ -1242,17 +1242,17 @@ void DBaseStatusBar::Draw (EHudState state) int xpos; int y; - if (con_uiscale() == 1) + if (active_con_scaletext() == 1) { vwidth = SCREENWIDTH; vheight = SCREENHEIGHT; xpos = vwidth - 80; y = ::ST_Y - height; } - else if (con_uiscale() > 1) + else if (active_con_scaletext() > 1) { - vwidth = SCREENWIDTH / con_uiscale(); - vheight = SCREENHEIGHT / con_uiscale(); + vwidth = SCREENWIDTH / active_con_scaletext(); + vheight = SCREENHEIGHT / active_con_scaletext(); xpos = vwidth - SmallFont->StringWidth("X: -00000")-6; y = ::ST_Y/4 - height; } @@ -1266,9 +1266,9 @@ void DBaseStatusBar::Draw (EHudState state) if (gameinfo.gametype == GAME_Strife) { - if (con_uiscale() == 1) + if (active_con_scaletext() == 1) y -= height * 4; - else if (con_uiscale() > 3) + else if (active_con_scaletext() > 3) y -= height; else y -= height * 2; @@ -1402,15 +1402,15 @@ void DBaseStatusBar::DrawLog () if (CPlayer->LogText.IsNotEmpty()) { // This uses the same scaling as regular HUD messages - if (con_uiscale() == 0) + if (active_con_scaletext() == 0) { hudwidth = SCREENWIDTH / CleanXfac; hudheight = SCREENHEIGHT / CleanYfac; } else { - hudwidth = SCREENWIDTH / con_uiscale(); - hudheight = SCREENHEIGHT / con_uiscale(); + hudwidth = SCREENWIDTH / active_con_scaletext(); + hudheight = SCREENHEIGHT / active_con_scaletext(); } int linelen = hudwidth<640? Scale(hudwidth,9,10)-40 : 560; diff --git a/src/v_video.cpp b/src/v_video.cpp index e1997d581..b9917a1cb 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -65,7 +65,7 @@ #include "menu/menu.h" #include "r_data/voxels.h" -int con_uiscale(); +int active_con_scale(); FRenderer *Renderer; @@ -858,7 +858,7 @@ void DFrameBuffer::DrawRateStuff () int chars; int rate_x; - int textScale = con_uiscale(); + int textScale = active_con_scale(); if (textScale == 0) textScale = CleanXfac; diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index d337f0cbf..e91c9dd43 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1947,6 +1947,7 @@ MSGMNU_SHOWMESSAGES = "Show messages"; MSGMNU_SHOWOBITUARIES = "Show obituaries"; MSGMNU_SHOWSECRETS = "Show secret notifications"; MSGMNU_SCALETEXT = "Scale text in high res"; +MSGMNU_SCALECONSOLE = "Scale console"; MSGMNU_MESSAGELEVEL = "Minimum message level"; MSGMNU_CENTERMESSAGES = "Center messages"; MSGMNU_MESSAGECOLORS = "Message Colors"; @@ -2238,6 +2239,7 @@ OPTVAL_ANIMATED = "Animated"; OPTVAL_ROTATED = "Rotated"; OPTVAL_MAPDEFINEDCOLORSONLY = "Map defined colors only"; OPTVAL_DOUBLE = "Double"; +OPTVAL_TRIPLE = "Triple"; OPTVAL_QUADRUPLE = "Quadruple"; OPTVAL_ITEMPICKUP = "Item Pickup"; OPTVAL_OBITUARIES = "Obituaries"; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index f814b6709..a605b5cc4 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1114,6 +1114,15 @@ OptionValue ScaleValues 3, "$OPTVAL_QUADRUPLE" } +OptionValue ConsoleScaleValues +{ + 1, "$OPTVAL_OFF" + 0, "$OPTVAL_ON" + 2, "$OPTVAL_DOUBLE" + 3, "$OPTVAL_TRIPLE" + 4, "$OPTVAL_QUADRUPLE" +} + OptionValue MessageLevels { 0.0, "$OPTVAL_ITEMPICKUP" @@ -1137,6 +1146,7 @@ OptionMenu MessageOptions Option "$MSGMNU_SHOWOBITUARIES", "show_obituaries", "OnOff" Option "$MSGMNU_SHOWSECRETS", "cl_showsecretmessage", "OnOff" Option "$MSGMNU_SCALETEXT", "con_scaletext", "ScaleValues" + Option "$MSGMNU_SCALECONSOLE", "con_scale", "ConsoleScaleValues" Option "$MSGMNU_MESSAGELEVEL", "msg", "MessageLevels" Option "$MSGMNU_DEVELOPER", "developer", "DevMessageLevels" Option "$MSGMNU_CENTERMESSAGES", "con_centernotify", "OnOff" From 5a64307ad12253d5f4a492bf5201d2552d68b6a9 Mon Sep 17 00:00:00 2001 From: raa-eruanna Date: Wed, 7 Sep 2016 03:34:08 -0400 Subject: [PATCH 37/44] Changes the tonemap generation algorithm. --- src/gl/renderer/gl_postprocess.cpp | 39 +++++++++++++++++++++++++++++- src/gl/renderer/gl_renderer.h | 2 ++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index e6ca7faad..d7eac8007 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -257,7 +257,7 @@ void FGLRenderer::BindTonemapPalette(int texunit) { for (int b = 0; b < 64; b++) { - PalEntry color = GPalette.BaseColors[ColorMatcher.Pick((r << 2) | (r >> 4), (g << 2) | (g >> 4), (b << 2) | (b >> 4))]; + PalEntry color = GPalette.BaseColors[(BYTE)PTM_BestColor((uint32 *)GPalette.BaseColors, (r << 2) | (r >> 4), (g << 2) | (g >> 4), (b << 2) | (b >> 4), 0, 256)]; int index = ((r * 64 + g) * 64 + b) * 4; lut[index] = color.r; lut[index + 1] = color.g; @@ -472,3 +472,40 @@ void FGLRenderer::ClearBorders() } glDisable(GL_SCISSOR_TEST); } + + +// [SP] Re-implemented BestColor for more precision rather than speed. This function is only ever called once until the game palette is changed. + +int FGLRenderer::PTM_BestColor (const uint32 *pal_in, int r, int g, int b, int first, int num) +{ + const PalEntry *pal = (const PalEntry *)pal_in; + static double powtable[256]; + static bool firstTime = true; + + double fbestdist, fdist; + int bestcolor; + + if (firstTime) + { + firstTime = false; + for (int x = 0; x < 256; x++) powtable[x] = pow((double)x/255,1.2); + } + + for (int color = first; color < num; color++) + { + double x = powtable[abs(r-pal[color].r)]; + double y = powtable[abs(g-pal[color].g)]; + double z = powtable[abs(b-pal[color].b)]; + fdist = x + y + z; + if (color == first || fdist < fbestdist) + { + if (fdist == 0) + return color; + + fbestdist = fdist; + bestcolor = color; + } + } + return bestcolor; +} + diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index 120f6a449..f652b4765 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -189,6 +189,8 @@ public: void FillSimplePoly(FTexture *texture, FVector2 *points, int npoints, double originx, double originy, double scalex, double scaley, DAngle rotation, FDynamicColormap *colormap, int lightlevel); + + int PTM_BestColor (const uint32 *pal_in, int r, int g, int b, int first, int num); }; // Global functions. Make them members of GLRenderer later? From b9ca3c85f8cde4b145a82029307728577a07aed6 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 8 Sep 2016 12:18:09 +0200 Subject: [PATCH 38/44] - fixed: translucency detection in multipatch textures did not work. - fixed: The flat drawer never checked if a texture had translucent parts. --- src/gl/scene/gl_flats.cpp | 19 ++++++++++++++++++- src/textures/bitmap.h | 2 +- src/textures/multipatchtexture.cpp | 5 +++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/gl/scene/gl_flats.cpp b/src/gl/scene/gl_flats.cpp index 9e9a73e5d..48ad30786 100644 --- a/src/gl/scene/gl_flats.cpp +++ b/src/gl/scene/gl_flats.cpp @@ -419,15 +419,17 @@ void GLFlat::Draw(int pass, bool trans) // trans only has meaning for GLPASS_LIG if (renderstyle==STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE); gl_SetColor(lightlevel, rel, Colormap, alpha); gl_SetFog(lightlevel, rel, &Colormap, false); - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold); if (!gltexture) { + gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); gl_RenderState.EnableTexture(false); DrawSubsectors(pass, false, true); gl_RenderState.EnableTexture(true); } else { + if (!gltexture->GetTransparent()) gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold); + else gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); gl_RenderState.SetMaterial(gltexture, CLAMP_NONE, 0, -1, false); gl_SetPlaneTextureRotation(&plane, gltexture); DrawSubsectors(pass, !gl.legacyMode, true); @@ -477,6 +479,21 @@ inline void GLFlat::PutFlat(bool fog) // translucent 3D floors go into the regular translucent list, translucent portals go into the translucent border list. list = (renderflags&SSRF_RENDER3DPLANES) ? GLDL_TRANSLUCENT : GLDL_TRANSLUCENTBORDER; } + else if (gltexture->GetTransparent()) + { + if (stack) + { + list = GLDL_TRANSLUCENTBORDER; + } + else if ((renderflags&SSRF_RENDER3DPLANES) && !plane.plane.isSlope()) + { + list = GLDL_TRANSLUCENT; + } + else + { + list = GLDL_PLAINFLATS; + } + } else { bool masked = gltexture->isMasked() && ((renderflags&SSRF_RENDER3DPLANES) || stack); diff --git a/src/textures/bitmap.h b/src/textures/bitmap.h index 27d3c0b4b..a27d1ff98 100644 --- a/src/textures/bitmap.h +++ b/src/textures/bitmap.h @@ -380,7 +380,7 @@ struct bCopyAlpha }; struct bOverlay -{ +{ static __forceinline void OpC(BYTE &d, BYTE s, BYTE a, FCopyInfo *i) { d = (s*a + d*(255-a))/255; } static __forceinline void OpA(BYTE &d, BYTE s, FCopyInfo *i) { d = MAX(s,d); } static __forceinline bool ProcessAlpha0() { return false; } diff --git a/src/textures/multipatchtexture.cpp b/src/textures/multipatchtexture.cpp index b0db481a8..41ba5f0f2 100644 --- a/src/textures/multipatchtexture.cpp +++ b/src/textures/multipatchtexture.cpp @@ -628,8 +628,9 @@ int FMultiPatchTexture::CopyTrueColorPixels(FBitmap *bmp, int x, int y, int rota { ret = Parts[i].Texture->CopyTrueColorPixels(bmp, x+Parts[i].OriginX, y+Parts[i].OriginY, Parts[i].Rotate, &info); } - - if (ret > retv) retv = ret; + // treat -1 (i.e. unknown) as absolute. We have no idea if this may have overwritten previous info so a real check needs to be done. + if (ret == -1) retv = ret; + else if (retv != -1 && ret > retv) retv = ret; } // Restore previous clipping rectangle. bmp->SetClipRect(saved_cr); From 4bdd872f097d7998718daf9fb11a56ca518ae85e Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 7 Sep 2016 20:31:42 +0200 Subject: [PATCH 39/44] Add eye textures and FBOs to FGLRenderBuffers --- src/gl/renderer/gl_renderbuffers.cpp | 81 ++++++++++++++++++++++++++++ src/gl/renderer/gl_renderbuffers.h | 10 ++++ 2 files changed, 91 insertions(+) diff --git a/src/gl/renderer/gl_renderbuffers.cpp b/src/gl/renderer/gl_renderbuffers.cpp index f66ea1354..a7a07a43c 100644 --- a/src/gl/renderer/gl_renderbuffers.cpp +++ b/src/gl/renderer/gl_renderbuffers.cpp @@ -85,6 +85,7 @@ FGLRenderBuffers::~FGLRenderBuffers() { ClearScene(); ClearPipeline(); + ClearEyeBuffers(); ClearBloom(); } @@ -119,6 +120,18 @@ void FGLRenderBuffers::ClearBloom() } } +void FGLRenderBuffers::ClearEyeBuffers() +{ + for (auto handle : mEyeTextures) + DeleteTexture(handle); + + for (auto handle : mEyeFBs) + DeleteFrameBuffer(handle); + + mEyeTextures.Clear(); + mEyeFBs.Clear(); +} + void FGLRenderBuffers::DeleteTexture(GLuint &handle) { if (handle != 0) @@ -202,6 +215,7 @@ bool FGLRenderBuffers::Setup(int width, int height, int sceneWidth, int sceneHei { ClearScene(); ClearPipeline(); + ClearEyeBuffers(); ClearBloom(); mWidth = 0; mHeight = 0; @@ -239,6 +253,7 @@ void FGLRenderBuffers::CreateScene(int width, int height, int samples) void FGLRenderBuffers::CreatePipeline(int width, int height) { ClearPipeline(); + ClearEyeBuffers(); for (int i = 0; i < NumPipelineTextures; i++) { @@ -279,6 +294,35 @@ void FGLRenderBuffers::CreateBloom(int width, int height) } } +//========================================================================== +// +// Creates eye buffers if needed +// +//========================================================================== + +void FGLRenderBuffers::CreateEyeBuffers(int eye) +{ + if (mEyeFBs.Size() > eye) + return; + + GLint activeTex, textureBinding, frameBufferBinding; + glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTex); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &frameBufferBinding); + + while (mEyeFBs.Size() < eye) + { + GLuint texture = Create2DTexture("EyeTexture", GL_RGBA16F, mWidth, mHeight); + mEyeTextures.Push(texture); + mEyeFBs.Push(CreateFrameBuffer("EyeFB", texture)); + } + + glBindTexture(GL_TEXTURE_2D, textureBinding); + glActiveTexture(activeTex); + glBindFramebuffer(GL_FRAMEBUFFER, frameBufferBinding); +} + //========================================================================== // // Creates a 2D texture defaulting to linear filtering and clamp to edge @@ -471,6 +515,43 @@ void FGLRenderBuffers::BlitSceneToTexture() glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } +//========================================================================== +// +// Eye textures and their frame buffers +// +//========================================================================== + +void FGLRenderBuffers::BlitToEyeTexture(int eye) +{ + CreateEyeBuffers(eye); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, mPipelineFB[mCurrentPipelineTexture]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mEyeFBs[eye]); + glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + if ((gl.flags & RFL_INVALIDATE_BUFFER) != 0) + { + GLenum attachments[2] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_STENCIL_ATTACHMENT }; + glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 2, attachments); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void FGLRenderBuffers::BindEyeTexture(int eye, int texunit) +{ + CreateEyeBuffers(eye); + glActiveTexture(GL_TEXTURE0 + texunit); + glBindTexture(GL_TEXTURE_2D, mEyeTextures[eye]); +} + +void FGLRenderBuffers::BindEyeFB(int eye) +{ + CreateEyeBuffers(eye); + glBindFramebuffer(GL_FRAMEBUFFER, mEyeFBs[eye]); +} + //========================================================================== // // Makes the scene frame buffer active (multisample, depth, stecil, etc.) diff --git a/src/gl/renderer/gl_renderbuffers.h b/src/gl/renderer/gl_renderbuffers.h index 08303a912..e5600fde4 100644 --- a/src/gl/renderer/gl_renderbuffers.h +++ b/src/gl/renderer/gl_renderbuffers.h @@ -32,6 +32,10 @@ public: void BindOutputFB(); + void BlitToEyeTexture(int eye); + void BindEyeTexture(int eye, int texunit); + void BindEyeFB(int eye); + enum { NumBloomLevels = 4 }; FGLBloomTextureLevel BloomLevels[NumBloomLevels]; @@ -43,10 +47,12 @@ public: private: void ClearScene(); void ClearPipeline(); + void ClearEyeBuffers(); void ClearBloom(); void CreateScene(int width, int height, int samples); void CreatePipeline(int width, int height); void CreateBloom(int width, int height); + void CreateEyeBuffers(int eye); GLuint Create2DTexture(const FString &name, GLuint format, int width, int height); GLuint CreateRenderBuffer(const FString &name, GLuint format, int width, int height); GLuint CreateRenderBuffer(const FString &name, GLuint format, int samples, int width, int height); @@ -83,6 +89,10 @@ private: // Back buffer frame buffer GLuint mOutputFB = 0; + // Eye buffers + TArray mEyeTextures; + TArray mEyeFBs; + static bool FailedCreate; static bool BuffersActive; }; From aaa3581ee698c6d6859cff551327f099e471c817 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 7 Sep 2016 21:52:43 +0200 Subject: [PATCH 40/44] Hook up eye textures in renderer --- src/gl/renderer/gl_postprocess.cpp | 33 ++++++++++++++++++++++++++++ src/gl/renderer/gl_renderbuffers.cpp | 12 +++++----- src/gl/renderer/gl_renderbuffers.h | 2 +- src/gl/renderer/gl_renderer.h | 2 +- src/gl/scene/gl_scene.cpp | 2 ++ src/gl/stereo3d/gl_stereo3d.cpp | 23 +++++++++++++++++++ src/gl/stereo3d/gl_stereo3d.h | 5 +++++ 7 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index d7eac8007..243a73a38 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -75,6 +75,7 @@ #include "gl/shaders/gl_lensshader.h" #include "gl/shaders/gl_presentshader.h" #include "gl/renderer/gl_2ddrawer.h" +#include "gl/stereo3d/gl_stereo3d.h" //========================================================================== // @@ -375,6 +376,38 @@ void FGLRenderer::LensDistortScene() FGLDebug::PopGroup(); } +//----------------------------------------------------------------------------- +// +// Copies the rendered screen to its final destination +// +//----------------------------------------------------------------------------- + +void FGLRenderer::Flush() +{ + const s3d::Stereo3DMode& stereo3dMode = s3d::Stereo3DMode::getCurrentMode(); + + if (stereo3dMode.IsMono() || !FGLRenderBuffers::IsEnabled()) + { + CopyToBackbuffer(nullptr, true); + } + else + { + // Render 2D to eye textures + for (int eye_ix = 0; eye_ix < stereo3dMode.eye_count(); ++eye_ix) + { + FGLDebug::PushGroup("Eye2D"); + mBuffers->BindEyeFB(eye_ix); + m2DDrawer->Flush(); // Draw the 2D + FGLDebug::PopGroup(); + } + + FGLPostProcessState savedState; + FGLDebug::PushGroup("PresentEyes"); + stereo3dMode.Present(); + FGLDebug::PopGroup(); + } +} + //----------------------------------------------------------------------------- // // Gamma correct while copying to frame buffer diff --git a/src/gl/renderer/gl_renderbuffers.cpp b/src/gl/renderer/gl_renderbuffers.cpp index a7a07a43c..7ad299e2e 100644 --- a/src/gl/renderer/gl_renderbuffers.cpp +++ b/src/gl/renderer/gl_renderbuffers.cpp @@ -122,12 +122,12 @@ void FGLRenderBuffers::ClearBloom() void FGLRenderBuffers::ClearEyeBuffers() { - for (auto handle : mEyeTextures) - DeleteTexture(handle); - for (auto handle : mEyeFBs) DeleteFrameBuffer(handle); + for (auto handle : mEyeTextures) + DeleteTexture(handle); + mEyeTextures.Clear(); mEyeFBs.Clear(); } @@ -311,7 +311,7 @@ void FGLRenderBuffers::CreateEyeBuffers(int eye) glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding); glGetIntegerv(GL_FRAMEBUFFER_BINDING, &frameBufferBinding); - while (mEyeFBs.Size() < eye) + while (mEyeFBs.Size() <= eye) { GLuint texture = Create2DTexture("EyeTexture", GL_RGBA16F, mWidth, mHeight); mEyeTextures.Push(texture); @@ -546,10 +546,10 @@ void FGLRenderBuffers::BindEyeTexture(int eye, int texunit) glBindTexture(GL_TEXTURE_2D, mEyeTextures[eye]); } -void FGLRenderBuffers::BindEyeFB(int eye) +void FGLRenderBuffers::BindEyeFB(int eye, bool readBuffer) { CreateEyeBuffers(eye); - glBindFramebuffer(GL_FRAMEBUFFER, mEyeFBs[eye]); + glBindFramebuffer(readBuffer ? GL_READ_FRAMEBUFFER : GL_FRAMEBUFFER, mEyeFBs[eye]); } //========================================================================== diff --git a/src/gl/renderer/gl_renderbuffers.h b/src/gl/renderer/gl_renderbuffers.h index e5600fde4..08731e39f 100644 --- a/src/gl/renderer/gl_renderbuffers.h +++ b/src/gl/renderer/gl_renderbuffers.h @@ -34,7 +34,7 @@ public: void BlitToEyeTexture(int eye); void BindEyeTexture(int eye, int texunit); - void BindEyeFB(int eye); + void BindEyeFB(int eye, bool readBuffer = false); enum { NumBloomLevels = 4 }; FGLBloomTextureLevel BloomLevels[NumBloomLevels]; diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index f652b4765..d768728c4 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -173,7 +173,7 @@ public: void ClearTonemapPalette(); void LensDistortScene(); void CopyToBackbuffer(const GL_IRECT *bounds, bool applyGamma); - void Flush() { CopyToBackbuffer(nullptr, true); } + void Flush(); void SetProjection(float fov, float ratio, float fovratio); void SetProjection(VSMatrix matrix); // raw matrix input from stereo 3d modes diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 48d4fb7ff..5fca06dad 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -863,6 +863,8 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo DrawBlend(lviewsector); } mDrawingScene2D = false; + if (!stereo3dMode.IsMono()) + mBuffers->BlitToEyeTexture(eye_ix); eye->TearDown(); } stereo3dMode.TearDown(); diff --git a/src/gl/stereo3d/gl_stereo3d.cpp b/src/gl/stereo3d/gl_stereo3d.cpp index d686a4a43..e1c4f6306 100644 --- a/src/gl/stereo3d/gl_stereo3d.cpp +++ b/src/gl/stereo3d/gl_stereo3d.cpp @@ -36,6 +36,7 @@ #include "gl/system/gl_system.h" #include "gl/stereo3d/gl_stereo3d.h" #include "gl/renderer/gl_renderer.h" +#include "gl/renderer/gl_renderbuffers.h" #include "vectors.h" // RAD2DEG #include "doomtype.h" // M_PI @@ -79,6 +80,28 @@ Stereo3DMode::~Stereo3DMode() { } +void Stereo3DMode::Present() const +{ + // Example copying eye textures to the back buffer: + + // The letterbox for the output destination. + // If stereo modes needs different dimensions then that needs to be added to FGLRenderer::SetOutputViewport. + const auto &box = GLRenderer->mOutputLetterbox; + + GLRenderer->mBuffers->BindOutputFB(); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + for (int eye_ix = 0; eye_ix < eye_count(); ++eye_ix) + { + GLRenderer->mBuffers->BindEyeFB(eye_ix, true); + int width = GLRenderer->mBuffers->GetWidth(); + int height = GLRenderer->mBuffers->GetHeight(); + glBlitFramebuffer(0, 0, width, height, box.left + eye_ix * box.width / eye_count(), box.top, box.left + (eye_ix + 1) * box.width / eye_count(), box.height, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + // Avoid static initialization order fiasco by declaring first Mode type (Mono) here in the // same source file as Stereo3DMode::getCurrentMode() // https://isocpp.org/wiki/faq/ctors#static-init-order diff --git a/src/gl/stereo3d/gl_stereo3d.h b/src/gl/stereo3d/gl_stereo3d.h index c56cc078f..3cac6b79d 100644 --- a/src/gl/stereo3d/gl_stereo3d.h +++ b/src/gl/stereo3d/gl_stereo3d.h @@ -85,6 +85,9 @@ public: virtual void SetUp() const {}; virtual void TearDown() const {}; + virtual bool IsMono() const { return false; } + virtual void Present() const; + protected: TArray eye_ptrs; @@ -102,6 +105,8 @@ class MonoView : public Stereo3DMode public: static const MonoView& getInstance(); + bool IsMono() const override { return true; } + protected: MonoView() { eye_ptrs.Push(¢ralEye); } EyePose centralEye; From ccbe5160b0b6546a9c8fcdb4cf02267603de4056 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 8 Sep 2016 09:18:10 +0200 Subject: [PATCH 41/44] Split F2DDrawer::Flush into Draw and Clear --- src/gl/renderer/gl_2ddrawer.cpp | 10 ++++++---- src/gl/renderer/gl_2ddrawer.h | 3 ++- src/gl/renderer/gl_postprocess.cpp | 7 +++++-- src/gl/system/gl_wipe.cpp | 3 ++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/gl/renderer/gl_2ddrawer.cpp b/src/gl/renderer/gl_2ddrawer.cpp index ac7d35397..e0f67c713 100644 --- a/src/gl/renderer/gl_2ddrawer.cpp +++ b/src/gl/renderer/gl_2ddrawer.cpp @@ -378,7 +378,7 @@ void F2DDrawer::AddPixel(int x1, int y1, int palcolor, uint32 color) // //========================================================================== -void F2DDrawer::Flush() +void F2DDrawer::Draw() { F2DDrawer::EDrawType lasttype = DrawTypeTexture; @@ -490,10 +490,12 @@ void F2DDrawer::Flush() } i += dg->mLen; } - mVertices.Clear(); - mData.Clear(); gl_RenderState.SetVertexBuffer(GLRenderer->mVBO); glset.lightmode = savedlightmode; } - +void F2DDrawer::Clear() +{ + mVertices.Clear(); + mData.Clear(); +} diff --git a/src/gl/renderer/gl_2ddrawer.h b/src/gl/renderer/gl_2ddrawer.h index 32d2ad1f5..8c27f13d8 100644 --- a/src/gl/renderer/gl_2ddrawer.h +++ b/src/gl/renderer/gl_2ddrawer.h @@ -66,7 +66,8 @@ public: void AddLine(int x1, int y1, int x2, int y2, int palcolor, uint32 color); void AddPixel(int x1, int y1, int palcolor, uint32 color); - void Flush(); + void Draw(); + void Clear(); }; diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index 243a73a38..4bacf54d4 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -397,9 +397,10 @@ void FGLRenderer::Flush() { FGLDebug::PushGroup("Eye2D"); mBuffers->BindEyeFB(eye_ix); - m2DDrawer->Flush(); // Draw the 2D + m2DDrawer->Draw(); FGLDebug::PopGroup(); } + m2DDrawer->Clear(); FGLPostProcessState savedState; FGLDebug::PushGroup("PresentEyes"); @@ -416,7 +417,9 @@ void FGLRenderer::Flush() void FGLRenderer::CopyToBackbuffer(const GL_IRECT *bounds, bool applyGamma) { - m2DDrawer->Flush(); // draw all pending 2D stuff before copying the buffer + m2DDrawer->Draw(); // draw all pending 2D stuff before copying the buffer + m2DDrawer->Clear(); + FGLDebug::PushGroup("CopyToBackbuffer"); if (FGLRenderBuffers::IsEnabled()) { diff --git a/src/gl/system/gl_wipe.cpp b/src/gl/system/gl_wipe.cpp index 05d63f65d..577bc12db 100644 --- a/src/gl/system/gl_wipe.cpp +++ b/src/gl/system/gl_wipe.cpp @@ -189,7 +189,8 @@ bool OpenGLFrameBuffer::WipeStartScreen(int type) void OpenGLFrameBuffer::WipeEndScreen() { - GLRenderer->m2DDrawer->Flush(); + GLRenderer->m2DDrawer->Draw(); + GLRenderer->m2DDrawer->Clear(); const auto &viewport = GLRenderer->mScreenViewport; wipeendscreen = new FHardwareTexture(viewport.width, viewport.height, true); From 8b7267cf879d80515f1f63a90fcd4f7f4bdbec09 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 8 Sep 2016 19:02:13 +0200 Subject: [PATCH 42/44] MaskAnaglyph present mode --- src/gl/renderer/gl_postprocess.cpp | 50 +++++++++++++++++------------- src/gl/renderer/gl_renderer.h | 1 + src/gl/stereo3d/gl_anaglyph.cpp | 21 +++++++++++++ src/gl/stereo3d/gl_anaglyph.h | 21 +++---------- 4 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/gl/renderer/gl_postprocess.cpp b/src/gl/renderer/gl_postprocess.cpp index 4bacf54d4..00083480c 100644 --- a/src/gl/renderer/gl_postprocess.cpp +++ b/src/gl/renderer/gl_postprocess.cpp @@ -397,6 +397,8 @@ void FGLRenderer::Flush() { FGLDebug::PushGroup("Eye2D"); mBuffers->BindEyeFB(eye_ix); + glViewport(mScreenViewport.left, mScreenViewport.top, mScreenViewport.width, mScreenViewport.height); + glScissor(mScreenViewport.left, mScreenViewport.top, mScreenViewport.width, mScreenViewport.height); m2DDrawer->Draw(); FGLDebug::PopGroup(); } @@ -437,28 +439,8 @@ void FGLRenderer::CopyToBackbuffer(const GL_IRECT *bounds, bool applyGamma) box = mOutputLetterbox; } - // Present what was rendered: - glViewport(box.left, box.top, box.width, box.height); - - mPresentShader->Bind(); - mPresentShader->InputTexture.Set(0); - if (!applyGamma || framebuffer->IsHWGammaActive()) - { - mPresentShader->InvGamma.Set(1.0f); - mPresentShader->Contrast.Set(1.0f); - mPresentShader->Brightness.Set(0.0f); - } - else - { - mPresentShader->InvGamma.Set(1.0f / clamp(Gamma, 0.1f, 4.f)); - mPresentShader->Contrast.Set(clamp(vid_contrast, 0.1f, 3.f)); - mPresentShader->Brightness.Set(clamp(vid_brightness, -0.8f, 0.8f)); - } - mPresentShader->Scale.Set(mScreenViewport.width / (float)mBuffers->GetWidth(), mScreenViewport.height / (float)mBuffers->GetHeight()); mBuffers->BindCurrentTexture(0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - RenderScreenQuad(); + DrawPresentTexture(box, applyGamma); } else if (!bounds) { @@ -468,6 +450,32 @@ void FGLRenderer::CopyToBackbuffer(const GL_IRECT *bounds, bool applyGamma) FGLDebug::PopGroup(); } +void FGLRenderer::DrawPresentTexture(const GL_IRECT &box, bool applyGamma) +{ + glViewport(box.left, box.top, box.width, box.height); + + glActiveTexture(GL_TEXTURE0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + mPresentShader->Bind(); + mPresentShader->InputTexture.Set(0); + if (!applyGamma || framebuffer->IsHWGammaActive()) + { + mPresentShader->InvGamma.Set(1.0f); + mPresentShader->Contrast.Set(1.0f); + mPresentShader->Brightness.Set(0.0f); + } + else + { + mPresentShader->InvGamma.Set(1.0f / clamp(Gamma, 0.1f, 4.f)); + mPresentShader->Contrast.Set(clamp(vid_contrast, 0.1f, 3.f)); + mPresentShader->Brightness.Set(clamp(vid_brightness, -0.8f, 0.8f)); + } + mPresentShader->Scale.Set(mScreenViewport.width / (float)mBuffers->GetWidth(), mScreenViewport.height / (float)mBuffers->GetHeight()); + RenderScreenQuad(); +} + //----------------------------------------------------------------------------- // // Fills the black bars around the screen letterbox diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index d768728c4..162fb6dcf 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -173,6 +173,7 @@ public: void ClearTonemapPalette(); void LensDistortScene(); void CopyToBackbuffer(const GL_IRECT *bounds, bool applyGamma); + void DrawPresentTexture(const GL_IRECT &box, bool applyGamma); void Flush(); void SetProjection(float fov, float ratio, float fovratio); diff --git a/src/gl/stereo3d/gl_anaglyph.cpp b/src/gl/stereo3d/gl_anaglyph.cpp index c84c35091..3c051d390 100644 --- a/src/gl/stereo3d/gl_anaglyph.cpp +++ b/src/gl/stereo3d/gl_anaglyph.cpp @@ -34,6 +34,8 @@ */ #include "gl_anaglyph.h" +#include "gl/renderer/gl_renderer.h" +#include "gl/renderer/gl_renderbuffers.h" namespace s3d { @@ -44,6 +46,25 @@ MaskAnaglyph::MaskAnaglyph(const ColorMask& leftColorMask, double ipdMeters) eye_ptrs.Push(&rightEye); } +void MaskAnaglyph::Present() const +{ + GLRenderer->mBuffers->BindOutputFB(); + GLRenderer->ClearBorders(); + + gl_RenderState.SetColorMask(leftEye.GetColorMask().r, leftEye.GetColorMask().g, leftEye.GetColorMask().b, true); + gl_RenderState.ApplyColorMask(); + GLRenderer->mBuffers->BindEyeTexture(0, 0); + GLRenderer->DrawPresentTexture(GLRenderer->mOutputLetterbox, true); + + gl_RenderState.SetColorMask(rightEye.GetColorMask().r, rightEye.GetColorMask().g, rightEye.GetColorMask().b, true); + gl_RenderState.ApplyColorMask(); + GLRenderer->mBuffers->BindEyeTexture(1, 0); + GLRenderer->DrawPresentTexture(GLRenderer->mOutputLetterbox, true); + + gl_RenderState.ResetColorMask(); + gl_RenderState.ApplyColorMask(); +} + /* static */ const GreenMagenta& GreenMagenta::getInstance(float ipd) diff --git a/src/gl/stereo3d/gl_anaglyph.h b/src/gl/stereo3d/gl_anaglyph.h index a82ce0f3b..e3383fe26 100644 --- a/src/gl/stereo3d/gl_anaglyph.h +++ b/src/gl/stereo3d/gl_anaglyph.h @@ -61,14 +61,8 @@ class AnaglyphLeftPose : public LeftEyePose { public: AnaglyphLeftPose(const ColorMask& colorMask, float ipd) : LeftEyePose(ipd), colorMask(colorMask) {} - virtual void SetUp() const { - gl_RenderState.SetColorMask(colorMask.r, colorMask.g, colorMask.b, true); - gl_RenderState.ApplyColorMask(); - } - virtual void TearDown() const { - gl_RenderState.ResetColorMask(); - gl_RenderState.ApplyColorMask(); - } + ColorMask GetColorMask() const { return colorMask; } + private: ColorMask colorMask; }; @@ -77,14 +71,8 @@ class AnaglyphRightPose : public RightEyePose { public: AnaglyphRightPose(const ColorMask& colorMask, float ipd) : RightEyePose(ipd), colorMask(colorMask) {} - virtual void SetUp() const { - gl_RenderState.SetColorMask(colorMask.r, colorMask.g, colorMask.b, true); - gl_RenderState.ApplyColorMask(); - } - virtual void TearDown() const { - gl_RenderState.ResetColorMask(); - gl_RenderState.ApplyColorMask(); - } + ColorMask GetColorMask() const { return colorMask; } + private: ColorMask colorMask; }; @@ -93,6 +81,7 @@ class MaskAnaglyph : public Stereo3DMode { public: MaskAnaglyph(const ColorMask& leftColorMask, double ipdMeters); + void Present() const override; private: AnaglyphLeftPose leftEye; AnaglyphRightPose rightEye; From 63dc3949139586fe5b7d71dc0800ff2adb01c4af Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 8 Sep 2016 21:15:00 +0200 Subject: [PATCH 43/44] Present function for left, right and quad-buffered stereo3d --- src/gl/stereo3d/gl_quadstereo.cpp | 31 ++++++++++++++++++++++++- src/gl/stereo3d/gl_quadstereo.h | 18 ++------------ src/gl/stereo3d/gl_stereo3d.cpp | 23 ------------------ src/gl/stereo3d/gl_stereo3d.h | 3 ++- src/gl/stereo3d/gl_stereo_leftright.cpp | 18 ++++++++++++++ src/gl/stereo3d/gl_stereo_leftright.h | 2 ++ 6 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/gl/stereo3d/gl_quadstereo.cpp b/src/gl/stereo3d/gl_quadstereo.cpp index 63bbbedc1..cd689d3b5 100644 --- a/src/gl/stereo3d/gl_quadstereo.cpp +++ b/src/gl/stereo3d/gl_quadstereo.cpp @@ -34,6 +34,8 @@ */ #include "gl_quadstereo.h" +#include "gl/renderer/gl_renderer.h" +#include "gl/renderer/gl_renderbuffers.h" namespace s3d { @@ -46,7 +48,7 @@ QuadStereo::QuadStereo(double ipdMeters) GLboolean supportsStereo, supportsBuffered; glGetBooleanv(GL_STEREO, &supportsStereo); glGetBooleanv(GL_DOUBLEBUFFER, &supportsBuffered); - bool bQuadStereoSupported = supportsStereo && supportsBuffered; + bQuadStereoSupported = supportsStereo && supportsBuffered; leftEye.bQuadStereoSupported = bQuadStereoSupported; rightEye.bQuadStereoSupported = bQuadStereoSupported; @@ -57,6 +59,33 @@ QuadStereo::QuadStereo(double ipdMeters) } } +void QuadStereo::Present() const +{ + if (bQuadStereoSupported) + { + GLRenderer->mBuffers->BindOutputFB(); + + glDrawBuffer(GL_BACK_LEFT); + GLRenderer->ClearBorders(); + GLRenderer->mBuffers->BindEyeTexture(0, 0); + GLRenderer->DrawPresentTexture(GLRenderer->mOutputLetterbox, true); + + glDrawBuffer(GL_BACK_RIGHT); + GLRenderer->ClearBorders(); + GLRenderer->mBuffers->BindEyeTexture(1, 0); + GLRenderer->DrawPresentTexture(GLRenderer->mOutputLetterbox, true); + + glDrawBuffer(GL_BACK); + } + else + { + GLRenderer->mBuffers->BindOutputFB(); + GLRenderer->ClearBorders(); + GLRenderer->mBuffers->BindEyeTexture(1, 0); + GLRenderer->DrawPresentTexture(GLRenderer->mOutputLetterbox, true); + } +} + /* static */ const QuadStereo& QuadStereo::getInstance(float ipd) { diff --git a/src/gl/stereo3d/gl_quadstereo.h b/src/gl/stereo3d/gl_quadstereo.h index f27fb78f8..d9aa4f435 100644 --- a/src/gl/stereo3d/gl_quadstereo.h +++ b/src/gl/stereo3d/gl_quadstereo.h @@ -47,14 +47,6 @@ class QuadStereoLeftPose : public LeftEyePose { public: QuadStereoLeftPose(float ipd) : LeftEyePose(ipd), bQuadStereoSupported(false) {} - virtual void SetUp() const { - if (bQuadStereoSupported) - glDrawBuffer(GL_BACK_LEFT); - } - virtual void TearDown() const { - if (bQuadStereoSupported) - glDrawBuffer(GL_BACK); - } bool bQuadStereoSupported; }; @@ -62,14 +54,6 @@ class QuadStereoRightPose : public RightEyePose { public: QuadStereoRightPose(float ipd) : RightEyePose(ipd), bQuadStereoSupported(false){} - virtual void SetUp() const { - if (bQuadStereoSupported) - glDrawBuffer(GL_BACK_RIGHT); - } - virtual void TearDown() const { - if (bQuadStereoSupported) - glDrawBuffer(GL_BACK); - } bool bQuadStereoSupported; }; @@ -84,10 +68,12 @@ class QuadStereo : public Stereo3DMode { public: QuadStereo(double ipdMeters); + void Present() const override; static const QuadStereo& getInstance(float ipd); private: QuadStereoLeftPose leftEye; QuadStereoRightPose rightEye; + bool bQuadStereoSupported; }; diff --git a/src/gl/stereo3d/gl_stereo3d.cpp b/src/gl/stereo3d/gl_stereo3d.cpp index e1c4f6306..d686a4a43 100644 --- a/src/gl/stereo3d/gl_stereo3d.cpp +++ b/src/gl/stereo3d/gl_stereo3d.cpp @@ -36,7 +36,6 @@ #include "gl/system/gl_system.h" #include "gl/stereo3d/gl_stereo3d.h" #include "gl/renderer/gl_renderer.h" -#include "gl/renderer/gl_renderbuffers.h" #include "vectors.h" // RAD2DEG #include "doomtype.h" // M_PI @@ -80,28 +79,6 @@ Stereo3DMode::~Stereo3DMode() { } -void Stereo3DMode::Present() const -{ - // Example copying eye textures to the back buffer: - - // The letterbox for the output destination. - // If stereo modes needs different dimensions then that needs to be added to FGLRenderer::SetOutputViewport. - const auto &box = GLRenderer->mOutputLetterbox; - - GLRenderer->mBuffers->BindOutputFB(); - glClearColor(0.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); - for (int eye_ix = 0; eye_ix < eye_count(); ++eye_ix) - { - GLRenderer->mBuffers->BindEyeFB(eye_ix, true); - int width = GLRenderer->mBuffers->GetWidth(); - int height = GLRenderer->mBuffers->GetHeight(); - glBlitFramebuffer(0, 0, width, height, box.left + eye_ix * box.width / eye_count(), box.top, box.left + (eye_ix + 1) * box.width / eye_count(), box.height, GL_COLOR_BUFFER_BIT, GL_LINEAR); - } - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); -} - // Avoid static initialization order fiasco by declaring first Mode type (Mono) here in the // same source file as Stereo3DMode::getCurrentMode() // https://isocpp.org/wiki/faq/ctors#static-init-order diff --git a/src/gl/stereo3d/gl_stereo3d.h b/src/gl/stereo3d/gl_stereo3d.h index 3cac6b79d..2cb9417d2 100644 --- a/src/gl/stereo3d/gl_stereo3d.h +++ b/src/gl/stereo3d/gl_stereo3d.h @@ -86,7 +86,7 @@ public: virtual void TearDown() const {}; virtual bool IsMono() const { return false; } - virtual void Present() const; + virtual void Present() const = 0; protected: TArray eye_ptrs; @@ -106,6 +106,7 @@ public: static const MonoView& getInstance(); bool IsMono() const override { return true; } + void Present() const override { } protected: MonoView() { eye_ptrs.Push(¢ralEye); } diff --git a/src/gl/stereo3d/gl_stereo_leftright.cpp b/src/gl/stereo3d/gl_stereo_leftright.cpp index 354185792..8605c9b53 100644 --- a/src/gl/stereo3d/gl_stereo_leftright.cpp +++ b/src/gl/stereo3d/gl_stereo_leftright.cpp @@ -37,7 +37,10 @@ #include "vectors.h" // RAD2DEG #include "doomtype.h" // M_PI #include "gl/system/gl_cvars.h" +#include "gl/system/gl_system.h" +#include "gl/renderer/gl_renderstate.h" #include "gl/renderer/gl_renderer.h" +#include "gl/renderer/gl_renderbuffers.h" #include EXTERN_CVAR(Float, vr_screendist) @@ -89,6 +92,13 @@ const LeftEyeView& LeftEyeView::getInstance(float ipd) return instance; } +void LeftEyeView::Present() const +{ + GLRenderer->mBuffers->BindOutputFB(); + GLRenderer->ClearBorders(); + GLRenderer->mBuffers->BindEyeTexture(0, 0); + GLRenderer->DrawPresentTexture(GLRenderer->mOutputLetterbox, true); +} /* static */ const RightEyeView& RightEyeView::getInstance(float ipd) @@ -98,5 +108,13 @@ const RightEyeView& RightEyeView::getInstance(float ipd) return instance; } +void RightEyeView::Present() const +{ + GLRenderer->mBuffers->BindOutputFB(); + GLRenderer->ClearBorders(); + GLRenderer->mBuffers->BindEyeTexture(0, 0); + GLRenderer->DrawPresentTexture(GLRenderer->mOutputLetterbox, true); +} + } /* namespace s3d */ diff --git a/src/gl/stereo3d/gl_stereo_leftright.h b/src/gl/stereo3d/gl_stereo_leftright.h index de13a32cb..250846651 100644 --- a/src/gl/stereo3d/gl_stereo_leftright.h +++ b/src/gl/stereo3d/gl_stereo_leftright.h @@ -83,6 +83,7 @@ public: LeftEyeView(float ipd) : eye(ipd) { eye_ptrs.Push(&eye); } float getIpd() const { return eye.getIpd(); } void setIpd(float ipd) { eye.setIpd(ipd); } + void Present() const override; protected: LeftEyePose eye; }; @@ -96,6 +97,7 @@ public: RightEyeView(float ipd) : eye(ipd) { eye_ptrs.Push(&eye); } float getIpd() const { return eye.getIpd(); } void setIpd(float ipd) { eye.setIpd(ipd); } + void Present() const override; protected: RightEyePose eye; }; From d83a72f361bd4df365caa2c7e14173afe34dee94 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 8 Sep 2016 21:24:25 +0200 Subject: [PATCH 44/44] Add missing IsEnabled check --- src/gl/scene/gl_scene.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 5fca06dad..26e226554 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -863,7 +863,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo DrawBlend(lviewsector); } mDrawingScene2D = false; - if (!stereo3dMode.IsMono()) + if (!stereo3dMode.IsMono() && FGLRenderBuffers::IsEnabled()) mBuffers->BlitToEyeTexture(eye_ix); eye->TearDown(); }