diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000000..d3c7a341b05ff --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,152 @@ +# This is a basic workflow that is manually triggered + +name: Build GroovyMAME on a new tag + +# Only build when a tag looks like gm0???sr???? +# It will, in the end, converted to the GM version + SR version +# eg: gm0223sr017q +on: + push: + tags: + - gm0*sr* + +jobs: +# GM build jobs : on linux/mingw (corssbuild for windows), on windows/msys2 and linux +# mingw-windows-build: + # Cross ompilation job, runs on a Ubuntu Linux runner and builds windows binaries + # Disabled for now because of https://github.com/mamedev/mame/issues/7240 + # Take care if you enable this job again, others must have changed since +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - name: Setup environment +# run: | +# sudo apt update +# sudo apt-get install mingw-w64 wine64 p7zip +# sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix +# - name: Build GroovyMAME +# run: | +# make -j$(nproc) TARGET=mame TOOLS=1 SEPARATE_BIN=1 PTR64=1 OPTIMIZE=3 SYMBOLS=0 SYMLEVEL=1 REGENIE=1 TARGETOS=windows OVERRIDE_CC=x86_64-w64-mingw32-gcc OVERRIDE_CXX=x86_64-w64-mingw32-g++ OVERRIDE_LD=x86_64-w64-mingw32-ld MINGW64=/usr +# make -f dist.mak PTR64=1 TARGETOS=windows +# - name: Create MAME basic configuration +# run: | +# cd build/release/x64/Release/mame +# wine64 mame64.exe -createconfig +# - name: Create Release Asset +# run: | +# cd build/release/x64/Release/mame +# 7z a groovymame-mingw-win32-64bits.7z * +# - name: Upload artifact +# uses: actions/upload-artifact@v2 +# with: +# name: binaries-win32-mingw +# path: build/release/x64/Release/mame/groovymame-mingw-win32-64bits.7z + + msys-windows-build: + # Windows build in a MSYS2 environment on a Windows runner + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + steps: + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + update: true + install: bash git make wget mingw-w64-x86_64-gcc mingw-w64-x86_64-python p7zip + - name: Setup MSYS2 for MAME + run: | + wget https://repo.mamedev.org/x86_64/mame-essentials-1.0.6-1-x86_64.pkg.tar.xz + pacman -U --noconfirm mame-essentials-1.0.6-1-x86_64.pkg.tar.xz + echo -e "[mame]\nInclude = /etc/pacman.d/mirrorlist.mame" >> /etc/pacman.conf + pacman -Sy + - uses: actions/checkout@v2 + - name: Build GroovyMAME + run: | + export MINGW64=/mingw64 + export MINGW32= + make -j$(nproc) TARGET=mame TOOLS=1 SEPARATE_BIN=1 PTR64=1 OPTIMIZE=3 SYMBOLS=0 SYMLEVEL=1 REGENIE=1 + make -f dist.mak PTR64=1 + cp -rf artwork bgfx hlsl plugins samples build/release/x64/Release/mame/ + - name: Create MAME basic configuration + run: | + cd build/release/x64/Release/mame + ./mame.exe -createconfig + - name: Create Release Asset + run: | + tag="${GITHUB_REF#refs/*/}" + # Now tag should be in the shape of gm0XXXsrYYYY, ex: gm0223sr017q + mamev="${tag:3:3}" + srv="${tag: -4}" + cd build/release/x64/Release + mv mame "groovymame_0${mamev}.${srv}_win-7-8-10" + 7z a "groovymame_0${mamev}.${srv}_win-7-8-10.7z" "groovymame_0${mamev}.${srv}_win-7-8-10" + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: binaries-win32-msys + path: build/release/x64/Release/groovymame*.7z + + linux-build: + # Simple linux build on ubuntu + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup environment + run: | + sudo apt update + sudo apt-get install git build-essential python libsdl2-dev libsdl2-ttf-dev libfontconfig-dev libdrm-dev qt5-default p7zip + - name: Build GroovyMAME + run: | + make -j$(nproc) TARGET=mame TOOLS=1 SEPARATE_BIN=1 PTR64=1 OPTIMIZE=3 SYMBOLS=0 SYMLEVEL=1 REGENIE=1 + make -f dist.mak PTR64=1 + - name: Create Release Asset + run: | + tag="${GITHUB_REF#refs/*/}" + # Now tag should be in the shape of gm0XXXsrYYYY, ex: gm0223sr017q + mamev="${tag:3:3}" + srv="${tag: -4}" + cd build/release/x64/Release/mame + mv mame groovymame + tar cvjf "groovymame_0${mamev}.${srv}_linux.tar.bz2" groovymame + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: binaries-linux-gcc + path: build/release/x64/Release/mame/groovymame*.tar.bz2 + + release: + runs-on: ubuntu-latest + #needs: [mingw-windows-build, msys-windows-build, linux-build] + needs: [msys-windows-build, linux-build] + steps: + - name: Prepare data + id: prepare_data + run: | + tag="${GITHUB_REF#refs/*/}" + # Now tag should be in the shape of gm0XXXsrYYYY, ex: gm0223sr017q + mamev="${tag:3:3}" + srv="${tag: -4}" + echo "GroovyMAME: 0.$mamev" + echo "Switchres : 2.$srv" + echo "::set-output name=mame_version::${mamev}" + echo "::set-output name=switchres_version::${srv}" + echo "::set-output name=current_tag::${tag}" + - name: Download Artifacts + uses: actions/download-artifact@v2.0.5 + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MAME_VERSION: ${{ steps.prepare_data.outputs.mame_version }} + SWITCHRES_VERSION: ${{ steps.prepare_data.outputs.switchres_version }} + CURRENT_TAG: ${{ steps.prepare_data.outputs.current_tag }} + with: + name: GroovyMAME 0.${{ steps.prepare_data.outputs.mame_version }} - Switchres 2.${{ steps.prepare_data.outputs.switchres_version }} + draft: true + prerelease: false + files: | + ./binaries-win32-msys/groovymame_*.7z + ./binaries-linux-gcc/groovymame_*.tar.bz2 +# ./binaries-win32-mingw/groovymame-mingw-win32-64bits.7z diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml deleted file mode 100644 index 6922871bcd3b7..0000000000000 --- a/.github/workflows/ci-linux.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: CI (Linux) - -on: [push, pull_request] - -jobs: - build-linux: - runs-on: ubuntu-latest - strategy: - matrix: - compiler: [gcc, clang] - include: - - compiler: gcc - cc: gcc - cxx: g++ - archopts: -U_FORTIFY_SOURCE - - compiler: clang - cc: clang - cxx: clang++ - steps: - - uses: actions/checkout@master - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y libsdl2-dev libsdl2-ttf-dev libasound2-dev libxinerama-dev libxi-dev qt5-default - - name: Install clang - if: matrix.compiler == 'clang' - run: sudo apt-get install -y clang - - name: Build - env: - OVERRIDE_CC: ${{ matrix.cc }} - OVERRIDE_CXX: ${{ matrix.cxx }} - ARCHOPTS: ${{ matrix.archopts }} - TOOLS: 1 - run: make -j2 - - name: Validate - run: ./mame -validate - - name: ORM check - run: python scripts/minimaws/minimaws.py load --executable ./mame --softwarepath hash - - uses: actions/upload-artifact@master - with: - name: mame-linux-${{ matrix.compiler }}-${{ github.sha }} - path: mame diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml deleted file mode 100644 index cf23ddf3c3ae4..0000000000000 --- a/.github/workflows/ci-macos.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: CI (macOS) - -on: [push, pull_request] - -jobs: - build-macos: - runs-on: macOS-latest - steps: - - uses: actions/checkout@master - - name: Install dependencies - run: brew install sdl2 - - name: Build - env: - USE_LIBSDL: 1 - TOOLS: 1 - run: make -j2 - - name: Validate - run: ./mame -validate - - uses: actions/upload-artifact@master - with: - name: mame-macos-${{ github.sha }} - path: mame diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml deleted file mode 100644 index c227c0d6f09b9..0000000000000 --- a/.github/workflows/ci-windows.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: CI (Windows) - -on: [push, pull_request] - -jobs: - - build-windows-gcc: - runs-on: windows-latest - defaults: - run: - shell: msys2 {0} - steps: - - uses: msys2/setup-msys2@v2 - with: - install: git make mingw-w64-x86_64-gcc mingw-w64-x86_64-python mingw-w64-x86_64-lld mingw-w64-x86_64-libc++ - - uses: actions/checkout@master - - name: Build - env: - MINGW64: "/mingw64" - ARCHOPTS: "-fuse-ld=lld" - TOOLS: 1 - run: make -j2 - - name: Validate - run: ./mame -validate - - uses: actions/upload-artifact@master - with: - name: mame-windows-gcc-${{ github.sha }} - path: mame.exe diff --git a/.gitignore b/.gitignore index 4cd57d9f2bfed..d50c475316d62 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ regtests/chdman/temp regtests/jedutil/output /CMakeLists.txt /samples +/plugins/inputlag + diff --git a/3rdparty/switchres/custom_video.cpp b/3rdparty/switchres/custom_video.cpp new file mode 100644 index 0000000000000..888a55709346d --- /dev/null +++ b/3rdparty/switchres/custom_video.cpp @@ -0,0 +1,170 @@ +/************************************************************** + + custom_video.cpp - Custom video library + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + + + +#include +#include "custom_video.h" +#include "log.h" + +#if defined(_WIN32) +#include "custom_video_ati.h" +#include "custom_video_adl.h" +#include "custom_video_pstrip.h" +#elif defined(__linux__) +#include "custom_video_xrandr.h" +#include "custom_video_drmkms.h" +#endif + + +extern bool ati_is_legacy(int vendor, int device); + +//============================================================ +// custom_video::make +//============================================================ + +custom_video *custom_video::make(char *device_name, char *device_id, int method, custom_video_settings *vs) +{ +#if defined(_WIN32) + if (method == CUSTOM_VIDEO_TIMING_POWERSTRIP) + { + m_custom_video = new pstrip_timing(device_name, vs); + if (m_custom_video) + { + m_custom_method = CUSTOM_VIDEO_TIMING_POWERSTRIP; + return m_custom_video; + } + } + else + { + int vendor, device; + sscanf(device_id, "PCI\\VEN_%x&DEV_%x", &vendor, &device); + + if (vendor == 0x1002) // ATI/AMD + { + if (ati_is_legacy(vendor, device)) + { + m_custom_video = new ati_timing(device_name, vs); + if (m_custom_video) + { + m_custom_method = CUSTOM_VIDEO_TIMING_ATI_LEGACY; + return m_custom_video; + } + } + else + { + m_custom_video = new adl_timing(device_name, vs); + if (m_custom_video) + { + m_custom_method = CUSTOM_VIDEO_TIMING_ATI_ADL; + return m_custom_video; + } + } + } + else + log_info("Video chipset is not compatible.\n"); + } +#elif defined(__linux__) + if (device_id != NULL) + log_info("Device value is %s.\n", device_id); + + if (method == CUSTOM_VIDEO_TIMING_XRANDR || method == 0) + { + try + { + m_custom_video = new xrandr_timing(device_name, vs); + } + catch (...) {}; + if (m_custom_video) + { + m_custom_method = CUSTOM_VIDEO_TIMING_XRANDR; + return m_custom_video; + } + } + + if (method == CUSTOM_VIDEO_TIMING_DRMKMS || method == 0) + { + m_custom_video = new drmkms_timing(device_name, vs); + if (m_custom_video) + { + m_custom_method = CUSTOM_VIDEO_TIMING_DRMKMS; + return m_custom_video; + } + } +#endif + + return this; +} + +//============================================================ +// custom_video::init +//============================================================ + +bool custom_video::init() { return false; } + +//============================================================ +// custom_video::get_timing +//============================================================ + +bool custom_video::get_timing(modeline *mode) +{ + log_verbose("system mode\n"); + mode->type |= CUSTOM_VIDEO_TIMING_SYSTEM; + return false; +} + +//============================================================ +// custom_video::set_timing +//============================================================ + +bool custom_video::set_timing(modeline *) +{ + return false; +} + +//============================================================ +// custom_video::add_mode +//============================================================ + +bool custom_video::add_mode(modeline *) +{ + return false; +} + +//============================================================ +// custom_video::delete_mode +//============================================================ + +bool custom_video::delete_mode(modeline *) +{ + return false; +} + +//============================================================ +// custom_video::update_mode +//============================================================ + +bool custom_video::update_mode(modeline *) +{ + return false; +} + +//============================================================ +// custom_video::process_modelist +//============================================================ + +bool custom_video::process_modelist(std::vector) +{ + return false; +} diff --git a/3rdparty/switchres/custom_video.h b/3rdparty/switchres/custom_video.h new file mode 100644 index 0000000000000..5218224cebd01 --- /dev/null +++ b/3rdparty/switchres/custom_video.h @@ -0,0 +1,107 @@ +/************************************************************** + + custom_video.h - Custom video library header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __CUSTOM_VIDEO__ +#define __CUSTOM_VIDEO__ + +#include +#include +#include "modeline.h" + +#define CUSTOM_VIDEO_TIMING_MASK 0x00000ff0 +#define CUSTOM_VIDEO_TIMING_AUTO 0x00000000 +#define CUSTOM_VIDEO_TIMING_SYSTEM 0x00000010 +#define CUSTOM_VIDEO_TIMING_XRANDR 0x00000020 +#define CUSTOM_VIDEO_TIMING_POWERSTRIP 0x00000040 +#define CUSTOM_VIDEO_TIMING_ATI_LEGACY 0x00000080 +#define CUSTOM_VIDEO_TIMING_ATI_ADL 0x00000100 +#define CUSTOM_VIDEO_TIMING_DRMKMS 0x00000200 + +// Custom video caps +#define CUSTOM_VIDEO_CAPS_UPDATE 0x001 +#define CUSTOM_VIDEO_CAPS_ADD 0x002 +#define CUSTOM_VIDEO_CAPS_DESKTOP_EDITABLE 0x004 +#define CUSTOM_VIDEO_CAPS_SCAN_EDITABLE 0x008 + +// Timing creation commands +#define TIMING_DELETE 0x001 +#define TIMING_CREATE 0x002 +#define TIMING_UPDATE 0x004 +#define TIMING_UPDATE_LIST 0x008 + +typedef struct custom_video_settings +{ + bool screen_compositing; + bool screen_reordering; + bool allow_hardware_refresh; + char device_reg_key[128]; + char custom_timing[256]; +} custom_video_settings; + +class custom_video +{ +public: + + custom_video() {}; + virtual ~custom_video() + { + if (m_custom_video) + { + delete m_custom_video; + m_custom_video = nullptr; + } + } + + custom_video *make(char *device_name, char *device_id, int method, custom_video_settings *vs); + virtual const char *api_name() { return "empty"; } + virtual bool init(); + virtual int caps() { return 0; } + + virtual bool add_mode(modeline *mode); + virtual bool delete_mode(modeline *mode); + virtual bool update_mode(modeline *mode); + + virtual bool get_timing(modeline *mode); + virtual bool set_timing(modeline *mode); + + virtual bool process_modelist(std::vector); + + // getters + bool screen_compositing() { return m_vs.screen_compositing; } + bool screen_reordering() { return m_vs.screen_reordering; } + bool allow_hardware_refresh() { return m_vs.allow_hardware_refresh; } + const char *custom_timing() { return (const char*) &m_vs.custom_timing; } + + // setters + void set_screen_compositing(bool value) { m_vs.screen_compositing = value; } + void set_screen_reordering(bool value) { m_vs.screen_reordering = value; } + void set_allow_hardware_refresh(bool value) { m_vs.allow_hardware_refresh = value; } + void set_custom_timing(const char *custom_timing) { strncpy(m_vs.custom_timing, custom_timing, sizeof(m_vs.custom_timing)-1); } + + // options + custom_video_settings m_vs = {}; + + modeline m_user_mode = {}; + modeline m_backup_mode = {}; + +private: + char m_device_name[32]; + char m_device_key[128]; + + custom_video *m_custom_video = 0; + int m_custom_method; + +}; + +#endif diff --git a/3rdparty/switchres/custom_video_adl.cpp b/3rdparty/switchres/custom_video_adl.cpp new file mode 100644 index 0000000000000..a1afcc4fdcffb --- /dev/null +++ b/3rdparty/switchres/custom_video_adl.cpp @@ -0,0 +1,532 @@ +/************************************************************** + + custom_video_adl.cpp - ATI/AMD ADL library + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +// Constants and structures ported from AMD ADL SDK files + +#include +#include +#include "custom_video_adl.h" +#include "log.h" + + +//============================================================ +// memory allocation callbacks +//============================================================ + +void* __stdcall ADL_Main_Memory_Alloc(int iSize) +{ + void* lpBuffer = malloc(iSize); + return lpBuffer; +} + +void __stdcall ADL_Main_Memory_Free(void** lpBuffer) +{ + if (NULL != *lpBuffer) + { + free(*lpBuffer); + *lpBuffer = NULL; + } +} + +//============================================================ +// adl_timing::adl_timing +//============================================================ + +adl_timing::adl_timing(char *display_name, custom_video_settings *vs) +{ + m_vs = *vs; + strcpy (m_display_name, display_name); + strcpy (m_device_key, m_vs.device_reg_key); +} + +//============================================================ +// adl_timing::~adl_timing +//============================================================ + +adl_timing::~adl_timing() +{ + close(); +} + +//============================================================ +// adl_timing::init +//============================================================ + +bool adl_timing::init() +{ + int ADL_Err = ADL_ERR; + + log_verbose("ATI/AMD ADL init\n"); + + ADL_Err = open(); + if (ADL_Err != ADL_OK) + { + log_verbose("ERROR: ADL Initialization error!\n"); + return false; + } + + ADL2_Adapter_NumberOfAdapters_Get = (ADL2_ADAPTER_NUMBEROFADAPTERS_GET) (void *) GetProcAddress(hDLL,"ADL2_Adapter_NumberOfAdapters_Get"); + if (ADL2_Adapter_NumberOfAdapters_Get == NULL) + { + log_verbose("ERROR: ADL2_Adapter_NumberOfAdapters_Get not available!"); + return false; + } + ADL2_Adapter_AdapterInfo_Get = (ADL2_ADAPTER_ADAPTERINFO_GET) (void *) GetProcAddress(hDLL,"ADL2_Adapter_AdapterInfo_Get"); + if (ADL2_Adapter_AdapterInfo_Get == NULL) + { + log_verbose("ERROR: ADL2_Adapter_AdapterInfo_Get not available!"); + return false; + } + ADL2_Display_DisplayInfo_Get = (ADL2_DISPLAY_DISPLAYINFO_GET) (void *) GetProcAddress(hDLL,"ADL2_Display_DisplayInfo_Get"); + if (ADL2_Display_DisplayInfo_Get == NULL) + { + log_verbose("ERROR: ADL2_Display_DisplayInfo_Get not available!"); + return false; + } + ADL2_Display_ModeTimingOverride_Get = (ADL2_DISPLAY_MODETIMINGOVERRIDE_GET) (void *) GetProcAddress(hDLL,"ADL2_Display_ModeTimingOverride_Get"); + if (ADL2_Display_ModeTimingOverride_Get == NULL) + { + log_verbose("ERROR: ADL2_Display_ModeTimingOverride_Get not available!"); + return false; + } + ADL2_Display_ModeTimingOverride_Set = (ADL2_DISPLAY_MODETIMINGOVERRIDE_SET) (void *) GetProcAddress(hDLL,"ADL2_Display_ModeTimingOverride_Set"); + if (ADL2_Display_ModeTimingOverride_Set == NULL) + { + log_verbose("ERROR: ADL2_Display_ModeTimingOverride_Set not available!"); + return false; + } + ADL2_Display_ModeTimingOverrideList_Get = (ADL2_DISPLAY_MODETIMINGOVERRIDELIST_GET) (void *) GetProcAddress(hDLL,"ADL2_Display_ModeTimingOverrideList_Get"); + if (ADL2_Display_ModeTimingOverrideList_Get == NULL) + { + log_verbose("ERROR: ADL2_Display_ModeTimingOverrideList_Get not available!"); + return false; + } + + ADL2_Flush_Driver_Data = (ADL2_FLUSH_DRIVER_DATA) (void *) GetProcAddress(hDLL,"ADL2_Flush_Driver_Data"); + if (ADL2_Flush_Driver_Data == NULL) + { + log_verbose("ERROR: ADL2_Flush_Driver_Data not available!"); + return false; + } + + if (!enum_displays()) + { + log_error("ADL error enumerating displays.\n"); + return false; + } + + if (!get_device_mapping_from_display_name()) + { + log_error("ADL error mapping display.\n"); + return false; + } + + if (!get_driver_version(m_device_key)) + { + log_error("ADL driver version unknown!.\n"); + } + + if (!get_timing_list()) + { + log_error("ADL error getting list of timing overrides.\n"); + } + + log_verbose("ADL functions retrieved successfully.\n"); + return true; +} + +//============================================================ +// adl_timing::adl_open +//============================================================ + +int adl_timing::open() +{ + ADL2_MAIN_CONTROL_CREATE ADL2_Main_Control_Create; + int ADL_Err = ADL_ERR; + + hDLL = LoadLibraryA("atiadlxx.dll"); + if (hDLL == NULL) hDLL = LoadLibraryA("atiadlxy.dll"); + + if (hDLL != NULL) + { + ADL2_Main_Control_Create = (ADL2_MAIN_CONTROL_CREATE) (void *) GetProcAddress(hDLL, "ADL2_Main_Control_Create"); + if (ADL2_Main_Control_Create != NULL) + ADL_Err = ADL2_Main_Control_Create(ADL_Main_Memory_Alloc, 1, &m_adl); + } + else + { + log_verbose("ADL Library not found!\n"); + } + + return ADL_Err; +} + +//============================================================ +// adl_timing::close +//============================================================ + +void adl_timing::close() +{ + ADL2_MAIN_CONTROL_DESTROY ADL2_Main_Control_Destroy; + + log_verbose("ATI/AMD ADL close\n"); + + for (int i = 0; i <= iNumberAdapters - 1; i++) + ADL_Main_Memory_Free((void **)&lpAdapter[i].m_display_list); + + ADL_Main_Memory_Free((void **)&lpAdapterInfo); + ADL_Main_Memory_Free((void **)&lpAdapter); + + ADL2_Main_Control_Destroy = (ADL2_MAIN_CONTROL_DESTROY) (void *) GetProcAddress(hDLL, "ADL2_Main_Control_Destroy"); + if (ADL2_Main_Control_Destroy != NULL) + ADL2_Main_Control_Destroy(m_adl); + + FreeLibrary(hDLL); +} + +//============================================================ +// adl_timing::get_driver_version +//============================================================ + +bool adl_timing::get_driver_version(char *device_key) +{ + HKEY hkey; + bool found = false; + + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, device_key, 0, KEY_READ , &hkey) == ERROR_SUCCESS) + { + BYTE cat_ver[32]; + DWORD length = sizeof(cat_ver); + if ((RegQueryValueExA(hkey, "Catalyst_Version", NULL, NULL, cat_ver, &length) == ERROR_SUCCESS) || + (RegQueryValueExA(hkey, "RadeonSoftwareVersion", NULL, NULL, cat_ver, &length) == ERROR_SUCCESS) || + (RegQueryValueExA(hkey, "DriverVersion", NULL, NULL, cat_ver, &length) == ERROR_SUCCESS)) + { + found = true; + is_patched = (RegQueryValueExA(hkey, "CalamityRelease", NULL, NULL, NULL, NULL) == ERROR_SUCCESS); + sscanf((char *)cat_ver, "%d.%d", &cat_version, &sub_version); + log_verbose("AMD driver version %d.%d%s\n", cat_version, sub_version, is_patched? "(patched)":""); + } + RegCloseKey(hkey); + } + return found; +} + +//============================================================ +// adl_timing::enum_displays +//============================================================ + +bool adl_timing::enum_displays() +{ + ADL2_Adapter_NumberOfAdapters_Get(m_adl, &iNumberAdapters); + + lpAdapterInfo = (LPAdapterInfo)malloc(sizeof(AdapterInfo) * iNumberAdapters); + memset(lpAdapterInfo, '\0', sizeof(AdapterInfo) * iNumberAdapters); + ADL2_Adapter_AdapterInfo_Get(m_adl, lpAdapterInfo, sizeof(AdapterInfo) * iNumberAdapters); + + lpAdapter = (LPAdapterList)malloc(sizeof(AdapterList) * iNumberAdapters); + for (int i = 0; i <= iNumberAdapters - 1; i++) + { + lpAdapter[i].m_index = lpAdapterInfo[i].iAdapterIndex; + lpAdapter[i].m_bus = lpAdapterInfo[i].iBusNumber; + memcpy(&lpAdapter[i].m_name, &lpAdapterInfo[i].strAdapterName, ADL_MAX_PATH); + memcpy(&lpAdapter[i].m_display_name, &lpAdapterInfo[i].strDisplayName, ADL_MAX_PATH); + lpAdapter[i].m_num_of_displays = 0; + lpAdapter[i].m_display_list = 0; + + // Only get display info from target adapter (this api is very slow!) + if (!strcmp(lpAdapter[i].m_display_name, m_display_name)) + ADL2_Display_DisplayInfo_Get(m_adl, lpAdapter[i].m_index, &lpAdapter[i].m_num_of_displays, &lpAdapter[i].m_display_list, 1); + } + return true; +} + +//============================================================ +// adl_timing::get_device_mapping_from_display_name +//============================================================ + +bool adl_timing::get_device_mapping_from_display_name() +{ + for (int i = 0; i <= iNumberAdapters -1; i++) + { + if (!strcmp(m_display_name, lpAdapter[i].m_display_name)) + { + ADLDisplayInfo *display_list; + display_list = lpAdapter[i].m_display_list; + + for (int j = 0; j <= lpAdapter[i].m_num_of_displays - 1; j++) + { + if (lpAdapter[i].m_index == display_list[j].displayID.iDisplayLogicalAdapterIndex) + { + m_adapter_index = lpAdapter[i].m_index; + m_display_index = display_list[j].displayID.iDisplayLogicalIndex; + return true; + } + } + } + } + return false; +} + +//============================================================ +// adl_timing::display_mode_info_to_modeline +//============================================================ + +bool adl_timing::display_mode_info_to_modeline(ADLDisplayModeInfo *dmi, modeline *m) +{ + if (dmi->sDetailedTiming.sHTotal == 0) return false; + + ADLDetailedTiming dt; + memcpy(&dt, &dmi->sDetailedTiming, sizeof(ADLDetailedTiming)); + + if (dt.sHTotal == 0) return false; + + m->htotal = dt.sHTotal; + m->hactive = dt.sHDisplay; + m->hbegin = dt.sHSyncStart; + m->hend = dt.sHSyncWidth + m->hbegin; + m->vtotal = dt.sVTotal; + m->vactive = dt.sVDisplay; + m->vbegin = dt.sVSyncStart; + m->vend = dt.sVSyncWidth + m->vbegin; + m->interlace = (dt.sTimingFlags & ADL_DL_TIMINGFLAG_INTERLACED)? 1 : 0; + m->doublescan = (dt.sTimingFlags & ADL_DL_TIMINGFLAG_DOUBLE_SCAN)? 1 : 0; + m->hsync = ((dt.sTimingFlags & ADL_DL_TIMINGFLAG_H_SYNC_POLARITY)? 1 : 0) ^ invert_pol(1); + m->vsync = ((dt.sTimingFlags & ADL_DL_TIMINGFLAG_V_SYNC_POLARITY)? 1 : 0) ^ invert_pol(1) ; + m->pclock = dt.sPixelClock * 10000; + + m->height = m->height? m->height : dmi->iPelsHeight; + m->width = m->width? m->width : dmi->iPelsWidth; + m->refresh = m->refresh? m->refresh : dmi->iRefreshRate / interlace_factor(m->interlace, 1);; + m->hfreq = float(m->pclock / m->htotal); + m->vfreq = float(m->hfreq / m->vtotal) * (m->interlace? 2 : 1); + + return true; +} + +//============================================================ +// adl_timing::get_timing_list +//============================================================ + +bool adl_timing::get_timing_list() +{ + if (ADL2_Display_ModeTimingOverrideList_Get(m_adl, m_adapter_index, m_display_index, MAX_MODELINES, adl_mode, &m_num_of_adl_modes) != ADL_OK) return false; + + return true; +} + +//============================================================ +// adl_timing::get_timing_from_cache +//============================================================ + +bool adl_timing::get_timing_from_cache(modeline *m) +{ + ADLDisplayModeInfo *mode = 0; + + for (int i = 0; i < m_num_of_adl_modes; i++) + { + mode = &adl_mode[i]; + if (mode->iPelsWidth == m->width && mode->iPelsHeight == m->height && mode->iRefreshRate == m->refresh) + { + if ((m->interlace) && !(mode->sDetailedTiming.sTimingFlags & ADL_DL_TIMINGFLAG_INTERLACED)) + continue; + goto found; + } + } + + return false; + + found: + if (display_mode_info_to_modeline(mode, m)) return true; + + return false; +} + +//============================================================ +// adl_timing::get_timing +//============================================================ + +bool adl_timing::get_timing(modeline *m) +{ + ADLDisplayMode mode_in; + ADLDisplayModeInfo mode_info_out; + modeline m_temp = *m; + + //modeline to ADLDisplayMode + mode_in.iPelsHeight = m->height; + mode_in.iPelsWidth = m->width; + mode_in.iBitsPerPel = 32; + mode_in.iDisplayFrequency = m->refresh * interlace_factor(m->interlace, 1); + + if (ADL2_Display_ModeTimingOverride_Get(m_adl, m_adapter_index, m_display_index, &mode_in, &mode_info_out) != ADL_OK) goto not_found; + if (display_mode_info_to_modeline(&mode_info_out, &m_temp)) + { + if (m_temp.interlace == m->interlace) + { + memcpy(m, &m_temp, sizeof(modeline)); + m->type |= CUSTOM_VIDEO_TIMING_ATI_ADL; + return true; + } + } + + not_found: + + // Try to get timing from our cache (interlaced modes are not properly retrieved by ADL_Display_ModeTimingOverride_Get) + if (get_timing_from_cache(m)) + { + m->type |= CUSTOM_VIDEO_TIMING_ATI_ADL; + return true; + } + + return false; +} + +//============================================================ +// adl_timing::set_timing +//============================================================ + +bool adl_timing::set_timing(modeline *m) +{ + return set_timing_override(m, TIMING_UPDATE); +} + +//============================================================ +// adl_timing::set_timing_override +//============================================================ + +bool adl_timing::set_timing_override(modeline *m, int update_mode) +{ + ADLDisplayModeInfo mode_info = {}; + ADLDetailedTiming *dt; + modeline m_temp; + + //modeline to ADLDisplayModeInfo + mode_info.iTimingStandard = (update_mode & TIMING_DELETE)? ADL_DL_MODETIMING_STANDARD_DRIVER_DEFAULT : ADL_DL_MODETIMING_STANDARD_CUSTOM; + mode_info.iPossibleStandard = 0; + mode_info.iRefreshRate = m->refresh * interlace_factor(m->interlace, 0); + mode_info.iPelsWidth = m->width; + mode_info.iPelsHeight = m->height; + + //modeline to ADLDetailedTiming + dt = &mode_info.sDetailedTiming; + dt->sTimingFlags = (m->interlace? ADL_DL_TIMINGFLAG_INTERLACED : 0) | + (m->doublescan? ADL_DL_TIMINGFLAG_DOUBLE_SCAN: 0) | + (m->hsync ^ invert_pol(0)? ADL_DL_TIMINGFLAG_H_SYNC_POLARITY : 0) | + (m->vsync ^ invert_pol(0)? ADL_DL_TIMINGFLAG_V_SYNC_POLARITY : 0); + dt->sHTotal = m->htotal; + dt->sHDisplay = m->hactive; + dt->sHSyncStart = m->hbegin; + dt->sHSyncWidth = m->hend - m->hbegin; + dt->sVTotal = m->vtotal; + dt->sVDisplay = m->vactive; + dt->sVSyncStart = m->vbegin; + dt->sVSyncWidth = m->vend - m->vbegin; + dt->sPixelClock = m->pclock / 10000; + dt->sHOverscanRight = 0; + dt->sHOverscanLeft = 0; + dt->sVOverscanBottom = 0; + dt->sVOverscanTop = 0; + + if (ADL2_Display_ModeTimingOverride_Set(m_adl, m_adapter_index, m_display_index, &mode_info, (update_mode & TIMING_UPDATE_LIST)? 1 : 0) != ADL_OK) return false; + + //ADL2_Flush_Driver_Data(m_adl, m_adapter_index); + + // read modeline to trigger timing refresh on modded drivers + memcpy(&m_temp, m, sizeof(modeline)); + if (update_mode & TIMING_UPDATE) get_timing(&m_temp); + + return true; +} + +//============================================================ +// adl_timing::add_mode +//============================================================ + +bool adl_timing::add_mode(modeline *mode) +{ + if (!set_timing_override(mode, TIMING_UPDATE_LIST)) + { + return false; + } + + m_resync.wait(); + mode->type |= CUSTOM_VIDEO_TIMING_ATI_ADL; + + return true; +} + +//============================================================ +// adl_timing::delete_mode +//============================================================ + +bool adl_timing::delete_mode(modeline *mode) +{ + if (!set_timing_override(mode, TIMING_DELETE | TIMING_UPDATE_LIST)) + { + return false; + } + + m_resync.wait(); + + return true; +} + +//============================================================ +// adl_timing::update_mode +//============================================================ + +bool adl_timing::update_mode(modeline *mode) +{ + bool refresh_required = !is_patched || (mode->type & MODE_DESKTOP); + + if (!set_timing_override(mode, refresh_required? TIMING_UPDATE_LIST : TIMING_UPDATE)) + { + return false; + } + + if (refresh_required) m_resync.wait(); + mode->type |= CUSTOM_VIDEO_TIMING_ATI_ADL; + return true; +} + +//============================================================ +// adl_timing::process_modelist +//============================================================ + +bool adl_timing::process_modelist(std::vector modelist) +{ + bool refresh_required = false; + bool error = false; + + for (auto &mode : modelist) + { + if (mode->type & MODE_DELETE || mode->type & MODE_ADD || (mode->type & MODE_UPDATE && (!is_patched || (mode->type & MODE_DESKTOP)))) + refresh_required = true; + + bool is_last = (mode == modelist.back()); + + if (!set_timing_override(mode, (mode->type & MODE_DELETE? TIMING_DELETE : TIMING_UPDATE) | (is_last && refresh_required? TIMING_UPDATE_LIST : 0))) + { + mode->type |= MODE_ERROR; + error = true; + } + else + { + mode->type &= ~MODE_ERROR; + mode->type |= CUSTOM_VIDEO_TIMING_ATI_ADL; + } + } + + if (refresh_required) m_resync.wait(); + return !error; +} diff --git a/3rdparty/switchres/custom_video_adl.h b/3rdparty/switchres/custom_video_adl.h new file mode 100644 index 0000000000000..e9bdd5a6e51a0 --- /dev/null +++ b/3rdparty/switchres/custom_video_adl.h @@ -0,0 +1,201 @@ +/************************************************************** + + custom_video_adl.h - ATI/AMD ADL library header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include "custom_video.h" +#include "resync_windows.h" + +// Constants and structures ported from AMD ADL SDK files +#define ADL_MAX_PATH 256 +#define ADL_OK 0 +#define ADL_ERR -1 + +//ADL_DETAILED_TIMING.sTimingFlags +#define ADL_DL_TIMINGFLAG_DOUBLE_SCAN 0x0001 +#define ADL_DL_TIMINGFLAG_INTERLACED 0x0002 +#define ADL_DL_TIMINGFLAG_H_SYNC_POLARITY 0x0004 +#define ADL_DL_TIMINGFLAG_V_SYNC_POLARITY 0x0008 + +//ADL_DISPLAY_MODE_INFO.iTimingStandard +#define ADL_DL_MODETIMING_STANDARD_CVT 0x00000001 // CVT Standard +#define ADL_DL_MODETIMING_STANDARD_GTF 0x00000002 // GFT Standard +#define ADL_DL_MODETIMING_STANDARD_DMT 0x00000004 // DMT Standard +#define ADL_DL_MODETIMING_STANDARD_CUSTOM 0x00000008 // User-defined standard +#define ADL_DL_MODETIMING_STANDARD_DRIVER_DEFAULT 0x00000010 // Remove Mode from overriden list +#define ADL_DL_MODETIMING_STANDARD_CVT_RB 0x00000020 // CVT-RB Standard + +typedef struct AdapterInfo +{ + int iSize; + int iAdapterIndex; + char strUDID[ADL_MAX_PATH]; + int iBusNumber; + int iDeviceNumber; + int iFunctionNumber; + int iVendorID; + char strAdapterName[ADL_MAX_PATH]; + char strDisplayName[ADL_MAX_PATH]; + int iPresent; + int iExist; + char strDriverPath[ADL_MAX_PATH]; + char strDriverPathExt[ADL_MAX_PATH]; + char strPNPString[ADL_MAX_PATH]; + int iOSDisplayIndex; +} AdapterInfo, *LPAdapterInfo; + +typedef struct ADLDisplayID +{ + int iDisplayLogicalIndex; + int iDisplayPhysicalIndex; + int iDisplayLogicalAdapterIndex; + int iDisplayPhysicalAdapterIndex; +} ADLDisplayID, *LPADLDisplayID; + + +typedef struct ADLDisplayInfo +{ + ADLDisplayID displayID; + int iDisplayControllerIndex; + char strDisplayName[ADL_MAX_PATH]; + char strDisplayManufacturerName[ADL_MAX_PATH]; + int iDisplayType; + int iDisplayOutputType; + int iDisplayConnector; + int iDisplayInfoMask; + int iDisplayInfoValue; +} ADLDisplayInfo, *LPADLDisplayInfo; + +typedef struct ADLDisplayMode +{ + int iPelsHeight; + int iPelsWidth; + int iBitsPerPel; + int iDisplayFrequency; +} ADLDisplayMode; + +typedef struct ADLDetailedTiming +{ + int iSize; + short sTimingFlags; + short sHTotal; + short sHDisplay; + short sHSyncStart; + short sHSyncWidth; + short sVTotal; + short sVDisplay; + short sVSyncStart; + short sVSyncWidth; + unsigned short sPixelClock; + short sHOverscanRight; + short sHOverscanLeft; + short sVOverscanBottom; + short sVOverscanTop; + short sOverscan8B; + short sOverscanGR; +} ADLDetailedTiming; + +typedef struct ADLDisplayModeInfo +{ + int iTimingStandard; + int iPossibleStandard; + int iRefreshRate; + int iPelsWidth; + int iPelsHeight; + ADLDetailedTiming sDetailedTiming; +} ADLDisplayModeInfo; + +typedef struct AdapterList +{ + int m_index; + int m_bus; + char m_name[ADL_MAX_PATH]; + char m_display_name[ADL_MAX_PATH]; + int m_num_of_displays; + ADLDisplayInfo *m_display_list; +} AdapterList, *LPAdapterList; + + +typedef void* ADL_CONTEXT_HANDLE; +typedef void* (__stdcall *ADL_MAIN_MALLOC_CALLBACK)(int); +typedef int (*ADL2_MAIN_CONTROL_CREATE)(ADL_MAIN_MALLOC_CALLBACK, int, ADL_CONTEXT_HANDLE *); +typedef int (*ADL2_MAIN_CONTROL_DESTROY)(ADL_CONTEXT_HANDLE); +typedef int (*ADL2_ADAPTER_NUMBEROFADAPTERS_GET) (ADL_CONTEXT_HANDLE, int*); +typedef int (*ADL2_ADAPTER_ADAPTERINFO_GET) (ADL_CONTEXT_HANDLE, LPAdapterInfo, int); +typedef int (*ADL2_DISPLAY_DISPLAYINFO_GET) (ADL_CONTEXT_HANDLE, int, int *, ADLDisplayInfo **, int); +typedef int (*ADL2_DISPLAY_MODETIMINGOVERRIDE_GET) (ADL_CONTEXT_HANDLE, int iAdapterIndex, int iDisplayIndex, ADLDisplayMode *lpModeIn, ADLDisplayModeInfo *lpModeInfoOut); +typedef int (*ADL2_DISPLAY_MODETIMINGOVERRIDE_SET) (ADL_CONTEXT_HANDLE, int iAdapterIndex, int iDisplayIndex, ADLDisplayModeInfo *lpMode, int iForceUpdate); +typedef int (*ADL2_DISPLAY_MODETIMINGOVERRIDELIST_GET) (ADL_CONTEXT_HANDLE, int iAdapterIndex, int iDisplayIndex, int iMaxNumOfOverrides, ADLDisplayModeInfo *lpModeInfoList, int *lpNumOfOverrides); +typedef int (*ADL2_FLUSH_DRIVER_DATA) (ADL_CONTEXT_HANDLE, int iAdapterIndex); + + +class adl_timing : public custom_video +{ + public: + adl_timing(char *display_name, custom_video_settings *vs); + ~adl_timing(); + const char *api_name() { return "AMD ADL"; } + bool init(); + void close(); + int caps() { return allow_hardware_refresh()? CUSTOM_VIDEO_CAPS_UPDATE | CUSTOM_VIDEO_CAPS_ADD | CUSTOM_VIDEO_CAPS_DESKTOP_EDITABLE : is_patched? CUSTOM_VIDEO_CAPS_UPDATE : 0; } + + bool add_mode(modeline *mode); + bool delete_mode(modeline *mode); + bool update_mode(modeline *mode); + + bool get_timing(modeline *m); + bool set_timing(modeline *m); + + bool process_modelist(std::vector); + + private: + int open(); + bool get_driver_version(char *device_key); + bool enum_displays(); + bool get_device_mapping_from_display_name(); + bool display_mode_info_to_modeline(ADLDisplayModeInfo *dmi, modeline *m); + bool get_timing_list(); + bool get_timing_from_cache(modeline *m); + bool set_timing_override(modeline *m, int update_mode); + + char m_display_name[32]; + char m_device_key[128]; + + int m_adapter_index = 0; + int m_display_index = 0; + + ADL2_ADAPTER_NUMBEROFADAPTERS_GET ADL2_Adapter_NumberOfAdapters_Get; + ADL2_ADAPTER_ADAPTERINFO_GET ADL2_Adapter_AdapterInfo_Get; + ADL2_DISPLAY_DISPLAYINFO_GET ADL2_Display_DisplayInfo_Get; + ADL2_DISPLAY_MODETIMINGOVERRIDE_GET ADL2_Display_ModeTimingOverride_Get; + ADL2_DISPLAY_MODETIMINGOVERRIDE_SET ADL2_Display_ModeTimingOverride_Set; + ADL2_DISPLAY_MODETIMINGOVERRIDELIST_GET ADL2_Display_ModeTimingOverrideList_Get; + ADL2_FLUSH_DRIVER_DATA ADL2_Flush_Driver_Data; + + HINSTANCE hDLL; + LPAdapterInfo lpAdapterInfo = NULL; + LPAdapterList lpAdapter = NULL;; + int iNumberAdapters = 0; + int cat_version = 0; + int sub_version = 0; + bool is_patched = false; + + ADL_CONTEXT_HANDLE m_adl = 0; + ADLDisplayModeInfo adl_mode[MAX_MODELINES]; + int m_num_of_adl_modes = 0; + + resync_handler m_resync; + + int invert_pol(bool on_read) { return ((cat_version <= 12) || (cat_version >= 15 && on_read)); } + int interlace_factor(bool interlace, bool on_read) { return interlace && ((cat_version <= 12) || (cat_version >= 15 && on_read))? 2 : 1; } +}; diff --git a/3rdparty/switchres/custom_video_ati.cpp b/3rdparty/switchres/custom_video_ati.cpp new file mode 100644 index 0000000000000..4b8dfaf97112f --- /dev/null +++ b/3rdparty/switchres/custom_video_ati.cpp @@ -0,0 +1,343 @@ +/************************************************************** + + custom_video_ati.cpp - ATI legacy library + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include "custom_video_ati.h" +#include "log.h" + + +//============================================================ +// ati_timing::ati_timing +//============================================================ + +ati_timing::ati_timing(char *device_name, custom_video_settings *vs) +{ + m_vs = *vs; + strcpy (m_device_name, device_name); + strcpy (m_device_key, m_vs.device_reg_key); +} + +//============================================================ +// ati_timing::ati_timing +//============================================================ + +bool ati_timing::init() +{ + log_verbose("ATI legacy init\n"); + + // Get Windows version + win_version = os_version(); + + if (win_version > 5 && !is_elevated()) + { + log_error("ATI legacy error: the program needs administrator rights.\n"); + return false; + } + + return true; +} + +//============================================================ +// ati_timing::get_timing +//============================================================ + +bool ati_timing::get_timing(modeline *mode) +{ + HKEY hKey; + char lp_name[1024]; + char lp_data[68]; + DWORD length; + bool found = false; + int refresh_label = mode->refresh_label? mode->refresh_label : mode->refresh * win_interlace_factor(mode); + int vfreq_incr = 0; + + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, m_device_key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + { + sprintf(lp_name, "DALDTMCRTBCD%dx%dx0x%d", mode->width, mode->height, refresh_label); + length = sizeof(lp_data); + + if (RegQueryValueExA(hKey, lp_name, NULL, NULL, (LPBYTE)lp_data, &length) == ERROR_SUCCESS && length == sizeof(lp_data)) + found = true; + else if (win_version > 5 && mode->interlace) + { + vfreq_incr = 1; + sprintf(lp_name, "DALDTMCRTBCD%dx%dx0x%d", mode->width, mode->height, refresh_label + vfreq_incr); + if (RegQueryValueExA(hKey, lp_name, NULL, NULL, (LPBYTE)lp_data, &length) == ERROR_SUCCESS && length == sizeof(lp_data)) + found = true; + } + if (found) + { + mode->pclock = get_DWORD_BCD(36, lp_data) * 10000; + mode->hactive = get_DWORD_BCD(8, lp_data); + mode->hbegin = get_DWORD_BCD(12, lp_data); + mode->hend = get_DWORD_BCD(16, lp_data) + mode->hbegin; + mode->htotal = get_DWORD_BCD(4, lp_data); + mode->vactive = get_DWORD_BCD(24, lp_data); + mode->vbegin = get_DWORD_BCD(28, lp_data); + mode->vend = get_DWORD_BCD(32, lp_data) + mode->vbegin; + mode->vtotal = get_DWORD_BCD(20, lp_data); + mode->interlace = (get_DWORD(0, lp_data) & CRTC_INTERLACED)?1:0; + mode->hsync = (get_DWORD(0, lp_data) & CRTC_H_SYNC_POLARITY)?0:1; + mode->vsync = (get_DWORD(0, lp_data) & CRTC_V_SYNC_POLARITY)?0:1; + mode->hfreq = mode->pclock / mode->htotal; + mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace?2:1); + mode->refresh_label = refresh_label; + mode->type |= CUSTOM_VIDEO_TIMING_ATI_LEGACY; + + int checksum = 65535 - get_DWORD(0, lp_data) - mode->htotal - mode->hactive - mode->hend + - mode->vtotal - mode->vactive - mode->vend - mode->pclock/10000; + if (checksum != get_DWORD(64, lp_data)) + log_verbose("bad checksum! "); + } + RegCloseKey(hKey); + return (found); + } + log_verbose("Failed opening registry entry for mode. "); + return false; +} + +//============================================================ +// ati_timing::set_timing +//============================================================ + +bool ati_timing::set_timing(modeline *mode) +{ + HKEY hKey; + char lp_name[1024]; + char lp_data[68]; + long checksum; + bool found = false; + int refresh_label = mode->refresh_label? mode->refresh_label : mode->refresh * win_interlace_factor(mode); + int vfreq_incr = 0; + + memset(lp_data, 0, sizeof(lp_data)); + set_DWORD_BCD(lp_data, (int)mode->pclock/10000, 36); + set_DWORD_BCD(lp_data, mode->hactive, 8); + set_DWORD_BCD(lp_data, mode->hbegin, 12); + set_DWORD_BCD(lp_data, mode->hend - mode->hbegin, 16); + set_DWORD_BCD(lp_data, mode->htotal, 4); + set_DWORD_BCD(lp_data, mode->vactive, 24); + set_DWORD_BCD(lp_data, mode->vbegin, 28); + set_DWORD_BCD(lp_data, mode->vend - mode->vbegin, 32); + set_DWORD_BCD(lp_data, mode->vtotal, 20); + set_DWORD(lp_data, (mode->interlace?CRTC_INTERLACED:0) | (mode->hsync?0:CRTC_H_SYNC_POLARITY) | (mode->vsync?0:CRTC_V_SYNC_POLARITY), 0); + + checksum = 65535 - get_DWORD(0, lp_data) - mode->htotal - mode->hactive - mode->hend + - mode->vtotal - mode->vactive - mode->vend - mode->pclock/10000; + set_DWORD(lp_data, checksum, 64); + + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, m_device_key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) + { + sprintf (lp_name, "DALDTMCRTBCD%dx%dx0x%d", mode->width, mode->height, refresh_label); + + if (RegQueryValueExA(hKey, lp_name, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) + found = true; + else if (win_version > 5 && mode->interlace) + { + vfreq_incr = 1; + sprintf(lp_name, "DALDTMCRTBCD%dx%dx0x%d", mode->width, mode->height, refresh_label + vfreq_incr); + if (RegQueryValueExA(hKey, lp_name, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) + found = true; + } + + if (!(found && RegSetValueExA(hKey, lp_name, 0, REG_BINARY, (LPBYTE)lp_data, 68) == ERROR_SUCCESS)) + log_info("Failed saving registry entry %s\n", lp_name); + + RegCloseKey(hKey); + return (found); + } + + log_info("Failed updating registry entry for mode.\n"); + return 0; +} + +//============================================================ +// ati_timing::update_mode +//============================================================ + +bool ati_timing::update_mode(modeline *mode) +{ + if (!set_timing(mode)) + return false; + + mode->type |= CUSTOM_VIDEO_TIMING_ATI_LEGACY; + + // ATI needs a call to EnumDisplaySettings to refresh timings + refresh_timings(); + + return true; +} + +//============================================================ +// ati_refresh_timings +//============================================================ + +void ati_timing::refresh_timings(void) +{ + int iModeNum = 0; + DEVMODEA lpDevMode; + + memset(&lpDevMode, 0, sizeof(DEVMODEA)); + lpDevMode.dmSize = sizeof(DEVMODEA); + + while (EnumDisplaySettingsExA(m_device_name, iModeNum, &lpDevMode, 0) != 0) + iModeNum++; +} + +//============================================================ +// adl_timing::process_modelist +//============================================================ + +bool ati_timing::process_modelist(std::vector modelist) +{ + bool error = false; + + for (auto &mode : modelist) + { + if (!set_timing(mode)) + { + mode->type |= MODE_ERROR; + error = true; + } + else + { + mode->type &= ~MODE_ERROR; + mode->type |= CUSTOM_VIDEO_TIMING_ATI_LEGACY; + } + } + + refresh_timings(); + return !error; +} + +//============================================================ +// get_DWORD +//============================================================ + +int ati_timing::get_DWORD(int i, char *lp_data) +{ + char out[32] = ""; + UINT32 x; + + sprintf(out, "%02X%02X%02X%02X", lp_data[i]&0xFF, lp_data[i+1]&0xFF, lp_data[i+2]&0xFF, lp_data[i+3]&0xFF); + sscanf(out, "%08X", &x); + return x; +} + +//============================================================ +// get_DWORD_BCD +//============================================================ + +int ati_timing::get_DWORD_BCD(int i, char *lp_data) +{ + char out[32] = ""; + UINT32 x; + + sprintf(out, "%02X%02X%02X%02X", lp_data[i]&0xFF, lp_data[i+1]&0xFF, lp_data[i+2]&0xFF, lp_data[i+3]&0xFF); + sscanf(out, "%d", &x); + return x; +} + +//============================================================ +// set_DWORD +//============================================================ + +void ati_timing::set_DWORD(char *data_string, UINT32 data_dword, int offset) +{ + char *p_dword = (char*)&data_dword; + + data_string[offset] = p_dword[3]&0xFF; + data_string[offset+1] = p_dword[2]&0xFF; + data_string[offset+2] = p_dword[1]&0xFF; + data_string[offset+3] = p_dword[0]&0xFF; +} + +//============================================================ +// set_DWORD_BCD +//============================================================ + +void ati_timing::set_DWORD_BCD(char *data_string, UINT32 data_dword, int offset) +{ + if (data_dword < 100000000) + { + int low_word, high_word; + int a, b, c, d; + char out[32] = ""; + + low_word = data_dword % 10000; + high_word = data_dword / 10000; + + sprintf(out, "%d %d %d %d", high_word / 100, high_word % 100 , low_word / 100, low_word % 100); + sscanf(out, "%02X %02X %02X %02X", &a, &b, &c, &d); + + data_string[offset] = a; + data_string[offset+1] = b; + data_string[offset+2] = c; + data_string[offset+3] = d; + } +} + +//============================================================ +// os_version +//============================================================ + +int ati_timing::os_version(void) +{ + OSVERSIONINFOA lpVersionInfo; + + memset(&lpVersionInfo, 0, sizeof(OSVERSIONINFOA)); + lpVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); + GetVersionExA (&lpVersionInfo); + + return lpVersionInfo.dwMajorVersion; +} + +//============================================================ +// is_elevated +//============================================================ + +bool ati_timing::is_elevated() +{ + HANDLE htoken; + bool result = false; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htoken)) + return false; + + TOKEN_ELEVATION te = {0}; + DWORD dw_return_length; + + if (GetTokenInformation(htoken, TokenElevation, &te, sizeof(te), &dw_return_length)) + { + if (te.TokenIsElevated) + { + result = true; + } + } + + CloseHandle(htoken); + return (result); +} + +//============================================================ +// win_interlace_factor +//============================================================ + +int ati_timing::win_interlace_factor(modeline *mode) +{ + if (win_version > 5 && mode->interlace) + return 2; + + return 1; +} diff --git a/3rdparty/switchres/custom_video_ati.h b/3rdparty/switchres/custom_video_ati.h new file mode 100644 index 0000000000000..13736e8dd10b2 --- /dev/null +++ b/3rdparty/switchres/custom_video_ati.h @@ -0,0 +1,53 @@ +/************************************************************** + + custom_video_ati.h - ATI legacy library header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include "custom_video.h" + +#define CRTC_DOUBLE_SCAN 0x0001 +#define CRTC_INTERLACED 0x0002 +#define CRTC_H_SYNC_POLARITY 0x0004 +#define CRTC_V_SYNC_POLARITY 0x0008 + +class ati_timing : public custom_video +{ + public: + ati_timing(char *device_name, custom_video_settings *vs); + ~ati_timing() {}; + const char *api_name() { return "ATI Legacy"; } + bool init(); + int caps() { return CUSTOM_VIDEO_CAPS_UPDATE | CUSTOM_VIDEO_CAPS_SCAN_EDITABLE; } + + bool update_mode(modeline *mode); + + bool get_timing(modeline *mode); + bool set_timing(modeline *mode); + + bool process_modelist(std::vector); + + private: + void refresh_timings(void); + + int get_DWORD(int i, char *lp_data); + int get_DWORD_BCD(int i, char *lp_data); + void set_DWORD(char *data_string, UINT32 data_word, int offset); + void set_DWORD_BCD(char *data_string, UINT32 data_word, int offset); + int os_version(void); + bool is_elevated(); + int win_interlace_factor(modeline *mode); + + char m_device_name[32]; + char m_device_key[256]; + int win_version; +}; diff --git a/3rdparty/switchres/custom_video_ati_family.cpp b/3rdparty/switchres/custom_video_ati_family.cpp new file mode 100644 index 0000000000000..f2f60e8fe0fc5 --- /dev/null +++ b/3rdparty/switchres/custom_video_ati_family.cpp @@ -0,0 +1,848 @@ +/************************************************************** + + custom_video_ati_family.cpp - ATI/AMD Radeon family + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +/* Constants and structures ported from Linux open source drivers: + drivers\gpu\drm\radeon\radeon.h + drivers\gpu\drm\radeon\radeon_family.h + include\drm\drm_pciids.h +*/ + +#ifndef RADEON_FAMILY_H +#define RADEON_FAMILY_H + +struct pci_device_id +{ + int vendor, device; + int subvendor, subdevice; + int _class, _class_mask; + int driver_data; +}; + +enum radeon_family +{ + CHIP_R100 = 0, + CHIP_RV100, + CHIP_RS100, + CHIP_RV200, + CHIP_RS200, + CHIP_R200, + CHIP_RV250, + CHIP_RS300, + CHIP_RV280, + CHIP_R300, + CHIP_R350, + CHIP_RV350, + CHIP_RV380, + CHIP_R420, + CHIP_R423, + CHIP_RV410, + CHIP_RS400, + CHIP_RS480, + CHIP_RS600, + CHIP_RS690, + CHIP_RS740, + CHIP_RV515, + CHIP_R520, + CHIP_RV530, + CHIP_RV560, + CHIP_RV570, + CHIP_R580, + CHIP_R600, + CHIP_RV610, + CHIP_RV630, + CHIP_RV670, + CHIP_RV620, + CHIP_RV635, + CHIP_RS780, + CHIP_RS880, + CHIP_RV770, + CHIP_RV730, + CHIP_RV710, + CHIP_RV740, + CHIP_CEDAR, + CHIP_REDWOOD, + CHIP_JUNIPER, + CHIP_CYPRESS, + CHIP_HEMLOCK, + CHIP_PALM, + CHIP_SUMO, + CHIP_SUMO2, + CHIP_BARTS, + CHIP_TURKS, + CHIP_CAICOS, + CHIP_CAYMAN, + CHIP_ARUBA, + CHIP_TAHITI, + CHIP_PITCAIRN, + CHIP_VERDE, + CHIP_OLAND, + CHIP_HAINAN, + CHIP_BONAIRE, + CHIP_KAVERI, + CHIP_KABINI, + CHIP_HAWAII, + CHIP_MULLINS, + CHIP_LAST, +}; + +enum radeon_chip_flags +{ + RADEON_FAMILY_MASK = 0x0000ffffUL, + RADEON_FLAGS_MASK = 0xffff0000UL, + RADEON_IS_MOBILITY = 0x00010000UL, + RADEON_IS_IGP = 0x00020000UL, + RADEON_SINGLE_CRTC = 0x00040000UL, + RADEON_IS_AGP = 0x00080000UL, + RADEON_HAS_HIERZ = 0x00100000UL, + RADEON_IS_PCIE = 0x00200000UL, + RADEON_NEW_MEMMAP = 0x00400000UL, + RADEON_IS_PCI = 0x00800000UL, + RADEON_IS_IGPGART = 0x01000000UL, + RADEON_IS_PX = 0x02000000UL, +}; + +#define PCI_ANY_ID (~0) + +#define radeon_PCI_IDS \ + {0x1002, 0x1304, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1305, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1306, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1307, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1309, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x130A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x130B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x130C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x130D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x130E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x130F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1311, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1312, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1313, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1315, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1316, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1317, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x1318, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x131B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x131C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x131D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KAVERI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x3150, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY}, \ + {0x1002, 0x3151, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x3152, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x3154, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x3155, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x3E50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x3E54, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4136, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS100|RADEON_IS_IGP}, \ + {0x1002, 0x4137, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|RADEON_IS_IGP}, \ + {0x1002, 0x4144, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4145, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4146, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4147, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4148, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x4149, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x414A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x414B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x4150, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \ + {0x1002, 0x4151, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \ + {0x1002, 0x4152, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \ + {0x1002, 0x4153, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \ + {0x1002, 0x4154, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \ + {0x1002, 0x4155, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \ + {0x1002, 0x4156, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \ + {0x1002, 0x4237, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|RADEON_IS_IGP}, \ + {0x1002, 0x4242, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \ + {0x1002, 0x4336, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS100|RADEON_IS_IGP|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4337, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|RADEON_IS_IGP|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4437, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|RADEON_IS_IGP|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4966, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV250}, \ + {0x1002, 0x4967, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV250}, \ + {0x1002, 0x4A48, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A49, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A4A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A4B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A4C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A4D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A4E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A4F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4A54, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4B48, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4B49, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4B4A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4B4B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4B4C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R420|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x4C57, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4C58, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4C59, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4C5A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4C64, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV250|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4C66, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV250|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4C67, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV250|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4C6E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4E44, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4E45, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4E46, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4E47, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \ + {0x1002, 0x4E48, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x4E49, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x4E4A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x4E4B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R350}, \ + {0x1002, 0x4E50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4E51, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4E52, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4E53, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4E54, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|RADEON_IS_MOBILITY}, \ + {0x1002, 0x4E56, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|RADEON_IS_MOBILITY}, \ + {0x1002, 0x5144, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|RADEON_SINGLE_CRTC}, \ + {0x1002, 0x5145, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|RADEON_SINGLE_CRTC}, \ + {0x1002, 0x5146, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|RADEON_SINGLE_CRTC}, \ + {0x1002, 0x5147, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|RADEON_SINGLE_CRTC}, \ + {0x1002, 0x5148, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \ + {0x1002, 0x514C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \ + {0x1002, 0x514D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \ + {0x1002, 0x5157, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200}, \ + {0x1002, 0x5158, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200}, \ + {0x1002, 0x5159, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \ + {0x1002, 0x515A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \ + {0x1002, 0x515E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|RADEON_SINGLE_CRTC}, \ + {0x1002, 0x5460, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY}, \ + {0x1002, 0x5462, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY}, \ + {0x1002, 0x5464, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_IS_MOBILITY}, \ + {0x1002, 0x5548, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5549, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x554A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x554B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x554C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x554D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x554E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x554F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5550, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5551, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5552, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5554, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x564A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x564B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x564F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5652, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5653, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5657, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5834, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|RADEON_IS_IGP}, \ + {0x1002, 0x5835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|RADEON_IS_IGP|RADEON_IS_MOBILITY}, \ + {0x1002, 0x5954, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS480|RADEON_IS_IGP|RADEON_IS_MOBILITY|RADEON_IS_IGPGART}, \ + {0x1002, 0x5955, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS480|RADEON_IS_IGP|RADEON_IS_MOBILITY|RADEON_IS_IGPGART}, \ + {0x1002, 0x5974, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS480|RADEON_IS_IGP|RADEON_IS_MOBILITY|RADEON_IS_IGPGART}, \ + {0x1002, 0x5975, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS480|RADEON_IS_IGP|RADEON_IS_MOBILITY|RADEON_IS_IGPGART}, \ + {0x1002, 0x5960, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \ + {0x1002, 0x5961, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \ + {0x1002, 0x5962, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \ + {0x1002, 0x5964, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \ + {0x1002, 0x5965, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \ + {0x1002, 0x5969, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|RADEON_SINGLE_CRTC}, \ + {0x1002, 0x5a41, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS400|RADEON_IS_IGP|RADEON_IS_IGPGART}, \ + {0x1002, 0x5a42, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS400|RADEON_IS_IGP|RADEON_IS_MOBILITY|RADEON_IS_IGPGART}, \ + {0x1002, 0x5a61, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS400|RADEON_IS_IGP|RADEON_IS_IGPGART}, \ + {0x1002, 0x5a62, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS400|RADEON_IS_IGP|RADEON_IS_MOBILITY|RADEON_IS_IGPGART}, \ + {0x1002, 0x5b60, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5b62, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5b63, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5b64, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5b65, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV380|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5c61, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280|RADEON_IS_MOBILITY}, \ + {0x1002, 0x5c63, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280|RADEON_IS_MOBILITY}, \ + {0x1002, 0x5d48, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d49, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d4a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d4c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d4d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d4e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d4f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d52, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5d57, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R423|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5e48, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5e4a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5e4b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5e4c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5e4d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x5e4f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV410|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6600, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6601, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6602, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6603, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6604, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6605, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6606, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6607, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6608, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6610, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6611, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6613, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6620, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6621, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6623, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6631, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_OLAND|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6640, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6641, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6646, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6647, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6649, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6650, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6651, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6658, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x665c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x665d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BONAIRE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6660, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6663, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6664, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6665, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6667, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x666F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAINAN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6700, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6701, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6702, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6703, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6704, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6705, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6706, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6707, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6708, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6709, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6718, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6719, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x671c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x671d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x671f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAYMAN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6720, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6721, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6722, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6723, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6724, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6725, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6726, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6727, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6728, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6729, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6738, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6739, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x673e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_BARTS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6740, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6741, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6742, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6743, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6744, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6745, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6746, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6747, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6748, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6749, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x674A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6750, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6751, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6758, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6759, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x675B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x675D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x675F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6760, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6761, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6762, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6763, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6764, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6765, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6766, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6767, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6768, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6770, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6771, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6772, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6778, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6779, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x677B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CAICOS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6780, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6784, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6788, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x678A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6790, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6791, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6792, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6798, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6799, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x679A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x679B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x679E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x679F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TAHITI|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67A8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67A9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67AA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67B0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67B1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67B8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67B9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67BA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x67BE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HAWAII|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6800, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6802, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6806, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6808, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6809, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6810, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6811, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6816, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6817, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6818, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6819, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6820, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6821, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6822, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6823, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6824, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6825, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6826, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6827, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6828, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6829, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x682A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x682B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x682C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x682D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x682F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6830, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6831, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6838, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6839, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x683B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x683D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x683F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_VERDE|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6840, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6841, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6842, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6843, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6849, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x684C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PITCAIRN|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6850, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6858, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6859, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_TURKS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6880, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6888, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6889, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x688A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x688C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x688D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6898, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x6899, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x689b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x689c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HEMLOCK|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x689d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_HEMLOCK|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x689e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CYPRESS|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68a0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68a1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68a8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68a9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68b0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68b8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68b9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68ba, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68be, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68bf, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_JUNIPER|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68c0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68c1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68c7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68c8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68c9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68d8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68d9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68da, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68de, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_REDWOOD|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68e0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68e1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68e4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68e5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68e8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68e9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68f1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68f2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68f8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68f9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68fa, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x68fe, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_CEDAR|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7101, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7102, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7103, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7104, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7105, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7108, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7109, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x710A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x710B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x710C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x710E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x710F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R520|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7140, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7141, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7142, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7143, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7144, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7145, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7146, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7147, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7149, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x714A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x714B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x714C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x714D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x714E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x714F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7151, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7152, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7153, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x715E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x715F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7180, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7181, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7183, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7186, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7187, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7188, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x718A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x718B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x718C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x718D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x718F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7193, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7196, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x719B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x719F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C6, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71C7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71CD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71CE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71D2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71D4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71D5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71D6, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71DA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x71DE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV530|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7200, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7210, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7211, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV515|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7240, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7243, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7244, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7245, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7246, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7247, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7248, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7249, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x724A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x724B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x724C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x724D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x724E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x724F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7280, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV570|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7281, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV560|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7283, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV560|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7284, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R580|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7287, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV560|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7288, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV570|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7289, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV570|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x728B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV570|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x728C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV570|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7290, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV560|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7291, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV560|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7293, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV560|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7297, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV560|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7834, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|RADEON_IS_IGP|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|RADEON_IS_IGP|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x791e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS690|RADEON_IS_IGP|RADEON_NEW_MEMMAP|RADEON_IS_IGPGART}, \ + {0x1002, 0x791f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS690|RADEON_IS_IGP|RADEON_NEW_MEMMAP|RADEON_IS_IGPGART}, \ + {0x1002, 0x793f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS600|RADEON_IS_IGP|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7941, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS600|RADEON_IS_IGP|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x7942, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS600|RADEON_IS_IGP|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x796c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS740|RADEON_IS_IGP|RADEON_NEW_MEMMAP|RADEON_IS_IGPGART}, \ + {0x1002, 0x796d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS740|RADEON_IS_IGP|RADEON_NEW_MEMMAP|RADEON_IS_IGPGART}, \ + {0x1002, 0x796e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS740|RADEON_IS_IGP|RADEON_NEW_MEMMAP|RADEON_IS_IGPGART}, \ + {0x1002, 0x796f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS740|RADEON_IS_IGP|RADEON_NEW_MEMMAP|RADEON_IS_IGPGART}, \ + {0x1002, 0x9400, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9401, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9402, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9403, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9405, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x940A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x940B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x940F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R600|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94A1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94A3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94B1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94B3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94B4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94B5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94B9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV740|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9440, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9441, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9442, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9443, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9444, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9446, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x944A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x944B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x944C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x944E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9450, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9452, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9456, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x945A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x945B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x945E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9460, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9462, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x946A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x946B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x947A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x947B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV770|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9480, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9487, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9488, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9489, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x948A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x948F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9490, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9491, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9495, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9498, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x949C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x949E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x949F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV730|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C6, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94C9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94CB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94CC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x94CD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV610|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9500, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9501, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9504, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9505, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9506, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9507, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9508, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9509, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x950F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9511, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9515, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9517, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9519, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV670|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9540, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9541, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9542, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x954E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x954F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9552, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9553, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9555, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9557, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x955f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV710|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9580, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9581, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9583, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9586, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9587, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9588, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9589, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x958A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x958B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x958C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x958D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x958E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x958F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV630|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9590, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9591, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9593, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9595, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9596, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9597, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9598, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9599, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x959B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV635|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95C0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95C2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95C4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95C5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95C6, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95C7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95C9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95CC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95CD, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95CE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x95CF, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV620|RADEON_NEW_MEMMAP}, \ + {0x1002, 0x9610, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS780|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9611, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS780|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9612, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS780|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9613, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS780|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9614, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS780|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9615, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS780|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9616, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS780|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9640, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9641, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9642, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO2|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9643, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO2|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9644, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO2|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9645, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO2|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9647, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP},\ + {0x1002, 0x9648, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP},\ + {0x1002, 0x9649, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO2|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP},\ + {0x1002, 0x964a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x964b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x964c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x964e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP},\ + {0x1002, 0x964f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_SUMO|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP},\ + {0x1002, 0x9710, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS880|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9711, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS880|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9712, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS880|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9713, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS880|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9714, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS880|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9715, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS880|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9802, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9803, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9804, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9805, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9806, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9807, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9808, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9809, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x980A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_PALM|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9830, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9831, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9832, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9833, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9834, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9836, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9838, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9839, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x983a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x983b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x983c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x983d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x983e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x983f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_KABINI|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9850, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9851, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9852, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9853, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9854, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9855, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9856, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9857, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9858, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9859, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x985A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x985B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x985C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x985D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x985E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x985F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_MULLINS|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9900, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9901, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9903, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9904, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9905, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9906, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9907, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9908, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9909, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x990A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x990B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x990C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x990D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x990E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x990F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9910, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9913, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9917, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9918, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9919, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9990, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9991, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9992, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9993, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9994, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9995, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9996, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9997, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9998, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x9999, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x999A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x999B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x999C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x999D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x99A0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x99A2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_IS_MOBILITY|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0x1002, 0x99A4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_ARUBA|RADEON_NEW_MEMMAP|RADEON_IS_IGP}, \ + {0, 0, 0, 0, 0, 0, 0} + +static struct pci_device_id pciidlist[] = {radeon_PCI_IDS}; + +//============================================================ +// ati_family +//============================================================ + +int ati_family(int vendor, int device) +{ + int i = 0; + while (pciidlist[i].vendor) + { + if (pciidlist[i].vendor == vendor && pciidlist[i].device == device) + return (pciidlist[i].driver_data & RADEON_FAMILY_MASK); + i++; + } + // Not found, must be newer + if (vendor == 0x1002) + return CHIP_LAST; + + return 0; +} + +//============================================================ +// ati_is_legacy +//============================================================ + +bool ati_is_legacy(int vendor, int device) +{ + return (ati_family(vendor, device) < CHIP_CEDAR); +} + +#endif diff --git a/3rdparty/switchres/custom_video_drmkms.cpp b/3rdparty/switchres/custom_video_drmkms.cpp new file mode 100644 index 0000000000000..42c5b9b4998fb --- /dev/null +++ b/3rdparty/switchres/custom_video_drmkms.cpp @@ -0,0 +1,795 @@ +/************************************************************** + + custom_video_drmkms.cpp - Linux DRM/KMS video management layer + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "custom_video_drmkms.h" +#include "log.h" + +#define drmGetVersion p_drmGetVersion +#define drmFreeVersion p_drmFreeVersion +#define drmModeGetResources p_drmModeGetResources +#define drmModeGetConnector p_drmModeGetConnector +#define drmModeFreeConnector p_drmModeFreeConnector +#define drmModeFreeResources p_drmModeFreeResources +#define drmModeGetEncoder p_drmModeGetEncoder +#define drmModeFreeEncoder p_drmModeFreeEncoder +#define drmModeGetCrtc p_drmModeGetCrtc +#define drmModeSetCrtc p_drmModeSetCrtc +#define drmModeFreeCrtc p_drmModeFreeCrtc +#define drmModeAttachMode p_drmModeAttachMode +#define drmModeAddFB p_drmModeAddFB +#define drmModeRmFB p_drmModeRmFB +#define drmModeGetFB p_drmModeGetFB +#define drmModeFreeFB p_drmModeFreeFB +#define drmPrimeHandleToFD p_drmPrimeHandleToFD +#define drmModeGetPlaneResources p_drmModeGetPlaneResources +#define drmModeFreePlaneResources p_drmModeFreePlaneResources +#define drmIoctl p_drmIoctl +#define drmGetCap p_drmGetCap +#define drmIsMaster p_drmIsMaster +#define drmSetMaster p_drmSetMaster +#define drmDropMaster p_drmDropMaster + +//============================================================ +// shared the privileges of the master fd +//============================================================ + +static int s_shared_fd[10] = {}; +static int s_shared_count[10] = {}; + +//============================================================ +// list connector types +//============================================================ + +const char *get_connector_name(int mode) +{ + switch (mode) + { + case DRM_MODE_CONNECTOR_Unknown: + return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: + return "VGA-"; + case DRM_MODE_CONNECTOR_DVII: + return "DVI-I-"; + case DRM_MODE_CONNECTOR_DVID: + return "DVI-D-"; + case DRM_MODE_CONNECTOR_DVIA: + return "DVI-A-"; + case DRM_MODE_CONNECTOR_Composite: + return "Composite-"; + case DRM_MODE_CONNECTOR_SVIDEO: + return "SVIDEO-"; + case DRM_MODE_CONNECTOR_LVDS: + return "LVDS-"; + case DRM_MODE_CONNECTOR_Component: + return "Component-"; + case DRM_MODE_CONNECTOR_9PinDIN: + return "9PinDIN-"; + case DRM_MODE_CONNECTOR_DisplayPort: + return "DisplayPort-"; + case DRM_MODE_CONNECTOR_HDMIA: + return "HDMI-A-"; + case DRM_MODE_CONNECTOR_HDMIB: + return "HDMI-B-"; + case DRM_MODE_CONNECTOR_TV: + return "TV-"; + case DRM_MODE_CONNECTOR_eDP: + return "eDP-"; + case DRM_MODE_CONNECTOR_VIRTUAL: + return "VIRTUAL-"; + case DRM_MODE_CONNECTOR_DSI: + return "DSI-"; + case DRM_MODE_CONNECTOR_DPI: + return "DPI-"; + default: + return "not_defined-"; + } +} + +//============================================================ +// id for class object (static) +//============================================================ + +static int static_id = 0; + +//============================================================ +// drmkms_timing::drmkms_timing +//============================================================ + +drmkms_timing::drmkms_timing(char *device_name, custom_video_settings *vs) +{ + m_vs = *vs; + m_id = ++static_id; + + log_verbose("DRM/KMS: <%d> (drmkms_timing) creation (%s)\n", m_id, device_name); + // Copy screen device name and limit size + if ((strlen(device_name) + 1) > 32) + { + strncpy(m_device_name, device_name, 31); + log_error("DRM/KMS: <%d> (drmkms_timing) [ERROR] the devine name is too long it has been trucated to %s\n", m_id, m_device_name); + } + else + strcpy(m_device_name, device_name); +} + +//============================================================ +// drmkms_timing::~drmkms_timing +//============================================================ + +drmkms_timing::~drmkms_timing() +{ + // close DRM/KMS library + if (mp_drm_handle) + dlclose(mp_drm_handle); + + if (m_drm_fd > 0) + { + if (!--s_shared_count[m_card_id]) + close(m_drm_fd); + } +} + +//============================================================ +// drmkms_timing::init +//============================================================ + +bool drmkms_timing::init() +{ + log_verbose("DRM/KMS: <%d> (init) loading DRM/KMS library\n", m_id); + mp_drm_handle = dlopen("libdrm.so", RTLD_NOW); + if (mp_drm_handle) + { + p_drmGetVersion = (__typeof__(drmGetVersion)) dlsym(mp_drm_handle, "drmGetVersion"); + if (p_drmGetVersion == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmGetVersion", "DRM_LIBRARY"); + return false; + } + + p_drmFreeVersion = (__typeof__(drmFreeVersion)) dlsym(mp_drm_handle, "drmFreeVersion"); + if (p_drmFreeVersion == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmFreeVersion", "DRM_LIBRARY"); + return false; + } + + p_drmModeGetResources = (__typeof__(drmModeGetResources)) dlsym(mp_drm_handle, "drmModeGetResources"); + if (p_drmModeGetResources == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeGetResources", "DRM_LIBRARY"); + return false; + } + + p_drmModeGetConnector = (__typeof__(drmModeGetConnector)) dlsym(mp_drm_handle, "drmModeGetConnector"); + if (p_drmModeGetConnector == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeGetConnector", "DRM_LIBRARY"); + return false; + } + + p_drmModeFreeConnector = (__typeof__(drmModeFreeConnector)) dlsym(mp_drm_handle, "drmModeFreeConnector"); + if (p_drmModeFreeConnector == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeFreeConnector", "DRM_LIBRARY"); + return false; + } + + p_drmModeFreeResources = (__typeof__(drmModeFreeResources)) dlsym(mp_drm_handle, "drmModeFreeResources"); + if (p_drmModeFreeResources == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeFreeResources", "DRM_LIBRARY"); + return false; + } + + p_drmModeGetEncoder = (__typeof__(drmModeGetEncoder)) dlsym(mp_drm_handle, "drmModeGetEncoder"); + if (p_drmModeGetEncoder == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeGetEncoder", "DRM_LIBRARY"); + return false; + } + + p_drmModeFreeEncoder = (__typeof__(drmModeFreeEncoder)) dlsym(mp_drm_handle, "drmModeFreeEncoder"); + if (p_drmModeFreeEncoder == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeFreeEncoder", "DRM_LIBRARY"); + return false; + } + + p_drmModeGetCrtc = (__typeof__(drmModeGetCrtc)) dlsym(mp_drm_handle, "drmModeGetCrtc"); + if (p_drmModeGetCrtc == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeGetCrtc", "DRM_LIBRARY"); + return false; + } + + p_drmModeSetCrtc = (__typeof__(drmModeSetCrtc)) dlsym(mp_drm_handle, "drmModeSetCrtc"); + if (p_drmModeSetCrtc == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeSetCrtc", "DRM_LIBRARY"); + return false; + } + + p_drmModeFreeCrtc = (__typeof__(drmModeFreeCrtc)) dlsym(mp_drm_handle, "drmModeFreeCrtc"); + if (p_drmModeFreeCrtc == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeFreeCrtc", "DRM_LIBRARY"); + return false; + } + + p_drmModeAttachMode = (__typeof__(drmModeAttachMode)) dlsym(mp_drm_handle, "drmModeAttachMode"); + if (p_drmModeAttachMode == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeAttachMode", "DRM_LIBRARY"); + return false; + } + + p_drmModeAddFB = (__typeof__(drmModeAddFB)) dlsym(mp_drm_handle, "drmModeAddFB"); + if (p_drmModeAddFB == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeAddFB", "DRM_LIBRARY"); + return false; + } + + p_drmModeRmFB = (__typeof__(drmModeRmFB)) dlsym(mp_drm_handle, "drmModeRmFB"); + if (p_drmModeRmFB == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeRmFB", "DRM_LIBRARY"); + return false; + } + + p_drmModeGetFB = (__typeof__(drmModeGetFB)) dlsym(mp_drm_handle, "drmModeGetFB"); + if (p_drmModeGetFB == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeGetFB", "DRM_LIBRARY"); + return false; + } + + p_drmModeFreeFB = (__typeof__(drmModeFreeFB)) dlsym(mp_drm_handle, "drmModeFreeFB"); + if (p_drmModeFreeFB == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeFreeFB", "DRM_LIBRARY"); + return false; + } + + p_drmPrimeHandleToFD = (__typeof__(drmPrimeHandleToFD)) dlsym(mp_drm_handle, "drmPrimeHandleToFD"); + if (p_drmPrimeHandleToFD == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmPrimeHandleToFD", "DRM_LIBRARY"); + return false; + } + + p_drmModeGetPlaneResources = (__typeof__(drmModeGetPlaneResources)) dlsym(mp_drm_handle, "drmModeGetPlaneResources"); + if (p_drmModeGetPlaneResources == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeGetPlaneResources", "DRM_LIBRARY"); + return false; + } + + p_drmModeFreePlaneResources = (__typeof__(drmModeFreePlaneResources)) dlsym(mp_drm_handle, "drmModeFreePlaneResources"); + if (p_drmModeFreePlaneResources == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmModeFreePlaneResources", "DRM_LIBRARY"); + return false; + } + + p_drmIoctl = (__typeof__(drmIoctl)) dlsym(mp_drm_handle, "drmIoctl"); + if (p_drmIoctl == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmIoctl", "DRM_LIBRARY"); + return false; + } + + p_drmGetCap = (__typeof__(drmGetCap)) dlsym(mp_drm_handle, "drmGetCap"); + if (p_drmGetCap == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmGetCap", "DRM_LIBRARY"); + return false; + } + + p_drmIsMaster = (__typeof__(drmIsMaster)) dlsym(mp_drm_handle, "drmIsMaster"); + if (p_drmIsMaster == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmIsMaster", "DRM_LIBRARY"); + return false; + } + + p_drmSetMaster = (__typeof__(drmSetMaster)) dlsym(mp_drm_handle, "drmSetMaster"); + if (p_drmSetMaster == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmSetMaster", "DRM_LIBRARY"); + return false; + } + + p_drmDropMaster = (__typeof__(drmDropMaster)) dlsym(mp_drm_handle, "drmDropMaster"); + if (p_drmDropMaster == NULL) + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing func %s in %s", m_id, "drmDropMaster", "DRM_LIBRARY"); + return false; + } + } + else + { + log_error("DRM/KMS: <%d> (init) [ERROR] missing %s library\n", m_id, "DRM/KMS_LIBRARY"); + return false; + } + + int screen_pos = -1; + + // Handle the screen name, "auto", "screen[0-9]" and device name + if (strlen(m_device_name) == 7 && !strncmp(m_device_name, "screen", 6) && m_device_name[6] >= '0' && m_device_name[6] <= '9') + screen_pos = m_device_name[6] - '0'; + else if (strlen(m_device_name) == 1 && m_device_name[0] >= '0' && m_device_name[0] <= '9') + screen_pos = m_device_name[0] - '0'; + + char drm_name[15] = "/dev/dri/card_"; + drmModeRes *p_res; + drmModeConnector *p_connector; + + int output_position = 0; + for (int num = 0; !m_desktop_output && num < 10; num++) + { + drm_name[13] = '0' + num; + m_drm_fd = open(drm_name, O_RDWR | O_CLOEXEC); + + if (m_drm_fd > 0) + { + drmVersion *version = drmGetVersion(m_drm_fd); + log_verbose("DRM/KMS: <%d> (init) version %d.%d.%d type %s\n", m_id, version->version_major, version->version_minor, version->version_patchlevel, version->name); + drmFreeVersion(version); + + uint64_t check_dumb = 0; + if (drmGetCap(m_drm_fd, DRM_CAP_DUMB_BUFFER, &check_dumb) < 0) + log_error("DRM/KMS: <%d> (init) [ERROR] ioctl DRM_CAP_DUMB_BUFFER\n", m_id); + + if (!check_dumb) + log_error("DRM/KMS: <%d> (init) [ERROR] dumb buffer not supported\n", m_id); + + p_res = drmModeGetResources(m_drm_fd); + + for (int i = 0; i < p_res->count_connectors; i++) + { + p_connector = drmModeGetConnector(m_drm_fd, p_res->connectors[i]); + if (p_connector) + { + char connector_name[32]; + snprintf(connector_name, 32, "%s%d", get_connector_name(p_connector->connector_type), p_connector->connector_type_id); + log_verbose("DRM/KMS: <%d> (init) card %d connector %d id %d name %s status %d - modes %d\n", m_id, num, i, p_connector->connector_id, connector_name, p_connector->connection, p_connector->count_modes); + // detect desktop connector + if (!m_desktop_output && p_connector->connection == DRM_MODE_CONNECTED) + { + if (!strcmp(m_device_name, "auto") || !strcmp(m_device_name, connector_name) || output_position == screen_pos) + { + m_desktop_output = p_connector->connector_id; + m_card_id = num; + log_verbose("DRM/KMS: <%d> (init) card %d connector %d id %d name %s selected as primary output\n", m_id, num, i, m_desktop_output, connector_name); + + drmModeEncoder *p_encoder = drmModeGetEncoder(m_drm_fd, p_connector->encoder_id); + + if (p_encoder) + { + for (int e = 0; e < p_res->count_crtcs; e++) + { + mp_crtc_desktop = drmModeGetCrtc(m_drm_fd, p_res->crtcs[e]); + + if (mp_crtc_desktop->crtc_id == p_encoder->crtc_id) + { + log_verbose("DRM/KMS: <%d> (init) desktop mode name %s crtc %d fb %d valid %d\n", m_id, mp_crtc_desktop->mode.name, mp_crtc_desktop->crtc_id, mp_crtc_desktop->buffer_id, mp_crtc_desktop->mode_valid); + break; + } + drmModeFreeCrtc(mp_crtc_desktop); + } + } + if (!mp_crtc_desktop) + log_error("DRM/KMS: <%d> (init) [ERROR] no crtc found\n", m_id); + drmModeFreeEncoder(p_encoder); + } + output_position++; + } + drmModeFreeConnector(p_connector); + } + else + log_error("DRM/KMS: <%d> (init) [ERROR] card %d connector %d - %d\n", m_id, num, i, p_res->connectors[i]); + } + drmModeFreeResources(p_res); + if (!m_desktop_output) + close(m_drm_fd); + else + { + if (drmIsMaster(m_drm_fd)) + { + s_shared_fd[m_card_id] = m_drm_fd; + s_shared_count[m_card_id] = 1; + drmDropMaster(m_drm_fd); + } + else + { + if (s_shared_count[m_card_id] > 0) + { + close(m_drm_fd); + m_drm_fd = s_shared_fd[m_card_id]; + s_shared_count[m_card_id]++; + } + else if (m_id == 1) + { + log_verbose("DRM/KMS: <%d> (init) looking for the DRM master\n", m_id); + int fd = drm_master_hook(m_drm_fd); + if (fd) + { + close(m_drm_fd); + m_drm_fd = fd; + s_shared_fd[m_card_id] = m_drm_fd; + // start at 2 to disable closing the fd + s_shared_count[m_card_id] = 2; + } + } + } + if (!drmIsMaster(m_drm_fd)) + log_error("DRM/KMS: <%d> (init) [ERROR] limited DRM rights on this screen\n", m_id); + } + } + else + { + if (!num) + log_error("DRM/KMS: <%d> (init) [ERROR] cannot open device %s\n", m_id, drm_name); + break; + } + } + + // Handle no screen detected case + if (!m_desktop_output) + { + log_error("DRM/KMS: <%d> (init) [ERROR] no screen detected\n", m_id); + return false; + } + else + { + } + + return true; +} + +//============================================================ +// drmkms_timing::drm_master_hook +//============================================================ + +int drmkms_timing::drm_master_hook(int last_fd) +{ + for (int fd = 4; fd < last_fd; fd++) + { + struct stat st; + if (!fstat(fd, &st)) + { + // in case of multiple video cards, it wouldd be better to compare dri number + if (S_ISCHR(st.st_mode)) + { + if (drmIsMaster(fd)) + { + drmVersion *version_hook = drmGetVersion(m_drm_fd); + log_verbose("DRM/KMS: <%d> (init) DRM hook created version %d.%d.%d type %s\n", m_id, version_hook->version_major, version_hook->version_minor, version_hook->version_patchlevel, version_hook->name); + drmFreeVersion(version_hook); + return fd; + } + } + } + } + return 0; +} + +//============================================================ +// drmkms_timing::update_mode +//============================================================ + +bool drmkms_timing::update_mode(modeline *mode) +{ + if (!mode) + return false; + + if (!m_desktop_output) + { + log_error("DRM/KMS: <%d> (update_mode) [ERROR] no screen detected\n", m_id); + return false; + } + + if (!delete_mode(mode)) + { + log_error("DRM/KMS: <%d> (update_mode) [ERROR] delete operation not successful", m_id); + return false; + } + + if (!add_mode(mode)) + { + log_error("DRM/KMS: <%d> (update_mode) [ERROR] add operation not successful", m_id); + return false; + } + + return true; +} + +//============================================================ +// drmkms_timing::add_mode +//============================================================ + +bool drmkms_timing::add_mode(modeline *mode) +{ + if (!mode) + return false; + + // Handle no screen detected case + if (!m_desktop_output) + { + log_error("DRM/KMS: <%d> (add_mode) [ERROR] no screen detected\n", m_id); + return false; + } + + if (!mp_crtc_desktop) + { + log_error("DRM/KMS: <%d> (add_mode) [ERROR] no desktop crtc\n", m_id); + return false; + } + + if (!mode) + return false; + + return true; +} + +//============================================================ +// drmkms_timing::set_timing +//============================================================ + +bool drmkms_timing::set_timing(modeline *mode) +{ + if (!mode) + return false; + + // Handle no screen detected case + if (!m_desktop_output) + { + log_error("DRM/KMS: <%d> (set_timing) [ERROR] no screen detected\n", m_id); + return false; + } + + drmSetMaster(m_drm_fd); + + // Setup the DRM mode structure + drmModeModeInfo dmode = {}; + + // Create specific mode name + snprintf(dmode.name, 32, "SR-%d_%dx%d@%.02f%s", m_id, mode->hactive, mode->vactive, mode->vfreq, mode->interlace ? "i" : ""); + dmode.clock = mode->pclock / 1000; + dmode.hdisplay = mode->hactive; + dmode.hsync_start = mode->hbegin; + dmode.hsync_end = mode->hend; + dmode.htotal = mode->htotal; + dmode.vdisplay = mode->vactive; + dmode.vsync_start = mode->vbegin; + dmode.vsync_end = mode->vend; + dmode.vtotal = mode->vtotal; + dmode.flags = (mode->interlace ? DRM_MODE_FLAG_INTERLACE : 0) | (mode->doublescan ? DRM_MODE_FLAG_DBLSCAN : 0) | (mode->hsync ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC) | (mode->vsync ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC); + + dmode.hskew = 0; + dmode.vscan = 0; + + dmode.vrefresh = mode->refresh; // Used only for human readable output + + dmode.type = DRM_MODE_TYPE_USERDEF; //DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + + mode->type |= CUSTOM_VIDEO_TIMING_DRMKMS; + + if (mode->platform_data == 4815162342) + { + log_verbose("DRM/KMS: <%d> (set_timing) restore desktop mode\n", m_id); + drmModeSetCrtc(m_drm_fd, mp_crtc_desktop->crtc_id, mp_crtc_desktop->buffer_id, mp_crtc_desktop->x, mp_crtc_desktop->y, &m_desktop_output, 1, &mp_crtc_desktop->mode); + if (m_dumb_handle) + { + int ret = ioctl(m_drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &m_dumb_handle); + if (ret) + log_verbose("DRM/KMS: <%d> (add_mode) [ERROR] ioctl DRM_IOCTL_MODE_DESTROY_DUMB %d\n", m_id, ret); + m_dumb_handle = 0; + } + if (m_framebuffer_id && m_framebuffer_id != mp_crtc_desktop->buffer_id) + { + if (drmModeRmFB(m_drm_fd, m_framebuffer_id)) + log_verbose("DRM/KMS: <%d> (add_mode) [ERROR] remove frame buffer\n", m_id); + m_framebuffer_id = 0; + } + } + else + { + unsigned int old_dumb_handle = m_dumb_handle; + + drmModeFB *pframebuffer = drmModeGetFB(m_drm_fd, mp_crtc_desktop->buffer_id); + log_verbose("DRM/KMS: <%d> (add_mode) existing frame buffer id %d size %dx%d bpp %d\n", m_id, mp_crtc_desktop->buffer_id, pframebuffer->width, pframebuffer->height, pframebuffer->bpp); + //drmModePlaneRes *pplanes = drmModeGetPlaneResources(m_drm_fd); + //log_verbose("DRM/KMS: <%d> (add_mode) total planes %d\n", m_id, pplanes->count_planes); + //drmModeFreePlaneResources(pplanes); + + unsigned int framebuffer_id = mp_crtc_desktop->buffer_id; + + //if (pframebuffer->width < dmode.hdisplay || pframebuffer->height < dmode.vdisplay) + if (1) + { + log_verbose("DRM/KMS: <%d> (add_mode) creating new frame buffer with size %dx%d\n", m_id, dmode.hdisplay, dmode.vdisplay); + + // create a new dumb fb (not driver specefic) + drm_mode_create_dumb create_dumb = {}; + create_dumb.width = dmode.hdisplay; + create_dumb.height = dmode.vdisplay; + create_dumb.bpp = pframebuffer->bpp; + + int ret = ioctl(m_drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); + if (ret) + log_verbose("DRM/KMS: <%d> (add_mode) [ERROR] ioctl DRM_IOCTL_MODE_CREATE_DUMB %d\n", m_id, ret); + + if (drmModeAddFB(m_drm_fd, dmode.hdisplay, dmode.vdisplay, pframebuffer->depth, pframebuffer->bpp, create_dumb.pitch, create_dumb.handle, &framebuffer_id)) + log_error("DRM/KMS: <%d> (add_mode) [ERROR] cannot add frame buffer\n", m_id); + else + m_dumb_handle = create_dumb.handle; + + drm_mode_map_dumb map_dumb = {}; + map_dumb.handle = create_dumb.handle; + + ret = drmIoctl(m_drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); + if (ret) + log_verbose("DRM/KMS: <%d> (add_mode) [ERROR] ioctl DRM_IOCTL_MODE_MAP_DUMB %d\n", m_id, ret); + + void *map = mmap(0, create_dumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, m_drm_fd, map_dumb.offset); + if (map != MAP_FAILED) + { + // clear the frame buffer + memset(map, 0, create_dumb.size); + } + else + log_verbose("DRM/KMS: <%d> (add_mode) [ERROR] failed to map frame buffer %p\n", m_id, map); + } + else + log_verbose("DRM/KMS: <%d> (add_mode) use existing frame buffer\n", m_id); + + drmModeFreeFB(pframebuffer); + + pframebuffer = drmModeGetFB(m_drm_fd, framebuffer_id); + log_verbose("DRM/KMS: <%d> (add_mode) frame buffer id %d size %dx%d bpp %d\n", m_id, framebuffer_id, pframebuffer->width, pframebuffer->height, pframebuffer->bpp); + drmModeFreeFB(pframebuffer); + + // set the mode on the crtc + if (drmModeSetCrtc(m_drm_fd, mp_crtc_desktop->crtc_id, framebuffer_id, 0, 0, &m_desktop_output, 1, &dmode)) + log_error("DRM/KMS: <%d> (add_mode) [ERROR] cannot attach the mode to the crtc %d frame buffer %d\n", m_id, mp_crtc_desktop->crtc_id, framebuffer_id); + else + { + if (old_dumb_handle) + { + log_verbose("DRM/KMS: <%d> (add_mode) remove old dumb %d\n", m_id, old_dumb_handle); + int ret = ioctl(m_drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &old_dumb_handle); + if (ret) + log_verbose("DRM/KMS: <%d> (add_mode) [ERROR] ioctl DRM_IOCTL_MODE_DESTROY_DUMB %d\n", m_id, ret); + old_dumb_handle = 0; + } + if (m_framebuffer_id && framebuffer_id != mp_crtc_desktop->buffer_id) + { + log_verbose("DRM/KMS: <%d> (add_mode) remove old frame buffer %d\n", m_id, m_framebuffer_id); + drmModeRmFB(m_drm_fd, m_framebuffer_id); + m_framebuffer_id = 0; + } + m_framebuffer_id = framebuffer_id; + } + } + drmDropMaster(m_drm_fd); + + return true; +} + +//============================================================ +// drmkms_timing::delete_mode +//============================================================ + +bool drmkms_timing::delete_mode(modeline *mode) +{ + if (!mode) + return false; + + // Handle no screen detected case + if (!m_desktop_output) + { + log_error("DRM/KMS: <%d> (delete_mode) [ERROR] no screen detected\n", m_id); + return false; + } + + return true; +} + +//============================================================ +// drmkms_timing::get_timing +//============================================================ + +bool drmkms_timing::get_timing(modeline *mode) +{ + // Handle no screen detected case + if (!m_desktop_output) + { + log_error("DRM/KMS: <%d> (get_timing) [ERROR] no screen detected\n", m_id); + return false; + } + + // INFO: not used vrefresh, hskew, vscan + drmModeRes *p_res = drmModeGetResources(m_drm_fd); + + for (int i = 0; i < p_res->count_connectors; i++) + { + drmModeConnector *p_connector = drmModeGetConnector(m_drm_fd, p_res->connectors[i]); + + // Cycle through the modelines and report them back to the display manager + if (p_connector && m_desktop_output == p_connector->connector_id) + { + if (m_video_modes_position < p_connector->count_modes) + { + drmModeModeInfo *pdmode = &p_connector->modes[m_video_modes_position++]; + + // Use mode position as index + mode->platform_data = m_video_modes_position; + + mode->pclock = pdmode->clock * 1000; + mode->hactive = pdmode->hdisplay; + mode->hbegin = pdmode->hsync_start; + mode->hend = pdmode->hsync_end; + mode->htotal = pdmode->htotal; + mode->vactive = pdmode->vdisplay; + mode->vbegin = pdmode->vsync_start; + mode->vend = pdmode->vsync_end; + mode->vtotal = pdmode->vtotal; + mode->interlace = (pdmode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0; + mode->doublescan = (pdmode->flags & DRM_MODE_FLAG_DBLSCAN) ? 1 : 0; + mode->hsync = (pdmode->flags & DRM_MODE_FLAG_PHSYNC) ? 1 : 0; + mode->vsync = (pdmode->flags & DRM_MODE_FLAG_PVSYNC) ? 1 : 0; + + mode->hfreq = mode->pclock / mode->htotal; + mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace ? 2 : 1); + mode->refresh = mode->vfreq; + + mode->width = pdmode->hdisplay; + mode->height = pdmode->vdisplay; + + // Add the rotation flag from the plane (DRM_MODE_ROTATE_xxx) + // TODO: mode->type |= MODE_ROTATED; + + mode->type |= CUSTOM_VIDEO_TIMING_DRMKMS; + + if (strncmp(pdmode->name, "SR-", 3) == 0) + log_verbose("DRM/KMS: <%d> (get_timing) [WARNING] modeline %s detected\n", m_id, pdmode->name); + else if (!strcmp(pdmode->name, mp_crtc_desktop->mode.name) && pdmode->clock == mp_crtc_desktop->mode.clock && pdmode->vrefresh == mp_crtc_desktop->mode.vrefresh) + { + // Add the desktop flag to desktop modeline + log_verbose("DRM/KMS: <%d> (get_timing) desktop mode name %s refresh %d found\n", m_id, mp_crtc_desktop->mode.name, mp_crtc_desktop->mode.vrefresh); + mode->type |= MODE_DESKTOP; + mode->platform_data = 4815162342; + } + } + else + { + // Inititalise the position for the modeline list + m_video_modes_position = 0; + } + } + drmModeFreeConnector(p_connector); + } + drmModeFreeResources(p_res); + + return true; +} diff --git a/3rdparty/switchres/custom_video_drmkms.h b/3rdparty/switchres/custom_video_drmkms.h new file mode 100644 index 0000000000000..a85882255132b --- /dev/null +++ b/3rdparty/switchres/custom_video_drmkms.h @@ -0,0 +1,81 @@ +/************************************************************** + + custom_video_drmkms.h - Linux DRM/KMS video management layer + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __CUSTOM_VIDEO_DRMKMS_ +#define __CUSTOM_VIDEO_DRMKMS_ + +// DRM headers +#include +#include +#include "custom_video.h" + +class drmkms_timing : public custom_video +{ + public: + drmkms_timing(char *device_name, custom_video_settings *vs); + ~drmkms_timing(); + const char *api_name() { return "DRMKMS"; } + int caps() { return CUSTOM_VIDEO_CAPS_ADD; } + bool init(); + + bool add_mode(modeline *mode); + bool delete_mode(modeline *mode); + bool update_mode(modeline *mode); + + bool get_timing(modeline *mode); + bool set_timing(modeline *mode); + + private: + int m_id = 0; + + int m_drm_fd = 0; + drmModeCrtc *mp_crtc_desktop = NULL; + int m_card_id = 0; + int drm_master_hook(int fd); + + char m_device_name[32]; + unsigned int m_desktop_output = 0; + int m_video_modes_position = 0; + + void *mp_drm_handle = NULL; + unsigned int m_dumb_handle = 0; + unsigned int m_framebuffer_id = 0; + + __typeof__(drmGetVersion) *p_drmGetVersion; + __typeof__(drmFreeVersion) *p_drmFreeVersion; + __typeof__(drmModeGetResources) *p_drmModeGetResources; + __typeof__(drmModeGetConnector) *p_drmModeGetConnector; + __typeof__(drmModeFreeConnector) *p_drmModeFreeConnector; + __typeof__(drmModeFreeResources) *p_drmModeFreeResources; + __typeof__(drmModeGetEncoder) *p_drmModeGetEncoder; + __typeof__(drmModeFreeEncoder) *p_drmModeFreeEncoder; + __typeof__(drmModeGetCrtc) *p_drmModeGetCrtc; + __typeof__(drmModeSetCrtc) *p_drmModeSetCrtc; + __typeof__(drmModeFreeCrtc) *p_drmModeFreeCrtc; + __typeof__(drmModeAttachMode) *p_drmModeAttachMode; + __typeof__(drmModeAddFB) *p_drmModeAddFB; + __typeof__(drmModeRmFB) *p_drmModeRmFB; + __typeof__(drmModeGetFB) *p_drmModeGetFB; + __typeof__(drmModeFreeFB) *p_drmModeFreeFB; + __typeof__(drmPrimeHandleToFD) *p_drmPrimeHandleToFD; + __typeof__(drmModeGetPlaneResources) *p_drmModeGetPlaneResources; + __typeof__(drmModeFreePlaneResources) *p_drmModeFreePlaneResources; + __typeof__(drmIoctl) *p_drmIoctl; + __typeof__(drmGetCap) *p_drmGetCap; + __typeof__(drmIsMaster) *p_drmIsMaster; + __typeof__(drmSetMaster) *p_drmSetMaster; + __typeof__(drmDropMaster) *p_drmDropMaster; +}; + +#endif diff --git a/3rdparty/switchres/custom_video_pstrip.cpp b/3rdparty/switchres/custom_video_pstrip.cpp new file mode 100644 index 0000000000000..977c000104c60 --- /dev/null +++ b/3rdparty/switchres/custom_video_pstrip.cpp @@ -0,0 +1,629 @@ +/************************************************************** + + custom_video_pstrip.cpp - PowerStrip interface routines + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +/* http://forums.entechtaiwan.com/index.php?topic=5534.msg20902;topicseen#msg20902 + + UM_SETCUSTOMTIMING = WM_USER+200; + wparam = monitor number, zero-based + lparam = atom for string pointer + lresult = -1 for failure else current pixel clock (integer in Hz) + Note: pass full PowerStrip timing string* + + UM_SETREFRESHRATE = WM_USER+201; + wparam = monitor number, zero-based + lparam = refresh rate (integer in Hz), or 0 for read-only + lresult = -1 for failure else current refresh rate (integer in Hz) + + UM_SETPOLARITY = WM_USER+202; + wparam = monitor number, zero-based + lparam = polarity bits + lresult = -1 for failure else current polarity bits+1 + + UM_REMOTECONTROL = WM_USER+210; + wparam = 99 + lparam = + 0 to hide tray icon + 1 to show tray icon, + 2 to get build number + 10 to show Performance profiles + 11 to show Color profiles + 12 to show Display profiles + 13 to show Application profiles + 14 to show Adapter information + 15 to show Monitor information + 16 to show Hotkey manager + 17 to show Resource manager + 18 to show Preferences + 19 to show Online services + 20 to show About screen + 21 to show Tip-of-the-day + 22 to show Setup wizard + 23 to show Screen fonts + 24 to show Advanced timing options + 25 to show Custom resolutions + 99 to close PS + lresult = -1 for failure else lparam+1 for success or build number (e.g., 335) + if lparam was 2 + + UM_SETGAMMARAMP = WM_USER+203; + wparam = monitor number, zero-based + lparam = atom for string pointer + lresult = -1 for failure, 1 for success + + UM_CREATERESOLUTION = WM_USER+204; + wparam = monitor number, zero-based + lparam = atom for string pointer + lresult = -1 for failure, 1 for success + Note: pass full PowerStrip timing string*; reboot is usually necessary to see if + the resolution is accepted by the display driver + + UM_GETTIMING = WM_USER+205; + wparam = monitor number, zero-based + lresult = -1 for failure else GlobalAtom number identifiying the timing string* + Note: be sure to call GlobalDeleteAtom after reading the string associated with + the atom + + UM_GETSETCLOCKS = WM_USER+206; + wparam = monitor number, zero-based + lparam = atom for string pointer + lresult = -1 for failure else GlobalAtom number identifiying the performance + string** + Note: pass full PowerStrip performance string** to set the clocks, and ull to + get clocks; be sure to call GlobalDeleteAtom after reading the string associated + with the atom + + NegativeHorizontalPolarity = 0x02; + NegativeVerticalPolarity = 0x04; + + *Timing string parameter definition: + 1 = horizontal active pixels + 2 = horizontal front porch + 3 = horizontal sync width + 4 = horizontal back porch + 5 = vertical active pixels + 6 = vertical front porch + 7 = vertical sync width + 8 = vertical back porch + 9 = pixel clock in hertz + 10 = timing flags, where bit: + 1 = negative horizontal porlarity + 2 = negative vertical polarity + 3 = interlaced + 5 = composite sync + 7 = sync-on-green + all other bits reserved + + **Performance string parameter definition: + 1 = memory clock in hertz + 2 = engine clock in hertz + 3 = reserved + 4 = reserved + 5 = reserved + 6 = reserved + 7 = reserved + 8 = reserved + 9 = 2D memory clock in hertz (if different from 3D) + 10 = 2D engine clock in hertz (if different from 3D) */ + +#include +#include + +#include "custom_video_pstrip.h" +#include "log.h" + +//============================================================ +// CONSTANTS +//============================================================ + +#define UM_SETCUSTOMTIMING (WM_USER+200) +#define UM_SETREFRESHRATE (WM_USER+201) +#define UM_SETPOLARITY (WM_USER+202) +#define UM_REMOTECONTROL (WM_USER+210) +#define UM_SETGAMMARAMP (WM_USER+203) +#define UM_CREATERESOLUTION (WM_USER+204) +#define UM_GETTIMING (WM_USER+205) +#define UM_GETSETCLOCKS (WM_USER+206) +#define UM_SETCUSTOMTIMINGFAST (WM_USER+211) // glitches vertical sync with PS 3.65 build 568 + +#define NegativeHorizontalPolarity 0x02 +#define NegativeVerticalPolarity 0x04 +#define Interlace 0x08 + +#define HideTrayIcon 0x00 +#define ShowTrayIcon 0x01 +#define ClosePowerStrip 0x63 + +//============================================================ +// pstrip_timing::pstrip_timing +//============================================================ + +pstrip_timing::pstrip_timing(char *device_name, custom_video_settings *vs) +{ + m_vs = *vs; + strcpy (m_device_name, device_name); + strcpy (m_ps_timing, m_vs.custom_timing); +} + +//============================================================ +// pstrip_timing::~pstrip_timing() +//============================================================ + +pstrip_timing::~pstrip_timing() +{ + ps_reset(); +} + +//============================================================ +// pstrip_timing::init +//============================================================ + +bool pstrip_timing::init() +{ + m_monitor_index = ps_monitor_index(m_device_name); + + hPSWnd = FindWindowA("TPShidden", NULL); + + if (hPSWnd) + { + log_verbose("PStrip: PowerStrip found!\n"); + + // Save current settings + ps_get_monitor_timing(&m_timing_backup); + + // If we have a -ps_timing string defined, use it as user defined modeline + if (strcmp(m_ps_timing, "auto")) + { + MonitorTiming timing; + if (ps_read_timing_string(m_ps_timing, &timing)) + { + ps_pstiming_to_modeline(&timing, &m_user_mode); + m_user_mode.type |= CUSTOM_VIDEO_TIMING_POWERSTRIP; + + char modeline_txt[256]={'\x00'}; + log_verbose("SwitchRes: ps_string: %s (%s)\n", m_ps_timing, modeline_print(&m_user_mode, modeline_txt, MS_PARAMS)); + } + else + log_verbose("Switchres: ps_timing string with invalid format\n"); + } + } + else + { + log_verbose("PStrip: Could not get PowerStrip API interface\n"); + return false; + } + + return true; +} + +//============================================================ +// pstrip_timing::get_timing +//============================================================ + +bool pstrip_timing::get_timing(modeline *mode) +{ + // If we have an user defined mode (ps_timing), lock any non matching mode + if (m_user_mode.hactive) + { + if (mode->width != m_user_mode.width || mode->height != m_user_mode.height) + { + mode->type |= MODE_DISABLED; + return false; + } + } + + modeline m_temp = {}; + if (ps_get_modeline(&m_temp)) + { + // We can only get the timings of the current desktop mode, so filter out anything different + if (m_temp.width == mode->width && m_temp.height == mode->height && m_temp.refresh == mode->refresh) + { + *mode = m_temp; + } + mode->type |= CUSTOM_VIDEO_TIMING_POWERSTRIP; + return true; + } + + return false; +} + +//============================================================ +// pstrip_timing::set_timing +//============================================================ + +bool pstrip_timing::set_timing(modeline *mode) +{ + // In case -ps_timing is provided, pass it as raw string + if (m_user_mode.hactive) + ps_set_monitor_timing_string(m_ps_timing); + + // Otherwise pass it as modeline + else + ps_set_modeline(mode); + + Sleep(100); + return true; +} + + +//============================================================ +// pstrip_timing::ps_reset +//============================================================ + +int pstrip_timing::ps_reset() +{ + return ps_set_monitor_timing(&m_timing_backup); +} + +//============================================================ +// ps_get_modeline +//============================================================ + +int pstrip_timing::ps_get_modeline(modeline *modeline) +{ + MonitorTiming timing = {}; + + if (ps_get_monitor_timing(&timing)) + { + ps_pstiming_to_modeline(&timing, modeline); + return 1; + } + else return 0; +} + +//============================================================ +// pstrip_timing::ps_set_modeline +//============================================================ + +bool pstrip_timing::ps_set_modeline(modeline *modeline) +{ + MonitorTiming timing = {}; + + if (!ps_modeline_to_pstiming(modeline, &timing)) + return false; + + timing.PixelClockInKiloHertz = ps_best_pclock(&timing, timing.PixelClockInKiloHertz); + + if (ps_set_monitor_timing(&timing)) + return true; + else + return false; +} + +//============================================================ +// pstrip_timing::ps_get_monitor_timing +//============================================================ + +int pstrip_timing::ps_get_monitor_timing(MonitorTiming *timing) +{ + LRESULT lresult; + char in[256]; + + if (!hPSWnd) return 0; + + lresult = SendMessage(hPSWnd, UM_GETTIMING, m_monitor_index, 0); + + if (lresult == -1) + { + log_verbose("PStrip: Could not get PowerStrip timing string\n"); + return 0; + } + + if (!GlobalGetAtomNameA(lresult, in, sizeof(in))) + { + log_verbose("PStrip: GlobalGetAtomName failed\n"); + return 0; + } + + log_verbose("PStrip: ps_get_monitor_timing(%d): %s\n", m_monitor_index, in); + + ps_read_timing_string(in, timing); + + GlobalDeleteAtom(lresult); // delete atom created by PowerStrip + + return 1; +} + +//============================================================ +// pstrip_timing::ps_set_monitor_timing +//============================================================ + +int pstrip_timing::ps_set_monitor_timing(MonitorTiming *timing) +{ + LRESULT lresult; + ATOM atom; + char out[256]; + + if (!hPSWnd) return 0; + + ps_fill_timing_string(out, timing); + atom = GlobalAddAtomA(out); + + if (atom) + { + lresult = SendMessage(hPSWnd, UM_SETCUSTOMTIMING, m_monitor_index, atom); + + if (lresult < 0) + { + log_verbose("PStrip: SendMessage failed\n"); + GlobalDeleteAtom(atom); + } + else + { + log_verbose("PStrip: ps_set_monitor_timing(%d): %s\n", m_monitor_index, out); + return 1; + } + } + else log_verbose("PStrip: ps_set_monitor_timing atom creation failed\n"); + + return 0; +} + +//============================================================ +// pstrip_timing::ps_set_monitor_timing_string +//============================================================ + +int pstrip_timing::ps_set_monitor_timing_string(char *in) +{ + MonitorTiming timing = {}; + + ps_read_timing_string(in, &timing); + return ps_set_monitor_timing(&timing); +} + +//============================================================ +// pstrip_timing::ps_set_refresh +//============================================================ + +int pstrip_timing::ps_set_refresh(double vfreq) +{ + MonitorTiming timing = {}; + int hht, vvt, new_vvt; + int desired_pClock; + int best_pClock; + + memcpy(&timing, &m_timing_backup, sizeof(MonitorTiming)); + + hht = timing.HorizontalActivePixels + + timing.HorizontalFrontPorch + + timing.HorizontalSyncWidth + + timing.HorizontalBackPorch; + + vvt = timing.VerticalActivePixels + + timing.VerticalFrontPorch + + timing.VerticalSyncWidth + + timing.VerticalBackPorch; + + desired_pClock = hht * vvt * vfreq / 1000; + best_pClock = ps_best_pclock(&timing, desired_pClock); + + new_vvt = best_pClock * 1000 / (vfreq * hht); + + timing.VerticalBackPorch += (new_vvt - vvt); + timing.PixelClockInKiloHertz = best_pClock; + + ps_set_monitor_timing(&timing); + ps_get_monitor_timing(&timing); + + return 1; +} + +//============================================================ +// pstrip_timing::ps_best_pclock +//============================================================ + +int pstrip_timing::ps_best_pclock(MonitorTiming *timing, int desired_pclock) +{ + MonitorTiming timing_read = {}; + int best_pclock = 0; + + log_verbose("PStrip: ps_best_pclock(%d), getting stable dotclocks for %d...\n", m_monitor_index, desired_pclock); + + for (int i = -50; i <= 50; i += 25) + { + timing->PixelClockInKiloHertz = desired_pclock + i; + + ps_set_monitor_timing(timing); + ps_get_monitor_timing(&timing_read); + + if (abs(timing_read.PixelClockInKiloHertz - desired_pclock) < abs(desired_pclock - best_pclock)) + best_pclock = timing_read.PixelClockInKiloHertz; + } + + log_verbose("PStrip: ps_best_pclock(%d), new dotclock: %d\n", m_monitor_index, best_pclock); + + return best_pclock; +} + +//============================================================ +// pstrip_timing::ps_create_resolution +//============================================================ + +int pstrip_timing::ps_create_resolution(modeline *modeline) +{ + LRESULT lresult; + ATOM atom; + char out[256]; + MonitorTiming timing = {}; + + if (!hPSWnd) return 0; + + ps_modeline_to_pstiming(modeline, &timing); + + ps_fill_timing_string(out, &timing); + atom = GlobalAddAtomA(out); + + if (atom) + { + lresult = SendMessage(hPSWnd, UM_CREATERESOLUTION, m_monitor_index, atom); + + if (lresult < 0) + { + log_verbose("PStrip: SendMessage failed\n"); + GlobalDeleteAtom(atom); + } + else + { + log_verbose("PStrip: ps_create_resolution(%d): %dx%d succeded \n", + modeline->width, modeline->height, m_monitor_index); + return 1; + } + } + else + log_verbose("PStrip: ps_create_resolution atom creation failed\n"); + + return 0; +} + +//============================================================ +// pstrip_timing::ps_read_timing_string +//============================================================ + +bool pstrip_timing::ps_read_timing_string(char *in, MonitorTiming *timing) +{ + if (sscanf(in,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &timing->HorizontalActivePixels, + &timing->HorizontalFrontPorch, + &timing->HorizontalSyncWidth, + &timing->HorizontalBackPorch, + &timing->VerticalActivePixels, + &timing->VerticalFrontPorch, + &timing->VerticalSyncWidth, + &timing->VerticalBackPorch, + &timing->PixelClockInKiloHertz, + &timing->TimingFlags.w) == 10) return true; + + return false; +} + +//============================================================ +// pstrip_timing::ps_fill_timing_string +//============================================================ + +void pstrip_timing::ps_fill_timing_string(char *out, MonitorTiming *timing) +{ + sprintf(out, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + timing->HorizontalActivePixels, + timing->HorizontalFrontPorch, + timing->HorizontalSyncWidth, + timing->HorizontalBackPorch, + timing->VerticalActivePixels, + timing->VerticalFrontPorch, + timing->VerticalSyncWidth, + timing->VerticalBackPorch, + timing->PixelClockInKiloHertz, + timing->TimingFlags.w); +} + +//============================================================ +// pstrip_timing::ps_modeline_to_pstiming +//============================================================ + +bool pstrip_timing::ps_modeline_to_pstiming(modeline *modeline, MonitorTiming *timing) +{ + if (modeline->pclock == 0 || modeline->hactive == 0 || modeline->vactive == 0) + { + log_verbose("ps_modeline_to_pstiming error: invalid modeline\n"); + return false; + } + + timing->HorizontalActivePixels = modeline->hactive; + timing->HorizontalFrontPorch = modeline->hbegin - modeline->hactive; + timing->HorizontalSyncWidth = modeline->hend - modeline->hbegin; + timing->HorizontalBackPorch = modeline->htotal - modeline->hend; + + timing->VerticalActivePixels = modeline->vactive; + timing->VerticalFrontPorch = modeline->vbegin - modeline->vactive; + timing->VerticalSyncWidth = modeline->vend - modeline->vbegin; + timing->VerticalBackPorch = modeline->vtotal - modeline->vend; + + timing->PixelClockInKiloHertz = modeline->pclock / 1000; + + if (modeline->hsync == 0) + timing->TimingFlags.w |= NegativeHorizontalPolarity; + if (modeline->vsync == 0) + timing->TimingFlags.w |= NegativeVerticalPolarity; + if (modeline->interlace) + timing->TimingFlags.w |= Interlace; + + return true; +} + +//============================================================ +// pstrip_timing::ps_pstiming_to_modeline +//============================================================ + +int pstrip_timing::ps_pstiming_to_modeline(MonitorTiming *timing, modeline *modeline) +{ + modeline->hactive = timing->HorizontalActivePixels; + modeline->hbegin = modeline->hactive + timing->HorizontalFrontPorch; + modeline->hend = modeline->hbegin + timing->HorizontalSyncWidth; + modeline->htotal = modeline->hend + timing->HorizontalBackPorch; + + modeline->vactive = timing->VerticalActivePixels; + modeline->vbegin = modeline->vactive + timing->VerticalFrontPorch; + modeline->vend = modeline->vbegin + timing->VerticalSyncWidth; + modeline->vtotal = modeline->vend + timing->VerticalBackPorch; + + modeline->width = modeline->hactive; + modeline->height = modeline->vactive; + + modeline->pclock = timing->PixelClockInKiloHertz * 1000; + + if (!(timing->TimingFlags.w & NegativeHorizontalPolarity)) + modeline->hsync = 1; + + if (!(timing->TimingFlags.w & NegativeVerticalPolarity)) + modeline->vsync = 1; + + if ((timing->TimingFlags.w & Interlace)) + modeline->interlace = 1; + + modeline->hfreq = modeline->pclock / modeline->htotal; + modeline->vfreq = modeline->hfreq / modeline->vtotal * (modeline->interlace?2:1); + modeline->refresh = int(modeline->vfreq); + + return 0; +} + +//============================================================ +// pstrip_timing::ps_monitor_index +//============================================================ + +int pstrip_timing::ps_monitor_index (const char *display_name) +{ + int monitor_index = 0; + char sub_index[2]; + + sub_index[0] = display_name[strlen(display_name)-1]; + sub_index[1] = 0; + if (sscanf(sub_index,"%d", &monitor_index) == 1) + monitor_index --; + + return monitor_index; +} + +//============================================================ +// pstrip_timing::update_mode +//============================================================ + +bool pstrip_timing::update_mode(modeline *mode) +{ + if (!set_timing(mode)) + { + return false; + } + + mode->type |= CUSTOM_VIDEO_TIMING_POWERSTRIP; + return true; +} diff --git a/3rdparty/switchres/custom_video_pstrip.h b/3rdparty/switchres/custom_video_pstrip.h new file mode 100644 index 0000000000000..e8aad28422a0a --- /dev/null +++ b/3rdparty/switchres/custom_video_pstrip.h @@ -0,0 +1,83 @@ +/************************************************************** + + custom_video_powerstrip.h - PowerStrip interface routines + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include "custom_video.h" + +//============================================================ +// TYPE DEFINITIONS +//============================================================ + +typedef struct +{ + int HorizontalActivePixels; + int HorizontalFrontPorch; + int HorizontalSyncWidth; + int HorizontalBackPorch; + int VerticalActivePixels; + int VerticalFrontPorch; + int VerticalSyncWidth; + int VerticalBackPorch; + int PixelClockInKiloHertz; + union + { + int w; + struct + { + unsigned :1; + unsigned HorizontalPolarityNegative:1; + unsigned VerticalPolarityNegative:1; + unsigned :29; + } b; + } TimingFlags; +} MonitorTiming; + + +class pstrip_timing : public custom_video +{ + public: + pstrip_timing(char *device_name, custom_video_settings *vs); + ~pstrip_timing(); + const char *api_name() { return "PowerStrip"; } + bool init(); + int caps() { return CUSTOM_VIDEO_CAPS_UPDATE | CUSTOM_VIDEO_CAPS_SCAN_EDITABLE | CUSTOM_VIDEO_CAPS_DESKTOP_EDITABLE; } + + bool update_mode(modeline *mode); + + bool get_timing(modeline *mode); + bool set_timing(modeline *m); + + private: + + int ps_reset(); + int ps_get_modeline(modeline *modeline); + bool ps_set_modeline(modeline *modeline); + int ps_get_monitor_timing(MonitorTiming *timing); + int ps_set_monitor_timing(MonitorTiming *timing); + int ps_set_monitor_timing_string(char *in); + int ps_set_refresh(double vfreq); + int ps_best_pclock(MonitorTiming *timing, int desired_pclock); + int ps_create_resolution(modeline *modeline); + bool ps_read_timing_string(char *in, MonitorTiming *timing); + void ps_fill_timing_string(char *out, MonitorTiming *timing); + bool ps_modeline_to_pstiming(modeline *modeline, MonitorTiming *timing); + int ps_pstiming_to_modeline(MonitorTiming *timing, modeline *modeline); + int ps_monitor_index (const char *display_name); + + char m_device_name[32]; + char m_ps_timing[256]; + int m_monitor_index = 0; + modeline m_user_mode = {}; + MonitorTiming m_timing_backup = {}; + HWND hPSWnd = 0; +}; diff --git a/3rdparty/switchres/custom_video_xrandr.cpp b/3rdparty/switchres/custom_video_xrandr.cpp new file mode 100644 index 0000000000000..e9feb2ad872ea --- /dev/null +++ b/3rdparty/switchres/custom_video_xrandr.cpp @@ -0,0 +1,1190 @@ +/************************************************************** + + custom_video_xrandr.cpp - Linux XRANDR video management layer + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include +#include +#include "custom_video_xrandr.h" +#include "log.h" + +//============================================================ +// library functions +//============================================================ + +#define XRRAddOutputMode p_XRRAddOutputMode +#define XRRConfigCurrentConfiguration p_XRRConfigCurrentConfiguration +#define XRRCreateMode p_XRRCreateMode +#define XRRDeleteOutputMode p_XRRDeleteOutputMode +#define XRRDestroyMode p_XRRDestroyMode +#define XRRFreeCrtcInfo p_XRRFreeCrtcInfo +#define XRRFreeOutputInfo p_XRRFreeOutputInfo +#define XRRFreeScreenConfigInfo p_XRRFreeScreenConfigInfo +#define XRRFreeScreenResources p_XRRFreeScreenResources +#define XRRGetCrtcInfo p_XRRGetCrtcInfo +#define XRRGetOutputInfo p_XRRGetOutputInfo +#define XRRGetScreenInfo p_XRRGetScreenInfo +#define XRRGetScreenResourcesCurrent p_XRRGetScreenResourcesCurrent +#define XRRQueryVersion p_XRRQueryVersion +#define XRRSetCrtcConfig p_XRRSetCrtcConfig +#define XRRSetScreenSize p_XRRSetScreenSize +#define XRRGetScreenSizeRange p_XRRGetScreenSizeRange + +#define XCloseDisplay p_XCloseDisplay +#define XGrabServer p_XGrabServer +#define XOpenDisplay p_XOpenDisplay +#define XSync p_XSync +#define XUngrabServer p_XUngrabServer +#define XSetErrorHandler p_XSetErrorHandler +#define XClearWindow p_XClearWindow +#define XFillRectangle p_XFillRectangle +#define XCreateGC p_XCreateGC + +//============================================================ +// error_handler +// xorg error handler (static) +//============================================================ + +int xrandr_timing::ms_xerrors = 0; +int xrandr_timing::ms_xerrors_flag = 0; +static int (*old_error_handler)(Display *, XErrorEvent *); + +static __typeof__(XGetErrorText) *p_XGetErrorText; +#define XGetErrorText p_XGetErrorText + +static int error_handler(Display *dpy, XErrorEvent *err) +{ + char buf[64]; + XGetErrorText(dpy, err->error_code, buf, 64); + buf[0] = '\0'; + xrandr_timing::ms_xerrors |= xrandr_timing::ms_xerrors_flag; + old_error_handler(dpy, err); + log_error("XRANDR: <-> (error_handler) [ERROR] %s error code %d flags %02x\n", buf, err->error_code, xrandr_timing::ms_xerrors); + return 0; +} + +//============================================================ +// id for class object (static) +//============================================================ + +static int s_id = 0; + +//============================================================ +// screen management exclusivity array (static) +//============================================================ + +static int s_total_managed_screen = 0; +static int *sp_shared_screen_manager = NULL; + +//============================================================ +// desktop screen positions (static) +//============================================================ + +static XRRCrtcInfo *sp_desktop_crtc = NULL; + +//============================================================ +// xrandr_timing::xrandr_timing +//============================================================ + +xrandr_timing::xrandr_timing(char *device_name, custom_video_settings *vs) +{ + m_vs = *vs; + + // Increment id for each new screen + m_id = ++s_id; + + log_verbose("XRANDR: <%d> (xrandr_timing) creation (%s)\n", m_id, device_name); + // Copy screen device name and limit size + if ((strlen(device_name) + 1) > 32) + { + strncpy(m_device_name, device_name, 31); + log_error("XRANDR: <%d> (xrandr_timing) [ERROR] the device name is too long it has been trucated to %s\n", m_id, m_device_name); + } + else + strcpy(m_device_name, device_name); + + if (m_vs.screen_reordering) + { + if (m_id == 1) + m_enable_screen_reordering = 1; + } + else if (m_vs.screen_compositing) + m_enable_screen_compositing = 1; + + log_verbose("XRANDR: <%d> (xrandr_timing) checking X availability (early stub)\n", m_id); + + m_x11_handle = dlopen("libX11.so", RTLD_NOW); + + if (m_x11_handle) + { + p_XOpenDisplay = (__typeof__(XOpenDisplay)) dlsym(m_x11_handle, "XOpenDisplay"); + if (p_XOpenDisplay == NULL) + { + log_error("XRANDR: <%d> (xrandr_timing) [ERROR] missing func %s in %s\n", m_id, "XOpenDisplay", "X11_LIBRARY"); + throw new std::exception(); + } + else + { + if (!XOpenDisplay(NULL)) + { + log_verbose("XRANDR: <%d> (xrandr_timing) X server not found\n", m_id); + throw new std::exception(); + } + } + } + else + { + log_error("XRANDR: <%d> (xrandr_timing) [ERROR] missing %s library\n", m_id, "X11_LIBRARY"); + throw new std::exception(); + } + + s_total_managed_screen++; +} + +//============================================================ +// xrandr_timing::~xrandr_timing +//============================================================ + +xrandr_timing::~xrandr_timing() +{ + s_total_managed_screen--; + if (s_total_managed_screen == 0) + { + if (sp_desktop_crtc) + delete[]sp_desktop_crtc; + + if (sp_shared_screen_manager) + delete[]sp_shared_screen_manager; + + // Restore default desktop background + XClearWindow(m_pdisplay, m_root); + } + + // Free the display + if (m_pdisplay != NULL) + XCloseDisplay(m_pdisplay); + + // close Xrandr library + if (m_xrandr_handle) + dlclose(m_xrandr_handle); + + // close X11 library + if (m_x11_handle) + dlclose(m_x11_handle); +} + +//============================================================ +// xrandr_timing::init +//============================================================ + +bool xrandr_timing::init() +{ + log_verbose("XRANDR: <%d> (init) loading Xrandr library\n", m_id); + if (!m_xrandr_handle) + m_xrandr_handle = dlopen("libXrandr.so", RTLD_NOW); + if (m_xrandr_handle) + { + p_XRRAddOutputMode = (__typeof__(XRRAddOutputMode)) dlsym(m_xrandr_handle, "XRRAddOutputMode"); + if (p_XRRAddOutputMode == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRAddOutputMode", "XRANDR_LIBRARY"); + return false; + } + + p_XRRConfigCurrentConfiguration = (__typeof__(XRRConfigCurrentConfiguration)) dlsym(m_xrandr_handle, "XRRConfigCurrentConfiguration"); + if (p_XRRConfigCurrentConfiguration == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRConfigCurrentConfiguration", "XRANDR_LIBRARY"); + return false; + } + + p_XRRCreateMode = (__typeof__(XRRCreateMode)) dlsym(m_xrandr_handle, "XRRCreateMode"); + if (p_XRRCreateMode == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRCreateMode", "XRANDR_LIBRARY"); + return false; + } + + p_XRRDeleteOutputMode = (__typeof__(XRRDeleteOutputMode)) dlsym(m_xrandr_handle, "XRRDeleteOutputMode"); + if (p_XRRDeleteOutputMode == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRDeleteOutputMode", "XRANDR_LIBRARY"); + return false; + } + + p_XRRDestroyMode = (__typeof__(XRRDestroyMode)) dlsym(m_xrandr_handle, "XRRDestroyMode"); + if (p_XRRDestroyMode == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRDestroyMode", "XRANDR_LIBRARY"); + return false; + } + + p_XRRFreeCrtcInfo = (__typeof__(XRRFreeCrtcInfo)) dlsym(m_xrandr_handle, "XRRFreeCrtcInfo"); + if (p_XRRFreeCrtcInfo == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeCrtcInfo", "XRANDR_LIBRARY"); + return false; + } + + p_XRRFreeOutputInfo = (__typeof__(XRRFreeOutputInfo)) dlsym(m_xrandr_handle, "XRRFreeOutputInfo"); + if (p_XRRFreeOutputInfo == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeOutputInfo", "XRANDR_LIBRARY"); + return false; + } + + p_XRRFreeScreenConfigInfo = (__typeof__(XRRFreeScreenConfigInfo)) dlsym(m_xrandr_handle, "XRRFreeScreenConfigInfo"); + if (p_XRRFreeScreenConfigInfo == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeScreenConfigInfo", "XRANDR_LIBRARY"); + return false; + } + + p_XRRFreeScreenResources = (__typeof__(XRRFreeScreenResources)) dlsym(m_xrandr_handle, "XRRFreeScreenResources"); + if (p_XRRFreeScreenResources == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeScreenResources", "XRANDR_LIBRARY"); + return false; + } + + p_XRRGetCrtcInfo = (__typeof__(XRRGetCrtcInfo)) dlsym(m_xrandr_handle, "XRRGetCrtcInfo"); + if (p_XRRGetCrtcInfo == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetCrtcInfo", "XRANDR_LIBRARY"); + return false; + } + + p_XRRGetOutputInfo = (__typeof__(XRRGetOutputInfo)) dlsym(m_xrandr_handle, "XRRGetOutputInfo"); + if (p_XRRGetOutputInfo == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetOutputInfo", "XRANDR_LIBRARY"); + return false; + } + + p_XRRGetScreenInfo = (__typeof__(XRRGetScreenInfo)) dlsym(m_xrandr_handle, "XRRGetScreenInfo"); + if (p_XRRGetScreenInfo == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetScreenInfo", "XRANDR_LIBRARY"); + return false; + } + + p_XRRGetScreenResourcesCurrent = (__typeof__(XRRGetScreenResourcesCurrent)) dlsym(m_xrandr_handle, "XRRGetScreenResourcesCurrent"); + if (p_XRRGetScreenResourcesCurrent == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetScreenResourcesCurrent", "XRANDR_LIBRARY"); + return false; + } + + p_XRRQueryVersion = (__typeof__(XRRQueryVersion)) dlsym(m_xrandr_handle, "XRRQueryVersion"); + if (p_XRRQueryVersion == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRQueryVersion", "XRANDR_LIBRARY"); + return false; + } + + p_XRRSetCrtcConfig = (__typeof__(XRRSetCrtcConfig)) dlsym(m_xrandr_handle, "XRRSetCrtcConfig"); + if (p_XRRSetCrtcConfig == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRSetCrtcConfig", "XRANDR_LIBRARY"); + return false; + } + + p_XRRSetScreenSize = (__typeof__(XRRSetScreenSize)) dlsym(m_xrandr_handle, "XRRSetScreenSize"); + if (p_XRRSetScreenSize == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRSetScreenSize", "XRANDR_LIBRARY"); + return false; + } + + p_XRRGetScreenSizeRange = (__typeof__(XRRGetScreenSizeRange)) dlsym(m_xrandr_handle, "XRRGetScreenSizeRange"); + if (p_XRRGetScreenSizeRange == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRSetScreenSize", "XRANDR_LIBRARY"); + return false; + } + } + else + { + log_error("XRANDR: <%d> (init) [ERROR] missing %s library\n", m_id, "XRANDR_LIBRARY"); + return false; + } + + log_verbose("XRANDR: <%d> (init) loading X11 library\n", m_id); + if (!m_x11_handle) + m_x11_handle = dlopen("libX11.so", RTLD_NOW); + if (m_x11_handle) + { + p_XCloseDisplay = (__typeof__(XCloseDisplay)) dlsym(m_x11_handle, "XCloseDisplay"); + if (p_XCloseDisplay == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XCloseDisplay", "X11_LIBRARY"); + return false; + } + + p_XGrabServer = (__typeof__(XGrabServer)) dlsym(m_x11_handle, "XGrabServer"); + if (p_XGrabServer == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XGrabServer", "X11_LIBRARY"); + return false; + } + + p_XOpenDisplay = (__typeof__(XOpenDisplay)) dlsym(m_x11_handle, "XOpenDisplay"); + if (p_XOpenDisplay == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XOpenDisplay", "X11_LIBRARY"); + return false; + } + + p_XSync = (__typeof__(XSync)) dlsym(m_x11_handle, "XSync"); + if (p_XSync == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XSync", "X11_LIBRARY"); + return false; + } + + p_XUngrabServer = (__typeof__(XUngrabServer)) dlsym(m_x11_handle, "XUngrabServer"); + if (p_XUngrabServer == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XUngrabServer", "X11_LIBRARY"); + return false; + } + + p_XSetErrorHandler = (__typeof__(XSetErrorHandler)) dlsym(m_x11_handle, "XSetErrorHandler"); + if (p_XSetErrorHandler == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XSetErrorHandler", "X11_LIBRARY"); + return false; + } + + p_XGetErrorText = (__typeof__(XGetErrorText)) dlsym(m_x11_handle, "XGetErrorText"); + if (p_XGetErrorText == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XGetErrorText", "X11_LIBRARY"); + return false; + } + + p_XClearWindow = (__typeof__(XClearWindow)) dlsym(m_x11_handle, "XClearWindow"); + if (p_XClearWindow == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XClearWindow", "X11_LIBRARY"); + return false; + } + + p_XFillRectangle = (__typeof__(XFillRectangle)) dlsym(m_x11_handle, "XFillRectangle"); + if (p_XFillRectangle == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XFillRectangle", "X11_LIBRARY"); + return false; + } + + p_XCreateGC = (__typeof__(XCreateGC)) dlsym(m_x11_handle, "XCreateGC"); + if (p_XCreateGC == NULL) + { + log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XCreateGC", "X11_LIBRARY"); + return false; + } + } + else + { + log_error("XRANDR: <%d> (init) [ERROR] missing %s library\n", m_id, "X11_LIBRARY"); + return false; + } + + // Select current display and root window + // m_pdisplay is global to reduce open/close calls, resource is freed when class is destroyed + if (!m_pdisplay) + m_pdisplay = XOpenDisplay(NULL); + + if (!m_pdisplay) + { + log_error("XRANDR: <%d> (init) [ERROR] failed to connect to the X server\n", m_id); + return false; + } + + // Display XRANDR version + int major_version, minor_version; + XRRQueryVersion(m_pdisplay, &major_version, &minor_version); + log_verbose("XRANDR: <%d> (init) version %d.%d\n", m_id, major_version, minor_version); + + if (major_version < 1 || (major_version == 1 && minor_version < 2)) + { + log_error("XRANDR: <%d> (init) [ERROR] Xrandr version 1.2 or above is required\n", m_id); + return false; + } + + // screen_pos defines screen position, 0 is default first screen position and equivalent to 'auto' + int screen_pos = -1; + bool detected = false; + + // Handle the screen name, "auto", "screen[0-9]" and XRANDR device name + if (strlen(m_device_name) == 7 && !strncmp(m_device_name, "screen", 6) && m_device_name[6] >= '0' && m_device_name[6] <= '9') + screen_pos = m_device_name[6] - '0'; + else if (strlen(m_device_name) == 1 && m_device_name[0] >= '0' && m_device_name[0] <= '9') + screen_pos = m_device_name[0] - '0'; + + if (ScreenCount(m_pdisplay) > 1) + log_verbose("XRANDR: <%d> (init) [WARNING] screen count is %d, unpredictable behavior to be expected\n", m_id, ScreenCount(m_pdisplay)); + + for (int screen = 0; !detected && screen < ScreenCount(m_pdisplay); screen++) + { + log_verbose("XRANDR: <%d> (init) check screen number %d\n", m_id, screen); + m_screen = screen; + m_root = RootWindow(m_pdisplay, screen); + + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root); + + if (m_id == 1) + { + // Prepare the shared screen array + sp_shared_screen_manager = new int[resources->noutput]; + for (int o = 0; o < resources->noutput; o++) + sp_shared_screen_manager[o] = 0; + + // Save all active crtc positions + sp_desktop_crtc = new XRRCrtcInfo[resources->ncrtc]; + for (int c = 0; c < resources->ncrtc; c++) + memcpy(&sp_desktop_crtc[c], XRRGetCrtcInfo(m_pdisplay, resources, resources->crtcs[c]), sizeof(XRRCrtcInfo)); + } + + // Get default screen rotation from screen configuration + XRRScreenConfiguration *sc = XRRGetScreenInfo(m_pdisplay, m_root); + XRRConfigCurrentConfiguration(sc, &m_desktop_rotation); + XRRFreeScreenConfigInfo(sc); + + Rotation current_rotation = 0; + int output_position = 0; + for (int o = 0; o < resources->noutput; o++) + { + XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[o]); + if (!output_info) + { + log_error("XRANDR: <%d> (init) [ERROR] could not get output 0x%x information\n", m_id, (unsigned int)resources->outputs[o]); + continue; + } + // Check all connected output + if (m_desktop_output == -1 && output_info->connection == RR_Connected && output_info->crtc) + { + + if (!strcmp(m_device_name, "auto") || !strcmp(m_device_name, output_info->name) || output_position == screen_pos) + { + // store the output connector + m_desktop_output = o; + + // store screen minium and maximum resolutions + int min_width; + int max_width; + int min_height; + int max_height; + XRRGetScreenSizeRange (m_pdisplay, m_root, &min_width, &min_height, &max_width, &max_height); + m_min_width = min_width; + m_max_width = max_width; + m_min_height = min_height; + m_max_height = max_height; + + if (sp_shared_screen_manager[m_desktop_output] == 0) + { + sp_shared_screen_manager[m_desktop_output] = m_id; + m_managed = 1; + } + + // identify the current modeline and rotation + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc); + current_rotation = crtc_info->rotation; + for (int m = 0; m < resources->nmode && m_desktop_mode.id == 0; m++) + { + // Get screen mode + if (crtc_info->mode == resources->modes[m].id) + { + m_desktop_mode = resources->modes[m]; + m_last_crtc = *crtc_info; + } + } + XRRFreeCrtcInfo(crtc_info); + + // check screen rotation (left or right) + if (current_rotation & 0xe) + { + m_crtc_flags = MODE_ROTATED; + log_verbose("XRANDR: <%d> (init) desktop rotation is %s\n", m_id, (current_rotation & 0x2) ? "left" : ((current_rotation & 0x8) ? "right" : "inverted")); + } + } + output_position++; + } + log_verbose("XRANDR: <%d> (init) check output connector '%s' active %d crtc %d %s\n", m_id, output_info->name, output_info->connection == RR_Connected ? 1 : 0, output_info->crtc ? 1 : 0, m_desktop_output == o ? (m_managed ? "[SELECTED]" : "[UNMANAGED]") : ""); + XRRFreeOutputInfo(output_info); + } + XRRFreeScreenResources(resources); + + // Check if screen has been detected + detected = m_desktop_output != -1; + } + + if (!detected) + log_error("XRANDR: <%d> (init) [ERROR] no screen detected\n", m_id); + + else if (m_enable_screen_reordering) + { + // Global screen placement + modeline mode = {}; + mode.type = MODE_DESKTOP; + set_timing(&mode, XRANDR_ENABLE_SCREEN_REORDERING); + } + + return detected; +} + +//============================================================ +// xrandr_timing::update_mode +//============================================================ + +bool xrandr_timing::update_mode(modeline *mode) +{ + if (!mode) + return false; + + // Handle no screen detected case + if (m_desktop_output == -1) + { + log_error("XRANDR: <%d> (update_mode) [ERROR] no screen detected\n", m_id); + return false; + } + + if (!delete_mode(mode)) + { + log_error("XRANDR: <%d> (update_mode) [ERROR] delete operation not successful", m_id); + return false; + } + + if (!add_mode(mode)) + { + log_error("XRANDR: <%d> (update_mode) [ERROR] add operation not successful", m_id); + return false; + } + + return true; +} + +//============================================================ +// xrandr_timing::add_mode +//============================================================ + +bool xrandr_timing::add_mode(modeline *mode) +{ + if (!mode) + return false; + + // Handle no screen detected case + if (m_desktop_output == -1) + { + log_error("XRANDR: <%d> (add_mode) [ERROR] no screen detected\n", m_id); + return false; + } + + if (!m_managed) + { + log_error("XRANDR: <%d> (add_mode) [WARNING] this screen is managed by <%d>\n", m_id, sp_shared_screen_manager[m_desktop_output]); + return false; + } + + // Check if mode is available from the plaftform_data mode id + XRRModeInfo *pxmode = find_mode(mode); + if (pxmode != NULL) + { + log_error("XRANDR: <%d> (add_mode) [WARNING] mode already exist\n", m_id); + return true; + } + + // Create specific mode name + char name[48]; + sprintf(name, "SR-%d_%dx%d@%.02f%s", m_id, mode->hactive, mode->vactive, mode->vfreq, mode->interlace ? "i" : ""); + + // Check if mode is available from the SR name (should not be the case, otherwise it means that we recevied twice the same mode request) + pxmode = find_mode_by_name(name); + if (pxmode != NULL) + { + log_error("XRANDR: <%d> (add_mode) [WARNING] mode already exist (duplicate request)\n", m_id); + mode->platform_data = pxmode->id; + return true; + } + + log_verbose("XRANDR: <%d> (add_mode) create mode %s\n", m_id, name); + + // Setup the xrandr mode structure + XRRModeInfo xmode = {}; + + xmode.name = name; + xmode.nameLength = strlen(name); + xmode.dotClock = mode->pclock; + xmode.width = mode->hactive; + xmode.hSyncStart = mode->hbegin; + xmode.hSyncEnd = mode->hend; + xmode.hTotal = mode->htotal; + xmode.height = mode->vactive; + xmode.vSyncStart = mode->vbegin; + xmode.vSyncEnd = mode->vend; + xmode.vTotal = mode->vtotal; + xmode.modeFlags = (mode->interlace ? RR_Interlace : 0) | (mode->doublescan ? RR_DoubleScan : 0) | (mode->hsync ? RR_HSyncPositive : RR_HSyncNegative) | (mode->vsync ? RR_VSyncPositive : RR_VSyncNegative); + xmode.hSkew = 0; + + mode->type |= CUSTOM_VIDEO_TIMING_XRANDR; + + // Create the modeline + XSync(m_pdisplay, False); + ms_xerrors = 0; + ms_xerrors_flag = 0x01; + old_error_handler = XSetErrorHandler(error_handler); + RRMode gmid = XRRCreateMode(m_pdisplay, m_root, &xmode); + XSync(m_pdisplay, False); + XSetErrorHandler(old_error_handler); + if (ms_xerrors & ms_xerrors_flag) + { + log_error("XRANDR: <%d> (add_mode) [ERROR] in %s\n", m_id, "XRRCreateMode"); + return false; + } + + mode->platform_data = gmid; + + // Add new modeline to primary output + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root); + + XSync(m_pdisplay, False); + ms_xerrors_flag = 0x02; + old_error_handler = XSetErrorHandler(error_handler); + XRRAddOutputMode(m_pdisplay, resources->outputs[m_desktop_output], mode->platform_data); + XSync(m_pdisplay, False); + XSetErrorHandler(old_error_handler); + + XRRFreeScreenResources(resources); + + if (ms_xerrors & ms_xerrors_flag) + { + log_error("XRANDR: <%d> (add_mode) [ERROR] in %s\n", m_id, "XRRAddOutputMode"); + + // remove unlinked modeline + if (mode->platform_data) + { + log_error("XRANDR: <%d> (add_mode) [ERROR] remove mode [%04lx]\n", m_id, mode->platform_data); + XRRDestroyMode(m_pdisplay, mode->platform_data); + mode->platform_data = 0; + } + } + else + log_verbose("XRANDR: <%d> (add_mode) mode %04lx %dx%d refresh %.6f added\n", m_id, mode->platform_data, mode->hactive, mode->vactive, mode->vfreq); + + return ms_xerrors == 0; +} + +//============================================================ +// xrandr_timing::find_mode_by_name +//============================================================ + +XRRModeInfo *xrandr_timing::find_mode_by_name(char *name) +{ + XRRModeInfo *pxmode = NULL; + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root); + + // use SR name to return the mode + for (int m = 0; m < resources->nmode; m++) + { + if (strcmp(resources->modes[m].name, name) == 0) + { + pxmode = &resources->modes[m]; + break; + } + } + + XRRFreeScreenResources(resources); + + return pxmode; +} + +//============================================================ +// xrandr_timing::find_mode +//============================================================ + +XRRModeInfo *xrandr_timing::find_mode(modeline *mode) +{ + XRRModeInfo *pxmode = NULL; + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root); + + // use platform_data (mode id) to return the mode + for (int m = 0; m < resources->nmode; m++) + { + if (mode->platform_data == resources->modes[m].id) + { + pxmode = &resources->modes[m]; + break; + } + } + + XRRFreeScreenResources(resources); + + return pxmode; +} + +//============================================================ +// xrandr_timing::set_timing +//============================================================ + +bool xrandr_timing::set_timing(modeline *mode) +{ + if (m_enable_screen_compositing) + return set_timing(mode, 0); + + return set_timing(mode, XRANDR_DISABLE_CRTC_RELOCATION); +} + +//============================================================ +// xrandr_timing::set_timing +//============================================================ + +bool xrandr_timing::set_timing(modeline *mode, int flags) +{ + // Handle no screen detected case + if (m_desktop_output == -1) + { + log_error("XRANDR: <%d> (set_timing) [ERROR] no screen detected\n", m_id); + return false; + } + + if (!m_managed) + { + log_error("XRANDR: <%d> (set_timing) [WARNING] this screen is managed by <%d>\n", m_id, sp_shared_screen_manager[m_desktop_output]); + return false; + } + + if (m_id != 1 && (flags & XRANDR_ENABLE_SCREEN_REORDERING)) + flags = XRANDR_DISABLE_CRTC_RELOCATION; // only master can do global screen preparation + + XRRModeInfo *pxmode = NULL; + + if (mode->type & MODE_DESKTOP) + pxmode = &m_desktop_mode; + else + pxmode = find_mode(mode); + + if (pxmode == NULL) + { + log_error("XRANDR: <%d> (set_timing) [ERROR] mode not found\n", m_id); + return false; + } + + // Use xrandr to switch to new mode. + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root); + XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[m_desktop_output]); + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc); + + if (flags & XRANDR_DISABLE_CRTC_RELOCATION) + log_verbose("XRANDR: <%d> (set_timing) DISABLE crtc relocation\n", m_id); + + if (flags & XRANDR_ENABLE_SCREEN_REORDERING) + log_verbose("XRANDR: <%d> (set_timing) GLOBAL desktop screen preparation\n", m_id); + else if (m_last_crtc.mode == crtc_info->mode && m_last_crtc.x == crtc_info->x && m_last_crtc.y == crtc_info->y && pxmode->id == crtc_info->mode) + log_verbose("XRANDR: <%d> (set_timing) requested mode is already active [%04lx] %ux%u+%d+%d\n", m_id, crtc_info->mode, crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y); + else if (m_last_crtc.mode != crtc_info->mode) + { + log_verbose("XRANDR: <%d> (set_timing) [WARNING] unexpected active modeline detected (last:[%04lx] now:[%04lx] %ux%u+%d+%d want:[%04lx])\n", m_id, m_last_crtc.mode, crtc_info->mode, crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y, pxmode->id); + *crtc_info = m_last_crtc; + } + + // Grab X server to prevent unwanted interaction from the window manager + XGrabServer(m_pdisplay); + + unsigned int width = m_min_width; + unsigned int height = m_min_height; + + unsigned int active_crtc = 0; + + unsigned int reordering_last_y = 0; + + ms_xerrors = 0; + + XRRCrtcInfo *global_crtc = new XRRCrtcInfo[resources->ncrtc]; + XRRCrtcInfo *original_crtc = new XRRCrtcInfo[resources->ncrtc]; + + // caculate necessary screen size and of crtc neighborhood if they have at least one side aligned with the mode changed crtc + for (int c = 0; c < resources->ncrtc; c++) + { + // Prepare crtc references + memcpy(&original_crtc[c], XRRGetCrtcInfo(m_pdisplay, resources, resources->crtcs[c]), sizeof(XRRCrtcInfo)); + memcpy(&global_crtc[c], XRRGetCrtcInfo(m_pdisplay, resources, resources->crtcs[c]), sizeof(XRRCrtcInfo)); + // Original state + XRRCrtcInfo *crtc_info0 = &original_crtc[c]; + // Modified state + XRRCrtcInfo *crtc_info1 = &global_crtc[c]; + // clear timestamp + crtc_info1->timestamp = 0; + + // Skip unused crtc + if (output_info->crtc != 0 && crtc_info0->mode != 0) + { + if (flags & XRANDR_ENABLE_SCREEN_REORDERING) + { + // Relocate all crtcs + // Super resolution placement, vertical stacking, reserved XRANDR_REORDERING_MAXIMUM_HEIGHT pixels + crtc_info1->x = 0; + crtc_info1->y = reordering_last_y; + if (crtc_info1->height > XRANDR_REORDERING_MAXIMUM_HEIGHT) + reordering_last_y += crtc_info1->height; + else + reordering_last_y += XRANDR_REORDERING_MAXIMUM_HEIGHT; + crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_REORDERING; + active_crtc++; + } + // Switchres selected desktop output + else if (resources->crtcs[c] == output_info->crtc) + { + crtc_info1->timestamp |= XRANDR_SETMODE_IS_DESKTOP; + crtc_info1->mode = pxmode->id; + crtc_info1->width = pxmode->width; + crtc_info1->height = pxmode->height; + + if (mode->type & MODE_DESKTOP) + { + if (!m_enable_screen_compositing && (crtc_info1->x != sp_desktop_crtc[c].x || crtc_info1->y != sp_desktop_crtc[c].y)) + { + // Restore original desktop position + crtc_info1->x = sp_desktop_crtc[c].x; + crtc_info1->y = sp_desktop_crtc[c].y; + crtc_info1->timestamp |= XRANDR_SETMODE_RESTORE_DESKTOP; + } + } + else + { + // Use curent position + crtc_info1->x = crtc_info->x; + crtc_info1->y = crtc_info->y; + } + + if (crtc_info0->mode != crtc_info1->mode || crtc_info0->width != crtc_info1->width || crtc_info0->height != crtc_info1->height || crtc_info0->x != crtc_info1->x || crtc_info0->y != crtc_info1->y) + crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_DESKTOP_CRTC; + } + else if (mode->type & MODE_DESKTOP && m_enable_screen_reordering && (crtc_info1->x != sp_desktop_crtc[c].x || crtc_info1->y != sp_desktop_crtc[c].y)) + { + crtc_info1->x = sp_desktop_crtc[c].x; + crtc_info1->y = sp_desktop_crtc[c].y; + crtc_info1->timestamp |= (XRANDR_SETMODE_RESTORE_DESKTOP | XRANDR_SETMODE_UPDATE_REORDERING); + } + } + } + + for (int c = 0; c < resources->ncrtc; c++) + { + // Original state + XRRCrtcInfo *crtc_info0 = &original_crtc[c]; + // Modified state + XRRCrtcInfo *crtc_info1 = &global_crtc[c]; + + // Skip unused crtc + if (output_info->crtc != 0 && crtc_info0->mode != 0) + { + if ((flags & XRANDR_DISABLE_CRTC_RELOCATION) == 0 && (crtc_info1->timestamp & XRANDR_SETMODE_IS_DESKTOP) == 0) + { + // relocate crtc impacted by new width + if (crtc_info1->x >= crtc_info->x + (int)crtc_info->width) + { + crtc_info1->x += pxmode->width - crtc_info->width; + crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_OTHER_CRTC; + } + + // relocate crtc impacted by new height + if (crtc_info1->y >= crtc_info->y + (int)crtc_info->height) + { + crtc_info1->y += pxmode->height - crtc_info->height; + crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_OTHER_CRTC; + } + } + + // Calculate overall screen size based on crtcs placement + if (crtc_info1->x + crtc_info1->width > width) + width = crtc_info1->x + crtc_info1->width; + + if (crtc_info1->y + crtc_info1->height > height) + height = crtc_info1->y + crtc_info1->height; + + if (width > m_max_width) + { + log_error("XRANDR: <%d> (set_timing) [ERROR] width is above allowed maximum (%d > %d)\n", m_id, width, m_max_width); + width = m_max_width; + } + + if (height > m_max_height) + { + log_error("XRANDR: <%d> (set_timing) [ERROR] height is above allowed maximum (%d > %d)\n", m_id, height, m_max_height); + height = m_max_height; + } + + if (crtc_info1->timestamp & XRANDR_SETMODE_UPDATE_MASK) + log_verbose("XRANDR: <%d> (set_timing) crtc %d%s [%04lx] %ux%u+%d+%d --> [%04lx] %ux%u+%d+%d flags [%02lx]\n", m_id, c, crtc_info1->timestamp & 1 ? "*" : " ", crtc_info0->mode, crtc_info0->width, crtc_info0->height, crtc_info0->x, crtc_info0->y, crtc_info1->mode, crtc_info1->width, crtc_info1->height, crtc_info1->x, crtc_info1->y, crtc_info1->timestamp); + else if (crtc_info1->timestamp & XRANDR_SETMODE_INFO_MASK) + log_verbose("XRANDR: <%d> (set_timing) crtc %d%s [%04lx] %ux%u+%d+%d flags [%02lx]\n", m_id, c, crtc_info1->timestamp & 1 ? "*" : " ", crtc_info1->mode, crtc_info1->width, crtc_info1->height, crtc_info1->x, crtc_info1->y, crtc_info1->timestamp); + else + log_verbose("XRANDR: <%d> (set_timing) crtc %d [%04lx] %ux%u+%d+%d\n", m_id, c, crtc_info1->mode, crtc_info1->width, crtc_info1->height, crtc_info1->x, crtc_info1->y); + } + } + + // Disable crtc with pending modification + for (int c = 0; c < resources->ncrtc; c++) + { + // Modified state + if (global_crtc[c].timestamp & XRANDR_SETMODE_UPDATE_MASK) + { + if (XRRSetCrtcConfig(m_pdisplay, resources, resources->crtcs[c], CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0) != RRSetConfigSuccess) + { + log_error("XRANDR: <%d> (set_timing) [ERROR] when disabling crtc %d\n", m_id, c); + ms_xerrors_flag = 0x01; + ms_xerrors |= ms_xerrors_flag; + } + } + } + + // Set the framebuffer screen size to enable all crtc + if (ms_xerrors == 0) + { + log_verbose("XRANDR: <%d> (set_timing) setting screen size to %d x %d\n", m_id, width, height); + XSync(m_pdisplay, False); + ms_xerrors_flag = 0x02; + old_error_handler = XSetErrorHandler(error_handler); + XRRSetScreenSize(m_pdisplay, m_root, width, height, (int) ((25.4 * width) / 96.0), (int) ((25.4 * height) / 96.0)); + XSync(m_pdisplay, False); + XSetErrorHandler(old_error_handler); + if (ms_xerrors & ms_xerrors_flag) + log_error("XRANDR: <%d> (set_timing) [ERROR] in %s\n", m_id, "XRRSetScreenSize"); + } + + // Refresh all crtc, switch modeline and set new placement + for (int c = 0; c < resources->ncrtc; c++) + { + // Modified state + XRRCrtcInfo *crtc_info1 = &global_crtc[c]; + if (crtc_info1->timestamp & XRANDR_SETMODE_UPDATE_MASK) + { + if (crtc_info1->timestamp & XRANDR_SETMODE_IS_DESKTOP) + XFillRectangle(m_pdisplay, m_root, XCreateGC(m_pdisplay, m_root, 0, 0), crtc_info1->x, crtc_info1->y, crtc_info1->width, crtc_info1->height); + // enable crtc with updated parameters + XSync(m_pdisplay, False); + ms_xerrors_flag = 0x14; + old_error_handler = XSetErrorHandler(error_handler); + XRRSetCrtcConfig(m_pdisplay, resources, resources->crtcs[c], CurrentTime, crtc_info1->x, crtc_info1->y, crtc_info1->mode, crtc_info1->rotation, crtc_info1->outputs, crtc_info1->noutput); + XSync(m_pdisplay, False); + XSetErrorHandler(old_error_handler); + if (ms_xerrors & 0x10) + { + log_error("XRANDR: <%d> (set_timing) [ERROR] in %s crtc %d set modeline %04lx\n", m_id, "XRRSetCrtcConfig", c, crtc_info1->mode); + ms_xerrors &= 0xEF; + } + } + } + delete[]original_crtc; + delete[]global_crtc; + + // Release X server, events can be processed now + XUngrabServer(m_pdisplay); + + if (ms_xerrors & ms_xerrors_flag) + log_error("XRANDR: <%d> (set_timing) [ERROR] in %s\n", m_id, "XRRSetCrtcConfig"); + + // Recall the impacted crtc to settle parameters + XRRFreeCrtcInfo(crtc_info); + crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc); + + // crtc config modeline change fail + if (crtc_info->mode == 0) + log_error("XRANDR: <%d> (set_timing) [ERROR] switching resolution failed, no modeline is set\n", m_id); + else + // save last crtc + m_last_crtc = *crtc_info; + + XRRFreeCrtcInfo(crtc_info); + XRRFreeOutputInfo(output_info); + XRRFreeScreenResources(resources); + + return (ms_xerrors == 0 && crtc_info->mode != 0); +} + +//============================================================ +// xrandr_timing::delete_mode +//============================================================ + +bool xrandr_timing::delete_mode(modeline *mode) +{ + // Handle no screen detected case + if (m_desktop_output == -1) + { + log_error("XRANDR: <%d> (delete_mode) [ERROR] no screen detected\n", m_id); + return false; + } + + if (!m_managed) + { + log_error("XRANDR: <%d> (delete_mode) [WARNING] this screen is managed by <%d>\n", m_id, sp_shared_screen_manager[m_desktop_output]); + return false; + } + + if (!mode) + return false; + + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root); + + int total_xerrors = 0; + // Delete modeline + for (int m = 0; m < resources->nmode && mode->platform_data != 0; m++) + { + if (mode->platform_data == resources->modes[m].id) + { + XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[m_desktop_output]); + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc); + if (resources->modes[m].id == crtc_info->mode) + log_verbose("XRANDR: <%d> (delete_mode) [WARNING] modeline [%04lx] is currently active\n", m_id, resources->modes[m].id); + + XRRFreeCrtcInfo(crtc_info); + XRRFreeOutputInfo(output_info); + + log_verbose("XRANDR: <%d> (delete_mode) remove mode %s\n", m_id, resources->modes[m].name); + + XSync(m_pdisplay, False); + ms_xerrors = 0; + ms_xerrors_flag = 0x01; + old_error_handler = XSetErrorHandler(error_handler); + XRRDeleteOutputMode(m_pdisplay, resources->outputs[m_desktop_output], resources->modes[m].id); + if (ms_xerrors & ms_xerrors_flag) + { + log_error("XRANDR: <%d> (delete_mode) [ERROR] in %s\n", m_id, "XRRDeleteOutputMode"); + total_xerrors++; + } + + ms_xerrors_flag = 0x02; + XRRDestroyMode(m_pdisplay, resources->modes[m].id); + XSync(m_pdisplay, False); + XSetErrorHandler(old_error_handler); + if (ms_xerrors & ms_xerrors_flag) + { + log_error("XRANDR: <%d> (delete_mode) [ERROR] in %s\n", m_id, "XRRDestroyMode"); + total_xerrors++; + } + mode->platform_data = 0; + } + } + + XRRFreeScreenResources(resources); + + return total_xerrors == 0; +} + +//============================================================ +// xrandr_timing::get_timing +//============================================================ + +bool xrandr_timing::get_timing(modeline *mode) +{ + // Handle no screen detected case + if (m_desktop_output == -1) + { + log_error("XRANDR: <%d> (get_timing) [ERROR] no screen detected\n", m_id); + return false; + } + + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root); + XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[m_desktop_output]); + + // Cycle through the modelines and report them back to the display manager + if (m_video_modes_position < output_info->nmode) + { + for (int m = 0; m < resources->nmode; m++) + { + XRRModeInfo *pxmode = &resources->modes[m]; + + if (pxmode->id == output_info->modes[m_video_modes_position]) + { + mode->platform_data = pxmode->id; + + mode->pclock = pxmode->dotClock; + mode->hactive = pxmode->width; + mode->hbegin = pxmode->hSyncStart; + mode->hend = pxmode->hSyncEnd; + mode->htotal = pxmode->hTotal; + mode->vactive = pxmode->height; + mode->vbegin = pxmode->vSyncStart; + mode->vend = pxmode->vSyncEnd; + mode->vtotal = pxmode->vTotal; + mode->interlace = (pxmode->modeFlags & RR_Interlace) ? 1 : 0; + mode->doublescan = (pxmode->modeFlags & RR_DoubleScan) ? 1 : 0; + mode->hsync = (pxmode->modeFlags & RR_HSyncPositive) ? 1 : 0; + mode->vsync = (pxmode->modeFlags & RR_VSyncPositive) ? 1 : 0; + + mode->hfreq = mode->pclock / mode->htotal; + mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace ? 2 : 1); + mode->refresh = mode->vfreq; + + mode->width = pxmode->width; + mode->height = pxmode->height; + + // Add the rotation flag from the crtc + mode->type |= m_crtc_flags; + + mode->type |= CUSTOM_VIDEO_TIMING_XRANDR; + + if (strncmp(pxmode->name, "SR-", 3) == 0) + log_verbose("XRANDR: <%d> (get_timing) [WARNING] modeline %s detected\n", m_id, pxmode->name); + + // Add the desktop flag to desktop modeline + if (m_desktop_mode.id == pxmode->id) + mode->type |= MODE_DESKTOP; + + log_verbose("XRANDR: <%d> (get_timing) mode %04lx %dx%d refresh %.6f added\n", m_id, pxmode->id, pxmode->width, pxmode->height, mode->vfreq); + } + } + m_video_modes_position++; + } + else + { + // Inititalise the position for the modeline list + m_video_modes_position = 0; + } + + XRRFreeOutputInfo(output_info); + XRRFreeScreenResources(resources); + + return true; +} + +//============================================================ +// xrandr_timing::process_modelist +//============================================================ + +bool xrandr_timing::process_modelist(std::vector modelist) +{ + bool error = false; + bool result = false; + + for (auto &mode : modelist) + { + if (mode->type & MODE_DELETE) + result = delete_mode(mode); + + else if (mode->type & MODE_ADD) + result = add_mode(mode); + + if (!result) + { + mode->type |= MODE_ERROR; + error = true; + } + else + // succeed + mode->type &= ~MODE_ERROR; + } + + return !error; +} diff --git a/3rdparty/switchres/custom_video_xrandr.h b/3rdparty/switchres/custom_video_xrandr.h new file mode 100644 index 0000000000000..5a0db59125a9b --- /dev/null +++ b/3rdparty/switchres/custom_video_xrandr.h @@ -0,0 +1,123 @@ +/************************************************************** + + custom_video_xrandr.h - Linux XRANDR video management layer + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __CUSTOM_VIDEO_XRANDR__ +#define __CUSTOM_VIDEO_XRANDR__ + +// X11 Xrandr headers +#include +#include "custom_video.h" + +// Set timing option flags +#define XRANDR_DISABLE_CRTC_RELOCATION 0x00000001 +#define XRANDR_ENABLE_SCREEN_REORDERING 0x00000002 + +// Set timing internal flags +#define XRANDR_SETMODE_IS_DESKTOP 0x00000001 +#define XRANDR_SETMODE_RESTORE_DESKTOP 0x00000002 +#define XRANDR_SETMODE_UPDATE_DESKTOP_CRTC 0x00000010 +#define XRANDR_SETMODE_UPDATE_OTHER_CRTC 0x00000020 +#define XRANDR_SETMODE_UPDATE_REORDERING 0x00000040 + +#define XRANDR_SETMODE_INFO_MASK 0x0000000F +#define XRANDR_SETMODE_UPDATE_MASK 0x000000F0 + +// Super resolution placement, vertical stacking, reserved XRANDR_REORDERING_MAXIMUM_HEIGHT pixels +//TODO confirm 1024 height is sufficient +#define XRANDR_REORDERING_MAXIMUM_HEIGHT 1024 + +class xrandr_timing : public custom_video +{ + public: + xrandr_timing(char *device_name, custom_video_settings *vs); + ~xrandr_timing(); + const char *api_name() { return "XRANDR"; } + int caps() { return CUSTOM_VIDEO_CAPS_ADD; } + bool init(); + + bool add_mode(modeline *mode); + bool delete_mode(modeline *mode); + bool update_mode(modeline *mode); + + bool get_timing(modeline *mode); + bool set_timing(modeline *mode); + + bool process_modelist(std::vector); + + static int ms_xerrors; + static int ms_xerrors_flag; + + private: + int m_id = 0; + int m_managed = 0; + int m_enable_screen_reordering = 0; + int m_enable_screen_compositing = 0; + + XRRModeInfo *find_mode(modeline *mode); + XRRModeInfo *find_mode_by_name(char *name); + + bool set_timing(modeline *mode, int flags); + + int m_video_modes_position = 0; + char m_device_name[32]; + Rotation m_desktop_rotation; + unsigned int m_min_width; + unsigned int m_max_width; + unsigned int m_min_height; + unsigned int m_max_height; + + Display *m_pdisplay = NULL; + Window m_root; + int m_screen; + + int m_desktop_output = -1; + XRRModeInfo m_desktop_mode = {}; + int m_crtc_flags = 0; + + XRRCrtcInfo m_last_crtc = {}; + + void *m_xrandr_handle = 0; + + __typeof__(XRRAddOutputMode) *p_XRRAddOutputMode; + __typeof__(XRRConfigCurrentConfiguration) *p_XRRConfigCurrentConfiguration; + __typeof__(XRRCreateMode) *p_XRRCreateMode; + __typeof__(XRRDeleteOutputMode) *p_XRRDeleteOutputMode; + __typeof__(XRRDestroyMode) *p_XRRDestroyMode; + __typeof__(XRRFreeCrtcInfo) *p_XRRFreeCrtcInfo; + __typeof__(XRRFreeOutputInfo) *p_XRRFreeOutputInfo; + __typeof__(XRRFreeScreenConfigInfo) *p_XRRFreeScreenConfigInfo; + __typeof__(XRRFreeScreenResources) *p_XRRFreeScreenResources; + __typeof__(XRRGetCrtcInfo) *p_XRRGetCrtcInfo; + __typeof__(XRRGetOutputInfo) *p_XRRGetOutputInfo; + __typeof__(XRRGetScreenInfo) *p_XRRGetScreenInfo; + __typeof__(XRRGetScreenResourcesCurrent) *p_XRRGetScreenResourcesCurrent; + __typeof__(XRRQueryVersion) *p_XRRQueryVersion; + __typeof__(XRRSetCrtcConfig) *p_XRRSetCrtcConfig; + __typeof__(XRRSetScreenSize) *p_XRRSetScreenSize; + __typeof__(XRRGetScreenSizeRange) *p_XRRGetScreenSizeRange; + + void *m_x11_handle = 0; + + __typeof__(XCloseDisplay) *p_XCloseDisplay; + __typeof__(XGrabServer) *p_XGrabServer; + __typeof__(XOpenDisplay) *p_XOpenDisplay; + __typeof__(XSync) *p_XSync; + __typeof__(XUngrabServer) *p_XUngrabServer; + __typeof__(XSetErrorHandler) *p_XSetErrorHandler; + __typeof__(XClearWindow) *p_XClearWindow; + __typeof__(XFillRectangle) *p_XFillRectangle; + __typeof__(XCreateGC) *p_XCreateGC; +}; + +#endif diff --git a/3rdparty/switchres/display.cpp b/3rdparty/switchres/display.cpp new file mode 100644 index 0000000000000..554cab60511f2 --- /dev/null +++ b/3rdparty/switchres/display.cpp @@ -0,0 +1,481 @@ +/************************************************************** + + display.cpp - Display manager + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include "display.h" +#if defined(_WIN32) +#include "display_windows.h" +#elif defined(__linux__) +#include +#include "display_linux.h" +#endif +#include "log.h" + +//============================================================ +// display_manager::make +//============================================================ + +display_manager *display_manager::make(display_settings *ds) +{ + display_manager *display = nullptr; + +#if defined(_WIN32) + display = new windows_display(ds); +#elif defined(__linux__) + display = new linux_display(ds); +#endif + + return display; +} + +//============================================================ +// display_manager::parse_options +//============================================================ + +void display_manager::parse_options() +{ + // Get user_mode as x@ + set_user_mode(&m_ds.user_mode); + + // Get user defined modeline (overrides user_mode) + modeline user_mode = {}; + if (m_ds.modeline_generation) + { + if (modeline_parse(m_ds.user_modeline, &user_mode)) + { + user_mode.type |= MODE_USER_DEF; + set_user_mode(&user_mode); + } + } + + // Get monitor specs + if (user_mode.hactive) + { + modeline_to_monitor_range(range, &user_mode); + monitor_show_range(range); + } + else + { + char default_monitor[] = "generic_15"; + + memset(&range[0], 0, sizeof(struct monitor_range) * MAX_RANGES); + + if (!strcmp(m_ds.monitor, "custom")) + for (int i = 0; i < MAX_RANGES; i++) monitor_fill_range(&range[i], m_ds.crt_range[i]); + + else if (!strcmp(m_ds.monitor, "lcd")) + monitor_fill_lcd_range(&range[0], m_ds.lcd_range); + + else if (monitor_set_preset(m_ds.monitor, range) == 0) + monitor_set_preset(default_monitor, range); + } +} + +//============================================================ +// display_manager::init +//============================================================ + +bool display_manager::init() +{ + sprintf(m_ds.screen, "ram"); + + return true; +} + +//============================================================ +// display_manager::caps +//============================================================ + +int display_manager::caps() +{ + if (video()) + return video()->caps(); + else + return CUSTOM_VIDEO_CAPS_ADD; +} + +//============================================================ +// display_manager::add_mode +//============================================================ + +bool display_manager::add_mode(modeline *mode) +{ + if (video() == nullptr) + return false; + + // Add new mode + if (!video()->add_mode(mode)) + { + log_verbose("Switchres: error adding mode "); + log_mode(mode); + return false; + } + + mode->type &= ~MODE_ADD; + + log_verbose("Switchres: added "); + log_mode(mode); + + return true; +} + +//============================================================ +// display_manager::delete_mode +//============================================================ + +bool display_manager::delete_mode(modeline *mode) +{ + if (video() == nullptr) + return false; + + if (!video()->delete_mode(mode)) + { + log_verbose("Switchres: error deleting mode "); + log_mode(mode); + return false; + } + + log_verbose("Switchres: deleted "); + log_mode(mode); + return true; +} + +//============================================================ +// display_manager::update_mode +//============================================================ + +bool display_manager::update_mode(modeline *mode) +{ + if (video() == nullptr) + return false; + + // Apply new timings + if (!video()->update_mode(mode)) + { + log_verbose("Switchres: error updating mode "); + log_mode(mode); + return false; + } + + mode->type &= ~MODE_UPDATE; + + log_verbose("Switchres: updated "); + log_mode(mode); + return true; +} + +//============================================================ +// display_manager::set_mode +//============================================================ + +bool display_manager::set_mode(modeline *) +{ + return false; +} + +//============================================================ +// display_manager::log_mode +//============================================================ + +void display_manager::log_mode(modeline *mode) +{ + char modeline_txt[256]; + log_verbose("%s timing %s\n", video()->api_name(), modeline_print(mode, modeline_txt, MS_FULL)); +} + +//============================================================ +// display_manager::restore_modes +//============================================================ + +bool display_manager::restore_modes() +{ + // Compare each mode in our table with its original state + for (unsigned i = video_modes.size(); i-- > 0; ) + { + // First, delete all modes we've added + if (i + 1 > backup_modes.size()) + video_modes[i].type |= MODE_DELETE; + + // Now restore all modes which timings have been modified + else if (modeline_is_different(&video_modes[i], &backup_modes[i])) + { + video_modes[i] = backup_modes[i]; + video_modes[i].type |= MODE_UPDATE; + } + } + // Finally, flush pending changes to driver + return flush_modes(); +} + +//============================================================ +// display_manager::flush_modes +//============================================================ + +bool display_manager::flush_modes() +{ + bool error = false; + std::vector modified_modes = {}; + + if (video() == nullptr) + return false; + + // Loop through our mode table to collect all pending changes + for (auto &mode : video_modes) + if (mode.type & (MODE_UPDATE | MODE_ADD | MODE_DELETE)) + modified_modes.push_back(&mode); + + // Flush pending changes to driver + if (modified_modes.size() > 0) + { + video()->process_modelist(modified_modes); + + // Log error/success result for each mode + for (auto &mode : modified_modes) + { + log_verbose("Switchres: %s %s mode ", mode->type & MODE_ERROR? "error" : "success", mode->type & MODE_DELETE? "deleting" : mode->type & MODE_ADD? "adding" : "updating"); + log_mode(mode); + + if (mode->type & MODE_ERROR) + error = true; + } + + // Update our internal mode table to reflect the changes + for (unsigned i = video_modes.size(); i-- > 0; ) + { + if (video_modes[i].type & MODE_ERROR) + continue; + + if (video_modes[i].type & MODE_DELETE) + { + video_modes.erase(video_modes.begin() + i); + m_best_mode = 0; + } + else + video_modes[i].type &= ~(MODE_UPDATE | MODE_ADD); + } + } + + return !error; +} + +//============================================================ +// display_manager::filter_modes +//============================================================ + +bool display_manager::filter_modes() +{ + for (auto &mode : video_modes) + { + // apply options to mode type + if (m_ds.refresh_dont_care) + mode.type |= V_FREQ_EDITABLE; + + if ((caps() & CUSTOM_VIDEO_CAPS_UPDATE)) + mode.type |= V_FREQ_EDITABLE; + + if (caps() & CUSTOM_VIDEO_CAPS_SCAN_EDITABLE) + mode.type |= SCAN_EDITABLE; + + if (!m_ds.modeline_generation) + mode.type &= ~(XYV_EDITABLE | SCAN_EDITABLE); + + if ((mode.type & MODE_DESKTOP) && !(caps() & CUSTOM_VIDEO_CAPS_DESKTOP_EDITABLE)) + mode.type &= ~V_FREQ_EDITABLE; + + if (m_ds.lock_system_modes && (mode.type & CUSTOM_VIDEO_TIMING_SYSTEM)) + mode.type |= MODE_DISABLED; + + // Make sure to unlock the desktop mode as fallback + if (mode.type & MODE_DESKTOP) + mode.type &= ~MODE_DISABLED; + + // Lock all modes that don't match the user's -resolution rules + if (m_user_mode.width != 0 || m_user_mode.height != 0 || m_user_mode.refresh == !0) + { + if (!( (mode.width == m_user_mode.width || (mode.type & X_RES_EDITABLE) || m_user_mode.width == 0) + && (mode.height == m_user_mode.height || (mode.type & Y_RES_EDITABLE) || m_user_mode.height == 0) + && (mode.refresh == m_user_mode.refresh || (mode.type & V_FREQ_EDITABLE) || m_user_mode.refresh == 0) )) + mode.type |= MODE_DISABLED; + else + mode.type &= ~MODE_DISABLED; + } + } + + return true; +} + +//============================================================ +// display_manager::get_video_mode +//============================================================ + +modeline *display_manager::get_mode(int width, int height, float refresh, bool interlaced) +{ + modeline s_mode = {}; + modeline t_mode = {}; + modeline best_mode = {}; + char result[256]={'\x00'}; + + log_verbose("Switchres: Calculating best video mode for %dx%d@%.6f%s orientation: %s\n", + width, height, refresh, interlaced?"i":"", rotation()?"rotated":"normal"); + + best_mode.result.weight |= R_OUT_OF_RANGE; + + s_mode.interlace = interlaced; + s_mode.vfreq = refresh; + + s_mode.hactive = normalize(width, 8); + s_mode.vactive = height; + + if (rotation()) std::swap(s_mode.hactive, s_mode.vactive); + + // Create a dummy mode entry if allowed + if (caps() & CUSTOM_VIDEO_CAPS_ADD && m_ds.modeline_generation) + { + modeline new_mode = {}; + new_mode.type = XYV_EDITABLE | V_FREQ_EDITABLE | SCAN_EDITABLE | MODE_ADD | (desktop_is_rotated()? MODE_ROTATED : MODE_OK); + video_modes.push_back(new_mode); + } + + // Run through our mode list and find the most suitable mode + for (auto &mode : video_modes) + { + log_verbose("\nSwitchres: %s%4d%sx%s%4d%s_%s%d=%.6fHz%s%s\n", + mode.type & X_RES_EDITABLE?"(":"[", mode.width, mode.type & X_RES_EDITABLE?")":"]", + mode.type & Y_RES_EDITABLE?"(":"[", mode.height, mode.type & Y_RES_EDITABLE?")":"]", + mode.type & V_FREQ_EDITABLE?"(":"[", mode.refresh, mode.vfreq, mode.type & V_FREQ_EDITABLE?")":"]", + mode.type & MODE_DISABLED?" - locked":""); + + // now get the mode if allowed + if (!(mode.type & MODE_DISABLED)) + { + for (int i = 0 ; i < MAX_RANGES ; i++) + { + if (range[i].hfreq_min) + { + t_mode = mode; + + // init all editable fields with source or user values + if (t_mode.type & X_RES_EDITABLE) + t_mode.hactive = m_user_mode.width? m_user_mode.width : s_mode.hactive; + + if (t_mode.type & Y_RES_EDITABLE) + t_mode.vactive = m_user_mode.height? m_user_mode.height : s_mode.vactive; + + if (t_mode.type & V_FREQ_EDITABLE) + { + // If user's vfreq is defined, it means we have an user modeline, so force it + if (m_user_mode.vfreq) + t_mode = m_user_mode; + else + t_mode.vfreq = s_mode.vfreq; + } + + // lock resolution fields if required + if (m_user_mode.width) t_mode.type &= ~X_RES_EDITABLE; + if (m_user_mode.height) t_mode.type &= ~Y_RES_EDITABLE; + if (m_user_mode.vfreq) t_mode.type &= ~V_FREQ_EDITABLE; + + modeline_create(&s_mode, &t_mode, &range[i], &m_ds.gs); + t_mode.range = i; + + log_verbose("%s\n", modeline_result(&t_mode, result)); + + if (modeline_compare(&t_mode, &best_mode)) + { + best_mode = t_mode; + m_best_mode = &mode; + } + } + } + } + } + + // If we didn't need to create a new mode, remove our dummy entry + if (caps() & CUSTOM_VIDEO_CAPS_ADD && m_ds.modeline_generation && m_best_mode != &video_modes.back()) + video_modes.pop_back(); + + // If we didn't find a suitable mode, exit now + if (best_mode.result.weight & R_OUT_OF_RANGE) + { + m_best_mode = 0; + log_error("Switchres: could not find a video mode that meets your specs\n"); + return nullptr; + } + + log_verbose("\nSwitchres: %s (%dx%d@%.6f)->(%dx%d@%.6f)\n", rotation()?"rotated":"normal", + width, height, refresh, best_mode.hactive, best_mode.vactive, best_mode.vfreq); + + log_verbose("%s\n", modeline_result(&best_mode, result)); + + // Copy the new modeline to our mode list + if (m_ds.modeline_generation) + { + if (best_mode.type & MODE_ADD) + { + best_mode.width = best_mode.hactive; + best_mode.height = best_mode.vactive; + best_mode.refresh = int(best_mode.vfreq); + // lock new mode + best_mode.type &= ~(X_RES_EDITABLE | Y_RES_EDITABLE | (caps() & CUSTOM_VIDEO_CAPS_UPDATE? 0 : V_FREQ_EDITABLE)); + } + else if (modeline_is_different(&best_mode, m_best_mode) != 0) + best_mode.type |= MODE_UPDATE; + + char modeline[256]={'\x00'}; + log_info("Switchres: Modeline %s\n", modeline_print(&best_mode, modeline, MS_FULL)); + } + + // Check if new best mode is different than previous one + m_switching_required = (m_current_mode != m_best_mode || best_mode.type & MODE_UPDATE); + + *m_best_mode = best_mode; + return m_best_mode; +} + +//============================================================ +// display_manager::auto_specs +//============================================================ + +bool display_manager::auto_specs() +{ + // Make sure we have a valid mode + if (desktop_mode.width == 0 || desktop_mode.height == 0 || desktop_mode.refresh == 0) + { + log_error("Switchres: Invalid desktop mode %dx%d@%d\n", desktop_mode.width, desktop_mode.height, desktop_mode.refresh); + return false; + } + + log_verbose("Switchres: Creating automatic specs for LCD based on %s\n", (desktop_mode.type & CUSTOM_VIDEO_TIMING_SYSTEM)? "VESA GTF" : "current timings"); + + // Make sure our current refresh is within range if set to auto + if (!strcmp(m_ds.lcd_range, "auto")) + { + sprintf(m_ds.lcd_range, "%d-%d", desktop_mode.refresh - 1, desktop_mode.refresh + 1); + monitor_fill_lcd_range(range, m_ds.lcd_range); + } + + // Create a working range with the best possible information + if (desktop_mode.type & CUSTOM_VIDEO_TIMING_SYSTEM) modeline_vesa_gtf(&desktop_mode); + modeline_to_monitor_range(range, &desktop_mode); + monitor_show_range(range); + + // Force our resolution to LCD's native one + modeline user_mode = {}; + user_mode.width = desktop_mode.width; + user_mode.height = desktop_mode.height; + user_mode.refresh = desktop_mode.refresh; + set_user_mode(&user_mode); + + return true; +} diff --git a/3rdparty/switchres/display.h b/3rdparty/switchres/display.h new file mode 100644 index 0000000000000..a86c1c8d34400 --- /dev/null +++ b/3rdparty/switchres/display.h @@ -0,0 +1,161 @@ +/************************************************************** + + display.h - Display manager + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __DISPLAY_H__ +#define __DISPLAY_H__ + +#include +#include "modeline.h" +#include "custom_video.h" + +typedef struct display_settings +{ + char screen[32]; + char api[32]; + bool modeline_generation; + bool lock_unsupported_modes; + bool lock_system_modes; + bool refresh_dont_care; + bool keep_changes; + char monitor[32]; + char crt_range[MAX_RANGES][256]; + char lcd_range[256]; + char user_modeline[256]; + modeline user_mode; + + generator_settings gs; + custom_video_settings vs; +} display_settings; + + +class display_manager +{ +public: + + display_manager() {}; + virtual ~display_manager() + { + if (!m_ds.keep_changes) restore_modes(); + if (m_factory) delete m_factory; + }; + + display_manager *make(display_settings *ds); + void parse_options(); + virtual bool init(); + virtual int caps(); + + // getters + custom_video *factory() const { return m_factory; } + custom_video *video() const { return m_video; } + modeline user_mode() const { return m_user_mode; } + modeline *best_mode() const { return m_best_mode; } + modeline *current_mode() const { return m_current_mode; } + int index() const { return m_index; } + bool desktop_is_rotated() const { return m_desktop_is_rotated; } + + // getters (display manager) + const char *set_monitor() { return (const char*) &m_ds.monitor; } + const char *user_modeline() { return (const char*) &m_ds.user_modeline; } + const char *crt_range(int i) { return (const char*) &m_ds.crt_range[i]; } + const char *lcd_range() { return (const char*) &m_ds.lcd_range; } + const char *screen() { return (const char*) &m_ds.screen; } + const char *api() { return (const char*) &m_ds.api; } + bool modeline_generation() { return m_ds.modeline_generation; } + bool lock_unsupported_modes() { return m_ds.lock_unsupported_modes; } + bool lock_system_modes() { return m_ds.lock_system_modes; } + bool refresh_dont_care() { return m_ds.refresh_dont_care; } + bool keep_changes() { return m_ds.keep_changes; } + + // getters (modeline generator) + bool interlace() { return m_ds.gs.interlace; } + bool doublescan() { return m_ds.gs.doublescan; } + double dotclock_min() { return m_ds.gs.pclock_min; } + double refresh_tolerance() { return m_ds.gs.refresh_tolerance; } + int super_width() { return m_ds.gs.super_width; } + bool rotation() { return m_ds.gs.rotation; } + double monitor_aspect() { return m_ds.gs.monitor_aspect; } + int v_shift_correct() { return m_ds.gs.v_shift_correct; } + int pixel_precision() { return m_ds.gs.pixel_precision; } + + // getters (modeline result) + bool got_mode() { return (m_best_mode != nullptr); } + int width() { return m_best_mode != nullptr? m_best_mode->width : 0; } + int height() { return m_best_mode != nullptr? m_best_mode->height : 0; } + int refresh() { return m_best_mode != nullptr? m_best_mode->refresh : 0; } + double v_freq() { return m_best_mode != nullptr? m_best_mode->vfreq : 0; } + double h_freq() { return m_best_mode != nullptr? m_best_mode->hfreq : 0; } + int x_scale() { return m_best_mode != nullptr? m_best_mode->result.x_scale : 0; } + int y_scale() { return m_best_mode != nullptr? m_best_mode->result.y_scale : 0; } + int v_scale() { return m_best_mode != nullptr? m_best_mode->result.v_scale : 0; } + bool is_interlaced() { return m_best_mode != nullptr? m_best_mode->interlace : false; } + bool is_doublescanned() { return m_best_mode != nullptr? m_best_mode->doublescan : false; } + bool is_stretched() { return m_best_mode != nullptr? m_best_mode->result.weight & R_RES_STRETCH : false; } + bool is_refresh_off() { return m_best_mode != nullptr? m_best_mode->result.weight & R_V_FREQ_OFF : false; } + bool is_switching_required() { return m_switching_required; } + bool is_mode_updated() { return m_best_mode != nullptr? m_best_mode->type & MODE_UPDATE : false; } + bool is_mode_new() { return m_best_mode != nullptr? m_best_mode->type & MODE_ADD : false; } + + // setters + void set_factory(custom_video *factory) { m_factory = factory; } + void set_custom_video(custom_video *video) { m_video = video; } + void set_user_mode(modeline *mode) { m_user_mode = *mode; filter_modes(); } + void set_current_mode(modeline *mode) { m_current_mode = mode; } + void set_index(int index) { m_index = index; } + void set_desktop_is_rotated(bool value) { m_desktop_is_rotated = value; } + void set_rotation(bool value) { m_ds.gs.rotation = value; } + void set_monitor_aspect(float aspect) { m_ds.gs.monitor_aspect = aspect; } + void set_v_shift_correct(int value) { m_ds.gs.v_shift_correct = value; } + void set_pixel_precision(int value) { m_ds.gs.pixel_precision = value; } + + // options + display_settings m_ds = {}; + + // mode setting interface + modeline *get_mode(int width, int height, float refresh, bool interlaced); + bool add_mode(modeline *mode); + bool delete_mode(modeline *mode); + bool update_mode(modeline *mode); + virtual bool set_mode(modeline *); + void log_mode(modeline *mode); + + // mode list handling + bool filter_modes(); + bool restore_modes(); + bool flush_modes(); + bool auto_specs(); + + // mode list + std::vector video_modes = {}; + std::vector backup_modes = {}; + modeline desktop_mode = {}; + + // monitor preset + monitor_range range[MAX_RANGES]; + +private: + + // custom video backend + custom_video *m_factory = 0; + custom_video *m_video = 0; + + modeline m_user_mode = {}; + modeline *m_best_mode = 0; + modeline *m_current_mode = 0; + + int m_index = 0; + bool m_desktop_is_rotated = 0; + bool m_switching_required = 0; +}; + +#endif diff --git a/3rdparty/switchres/display_linux.cpp b/3rdparty/switchres/display_linux.cpp new file mode 100644 index 0000000000000..7eb8366854e12 --- /dev/null +++ b/3rdparty/switchres/display_linux.cpp @@ -0,0 +1,163 @@ +/************************************************************** + + display_linux.cpp - Display manager for Linux + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include + +#include "display_linux.h" +#include "log.h" + +//============================================================ +// linux_display::linux_display +//============================================================ + +linux_display::linux_display(display_settings *ds) +{ + // Get display settings + m_ds = *ds; +} + +//============================================================ +// linux_display::~linux_display +//============================================================ + +linux_display::~linux_display() +{ + if (!m_ds.keep_changes) + restore_desktop_mode(); +} + +//============================================================ +// linux_display::init +//============================================================ + +bool linux_display::init() +{ + // Initialize custom video + int method = CUSTOM_VIDEO_TIMING_AUTO; + + if (!strcmp(m_ds.api, "xrandr")) + method = CUSTOM_VIDEO_TIMING_XRANDR; + else if (!strcmp(m_ds.api, "drmkms")) + method = CUSTOM_VIDEO_TIMING_DRMKMS; + + set_factory(new custom_video); + set_custom_video(factory()->make(m_ds.screen, NULL, method, &m_ds.vs)); + if (video()) video()->init(); + + // Build our display's mode list + video_modes.clear(); + backup_modes.clear(); + get_desktop_mode(); + get_available_video_modes(); + + if (!strcmp(m_ds.monitor, "lcd")) auto_specs(); + filter_modes(); + + return true; +} + +//============================================================ +// linux_display::set_mode +//============================================================ + +bool linux_display::set_mode(modeline *mode) +{ + if (mode && set_desktop_mode(mode, 0)) + { + set_current_mode(mode); + return true; + } + return false; +} + +//============================================================ +// linux_display::get_desktop_mode +//============================================================ + +bool linux_display::get_desktop_mode() +{ + if (video() == NULL) + return false; + + return true; +} + +//============================================================ +// linux_display::set_desktop_mode +//============================================================ + +bool linux_display::set_desktop_mode(modeline *mode, int flags) +{ + if (!mode) + return false; + + if (video() == NULL) + return false; + + if (flags != 0) + log_info("Set desktop mode flags value is 0x%x.\n", flags); + + return video()->set_timing(mode); +} + +//============================================================ +// linux_display::restore_desktop_mode +//============================================================ + +bool linux_display::restore_desktop_mode() +{ + if (video() == NULL) + return false; + + return video()->set_timing(&desktop_mode); +} + +//============================================================ +// linux_display::get_available_video_modes +//============================================================ + +int linux_display::get_available_video_modes() +{ + if (video() == NULL) + return false; + + // loop through all modes until NULL mode type is received + for (;;) + { + modeline mode; + memset(&mode, 0, sizeof(struct modeline)); + + // get next mode + video()->get_timing(&mode); + if (mode.type == 0 || mode.platform_data == 0) + break; + + // set the desktop mode + if (mode.type & MODE_DESKTOP) + { + memcpy(&desktop_mode, &mode, sizeof(modeline)); + if (current_mode() == nullptr) + set_current_mode(&mode); + } + + video_modes.push_back(mode); + backup_modes.push_back(mode); + + log_verbose("Switchres: [%3ld] %4dx%4d @%3d%s%s %s: ", video_modes.size(), mode.width, mode.height, mode.refresh, mode.interlace ? "i" : "p", mode.type & MODE_DESKTOP ? "*" : "", mode.type & MODE_ROTATED ? "rot" : ""); + log_mode(&mode); + }; + + return true; +} diff --git a/3rdparty/switchres/display_linux.h b/3rdparty/switchres/display_linux.h new file mode 100644 index 0000000000000..41d377b715120 --- /dev/null +++ b/3rdparty/switchres/display_linux.h @@ -0,0 +1,30 @@ +/************************************************************** + + display_linux.h - Display manager for Linux + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include "display.h" + +class linux_display : public display_manager +{ + public: + linux_display(display_settings *ds); + ~linux_display(); + bool init(); + bool set_mode(modeline *mode); + + private: + bool get_desktop_mode(); + bool set_desktop_mode(modeline *mode, int flags); + bool restore_desktop_mode(); + int get_available_video_modes(); +}; diff --git a/3rdparty/switchres/display_windows.cpp b/3rdparty/switchres/display_windows.cpp new file mode 100644 index 0000000000000..9bd559ea274c0 --- /dev/null +++ b/3rdparty/switchres/display_windows.cpp @@ -0,0 +1,260 @@ +/************************************************************** + + display_windows.cpp - Display manager for Windows + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include "display_windows.h" +#include "log.h" + + +//============================================================ +// windows_display::windows_display +//============================================================ + +windows_display::windows_display(display_settings *ds) +{ + // Get display settings + m_ds = *ds; +} + +//============================================================ +// windows_display::~windows_display +//============================================================ + +windows_display::~windows_display() +{ + // Restore previous settings + if (!m_ds.keep_changes) ChangeDisplaySettingsExA(m_device_name, NULL, NULL, 0, 0); +} + +//============================================================ +// windows_display::init +//============================================================ + +bool windows_display::init() +{ + DISPLAY_DEVICEA lpDisplayDevice[DISPLAY_MAX]; + int idev = 0; + int found = -1; + + while (idev < DISPLAY_MAX) + { + memset(&lpDisplayDevice[idev], 0, sizeof(DISPLAY_DEVICEA)); + lpDisplayDevice[idev].cb = sizeof(DISPLAY_DEVICEA); + + if (EnumDisplayDevicesA(NULL, idev, &lpDisplayDevice[idev], 0) == FALSE) + break; + + if ((!strcmp(m_ds.screen, "auto") && (lpDisplayDevice[idev].StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)) + || !strcmp(m_ds.screen, lpDisplayDevice[idev].DeviceName) || m_ds.screen[0] - '0' == idev) + found = idev; + + idev++; + } + if (found != -1) + { + strncpy(m_device_name, lpDisplayDevice[found].DeviceName, sizeof(m_device_name) -1); + strncpy(m_device_id, lpDisplayDevice[found].DeviceID, sizeof(m_device_id) -1); + log_verbose("Switchres: %s: %s (%s)\n", m_device_name, lpDisplayDevice[found].DeviceString, m_device_id); + + char *pch; + int i; + for (i = 0; i < idev; i++) + { + pch = strstr(lpDisplayDevice[i].DeviceString, lpDisplayDevice[found].DeviceString); + if (pch) + { + found = i; + break; + } + } + + char *chsrc, *chdst; + chdst = m_device_key; + + for (chsrc = lpDisplayDevice[i].DeviceKey + 18; *chsrc != 0; chsrc++) + *chdst++ = *chsrc; + + *chdst = 0; + } + else + { + log_verbose("Switchres: Failed obtaining default video registry key\n"); + return false; + } + + log_verbose("Switchres: Device key: %s\n", m_device_key); + + // Initialize custom video + int method = CUSTOM_VIDEO_TIMING_AUTO; + if(!strcmp(m_ds.api, "powerstrip")) method = CUSTOM_VIDEO_TIMING_POWERSTRIP; + strcpy(m_ds.vs.device_reg_key, m_device_key); + + // Create custom video backend + set_factory(new custom_video); + set_custom_video(factory()->make(m_device_name, m_device_id, method, &m_ds.vs)); + if (video()) video()->init(); + + // Build our display's mode list + video_modes.clear(); + backup_modes.clear(); + get_desktop_mode(); + get_available_video_modes(); + if (!strcmp(m_ds.monitor, "lcd")) auto_specs(); + filter_modes(); + + return true; +} + +//============================================================ +// windows_display::set_mode +//============================================================ + +bool windows_display::set_mode(modeline *mode) +{ + if (mode && set_desktop_mode(mode, (m_ds.keep_changes? CDS_UPDATEREGISTRY : CDS_FULLSCREEN) | CDS_RESET)) + { + set_current_mode(mode); + return true; + } + + return false; +} + +//============================================================ +// windows_display::get_desktop_mode +//============================================================ + +bool windows_display::get_desktop_mode() +{ + memset(&m_devmode, 0, sizeof(DEVMODEA)); + m_devmode.dmSize = sizeof(DEVMODEA); + + if (EnumDisplaySettingsExA(!strcmp(m_device_name, "auto")?NULL:m_device_name, ENUM_CURRENT_SETTINGS, &m_devmode, 0)) + { + desktop_mode.width = m_devmode.dmDisplayOrientation == DMDO_DEFAULT || m_devmode.dmDisplayOrientation == DMDO_180? m_devmode.dmPelsWidth:m_devmode.dmPelsHeight; + desktop_mode.height = m_devmode.dmDisplayOrientation == DMDO_DEFAULT || m_devmode.dmDisplayOrientation == DMDO_180? m_devmode.dmPelsHeight:m_devmode.dmPelsWidth; + desktop_mode.refresh = m_devmode.dmDisplayFrequency; + desktop_mode.interlace = (m_devmode.dmDisplayFlags & DM_INTERLACED)?1:0; + return true; + } + return false; +} + +//============================================================ +// windows_display::set_desktop_mode +//============================================================ + +bool windows_display::set_desktop_mode(modeline *mode, int flags) +{ + if (mode) + { + DEVMODEA lpDevMode; + memset(&lpDevMode, 0, sizeof(DEVMODEA)); + lpDevMode.dmSize = sizeof(DEVMODEA); + lpDevMode.dmPelsWidth = mode->type & MODE_ROTATED? mode->height : mode->width; + lpDevMode.dmPelsHeight = mode->type & MODE_ROTATED? mode->width : mode->height; + lpDevMode.dmDisplayFrequency = (int)mode->refresh; + lpDevMode.dmDisplayFlags = mode->interlace? DM_INTERLACED : 0; + lpDevMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS; + + log_info("set_desktop_mode: %s (%dx%d@%d) flags(%x)\n", m_device_name, (int)lpDevMode.dmPelsWidth, (int)lpDevMode.dmPelsHeight, (int)lpDevMode.dmDisplayFrequency, (int)lpDevMode.dmDisplayFlags); + + int result = ChangeDisplaySettingsExA(m_device_name, &lpDevMode, NULL, flags, 0); + if (result == DISP_CHANGE_SUCCESSFUL) + return true; + + log_error("ChangeDisplaySettingsExA error(%x)\n", (int)result); + } + return false; +} + +//============================================================ +// windows_display::restore_desktop_mode +//============================================================ + +bool windows_display::restore_desktop_mode() +{ + if (ChangeDisplaySettingsExA(m_device_name, &m_devmode, NULL, 0, 0) == DISP_CHANGE_SUCCESSFUL) + return true; + + return false; +} + +//============================================================ +// windows_display::get_available_video_modes +//============================================================ + +int windows_display::get_available_video_modes() +{ + int iModeNum = 0, j = 0, k = 0; + DEVMODEA lpDevMode; + + memset(&lpDevMode, 0, sizeof(DEVMODEA)); + lpDevMode.dmSize = sizeof(DEVMODEA); + + log_verbose("Switchres: Searching for custom video modes...\n"); + + while (EnumDisplaySettingsExA(m_device_name, iModeNum, &lpDevMode, m_ds.lock_unsupported_modes?0:EDS_RAWMODE) != 0) + { + if (lpDevMode.dmBitsPerPel == 32 && lpDevMode.dmDisplayFixedOutput == DMDFO_DEFAULT) + { + modeline m; + memset(&m, 0, sizeof(struct modeline)); + m.interlace = (lpDevMode.dmDisplayFlags & DM_INTERLACED)?1:0; + m.width = lpDevMode.dmDisplayOrientation == DMDO_DEFAULT || lpDevMode.dmDisplayOrientation == DMDO_180? lpDevMode.dmPelsWidth:lpDevMode.dmPelsHeight; + m.height = lpDevMode.dmDisplayOrientation == DMDO_DEFAULT || lpDevMode.dmDisplayOrientation == DMDO_180? lpDevMode.dmPelsHeight:lpDevMode.dmPelsWidth; + m.refresh = lpDevMode.dmDisplayFrequency; + m.hactive = m.width; + m.vactive = m.height; + m.vfreq = m.refresh; + m.type |= lpDevMode.dmDisplayOrientation == DMDO_90 || lpDevMode.dmDisplayOrientation == DMDO_270? MODE_ROTATED : MODE_OK; + + for (auto &mode : video_modes) if (mode.width == m.width && mode.height == m.height && mode.refresh == m.refresh && m.interlace == mode.interlace) goto found; + + if (m.width == desktop_mode.width && m.height == desktop_mode.height && m.refresh == desktop_mode.refresh && m.interlace == desktop_mode.interlace) + { + m.type |= MODE_DESKTOP; + if (m.type & MODE_ROTATED) set_desktop_is_rotated(true); + if (current_mode() == nullptr) + set_current_mode(&m); + } + + log_verbose("Switchres: [%3d] %4dx%4d @%3d%s%s %s: ", k, m.width, m.height, m.refresh, m.interlace?"i":"p", m.type & MODE_DESKTOP?"*":"", m.type & MODE_ROTATED?"rot":""); + + if (video() && video()->get_timing(&m)) + { + j++; + log_mode(&m); + } + else + { + m.type |= CUSTOM_VIDEO_TIMING_SYSTEM; + log_verbose("system mode\n"); + } + + // Save our desktop mode now that we queried detailed timings + if (m.type & MODE_DESKTOP) desktop_mode = m; + + video_modes.push_back(m); + backup_modes.push_back(m); + k++; + } + found: + iModeNum++; + } + k--; + log_verbose("Switchres: Found %d custom of %d active video modes\n", j, k); + return k; +} + diff --git a/3rdparty/switchres/display_windows.h b/3rdparty/switchres/display_windows.h new file mode 100644 index 0000000000000..9c300aca61600 --- /dev/null +++ b/3rdparty/switchres/display_windows.h @@ -0,0 +1,45 @@ +/************************************************************** + + display_windows.h - Display manager for Windows + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include "display.h" + +//============================================================ +// PARAMETERS +//============================================================ + +// display modes +#define DM_INTERLACED 0x00000002 +#define DISPLAY_MAX 16 + + +class windows_display : public display_manager +{ + public: + windows_display(display_settings *ds); + ~windows_display(); + bool init(); + bool set_mode(modeline *mode); + + private: + bool get_desktop_mode(); + bool set_desktop_mode(modeline *mode, int flags); + bool restore_desktop_mode(); + int get_available_video_modes(); + + char m_device_name[32]; + char m_device_id[128]; + char m_device_key[128]; + DEVMODEA m_devmode; +}; diff --git a/3rdparty/switchres/edid.cpp b/3rdparty/switchres/edid.cpp new file mode 100644 index 0000000000000..57094fcdf7d75 --- /dev/null +++ b/3rdparty/switchres/edid.cpp @@ -0,0 +1,244 @@ +/************************************************************** + + edid.c - Basic EDID generation + (based on edid.S: EDID data template by Carsten Emde) + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include "switchres.h" +#include "edid.h" + +//============================================================ +// edid_from_modeline +//============================================================ + +int edid_from_modeline(modeline *mode, monitor_range *range, char *name, edid_block *edid) +{ + if (!edid) return 0; + + // header + edid->b[0] = 0x00; + edid->b[1] = 0xff; + edid->b[2] = 0xff; + edid->b[3] = 0xff; + edid->b[4] = 0xff; + edid->b[5] = 0xff; + edid->b[6] = 0xff; + edid->b[7] = 0x00; + + // Manufacturer ID = "SWR" + edid->b[8] = 0x4e; + edid->b[9] = 0xf2; + + // Manufacturer product code + edid->b[10] = 0x00; + edid->b[11] = 0x00; + + // Serial number + edid->b[12] = 0x00; + edid->b[13] = 0x00; + edid->b[14] = 0x00; + edid->b[15] = 0x00; + + // Week of manufacture + edid->b[16] = 5; + + // Year of manufacture + edid->b[17] = 2021 - 1990; + + // EDID version and revision + edid->b[18] = 1; + edid->b[19] = 3; + + // video params + edid->b[20] = 0x6d; + + // Maximum H & V size in cm + edid->b[21] = 48; + edid->b[22] = 36; + + // Gamma + edid->b[23] = 120; + + // Display features + edid->b[24] = 0x0A; + + // Chromacity coordinates; + edid->b[25] = 0x5e; + edid->b[26] = 0xc0; + edid->b[27] = 0xa4; + edid->b[28] = 0x59; + edid->b[29] = 0x4a; + edid->b[30] = 0x98; + edid->b[31] = 0x25; + edid->b[32] = 0x20; + edid->b[33] = 0x50; + edid->b[34] = 0x54; + + // Established timings + edid->b[35] = 0x00; + edid->b[36] = 0x00; + edid->b[37] = 0x00; + + // Standard timing information + edid->b[38] = 0x01; + edid->b[39] = 0x01; + edid->b[40] = 0x01; + edid->b[41] = 0x01; + edid->b[42] = 0x01; + edid->b[43] = 0x01; + edid->b[44] = 0x01; + edid->b[45] = 0x01; + edid->b[46] = 0x01; + edid->b[47] = 0x01; + edid->b[48] = 0x01; + edid->b[49] = 0x01; + edid->b[50] = 0x01; + edid->b[51] = 0x01; + edid->b[52] = 0x01; + edid->b[53] = 0x01; + + // Pixel clock in 10 kHz units. (0.-655.35 MHz, little-endian) + edid->b[54] = (mode->pclock / 10000) & 0xff; + edid->b[55] = (mode->pclock / 10000) >> 8; + + int h_active = mode->hactive; + int h_blank = mode->htotal - mode->hactive; + int h_offset = mode->hbegin - mode->hactive; + int h_pulse = mode->hend - mode->hbegin; + + int v_active = mode->vactive; + int v_blank = (int)mode->vtotal - mode->vactive; + int v_offset = mode->vbegin - mode->vactive; + int v_pulse = mode->vend - mode->vbegin; + + // Horizontal active pixels 8 lsbits (0-4095) + edid->b[56] = h_active & 0xff; + + // Horizontal blanking pixels 8 lsbits (0-4095) + edid->b[57] = h_blank & 0xff; + + // Bits 7-4 Horizontal active pixels 4 msbits + // Bits 3-0 Horizontal blanking pixels 4 msbits + edid->b[58] = (((h_active >> 8) & 0x0f) << 4) + ((h_blank >> 8) & 0x0f); + + // Vertical active lines 8 lsbits (0-4095) + edid->b[59] = v_active & 0xff; + + // Vertical blanking lines 8 lsbits (0-4095) + edid->b[60] = v_blank & 0xff; + + // Bits 7-4 Vertical active lines 4 msbits + // Bits 3-0 Vertical blanking lines 4 msbits + edid->b[61] = (((v_active >> 8) & 0x0f) << 4) + ((v_blank >> 8) & 0x0f); + + // Horizontal sync offset pixels 8 lsbits (0-1023) From blanking start + edid->b[62] = h_offset & 0xff; + + // Horizontal sync pulse width pixels 8 lsbits (0-1023) + edid->b[63] = h_pulse & 0xff; + + // Bits 7-4 Vertical sync offset lines 4 lsbits 0-63) + // Bits 3-0 Vertical sync pulse width lines 4 lsbits 0-63) + edid->b[64] = ((v_offset & 0x0f) << 4) + (v_pulse & 0x0f); + + // Bits 7-6 Horizontal sync offset pixels 2 msbits + // Bits 5-4 Horizontal sync pulse width pixels 2 msbits + // Bits 3-2 Vertical sync offset lines 2 msbits + // Bits 1-0 Vertical sync pulse width lines 2 msbits + edid->b[65] = (((h_offset >> 8) & 0x03) << 6) + + (((h_pulse >> 8) & 0x03) << 4) + + (((v_offset >> 8) & 0x03) << 2) + + ((v_pulse >> 8) & 0x03); + + // Horizontal display size, mm, 8 lsbits (0-4095 mm, 161 in) + edid->b[66] = 485 & 0xff; + + // Vertical display size, mm, 8 lsbits (0-4095 mm, 161 in) + edid->b[67] = 364 & 0xff; + + // Bits 7-4 Horizontal display size, mm, 4 msbits + // Bits 3-0 Vertical display size, mm, 4 msbits + edid->b[68] = (((485 >> 8) & 0x0f) << 4) + ((364 >> 8) & 0x0f); + + // Horizontal border pixels (each side; total is twice this) + edid->b[69] = 0; + + // Vertical border lines (each side; total is twice this) + edid->b[70] = 0; + + // Features bitmap + edid->b[71] = ((mode->interlace & 0x01) << 7) + 0x18 + (mode->vsync << 2) + (mode->hsync << 2); + + + // Descriptor: monitor serial number + edid->b[72] = 0; + edid->b[73] = 0; + edid->b[74] = 0; + edid->b[75] = 0xff; + edid->b[76] = 0; + edid->b[77] = 'S'; + edid->b[78] = 'w'; + edid->b[79] = 'i'; + edid->b[80] = 't'; + edid->b[81] = 'c'; + edid->b[82] = 'h'; + edid->b[83] = 'r'; + edid->b[84] = 'e'; + edid->b[85] = 's'; + edid->b[86] = '2'; + edid->b[87] = '0'; + edid->b[88] = '0'; + edid->b[89] = 0x0a; + + // Descriptor: monitor range limits + edid->b[90] = 0; + edid->b[91] = 0; + edid->b[92] = 0; + edid->b[93] = 0xfd; + edid->b[94] = 0; + edid->b[95] = ((int)range->vfreq_min) & 0xff; + edid->b[96] = ((int)range->vfreq_max) & 0xff; + edid->b[97] = ((int)range->hfreq_min / 1000) & 0xff; + edid->b[98] = ((int)range->hfreq_max / 1000) & 0xff; + edid->b[99] = 0xff; + edid->b[100] = 0; + edid->b[101] = 0x0a; + edid->b[102] = 0x20; + edid->b[103] = 0x20; + edid->b[104] = 0x20; + edid->b[105] = 0x20; + edid->b[106] = 0x20; + edid->b[107] = 0x20; + + // Descriptor: text + edid->b[108] = 0; + edid->b[109] = 0; + edid->b[110] = 0; + edid->b[111] = 0xfc; + edid->b[112] = 0; + snprintf(&edid->b[113], 13, "%s", name); + edid->b[125] = 0x0a; + + // Extensions to follow + edid->b[126] = 0; + + // Compute checksum + char checksum = 0; + int i; + for (i = 0; i <= 126; i++) + checksum += edid->b[i]; + edid->b[127] = 256 - checksum; + + return 1; +} diff --git a/3rdparty/switchres/edid.h b/3rdparty/switchres/edid.h new file mode 100644 index 0000000000000..e5723bd006342 --- /dev/null +++ b/3rdparty/switchres/edid.h @@ -0,0 +1,37 @@ +/************************************************************** + + edid.h - Basic EDID generation + (based on edid.S: EDID data template by Carsten Emde) + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __EDID_H__ +#define __EDID_H__ + +//============================================================ +// TYPE DEFINITIONS +//============================================================ + +typedef struct edid_block +{ + char b[128]; +/* char ext1[128]; + char ext2[128]; + char ext3[128];*/ +} edid_block; + +//============================================================ +// PROTOTYPES +//============================================================ + +int edid_from_modeline(modeline *mode, monitor_range *range, char *name, edid_block *edid); + +#endif diff --git a/3rdparty/switchres/grid.cpp b/3rdparty/switchres/grid.cpp new file mode 100644 index 0000000000000..670c59e3ed06a --- /dev/null +++ b/3rdparty/switchres/grid.cpp @@ -0,0 +1,228 @@ +/************************************************************** + + grid.cpp - Simple test grid + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#define SDL_MAIN_HANDLED +#define NUM_GRIDS 2 + +#include + +typedef struct grid_display +{ + int index; + int width; + int height; + + SDL_Window *window; + SDL_Renderer *renderer; +} GRID_DISPLAY; + +//============================================================ +// draw_grid +//============================================================ + +void draw_grid(int num_grid, int width, int height, SDL_Renderer *renderer) +{ + // Clean the surface + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + + SDL_Rect rect {0, 0, width, height}; + + switch (num_grid) + { + case 0: + // 16 x 12 squares + { + // Fill the screen with red + rect = {0, 0, width, height}; + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + SDL_RenderFillRect(renderer, &rect); + + // Draw white rectangle + rect = {width / 32, height / 24 , width - width / 16, height - height / 12}; + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderFillRect(renderer, &rect); + + // Draw grid using black rectangles + SDL_Rect rects[16 * 12]; + + // Set the thickness of horizontal and vertical lines based on the screen resolution + int line_w = round(float(width) / 320.0); + int line_h = round(float(height) / 240.0); + if ( line_w < 1 ) line_w = 1; + if ( line_h < 1 ) line_h = 1; + + float rect_w = (width - line_w * 17) / 16.0; + float rect_h = (height - line_h * 13) / 12.0; + + for (int i = 0; i < 16; i++) + { + int x_pos1 = ceil(i * rect_w); + int x_pos2 = ceil((i+1) * rect_w); + for (int j = 0; j < 12; j++) + { + int y_pos1 = ceil(j * rect_h); + int y_pos2 = ceil((j+1) * rect_h); + rects[i + j * 16] = {x_pos1 + (i+1) * line_w , y_pos1 + (j+1) * line_h, x_pos2 - x_pos1, y_pos2 - y_pos1}; + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderFillRects(renderer, rects, 16 * 12); + } + break; + + case 1: + // cps2 grid + + // Draw outer rectangle + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderDrawRect(renderer, &rect); + + for (int i = 0; i < width / 16; i++) + { + for (int j = 0; j < height / 16; j++) + { + if (i == 0 || j == 0 || i == (width / 16) - 1 || j == (height / 16) - 1) + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + else + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + + rect = {i * 16, j * 16, 16, 16}; + SDL_RenderDrawRect(renderer, &rect); + + rect = {i * 16 + 7, j * 16 + 7, 2, 2}; + SDL_RenderDrawRect(renderer, &rect); + } + } + break; + } + + SDL_RenderPresent(renderer); +} + +//============================================================ +// main +//============================================================ + +int main(int argc, char **argv) +{ + SDL_Window* win_array[10] = {}; + GRID_DISPLAY display_array[10] = {}; + int display_total = 0; + + // Initialize SDL + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + printf("error initializing SDL: %s\n", SDL_GetError()); + return 1; + } + + // Get target displays + if (argc > 1) + { + // Parse command line for display indexes + int display_index = 0; + int num_displays = SDL_GetNumVideoDisplays(); + + for (int arg = 1; arg < argc; arg++) + { + sscanf(argv[arg], "%d", &display_index); + + if (display_index < 0 || display_index > num_displays - 1) + { + printf("error, bad display_index: %d\n", display_index); + return 1; + } + + display_array[display_total].index = display_index; + display_total++; + } + } + else + { + // No display specified, use default + display_array[0].index = 0; + display_total = 1; + } + + // Create windows + for (int disp = 0; disp < display_total; disp++) + { + // Get target display size + SDL_DisplayMode dm; + SDL_GetCurrentDisplayMode(display_array[disp].index, &dm); + + SDL_ShowCursor(SDL_DISABLE); + + display_array[disp].width = dm.w; + display_array[disp].height = dm.h; + + // Create window + display_array[disp].window = SDL_CreateWindow("Switchres test grid", SDL_WINDOWPOS_CENTERED_DISPLAY(display_array[disp].index), SDL_WINDOWPOS_CENTERED, dm.w, dm.h, SDL_WINDOW_FULLSCREEN_DESKTOP); + + // Required by Window multi-monitor + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); + + // Create renderer + display_array[disp].renderer = SDL_CreateRenderer(display_array[disp].window, -1, SDL_RENDERER_ACCELERATED); + + // Draw grid + draw_grid(0, display_array[disp].width, display_array[disp].height, display_array[disp].renderer); + } + + // Wait for escape key + bool close = false; + int num_grid = 0; + + while (!close) + { + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + close = true; + break; + + case SDL_KEYDOWN: + switch (event.key.keysym.scancode) + { + case SDL_SCANCODE_ESCAPE: + close = true; + break; + + case SDL_SCANCODE_TAB: + num_grid ++; + for (int disp = 0; disp < display_total; disp++) + draw_grid(num_grid % NUM_GRIDS, display_array[disp].width, display_array[disp].height, display_array[disp].renderer); + break; + + default: + break; + } + } + } + } + + // Destroy all windows + for (int disp = 0; disp < display_total; disp++) + SDL_DestroyWindow(display_array[disp].window); + + SDL_Quit(); + + return 0; +} diff --git a/3rdparty/switchres/log.cpp b/3rdparty/switchres/log.cpp new file mode 100644 index 0000000000000..8fcbe5e90882d --- /dev/null +++ b/3rdparty/switchres/log.cpp @@ -0,0 +1,36 @@ +/************************************************************** + + log.cpp - Simple logging for Switchres + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include "log.h" + +void log_dummy(const char *, ...) {} + +LOG_VERBOSE log_verbose = &log_dummy; +LOG_INFO log_info = &log_dummy; +LOG_ERROR log_error = &log_dummy; + +void set_log_verbose(void *func_ptr) +{ + log_verbose = (LOG_VERBOSE)func_ptr; +} + +void set_log_info(void *func_ptr) +{ + log_info = (LOG_INFO)func_ptr; +} + +void set_log_error(void *func_ptr) +{ + log_error = (LOG_ERROR)func_ptr; +} diff --git a/3rdparty/switchres/log.h b/3rdparty/switchres/log.h new file mode 100644 index 0000000000000..d7196f0547c73 --- /dev/null +++ b/3rdparty/switchres/log.h @@ -0,0 +1,37 @@ +/************************************************************** + + log.h - Simple logging for Switchres + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __LOG__ +#define __LOG__ + +#if defined(__GNUC__) +#define ATTR_PRINTF(x,y) __attribute__((format(printf, x, y))) +#else +#define ATTR_PRINTF(x,y) +#endif + +typedef void (*LOG_VERBOSE)(const char *format, ...) ATTR_PRINTF(1,2); +extern LOG_VERBOSE log_verbose; + +typedef void (*LOG_INFO)(const char *format, ...) ATTR_PRINTF(1,2); +extern LOG_INFO log_info; + +typedef void (*LOG_ERROR)(const char *format, ...) ATTR_PRINTF(1,2); +extern LOG_ERROR log_error; + +void set_log_verbose(void *func_ptr); +void set_log_info(void *func_ptr); +void set_log_error(void *func_ptr); + +#endif diff --git a/3rdparty/switchres/makefile b/3rdparty/switchres/makefile new file mode 100644 index 0000000000000..4fc69cc81ecbb --- /dev/null +++ b/3rdparty/switchres/makefile @@ -0,0 +1,77 @@ +PLATFORM := $(shell uname) + +MAIN = switchres_main +STANDALONE = switchres +TARGET_LIB = libswitchres +GRID = grid +SRC = monitor.cpp modeline.cpp switchres.cpp display.cpp custom_video.cpp log.cpp switchres_wrapper.cpp edid.cpp +OBJS = $(SRC:.cpp=.o) + +CROSS_COMPILE ?= +CXX ?= g++ +AR ?= ar +LDFLAGS = -shared +FINAL_CXX=$(CROSS_COMPILE)$(CXX) +FINAL_AR=$(CROSS_COMPILE)$(AR) +CPPFLAGS = -O3 -Wall -Wextra + +PKG_CONFIG=pkg-config +INSTALL=install +SED=sed + +DESTDIR ?= +PREFIX ?= /usr +INCDIR = $(DESTDIR)$(PREFIX)/include +LIBDIR = $(DESTDIR)$(PREFIX)/lib +BINDIR = $(DESTDIR)$(PREFIX)/bin +PKGDIR = $(LIBDIR)/pkgconfig + +# Linux +ifeq ($(PLATFORM),Linux) +EXTRA_LIBS = libdrm +CPPFLAGS += $(shell $(PKG_CONFIG) --cflags $(EXTRA_LIBS)) +SRC += display_linux.cpp custom_video_xrandr.cpp custom_video_drmkms.cpp +CPPFLAGS += -fPIC +LIBS = -ldl +REMOVE = rm -f +STATIC_LIB_EXT = a +DYNAMIC_LIB_EXT = so + +# Windows +else ifneq (,$(findstring NT,$(PLATFORM))) +SRC += display_windows.cpp custom_video_ati_family.cpp custom_video_ati.cpp custom_video_adl.cpp custom_video_pstrip.cpp resync_windows.cpp +CPPFLAGS += -static -static-libgcc -static-libstdc++ +LIBS = +REMOVE = del /f +STATIC_LIB_EXT = lib +DYNAMIC_LIB_EXT = dll +endif + +%.o : %.cpp + $(FINAL_CXX) -c $(CPPFLAGS) $< -o $@ + +all: $(SRC:.cpp=.o) $(MAIN).cpp $(TARGET_LIB) + @echo $(OSFLAG) + $(FINAL_CXX) $(CPPFLAGS) $(CXXFLAGS) $(SRC:.cpp=.o) $(MAIN).cpp $(LIBS) -o $(STANDALONE) + +$(TARGET_LIB): $(OBJS) + $(FINAL_CXX) $(LDFLAGS) $(CPPFLAGS) -o $@.$(DYNAMIC_LIB_EXT) $^ + $(FINAL_AR) rcs $@.$(STATIC_LIB_EXT) $(^) + +$(GRID): + $(FINAL_CXX) grid.cpp -lSDL2 -o grid + +clean: + $(REMOVE) $(OBJS) $(STANDALONE) $(TARGET_LIB).* + +prepare_pkg_config: + $(SED) -e "s+@prefix@+$(PREFIX)+g" \ + -e"s+@libdir@+$(LIBDIR)+g" \ + -e"s+@includedir@+$(INCDIR)+g" \ + switchres.pc.in > switchres.pc + +install: prepare_pkg_config + $(INSTALL) -Dm644 $(TARGET_LIB).$(DYNAMIC_LIB_EXT) $(LIBDIR)/$(TARGET_LIB).$(DYNAMIC_LIB_EXT) + $(INSTALL) -Dm644 switchres_wrapper.h $(INCDIR)/switchres/switchres_wrapper.h + $(INSTALL) -Dm644 switchres.h $(INCDIR)/switchres/switchres.h + $(INSTALL) -Dm644 switchres.pc $(PKGDIR)/switchres.pc diff --git a/3rdparty/switchres/modeline.cpp b/3rdparty/switchres/modeline.cpp new file mode 100644 index 0000000000000..49cf4df100e61 --- /dev/null +++ b/3rdparty/switchres/modeline.cpp @@ -0,0 +1,763 @@ +/************************************************************** + + modeline.cpp - Modeline generation and scoring routines + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include +#include "modeline.h" +#include "log.h" + +#define max(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a > _b ? _a : _b; }) +#define min(a,b)({ __typeof__ (a) _a = (a);__typeof__ (b) _b = (b);_a < _b ? _a : _b; }) + + +//============================================================ +// PROTOTYPES +//============================================================ + +int get_line_params(modeline *mode, monitor_range *range, int char_size); +int scale_into_range (int value, int lower_limit, int higher_limit); +int scale_into_range (double value, double lower_limit, double higher_limit); +int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff); +int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace); +int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace); +double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace); + +//============================================================ +// modeline_create +//============================================================ + +int modeline_create(modeline *s_mode, modeline *t_mode, monitor_range *range, generator_settings *cs) +{ + double vfreq_real = 0; + double interlace = 1; + double doublescan = 1; + double scan_factor = 1; + int x_scale = 0; + int y_scale = 0; + int v_scale = 0; + double x_diff = 0; + double y_diff = 0; + double v_diff = 0; + double y_ratio = 0; + double x_ratio = 0; + double borders = 0; + t_mode->result.weight = 0; + + // ≈≈≈ Vertical refresh ≈≈≈ + // try to fit vertical frequency into current range + v_scale = scale_into_range(t_mode->vfreq, range->vfreq_min, range->vfreq_max); + + if (!v_scale && (t_mode->type & V_FREQ_EDITABLE)) + { + t_mode->vfreq = t_mode->vfreq < range->vfreq_min? range->vfreq_min : range->vfreq_max; + v_scale = 1; + } + else if (v_scale != 1 && !(t_mode->type & V_FREQ_EDITABLE)) + { + t_mode->result.weight |= R_OUT_OF_RANGE; + return -1; + } + + // ≈≈≈ Vertical resolution ≈≈≈ + // try to fit active lines in the progressive range first + if (range->progressive_lines_min && (!t_mode->interlace || (t_mode->type & SCAN_EDITABLE))) + y_scale = scale_into_range(t_mode->vactive, range->progressive_lines_min, range->progressive_lines_max); + + // if not possible, try to fit in the interlaced range, if any + if (!y_scale && range->interlaced_lines_min && cs->interlace && (t_mode->interlace || (t_mode->type & SCAN_EDITABLE))) + { + y_scale = scale_into_range(t_mode->vactive, range->interlaced_lines_min, range->interlaced_lines_max); + interlace = 2; + } + + // if we succeeded, let's see if we can apply integer scaling + if (y_scale == 1 || (y_scale > 1 && (t_mode->type & Y_RES_EDITABLE))) + { + // check if we should apply doublescan + if (cs->doublescan && y_scale % 2 == 0) + { + y_scale /= 2; + doublescan = 0.5; + } + scan_factor = interlace * doublescan; + + // Calculate top border in case of multi-standard consumer TVs + if (cs->v_shift_correct) + borders = (range->progressive_lines_max - t_mode->vactive * y_scale / interlace) * (1.0 / range->hfreq_min) / 2; + + // calculate expected achievable refresh for this height + vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive * y_scale, range, borders, scan_factor)); + if (vfreq_real != t_mode->vfreq * v_scale && !(t_mode->type & V_FREQ_EDITABLE)) + { + t_mode->result.weight |= R_OUT_OF_RANGE; + return -1; + } + + // calculate the ratio that our scaled yres represents with respect to the original height + y_ratio = double(t_mode->vactive) * y_scale / s_mode->vactive; + int y_source_scaled = s_mode->vactive * floor(y_ratio); + + // if our original height doesn't fit the target height, we're forced to stretch + if (!y_source_scaled) + t_mode->result.weight |= R_RES_STRETCH; + + // otherwise we try to perform integer scaling + else + { + // exclude lcd ranges from raw border computation + if (t_mode->type & V_FREQ_EDITABLE && range->progressive_lines_max - range->progressive_lines_min > 0) + { + // calculate y borders considering physical lines (instead of logical resolution) + int tot_yres = total_lines_for_yres(t_mode->vactive * y_scale, vfreq_real, range, borders, scan_factor); + int tot_source = total_lines_for_yres(y_source_scaled, t_mode->vfreq * v_scale, range, borders, scan_factor); + y_diff = tot_yres > tot_source?double(tot_yres % tot_source) / tot_yres * 100:0; + + // we penalize for the logical lines we need to add in order to meet the user's lower active lines limit + int y_min = interlace == 2?range->interlaced_lines_min:range->progressive_lines_min; + int tot_rest = (y_min >= y_source_scaled / doublescan)? y_min % int(y_source_scaled / doublescan):0; + y_diff += double(tot_rest) / tot_yres * 100; + } + else + y_diff = double((t_mode->vactive * y_scale) % y_source_scaled) / (t_mode->vactive * y_scale) * 100; + + // we save the integer ratio between source and target resolutions, this will be used for prescaling + y_scale = floor(y_ratio); + + // now if the borders obtained are low enough (< 10%) we'll finally apply integer scaling + // otherwise we'll stretch the original resolution over the target one + if (!(y_ratio >= 1.0 && y_ratio < 16.0 && y_diff < 10.0)) + t_mode->result.weight |= R_RES_STRETCH; + } + } + + // otherwise, check if we're allowed to apply fractional scaling + else if (t_mode->type & Y_RES_EDITABLE) + t_mode->result.weight |= R_RES_STRETCH; + + // if there's nothing we can do, we're out of range + else + { + t_mode->result.weight |= R_OUT_OF_RANGE; + return -1; + } + + // ≈≈≈ Horizontal resolution ≈≈≈ + // make the best possible adjustment of xres depending on what happened in the previous steps + // let's start with the SCALED case + if (!(t_mode->result.weight & R_RES_STRETCH)) + { + // apply integer scaling to yres + if (t_mode->type & Y_RES_EDITABLE) t_mode->vactive *= y_scale; + + // if we can, let's apply the same scaling to both directions + if (t_mode->type & X_RES_EDITABLE) + { + x_scale = y_scale; + double aspect_corrector = max(1.0f, cs->monitor_aspect / (cs->rotation? (1.0/(STANDARD_CRT_ASPECT)) : (STANDARD_CRT_ASPECT))); + t_mode->hactive = normalize(double(t_mode->hactive) * double(x_scale) * aspect_corrector, 8); + } + + // otherwise, try to get the best out of our current xres + else + { + x_scale = t_mode->hactive / s_mode->hactive; + // if the source width fits our xres, try applying integer scaling + if (x_scale) + { + x_scale = scale_into_aspect(s_mode->hactive, t_mode->hactive, cs->rotation?1.0/(STANDARD_CRT_ASPECT):STANDARD_CRT_ASPECT, cs->monitor_aspect, &x_diff); + if (x_diff > 15.0 && t_mode->width < cs->super_width) + t_mode->result.weight |= R_RES_STRETCH; + } + // otherwise apply fractional scaling + else + t_mode->result.weight |= R_RES_STRETCH; + } + } + + // if the result was fractional scaling in any of the previous steps, deal with it + if (t_mode->result.weight & R_RES_STRETCH) + { + if (t_mode->type & Y_RES_EDITABLE) + { + // always try to use the interlaced range first if it exists, for better resolution + t_mode->vactive = stretch_into_range(t_mode->vfreq * v_scale, range, borders, cs->interlace, &interlace); + + // check in case we couldn't achieve the desired refresh + vfreq_real = min(t_mode->vfreq * v_scale, max_vfreq_for_yres(t_mode->vactive, range, borders, interlace)); + } + + // check if we can create a normal aspect resolution + if (t_mode->type & X_RES_EDITABLE) + t_mode->hactive = max(t_mode->hactive, normalize(STANDARD_CRT_ASPECT * t_mode->vactive, 8)); + + // calculate integer scale for prescaling + x_scale = max(1, scale_into_aspect(s_mode->hactive, t_mode->hactive, cs->rotation?1.0/(STANDARD_CRT_ASPECT):STANDARD_CRT_ASPECT, cs->monitor_aspect, &x_diff)); + y_scale = max(1, floor(double(t_mode->vactive) / s_mode->vactive)); + + scan_factor = interlace; + doublescan = 1; + } + + x_ratio = double(t_mode->hactive) / s_mode->hactive; + y_ratio = double(t_mode->vactive) / s_mode->vactive; + v_scale = max(round_near(vfreq_real / s_mode->vfreq), 1); + v_diff = (vfreq_real / v_scale) - s_mode->vfreq; + if (fabs(v_diff) > cs->refresh_tolerance) + t_mode->result.weight |= R_V_FREQ_OFF; + + // ≈≈≈ Modeline generation ≈≈≈ + // compute new modeline if we are allowed to + if (t_mode->type & V_FREQ_EDITABLE) + { + double margin = 0; + double vblank_lines = 0; + double vvt_ini = 0; + + // Get resulting refresh + t_mode->vfreq = vfreq_real; + + // Get total vertical lines + vvt_ini = total_lines_for_yres(t_mode->vactive, t_mode->vfreq, range, borders, scan_factor) + (interlace == 2?0.5:0); + + // Calculate horizontal frequency + t_mode->hfreq = t_mode->vfreq * vvt_ini; + + horizontal_values: + + // Fill horizontal part of modeline + get_line_params(t_mode, range, cs->pixel_precision? 1 : 8); + + // Calculate pixel clock + t_mode->pclock = t_mode->htotal * t_mode->hfreq; + if (t_mode->pclock <= cs->pclock_min) + { + if (t_mode->type & X_RES_EDITABLE) + { + x_scale *= 2; + t_mode->hactive *= 2; + goto horizontal_values; + } + else + { + t_mode->result.weight |= R_OUT_OF_RANGE; + return -1; + } + } + + // Vertical blanking + t_mode->vtotal = vvt_ini * scan_factor; + vblank_lines = int(t_mode->hfreq * (range->vertical_blank + borders)) + (interlace == 2?0.5:0); + margin = (t_mode->vtotal - t_mode->vactive - vblank_lines * scan_factor) / (cs->v_shift_correct? 1 : 2); + + t_mode->vbegin = t_mode->vactive + max(round_near(t_mode->hfreq * range->vfront_porch * scan_factor + margin), 1); + t_mode->vend = t_mode->vbegin + max(round_near(t_mode->hfreq * range->vsync_pulse * scan_factor), 1); + + // Recalculate final vfreq + t_mode->vfreq = (t_mode->hfreq / t_mode->vtotal) * scan_factor; + + t_mode->hsync = range->hsync_polarity; + t_mode->vsync = range->vsync_polarity; + t_mode->interlace = interlace == 2?1:0; + t_mode->doublescan = doublescan == 1?0:1; + } + + // finally, store result + t_mode->result.scan_penalty = (s_mode->interlace != t_mode->interlace? 1:0) + (s_mode->doublescan != t_mode->doublescan? 1:0); + t_mode->result.x_scale = x_scale; + t_mode->result.y_scale = y_scale; + t_mode->result.v_scale = v_scale; + t_mode->result.x_diff = x_diff; + t_mode->result.y_diff = y_diff; + t_mode->result.v_diff = v_diff; + t_mode->result.x_ratio = x_ratio; + t_mode->result.y_ratio = y_ratio; + t_mode->result.v_ratio = 0; + + return 0; +} + +//============================================================ +// get_line_params +//============================================================ + +int get_line_params(modeline *mode, monitor_range *range, int char_size) +{ + int hhi, hhf, hht; + int hh, hs, he, ht; + double line_time, char_time, new_char_time; + double hfront_porch_min, hsync_pulse_min, hback_porch_min; + + hfront_porch_min = range->hfront_porch * .90; + hsync_pulse_min = range->hsync_pulse * .90; + hback_porch_min = range->hback_porch * .90; + + line_time = 1 / mode->hfreq * 1000000; + + hh = round(mode->hactive / char_size); + hs = he = ht = 1; + + do { + char_time = line_time / (hh + hs + he + ht); + if (hs * char_time < hfront_porch_min || + fabs((hs + 1) * char_time - range->hfront_porch) < fabs(hs * char_time - range->hfront_porch)) + hs++; + + if (he * char_time < hsync_pulse_min || + fabs((he + 1) * char_time - range->hsync_pulse) < fabs(he * char_time - range->hsync_pulse)) + he++; + + if (ht * char_time < hback_porch_min || + fabs((ht + 1) * char_time - range->hback_porch) < fabs(ht * char_time - range->hback_porch)) + ht++; + + new_char_time = line_time / (hh + hs + he + ht); + } while (new_char_time != char_time); + + hhi = (hh + hs) * char_size; + hhf = (hh + hs + he) * char_size; + hht = (hh + hs + he + ht) * char_size; + + mode->hbegin = hhi; + mode->hend = hhf; + mode->htotal = hht; + + return 0; +} + +//============================================================ +// scale_into_range +//============================================================ + +int scale_into_range (int value, int lower_limit, int higher_limit) +{ + int scale = 1; + while (value * scale < lower_limit) scale ++; + if (value * scale <= higher_limit) + return scale; + else + return 0; +} + +//============================================================ +// scale_into_range +//============================================================ + +int scale_into_range (double value, double lower_limit, double higher_limit) +{ + int scale = 1; + while (value * scale < lower_limit) scale ++; + if (value * scale <= higher_limit) + return scale; + else + return 0; +} + +//============================================================ +// scale_into_aspect +//============================================================ + +int scale_into_aspect (int source_res, int tot_res, double original_monitor_aspect, double users_monitor_aspect, double *best_diff) +{ + int scale = 1, best_scale = 1; + double diff = 0; + *best_diff = 0; + + while (source_res * scale <= tot_res) + { + diff = fabs(1.0 - (users_monitor_aspect / (double(tot_res) / double(source_res * scale) * original_monitor_aspect))) * 100.0; + if (diff < *best_diff || *best_diff == 0) + { + *best_diff = diff; + best_scale = scale; + } + scale ++; + } + return best_scale; +} + +//============================================================ +// stretch_into_range +//============================================================ + +int stretch_into_range(double vfreq, monitor_range *range, double borders, bool interlace_allowed, double *interlace) +{ + int yres, lower_limit; + + if (range->interlaced_lines_min && interlace_allowed) + { + yres = range->interlaced_lines_max; + lower_limit = range->interlaced_lines_min; + *interlace = 2; + } + else + { + yres = range->progressive_lines_max; + lower_limit = range->progressive_lines_min; + } + + while (yres > lower_limit && max_vfreq_for_yres(yres, range, borders, *interlace) < vfreq) + yres -= 8; + + return yres; +} + + +//============================================================ +// total_lines_for_yres +//============================================================ + +int total_lines_for_yres(int yres, double vfreq, monitor_range *range, double borders, double interlace) +{ + int vvt = max(yres / interlace + round_near(vfreq * yres / (interlace * (1.0 - vfreq * (range->vertical_blank + borders))) * (range->vertical_blank + borders)), 1); + while ((vfreq * vvt < range->hfreq_min) && (vfreq * (vvt + 1) < range->hfreq_max)) vvt++; + return vvt; +} + +//============================================================ +// max_vfreq_for_yres +//============================================================ + +double max_vfreq_for_yres (int yres, monitor_range *range, double borders, double interlace) +{ + return range->hfreq_max / (yres / interlace + round_near(range->hfreq_max * (range->vertical_blank + borders))); +} + +//============================================================ +// modeline_print +//============================================================ + +char * modeline_print(modeline *mode, char *modeline, int flags) +{ + char label[48]={'\x00'}; + char params[192]={'\x00'}; + + if (flags & MS_LABEL) + sprintf(label, "\"%dx%d_%d%s %.6fKHz %.6fHz\"", mode->hactive, mode->vactive, mode->refresh, mode->interlace?"i":"", mode->hfreq/1000, mode->vfreq); + + if (flags & MS_LABEL_SDL) + sprintf(label, "\"%dx%d_%.6f\"", mode->hactive, mode->vactive, mode->vfreq); + + if (flags & MS_PARAMS) + sprintf(params, " %.6f %d %d %d %d %d %d %d %d %s %s %s %s", double(mode->pclock)/1000000.0, mode->hactive, mode->hbegin, mode->hend, mode->htotal, mode->vactive, mode->vbegin, mode->vend, mode->vtotal, + mode->interlace?"interlace":"", mode->doublescan?"doublescan":"", mode->hsync?"+hsync":"-hsync", mode->vsync?"+vsync":"-vsync"); + + sprintf(modeline, "%s%s", label, params); + + return modeline; +} + +//============================================================ +// modeline_result +//============================================================ + +char * modeline_result(modeline *mode, char *result) +{ + log_verbose(" rng(%d): ", mode->range); + + if (mode->result.weight & R_OUT_OF_RANGE) + sprintf(result, " out of range"); + + else + sprintf(result, "%4d x%4d_%3.6f%s%s %3.6f [%s] scale(%d, %d, %d) diff(%.2f, %.2f, %.4f) ratio(%.3f, %.3f)", + mode->hactive, mode->vactive, mode->vfreq, mode->interlace?"i":"p", mode->doublescan?"d":"", mode->hfreq/1000, mode->result.weight & R_RES_STRETCH?"fract":"integ", + mode->result.x_scale, mode->result.y_scale, mode->result.v_scale, mode->result.x_diff, mode->result.y_diff, mode->result.v_diff, mode->result.x_ratio, mode->result.y_ratio); + return result; +} + +//============================================================ +// modeline_compare +//============================================================ + +int modeline_compare(modeline *t, modeline *best) +{ + bool vector = (t->hactive == (int)t->result.x_ratio); + + if (t->result.weight < best->result.weight) + return 1; + + else if (t->result.weight <= best->result.weight) + { + double t_v_diff = fabs(t->result.v_diff); + double b_v_diff = fabs(best->result.v_diff); + + if (t->result.weight & R_RES_STRETCH || vector) + { + double t_y_score = t->result.y_ratio * (t->interlace?(2.0/3.0):1.0); + double b_y_score = best->result.y_ratio * (best->interlace?(2.0/3.0):1.0); + + if ((t_v_diff < b_v_diff) || + ((t_v_diff == b_v_diff) && (t_y_score > b_y_score)) || + ((t_v_diff == b_v_diff) && (t_y_score == b_y_score) && (t->result.x_ratio > best->result.x_ratio))) + return 1; + } + else + { + int t_y_score = t->result.y_scale + t->result.scan_penalty; + int b_y_score = best->result.y_scale + best->result.scan_penalty; + double xy_diff = roundf((t->result.x_diff + t->result.y_diff) * 100) / 100; + double best_xy_diff = roundf((best->result.x_diff + best->result.y_diff) * 100) / 100; + + if ((t_y_score < b_y_score) || + ((t_y_score == b_y_score) && (xy_diff < best_xy_diff)) || + ((t_y_score == b_y_score) && (xy_diff == best_xy_diff) && (t->result.x_scale < best->result.x_scale)) || + ((t_y_score == b_y_score) && (xy_diff == best_xy_diff) && (t->result.x_scale == best->result.x_scale) && (t_v_diff < b_v_diff))) + return 1; + } + } + return 0; +} + +//============================================================ +// modeline_vesa_gtf +// Based on the VESA GTF spreadsheet by Andy Morrish 1/5/97 +//============================================================ + +int modeline_vesa_gtf(modeline *m) +{ + int C, M; + int v_sync_lines, v_porch_lines_min, v_front_porch_lines, v_back_porch_lines, v_sync_v_back_porch_lines, v_total_lines; + int h_sync_width_percent, h_sync_width_pixels, h_blanking_pixels, h_front_porch_pixels, h_total_pixels; + double v_freq, v_freq_est, v_freq_real, v_sync_v_back_porch; + double h_freq, h_period, h_period_real, h_ideal_blanking; + double pixel_freq, interlace; + + // Check if there's a value defined for vfreq. We're assuming input vfreq is the total field vfreq regardless interlace + v_freq = m->vfreq? m->vfreq:double(m->refresh); + + // These values are GTF defined defaults + v_sync_lines = 3; + v_porch_lines_min = 1; + v_front_porch_lines = v_porch_lines_min; + v_sync_v_back_porch = 550; + h_sync_width_percent = 8; + M = 128.0 / 256 * 600; + C = ((40 - 20) * 128.0 / 256) + 20; + + // GTF calculation + interlace = m->interlace?0.5:0; + h_period = ((1.0 / v_freq) - (v_sync_v_back_porch / 1000000)) / ((double)m->height + v_front_porch_lines + interlace) * 1000000; + v_sync_v_back_porch_lines = round_near(v_sync_v_back_porch / h_period); + v_back_porch_lines = v_sync_v_back_porch_lines - v_sync_lines; + v_total_lines = m->height + v_front_porch_lines + v_sync_lines + v_back_porch_lines; + v_freq_est = (1.0 / h_period) / v_total_lines * 1000000; + h_period_real = h_period / (v_freq / v_freq_est); + v_freq_real = (1.0 / h_period_real) / v_total_lines * 1000000; + h_ideal_blanking = double(C - (M * h_period_real / 1000)); + h_blanking_pixels = round_near(m->width * h_ideal_blanking /(100 - h_ideal_blanking) / (2 * 8)) * (2 * 8); + h_total_pixels = m->width + h_blanking_pixels; + pixel_freq = h_total_pixels / h_period_real * 1000000; + h_freq = 1000000 / h_period_real; + h_sync_width_pixels = round_near(h_sync_width_percent * h_total_pixels / 100 / 8) * 8; + h_front_porch_pixels = (h_blanking_pixels / 2) - h_sync_width_pixels; + + // Results + m->hactive = m->width; + m->hbegin = m->hactive + h_front_porch_pixels; + m->hend = m->hbegin + h_sync_width_pixels; + m->htotal = h_total_pixels; + m->vactive = m->height; + m->vbegin = m->vactive + v_front_porch_lines; + m->vend = m->vbegin + v_sync_lines; + m->vtotal = v_total_lines; + m->hfreq = h_freq; + m->vfreq = v_freq_real; + m->pclock = pixel_freq; + m->hsync = 0; + m->vsync = 1; + + return true; +} + +//============================================================ +// modeline_parse +//============================================================ + +int modeline_parse(const char *user_modeline, modeline *mode) +{ + char modeline_txt[256]={'\x00'}; + + if (!strcmp(user_modeline, "auto")) + return false; + + // Remove quotes + char *quote_start, *quote_end; + quote_start = strstr((char*)user_modeline, "\""); + if (quote_start) + { + quote_start++; + quote_end = strstr(quote_start, "\""); + if (!quote_end || *quote_end++ == 0) + return false; + user_modeline = quote_end; + } + + // Get timing flags + mode->interlace = strstr(user_modeline, "interlace")?1:0; + mode->doublescan = strstr(user_modeline, "doublescan")?1:0; + mode->hsync = strstr(user_modeline, "+hsync")?1:0; + mode->vsync = strstr(user_modeline, "+vsync")?1:0; + + // Get timing values + double pclock; + int e = sscanf(user_modeline, " %lf %d %d %d %d %d %d %d %d", + &pclock, + &mode->hactive, &mode->hbegin, &mode->hend, &mode->htotal, + &mode->vactive, &mode->vbegin, &mode->vend, &mode->vtotal); + + if (e != 9) + { + log_error("SwitchRes: missing parameter in user modeline\n %s\n", user_modeline); + memset(mode, 0, sizeof(struct modeline)); + return false; + } + + // Calculate timings + mode->pclock = pclock * 1000000.0; + mode->hfreq = mode->pclock / mode->htotal; + mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace?2:1); + mode->refresh = mode->vfreq; + mode->width = mode->hactive; + mode->height = mode->vactive; + log_verbose("SwitchRes: user modeline %s\n", modeline_print(mode, modeline_txt, MS_FULL)); + + return true; +} + +//============================================================ +// modeline_to_monitor_range +//============================================================ + +int modeline_to_monitor_range(monitor_range *range, modeline *mode) +{ + if (range->vfreq_min == 0) + { + range->vfreq_min = mode->vfreq - 0.2; + range->vfreq_max = mode->vfreq + 0.2; + } + + double line_time = 1 / mode->hfreq; + double pixel_time = line_time / mode->htotal * 1000000; + + range->hfront_porch = pixel_time * (mode->hbegin - mode->hactive); + range->hsync_pulse = pixel_time * (mode->hend - mode->hbegin); + range->hback_porch = pixel_time * (mode->htotal - mode->hend); + + range->vfront_porch = line_time * (mode->vbegin - mode->vactive); + range->vsync_pulse = line_time * (mode->vend - mode->vbegin); + range->vback_porch = line_time * (mode->vtotal - mode->vend); + range->vertical_blank = range->vfront_porch + range->vsync_pulse + range->vback_porch; + + range->hsync_polarity = mode->hsync; + range->vsync_polarity = mode->vsync; + + range->progressive_lines_min = mode->interlace?0:mode->vactive; + range->progressive_lines_max = mode->interlace?0:mode->vactive; + range->interlaced_lines_min = mode->interlace?mode->vactive:0; + range->interlaced_lines_max= mode->interlace?mode->vactive:0; + + range->hfreq_min = range->vfreq_min * mode->vtotal; + range->hfreq_max = range->vfreq_max * mode->vtotal; + + return 1; +} + +//============================================================ +// modeline_is_different +//============================================================ + +int modeline_is_different(modeline *n, modeline *p) +{ + // Remove on last fields in modeline comparison + return memcmp(n, p, offsetof(struct modeline, vfreq)); +} + +//============================================================ +// monitor_fill_vesa_gtf +//============================================================ + +int monitor_fill_vesa_gtf(monitor_range *range, const char *max_lines) +{ + int lines = 0; + sscanf(max_lines, "vesa_%d", &lines); + + if (!lines) + return 0; + + int i = 0; + if (lines >= 480) + i += monitor_fill_vesa_range(&range[i], 384, 480); + if (lines >= 600) + i += monitor_fill_vesa_range(&range[i], 480, 600); + if (lines >= 768) + i += monitor_fill_vesa_range(&range[i], 600, 768); + if (lines >= 1024) + i += monitor_fill_vesa_range(&range[i], 768, 1024); + + return i; +} + +//============================================================ +// monitor_fill_vesa_range +//============================================================ + +int monitor_fill_vesa_range(monitor_range *range, int lines_min, int lines_max) +{ + modeline mode; + memset(&mode, 0, sizeof(modeline)); + + mode.width = real_res(STANDARD_CRT_ASPECT * lines_max); + mode.height = lines_max; + mode.refresh = 60; + range->vfreq_min = 50; + range->vfreq_max = 65; + + modeline_vesa_gtf(&mode); + modeline_to_monitor_range(range, &mode); + + range->progressive_lines_min = lines_min; + range->hfreq_min = mode.hfreq - 500; + range->hfreq_max = mode.hfreq + 500; + monitor_show_range(range); + + return 1; +} + +//============================================================ +// round_near +//============================================================ + +int round_near(double number) +{ + return number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5); +} + +//============================================================ +// normalize +//============================================================ + +int normalize(int a, int b) +{ + int c, d; + c = a % b; + d = a / b; + if (c) d++; + return d * b; +} + +//============================================================ +// real_res +//============================================================ + +int real_res(int x) {return (int) (x / 8) * 8;} diff --git a/3rdparty/switchres/modeline.h b/3rdparty/switchres/modeline.h new file mode 100644 index 0000000000000..e04ab945b0b98 --- /dev/null +++ b/3rdparty/switchres/modeline.h @@ -0,0 +1,139 @@ +/************************************************************** + + modeline.h - Modeline generation header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __MODELINE_H__ +#define __MODELINE_H__ + +#include +#include +#include +#include "monitor.h" + + +//============================================================ +// CONSTANTS +//============================================================ + +// Modeline print flags +#define MS_LABEL 0x00000001 +#define MS_LABEL_SDL 0x00000002 +#define MS_PARAMS 0x00000004 +#define MS_FULL MS_LABEL | MS_PARAMS + +// Modeline result +#define R_V_FREQ_OFF 0x00000001 +#define R_RES_STRETCH 0x00000002 +#define R_OUT_OF_RANGE 0x00000004 + +// Mode types +#define MODE_OK 0x00000000 +#define MODE_DESKTOP 0x01000000 +#define MODE_ROTATED 0x02000000 +#define MODE_DISABLED 0x04000000 +#define MODE_USER_DEF 0x08000000 +#define MODE_UPDATE 0x10000000 +#define MODE_ADD 0x20000000 +#define MODE_DELETE 0x40000000 +#define MODE_ERROR 0x80000000 +#define V_FREQ_EDITABLE 0x00000001 +#define X_RES_EDITABLE 0x00000002 +#define Y_RES_EDITABLE 0x00000004 +#define SCAN_EDITABLE 0x00000008 +#define XYV_EDITABLE (X_RES_EDITABLE | Y_RES_EDITABLE | V_FREQ_EDITABLE ) + +#define DUMMY_WIDTH 1234 +#define MAX_MODELINES 256 + +//============================================================ +// TYPE DEFINITIONS +//============================================================ + +typedef struct mode_result +{ + int weight; + int scan_penalty; + int x_scale; + int y_scale; + int v_scale; + double x_diff; + double y_diff; + double v_diff; + double x_ratio; + double y_ratio; + double v_ratio; +} mode_result; + +typedef struct modeline +{ + uint64_t pclock; + int hactive; + int hbegin; + int hend; + int htotal; + int vactive; + int vbegin; + int vend; + int vtotal; + int interlace; + int doublescan; + int hsync; + int vsync; + // + double vfreq; + double hfreq; + // + int width; + int height; + int refresh; + int refresh_label; + // + int type; + int range; + uint64_t platform_data; + // + mode_result result; +} modeline; + +typedef struct generator_settings +{ + int interlace; + int doublescan; + uint64_t pclock_min; + bool rotation; + double monitor_aspect; + double refresh_tolerance; + int super_width; + int v_shift_correct; + int pixel_precision; +} generator_settings; + +//============================================================ +// PROTOTYPES +//============================================================ + +int modeline_create(modeline *s_mode, modeline *t_mode, monitor_range *range, generator_settings *cs); +int modeline_compare(modeline *t_mode, modeline *best_mode); +char * modeline_print(modeline *mode, char *modeline, int flags); +char * modeline_result(modeline *mode, char *result); +int modeline_vesa_gtf(modeline *m); +int modeline_parse(const char *user_modeline, modeline *mode); +int modeline_to_monitor_range(monitor_range *range, modeline *mode); +int modeline_is_different(modeline *n, modeline *p); + +int round_near(double number); +int normalize(int a, int b); +int real_res(int x); + + +#endif diff --git a/3rdparty/switchres/monitor.cpp b/3rdparty/switchres/monitor.cpp new file mode 100644 index 0000000000000..4d5001ab41db8 --- /dev/null +++ b/3rdparty/switchres/monitor.cpp @@ -0,0 +1,431 @@ +/************************************************************** + + monitor.cpp - Monitor presets and custom monitor definition + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include "monitor.h" +#include "log.h" + +//============================================================ +// CONSTANTS +//============================================================ + +#define HFREQ_MIN 14000 +#define HFREQ_MAX 540672 // 8192 * 1.1 * 60 +#define VFREQ_MIN 40 +#define VFREQ_MAX 200 +#define PROGRESSIVE_LINES_MIN 128 + +//============================================================ +// monitor_fill_range +//============================================================ + +int monitor_fill_range(monitor_range *range, const char *specs_line) +{ + monitor_range new_range; + + if (strcmp(specs_line, "auto")) { + int e = sscanf(specs_line, "%lf-%lf,%lf-%lf,%lf,%lf,%lf,%lf,%lf,%lf,%d,%d,%d,%d,%d,%d", + &new_range.hfreq_min, &new_range.hfreq_max, + &new_range.vfreq_min, &new_range.vfreq_max, + &new_range.hfront_porch, &new_range.hsync_pulse, &new_range.hback_porch, + &new_range.vfront_porch, &new_range.vsync_pulse, &new_range.vback_porch, + &new_range.hsync_polarity, &new_range.vsync_polarity, + &new_range.progressive_lines_min, &new_range.progressive_lines_max, + &new_range.interlaced_lines_min, &new_range.interlaced_lines_max); + + if (e != 16) { + log_error("Switchres: Error trying to fill monitor range with\n %s\n", specs_line); + return -1; + } + + new_range.vfront_porch /= 1000; + new_range.vsync_pulse /= 1000; + new_range.vback_porch /= 1000; + new_range.vertical_blank = (new_range.vfront_porch + new_range.vsync_pulse + new_range.vback_porch); + + if (monitor_evaluate_range(&new_range)) + { + log_error("Switchres: Error in monitor range (ignoring): %s\n", specs_line); + return -1; + } + else + { + memcpy(range, &new_range, sizeof(struct monitor_range)); + monitor_show_range(range); + } + } + return 0; +} + +//============================================================ +// monitor_fill_lcd_range +//============================================================ + +int monitor_fill_lcd_range(monitor_range *range, const char *specs_line) +{ + if (strcmp(specs_line, "auto")) + { + if (sscanf(specs_line, "%lf-%lf", &range->vfreq_min, &range->vfreq_max) == 2) + { + log_verbose("Switchres: LCD vfreq range set by user as %f-%f\n", range->vfreq_min, range->vfreq_max); + return true; + } + else + log_error("Switchres: Error trying to fill LCD range with\n %s\n", specs_line); + } + // Use default values + range->vfreq_min = 59; + range->vfreq_max = 61; + log_verbose("Switchres: Using default vfreq range for LCD %f-%f\n", range->vfreq_min, range->vfreq_max); + + return 0; +} + +//============================================================ +// monitor_show_range +//============================================================ + +int monitor_show_range(monitor_range *range) +{ + log_verbose("Switchres: Monitor range %.2f-%.2f,%.2f-%.2f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%d,%d,%d,%d,%d,%d\n", + range->hfreq_min, range->hfreq_max, + range->vfreq_min, range->vfreq_max, + range->hfront_porch, range->hsync_pulse, range->hback_porch, + range->vfront_porch * 1000, range->vsync_pulse * 1000, range->vback_porch * 1000, + range->hsync_polarity, range->vsync_polarity, + range->progressive_lines_min, range->progressive_lines_max, + range->interlaced_lines_min, range->interlaced_lines_max); + + return 0; +} + +//============================================================ +// monitor_set_preset +//============================================================ + +int monitor_set_preset(char *type, monitor_range *range) +{ + // PAL TV - 50 Hz/625 + if (!strcmp(type, "pal")) + { + monitor_fill_range(&range[0], "15625.00-15625.00, 50.00-50.00, 1.500, 4.700, 5.800, 0.064, 0.160, 1.056, 0, 0, 192, 288, 448, 576"); + return 1; + } + // NTSC TV - 60 Hz/525 + else if (!strcmp(type, "ntsc")) + { + monitor_fill_range(&range[0], "15734.26-15734.26, 59.94-59.94, 1.500, 4.700, 4.700, 0.191, 0.191, 0.953, 0, 0, 192, 240, 448, 480"); + return 1; + } + // Generic 15.7 kHz + else if (!strcmp(type, "generic_15")) + { + monitor_fill_range(&range[0], "15625-15750, 49.50-65.00, 2.000, 4.700, 8.000, 0.064, 0.192, 1.024, 0, 0, 192, 288, 448, 576"); + return 1; + } + // Arcade 15.7 kHz - standard resolution + else if (!strcmp(type, "arcade_15")) + { + monitor_fill_range(&range[0], "15625-16200, 49.50-65.00, 2.000, 4.700, 8.000, 0.064, 0.192, 1.024, 0, 0, 192, 288, 448, 576"); + return 1; + } + // Arcade 15.7-16.5 kHz - extended resolution + else if (!strcmp(type, "arcade_15ex")) + { + monitor_fill_range(&range[0], "15625-16500, 49.50-65.00, 2.000, 4.700, 8.000, 0.064, 0.192, 1.024, 0, 0, 192, 288, 448, 576"); + return 1; + } + // Arcade 25.0 kHz - medium resolution + else if (!strcmp(type, "arcade_25")) + { + monitor_fill_range(&range[0], "24960-24960, 49.50-65.00, 0.800, 4.000, 3.200, 0.080, 0.200, 1.000, 0, 0, 384, 400, 768, 800"); + return 1; + } + // Arcade 31.5 kHz - medium resolution + else if (!strcmp(type, "arcade_31")) + { + monitor_fill_range(&range[0], "31400-31500, 49.50-65.00, 0.940, 3.770, 1.890, 0.349, 0.064, 1.017, 0, 0, 400, 512, 0, 0"); + return 1; + } + // Arcade 15.7/25.0 kHz - dual-sync + else if (!strcmp(type, "arcade_15_25")) + { + monitor_fill_range(&range[0], "15625-16200, 49.50-65.00, 2.000, 4.700, 8.000, 0.064, 0.192, 1.024, 0, 0, 192, 288, 448, 576"); + monitor_fill_range(&range[1], "24960-24960, 49.50-65.00, 0.800, 4.000, 3.200, 0.080, 0.200, 1.000, 0, 0, 384, 400, 768, 800"); + return 2; + } + // Arcade 15.7/31.5 kHz - dual-sync + else if (!strcmp(type, "arcade_15_31")) + { + monitor_fill_range(&range[0], "15625-16200, 49.50-65.00, 2.000, 4.700, 8.000, 0.064, 0.192, 1.024, 0, 0, 192, 288, 448, 576"); + monitor_fill_range(&range[1], "31400-31500, 49.50-65.00, 0.940, 3.770, 1.890, 0.349, 0.064, 1.017, 0, 0, 400, 512, 0, 0"); + return 2; + } + // Arcade 15.7/25.0/31.5 kHz - tri-sync + else if (!strcmp(type, "arcade_15_25_31")) + { + monitor_fill_range(&range[0], "15625-16200, 49.50-65.00, 2.000, 4.700, 8.000, 0.064, 0.192, 1.024, 0, 0, 192, 288, 448, 576"); + monitor_fill_range(&range[1], "24960-24960, 49.50-65.00, 0.800, 4.000, 3.200, 0.080, 0.200, 1.000, 0, 0, 384, 400, 768, 800"); + monitor_fill_range(&range[2], "31400-31500, 49.50-65.00, 0.940, 3.770, 1.890, 0.349, 0.064, 1.017, 0, 0, 400, 512, 0, 0"); + return 3; + } + // Makvision 2929D + else if (!strcmp(type, "m2929")) + { + monitor_fill_range(&range[0], "30000-40000, 47.00-90.00, 0.600, 2.500, 2.800, 0.032, 0.096, 0.448, 0, 0, 384, 640, 0, 0"); + return 1; + } + // Wells Gardner D9800, D9400 + else if (!strcmp(type, "d9800") || !strcmp(type, "d9400")) + { + monitor_fill_range(&range[0], "15250-18000, 40-80, 2.187, 4.688, 6.719, 0.190, 0.191, 1.018, 0, 0, 224, 288, 448, 576"); + monitor_fill_range(&range[1], "18001-19000, 40-80, 2.187, 4.688, 6.719, 0.140, 0.191, 0.950, 0, 0, 288, 320, 0, 0"); + monitor_fill_range(&range[2], "20501-29000, 40-80, 2.910, 3.000, 4.440, 0.451, 0.164, 1.048, 0, 0, 320, 384, 0, 0"); + monitor_fill_range(&range[3], "29001-32000, 40-80, 0.636, 3.813, 1.906, 0.318, 0.064, 1.048, 0, 0, 384, 480, 0, 0"); + monitor_fill_range(&range[4], "32001-34000, 40-80, 0.636, 3.813, 1.906, 0.020, 0.106, 0.607, 0, 0, 480, 576, 0, 0"); + monitor_fill_range(&range[5], "34001-38000, 40-80, 1.000, 3.200, 2.200, 0.020, 0.106, 0.607, 0, 0, 576, 600, 0, 0"); + return 6; + } + // Wells Gardner D9200 + else if (!strcmp(type, "d9200")) + { + monitor_fill_range(&range[0], "15250-16500, 40-80, 2.187, 4.688, 6.719, 0.190, 0.191, 1.018, 0, 0, 224, 288, 448, 576"); + monitor_fill_range(&range[1], "23900-24420, 40-80, 2.910, 3.000, 4.440, 0.451, 0.164, 1.148, 0, 0, 384, 400, 0, 0"); + monitor_fill_range(&range[2], "31000-32000, 40-80, 0.636, 3.813, 1.906, 0.318, 0.064, 1.048, 0, 0, 400, 512, 0, 0"); + monitor_fill_range(&range[3], "37000-38000, 40-80, 1.000, 3.200, 2.200, 0.020, 0.106, 0.607, 0, 0, 512, 600, 0, 0"); + return 4; + } + // Wells Gardner K7000 + else if (!strcmp(type, "k7000")) + { + monitor_fill_range(&range[0], "15625-15800, 49.50-63.00, 2.000, 4.700, 8.000, 0.064, 0.160, 1.056, 0, 0, 192, 288, 448, 576"); + return 1; + } + // Wells Gardner 25K7131 + else if (!strcmp(type, "k7131")) + { + monitor_fill_range(&range[0], "15625-16670, 49.5-65, 2.000, 4.700, 8.000, 0.064, 0.160, 1.056, 0, 0, 192, 288, 448, 576"); + return 1; + } + // Wei-Ya M3129 + else if (!strcmp(type, "m3129")) + { + monitor_fill_range(&range[0], "15250-16500, 40-80, 2.187, 4.688, 6.719, 0.190, 0.191, 1.018, 1, 1, 192, 288, 448, 576"); + monitor_fill_range(&range[1], "23900-24420, 40-80, 2.910, 3.000, 4.440, 0.451, 0.164, 1.048, 1, 1, 384, 400, 0, 0"); + monitor_fill_range(&range[2], "31000-32000, 40-80, 0.636, 3.813, 1.906, 0.318, 0.064, 1.048, 1, 1, 400, 512, 0, 0"); + return 3; + } + // Hantarex MTC 9110 + else if (!strcmp(type, "h9110") || !strcmp(type, "polo")) + { + monitor_fill_range(&range[0], "15625-16670, 49.5-65, 2.000, 4.700, 8.000, 0.064, 0.160, 1.056, 0, 0, 192, 288, 448, 576"); + return 1; + } + // Hantarex Polostar 25 + else if (!strcmp(type, "pstar")) + { + monitor_fill_range(&range[0], "15700-15800, 50-65, 1.800, 0.400, 7.400, 0.064, 0.160, 1.056, 0, 0, 192, 256, 0, 0"); + monitor_fill_range(&range[1], "16200-16300, 50-65, 0.200, 0.400, 8.000, 0.040, 0.040, 0.640, 0, 0, 256, 264, 512, 528"); + monitor_fill_range(&range[2], "25300-25400, 50-65, 0.200, 0.400, 8.000, 0.040, 0.040, 0.640, 0, 0, 384, 400, 768, 800"); + monitor_fill_range(&range[3], "31500-31600, 50-65, 0.170, 0.350, 5.500, 0.040, 0.040, 0.640, 0, 0, 400, 512, 0, 0"); + return 4; + } + // Nanao MS-2930, MS-2931 + else if (!strcmp(type, "ms2930")) + { + monitor_fill_range(&range[0], "15450-16050, 50-65, 3.190, 4.750, 6.450, 0.191, 0.191, 1.164, 0, 0, 192, 288, 448, 576"); + monitor_fill_range(&range[1], "23900-24900, 50-65, 2.870, 3.000, 4.440, 0.451, 0.164, 1.148, 0, 0, 384, 400, 0, 0"); + monitor_fill_range(&range[2], "31000-32000, 50-65, 0.330, 3.580, 1.750, 0.316, 0.063, 1.137, 0, 0, 480, 512, 0, 0"); + return 3; + } + // Nanao MS9-29 + else if (!strcmp(type, "ms929")) + { + monitor_fill_range(&range[0], "15450-16050, 50-65, 3.910, 4.700, 6.850, 0.190, 0.191, 1.018, 0, 0, 192, 288, 448, 576"); + monitor_fill_range(&range[1], "23900-24900, 50-65, 2.910, 3.000, 4.440, 0.451, 0.164, 1.048, 0, 0, 384, 400, 0, 0"); + return 2; + } + // Rodotron 666B-29 + else if (!strcmp(type, "r666b")) + { + monitor_fill_range(&range[0], "15450-16050, 50-65, 3.190, 4.750, 6.450, 0.191, 0.191, 1.164, 0, 0, 192, 288, 448, 576"); + monitor_fill_range(&range[1], "23900-24900, 50-65, 2.870, 3.000, 4.440, 0.451, 0.164, 1.148, 0, 0, 384, 400, 0, 0"); + monitor_fill_range(&range[2], "31000-32500, 50-65, 0.330, 3.580, 1.750, 0.316, 0.063, 1.137, 0, 0, 400, 512, 0, 0"); + return 3; + } + // PC CRT 70kHz/120Hz + else if (!strcmp(type, "pc_31_120")) + { + monitor_fill_range(&range[0], "31400-31600, 100-130, 0.671, 2.683, 3.353, 0.034, 0.101, 0.436, 0, 0, 200, 256, 0, 0"); + monitor_fill_range(&range[1], "31400-31600, 50-65, 0.671, 2.683, 3.353, 0.034, 0.101, 0.436, 0, 0, 400, 512, 0, 0"); + return 2; + } + // PC CRT 70kHz/120Hz + else if (!strcmp(type, "pc_70_120")) + { + monitor_fill_range(&range[0], "30000-70000, 100-130, 2.201, 0.275, 4.678, 0.063, 0.032, 0.633, 0, 0, 192, 320, 0, 0"); + monitor_fill_range(&range[1], "30000-70000, 50-65, 2.201, 0.275, 4.678, 0.063, 0.032, 0.633, 0, 0, 400, 1024, 0, 0"); + return 2; + } + // VESA GTF + else if (!strcmp(type, "vesa_480") || !strcmp(type, "vesa_600") || !strcmp(type, "vesa_768") || !strcmp(type, "vesa_1024")) + { + return monitor_fill_vesa_gtf(&range[0], type); + } + + log_error("Switchres: Monitor type unknown: %s\n", type); + return 0; +} + +//============================================================ +// monitor_evaluate_range +//============================================================ + +int monitor_evaluate_range(monitor_range *range) +{ + // First we check that all frequency ranges are reasonable + if (range->hfreq_min < HFREQ_MIN || range->hfreq_min > HFREQ_MAX) + { + log_error("Switchres: hfreq_min %.2f out of range\n", range->hfreq_min); + return 1; + } + if (range->hfreq_max < HFREQ_MIN || range->hfreq_max < range->hfreq_min || range->hfreq_max > HFREQ_MAX) + { + log_error("Switchres: hfreq_max %.2f out of range\n", range->hfreq_max); + return 1; + } + if (range->vfreq_min < VFREQ_MIN || range->vfreq_min > VFREQ_MAX) + { + log_error("Switchres: vfreq_min %.2f out of range\n", range->vfreq_min); + return 1; + } + if (range->vfreq_max < VFREQ_MIN || range->vfreq_max < range->vfreq_min || range->vfreq_max > VFREQ_MAX) + { + log_error("Switchres: vfreq_max %.2f out of range\n", range->vfreq_max); + return 1; + } + + // line_time in μs. We check that no horizontal value is longer than a whole line + double line_time = 1 / range->hfreq_max * 1000000; + + if (range->hfront_porch <= 0 || range->hfront_porch > line_time) + { + log_error("Switchres: hfront_porch %.3f out of range\n", range->hfront_porch); + return 1; + } + if (range->hsync_pulse <= 0 || range->hsync_pulse > line_time) + { + log_error("Switchres: hsync_pulse %.3f out of range\n", range->hsync_pulse); + return 1; + } + if (range->hback_porch <= 0 || range->hback_porch > line_time) + { + log_error("Switchres: hback_porch %.3f out of range\n", range->hback_porch); + return 1; + } + + // frame_time in ms. We check that no vertical value is longer than a whole frame + double frame_time = 1 / range->vfreq_max * 1000; + + if (range->vfront_porch <= 0 || range->vfront_porch > frame_time) + { + log_error("Switchres: vfront_porch %.3f out of range\n", range->vfront_porch); + return 1; + } + if (range->vsync_pulse <= 0 || range->vsync_pulse > frame_time) + { + log_error("Switchres: vsync_pulse %.3f out of range\n", range->vsync_pulse); + return 1; + } + if (range->vback_porch <= 0 || range->vback_porch > frame_time) + { + log_error("Switchres: vback_porch %.3f out of range\n", range->vback_porch); + return 1; + } + + // Now we check sync polarities + if (range->hsync_polarity != 0 && range->hsync_polarity != 1) + { + log_error("Switchres: Hsync polarity can be only 0 or 1\n"); + return 1; + } + if (range->vsync_polarity != 0 && range->vsync_polarity != 1) + { + log_error("Switchres: Vsync polarity can be only 0 or 1\n"); + return 1; + } + + // Finally we check that the line limiters are reasonable + // Progressive range: + if (range->progressive_lines_min > 0 && range->progressive_lines_min < PROGRESSIVE_LINES_MIN) + { + log_error("Switchres: progressive_lines_min must be greater than %d\n", PROGRESSIVE_LINES_MIN); + return 1; + } + if ((range->progressive_lines_min + range->hfreq_max * range->vertical_blank) * range->vfreq_min > range->hfreq_max) + { + log_error("Switchres: progressive_lines_min %d out of range\n", range->progressive_lines_min); + return 1; + } + if (range->progressive_lines_max < range->progressive_lines_min) + { + log_error("Switchres: progressive_lines_max must greater than progressive_lines_min\n"); + return 1; + } + if ((range->progressive_lines_max + range->hfreq_max * range->vertical_blank) * range->vfreq_min > range->hfreq_max) + { + log_error("Switchres: progressive_lines_max %d out of range\n", range->progressive_lines_max); + return 1; + } + + // Interlaced range: + if (range->interlaced_lines_min != 0) + { + if (range->interlaced_lines_min < range->progressive_lines_max) + { + log_error("Switchres: interlaced_lines_min must greater than progressive_lines_max\n"); + return 1; + } + if (range->interlaced_lines_min < PROGRESSIVE_LINES_MIN * 2) + { + log_error("Switchres: interlaced_lines_min must be greater than %d\n", PROGRESSIVE_LINES_MIN * 2); + return 1; + } + if ((range->interlaced_lines_min / 2 + range->hfreq_max * range->vertical_blank) * range->vfreq_min > range->hfreq_max) + { + log_error("Switchres: interlaced_lines_min %d out of range\n", range->interlaced_lines_min); + return 1; + } + if (range->interlaced_lines_max < range->interlaced_lines_min) + { + log_error("Switchres: interlaced_lines_max must greater than interlaced_lines_min\n"); + return 1; + } + if ((range->interlaced_lines_max / 2 + range->hfreq_max * range->vertical_blank) * range->vfreq_min > range->hfreq_max) + { + log_error("Switchres: interlaced_lines_max %d out of range\n", range->interlaced_lines_max); + return 1; + } + } + else + { + if (range->interlaced_lines_max != 0) + { + log_error("Switchres: interlaced_lines_max must be zero if interlaced_lines_min is not defined\n"); + return 1; + } + } + return 0; +} diff --git a/3rdparty/switchres/monitor.h b/3rdparty/switchres/monitor.h new file mode 100644 index 0000000000000..5b0147c42bfa2 --- /dev/null +++ b/3rdparty/switchres/monitor.h @@ -0,0 +1,64 @@ +/************************************************************** + + monitor.h - Monitor presets header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __MONITOR_H__ +#define __MONITOR_H__ + +//============================================================ +// CONSTANTS +//============================================================ + +#define MAX_RANGES 10 +#define MONITOR_CRT 0 +#define MONITOR_LCD 1 +#define STANDARD_CRT_ASPECT 4.0/3.0 + +//============================================================ +// TYPE DEFINITIONS +//============================================================ + +typedef struct monitor_range +{ + double hfreq_min; + double hfreq_max; + double vfreq_min; + double vfreq_max; + double hfront_porch; + double hsync_pulse; + double hback_porch; + double vfront_porch; + double vsync_pulse; + double vback_porch; + int hsync_polarity; + int vsync_polarity; + int progressive_lines_min; + int progressive_lines_max; + int interlaced_lines_min; + int interlaced_lines_max; + double vertical_blank; +} monitor_range; + +//============================================================ +// PROTOTYPES +//============================================================ + +int monitor_fill_range(monitor_range *range, const char *specs_line); +int monitor_show_range(monitor_range *range); +int monitor_set_preset(char *type, monitor_range *range); +int monitor_fill_lcd_range(monitor_range *range, const char *specs_line); +int monitor_fill_vesa_gtf(monitor_range *range, const char *max_lines); +int monitor_fill_vesa_range(monitor_range *range, int lines_min, int lines_max); +int monitor_evaluate_range(monitor_range *range); + +#endif diff --git a/3rdparty/switchres/resync_windows.cpp b/3rdparty/switchres/resync_windows.cpp new file mode 100644 index 0000000000000..1ac09bcfdbb8e --- /dev/null +++ b/3rdparty/switchres/resync_windows.cpp @@ -0,0 +1,164 @@ +/************************************************************** + + resync_windows.cpp - Windows device change notifying helper + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include "resync_windows.h" +#include "log.h" + +GUID GUID_DEVINTERFACE_MONITOR = { 0xe6f07b5f, 0xee97, 0x4a90, 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 }; + +//============================================================ +// resync_handler::resync_handler +//============================================================ + +resync_handler::resync_handler() +{ + my_thread = std::thread(std::bind(&resync_handler::handler_thread, this)); +} + +//============================================================ +// resync_handler::~resync_handler +//============================================================ + +resync_handler::~resync_handler() +{ + SendMessage(m_hwnd, WM_CLOSE, 0, 0); + my_thread.join(); +} + +//============================================================ +// resync_handler::handler_thread +//============================================================ + +void resync_handler::handler_thread() +{ + WNDCLASSEX wc; + MSG msg; + HINSTANCE hinst = GetModuleHandle(NULL); + + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = this->resync_wnd_proc; + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.cbWndExtra = 0; + wc.cbClsExtra = 0; + wc.hInstance = hinst; + wc.hbrBackground = 0; + wc.lpszMenuName = NULL; + wc.lpszClassName = "resync_handler"; + wc.hIcon = NULL; + wc.hIconSm = wc.hIcon; + wc.hCursor = LoadCursor(NULL, IDC_HAND); + + RegisterClassEx(&wc); + + m_hwnd = CreateWindowEx(0, "resync_handler", NULL, WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hinst, NULL); + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this); + + // Register notifications of display monitor events + DEV_BROADCAST_DEVICEINTERFACE filter; + ZeroMemory(&filter, sizeof(filter)); + filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); + filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + filter.dbcc_classguid = GUID_DEVINTERFACE_MONITOR; + HDEVNOTIFY hDeviceNotify = RegisterDeviceNotification(m_hwnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE); + if (hDeviceNotify == NULL) + log_error("Error registering notification\n"); + + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +//============================================================ +// resync_handler::wait +//============================================================ + +void resync_handler::wait() +{ + std::unique_lock lock(m_mutex); + m_is_notified_1 = false; + m_is_notified_2 = false; + + auto start = std::chrono::steady_clock::now(); + + while (!m_is_notified_1 || !m_is_notified_2) + m_event.wait_for(lock, std::chrono::milliseconds(10)); + + auto end = std::chrono::steady_clock::now(); + log_verbose("resync time elapsed %I64d ms\n", std::chrono::duration_cast(end-start).count()); +} + +//============================================================ +// resync_handler::resync_wnd_proc +//============================================================ + +LRESULT CALLBACK resync_handler::resync_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + resync_handler *me = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (me) return me->my_wnd_proc(hwnd, msg, wparam, lparam); + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +LRESULT CALLBACK resync_handler::my_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) + { + case WM_DEVICECHANGE: + { + switch (wparam) + { + case DBT_DEVICEARRIVAL: + { + log_verbose("Message: DBT_DEVICEARRIVAL\n"); + PDEV_BROADCAST_DEVICEINTERFACE db = (PDEV_BROADCAST_DEVICEINTERFACE) lparam; + if (db != nullptr) + { + if (db->dbcc_classguid == GUID_DEVINTERFACE_MONITOR) + { + m_is_notified_1 = true; + m_event.notify_one(); + } + } + break; + } + case DBT_DEVICEREMOVECOMPLETE: + log_verbose("Message: DBT_DEVICEREMOVECOMPLETE\n"); + break; + case DBT_DEVNODES_CHANGED: + log_verbose("Message: DBT_DEVNODES_CHANGED\n"); + m_is_notified_2 = true; + m_event.notify_one(); + break; + default: + log_verbose("Message: WM_DEVICECHANGE message received, value %x unhandled.\n", (int)wparam); + break; + } + return 0; + } + break; + + case WM_CLOSE: + { + PostQuitMessage(0); + return 0; + } + + default: + return DefWindowProc(hwnd, msg, wparam, lparam); + } + return 0; +} diff --git a/3rdparty/switchres/resync_windows.h b/3rdparty/switchres/resync_windows.h new file mode 100644 index 0000000000000..13e6b591dc0e7 --- /dev/null +++ b/3rdparty/switchres/resync_windows.h @@ -0,0 +1,46 @@ +/************************************************************** + + resync_windows.h - Windows device change notifying helper + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __RESYNC_WINDOWS__ +#define __RESYNC_WINDOWS__ + +#include +#include +#include +#include +#include +#include + +class resync_handler +{ + public: + resync_handler(); + ~resync_handler(); + + void wait(); + + private: + static LRESULT CALLBACK resync_wnd_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT CALLBACK my_wnd_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void handler_thread(); + + HWND m_hwnd; + std::thread my_thread; + bool m_is_notified_1; + bool m_is_notified_2; + std::mutex m_mutex; + std::condition_variable m_event; +}; + +#endif diff --git a/3rdparty/switchres/switchres.cpp b/3rdparty/switchres/switchres.cpp new file mode 100644 index 0000000000000..2bee7eca5e095 --- /dev/null +++ b/3rdparty/switchres/switchres.cpp @@ -0,0 +1,356 @@ +/************************************************************** + + switchres.cpp - Swichres manager + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include +#include "switchres.h" +#include "log.h" + +using namespace std; +const string WHITESPACE = " \n\r\t\f\v"; + +#if defined(_WIN32) + #define SR_CONFIG_PATHS ".\\;.\\ini\\;" +#elif defined(__linux__) + #define SR_CONFIG_PATHS "./;./ini/;/etc/;" +#endif + +//============================================================ +// logging +//============================================================ + +void switchres_manager::set_log_verbose_fn(void *func_ptr) { set_log_verbose((void *)func_ptr); } +void switchres_manager::set_log_info_fn(void *func_ptr) { set_log_info((void *)func_ptr); } +void switchres_manager::set_log_error_fn(void *func_ptr) { set_log_error((void *)func_ptr); } + +//============================================================ +// File parsing helpers +//============================================================ + +string ltrim(const string& s) +{ + size_t start = s.find_first_not_of(WHITESPACE); + return (start == string::npos) ? "" : s.substr(start); +} + +string rtrim(const string& s) +{ + size_t end = s.find_last_not_of(WHITESPACE); + return (end == string::npos) ? "" : s.substr(0, end + 1); +} + +string trim(const string& s) +{ + return rtrim(ltrim(s)); +} + +bool get_value(const string& line, string& key, string& value) +{ + size_t key_end = line.find_first_of(WHITESPACE); + + key = line.substr(0, key_end); + value = ltrim(line.substr(key_end + 1)); + + if (key.length() > 0 && value.length() > 0) + return true; + + return false; +} + +constexpr unsigned int s2i(const char* str, int h = 0) +{ + return !str[h] ? 5381 : (s2i(str, h+1)*33) ^ str[h]; +} + +//============================================================ +// switchres_manager::switchres_manager +//============================================================ + +switchres_manager::switchres_manager() +{ + // Set Switchres default config options + set_monitor("generic_15"); + set_modeline("auto"); + set_lcd_range("auto"); + for (int i = 0; i++ < MAX_RANGES;) set_crt_range(i, "auto"); + + // Set display manager default options + set_screen("auto"); + set_modeline_generation(true); + set_lock_unsupported_modes(true); + set_lock_system_modes(true); + set_refresh_dont_care(false); + + // Set modeline generator default options + set_interlace(true); + set_doublescan(true); + set_dotclock_min(0.0f); + set_rotation(false); + set_monitor_aspect(STANDARD_CRT_ASPECT); + set_refresh_tolerance(2.0f); + set_super_width(2560); + set_v_shift_correct(0); + set_pixel_precision(1); + + // Create our display manager + m_display_factory = new display_manager(); +} + +//============================================================ +// switchres_manager::~switchres_manager +//============================================================ + +switchres_manager::~switchres_manager() +{ + if (m_display_factory) delete m_display_factory; + + for (auto &display : displays) + delete display; +}; + +//============================================================ +// switchres_manager::add_display +//============================================================ + +display_manager* switchres_manager::add_display() +{ + // Parse display specific ini, if it exists + display_settings base_ds = ds; + char file_name[32] = {0}; + sprintf(file_name, "display%d.ini", (int)displays.size()); + parse_config(file_name); + + // Create new display + display_manager *display = m_display_factory->make(&ds); + display->set_index(displays.size()); + displays.push_back(display); + + log_verbose("Switchres(v%s) display[%d]: monitor[%s] generation[%s]\n", + SWITCHRES_VERSION, display->index(), ds.monitor, ds.modeline_generation?"on":"off"); + + display->parse_options(); + + // restore base display settings + ds = base_ds; + + return display; +} + +//============================================================ +// switchres_manager::parse_config +//============================================================ + +bool switchres_manager::parse_config(const char *file_name) +{ + ifstream config_file; + + // Search for ini file in our config paths + auto start = 0U; + while (true) + { + char full_path[256] = ""; + string paths = SR_CONFIG_PATHS; + + auto end = paths.find(";", start); + if (end == string::npos) return false; + + snprintf(full_path, sizeof(full_path), "%s%s", paths.substr(start, end - start).c_str(), file_name); + config_file.open(full_path); + + if (config_file.is_open()) + { + log_verbose("parsing %s\n", full_path); + break; + } + start = end + 1; + } + + // Ini file found, parse it + string line; + while (getline(config_file, line)) + { + line = trim(line); + if (line.length() == 0 || line.at(0) == '#') + continue; + + string key, value; + if(get_value(line, key, value)) + { + switch (s2i(key.c_str())) + { + // Switchres options + case s2i("verbose"): + if (atoi(value.c_str())) set_log_verbose_fn((void*)printf); + break; + case s2i("monitor"): + transform(value.begin(), value.end(), value.begin(), ::tolower); + set_monitor(value.c_str()); + break; + case s2i("crt_range0"): + set_crt_range(0, value.c_str()); + break; + case s2i("crt_range1"): + set_crt_range(1, value.c_str()); + break; + case s2i("crt_range2"): + set_crt_range(2, value.c_str()); + break; + case s2i("crt_range3"): + set_crt_range(3, value.c_str()); + break; + case s2i("crt_range4"): + set_crt_range(4, value.c_str()); + break; + case s2i("crt_range5"): + set_crt_range(5, value.c_str()); + break; + case s2i("crt_range6"): + set_crt_range(6, value.c_str()); + break; + case s2i("crt_range7"): + set_crt_range(7, value.c_str()); + break; + case s2i("crt_range8"): + set_crt_range(8, value.c_str()); + break; + case s2i("crt_range9"): + set_crt_range(9, value.c_str()); + break; + case s2i("lcd_range"): + set_lcd_range(value.c_str()); + break; + case s2i("modeline"): + set_modeline(value.c_str()); + break; + case s2i("user_mode"): + { + if (strcmp(value.c_str(), "auto")) + { + modeline user_mode = {}; + if (sscanf(value.c_str(), "%dx%d@%d", &user_mode.width, &user_mode.height, &user_mode.refresh) < 1) + log_error("Error: use format resolution x@\n"); + else + set_user_mode(&user_mode); + } + break; + } + + // Display options + case s2i("display"): + set_screen(value.c_str()); + break; + case s2i("api"): + set_api(value.c_str()); + break; + case s2i("modeline_generation"): + set_modeline_generation(atoi(value.c_str())); + break; + case s2i("lock_unsupported_modes"): + set_lock_unsupported_modes(atoi(value.c_str())); + break; + case s2i("lock_system_modes"): + set_lock_system_modes(atoi(value.c_str())); + break; + case s2i("refresh_dont_care"): + set_refresh_dont_care(atoi(value.c_str())); + break; + case s2i("keep_changes"): + set_keep_changes(atoi(value.c_str())); + break; + + // Modeline generation options + case s2i("interlace"): + set_interlace(atoi(value.c_str())); + break; + case s2i("doublescan"): + set_doublescan(atoi(value.c_str())); + break; + case s2i("dotclock_min"): + { + double pclock_min = 0.0f; + sscanf(value.c_str(), "%lf", &pclock_min); + set_dotclock_min(pclock_min); + break; + } + case s2i("sync_refresh_tolerance"): + { + double refresh_tolerance = 0.0f; + sscanf(value.c_str(), "%lf", &refresh_tolerance); + set_refresh_tolerance(refresh_tolerance); + break; + } + case s2i("super_width"): + { + int super_width = 0; + sscanf(value.c_str(), "%d", &super_width); + set_super_width(super_width); + break; + } + case s2i("aspect"): + set_monitor_aspect(get_aspect(value.c_str())); + break; + + case s2i("v_shift_correct"): + set_v_shift_correct(atoi(value.c_str())); + break; + + case s2i("pixel_precision"): + set_pixel_precision(atoi(value.c_str())); + break; + + // Custom video backend options + case s2i("screen_compositing"): + set_screen_compositing(atoi(value.c_str())); + break; + case s2i("screen_reordering"): + set_screen_reordering(atoi(value.c_str())); + break; + case s2i("allow_hardware_refresh"): + set_allow_hardware_refresh(atoi(value.c_str())); + break; + case s2i("custom_timing"): + set_custom_timing(value.c_str()); + break; + + default: + log_error("Invalid option %s\n", key.c_str()); + break; + } + } + } + config_file.close(); + return true; +} + +//============================================================ +// switchres_manager::get_aspect +//============================================================ + +double switchres_manager::get_aspect(const char* aspect) +{ + int num, den; + if (sscanf(aspect, "%d:%d", &num, &den) == 2) + { + if (den == 0) + { + log_error("Error: denominator can't be zero\n"); + return STANDARD_CRT_ASPECT; + } + return (double(num)/double(den)); + } + + log_error("Error: use format --aspect \n"); + return STANDARD_CRT_ASPECT; +} diff --git a/3rdparty/switchres/switchres.h b/3rdparty/switchres/switchres.h new file mode 100644 index 0000000000000..9ce10fd671382 --- /dev/null +++ b/3rdparty/switchres/switchres.h @@ -0,0 +1,108 @@ +/************************************************************** + + switchres.h - SwichRes general header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __SWITCHRES_H__ +#define __SWITCHRES_H__ + +#include +#include +#include "monitor.h" +#include "modeline.h" +#include "display.h" +#include "edid.h" + +//============================================================ +// CONSTANTS +//============================================================ + +#define SWITCHRES_VERSION "2.002" + +//============================================================ +// TYPE DEFINITIONS +//============================================================ + +typedef struct config_settings +{ + bool mode_switching; +} config_settings; + + +class switchres_manager +{ +public: + + switchres_manager(); + ~switchres_manager(); + + // getters + display_manager *display() const { return displays[0]; } + display_manager *display(int i) const { return i < (int)displays.size()? displays[i] : nullptr; } + + // setters (log manager) + void set_log_verbose_fn(void *func_ptr); + void set_log_info_fn(void *func_ptr); + void set_log_error_fn(void *func_ptr); + + // setters (display manager) + void set_monitor(const char *preset) { strncpy(ds.monitor, preset, sizeof(ds.monitor)-1); } + void set_modeline(const char *modeline) { strncpy(ds.user_modeline, modeline, sizeof(ds.user_modeline)-1); } + void set_user_mode(modeline *user_mode) { ds.user_mode = *user_mode;} + void set_crt_range(int i, const char *range) { strncpy(ds.crt_range[i], range, sizeof(ds.crt_range[i])-1); } + void set_lcd_range(const char *range) { strncpy(ds.lcd_range, range, sizeof(ds.lcd_range)-1); } + void set_screen(const char *screen) { strncpy(ds.screen, screen, sizeof(ds.screen)-1); } + void set_api(const char *api) { strncpy(ds.api, api, sizeof(ds.api)-1); } + void set_modeline_generation(bool value) { ds.modeline_generation = value; } + void set_lock_unsupported_modes(bool value) { ds.lock_unsupported_modes = value; } + void set_lock_system_modes(bool value) { ds.lock_system_modes = value; } + void set_refresh_dont_care(bool value) { ds.refresh_dont_care = value; } + void set_keep_changes(bool value) { ds.keep_changes = value; } + + // setters (modeline generator) + void set_interlace(bool value) { ds.gs.interlace = value; } + void set_doublescan(bool value) { ds.gs.doublescan = value; } + void set_dotclock_min(double value) { ds.gs.pclock_min = value * 1000000; } + void set_refresh_tolerance(double value) { ds.gs.refresh_tolerance = value; } + void set_super_width(int value) { ds.gs.super_width = value; } + void set_rotation(bool value) { ds.gs.rotation = value; } + void set_monitor_aspect(double value) { ds.gs.monitor_aspect = value; } + void set_monitor_aspect(const char* aspect) { set_monitor_aspect(get_aspect(aspect)); } + void set_v_shift_correct(int value) { ds.gs.v_shift_correct = value; } + void set_pixel_precision(int value) { ds.gs.pixel_precision = value; } + + // setters (custom_video backend) + void set_screen_compositing(bool value) { ds.vs.screen_compositing = value; } + void set_screen_reordering(bool value) { ds.vs.screen_reordering = value; } + void set_allow_hardware_refresh(bool value) { ds.vs.allow_hardware_refresh = value; } + void set_custom_timing(const char *custom_timing) { strncpy(ds.vs.custom_timing, custom_timing, sizeof(ds.vs.custom_timing)-1); } + + // interface + display_manager* add_display(); + bool parse_config(const char *file_name); + + //settings + config_settings cs = {}; + display_settings ds = {}; + + // display list + std::vector displays; + +private: + + display_manager *m_display_factory = 0; + + double get_aspect(const char* aspect); +}; + + +#endif diff --git a/3rdparty/switchres/switchres_main.cpp b/3rdparty/switchres/switchres_main.cpp new file mode 100644 index 0000000000000..8f9d745182cce --- /dev/null +++ b/3rdparty/switchres/switchres_main.cpp @@ -0,0 +1,306 @@ +/************************************************************** + + switchres_main.cpp - Swichres standalone launcher + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include +#include +#include +#include "switchres.h" +#include "log.h" + +using namespace std; + +int show_version(); +int show_usage(); + + +//============================================================ +// main +//============================================================ + +int main(int argc, char **argv) +{ + + switchres_manager switchres; + + // Init logging + switchres.set_log_info_fn((void*)printf); + switchres.set_log_error_fn((void*)printf); + + switchres.parse_config("switchres.ini"); + + int width = 0; + int height = 0; + float refresh = 0.0; + modeline user_mode = {}; + int index = 0; + + int version_flag = false; + bool help_flag = false; + bool resolution_flag = false; + bool calculate_flag = false; + bool edid_flag = false; + bool switch_flag = false; + bool launch_flag = false; + bool force_flag = false; + bool interlaced_flag = false; + bool user_ini_flag = false; + bool keep_changes_flag = false; + + string ini_file; + string launch_command; + + while (1) + { + static struct option long_options[] = + { + {"version", no_argument, &version_flag, '1'}, + {"help", no_argument, 0, 'h'}, + {"calc", no_argument, 0, 'c'}, + {"switch", no_argument, 0, 's'}, + {"launch", required_argument, 0, 'l'}, + {"monitor", required_argument, 0, 'm'}, + {"aspect", required_argument, 0, 'a'}, + {"edid", no_argument, 0, 'e'}, + {"rotated", no_argument, 0, 'r'}, + {"display", required_argument, 0, 'd'}, + {"force", required_argument, 0, 'f'}, + {"ini", required_argument, 0, 'i'}, + {"verbose", no_argument, 0, 'v'}, + {"backend", required_argument, 0, 'b'}, + {"keep", no_argument, 0, 'k'}, + {0, 0, 0, 0} + }; + + int option_index = 0; + int c = getopt_long(argc, argv, "vhcsl:m:a:erd:f:i:b:k", long_options, &option_index); + + if (c == -1) + break; + + if (version_flag) + { + show_version(); + return 0; + } + + switch (c) + { + case 'v': + switchres.set_log_verbose_fn((void*)printf); + break; + + case 'h': + help_flag = true; + break; + + case 'c': + calculate_flag = true; + break; + + case 's': + switch_flag = true; + break; + + case 'l': + launch_flag = true; + launch_command = optarg; + break; + + case 'm': + switchres.set_monitor(optarg); + break; + + case 'r': + switchres.set_rotation(true); + break; + + case 'd': + // Add new display in multi-monitor case + if (index > 0) switchres.add_display(); + index ++; + switchres.set_screen(optarg); + break; + + case 'a': + switchres.set_monitor_aspect(optarg); + break; + + case 'e': + edid_flag = true; + break; + + case 'f': + force_flag = true; + if (sscanf(optarg, "%dx%d@%d", &user_mode.width, &user_mode.height, &user_mode.refresh) < 1) + log_error("Error: use format --force x@\n"); + break; + + case 'i': + user_ini_flag = true; + ini_file = optarg; + break; + + case 'b': + switchres.set_api(optarg); + break; + + case 'k': + keep_changes_flag = true; + switchres.set_keep_changes(true); + break; + + default: + return 0; + } + } + + if (help_flag) + goto usage; + + // Get user video mode information from command line + if ((argc - optind) < 3) + { + log_error("Error: missing argument\n"); + goto usage; + } + else if ((argc - optind) > 3) + { + log_error("Error: too many arguments\n"); + goto usage; + } + else + { + resolution_flag = true; + width = atoi(argv[optind]); + height = atoi(argv[optind + 1]); + refresh = atof(argv[optind + 2]); + + char scan_mode = argv[optind + 2][strlen(argv[optind + 2]) -1]; + if (scan_mode == 'i') + interlaced_flag = true; + } + + if (user_ini_flag) + switchres.parse_config(ini_file.c_str()); + + switchres.add_display(); + + if (force_flag) + switchres.display()->set_user_mode(&user_mode); + + if (!calculate_flag && !edid_flag) + { + for (auto &display : switchres.displays) + display->init(); + } + + if (resolution_flag) + { + for (auto &display : switchres.displays) + { + modeline *mode = display->get_mode(width, height, refresh, interlaced_flag); + if (mode) display->flush_modes(); + } + + if (edid_flag) + { + edid_block edid = {}; + modeline *mode = switchres.display()->best_mode(); + if (mode) + { + monitor_range *range = &switchres.display()->range[mode->range]; + edid_from_modeline(mode, range, switchres.ds.monitor, &edid); + + char file_name[sizeof(switchres.ds.monitor) + 4]; + sprintf(file_name, "%s.bin", switchres.ds.monitor); + + FILE *file = fopen(file_name, "wb"); + if (file) + { + fwrite(&edid, sizeof(edid), 1, file); + fclose (file); + log_info("EDID saved as %s\n", file_name); + } + } + } + + if (switch_flag) for (auto &display : switchres.displays) display->set_mode(display->best_mode()); + + if (switch_flag && !launch_flag && !keep_changes_flag) + { + log_info("Press ENTER to exit...\n"); + cin.get(); + } + + if (launch_flag) + { + int status_code = system(launch_command.c_str()); + log_info("Process exited with value %d\n", status_code); + } + } + + return (0); + +usage: + show_usage(); + return 0; +} + +//============================================================ +// show_version +//============================================================ + +int show_version() +{ + char version[] + { + "Switchres " SWITCHRES_VERSION "\n" + "Modeline generation engine for emulation\n" + "Copyright (C) 2010-2021 - Chris Kennedy, Antonio Giner, Alexandre Wodarczyk, Gil Delescluse\n" + "License GPL-2.0+\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n" + }; + + log_info("%s", version); + return 0; +} + +//============================================================ +// show_usage +//============================================================ + +int show_usage() +{ + char usage[] = + { + "Usage: switchres [options]\n" + "Options:\n" + " -c, --calc Calculate video mode and exit\n" + " -s, --switch Switch to video mode\n" + " -l, --launch Launch \n" + " -m, --monitor Monitor preset (generic_15, arcade_15, pal, ntsc, etc.)\n" + " -a --aspect Monitor aspect ratio\n" + " -r --rotated Original mode's native orientation is rotated\n" + " -d, --display Use target display (Windows: \\\\.\\DISPLAY1, ... Linux: VGA-0, ...)\n" + " -f, --force x@ Force a specific video mode from display mode list\n" + " -i, --ini Specify an ini file\n" + " -b, --backend Specify the api name\n" + " -e, --edid Create an EDID binary with calculated video modes\n" + " -k, --keep Keep changes on exit (warning: this disables cleanup)\n" + }; + + log_info("%s", usage); + return 0; +} diff --git a/3rdparty/switchres/switchres_wrapper.cpp b/3rdparty/switchres/switchres_wrapper.cpp new file mode 100644 index 0000000000000..b175f6020fcb1 --- /dev/null +++ b/3rdparty/switchres/switchres_wrapper.cpp @@ -0,0 +1,181 @@ +/************************************************************** + + log.h - Simple logging for Switchres + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#define MODULE_API_EXPORTS +#include "switchres.h" +#include "switchres_wrapper.h" +#include "log.h" +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +switchres_manager* swr; + + +MODULE_API void sr_init() { + setlocale(LC_NUMERIC, "C"); + swr = new switchres_manager; + //swr->set_log_verbose_fn((void *)printf); + swr->set_log_info_fn((void *)printf); + swr->set_log_error_fn((void *)printf); + swr->parse_config("switchres.ini"); +} + + +MODULE_API void sr_init_disp() { + swr->add_display(); + for (auto &display : swr->displays) + display->init(); +} + + +MODULE_API void sr_deinit() { + delete swr; +} + + +MODULE_API void sr_set_monitor(const char *preset) { + swr->set_monitor(preset); +} + +void disp_best_mode_to_sr_mode(display_manager* disp, sr_mode* srm) +{ + srm->width = disp->width(); + srm->height = disp->height(); + srm->refresh = disp->refresh(); + srm->is_refresh_off = (disp->is_refresh_off() ? 1 : 0); + srm->is_stretched = (disp->is_stretched() ? 1 : 0); + srm->x_scale = disp->x_scale(); + srm->y_scale = disp->y_scale(); + srm->interlace = (disp->is_interlaced() ? 105 : 0); +} + + +bool sr_refresh_display(display_manager *disp) +{ + if (disp->is_mode_updated()) + { + if (disp->update_mode(disp->best_mode())) + { + log_info("sr_refresh_display: mode was updated\n"); + return true; + } + } + else if (disp->is_mode_new()) + { + if (disp->add_mode(disp->best_mode())) + { + log_info("sr_refresh_display: mode was added\n"); + return true; + } + } + else + { + log_info("sr_refresh_display: no refresh required\n"); + return true; + } + + log_error("sr_refresh_display: error refreshing display\n"); + return false; +} + + +MODULE_API unsigned char sr_add_mode(int width, int height, double refresh, unsigned char interlace, sr_mode *return_mode) { + + log_verbose("Inside sr_add_mode(%dx%d@%f%s)\n", width, height, refresh, interlace > 0? "i":""); + display_manager *disp = swr->display(); + if (disp == nullptr) + { + log_error("sr_add_mode: error, didn't get a display\n"); + return 0; + } + + disp->get_mode(width, height, refresh, (interlace > 0? true : false)); + if (disp->got_mode()) + { + log_verbose("sr_add_mode: got mode %dx%d@%f type(%x)\n", disp->width(), disp->height(), disp->v_freq(), disp->best_mode()->type); + if (return_mode != nullptr) disp_best_mode_to_sr_mode(disp, return_mode); + if (sr_refresh_display(disp)) + return 1; + } + + printf("sr_add_mode: error adding mode\n"); + return 0; +} + + +MODULE_API unsigned char sr_switch_to_mode(int width, int height, double refresh, unsigned char interlace, sr_mode *return_mode) { + + log_verbose("Inside sr_switch_to_mode(%dx%d@%f%s)\n", width, height, refresh, interlace > 0? "i":""); + display_manager *disp = swr->display(); + if (disp == nullptr) + { + log_error("sr_switch_to_mode: error, didn't get a display\n"); + return 0; + } + + disp->get_mode(width, height, refresh, (interlace > 0? true : false)); + if (disp->got_mode()) + { + log_verbose("sr_switch_to_mode: got mode %dx%d@%f type(%x)\n", disp->width(), disp->height(), disp->v_freq(), disp->best_mode()->type); + if (return_mode != nullptr) disp_best_mode_to_sr_mode(disp, return_mode); + if (!sr_refresh_display(disp)) + return 0; + } + + if (disp->is_switching_required()) + { + if (disp->set_mode(disp->best_mode())) + { + log_info("sr_switch_to_mode: successfully switched to %dx%d@%f\n", disp->width(), disp->height(), disp->v_freq()); + return 1; + } + } + else + { + log_info("sr_switch_to_mode: switching not required\n"); + return 1; + } + + log_error("sr_switch_to_mode: error switching to mode\n"); + return 0; +} + + +MODULE_API void sr_set_rotation (unsigned char r) { + if (r > 0) + { + swr->set_rotation(true); + } + else + { + swr->set_rotation(false); + } +} + + +MODULE_API srAPI srlib = { + sr_init, + sr_deinit, + sr_init_disp, + sr_add_mode, + sr_switch_to_mode, + sr_set_rotation +}; + +#ifdef __cplusplus +} +#endif diff --git a/3rdparty/switchres/switchres_wrapper.h b/3rdparty/switchres/switchres_wrapper.h new file mode 100644 index 0000000000000..6811bdee6f3d8 --- /dev/null +++ b/3rdparty/switchres/switchres_wrapper.h @@ -0,0 +1,98 @@ +/************************************************************** + + log.h - Simple logging for Switchres + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __linux__ +#include +#define LIBTYPE void* +#define OPENLIB(libname) dlopen((libname), RTLD_LAZY) +#define LIBFUNC(libh, fn) dlsym((libh), (fn)) +#define LIBERROR dlerror +#define CLOSELIB(libh) dlclose((libh)) + +#elif defined _WIN32 +#include +//#include +#define LIBTYPE HINSTANCE +#define OPENLIB(libname) LoadLibrary(TEXT((libname))) +#define LIBFUNC(lib, fn) GetProcAddress((lib), (fn)) +char* LIBERROR() +{ + //Get the error message, if any. + DWORD errorMessageID = GetLastError(); + if(errorMessageID == 0) + return NULL; //No error message has been recorded + + LPSTR messageBuffer; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + + SetLastError(0); + + static char error_msg[256] = {0}; + strncpy(error_msg, messageBuffer, sizeof(error_msg)-1); + LocalFree(messageBuffer); + return error_msg; +} +#define CLOSELIB(libp) FreeLibrary((libp)) +#endif + +#ifdef _WIN32 + #ifdef MODULE_API_EXPORTS + #define MODULE_API __declspec(dllexport) + #else + #define MODULE_API __declspec(dllimport) + #endif +#else + #define MODULE_API +#endif + +// That's all the exposed data from Switchres calculation +typedef struct MODULE_API { + int width; + int height; + double refresh; + unsigned char is_refresh_off; + unsigned char is_stretched; + int x_scale; + int y_scale; + unsigned char interlace; +} sr_mode; + +MODULE_API void sr_init(); +MODULE_API void sr_deinit(); +MODULE_API void sr_init_disp(); +MODULE_API unsigned char sr_add_mode(int, int, double, unsigned char, sr_mode*); +MODULE_API unsigned char sr_switch_to_mode(int, int, double, unsigned char, sr_mode*); +MODULE_API void sr_set_monitor(const char*); +MODULE_API void sr_set_rotation(unsigned char); + + +// Inspired by https://stackoverflow.com/a/1067684 +typedef struct MODULE_API { + void (*init)(void); + void (*deinit)(void); + void (*sr_init_disp)(void); + unsigned char (*sr_add_mode)(int, int, double, unsigned char, sr_mode*); + unsigned char (*sr_switch_to_mode)(int, int, double, unsigned char, sr_mode*); + void (*sr_set_rotation)(unsigned char); +} srAPI; + + +#ifdef __cplusplus +} +#endif diff --git a/scripts/extlib.lua b/scripts/extlib.lua index a1fa4f5eb9df4..4afdceb6db18c 100644 --- a/scripts/extlib.lua +++ b/scripts/extlib.lua @@ -19,6 +19,7 @@ local extlibs = { glm = { "glm", "3rdparty/glm" }, rapidjson = { "rapidjson", "3rdparty/rapidjson/include" }, pugixml = { "pugixml", "3rdparty/pugixml/src" }, + switchres = { "switchres", "3rdparty/switchres" }, } -- system lib options diff --git a/scripts/src/3rdparty.lua b/scripts/src/3rdparty.lua index 2056e2e95a5c5..dea9091c43e03 100644 --- a/scripts/src/3rdparty.lua +++ b/scripts/src/3rdparty.lua @@ -2389,3 +2389,66 @@ project "asmjit" MAME_DIR .. "3rdparty/asmjit/src/asmjit/x86/x86rapass_p.h", } end + + +-------------------------------------------------- +-- switchres library +-------------------------------------------------- + +project "switchres" + uuid "556720c2-c830-4c5f-bb6c-ec89eface072" + kind "StaticLib" + +files { + MAME_DIR .. "3rdparty/switchres/switchres.cpp", + MAME_DIR .. "3rdparty/switchres/switchres.h", + MAME_DIR .. "3rdparty/switchres/modeline.cpp", + MAME_DIR .. "3rdparty/switchres/modeline.h", + MAME_DIR .. "3rdparty/switchres/monitor.cpp", + MAME_DIR .. "3rdparty/switchres/monitor.h", + MAME_DIR .. "3rdparty/switchres/display.cpp", + MAME_DIR .. "3rdparty/switchres/display.h", + MAME_DIR .. "3rdparty/switchres/custom_video.cpp", + MAME_DIR .. "3rdparty/switchres/custom_video.h", + MAME_DIR .. "3rdparty/switchres/log.cpp", + MAME_DIR .. "3rdparty/switchres/log.h", +} + +if _OPTIONS["targetos"]=="windows" then + files { + MAME_DIR .. "3rdparty/switchres/display_windows.cpp", + MAME_DIR .. "3rdparty/switchres/display_windows.h", + MAME_DIR .. "3rdparty/switchres/resync_windows.cpp", + MAME_DIR .. "3rdparty/switchres/resync_windows.h", + MAME_DIR .. "3rdparty/switchres/custom_video_adl.cpp", + MAME_DIR .. "3rdparty/switchres/custom_video_adl.h", + MAME_DIR .. "3rdparty/switchres/custom_video_ati.cpp", + MAME_DIR .. "3rdparty/switchres/custom_video_ati.h", + MAME_DIR .. "3rdparty/switchres/custom_video_ati_family.cpp", + MAME_DIR .. "3rdparty/switchres/custom_video_pstrip.cpp", + MAME_DIR .. "3rdparty/switchres/custom_video_pstrip.h", + } +end + +if _OPTIONS["targetos"]=="linux" then + files { + MAME_DIR .. "3rdparty/switchres/display_linux.cpp", + MAME_DIR .. "3rdparty/switchres/display_linux.h", + MAME_DIR .. "3rdparty/switchres/custom_video_xrandr.cpp", + MAME_DIR .. "3rdparty/switchres/custom_video_xrandr.h", + MAME_DIR .. "3rdparty/switchres/custom_video_drmkms.cpp", + MAME_DIR .. "3rdparty/switchres/custom_video_drmkms.h", + } + + buildoptions { + backtick("pkg-config --cflags libdrm"), + } + + links { + "Xrandr", + } + + local str = backtick("pkg-config --libs libdrm") + addlibfromstring(str) + addoptionsfromstring(str) +end diff --git a/scripts/src/main.lua b/scripts/src/main.lua index 256a99c690f94..bfc089c6bf25f 100644 --- a/scripts/src/main.lua +++ b/scripts/src/main.lua @@ -299,6 +299,7 @@ end "bgfx", "bimg", "bx", + "switchres", "ocore_" .. _OPTIONS["osd"], } diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua index d7987b5b80e4b..2bd5cb5a2a90e 100644 --- a/scripts/src/osd/modules.lua +++ b/scripts/src/osd/modules.lua @@ -117,6 +117,7 @@ function osdmodulesbuild() MAME_DIR .. "src/osd/modules/monitor/monitor_dxgi.cpp", MAME_DIR .. "src/osd/modules/monitor/monitor_sdl.cpp", MAME_DIR .. "src/osd/modules/monitor/monitor_mac.cpp", + MAME_DIR .. "src/osd/modules/switchres/switchres_module.cpp", } includedirs { ext_includedir("asio"), diff --git a/scripts/src/osd/sdl.lua b/scripts/src/osd/sdl.lua index 37dfb22106775..1ff1aa51cde3d 100644 --- a/scripts/src/osd/sdl.lua +++ b/scripts/src/osd/sdl.lua @@ -53,6 +53,12 @@ function maintargetosdoptions(_target,_subtarget) addoptionsfromstring(str) end + if BASE_TARGETOS=="unix" and _OPTIONS["targetos"]=="linux" then + local str = backtick("pkg-config --libs libdrm") + addlibfromstring(str) + addoptionsfromstring(str) + end + if _OPTIONS["targetos"]=="windows" then if _OPTIONS["with-bundled-sdl2"]~=nil then configuration { "mingw*"} diff --git a/scripts/src/osd/sdl_cfg.lua b/scripts/src/osd/sdl_cfg.lua index e942e50fddeee..c9b37d07c700b 100644 --- a/scripts/src/osd/sdl_cfg.lua +++ b/scripts/src/osd/sdl_cfg.lua @@ -134,6 +134,9 @@ if _OPTIONS["targetos"]=="windows" then configuration { } elseif _OPTIONS["targetos"]=="linux" then + buildoptions { + backtick("pkg-config --cflags libdrm"), + } if _OPTIONS["QT_HOME"]~=nil then buildoptions { "-I" .. backtick(_OPTIONS["QT_HOME"] .. "/bin/qmake -query QT_INSTALL_HEADERS"), diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp index eda6df9b6b6cf..2c59b6aa481e0 100644 --- a/src/emu/emuopts.cpp +++ b/src/emu/emuopts.cpp @@ -90,7 +90,11 @@ const options_entry emu_options::s_option_entries[] = { OPTION_SLEEP, "1", OPTION_BOOLEAN, "enable sleeping, which gives time back to other applications when idle" }, { OPTION_SPEED "(0.01-100)", "1.0", OPTION_FLOAT, "controls the speed of gameplay, relative to realtime; smaller numbers are slower" }, { OPTION_REFRESHSPEED ";rs", "0", OPTION_BOOLEAN, "automatically adjust emulation speed to keep the emulated refresh rate slower than the host screen" }, - { OPTION_LOWLATENCY ";lolat", "0", OPTION_BOOLEAN, "draws new frame before throttling to reduce input latency" }, + { OPTION_SYNCREFRESH ";srf", "0", OPTION_BOOLEAN, "enable using the start of VBLANK for throttling instead of the game time" }, + { OPTION_LOWLATENCY ";lolat", "1", OPTION_BOOLEAN, "draws new frame before throttling to reduce input latency" }, + { OPTION_FRAMEDELAY ";fd", "0", OPTION_INTEGER, "delays the start of each frame to minimize input lag (0-9)"}, + { OPTION_VSYNC_OFFSET, "0", OPTION_INTEGER, "offset vsync position by this many lines to prevent tearing with frame_delay and high-resolution displays" }, + { OPTION_BLACK_FRAME_INSERTION ";bfi", "0", OPTION_INTEGER, "number of black frames to insert after each normal frame, intended to reduce motion blur on 120+ Hz monitors" }, // render options { nullptr, nullptr, OPTION_HEADER, "CORE RENDER OPTIONS" }, diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h index 6ad1d70bcf431..48253c53960e4 100644 --- a/src/emu/emuopts.h +++ b/src/emu/emuopts.h @@ -75,7 +75,11 @@ #define OPTION_SLEEP "sleep" #define OPTION_SPEED "speed" #define OPTION_REFRESHSPEED "refreshspeed" +#define OPTION_SYNCREFRESH "syncrefresh" #define OPTION_LOWLATENCY "lowlatency" +#define OPTION_FRAMEDELAY "framedelay" +#define OPTION_VSYNC_OFFSET "vsync_offset" +#define OPTION_BLACK_FRAME_INSERTION "black_frame_insertion" // core render options #define OPTION_KEEPASPECT "keepaspect" @@ -359,7 +363,11 @@ class emu_options : public core_options bool sleep() const { return m_sleep; } float speed() const { return float_value(OPTION_SPEED); } bool refresh_speed() const { return m_refresh_speed; } + bool sync_refresh() const { return bool_value(OPTION_SYNCREFRESH); } bool low_latency() const { return bool_value(OPTION_LOWLATENCY); } + int frame_delay() const { return int_value(OPTION_FRAMEDELAY); } + int vsync_offset() const { return int_value(OPTION_VSYNC_OFFSET); } + int black_frame_insertion() const { return int_value(OPTION_BLACK_FRAME_INSERTION); } // core render options bool keep_aspect() const { return bool_value(OPTION_KEEPASPECT); } diff --git a/src/emu/sound.cpp b/src/emu/sound.cpp index 7ee663aba693e..adf5a2de629c4 100644 --- a/src/emu/sound.cpp +++ b/src/emu/sound.cpp @@ -1529,13 +1529,13 @@ void sound_manager::update(void *ptr, int param) stream_buffer::sample_t lprev = 0, rprev = 0; // now downmix the final result - u32 finalmix_step = machine().video().speed_factor(); + u32 finalmix_step = machine().video().speed_factor() * 100; u32 finalmix_offset = 0; s16 *finalmix = &m_finalmix[0]; int sample; - for (sample = m_finalmix_leftover; sample < m_samples_this_update * 1000; sample += finalmix_step) + for (sample = m_finalmix_leftover; sample < m_samples_this_update * 100000; sample += finalmix_step) { - int sampindex = sample / 1000; + int sampindex = sample / 100000; // ensure that changing the compression won't reverse direction to reduce "pops" stream_buffer::sample_t lsamp = m_leftmix[sampindex]; @@ -1563,7 +1563,7 @@ void sound_manager::update(void *ptr, int param) rsamp = -1.0; finalmix[finalmix_offset++] = s16(rsamp * 32767.0); } - m_finalmix_leftover = sample - m_samples_this_update * 1000; + m_finalmix_leftover = sample - m_samples_this_update * 100000; // play the result if (finalmix_offset > 0) diff --git a/src/emu/video.cpp b/src/emu/video.cpp index a4b02e2e2f3db..d5983243fee36 100644 --- a/src/emu/video.cpp +++ b/src/emu/video.cpp @@ -21,6 +21,7 @@ #include "xmlfile.h" #include "osdepend.h" +#include //************************************************************************** @@ -52,7 +53,7 @@ const bool video_manager::s_skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS] = { false, true , true , true , true , true , true , true , true , true , true , true } }; - +int video_manager::s_fd_speeds[FD_BINS] = { 0,0,0,0,0,0,0,0,0,0 }; //************************************************************************** // VIDEO MANAGER @@ -87,6 +88,8 @@ video_manager::video_manager(running_machine &machine) , m_overall_valid_counter(0) , m_throttled(machine.options().throttle()) , m_throttle_rate(1.0f) + , m_syncrefresh(machine.options().sync_refresh()) + , m_framedelay(machine.options().frame_delay()) , m_fastforward(false) , m_seconds_to_run(machine.options().seconds_to_run()) , m_auto_frameskip(machine.options().auto_frameskip()) @@ -239,6 +242,21 @@ void video_manager::frame_update(bool from_debugger) machine().osd().update(!from_debugger && skipped_it); g_profiler.stop(); + // manage black frame insertion + if (machine().options().black_frame_insertion() && machine().options().sync_refresh()) + { + if (phase == machine_phase::RUNNING && (!machine().paused() || machine().options().update_in_pause())) + { + render_container *container = &machine().render().ui_container(); + container->add_rect(0, 0, 1, 1, 0xff000000, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + for (int i = 0; i < machine().options().black_frame_insertion(); i++) + { + update_throttle(current_time); + machine().osd().update(!from_debugger && skipped_it); + } + } + } + // we synchronize after rendering instead of before, if low latency mode is enabled if (!from_debugger && !skipped_it && phase > machine_phase::INIT && m_low_latency && effective_throttle()) update_throttle(current_time); @@ -525,6 +543,23 @@ void video_manager::exit() osd_ticks_t tps = osd_ticks_per_second(); double final_real_time = (double)m_overall_real_seconds + (double)m_overall_real_ticks / (double)tps; double final_emu_time = m_overall_emutime.as_double(); + + if (!m_throttled) + { + int i; + float sum = 0; + + osd_printf_info("Frame delay/percentage:"); + + for (i = 0; i < FD_BINS; i++) + sum += s_fd_speeds[i]; + + for (i = 0; i < FD_BINS; i++) + if (s_fd_speeds[i]) + osd_printf_info(" %d/%.2f%%", i, (float) s_fd_speeds[i] / sum * 100.f); + osd_printf_info("\n"); + } + osd_printf_info("Average speed: %.2f%% (%d seconds)\n", 100 * final_emu_time / final_real_time, (m_overall_emutime + attotime(0, ATTOSECONDS_PER_SECOND / 2)).seconds()); } } @@ -712,6 +747,26 @@ void video_manager::update_throttle(attotime emutime) */ + // if we're only syncing to the refresh, bail now + if (m_syncrefresh) + { + if (m_framedelay == 0 || m_framedelay > 9) + return; + + screen_device *const screen = screen_device_enumerator(machine().root_device()).first(); + if (screen) + { + osd_ticks_t now = osd_ticks(); + osd_ticks_t ticks_per_second = osd_ticks_per_second(); + attoseconds_t attoseconds_per_tick = ATTOSECONDS_PER_SECOND / ticks_per_second * m_throttle_rate; + + attoseconds_t period = screen->frame_period().attoseconds(); + int bfi = machine().options().black_frame_insertion(); + throttle_until_ticks(now + period / attoseconds_per_tick * m_framedelay / 10 / (bfi + 1)); + return; + } + } + // outer scope so we can break out in case of a resync while (1) { @@ -979,6 +1034,21 @@ void video_manager::recompute_speed(const attotime &emutime) osd_ticks_t tps = osd_ticks_per_second(); m_speed_percent = delta_emutime.as_double() * (double)tps / (double)delta_realtime; + // adjust speed for audio resampling + if (m_syncrefresh && m_throttled) + { + if (m_speed_percent >= 0.8 && m_speed_percent <= 1.2) + m_speed = m_speed_percent * 1000; + } + + // log speed for frame delay statistic + if (!m_throttled) + { + int bin = (float) FD_BINS - 10.f/m_speed_percent; + bin = bin > (FD_BINS - 1) ? (FD_BINS - 1) : bin < 0 ? 0 : bin; + s_fd_speeds[bin]++; + } + // remember the last times m_speed_last_realtime = realtime; m_speed_last_emutime = emutime; diff --git a/src/emu/video.h b/src/emu/video.h index bd58db9ca1d43..21c5430738bf7 100644 --- a/src/emu/video.h +++ b/src/emu/video.h @@ -28,6 +28,7 @@ constexpr int FRAMESKIP_LEVELS = 12; constexpr int MAX_FRAMESKIP = FRAMESKIP_LEVELS - 2; +constexpr int FD_BINS = 10; //************************************************************************** // TYPE DEFINITIONS @@ -50,6 +51,8 @@ class video_manager int frameskip() const { return m_auto_frameskip ? -1 : m_frameskip_level; } bool throttled() const { return m_throttled; } float throttle_rate() const { return m_throttle_rate; } + bool sync_refresh() const { return m_syncrefresh; } + int32_t framedelay() const { return m_framedelay; } bool fastforward() const { return m_fastforward; } // setters @@ -58,6 +61,8 @@ class video_manager void set_throttle_rate(float throttle_rate) { m_throttle_rate = throttle_rate; } void set_fastforward(bool ffwd) { m_fastforward = ffwd; } void set_output_changed() { m_output_changed = true; } + void set_sync_refresh(bool syncrefresh) { m_syncrefresh = syncrefresh; } + void set_framedelay(int framedelay) { m_framedelay = framedelay; } // misc void toggle_record_movie(movie_recording::format format); @@ -149,6 +154,8 @@ class video_manager // configuration bool m_throttled; // flag: true if we're currently throttled float m_throttle_rate; // target rate for throttling + bool m_syncrefresh; // flag: TRUE if we're currently refresh-synced + int32_t m_framedelay; // tenths of frame to delay emulation start bool m_fastforward; // flag: true if we're currently fast-forwarding u32 m_seconds_to_run; // number of seconds to run before quitting bool m_auto_frameskip; // flag: true if we're automatically frameskipping @@ -174,6 +181,9 @@ class video_manager // movie recordings std::vector m_movie_recordings; + // frame delay statistics + static int s_fd_speeds[FD_BINS]; + static const bool s_skiptable[FRAMESKIP_LEVELS][FRAMESKIP_LEVELS]; static const attoseconds_t ATTOSECONDS_PER_SPEED_UPDATE = ATTOSECONDS_PER_SECOND / 4; diff --git a/src/frontend/mame/ui/info.cpp b/src/frontend/mame/ui/info.cpp index 3a57aac42c0da..1c63f8286668d 100644 --- a/src/frontend/mame/ui/info.cpp +++ b/src/frontend/mame/ui/info.cpp @@ -17,6 +17,7 @@ #include "romload.h" #include "softlist.h" #include "emuopts.h" +#include "osdepend.h" namespace ui { @@ -432,6 +433,7 @@ std::string machine_info::game_info_string() const buf << _("None\n"); else { + int i = 0; for (screen_device &screen : scriter) { std::string detail; @@ -454,6 +456,9 @@ std::string machine_info::game_info_string() const util::stream_format(buf, (scrcount > 1) ? _("%1$s: %2$s\n") : _("%2$s\n"), get_screen_desc(screen), detail); + + // display Switchres information + buf << _(m_machine.osd().switchres_mode(i++)); } } diff --git a/src/frontend/mame/ui/submenu.cpp b/src/frontend/mame/ui/submenu.cpp index 6d034841b513b..78030d38435a9 100644 --- a/src/frontend/mame/ui/submenu.cpp +++ b/src/frontend/mame/ui/submenu.cpp @@ -105,7 +105,6 @@ std::vector const submenu::video_options = { { submenu::option_type::OSD, __("Video Mode"), OSDOPTION_VIDEO }, { submenu::option_type::OSD, __("Number Of Screens"), OSDOPTION_NUMSCREENS }, #if defined(UI_WINDOWS) && !defined(UI_SDL) - { submenu::option_type::OSD, __("Triple Buffering"), WINOPTION_TRIPLEBUFFER }, { submenu::option_type::OSD, __("HLSL"), WINOPTION_HLSL_ENABLE }, #endif { submenu::option_type::OSD, __("GLSL"), OSDOPTION_GL_GLSL }, @@ -114,7 +113,7 @@ std::vector const submenu::video_options = { { submenu::option_type::OSD, __("Window Mode"), OSDOPTION_WINDOW }, { submenu::option_type::EMU, __("Enforce Aspect Ratio"), OPTION_KEEPASPECT }, { submenu::option_type::OSD, __("Start Out Maximized"), OSDOPTION_MAXIMIZE }, - { submenu::option_type::OSD, __("Synchronized Refresh"), OSDOPTION_SYNCREFRESH }, + { submenu::option_type::EMU, __("Synchronized Refresh"), OPTION_SYNCREFRESH }, { submenu::option_type::OSD, __("Wait Vertical Sync"), OSDOPTION_WAITVSYNC } }; diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index f5e04284cd793..716660498bd68 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -39,6 +39,7 @@ #include "uiinput.h" #include "../osd/modules/lib/osdobj_common.h" +#include "config.h" #include #include @@ -220,6 +221,9 @@ void mame_ui_manager::init() config_load_delegate(&mame_ui_manager::config_load, this), config_save_delegate(&mame_ui_manager::config_save, this)); + // register callbacks + machine().configuration().config_register("sliders", config_load_delegate(&mame_ui_manager::sliders_load, this), config_save_delegate(&mame_ui_manager::sliders_save, this)); + // create mouse bitmap uint32_t *dst = &m_mouse_bitmap.pix(0); memcpy(dst,mouse_bitmap,32*32*sizeof(uint32_t)); @@ -1473,6 +1477,14 @@ std::vector mame_ui_manager::slider_init(running_machine &machine // add overall volume slider_alloc(_("Master Volume"), -32, 0, 0, 1, std::bind(&mame_ui_manager::slider_volume, this, _1, _2)); + // add frame delay + slider_alloc(_("Frame Delay"), 0, machine.options().frame_delay(), 9, 1, std::bind(&mame_ui_manager::slider_framedelay, this, _1, _2)); + +#ifdef _WIN32 + // add vsync offset + slider_alloc(_("V-Sync Offset"), 0, machine.options().vsync_offset(), 1024, 1, std::bind(&mame_ui_manager::slider_vsync_offset, this, _1, _2)); +#endif + // add per-channel volume mixer_input info; for (int item = 0; machine.sound().indexed_mixer_input(item, info); item++) @@ -1503,7 +1515,7 @@ std::vector mame_ui_manager::slider_init(running_machine &machine for (device_execute_interface &exec : execute_interface_enumerator(machine.root_device())) { std::string str = string_format(_("Overclock CPU %1$s"), exec.device().tag()); - slider_alloc(std::move(str), 100, 1000, 4000, 10, std::bind(&mame_ui_manager::slider_overclock, this, std::ref(exec.device()), _1, _2)); + slider_alloc(std::move(str), 100, 1000, 4000, 1, std::bind(&mame_ui_manager::slider_overclock, this, std::ref(exec.device()), _1, _2)); } for (device_sound_interface &snd : sound_interface_enumerator(machine.root_device())) { @@ -1511,7 +1523,7 @@ std::vector mame_ui_manager::slider_init(running_machine &machine if (!snd.device().interface(exec) && snd.device().unscaled_clock() != 0) { std::string str = string_format(_("Overclock %1$s sound"), snd.device().tag()); - slider_alloc(std::move(str), 100, 1000, 4000, 10, std::bind(&mame_ui_manager::slider_overclock, this, std::ref(snd.device()), _1, _2)); + slider_alloc(std::move(str), 100, 1000, 4000, 1, std::bind(&mame_ui_manager::slider_overclock, this, std::ref(snd.device()), _1, _2)); } } } @@ -1608,6 +1620,8 @@ std::vector mame_ui_manager::slider_init(running_machine &machine } #endif + sliders_apply(); + std::vector items; for (auto &slider : m_sliders) { @@ -1638,6 +1652,36 @@ int32_t mame_ui_manager::slider_volume(std::string *str, int32_t newval) } +//------------------------------------------------- +// slider_framedelay - global frame delay slider +// callback +//------------------------------------------------- + +int32_t mame_ui_manager::slider_framedelay(std::string *str, int32_t newval) +{ + if (newval != SLIDER_NOCHANGE) + machine().video().set_framedelay(newval); + if (str) + *str = string_format(_("%1$3d"), machine().video().framedelay()); + return machine().video().framedelay(); +} + + +//-------------------------------------------------- +// slider_vsync_offset - global vsync_offset slider +// callback +//-------------------------------------------------- + +int32_t mame_ui_manager::slider_vsync_offset(std::string *str, int32_t newval) +{ + if (newval != SLIDER_NOCHANGE) + machine().options().set_value(OPTION_VSYNC_OFFSET, newval, OPTION_PRIORITY_HIGH); + if (str) + *str = string_format(_("%1$3d"), machine().options().vsync_offset()); + return machine().options().vsync_offset(); +} + + //------------------------------------------------- // slider_mixervol - single channel volume // slider callback @@ -1691,7 +1735,7 @@ int32_t mame_ui_manager::slider_overclock(device_t &device, std::string *str, in if (newval != SLIDER_NOCHANGE) device.set_clock_scale((float)newval * 0.001f); if (str) - *str = string_format(_("%1$3.0f%%"), floor(device.clock_scale() * 100.0 + 0.5)); + *str = string_format(_("%1$3.1f%%"), device.clock_scale() * 100.0); return floor(device.clock_scale() * 1000.0 + 0.5); } @@ -2235,3 +2279,83 @@ void ui_colors::refresh(const ui_options &options) m_dipsw_color = options.dipsw_color(); m_slider_color = options.slider_color(); } + +//------------------------------------------------- +// sliders_load - read data from the +// configuration file +//------------------------------------------------- + +void mame_ui_manager::sliders_load(config_type cfg_type, util::xml::data_node const *parentnode) +{ + // we only care about game files + if (cfg_type != config_type::GAME) + return; + + // might not have any data + if (parentnode == nullptr) + return; + + // iterate over slider nodes + for (util::xml::data_node const *slider_node = parentnode->get_child("slider"); slider_node; slider_node = slider_node->get_next_sibling("slider")) + { + std::string desc = slider_node->get_attribute_string("desc", ""); + int32_t saved_val = slider_node->get_attribute_int("value", 0); + + // create a dummy slider to store the saved value + slider_saved_alloc(std::move(desc), 0, saved_val, 0, 0, nullptr); + } +} + + +//------------------------------------------------- +// sliders_appy - apply data from the conf. file +// This currently needs to be done on a separate +// step because sliders are not created yet when +// configuration file is loaded +//------------------------------------------------- + +void mame_ui_manager::sliders_apply(void) +{ + // iterate over sliders and restore saved values + for (auto &slider : m_sliders) + { + for (auto &slider_saved : m_sliders_saved) + { + if (!strcmp(slider->description.c_str(), slider_saved->description.c_str())) + { + std::string tempstring; + slider->update(&tempstring, slider_saved->defval); + break; + + } + } + } +} + + +//------------------------------------------------- +// sliders_save - save data to the configuration +// file +//------------------------------------------------- + +void mame_ui_manager::sliders_save(config_type cfg_type, util::xml::data_node *parentnode) +{ + // we only care about game files + if (cfg_type != config_type::GAME) + return; + + std::string tempstring; + util::xml::data_node *slider_node; + + // save UI sliders + for (auto &slider : m_sliders) + { + int32_t curval = slider->update(&tempstring, SLIDER_NOCHANGE); + if (curval != slider->defval) + { + slider_node = parentnode->add_child("slider", nullptr); + slider_node->set_attribute("desc", slider->description.c_str()); + slider_node->set_attribute_int("value", curval); + } + } +} diff --git a/src/frontend/mame/ui/ui.h b/src/frontend/mame/ui/ui.h index ea9897e78d3b8..896af17873b77 100644 --- a/src/frontend/mame/ui/ui.h +++ b/src/frontend/mame/ui/ui.h @@ -56,6 +56,64 @@ class laserdisc_device; /* cancel return value for a UI handler */ #define UI_HANDLER_CANCEL ((uint32_t)~0) +#define SLIDER_DEVICE_SPACING 0x0ff +#define SLIDER_SCREEN_SPACING 0x0f +#define SLIDER_INPUT_SPACING 0x0f + +enum +{ + SLIDER_ID_VOLUME = 0, + SLIDER_ID_FRAMEDELAY, + SLIDER_ID_VSYNC_OFFSET, + SLIDER_ID_MIXERVOL, + SLIDER_ID_MIXERVOL_LAST = SLIDER_ID_MIXERVOL + SLIDER_DEVICE_SPACING, + SLIDER_ID_ADJUSTER, + SLIDER_ID_ADJUSTER_LAST = SLIDER_ID_ADJUSTER + SLIDER_DEVICE_SPACING, + SLIDER_ID_OVERCLOCK, + SLIDER_ID_OVERCLOCK_LAST = SLIDER_ID_OVERCLOCK + SLIDER_DEVICE_SPACING, + SLIDER_ID_REFRESH, + SLIDER_ID_REFRESH_LAST = SLIDER_ID_REFRESH + SLIDER_SCREEN_SPACING, + SLIDER_ID_BRIGHTNESS, + SLIDER_ID_BRIGHTNESS_LAST = SLIDER_ID_BRIGHTNESS + SLIDER_SCREEN_SPACING, + SLIDER_ID_CONTRAST, + SLIDER_ID_CONTRAST_LAST = SLIDER_ID_CONTRAST + SLIDER_SCREEN_SPACING, + SLIDER_ID_GAMMA, + SLIDER_ID_GAMMA_LAST = SLIDER_ID_GAMMA + SLIDER_SCREEN_SPACING, + SLIDER_ID_XSCALE, + SLIDER_ID_XSCALE_LAST = SLIDER_ID_XSCALE + SLIDER_SCREEN_SPACING, + SLIDER_ID_YSCALE, + SLIDER_ID_YSCALE_LAST = SLIDER_ID_YSCALE + SLIDER_SCREEN_SPACING, + SLIDER_ID_XOFFSET, + SLIDER_ID_XOFFSET_LAST = SLIDER_ID_XOFFSET + SLIDER_SCREEN_SPACING, + SLIDER_ID_YOFFSET, + SLIDER_ID_YOFFSET_LAST = SLIDER_ID_YOFFSET + SLIDER_SCREEN_SPACING, + SLIDER_ID_OVERLAY_XSCALE, + SLIDER_ID_OVERLAY_XSCALE_LAST = SLIDER_ID_OVERLAY_XSCALE + SLIDER_SCREEN_SPACING, + SLIDER_ID_OVERLAY_YSCALE, + SLIDER_ID_OVERLAY_YSCALE_LAST = SLIDER_ID_OVERLAY_YSCALE + SLIDER_SCREEN_SPACING, + SLIDER_ID_OVERLAY_XOFFSET, + SLIDER_ID_OVERLAY_XOFFSET_LAST = SLIDER_ID_OVERLAY_XOFFSET + SLIDER_SCREEN_SPACING, + SLIDER_ID_OVERLAY_YOFFSET, + SLIDER_ID_OVERLAY_YOFFSET_LAST = SLIDER_ID_OVERLAY_YOFFSET + SLIDER_SCREEN_SPACING, + SLIDER_ID_FLICKER, + SLIDER_ID_FLICKER_LAST = SLIDER_ID_FLICKER + SLIDER_SCREEN_SPACING, + SLIDER_ID_BEAM_WIDTH_MIN, + SLIDER_ID_BEAM_WIDTH_MIN_LAST = SLIDER_ID_BEAM_WIDTH_MIN + SLIDER_SCREEN_SPACING, + SLIDER_ID_BEAM_WIDTH_MAX, + SLIDER_ID_BEAM_WIDTH_MAX_LAST = SLIDER_ID_BEAM_WIDTH_MAX + SLIDER_SCREEN_SPACING, + SLIDER_ID_BEAM_INTENSITY, + SLIDER_ID_BEAM_INTENSITY_LAST = SLIDER_ID_BEAM_INTENSITY + SLIDER_SCREEN_SPACING, + SLIDER_ID_BEAM_DOT_SIZE, + SLIDER_ID_BEAM_DOT_SIZE_LAST = SLIDER_ID_BEAM_DOT_SIZE + SLIDER_SCREEN_SPACING, + SLIDER_ID_CROSSHAIR_SCALE, + SLIDER_ID_CROSSHAIR_SCALE_LAST = SLIDER_ID_CROSSHAIR_SCALE + SLIDER_INPUT_SPACING, + SLIDER_ID_CROSSHAIR_OFFSET, + SLIDER_ID_CROSSHAIR_OFFSET_LAST = SLIDER_ID_CROSSHAIR_OFFSET + SLIDER_INPUT_SPACING, + + SLIDER_ID_CORE_LAST = SLIDER_ID_CROSSHAIR_OFFSET, + SLIDER_ID_CORE_COUNT +}; + /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ @@ -258,10 +316,16 @@ class mame_ui_manager : public ui_manager void exit(); void config_load(config_type cfg_type, util::xml::data_node const *parentnode); void config_save(config_type cfg_type, util::xml::data_node *parentnode); + void sliders_load(config_type cfg_type, util::xml::data_node const *parentnode); + void sliders_save(config_type cfg_type, util::xml::data_node *parentnode); + void sliders_apply(void); template void slider_alloc(Params &&...args) { m_sliders.push_back(std::make_unique(std::forward(args)...)); } + template void slider_saved_alloc(Params &&...args) { m_sliders_saved.push_back(std::make_unique(std::forward(args)...)); } // slider controls int32_t slider_volume(std::string *str, int32_t newval); + int32_t slider_framedelay(std::string *str, int32_t newval); + int32_t slider_vsync_offset(std::string *str, int32_t newval); int32_t slider_mixervol(int item, std::string *str, int32_t newval); int32_t slider_adjuster(ioport_field &field, std::string *str, int32_t newval); int32_t slider_overclock(device_t &device, std::string *str, int32_t newval); @@ -289,6 +353,7 @@ class mame_ui_manager : public ui_manager #endif std::vector> m_sliders; + std::vector> m_sliders_saved; }; diff --git a/src/osd/modules/lib/osdobj_common.cpp b/src/osd/modules/lib/osdobj_common.cpp index 932beed416b08..7f3b513f69204 100644 --- a/src/osd/modules/lib/osdobj_common.cpp +++ b/src/osd/modules/lib/osdobj_common.cpp @@ -59,7 +59,6 @@ const options_entry osd_options::s_option_entries[] = { OSDOPTION_WINDOW ";w", "0", OPTION_BOOLEAN, "enable window mode; otherwise, full screen mode is assumed" }, { OSDOPTION_MAXIMIZE ";max", "1", OPTION_BOOLEAN, "default to maximized windows" }, { OSDOPTION_WAITVSYNC ";vs", "0", OPTION_BOOLEAN, "enable waiting for the start of VBLANK before flipping screens (reduces tearing effects)" }, - { OSDOPTION_SYNCREFRESH ";srf", "0", OPTION_BOOLEAN, "enable using the start of VBLANK for throttling instead of the game time" }, { OSD_MONITOR_PROVIDER, OSDOPTVAL_AUTO, OPTION_STRING, "monitor discovery method: " }, // per-window options @@ -91,10 +90,42 @@ const options_entry osd_options::s_option_entries[] = // full screen options { nullptr, nullptr, OPTION_HEADER, "OSD FULL SCREEN OPTIONS" }, - { OSDOPTION_SWITCHRES, "0", OPTION_BOOLEAN, "enable resolution switching" }, + { OSDOPTION_SWITCHRES, "1", OPTION_BOOLEAN, "enable resolution switching" }, + { OSDOPTION_SWITCHRES_BACKEND, "auto", OPTION_STRING, "Switchres backend to use (adl, ati, powerstrip, xrandr, drmkms)" }, + { OSDOPTION_MODE_SETTING, "0", OPTION_BOOLEAN, "force resolution switching through Switchres backend" }, + { OSDOPTION_MODELINE_GENERATION ";ml", "1", OPTION_BOOLEAN, "Automatic generation of modelines based on the specified monitor type" }, + { OSDOPTION_MONITOR ";m", "generic_15", OPTION_STRING, "Monitor type, e.g.: generic_15, arcade_15, lcd, custom, etc." }, + { OSDOPTION_INTERLACE ";in", "1", OPTION_BOOLEAN, "Enable interlaced scanning when necessary" }, + { OSDOPTION_DOUBLESCAN ";ds", "0", OPTION_BOOLEAN, "Enable double scanning when necessary (unsupported by some backends/gpus)" }, + { OSDOPTION_SUPER_WIDTH ";cs", "2560", OPTION_INTEGER, "Automatically apply -unevenstretchx if resolution width is equal or greater than this value" }, + { OSDOPTION_CHANGERES ";cr", "1", OPTION_BOOLEAN, "Enable dynamic in-game video mode switching" }, + { OSDOPTION_LOCK_SYSTEM_MODES ";lsm", "1", OPTION_BOOLEAN, "Lock system (non-custom) video modes, only use modes created by us" }, + { OSDOPTION_LOCK_UNSUPPORTED_MODES ";lum","1", OPTION_BOOLEAN, "Lock video modes reported as unsupported by your monitor's EDID" }, + { OSDOPTION_REFRESH_DONT_CARE ";rdc", "0", OPTION_BOOLEAN, "Ignore video mode's refresh reported by OS when checking ranges" }, + { OSDOPTION_DOTCLOCK_MIN ";dcm", "0", OPTION_FLOAT, "Lowest pixel clock supported by video card, in MHz, default is 0" }, + { OSDOPTION_V_SHIFT_CORRECT, "0", OPTION_INTEGER, "Apply vertical shift correction for multi-standard consumer CRT TVs"}, + { OSDOPTION_PIXEL_PRECISION, "1", OPTION_BOOLEAN, "Calculate horizontal values with 1-pixel precision to improve horizontal centering" }, + { OSDOPTION_SYNC_REFRESH_TOLERANCE ";srt","2.0", OPTION_FLOAT, "Maximum refresh difference, in Hz, allowed in order to synchronize" }, + { OSDOPTION_AUTOSYNC, "1", OPTION_BOOLEAN, "automatically enable syncrefresh if refresh difference is below syncrefresh_tolerance" }, + { OSDOPTION_SCREEN_COMPOSITING, "0", OPTION_BOOLEAN, "Readjust relative screen positions of a multi-display setup after mode switching (Linux)" }, + { OSDOPTION_SCREEN_REORDERING, "0", OPTION_BOOLEAN, "Reallocates desktop multiple screens stacked vertically, so super-resolutions fit (Linux)" }, + { OSDOPTION_ALLOW_HW_REFRESH, "0", OPTION_BOOLEAN, "Allow on-the-fly mode addition (Windows)" }, + { OSDOPTION_MODELINE ";mode", "auto", OPTION_STRING, "Use custom defined modeline" }, + { OSDOPTION_PS_TIMING ";pst", "auto", OPTION_STRING, "Use custom Powertrip timing string" }, + { OSDOPTION_LCD_RANGE ";lcd", "auto", OPTION_STRING, "Add custom LCD range, VfreqMin-VfreqMax, in Hz, e.g.: 55.50-61.00" }, + { OSDOPTION_CRT_RANGE "0", "auto", OPTION_STRING, "Add custom CRT range, see documentation for details." }, + { OSDOPTION_CRT_RANGE "1", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "2", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "3", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "4", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "5", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "6", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "7", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "8", "auto", OPTION_STRING, "Add custom CRT range" }, + { OSDOPTION_CRT_RANGE "9", "auto", OPTION_STRING, "Add custom CRT range" }, { nullptr, nullptr, OPTION_HEADER, "OSD ACCELERATED VIDEO OPTIONS" }, - { OSDOPTION_FILTER ";glfilter;flt", "1", OPTION_BOOLEAN, "use bilinear filtering when scaling emulated video" }, + { OSDOPTION_FILTER ";glfilter;flt", "0", OPTION_BOOLEAN, "use bilinear filtering when scaling emulated video" }, { OSDOPTION_PRESCALE "(1-8)", "1", OPTION_INTEGER, "scale emulated video by this factor before applying filters/shaders" }, #if USE_OPENGL @@ -651,6 +682,8 @@ void osd_common_t::init_subsystems() assert(m_monitor_module != nullptr); m_monitor_module->init(options()); + m_switchres.init(machine()); + if (!video_init()) { video_exit(); @@ -735,6 +768,7 @@ void osd_common_t::input_resume() void osd_common_t::exit_subsystems() { video_exit(); + m_switchres.exit(); } void osd_common_t::video_exit() diff --git a/src/osd/modules/lib/osdobj_common.h b/src/osd/modules/lib/osdobj_common.h index 3be82fd2ee985..ce9aa9e968f27 100644 --- a/src/osd/modules/lib/osdobj_common.h +++ b/src/osd/modules/lib/osdobj_common.h @@ -24,6 +24,7 @@ #include "modules/midi/midi_module.h" #include "modules/output/output_module.h" #include "modules/monitor/monitor_module.h" +#include "modules/switchres/switchres_module.h" #include "emuopts.h" #include "../frontend/mame/ui/menuitem.h" #include @@ -51,7 +52,6 @@ #define OSDOPTION_WINDOW "window" #define OSDOPTION_MAXIMIZE "maximize" #define OSDOPTION_WAITVSYNC "waitvsync" -#define OSDOPTION_SYNCREFRESH "syncrefresh" #define OSDOPTION_SCREEN "screen" #define OSDOPTION_ASPECT "aspect" @@ -93,6 +93,31 @@ #define OSDOPTION_BGFX_LUT "bgfx_lut" #define OSDOPTION_BGFX_AVI_NAME "bgfx_avi_name" +#define OSDOPTION_SWITCHRES_BACKEND "switchres_backend" +#define OSDOPTION_MODE_SETTING "modesetting" +#define OSDOPTION_MODELINE_GENERATION "modeline_generation" +#define OSDOPTION_MONITOR "monitor" +#define OSDOPTION_INTERLACE "interlace" +#define OSDOPTION_DOUBLESCAN "doublescan" +#define OSDOPTION_SUPER_WIDTH "super_width" +#define OSDOPTION_CHANGERES "changeres" +#define OSDOPTION_LOCK_SYSTEM_MODES "lock_system_modes" +#define OSDOPTION_LOCK_UNSUPPORTED_MODES "lock_unsupported_modes" +#define OSDOPTION_REFRESH_DONT_CARE "refresh_dont_care" +#define OSDOPTION_DOTCLOCK_MIN "dotclock_min" +#define OSDOPTION_V_SHIFT_CORRECT "v_shift_correct" +#define OSDOPTION_PIXEL_PRECISION "pixel_precision" +#define OSDOPTION_SYNC_REFRESH_TOLERANCE "sync_refresh_tolerance" +#define OSDOPTION_AUTOSYNC "autosync" +#define OSDOPTION_SCREEN_COMPOSITING "screen_compositing" +#define OSDOPTION_SCREEN_REORDERING "screen_reordering" +#define OSDOPTION_ALLOW_HW_REFRESH "allow_hw_refresh" +#define OSDOPTION_MODELINE "modeline" +#define OSDOPTION_PS_TIMING "ps_timing" +#define OSDOPTION_LCD_RANGE "lcd_range" +#define OSDOPTION_CRT_RANGE "crt_range" + + //============================================================ // TYPE DEFINITIONS //============================================================ @@ -123,7 +148,6 @@ class osd_options : public emu_options bool window() const { return bool_value(OSDOPTION_WINDOW); } bool maximize() const { return bool_value(OSDOPTION_MAXIMIZE); } bool wait_vsync() const { return bool_value(OSDOPTION_WAITVSYNC); } - bool sync_refresh() const { return bool_value(OSDOPTION_SYNCREFRESH); } // per-window options const char *screen() const { return value(OSDOPTION_SCREEN); } @@ -137,6 +161,29 @@ class osd_options : public emu_options // full screen options bool switch_res() const { return bool_value(OSDOPTION_SWITCHRES); } + const char *switchres_backend() const { return value(OSDOPTION_SWITCHRES_BACKEND); } + bool mode_setting() const { return bool_value(OSDOPTION_MODE_SETTING); } + bool modeline_generation() const { return bool_value(OSDOPTION_MODELINE_GENERATION); } + const char *monitor() const { return value(OSDOPTION_MONITOR); } + bool doublescan() const { return bool_value(OSDOPTION_DOUBLESCAN); } + bool interlace() const { return bool_value(OSDOPTION_INTERLACE); } + int super_width() const { return int_value(OSDOPTION_SUPER_WIDTH); } + bool changeres() const { return int_value(OSDOPTION_CHANGERES); } + bool lock_system_modes() const { return bool_value(OSDOPTION_LOCK_SYSTEM_MODES); } + bool lock_unsupported_modes() const { return bool_value(OSDOPTION_LOCK_UNSUPPORTED_MODES); } + bool refresh_dont_care() const { return bool_value(OSDOPTION_REFRESH_DONT_CARE); } + float dotclock_min() const { return float_value(OSDOPTION_DOTCLOCK_MIN); } + int v_shift_correct() const { return int_value(OSDOPTION_V_SHIFT_CORRECT); } + bool pixel_precision() const { return bool_value(OSDOPTION_PIXEL_PRECISION); } + float sync_refresh_tolerance() const { return float_value(OSDOPTION_SYNC_REFRESH_TOLERANCE); } + bool autosync() const { return bool_value(OSDOPTION_AUTOSYNC); } + bool screen_compositing() const { return bool_value(OSDOPTION_SCREEN_COMPOSITING); } + bool screen_reordering() const { return bool_value(OSDOPTION_SCREEN_REORDERING); } + bool allow_hw_refresh() const { return bool_value(OSDOPTION_ALLOW_HW_REFRESH); } + const char *modeline() const { return value(OSDOPTION_MODELINE); } + const char *ps_timing() const { return value(OSDOPTION_PS_TIMING); } + const char *lcd_range() const { return value(OSDOPTION_LCD_RANGE); } + const char *crt_range(int index) const { return value(string_format("%s%d", OSDOPTION_CRT_RANGE, index).c_str()); } // accelerated video options bool filter() const { return bool_value(OSDOPTION_FILTER); } @@ -219,11 +266,14 @@ class osd_common_t : public osd_interface, osd_output virtual std::unique_ptr create_midi_device() override { return m_midi->create_midi_device(); } + virtual const char *switchres_mode(int i) override { return m_switchres.display_mode_to_txt(i); } + // FIXME: everything below seems to be osd specific and not part of // this INTERFACE but part of the osd IMPLEMENTATION // getters running_machine &machine() const { assert(m_machine != nullptr); return *m_machine; } + switchres_module *switchres() { return &m_switchres; } virtual void debugger_update(); @@ -304,6 +354,7 @@ class osd_common_t : public osd_interface, osd_output monitor_module* m_monitor_module; std::unique_ptr m_watchdog; std::vector m_sliders; + switchres_module m_switchres; private: std::vector m_video_names; diff --git a/src/osd/modules/monitor/monitor_module.h b/src/osd/modules/monitor/monitor_module.h index 3244e7fd42b0d..583f47d4b349f 100644 --- a/src/osd/modules/monitor/monitor_module.h +++ b/src/osd/modules/monitor/monitor_module.h @@ -53,7 +53,7 @@ class osd_monitor_info float aspect() const { return m_aspect; } float pixel_aspect() const { return m_aspect / (float(m_pos_size.width()) / float(m_pos_size.height())); } - void update_resolution(const int new_width, const int new_height) const { m_pos_size.resize(new_width, new_height); } + void update_resolution(const int new_width, const int new_height) { m_pos_size = m_pos_size.resize(new_width, new_height); } void set_aspect(const float a) { m_aspect = a; } bool is_primary() const { return m_is_primary; } diff --git a/src/osd/modules/osdwindow.h b/src/osd/modules/osdwindow.h index 4b74d504a1cc7..2dc5353675d54 100644 --- a/src/osd/modules/osdwindow.h +++ b/src/osd/modules/osdwindow.h @@ -230,6 +230,7 @@ class osd_renderer virtual void record() { }; virtual void toggle_fsfx() { }; virtual bool sliders_dirty() { return m_sliders_dirty; } + virtual int restart() { return 0; } static std::unique_ptr make_for_type(int mode, std::shared_ptr window, int extra_flags = FLAG_NONE); @@ -273,8 +274,8 @@ struct osd_video_config // hardware options int mode; // output mode int waitvsync; // spin until vsync - int syncrefresh; // sync only to refresh rate int switchres; // switch resolutions + int framedelay; // frame delay // d3d, accel, opengl int filter; // enable filtering @@ -292,9 +293,6 @@ struct osd_video_config int allowtexturerect; // allow GL_ARB_texture_rectangle, default: no int forcepow2texture; // force power of two textures, default: no - // dd, d3d - int triplebuf; // triple buffer - //============================================================ // SDL - options //============================================================ diff --git a/src/osd/modules/render/d3d/d3dhlsl.h b/src/osd/modules/render/d3d/d3dhlsl.h index 5a34d9830df2e..c0778af0bac70 100644 --- a/src/osd/modules/render/d3d/d3dhlsl.h +++ b/src/osd/modules/render/d3d/d3dhlsl.h @@ -302,7 +302,7 @@ class shaders bool init(d3d_base *d3dintf, running_machine *machine, renderer_d3d9 *renderer); - bool enabled() { return post_fx_enable && d3dintf->post_fx_available; } + bool enabled() { return (this != nullptr) && post_fx_enable && d3dintf->post_fx_available; } void toggle() { post_fx_enable = initialized && !post_fx_enable; } void begin_draw(); diff --git a/src/osd/modules/render/drawd3d.cpp b/src/osd/modules/render/drawd3d.cpp index a279034ef7dbb..d1e85d680628a 100644 --- a/src/osd/modules/render/drawd3d.cpp +++ b/src/osd/modules/render/drawd3d.cpp @@ -21,12 +21,13 @@ #include "modules/render/d3d/d3dhlsl.h" #include "modules/monitor/monitor_module.h" #include +#include //============================================================ // TYPE DEFINITIONS //============================================================ -typedef IDirect3D9* (WINAPI *d3d9_create_fn)(UINT); +typedef IDirect3D9Ex* (WINAPI *d3d9_create_fn)(UINT, IDirect3D9Ex **); //============================================================ @@ -206,25 +207,25 @@ bool renderer_d3d9::init(running_machine &machine) d3dintf->d3d9_dll = osd::dynamic_module::open({ "d3d9.dll" }); - d3d9_create_fn d3d9_create_ptr = d3dintf->d3d9_dll->bind("Direct3DCreate9"); + d3d9_create_fn d3d9_create_ptr = d3dintf->d3d9_dll->bind("Direct3DCreate9Ex"); if (d3d9_create_ptr == nullptr) { delete d3dintf; d3dintf = nullptr; - osd_printf_verbose("Direct3D: Unable to find Direct3D 9 runtime library\n"); + osd_printf_verbose("Direct3D: Unable to find Direct3D 9ex runtime library\n"); return true; } - d3dintf->d3dobj = (*d3d9_create_ptr)(D3D_SDK_VERSION); + (*d3d9_create_ptr)(D3D_SDK_VERSION, (IDirect3D9Ex**) &d3dintf->d3dobj); if (d3dintf->d3dobj == nullptr) { delete d3dintf; d3dintf = nullptr; - osd_printf_verbose("Direct3D: Unable to initialize Direct3D 9\n"); + osd_printf_verbose("Direct3D: Unable to initialize Direct3D 9ex\n"); return true; } - osd_printf_verbose("Direct3D: Using Direct3D 9\n"); + osd_printf_verbose("Direct3D: Using Direct3D 9Ex\n"); return false; } @@ -504,7 +505,7 @@ texture_info *d3d_texture_manager::find_texinfo(const render_texinfo *texinfo, u } renderer_d3d9::renderer_d3d9(std::shared_ptr window) - : osd_renderer(window, FLAG_NONE), m_adapter(0), m_width(0), m_height(0), m_refresh(0), m_create_error_count(0), m_device(nullptr), m_gamma_supported(0), m_pixformat(), + : osd_renderer(window, FLAG_NONE), m_adapter(0), m_width(0), m_height(0), m_refresh(0), m_frame_delay(0), m_create_error_count(0), m_device(nullptr), m_gamma_supported(0), m_pixformat(), m_vertexbuf(nullptr), m_lockedbuf(nullptr), m_numverts(0), m_vectorbatch(nullptr), m_batchindex(0), m_numpolys(0), m_toggle(false), m_screen_format(), m_last_texture(nullptr), m_last_texture_flags(0), m_last_blendenable(0), m_last_blendop(0), m_last_blendsrc(0), m_last_blenddst(0), m_last_filter(0), m_last_wrap(), m_last_modmode(0), m_shaders(nullptr), m_texture_manager() @@ -727,12 +728,115 @@ void renderer_d3d9::end_frame() if (FAILED(result)) osd_printf_verbose("Direct3D: Error %08lX during device end_scene call\n", result); + if ((m_frame_delay != video_config.framedelay) || (m_vsync_offset != win->machine().options().vsync_offset())) + { + m_frame_delay = video_config.framedelay; + m_vsync_offset = win->machine().options().vsync_offset(); + update_break_scanlines(); + } + + D3DRASTER_STATUS raster_status; + memset (&raster_status, 0, sizeof(D3DRASTER_STATUS)); + + // sync to VBLANK-BEGIN + if (video_config.framedelay && video_config.waitvsync) + { + // check if retrace has been missed + if (m_device->GetRasterStatus(0, &raster_status) == D3D_OK) + { + if (raster_status.ScanLine < m_delay_scanline && !raster_status.InVBlank) + { + static const double tps = (double)osd_ticks_per_second(); + static const double time_start = (double)osd_ticks() / tps; + osd_printf_verbose("renderer::end_frame(), probably missed retrace, entered at scanline %d, should break at %d, realtime is %f.\n", raster_status.ScanLine, m_break_scanline, (double)osd_ticks() / tps - time_start); + } + } + + do + { + if (m_device->GetRasterStatus(0, &raster_status) != D3D_OK) + break; + } while (!raster_status.InVBlank && raster_status.ScanLine < m_break_scanline); + } + // present the current buffers result = m_device->Present(nullptr, nullptr, nullptr, nullptr); if (FAILED(result)) osd_printf_verbose("Direct3D: Error %08lX during device present call\n", result); + + // sync to VBLANK-END + if (video_config.framedelay && video_config.waitvsync) + { + do + { + if (m_device->GetRasterStatus(0, &raster_status) != D3D_OK) + break; + } while (!raster_status.InVBlank); + } +} + +void renderer_d3d9::device_flush() +{ + HRESULT result; + + if(m_device) + { + if(m_query != nullptr) + { + m_query->Issue(D3DISSUE_END); + do + { + result = m_query->GetData(NULL, 0, D3DGETDATA_FLUSH); + if (result == D3DERR_DEVICELOST) + return; + } while(result == S_FALSE); + } + } +} + +void renderer_d3d9::update_break_scanlines() +{ + auto win = assert_window(); + modeline *m_switchres_mode = downcast(win->machine().osd()).switchres()->switchres().display(win->index())->best_mode(); + + switch (m_vendor_id) + { + case 0x1002: // ATI + m_first_scanline = m_switchres_mode && m_switchres_mode->vtotal ? + (m_switchres_mode->vtotal - m_switchres_mode->vbegin) / (m_switchres_mode->interlace ? 2 : 1) : + 1; + + m_last_scanline = m_switchres_mode && m_switchres_mode->vtotal ? + m_switchres_mode->vactive + (m_switchres_mode->vtotal - m_switchres_mode->vbegin) / (m_switchres_mode->interlace ? 2 : 1) : + m_height; + break; + + case 0x8086: // Intel + m_first_scanline = 1; + + m_last_scanline = m_switchres_mode && m_switchres_mode->vtotal ? + m_switchres_mode->vactive / (m_switchres_mode->interlace ? 2 : 1) : + m_height; + break; + + default: // NVIDIA (0x10DE) + others (?) + m_first_scanline = 0; + + m_last_scanline = m_switchres_mode && m_switchres_mode->vtotal ? + (m_switchres_mode->vactive - 1) / (m_switchres_mode->interlace ? 2 : 1) : + m_height - 1; + break; + } + + //auto win = assert_window(); + m_break_scanline = m_last_scanline - m_vsync_offset; + m_break_scanline = m_break_scanline > m_first_scanline ? m_break_scanline : m_last_scanline; + m_delay_scanline = m_first_scanline + m_height * (float)video_config.framedelay / (10 * m_switchres_mode->result.v_scale); + + osd_printf_verbose("Direct3D: Frame delay: %d, First scanline: %d, Last scanline: %d, Break scanline: %d, Delay scanline: %d\n", video_config.framedelay, m_first_scanline, m_last_scanline, m_break_scanline, m_delay_scanline); } + void renderer_d3d9::update_presentation_parameters() { auto win = assert_window(); @@ -741,21 +845,16 @@ void renderer_d3d9::update_presentation_parameters() m_presentation.BackBufferWidth = m_width; m_presentation.BackBufferHeight = m_height; m_presentation.BackBufferFormat = m_pixformat; - m_presentation.BackBufferCount = video_config.triplebuf ? 2 : 1; + m_presentation.BackBufferCount = 1; m_presentation.MultiSampleType = D3DMULTISAMPLE_NONE; m_presentation.SwapEffect = D3DSWAPEFFECT_DISCARD; m_presentation.hDeviceWindow = std::static_pointer_cast(win)->platform_window(); m_presentation.Windowed = !win->fullscreen() || win->win_has_menu(); m_presentation.EnableAutoDepthStencil = FALSE; m_presentation.AutoDepthStencilFormat = D3DFMT_D16; - m_presentation.Flags = 0; - m_presentation.FullScreen_RefreshRateInHz = m_refresh; - m_presentation.PresentationInterval = ( - (video_config.triplebuf && win->fullscreen()) - || video_config.waitvsync - || video_config.syncrefresh) - ? D3DPRESENT_INTERVAL_ONE - : D3DPRESENT_INTERVAL_IMMEDIATE; + m_presentation.Flags = D3DPRESENTFLAG_UNPRUNEDMODE; + m_presentation.FullScreen_RefreshRateInHz = win->fullscreen()?m_refresh : 0; + m_presentation.PresentationInterval = video_config.waitvsync && video_config.framedelay == 0? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; } @@ -833,9 +932,12 @@ int renderer_d3d9::device_create(HWND hwnd) // initialize the D3D presentation parameters update_presentation_parameters(); + auto win = assert_window(); + D3DDISPLAYMODEEX *display_mode = win->fullscreen()? &m_display_mode : nullptr; + // create the D3D device - result = d3dintf->d3dobj->CreateDevice( - m_adapter, D3DDEVTYPE_HAL, device_hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &m_presentation, &m_device); + result = d3dintf->d3dobj->CreateDeviceEx( + m_adapter, D3DDEVTYPE_HAL, device_hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &m_presentation, display_mode, &m_device); if (FAILED(result)) { // if we got a "DEVICELOST" error, it may be transitory; count it and only fail if @@ -856,6 +958,10 @@ int renderer_d3d9::device_create(HWND hwnd) m_create_error_count = 0; osd_printf_verbose("Direct3D: Device created at %dx%d\n", m_width, m_height); + result = m_device->SetMaximumFrameLatency(1); + if (FAILED(result)) + osd_printf_error("Unable to set Direct3DEx device maximum frame latency\n"); + update_gamma_ramp(); return device_create_resources(); @@ -1161,6 +1267,40 @@ int renderer_d3d9::device_test_cooperative() } +//============================================================ +// restart +//============================================================ + +int renderer_d3d9::restart() +{ + // free all existing resources + if (m_shaders->enabled()) device_delete_resources(); + + // configure new video mode + pick_best_mode(); + update_presentation_parameters(); + + if (m_frame_delay) + update_break_scanlines(); + + auto win = assert_window(); + D3DDISPLAYMODEEX *display_mode = win->fullscreen()? &m_display_mode : nullptr; + + // reset the device + HRESULT result = m_device->ResetEx(&m_presentation, display_mode); + if (FAILED(result)) + { + osd_printf_error("Unable to reset, result %08lX\n", result); + return 1; + } + + // create the resources again + if (m_shaders->enabled()) device_create_resources(); + + return 0; +} + + //============================================================ // config_adapter_mode //============================================================ @@ -1180,9 +1320,13 @@ int renderer_d3d9::config_adapter_mode() } osd_printf_verbose("Direct3D: Configuring adapter #%d = %s\n", m_adapter, id.Description); + osd_printf_verbose("Direct3D: Adapter has Vendor ID: %lX and Device ID: %lX\n", id.VendorId, id.DeviceId); + + m_vendor_id = id.VendorId; // get the current display mode - result = d3dintf->d3dobj->GetAdapterDisplayMode(m_adapter, &m_origmode); + m_origmode.Size = sizeof(D3DDISPLAYMODEEX); + result = d3dintf->d3dobj->GetAdapterDisplayModeEx(m_adapter, &m_origmode, 0); if (FAILED(result)) { osd_printf_error("Error getting mode for adapter #%d\n", m_adapter); @@ -1196,6 +1340,9 @@ int renderer_d3d9::config_adapter_mode() { RECT client; + // Use current desktop mode + m_display_mode = m_origmode; + // bounds are from the window client rect GetClientRectExceptMenu(std::static_pointer_cast(win)->platform_window(), &client, win->fullscreen()); m_width = client.right - client.left; @@ -1203,7 +1350,7 @@ int renderer_d3d9::config_adapter_mode() // pix format is from the current mode m_pixformat = m_origmode.Format; - m_refresh = 0; + m_refresh = m_origmode.RefreshRate; // make sure it's a pixel format we can get behind if (m_pixformat != D3DFMT_X1R5G5B5 && m_pixformat != D3DFMT_R5G6B5 && m_pixformat != D3DFMT_X8R8G8B8) @@ -1279,6 +1426,23 @@ void renderer_d3d9::pick_best_mode() auto win = assert_window(); + modeline *m_switchres_mode = downcast(win->machine().osd()).switchres()->switchres().display(win->index())->best_mode(); + if (m_switchres_mode) + { + m_width = m_switchres_mode->type & MODE_ROTATED? m_switchres_mode->height : m_switchres_mode->width; + m_height = m_switchres_mode->type & MODE_ROTATED? m_switchres_mode->width : m_switchres_mode->height; + m_refresh = (int)m_switchres_mode->refresh; + m_interlace = m_switchres_mode->interlace; + + m_display_mode.Size = sizeof(D3DDISPLAYMODEEX); + m_display_mode.Width = m_width; + m_display_mode.Height = m_height; + m_display_mode.RefreshRate = m_refresh; + m_display_mode.Format = m_pixformat; + m_display_mode.ScanLineOrdering = m_interlace? D3DSCANLINEORDERING_INTERLACED : D3DSCANLINEORDERING_PROGRESSIVE; + return; + } + // determine the refresh rate of the primary screen const screen_device *primary_screen = screen_device_enumerator(win->machine().root_device()).first(); if (primary_screen != nullptr) @@ -1303,9 +1467,16 @@ void renderer_d3d9::pick_best_mode() osd_printf_verbose("Direct3D: Selecting video mode...\n"); for (int modenum = 0; modenum < maxmodes; modenum++) { + // allow all modes + D3DDISPLAYMODEFILTER filter; + memset (&filter, 0, sizeof(filter)); + filter.Size = sizeof(D3DDISPLAYMODEFILTER); + filter.Format = D3DFMT_X8R8G8B8; + // check this mode - D3DDISPLAYMODE mode; - HRESULT result = d3dintf->d3dobj->EnumAdapterModes(m_adapter, D3DFMT_X8R8G8B8, modenum, &mode); + D3DDISPLAYMODEEX mode; + mode.Size = sizeof(mode); + HRESULT result = d3dintf->d3dobj->EnumAdapterModesEx(m_adapter, &filter, modenum, &mode); if (FAILED(result)) break; @@ -1351,6 +1522,7 @@ void renderer_d3d9::pick_best_mode() m_height = mode.Height; m_pixformat = mode.Format; m_refresh = mode.RefreshRate; + m_display_mode = mode; } } osd_printf_verbose("Direct3D: Mode selected = %4dx%4d@%3dHz\n", m_width, m_height, m_refresh); @@ -1959,7 +2131,7 @@ texture_info::texture_info(d3d_texture_manager *manager, const render_texinfo* t if (!PRIMFLAG_GET_SCREENTEX(flags)) { assert(PRIMFLAG_TEXFORMAT(flags) != TEXFORMAT_YUY16); - result = m_renderer->get_device()->CreateTexture(m_rawdims.c.x, m_rawdims.c.y, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &m_d3dtex, nullptr); + result = m_renderer->get_device()->CreateTexture(m_rawdims.c.x, m_rawdims.c.y, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &m_d3dtex, nullptr); if (FAILED(result)) goto error; m_d3dfinaltex = m_d3dtex; diff --git a/src/osd/modules/render/drawd3d.h b/src/osd/modules/render/drawd3d.h index 2fa4a30873c92..ec47503ed0911 100644 --- a/src/osd/modules/render/drawd3d.h +++ b/src/osd/modules/render/drawd3d.h @@ -39,7 +39,7 @@ struct d3d_base { // internal objects - IDirect3D9 *d3dobj; + IDirect3D9Ex *d3dobj; bool post_fx_available; osd::dynamic_module::ptr d3d9_dll; @@ -67,6 +67,7 @@ class renderer_d3d9 : public osd_renderer, public slider_dirty_notifier virtual void add_audio_to_recording(const int16_t *buffer, int samples_this_frame) override; virtual std::vector get_slider_list() override; virtual void set_sliders_dirty() override; + virtual int restart() override; int initialize(); @@ -74,6 +75,8 @@ class renderer_d3d9 : public osd_renderer, public slider_dirty_notifier int device_create_resources(); void device_delete(); void device_delete_resources(); + void device_flush(); + void update_break_scanlines(); void update_presentation_parameters(); void update_gamma_ramp(); @@ -114,7 +117,7 @@ class renderer_d3d9 : public osd_renderer, public slider_dirty_notifier int get_height() const { return m_height; } int get_refresh() const { return m_refresh; } - IDirect3DDevice9 * get_device() const { return m_device; } + IDirect3DDevice9Ex * get_device() const { return m_device; } D3DPRESENT_PARAMETERS * get_presentation() { return &m_presentation; } IDirect3DVertexBuffer9 *get_vertex_buffer() const { return m_vertexbuf; } @@ -123,7 +126,7 @@ class renderer_d3d9 : public osd_renderer, public slider_dirty_notifier D3DFORMAT get_screen_format() const { return m_screen_format; } D3DFORMAT get_pixel_format() const { return m_pixformat; } - D3DDISPLAYMODE get_origmode() const { return m_origmode; } + D3DDISPLAYMODEEX get_origmode() const { return m_origmode; } uint32_t get_last_texture_flags() const { return m_last_texture_flags; } @@ -134,16 +137,26 @@ class renderer_d3d9 : public osd_renderer, public slider_dirty_notifier private: int m_adapter; // ordinal adapter number + int m_vendor_id; // adapter vendor id int m_width; // current width int m_height; // current height int m_refresh; // current refresh rate + bool m_interlace; // current interlace + int m_frame_delay; // current frame delay value + int m_vsync_offset; // current vsync_offset value + int m_first_scanline; // first scanline number (visible) + int m_last_scanline; // last scanline number (visible) + int m_delay_scanline; // scanline number supposed to be after frame delay + int m_break_scanline; // break scanline number, for vsync offset int m_create_error_count; // number of consecutive create errors - IDirect3DDevice9 * m_device; // pointer to the Direct3DDevice object + IDirect3DDevice9Ex * m_device; // pointer to the Direct3DDevice object int m_gamma_supported; // is full screen gamma supported? D3DPRESENT_PARAMETERS m_presentation; // set of presentation parameters - D3DDISPLAYMODE m_origmode; // original display mode for the adapter + D3DDISPLAYMODEEX m_origmode; // original display mode for the adapter + D3DDISPLAYMODEEX m_display_mode; // full screen display mode D3DFORMAT m_pixformat; // pixel format we are using + IDirect3DQuery9 * m_query; IDirect3DVertexBuffer9 *m_vertexbuf; // pointer to the vertex buffer object vertex * m_lockedbuf; // pointer to the locked vertex buffer diff --git a/src/osd/modules/render/drawogl.cpp b/src/osd/modules/render/drawogl.cpp index 6b495dae3bbed..ab3be09343a85 100644 --- a/src/osd/modules/render/drawogl.cpp +++ b/src/osd/modules/render/drawogl.cpp @@ -40,6 +40,13 @@ #include "modules/opengl/gl_shader_tool.h" #include "modules/opengl/gl_shader_mgr.h" +#ifdef SDLMAME_X11 +// DRM +#include +#include +#include +#endif + #if defined(SDLMAME_MACOSX) || defined(OSD_MAC) #include #include @@ -245,6 +252,10 @@ void renderer_ogl::set_blendmode(int blendmode) // STATIC VARIABLES //============================================================ +#ifdef SDLMAME_X11 +static int drawogl_drm_open(void); +#endif + // OGL 1.3 #if defined(GL_ARB_multitexture) && !defined(OSD_MAC) static PFNGLACTIVETEXTUREARBPROC pfn_glActiveTexture = nullptr; @@ -578,7 +589,12 @@ int renderer_ogl::create() osd_printf_error("%s\n", m_gl_context->LastErrorMsg()); return 1; } - m_gl_context->SetSwapInterval(video_config.waitvsync ? 1 : 0); +#ifdef SDLMAME_X11 + // Try to open DRM device + if (win->index() == 0) + m_fd = drawogl_drm_open(); +#endif + m_gl_context->SetSwapInterval((video_config.waitvsync && m_fd == 0) ? 1 : 0); m_blittimer = 0; @@ -605,6 +621,26 @@ int renderer_ogl::create() return 0; } +#ifdef SDLMAME_X11 +//============================================================ +// drawogl_drm_open +//============================================================ + +static int drawogl_drm_open(void) +{ + int fd = 0; + const char *node = {"/dev/dri/card0"}; + + fd = open(node, O_RDWR | O_CLOEXEC); + if (fd < 0) + { + fprintf(stderr, "cannot open %s\n", node); + return 0; + } + osd_printf_verbose("%s successfully opened\n", node); + return fd; +} +#endif //============================================================ // drawsdl_xy_to_render_target @@ -1419,6 +1455,48 @@ int renderer_ogl::draw(const int update) win->m_primlist->release_lock(); m_init_context = 0; +#ifdef SDLMAME_X11 + + // wait for vertical retrace + if (video_config.waitvsync && m_fd) + { + drmVBlank vbl; + memset(&vbl, 0, sizeof(vbl)); + vbl.request.sequence = 1; + + // handle vblank for all SR managed crtc + // this is a hack based on SDL reported screen index + // it won't work on multi-gpu + // TO DO: find a correct way to map screen to crtc + int crtc = win->monitor()->oshandle(); + + // single screen (default) + vbl.request.type = DRM_VBLANK_RELATIVE; + + // two screens + if (crtc == 1) vbl.request.type = drmVBlankSeqType(DRM_VBLANK_RELATIVE | DRM_VBLANK_SECONDARY); + + // multi-screen + else if (crtc > 1) + { + static uint64_t caps; + static bool caps_checked = false; + + if (!caps_checked) + { + caps_checked = true; + if (drmGetCap(m_fd, DRM_CAP_VBLANK_HIGH_CRTC, &caps)) + osd_printf_error("A newer kernel is needed for vblank syncing on multi screen\n"); + } + if (caps) + vbl.request.type = drmVBlankSeqType(DRM_VBLANK_RELATIVE | ((crtc << DRM_VBLANK_HIGH_CRTC_SHIFT) & DRM_VBLANK_HIGH_CRTC_MASK)); + } + + if (drmWaitVBlank(m_fd, &vbl) != 0) + osd_printf_verbose("drmWaitVBlank failed\n"); + } +#endif + m_gl_context->SwapBuffer(); return 0; diff --git a/src/osd/modules/render/drawogl.h b/src/osd/modules/render/drawogl.h index 891c5cec814e2..fff14380b5800 100644 --- a/src/osd/modules/render/drawogl.h +++ b/src/osd/modules/render/drawogl.h @@ -124,6 +124,7 @@ class renderer_ogl : public osd_renderer , m_last_vofs(0.0f) , m_surf_w(0) , m_surf_h(0) + , m_fd(0) { for (int i=0; i < HASH_SIZE + OVERFLOW_SIZE; i++) m_texhash[i] = nullptr; @@ -236,6 +237,8 @@ class renderer_ogl : public osd_renderer static bool s_shown_video_info; static bool s_dll_loaded; + // DRM file handle + int m_fd; }; #endif // MAME_OSD_MODULES_RENDER_DRAWOGL_H diff --git a/src/osd/modules/switchres/switchres_module.cpp b/src/osd/modules/switchres/switchres_module.cpp new file mode 100644 index 0000000000000..4e8f77a0b97fa --- /dev/null +++ b/src/osd/modules/switchres/switchres_module.cpp @@ -0,0 +1,338 @@ +/************************************************************** + + switchres_module.cpp - Switchres MAME module + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +// MAME headers +#include "emu.h" + +// MAMEOS headers +#if defined(OSD_WINDOWS) +#include "winmain.h" +#elif defined(OSD_SDL) +#include "osdsdl.h" +#endif + +#include "modules/osdwindow.h" +#include +#include "switchres_module.h" + + +//============================================================ +// logging wrappers +//============================================================ + +static void sr_printf_verbose(const char *format, ...) +{ + char buffer[1024]; + va_list args; + va_start(args, format); + vsprintf(buffer, format, args); + osd_vprintf_verbose(util::make_format_argument_pack(std::forward(buffer))); + va_end(args); +} + +static void sr_printf_info(const char *format, ...) +{ + char buffer[1024]; + va_list args; + va_start(args, format); + vsprintf(buffer, format, args); + osd_vprintf_info(util::make_format_argument_pack(std::forward(buffer))); + va_end(args); +} + +static void sr_printf_error(const char *format, ...) +{ + char buffer[1024]; + va_list args; + va_start(args, format); + vsprintf(buffer, format, args); + osd_vprintf_error(util::make_format_argument_pack(std::forward(buffer))); + va_end(args); +} + +//============================================================ +// switchres_module::init +//============================================================ + +void switchres_module::init(running_machine &machine) +{ + m_machine = &machine; + m_switchres = new switchres_manager; + + // Set logging functions + if (machine.options().verbose()) switchres().set_log_verbose_fn((void *)sr_printf_verbose); + switchres().set_log_info_fn((void *)sr_printf_info); + switchres().set_log_error_fn((void *)sr_printf_error); +} + +//============================================================ +// switchres_module::exit +//============================================================ + +void switchres_module::exit() +{ + osd_printf_verbose("Switchres: exit\n"); + if (m_switchres) delete m_switchres; + m_switchres = 0; +} + +//============================================================ +// switchres_module::add_display +//============================================================ + +display_manager* switchres_module::add_display(int index, osd_monitor_info *monitor, render_target *target, osd_window_config *config) +{ + #if defined(OSD_WINDOWS) + windows_options &options = downcast(machine().options()); + #elif defined(OSD_SDL) + sdl_options &options = downcast(machine().options()); + #endif + + switchres().set_screen(monitor->devicename().c_str()); + switchres().set_monitor(options.monitor()); + switchres().set_modeline(options.modeline()); + for (int i = 0; i < MAX_RANGES; i++) switchres().set_crt_range(i, options.crt_range(i)); + switchres().set_lcd_range(options.lcd_range()); + switchres().set_modeline_generation(options.modeline_generation()); + switchres().set_lock_unsupported_modes(options.lock_unsupported_modes()); + switchres().set_lock_system_modes(options.lock_system_modes()); + switchres().set_refresh_dont_care(options.refresh_dont_care()); + + switchres().set_interlace(options.interlace()); + switchres().set_doublescan(options.doublescan()); + switchres().set_dotclock_min(options.dotclock_min()); + switchres().set_refresh_tolerance(options.sync_refresh_tolerance()); + switchres().set_super_width(options.super_width()); + switchres().set_v_shift_correct(options.v_shift_correct()); + + switchres().set_api(options.switchres_backend()); + switchres().set_screen_compositing(options.screen_compositing()); + switchres().set_screen_reordering(options.screen_reordering()); + switchres().set_allow_hardware_refresh(options.allow_hw_refresh()); + + modeline user_mode = {}; + user_mode.width = config->width; + user_mode.height = config->height; + user_mode.refresh = config->refresh; + + display_manager *display = switchres().add_display(); + display->set_user_mode(&user_mode); + display->init(); + display->set_monitor_aspect(display->desktop_is_rotated()? 1.0f / monitor->aspect() : monitor->aspect()); + + get_game_info(display, target); + + osd_printf_verbose("Switchres: get_mode(%d) %d %d %f %f\n", index, width(index), height(index), refresh(index), display->monitor_aspect()); + display->get_mode(width(index), height(index), refresh(index), 0); + if (display->got_mode()) set_mode(index, monitor, target, config); + + m_num_screens ++; + return display; +} + + +//============================================================ +// switchres_module::get_game_info +//============================================================ + +void switchres_module::get_game_info(display_manager* display, render_target *target) +{ + display->set_rotation(effective_orientation(display, target)); + + int minwidth, minheight; + target->compute_minimum_size(minwidth, minheight); + + if (display->rotation() ^ display->desktop_is_rotated()) std::swap(minwidth, minheight); + set_width(display->index(), minwidth); + set_height(display->index(), minheight); + + // determine the refresh rate of the primary screen + const screen_device *primary_screen = screen_device_enumerator(machine().root_device()).first(); + if (primary_screen != nullptr) set_refresh(display->index(), primary_screen->frame_number() == 0? ATTOSECONDS_TO_HZ(primary_screen->refresh_attoseconds()) : primary_screen->frame_period().as_hz()); + //if (primary_screen != nullptr) set_refresh(display->index(), primary_screen->frame_period().as_hz()); +} + +//============================================================ +// switchres_module::effective_orientation +//============================================================ + +bool switchres_module::effective_orientation(display_manager* display, render_target *target) +{ + + bool target_is_rotated = (target->orientation() & machine_flags::MASK_ORIENTATION) & ORIENTATION_SWAP_XY? true:false; + bool game_is_rotated = (machine().system().flags & machine_flags::MASK_ORIENTATION) & ORIENTATION_SWAP_XY; + + return target_is_rotated ^ game_is_rotated ^ display->desktop_is_rotated(); +} + +//============================================================ +// switchres_module::check_resolution_change +//============================================================ + +bool switchres_module::check_resolution_change(int i, osd_monitor_info *monitor, render_target *target, osd_window_config *config) +{ + display_manager *display = switchres().display(i); + + int old_width = width(i); + int old_height = height(i); + double old_refresh = refresh(i); + bool old_rotation = display->rotation(); + + get_game_info(display, target); + + if (old_width != width(i) || old_height != height(i) || old_refresh != refresh(i) || old_rotation != display->rotation()) + { + osd_printf_verbose("Switchres: Resolution change from %dx%d@%f %s to %dx%d@%f %s\n", + old_width, old_height, old_refresh, old_rotation?"rotated":"normal", width(i), height(i), refresh(i), display->rotation()?"rotated":"normal"); + + display->get_mode(width(i), height(i), refresh(i), 0); + + if (display->got_mode()) + { + if (display->is_switching_required()) + { + set_mode(i, monitor, target, config); + return true; + } + + set_options(display, target); + } + } + + return false; +} + +//============================================================ +// switchres_module::set_mode +//============================================================ + +bool switchres_module::set_mode(int i, osd_monitor_info *monitor, render_target *target, osd_window_config *config) +{ + #if defined(OSD_WINDOWS) + windows_options &options = downcast(machine().options()); + #elif defined(OSD_SDL) + sdl_options &options = downcast(machine().options()); + #endif + + display_manager *display = switchres().display(i); + + if (display->got_mode()) + { + if (display->is_mode_updated()) display->update_mode(display->best_mode()); + + else if (display->is_mode_new()) display->add_mode(display->best_mode()); + + config->width = display->width(); + config->height = display->height(); + config->refresh = display->refresh(); + + if (options.mode_setting()) + { + display->set_mode(display->best_mode()); + monitor->refresh(); + monitor->update_resolution(display->width(), display->height()); + } + + set_options(display, target); + + return true; + } + + return false; +} + +//============================================================ +// switchres_module::set_options +//============================================================ + +void switchres_module::set_options(display_manager* display, render_target *target) +{ + #if defined(OSD_WINDOWS) + windows_options &options = downcast(machine().options()); + #elif defined(OSD_SDL) + sdl_options &options = downcast(machine().options()); + #endif + + // Set scaling/stretching options + set_option(OPTION_KEEPASPECT, true); + set_option(OPTION_UNEVENSTRETCH, display->is_stretched()); + set_option(OPTION_UNEVENSTRETCHX, (!(display->is_stretched()) && (display->width() >= display->super_width()))); + + // Update target if it's already initialized + if (target) + { + if (options.uneven_stretch()) + target->set_scale_mode(SCALE_FRACTIONAL); + else if(options.uneven_stretch_x()) + target->set_scale_mode(SCALE_FRACTIONAL_X); + else if(options.uneven_stretch_y()) + target->set_scale_mode(SCALE_FRACTIONAL_Y); + else + target->set_scale_mode(SCALE_INTEGER); + } + + // Set MAME OSD specific options + + // Vertical synchronization management (autosync) + // Disable -syncrefresh if our vfreq is scaled or out of syncrefresh_tolerance + bool sync_refresh_effective = (options.black_frame_insertion() > 0) || !((display->is_refresh_off()) || display->v_scale() > 1); + set_option(OSDOPTION_WAITVSYNC, options.autosync()? sync_refresh_effective : options.wait_vsync()); + set_option(OPTION_SYNCREFRESH, options.autosync()? sync_refresh_effective : options.sync_refresh()); + + #if defined(OSD_WINDOWS) + downcast(machine().osd()).extract_video_config(); + #elif defined(OSD_SDL) + downcast(machine().osd()).extract_video_config(); + #endif +} + +//============================================================ +// switchres_module::set_option - option setting wrapper +//============================================================ + +void switchres_module::set_option(const char *option_ID, bool state) +{ + #if defined(OSD_WINDOWS) + windows_options &options = downcast(machine().options()); + #elif defined(OSD_SDL) + sdl_options &options = downcast(machine().options()); + #endif + + //options.set_value(option_ID, state, OPTION_PRIORITY_SWITCHRES); + options.set_value(option_ID, state, OPTION_PRIORITY_NORMAL+1); + osd_printf_verbose("SwitchRes: Setting option -%s%s\n", options.bool_value(option_ID)?"":"no", option_ID); +} + +//============================================================ +// switchres_module::mode_to_txt +//============================================================ + +const char *switchres_module::display_mode_to_txt(int i) +{ + if (!downcast(machine().options()).switch_res()) + return "Switchres is disabled\n"; + + display_manager *display = switchres().display(i); + + if (display == nullptr) + sprintf(m_mode_txt, "SR(%d): no physical display\n", i); + + else if (display->got_mode()) + sprintf(m_mode_txt, "SR(%d): %d x %d%s%s %2.3f Hz %2.3f kHz\n", + i, display->width(), display->height(), display->is_interlaced()?"i":"p", display->is_doublescanned()?"d":"", display->v_freq(), display->h_freq()/1000); + else + sprintf(m_mode_txt, "SR(%d): could not find a video mode\n", i); + + return m_mode_txt; +} diff --git a/src/osd/modules/switchres/switchres_module.h b/src/osd/modules/switchres/switchres_module.h new file mode 100644 index 0000000000000..fc209b7d0c255 --- /dev/null +++ b/src/osd/modules/switchres/switchres_module.h @@ -0,0 +1,67 @@ +/************************************************************** + + switchres_module.h - Switchres MAME module + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2020 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef SWITCHRES_MODULE_H_ +#define SWITCHRES_MODULE_H_ + +#define MAX_WINDOWS 4 + +class osd_monitor_info; +class osd_window_config; +class switchres_manager; +class display_manager; + +class switchres_module +{ +public: + switchres_module() {}; + ~switchres_module() {}; + + // getters + running_machine &machine() const { assert(m_machine != nullptr); return *m_machine; } + switchres_manager &switchres() const { assert(m_switchres != nullptr); return *m_switchres; } + int width(int i) { return m_width[i]; } + int height(int i) { return m_height[i]; } + double refresh(int i) { return m_refresh[i]; } + + // setters + void set_width(int i, int width) { m_width[i] = width; } + void set_height(int i, double height) { m_height[i] = height; } + void set_refresh(int i, double refresh) { m_refresh[i] = refresh; } + + // interface + void init(running_machine &machine); + void exit(); + display_manager* add_display(int index, osd_monitor_info *monitor, render_target *target, osd_window_config *config); + void get_game_info(display_manager* display, render_target *target); + bool effective_orientation(display_manager* display, render_target *target); + bool check_resolution_change(int i, osd_monitor_info *monitor, render_target *target, osd_window_config *config); + bool set_mode(int i, osd_monitor_info *monitor, render_target *target, osd_window_config *config); + void set_options(display_manager* display, render_target *target); + void set_option(const char *option_ID, bool state); + const char* display_mode_to_txt(int i); + +private: + switchres_manager* m_switchres; + running_machine* m_machine; + + int m_num_screens = 0; + + int m_width[MAX_WINDOWS]; + int m_height[MAX_WINDOWS]; + double m_refresh[MAX_WINDOWS]; + char m_mode_txt[256] = {}; +}; + +#endif diff --git a/src/osd/osdcore.cpp b/src/osd/osdcore.cpp index fd696706e6b00..fee08261a4fd4 100644 --- a/src/osd/osdcore.cpp +++ b/src/osd/osdcore.cpp @@ -133,13 +133,20 @@ void osd_vprintf_debug(util::format_argument_pack const &args) } +#ifdef OSD_WINDOWS + typedef std::chrono::steady_clock s_clock; +#else + typedef std::chrono::high_resolution_clock s_clock; +#endif + + //============================================================ // osd_ticks //============================================================ osd_ticks_t osd_ticks() { - return std::chrono::high_resolution_clock::now().time_since_epoch().count(); + return s_clock::now().time_since_epoch().count(); } @@ -149,7 +156,7 @@ osd_ticks_t osd_ticks() osd_ticks_t osd_ticks_per_second() { - return std::chrono::high_resolution_clock::period::den / std::chrono::high_resolution_clock::period::num; + return s_clock::period::den / s_clock::period::num; } //============================================================ @@ -162,7 +169,7 @@ void osd_sleep(osd_ticks_t duration) // sleep_for appears to oversleep on Windows with gcc 8 Sleep(duration / (osd_ticks_per_second() / 1000)); #else - std::this_thread::sleep_for(std::chrono::high_resolution_clock::duration(duration)); + std::this_thread::sleep_for(s_clock::duration(duration)); #endif } diff --git a/src/osd/osdepend.h b/src/osd/osdepend.h index cd00244f5bd96..455d9f78a29ac 100644 --- a/src/osd/osdepend.h +++ b/src/osd/osdepend.h @@ -92,6 +92,9 @@ class osd_interface // midi interface virtual std::unique_ptr create_midi_device() = 0; + // switchres interface + virtual const char *switchres_mode(int i) = 0; + protected: virtual ~osd_interface() { } }; diff --git a/src/osd/sdl/osdsdl.h b/src/osd/sdl/osdsdl.h index 126b007d331e6..e47f49b126145 100644 --- a/src/osd/sdl/osdsdl.h +++ b/src/osd/sdl/osdsdl.h @@ -137,6 +137,8 @@ class sdl_osd_interface : public osd_common_t virtual void video_exit() override; virtual void window_exit() override; + void extract_video_config(); + // sdl specific void poll_inputs(running_machine &machine); void release_keys(); @@ -152,7 +154,6 @@ class sdl_osd_interface : public osd_common_t private: virtual void osd_exit() override; - void extract_video_config(); void output_oslog(const char *buffer); sdl_options &m_options; diff --git a/src/osd/sdl/video.cpp b/src/osd/sdl/video.cpp index 9d9e2e64d5639..5fadff082e8c8 100644 --- a/src/osd/sdl/video.cpp +++ b/src/osd/sdl/video.cpp @@ -231,12 +231,7 @@ void sdl_osd_interface::extract_video_config() video_config.centerh = options().centerh(); video_config.centerv = options().centerv(); video_config.waitvsync = options().wait_vsync(); - video_config.syncrefresh = options().sync_refresh(); - if (!video_config.waitvsync && video_config.syncrefresh) - { - osd_printf_warning("-syncrefresh specified without -waitvsync. Reverting to -nosyncrefresh\n"); - video_config.syncrefresh = 0; - } + video_config.framedelay = options().frame_delay(); if (video_config.prescale < 1 || video_config.prescale > 8) { diff --git a/src/osd/sdl/window.cpp b/src/osd/sdl/window.cpp index 3216589de6ea6..fb10daf267395 100644 --- a/src/osd/sdl/window.cpp +++ b/src/osd/sdl/window.cpp @@ -411,6 +411,10 @@ int sdl_window_info::window_init() set_renderer(osd_renderer::make_for_type(video_config.mode, static_cast(this)->shared_from_this())); + // add they switchres display manager + if (downcast(machine().options()).switch_res()) + m_display_manager = downcast(machine().osd()).switchres()->add_display(index(), monitor(), target(), &m_win_config); + int result = complete_create(); // handle error conditions @@ -553,12 +557,19 @@ void sdl_window_info::update() } else if (video_config.switchres) { - osd_dim tmp = this->pick_best_mode(); - resize(tmp.width(), tmp.height()); + // check if we need to change the video mode + if (downcast(machine().options()).changeres()) + downcast(machine().osd()).switchres()->check_resolution_change(index(), monitor(), target(), &m_win_config); + + if (!downcast(machine().options()).mode_setting()) + { + osd_dim tmp = this->pick_best_mode(); + resize(tmp.width(), tmp.height()); + } } } - if (video_config.waitvsync && video_config.syncrefresh) + if (video_config.waitvsync) event_wait_ticks = osd_ticks_per_second(); // block at most a second else event_wait_ticks = 0; @@ -612,6 +623,7 @@ void sdl_window_info::update() int sdl_window_info::complete_create() { osd_dim temp(0,0); + bool mode_setting = downcast(machine().options()).mode_setting(); // clear out original mode. Needed on OSX if (fullscreen()) @@ -620,7 +632,7 @@ int sdl_window_info::complete_create() temp = monitor()->position_size().dim(); // if we're allowed to switch resolutions, override with something better - if (video_config.switchres) + if (video_config.switchres && !mode_setting) temp = pick_best_mode(); } else if (m_windowed_dim.width() > 0) @@ -675,11 +687,11 @@ int sdl_window_info::complete_create() // create the SDL window // soft driver also used | SDL_WINDOW_INPUT_GRABBED | SDL_WINDOW_MOUSE_FOCUS m_extra_flags |= (fullscreen() ? - SDL_WINDOW_BORDERLESS | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_FULLSCREEN : SDL_WINDOW_RESIZABLE); + SDL_WINDOW_BORDERLESS | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_RESIZABLE); -#if defined(SDLMAME_WIN32) +//#if defined(SDLMAME_WIN32) SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); -#endif +//#endif // get monitor work area for centering osd_rect work = monitor()->usuable_position_size(); @@ -703,7 +715,7 @@ int sdl_window_info::complete_create() set_platform_window(sdlwindow); - if (fullscreen() && video_config.switchres) + if (fullscreen() && video_config.switchres && !mode_setting) { SDL_DisplayMode mode; //SDL_GetCurrentDisplayMode(window().monitor()->handle, &mode); diff --git a/src/osd/sdl/window.h b/src/osd/sdl/window.h index 211d6348231f3..8ada3cd122336 100644 --- a/src/osd/sdl/window.h +++ b/src/osd/sdl/window.h @@ -35,6 +35,8 @@ typedef uintptr_t HashT; #define OSDWORK_CALLBACK(name) void *name(void *param, int threadid) +class display_manager; + class sdl_window_info : public osd_window_t { public: @@ -97,6 +99,7 @@ class sdl_window_info : public osd_window_t void measure_fps(int update); + display_manager * m_display_manager = 0; }; struct osd_draw_callbacks diff --git a/src/osd/windows/video.cpp b/src/osd/windows/video.cpp index c277e4a0c3c2d..e6841eeb3a8cd 100644 --- a/src/osd/windows/video.cpp +++ b/src/osd/windows/video.cpp @@ -189,9 +189,8 @@ void windows_osd_interface::extract_video_config() video_config.mode = VIDEO_MODE_GDI; } video_config.waitvsync = options().wait_vsync(); - video_config.syncrefresh = options().sync_refresh(); - video_config.triplebuf = options().triple_buffer(); video_config.switchres = options().switch_res(); + video_config.framedelay = options().frame_delay(); if (video_config.prescale < 1 || video_config.prescale > 8) { diff --git a/src/osd/windows/window.cpp b/src/osd/windows/window.cpp index 75090e0548f50..2662811961532 100644 --- a/src/osd/windows/window.cpp +++ b/src/osd/windows/window.cpp @@ -778,6 +778,10 @@ void win_window_info::create(running_machine &machine, int index, std::shared_pt window->m_targetlayerconfig = window->target()->layer_config(); window->m_targetvismask = window->target()->visibility_mask(); + // add they switchres display manager + if (window->m_fullscreen_safe && downcast(machine.options()).switch_res()) + window->m_display_manager = WINOSD(machine)->switchres()->add_display(index, monitor.get(), window->target(), &window->m_win_config); + // set the initial maximized state window->m_startmaximized = downcast(machine.options()).maximize(); @@ -835,6 +839,27 @@ void win_window_info::update() } } + bool reset_required = false; + + // check if we need to change the video mode + auto &options = downcast(machine().options()); + if (options.switch_res() && options.changeres()) + reset_required = WINOSD(machine())->switchres()->check_resolution_change(index(), monitor(), target(), &m_win_config); + + // check if frame delay has changed + int new_frame_delay = machine().video().framedelay(); + if (new_frame_delay != video_config.framedelay) + { + reset_required |= ((bool)video_config.framedelay != (bool)new_frame_delay); + video_config.framedelay = new_frame_delay; + } + + if (reset_required) + { + reset_fullscreen_renderer(); + return; + } + // if we're visible and running and not in the middle of a resize, draw if (platform_window() != nullptr && target() != nullptr && has_renderer()) { @@ -1279,6 +1304,9 @@ LRESULT CALLBACK win_window_info::video_window_proc(HWND wnd, UINT message, WPAR { for (const auto &w : osd_common_t::s_window_list) ShowWindow(std::static_pointer_cast(w)->platform_window(), SW_RESTORE); + + // Set the first window as foreground + SetForegroundWindow(std::static_pointer_cast(osd_common_t::s_window_list.front())->platform_window()); } else if ((wparam == WA_INACTIVE) && !is_mame_window(HWND(lparam))) { @@ -1326,6 +1354,12 @@ LRESULT CALLBACK win_window_info::video_window_proc(HWND wnd, UINT message, WPAR window->maximize_window(); break; + // make sure the taskbar doesn't get on top of us + case WM_DEVICECHANGE: + case WM_WININICHANGE: + SetWindowPos(wnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + break; + // maximum size set case WM_DISPLAYCHANGE: /* FIXME: The current codebase has an issue with setting aspect @@ -1335,7 +1369,6 @@ LRESULT CALLBACK win_window_info::video_window_proc(HWND wnd, UINT message, WPAR * should be used. */ window->monitor()->refresh(); - window->monitor()->update_resolution(LOWORD(lparam), HIWORD(lparam)); break; // set focus: if we're not the primary window, switch back @@ -1836,6 +1869,28 @@ void win_window_info::set_fullscreen(int fullscreen) } +//============================================================ +// reset_fullscreen_renderer +//============================================================ + +void win_window_info::reset_fullscreen_renderer() +{ + // if we're in the right state, punt + if (!m_fullscreen) + return; + + // D3D renderer needs a reset + if (video_config.mode == VIDEO_MODE_D3D) + { + renderer().restart(); + return; + } + + // Resize our window if required + adjust_window_position_after_major_change(); +} + + //============================================================ // focus // (main or window thread) diff --git a/src/osd/windows/window.h b/src/osd/windows/window.h index 71bd519ab3a6b..05637d33681b8 100644 --- a/src/osd/windows/window.h +++ b/src/osd/windows/window.h @@ -50,6 +50,8 @@ enum class win_window_focus }; +class display_manager; + class win_window_info : public osd_window_t { public: @@ -134,6 +136,7 @@ class win_window_info : public osd_window_t void maximize_window(); void adjust_window_position_after_major_change(); void set_fullscreen(int fullscreen); + void reset_fullscreen_renderer(); static POINT s_saved_cursor_pos; @@ -142,6 +145,8 @@ class win_window_info : public osd_window_t #endif bool m_attached_mode; + + display_manager * m_display_manager = 0; }; struct osd_draw_callbacks diff --git a/src/osd/windows/winmain.cpp b/src/osd/windows/winmain.cpp index 57b43116bd4c3..8152c38a6a394 100644 --- a/src/osd/windows/winmain.cpp +++ b/src/osd/windows/winmain.cpp @@ -261,7 +261,6 @@ const options_entry windows_options::s_option_entries[] = // full screen options { nullptr, nullptr, OPTION_HEADER, "FULL SCREEN OPTIONS" }, - { WINOPTION_TRIPLEBUFFER ";tb", "0", OPTION_BOOLEAN, "enable triple buffering" }, { WINOPTION_FULLSCREENBRIGHTNESS ";fsb(0.1-2.0)", "1.0", OPTION_FLOAT, "brightness value in full screen mode" }, { WINOPTION_FULLSCREENCONTRAST ";fsc(0.1-2.0)", "1.0", OPTION_FLOAT, "contrast value in full screen mode" }, { WINOPTION_FULLSCREENGAMMA ";fsg(0.1-3.0)", "1.0", OPTION_FLOAT, "gamma value in full screen mode" }, diff --git a/src/osd/windows/winmain.h b/src/osd/windows/winmain.h index faca28e5bcae2..072c13c73a54d 100644 --- a/src/osd/windows/winmain.h +++ b/src/osd/windows/winmain.h @@ -111,7 +111,6 @@ #define WINOPTION_UI_LUT_ENABLE "ui_lut_enable" // full screen options -#define WINOPTION_TRIPLEBUFFER "triplebuffer" #define WINOPTION_FULLSCREENBRIGHTNESS "full_screen_brightness" #define WINOPTION_FULLSCREENCONTRAST "full_screen_contrast" #define WINOPTION_FULLSCREENGAMMA "full_screen_gamma" @@ -223,7 +222,6 @@ class windows_options : public osd_options bool ui_lut_enable() const { return bool_value(WINOPTION_UI_LUT_ENABLE); } // full screen options - bool triple_buffer() const { return bool_value(WINOPTION_TRIPLEBUFFER); } float full_screen_brightness() const { return float_value(WINOPTION_FULLSCREENBRIGHTNESS); } float full_screen_contrast() const { return float_value(WINOPTION_FULLSCREENCONTRAST); } float full_screen_gamma() const { return float_value(WINOPTION_FULLSCREENGAMMA); }