diff --git a/Makefile.common b/Makefile.common index 6729a9d12b..c0fea61d70 100644 --- a/Makefile.common +++ b/Makefile.common @@ -304,7 +304,6 @@ OBJ += \ input/common/input_hid_common.o \ led/led_driver.o \ gfx/video_coord_array.o \ - gfx/video_crt_switch.o \ gfx/gfx_display.o \ gfx/gfx_animation.o \ gfx/gfx_thumbnail_path.o \ @@ -2274,6 +2273,39 @@ ifeq ($(HAVE_FFMPEG), 1) INCLUDE_DIRS += -Iffmpeg endif +# CRT mode switching +ifeq ($(HAVE_SR2), 1) + INCLUDE_DIRS += -I$(DEPS_DIR)/switchres + OBJ += gfx/video_crt_switch.o \ + $(DEPS_DIR)/switchres/monitor.o \ + $(DEPS_DIR)/switchres/modeline.o \ + $(DEPS_DIR)/switchres/switchres.o \ + $(DEPS_DIR)/switchres/display.o \ + $(DEPS_DIR)/switchres/custom_video.o \ + $(DEPS_DIR)/switchres/log.o \ + $(DEPS_DIR)/switchres/switchres_wrapper.o \ + $(DEPS_DIR)/switchres/edid.o + ifneq ($(findstring Win32,$(OS)),) + OBJ += $(DEPS_DIR)/switchres/display_windows.o \ + $(DEPS_DIR)/switchres/custom_video_ati_family.o \ + $(DEPS_DIR)/switchres/custom_video_ati.o \ + $(DEPS_DIR)/switchres/custom_video_adl.o \ + $(DEPS_DIR)/switchres/custom_video_pstrip.o \ + $(DEPS_DIR)/switchres/resync_windows.o + endif + ifneq ($(findstring Linux,$(OS)),) + OBJ += $(DEPS_DIR)/switchres/display_linux.o + ifeq ($(HAVE_X11)$(HAVE_XRANDR), 11) + OBJ += $(DEPS_DIR)/switchres/custom_video_xrandr.o + DEFINES += -DSR_WITH_XRANDR + endif + endif + ifneq ($(findstring Win32,$(OS)),) + DEFINES += -DSR_WIN32_STATIC + endif + LIBS += -lstdc++ +endif + ifeq ($(HAVE_COMPRESSION), 1) DEFINES += -DHAVE_COMPRESSION OBJ += tasks/task_decompress.o diff --git a/config.def.h b/config.def.h index 40c16677df..1bc7930d72 100644 --- a/config.def.h +++ b/config.def.h @@ -157,6 +157,8 @@ #define DEFAULT_CRT_SWITCH_PORCH_ADJUST 0 +#define DEFAULT_CRT_SWITCH_HIRES_MENU true + #define DEFAULT_HISTORY_LIST_ENABLE true #define DEFAULT_PLAYLIST_ENTRY_RENAME true diff --git a/configuration.c b/configuration.c index 186cd76947..0d5f9a835b 100644 --- a/configuration.c +++ b/configuration.c @@ -1460,6 +1460,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("frame_time_counter_reset_after_load_state", &settings->bools.frame_time_counter_reset_after_load_state, true, false, false); SETTING_BOOL("frame_time_counter_reset_after_save_state", &settings->bools.frame_time_counter_reset_after_save_state, true, false, false); SETTING_BOOL("crt_switch_resolution_use_custom_refresh_rate", &settings->bools.crt_switch_custom_refresh_enable, true, false, false); + SETTING_BOOL("crt_switch_hires_menu", &settings->bools.crt_switch_hires_menu, true, false, true); SETTING_BOOL("ui_companion_start_on_boot", &settings->bools.ui_companion_start_on_boot, true, ui_companion_start_on_boot, false); SETTING_BOOL("ui_companion_enable", &settings->bools.ui_companion_enable, true, ui_companion_enable, false); SETTING_BOOL("ui_companion_toggle", &settings->bools.ui_companion_toggle, false, ui_companion_toggle, false); diff --git a/configuration.h b/configuration.h index 0bad72ce8a..5ea88e1ff1 100644 --- a/configuration.h +++ b/configuration.h @@ -67,7 +67,9 @@ enum crt_switch_type { CRT_SWITCH_NONE = 0, CRT_SWITCH_15KHZ, - CRT_SWITCH_31KHZ + CRT_SWITCH_31KHZ, + CRT_SWITCH_32_120, + CRT_SWITCH_INI }; enum override_type @@ -697,6 +699,7 @@ typedef struct settings bool kiosk_mode_enable; bool crt_switch_custom_refresh_enable; + bool crt_switch_hires_menu; /* Netplay */ bool netplay_public_announce; diff --git a/deps/switchres/.gitlab-ci.yml b/deps/switchres/.gitlab-ci.yml new file mode 100644 index 0000000000..f1c9fb9f79 --- /dev/null +++ b/deps/switchres/.gitlab-ci.yml @@ -0,0 +1,59 @@ +# This file is a template, and might need editing before it works on your project. +# use the official gcc image, based on debian +# can use verions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ +image: gcc:latest + +before_script: + - apt update + - apt -y install make + +.pre_requisites_linux: &prerequisiteslinux + before_script: + - apt update + - apt -y install make + +.pre_requisites_win32: &prerequisiteswin32 + image: "ubuntu:rolling" + before_script: + - apt update + - apt -y install make mingw-w64 + + +linux:x86_64:standalone: + stage: build + <<: *prerequisiteslinux + script: + - make + +linux:x86_64:lib: + stage: build + <<: *prerequisiteslinux + script: + - make libswitchres + +win32:x86_64:standalone: + stage: build + <<: *prerequisiteswin32 + script: + - make PLATFORM=NT CROSS_COMPILE=x86_64-w64-mingw32- + +win32:x86_64:lib: + stage: build + <<: *prerequisiteswin32 + script: + - make PLATFORM=NT CROSS_COMPILE=x86_64-w64-mingw32- libswitchres + + +win32:i686:standalone: + stage: build + <<: *prerequisiteswin32 + script: + - make PLATFORM=NT CROSS_COMPILE=i686-w64-mingw32- + + +win32:i686:lib: + stage: build + <<: *prerequisiteswin32 + script: + - make PLATFORM=NT CROSS_COMPILE=i686-w64-mingw32- libswitchres \ No newline at end of file diff --git a/deps/switchres/README.md b/deps/switchres/README.md new file mode 100644 index 0000000000..9ee0fc8e11 --- /dev/null +++ b/deps/switchres/README.md @@ -0,0 +1,60 @@ +# What is Switchres 2.0 +Switchres is a modeline generation engine for emulation. + +Its purpose is on-the-fly creation of fully customized video modes that accurately reproduce those of the emulated systems. Based on a monitor profile, it will provide the best video mode for a given width, height, and refresh rate. + +Switchres features the most versatile modeline generation ever, ranging from 15-kHz low resolutions up to modern 8K, with full geometry control, smart scaling, refresh scaling, mode rotation, aspect ratio correction and much more. + +Switchres can be integrated into open-source emulators either as a library, or used as a standalone emulator launcher. It's written in C++ and a C wrapper is also available. + +Switchres 2.0 is a rewrite of the original Switchres code used in GroovyMAME. It currently supports mode switching on the following platforms, with their respective backends: + - **Windows**: + - AMD ADL (AMD Radeon HD 5000+) + - ATI legacy (ATI Radeon pre-HD 5000) + - PowerStrip (ATI, Nvidia, Matrox, etc., models up to 2012) + - **Linux**: + - X11/Xorg + - KMS/DRM (WIP) + +Each platform supports a different feature set, being X11/Xorg the most performant currently. In general, AMD video cards offer the best compatibility, and are a real requirement for the Windows platform. + +# Using Switchres as a library +If you are an emulator writer, you can integrate Switchres into your emulator in two ways: + +- **Switchres shared library** (.dll or .so). This method offers a simplified way to add advanced mode switching features to your emulator, with minimal knowledge of Switchres internals. + +- **Full Switchres integration**. If your emulator is written in C++, you can gain full access to Switchres' gears by including a Switchres manager class into your project, à la GroovyMAME. + +Ask our devs for help and advice. + +# Using Switchres standalone +The standalone binary supports the following options: +``` +Usage: switchres [options] +Options: + -c, --calc Calculate video mode and exit + -s, --switch Switch to video mode + -l, --launch Launch + -m, --monitor Monitor preset (generic_15, arcade_15, pal, ntsc, etc.) + -a --aspect Monitor aspect ratio + -r --rotated Original mode's native orientation is rotated + -d, --display Use target display (Windows: \\\\.\\DISPLAY1, ... Linux: VGA-0, ...) + -f, --force x@ Force a specific video mode from display mode list + -i, --ini Specify an ini file + -b, --backend Specify the api name + -k, --keep Keep changes on exit (warning: this disables cleanup) +``` + +A default `switchres.ini` file will be searched in the current working directory, then in `.\ini` on Windows, `./ini` then `/etc` on Linux. The repo has a switchres.ini example. + +## Examples +`switchres 320 240 60 --calc` will calculate and show a modeline for 320x240@60, computed using the current monitor preset in `switchres.ini`. + +`switchres 320 240 60 -m ntsc -s` will switch your primary screen to 320x240 at 60Hz using the ntsc monitor model. Then it will wait until you press enter, and restore your initial screen resolution on exit. + +`switchres 384 256 55.017605 -m arcade_15 -s -d \\.\DISPLAY1 -l "mame rtype"` will switch your display #1 to 384x256@55.017605 using the arcade_15 preset, then launch ``mame rtype``. Once mame has exited, it will restore the original resolution. + +`switchres 640 480 57 -d 0 -m arcade_15 -d 1 -m arcade_31 -s` will set 640x480@57i (15-kHz preset) on your first display (index #0), 640x480@57p (31-kHz preset) on your second display (index #1) + +# License +GNU General Public License, version 2 or later (GPL-2.0+). diff --git a/deps/switchres/custom_video.cpp b/deps/switchres/custom_video.cpp new file mode 100644 index 0000000000..32989f5173 --- /dev/null +++ b/deps/switchres/custom_video.cpp @@ -0,0 +1,178 @@ +/************************************************************** + + custom_video.cpp - Custom video library + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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__) +#ifdef SR_WITH_XRANDR +#include "custom_video_xrandr.h" +#endif +#ifdef SR_WITH_KMSDRM +#include "custom_video_drmkms.h" +#endif +#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); + +#ifdef SR_WITH_XRANDR + 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; + } + } +#endif /* SR_WITH_XRANDR */ + +#ifdef SR_WITH_KMSDRM + 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 /* SR_WITH_KMSDRM */ +#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/deps/switchres/custom_video.h b/deps/switchres/custom_video.h new file mode 100644 index 0000000000..80a96e4647 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_adl.cpp b/deps/switchres/custom_video_adl.cpp new file mode 100644 index 0000000000..699bf250d6 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_adl.h b/deps/switchres/custom_video_adl.h new file mode 100644 index 0000000000..456ad9395b --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_ati.cpp b/deps/switchres/custom_video_ati.cpp new file mode 100644 index 0000000000..488a65a0b9 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_ati.h b/deps/switchres/custom_video_ati.h new file mode 100644 index 0000000000..37d7775cef --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_ati_family.cpp b/deps/switchres/custom_video_ati_family.cpp new file mode 100644 index 0000000000..324617e9cc --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_drmkms.cpp b/deps/switchres/custom_video_drmkms.cpp new file mode 100644 index 0000000000..3706f2ea58 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_drmkms.h b/deps/switchres/custom_video_drmkms.h new file mode 100644 index 0000000000..53bb038182 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_pstrip.cpp b/deps/switchres/custom_video_pstrip.cpp new file mode 100644 index 0000000000..f7542bfe92 --- /dev/null +++ b/deps/switchres/custom_video_pstrip.cpp @@ -0,0 +1,628 @@ +/************************************************************** + + custom_video_pstrip.cpp - PowerStrip interface routines + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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/deps/switchres/custom_video_pstrip.h b/deps/switchres/custom_video_pstrip.h new file mode 100644 index 0000000000..5c32f7cfa6 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_xrandr.cpp b/deps/switchres/custom_video_xrandr.cpp new file mode 100644 index 0000000000..2eeb3c869d --- /dev/null +++ b/deps/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-2021 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/deps/switchres/custom_video_xrandr.h b/deps/switchres/custom_video_xrandr.h new file mode 100644 index 0000000000..d2cf795275 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/display.cpp b/deps/switchres/display.cpp new file mode 100644 index 0000000000..6a1c17dd6e --- /dev/null +++ b/deps/switchres/display.cpp @@ -0,0 +1,481 @@ +/************************************************************** + + display.cpp - Display manager + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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/deps/switchres/display.h b/deps/switchres/display.h new file mode 100644 index 0000000000..00934100cb --- /dev/null +++ b/deps/switchres/display.h @@ -0,0 +1,162 @@ +/************************************************************** + + display.h - Display manager + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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; } + int interlace_force_even() { return m_ds.gs.interlace_force_even; } + + // 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/deps/switchres/display_linux.cpp b/deps/switchres/display_linux.cpp new file mode 100644 index 0000000000..09cad8adea --- /dev/null +++ b/deps/switchres/display_linux.cpp @@ -0,0 +1,168 @@ +/************************************************************** + + display_linux.cpp - Display manager for Linux + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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; + +#ifdef SR_WITH_XRANDR + if (!strcmp(m_ds.api, "xrandr")) + method = CUSTOM_VIDEO_TIMING_XRANDR; +#endif +#ifdef SR_WITH_KMSDRM + if (!strcmp(m_ds.api, "drmkms")) + method = CUSTOM_VIDEO_TIMING_DRMKMS; +#endif + + set_factory(new custom_video); + set_custom_video(factory()->make(m_ds.screen, NULL, method, &m_ds.vs)); + if (!video() or !video()->init()) + return false; + + // 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/deps/switchres/display_linux.h b/deps/switchres/display_linux.h new file mode 100644 index 0000000000..f5ffe64a58 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/display_windows.cpp b/deps/switchres/display_windows.cpp new file mode 100644 index 0000000000..0372ccc916 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/display_windows.h b/deps/switchres/display_windows.h new file mode 100644 index 0000000000..81dff5b957 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/edid.cpp b/deps/switchres/edid.cpp new file mode 100644 index 0000000000..d16064dfd4 --- /dev/null +++ b/deps/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-2021 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/deps/switchres/edid.h b/deps/switchres/edid.h new file mode 100644 index 0000000000..6c5de8104a --- /dev/null +++ b/deps/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-2021 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/deps/switchres/examples/README.md b/deps/switchres/examples/README.md new file mode 100644 index 0000000000..fc9bcda1da --- /dev/null +++ b/deps/switchres/examples/README.md @@ -0,0 +1,56 @@ +# ANY OS - BASIC INFORMATION + +## Build libswitchres + +It supports cross compilation, and will build both dynamic and static libs as per the target OS +```bash +make libswitchres +``` +## Basic usage as a client with examples +libswitchres can be called in 2 different ways (with example code): + * `test_dlopen.c` -> by explicitely opening a .so/.dll, import the srlib object and call associated functions + * `test_liblink.c` -> by simply linking libswitchres at build time + +These options are generic whether you build for Linux or Windows + * -I ../ (to get libswitchres_wrapper.h) + * -L ../ or -L ./ (for win32, when the dll has been copied in the examples folder) + * -lswitchres to link the lib if not manually opening it in the code + +#please note#: static libs aven't been tested yet + +# LINUX + +You'll need a few extra parameters for gcc: + * -ldl (will try later to find a way to statically link libdl.a) + +When running, dont forget to add before the binary LD_LIBRARY_PATH=:$LD_LIBRARY_PATH + +## Examples: +```bash +make libswitchres +cd examples +g++ -o linux_dl_test test_dlopen.cpp -I ../ -ldl +LD_LIBRARY_PATH=../:$LD_LIBRARY_PATH ./linux_dl_test + +g++ -o linux_link_lib test_liblink.cpp -I ../ -L../ -lswitchres -ldl +LD_LIBRARY_PATH=../:$LD_LIBRARY_PATH ./linux_link_lib +``` + +# WINDOWS + +Pretty much the same as Linux, but with mingw64. The resulting exe and dll can be tested with wine + +## Examples (cross-building from windows) + +``` +make PLATFORM=NT CROSS_COMPILE=x86_64-w64-mingw32- libswitchres +(copy the dll to examples) + +x86_64-w64-mingw32-g++-win32 test_dlopen.cpp -o w32_loaddll.exe -I ../ -static-libgcc -static-libstdc++ +w32_loaddll.exe + +x86_64-w64-mingw32-g++-win32 test_liblink.cpp -o w32_linkdll.exe -I ../ -static-libgcc -static-libstdc++ -L ./ -lswitchres +w32_linkdll.exe +``` + +Note that, when building w32_linkdll.exe, I couldn't point to another dir else than ./ with -L diff --git a/deps/switchres/examples/test_dlopen.cpp b/deps/switchres/examples/test_dlopen.cpp new file mode 100644 index 0000000000..865ddfdf54 --- /dev/null +++ b/deps/switchres/examples/test_dlopen.cpp @@ -0,0 +1,81 @@ +#include +#include +#ifdef __cplusplus +#include // required for strcpy +#endif + +#ifdef __linux__ +#define LIBSWR "libswitchres.so" +#elif _WIN32 +#define LIBSWR "libswitchres.dll" +#endif + +#include + +int main(int argc, char** argv) { + const char* err_msg; + + printf("About to open %s.\n", LIBSWR); + + // Load the lib + LIBTYPE dlp = OPENLIB(LIBSWR); + + // Loading failed, inform and exit + if (!dlp) { + printf("Loading %s failed.\n", LIBSWR); + printf("Error: %s\n", LIBERROR()); + exit(EXIT_FAILURE); + } + + printf("Loading %s succeded.\n", LIBSWR); + + + // Load the init() + LIBERROR(); + srAPI* SRobj = (srAPI*)LIBFUNC(dlp, "srlib"); + if ((err_msg = LIBERROR()) != NULL) { + printf("Failed to load srAPI: %s\n", err_msg); + CLOSELIB(dlp); + exit(EXIT_FAILURE); + } + + // Testing the function + printf("Init a new switchres_manager object:\n"); + SRobj->init(); + SRobj->sr_init_disp(); + + // Call mode + get result values + int w = 384, h = 224; + double rr = 59.583393; + unsigned char interlace = 0, ret; + sr_mode srm; + + printf("Orignial resolution expected: %dx%d@%f-%d\n", w, h, rr, interlace); + + ret = SRobj->sr_add_mode(w, h, rr, interlace, &srm); + if(!ret) + { + printf("ERROR: couldn't add the required mode. Exiting!\n"); + SRobj->deinit(); + exit(1); + } + printf("Got resolution: %dx%d%c@%f\n", srm.width, srm.height, srm.interlace, srm.refresh); + printf("Press Any Key to switch to new mode\n"); + getchar(); + + ret = SRobj->sr_switch_to_mode(srm.width, srm.height, rr, srm.interlace, &srm); + if(!ret) + { + printf("ERROR: couldn't switch to the required mode. Exiting!\n"); + SRobj->deinit(); + exit(1); + } + printf("Press Any Key to quit.\n"); + getchar(); + + // Clean the mess, kiss goodnight SR + SRobj->deinit(); + + // We're done, let's closer + CLOSELIB(dlp); +} diff --git a/deps/switchres/examples/test_liblink.cpp b/deps/switchres/examples/test_liblink.cpp new file mode 100644 index 0000000000..bc78a9a569 --- /dev/null +++ b/deps/switchres/examples/test_liblink.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +int main(int argc, char** argv) { + sr_mode srm; + unsigned char ret; + + sr_init(); + sr_init_disp(); + + ret = sr_add_mode(384, 224, 59.63, 0, &srm); + if(!ret) + { + printf("ERROR: couldn't add the required mode. Exiting!\n"); + sr_deinit(); + exit(1); + } + printf("SR returned resolution: %dx%d%c@%f\n", srm.width, srm.height, srm.interlace, srm.refresh); + + ret = sr_switch_to_mode(384, 224, 59.63, 0, &srm); + if(!ret) + { + printf("ERROR: couldn't switch to the required mode. Exiting!\n"); + sr_deinit(); + exit(1); + } + + sr_deinit(); +} diff --git a/deps/switchres/grid.cpp b/deps/switchres/grid.cpp new file mode 100644 index 0000000000..072c52ff8f --- /dev/null +++ b/deps/switchres/grid.cpp @@ -0,0 +1,228 @@ +/************************************************************** + + grid.cpp - Simple test grid + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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/deps/switchres/log.cpp b/deps/switchres/log.cpp new file mode 100644 index 0000000000..50e3c9ea76 --- /dev/null +++ b/deps/switchres/log.cpp @@ -0,0 +1,78 @@ +/************************************************************** + + log.cpp - Simple logging for Switchres + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#include "log.h" + +enum log_verbosity { NONE, ERROR, INFO, DEBUG }; +static log_verbosity log_level = INFO; + +void log_dummy(const char *, ...) {} + +LOG_VERBOSE log_verbose = &log_dummy; +LOG_INFO log_info = &log_dummy; +LOG_ERROR log_error = &log_dummy; + +/* + * These bakup pointers are here to let the user modify the log level at runtime + * We can't sadly unify a log function and test the log level to test if it should + * output a log, because it would imply frewriting log_ functions with va_args + * and wouldn't work with emulators log functions anymore + */ +LOG_VERBOSE log_verbose_bak = &log_dummy; +LOG_INFO log_info_bak = &log_dummy; +LOG_ERROR log_error_bak = &log_dummy; + + +void set_log_verbose(void *func_ptr) +{ + if (log_level >= DEBUG) + log_verbose = (LOG_VERBOSE)func_ptr; + log_verbose_bak = (LOG_VERBOSE)func_ptr; +} + +void set_log_info(void *func_ptr) +{ + if (log_level >= INFO) + log_info = (LOG_INFO)func_ptr; + log_info_bak = (LOG_INFO)func_ptr; +} + +void set_log_error(void *func_ptr) +{ + if (log_level >= ERROR) + log_error = (LOG_ERROR)func_ptr; + log_error_bak = (LOG_ERROR)func_ptr; +} + +void set_log_verbosity(int level) +{ + // Keep the log in the enum bounds + if (level < NONE) + level = NONE; + if(level > DEBUG) + level = DEBUG; + + log_error = &log_dummy; + log_info = &log_dummy; + log_verbose = &log_dummy; + + if (level >= ERROR) + log_error = log_error_bak; + + if (level >= INFO) + log_info = log_info_bak; + + if (level >= DEBUG) + log_verbose = log_verbose_bak; +} \ No newline at end of file diff --git a/deps/switchres/log.h b/deps/switchres/log.h new file mode 100644 index 0000000000..8ac475a02d --- /dev/null +++ b/deps/switchres/log.h @@ -0,0 +1,38 @@ +/************************************************************** + + log.h - Simple logging for Switchres + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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_verbosity(int); +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/deps/switchres/modeline.cpp b/deps/switchres/modeline.cpp new file mode 100644 index 0000000000..7b37b32119 --- /dev/null +++ b/deps/switchres/modeline.cpp @@ -0,0 +1,771 @@ +/************************************************************** + + modeline.cpp - Modeline generation and scoring routines + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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->hactive < 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; + + // Apply interlace fixes + if (cs->interlace_force_even && interlace == 2) + { + t_mode->vbegin = (t_mode->vbegin / 2) * 2; + t_mode->vend = (t_mode->vend / 2) * 2; + t_mode->vtotal++; + } + } + + // 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/deps/switchres/modeline.h b/deps/switchres/modeline.h new file mode 100644 index 0000000000..9fd0e5fecc --- /dev/null +++ b/deps/switchres/modeline.h @@ -0,0 +1,140 @@ +/************************************************************** + + modeline.h - Modeline generation header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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; + int interlace_force_even; +} 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/deps/switchres/monitor.cpp b/deps/switchres/monitor.cpp new file mode 100644 index 0000000000..643b9ba19a --- /dev/null +++ b/deps/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-2021 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/deps/switchres/monitor.h b/deps/switchres/monitor.h new file mode 100644 index 0000000000..53794166e1 --- /dev/null +++ b/deps/switchres/monitor.h @@ -0,0 +1,64 @@ +/************************************************************** + + monitor.h - Monitor presets header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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/deps/switchres/resync_windows.cpp b/deps/switchres/resync_windows.cpp new file mode 100644 index 0000000000..141c958e80 --- /dev/null +++ b/deps/switchres/resync_windows.cpp @@ -0,0 +1,171 @@ +/************************************************************** + + resync_windows.cpp - Windows device change notifying helper + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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() +{ + m_event = CreateEvent(NULL, FALSE, FALSE, NULL); + CreateThread(NULL, 0, handler_thread, (LPVOID)this, 0, &my_thread); +} + +//============================================================ +// resync_handler::~resync_handler +//============================================================ + +resync_handler::~resync_handler() +{ + SendMessage(m_hwnd, WM_CLOSE, 0, 0); + if (m_event) CloseHandle(m_event); +} + +//============================================================ +// resync_handler::handler_thread +//============================================================ + +DWORD WINAPI resync_handler::handler_thread(LPVOID lpParameter) +{ + return ((resync_handler *)lpParameter)->handler_thread_wt(); +} + +DWORD resync_handler::handler_thread_wt() +{ + 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); + } + + return -1; +} + +//============================================================ +// resync_handler::wait +//============================================================ + +void resync_handler::wait() +{ + 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) + WaitForSingleObject(m_event, 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; + SetEvent(m_event); + } + } + 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; + SetEvent(m_event); + 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/deps/switchres/resync_windows.h b/deps/switchres/resync_windows.h new file mode 100644 index 0000000000..1b96b66b09 --- /dev/null +++ b/deps/switchres/resync_windows.h @@ -0,0 +1,43 @@ +/************************************************************** + + resync_windows.h - Windows device change notifying helper + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 Chris Kennedy, Antonio Giner, + Alexandre Wodarczyk, Gil Delescluse + + **************************************************************/ + +#ifndef __RESYNC_WINDOWS__ +#define __RESYNC_WINDOWS__ + +#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); + static DWORD WINAPI handler_thread(LPVOID lpParameter); + DWORD handler_thread_wt(); + + HWND m_hwnd; + DWORD my_thread; + bool m_is_notified_1; + bool m_is_notified_2; + HANDLE m_event; +}; + +#endif diff --git a/deps/switchres/switchres.cpp b/deps/switchres/switchres.cpp new file mode 100644 index 0000000000..c492ba8932 --- /dev/null +++ b/deps/switchres/switchres.cpp @@ -0,0 +1,377 @@ +/************************************************************** + + switchres.cpp - Swichres manager + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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_level(int log_level) { set_log_verbosity(log_level); } +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); + set_interlace_force_even(0); + + // Create our display manager + m_display_factory = new display_manager(); + + // Set logger properties + set_log_info_fn((void*)printf); + set_log_error_fn((void*)printf); + set_log_verbose_fn((void*)printf); + set_log_level(2); +} + +//============================================================ +// 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; + + case s2i("interlace_force_even"): + set_interlace_force_even(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; + + // Various + case s2i("verbosity"): + { + int verbosity_level = 1; + sscanf(value.c_str(), "%d", &verbosity_level); + set_log_level(verbosity_level); + 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/deps/switchres/switchres.h b/deps/switchres/switchres.h new file mode 100644 index 0000000000..a83fa9b5cf --- /dev/null +++ b/deps/switchres/switchres.h @@ -0,0 +1,110 @@ +/************************************************************** + + switchres.h - SwichRes general header + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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_level(int log_level); + 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; } + void set_interlace_force_even(int value) { ds.gs.interlace_force_even = 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/deps/switchres/switchres.ini b/deps/switchres/switchres.ini new file mode 100644 index 0000000000..0d7651c783 --- /dev/null +++ b/deps/switchres/switchres.ini @@ -0,0 +1,160 @@ +# +# Switchres config +# + +# Monitor preset. Sets typical monitor operational ranges: +# +# generic_15, ntsc, pal Generic CRT standards +# arcade_15, arcade_15ex Arcade fixed frequency +# arcade_25, arcade_31 Arcade fixed frequency +# arcade_15_25, arcade_15_25_31 Arcade multisync +# vesa_480, vesa_600, vesa_768, vesa_1024 VESA GTF +# pc_31_120, pc_70_120 PC monitor 120 Hz +# h9110, polo, pstar Hantarex +# k7000, k7131, d9200, d9800, d9400 Wells Gardner +# m2929 Makvision +# m3129 Wei-Ya +# ms2930, ms929 Nanao +# r666b Rodotron +# +# Special presets: +# custom Defines a custom preset. Use in combination with crt_range0-9 options below. +# lcd Will keep desktop's resolution but attempt variable refresh, use in combination with lcd_range +# + monitor arcade_15 + +# Define a custom preset, use monitor custom to activate +# crt_range0-9 HfreqMin-HfreqMax, VfreqMin-VfreqMax, HFrontPorch, HSyncPulse, HBackPorch, VfrontPorch, VSyncPulse, VBackPorch, HSyncPol, VSyncPol, ProgressiveLinesMin, ProgressiveLinesMax, InterlacedLinesMin, InterlacedLinesMax +# e.g.: crt_range0 15625-15750, 49.50-65.00, 2.000, 4.700, 8.000, 0.064, 0.192, 1.024, 0, 0, 192, 288, 448, 576 + crt_range0 auto + crt_range1 auto + crt_range2 auto + crt_range3 auto + crt_range4 auto + crt_range5 auto + crt_range6 auto + crt_range7 auto + crt_range8 auto + crt_range9 auto + +# Set the operational refresh range for LCD monitor, e.g. lcd_range 50-61 + lcd_range auto + +# Force a custom modeline, in XFree86 format. This option overrides the active monitor preset configuration. + modeline auto + +# Forces an user mode, in the format: width x height @ refresh. Here, 0 can used as a wildcard. At least one of the three values +# must be defined. E.g. user_mode 0x240 -> SR can freely choose any width based on the game's requested video mode, but will +# force height as 240. + user_mode auto + + +# +# Display config +# + +# Select target display +# auto Pick the default display +# 0, 1, 2, ... Pick a display by index +# \\.\DISPLAY1, ... Windows display name +# VGA-0, ... X11 display name + display auto + +# Choose a custom video backend when more than one is available. +# auto Let Switchres decide +# adl Windows - AMD ADL (AMD Radeon HD 5000+) +# ati Windows - ATI legacy (ATI Radeon pre-HD 5000) +# powerstrip Windows - PowerStrip (ATI, Nvidia, Matrox, etc., models up to 2012) +# xrandr Linux - X11/Xorg +# drmkms Linux - KMS/DRM (WIP) + api auto + +# [Windows] Lock video modes reported as unsupported by your monitor's EDID + lock_unsupported_modes 1 + +# Lock system (non-custom) video modes, only use modes that have full detailed timings available + lock_system_modes 0 + +# Ignore video mode's refresh reported by the OS when checking ranges + refresh_dont_care 0 + +# Keep changes on exit (warning: this skips video mode cleanup) + keep_changes 0 + + +# +# Modeline generation config +# + +# Enable on-the-fly generation of video modes + modeline_generation 1 + +# Allow interlaced modes (existing or generated) + interlace 1 + +# Allow doublescan modes (warning: doublescan support is broken in most drivers) + doublescan 0 + +# Force a minimum dotclock value, in MHz, e.g. dotclock_min 25.0 + dotclock_min 0 + +# Maximum refresh difference, in Hz, allowed in order to synchronize. Below this value, the mismatch does not involve penalization + sync_refresh_tolerance 2.0 + +# Super resolution width: above this width, fractional scaling on the horizontal axis is applied without penalization + super_width 2560 + +# Physical aspect ratio of the target monitor. Used to compensate aspect ratio when the target monitor is not 4:3 + aspect 4:3 + +# [Experimental] Attempts to compensate consumer TVs vertical centering issues + v_shift_correct 0 + +# Calculate horizontal borders with 1-pixel precision, instead of the default 8-pixels blocks that were required by old drivers. +# Greatly improves horizontal centering of video modes. + pixel_precision 1 + +# Calculate all vertical values of interlaced modes as even numbers. Required by AMD APU hardware on Linux + interlace_force_even 0 + + +# +# Custom video backend config +# + +# [X11] adjusts the crtc position after a new video mode is set, maintaining the relative position of screens in a multi-monitor setup. + screen_compositing 0 + +# [X11] stacks the screens vertically on startup to allow each screen to freely resize up to the maximum width. Useful to avoid video +# glitches when using super-resolutions. screen_reordering overrides screen_compositing. + screen_reordering 0 + +# [Windows] dynamically adds new modes or updates existing ones, even on stock AMD drivers*. This feature is experimental and is +# disabled by default. It has the following limitations and problems: +# - Synchronization is not perfect yet and the new modes may not always be ready on time for mode switching, causing a wrong display +# output. +# - A plug-n-play audio notification will be present on startup and exit, if the explorer shell is used. +# - Refreshing the hardware is an expensive task that takes time, specially if the app has already entered fullscreen mode. This +# makes it unpractical for games that switch video modes more than once. +# * When used with stock AMD drivers instead of CRT Emudriver, usual limitations apply: no support for low resolutions (below 640x480) +# nor low dotclocks. +# Not a problem however if you're using a 31 kHz monitor. + allow_hardware_refresh 0 + +# Pass a custom video timing string in the native backend's format. E.g. pstring timing for Powerstrip + custom_timing auto + + +# +# Logging +# + +# Enables verbose mode (0|1) + verbose 0 + +# Set verbosity level (from 0 to 3) +# 0: no messages from SR +# 1: only errors +# 2: general information +# 3: debug messages + verbosity 2 diff --git a/deps/switchres/switchres_main.cpp b/deps/switchres/switchres_main.cpp new file mode 100644 index 0000000000..c933b0d75d --- /dev/null +++ b/deps/switchres/switchres_main.cpp @@ -0,0 +1,305 @@ +/************************************************************** + + switchres_main.cpp - Swichres standalone launcher + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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; + + 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_level(3); + switchres.set_log_error_fn((void*)printf); + switchres.set_log_info_fn((void*)printf); + 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/deps/switchres/switchres_wrapper.cpp b/deps/switchres/switchres_wrapper.cpp new file mode 100644 index 0000000000..debae95b39 --- /dev/null +++ b/deps/switchres/switchres_wrapper.cpp @@ -0,0 +1,223 @@ +/************************************************************** + + switchres_wrapper.cpp - Switchres C wrapper API + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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->parse_config("switchres.ini"); +} + + +MODULE_API void sr_load_ini(char* config) { + swr->parse_config(config); +} + + +MODULE_API unsigned char sr_init_disp(const char* scr) { + if (scr) + swr->set_screen(scr); + swr->add_display(); + if (!swr->display()->init()) + return 0; + return 1; +} + + +MODULE_API void sr_deinit() { + delete swr; +} + + +MODULE_API void sr_set_monitor(const char *preset) { + swr->set_monitor(preset); +} + + +MODULE_API void sr_set_user_mode(int width, int height, int refresh) { + modeline user_mode = {}; + user_mode.width = width; + user_mode.height = height; + user_mode.refresh = refresh; + swr->set_user_mode(&user_mode); +} + + +void disp_best_mode_to_sr_mode(display_manager* disp, sr_mode* srm) +{ + srm->width = disp->width(); + srm->height = disp->height(); + srm->refresh = disp->v_freq(); + 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 void sr_set_log_level (int l) { + swr->set_log_level(l); +} + + +MODULE_API void sr_set_log_callback_info (void * f) { + swr->set_log_info_fn((void *)f); +} + + +MODULE_API void sr_set_log_callback_debug (void * f) { + swr->set_log_verbose_fn((void *)f); +} + + +MODULE_API void sr_set_log_callback_error (void * f) { + swr->set_log_error_fn((void *)f); +} + + +MODULE_API srAPI srlib = { + sr_init, + sr_load_ini, + sr_deinit, + sr_init_disp, + sr_add_mode, + sr_switch_to_mode, + sr_set_monitor, + sr_set_rotation, + sr_set_user_mode, + sr_set_log_level, + sr_set_log_callback_error, + sr_set_log_callback_info, + sr_set_log_callback_debug, +}; + +#ifdef __cplusplus +} +#endif diff --git a/deps/switchres/switchres_wrapper.h b/deps/switchres/switchres_wrapper.h new file mode 100644 index 0000000000..c31da657b9 --- /dev/null +++ b/deps/switchres/switchres_wrapper.h @@ -0,0 +1,128 @@ +/************************************************************** + + switchres_wrapper.h - Switchres C wrapper API header file + + --------------------------------------------------------- + + Switchres Modeline generation engine for emulation + + License GPL-2.0+ + Copyright 2010-2021 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 +#define LIBTYPE HINSTANCE +#define OPENLIB(libname) LoadLibrary(TEXT((libname))) +#define LIBFUNC(lib, fn) GetProcAddress((lib), (fn)) + +#define CLOSELIB(libp) FreeLibrary((libp)) +#endif + +#ifdef _WIN32 +/* + * This is a trick to avoid exporting some functions thus having the binary + * flagged as a virus. If switchres_wrapper.cpp is included in the compilation + * LIBERROR() is just declared and not compiled. If switchres_wrapper.cpp is + * not compiled, LIBERROR is defined here + */ +#ifndef SR_WIN32_STATIC +char* LIBERROR() +{ + DWORD errorMessageID = GetLastError(); + if(errorMessageID == 0) + return NULL; + + 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; +} +#endif /* SR_WIN32_STATIC */ + #ifndef SR_WIN32_STATIC + #define MODULE_API __declspec(dllexport) + #else + #define MODULE_API + #endif +#else + #define MODULE_API +#endif /* _WIN32 */ + +#ifdef __linux__ +#define LIBSWR "libswitchres.so" +#elif _WIN32 +#define LIBSWR "libswitchres.dll" +#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; + + +/* Declaration of the wrapper functions */ +MODULE_API void sr_init(); +MODULE_API void sr_load_ini(char* config); +MODULE_API void sr_deinit(); +MODULE_API unsigned char sr_init_disp(const char* src); +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); +MODULE_API void sr_set_user_mode(int, int, int); + +/* Logging related functions */ +MODULE_API void sr_set_log_level (int); +MODULE_API void sr_set_log_callback_error(void *); +MODULE_API void sr_set_log_callback_info(void *); +MODULE_API void sr_set_log_callback_debug(void *); + + +/* Inspired by https://stackoverflow.com/a/1067684 */ +typedef struct MODULE_API { + void (*init)(void); + void (*sr_sr_load_ini)(char*); + void (*deinit)(void); + unsigned char (*sr_init_disp)(const char*); + 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_monitor)(const char*); + void (*sr_set_rotation)(unsigned char); + void (*sr_set_user_mode)(int, int, int); + void (*sr_set_log_level) (int); + void (*sr_set_log_callback_error)(void *); + void (*sr_set_log_callback_info)(void *); + void (*sr_set_log_callback_debug)(void *); +} srAPI; + + +#ifdef __cplusplus +} +#endif diff --git a/gfx/common/win32_common.c b/gfx/common/win32_common.c index 200d545e70..5810fd061f 100644 --- a/gfx/common/win32_common.c +++ b/gfx/common/win32_common.c @@ -642,6 +642,26 @@ static void win32_save_position(void) } } +static void win32_resize_after_display_change(HWND hwnd) +{ + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + + if (monitor != NULL) + { + MONITORINFO info; + memset(&info, 0, sizeof(info)); + info.cbSize = sizeof(info); + + if (GetMonitorInfo(monitor, &info)) + { + int new_width = abs(info.rcMonitor.right - info.rcMonitor.left); + int new_height = abs(info.rcMonitor.bottom - info.rcMonitor.top); + + SetWindowPos(hwnd, 0, 0, 0, new_width, new_height, SWP_NOMOVE); + } + } +} + static bool win32_browser( HWND owner, char *filename, @@ -1049,6 +1069,9 @@ static LRESULT CALLBACK wnd_proc_common_internal(HWND hwnd, win32_clip_window(false); break; #endif + case WM_DISPLAYCHANGE: /* fix size after display mode switch when using SR */ + win32_resize_after_display_change(hwnd); + break; } return DefWindowProc(hwnd, message, wparam, lparam); @@ -1165,6 +1188,9 @@ static LRESULT CALLBACK wnd_proc_common_dinput_internal(HWND hwnd, win32_clip_window(false); break; #endif + case WM_DISPLAYCHANGE: /* fix size after display mode switch when using SR */ + win32_resize_after_display_change(hwnd); + break; } return DefWindowProc(hwnd, message, wparam, lparam); diff --git a/gfx/video_crt_switch.c b/gfx/video_crt_switch.c index 5c25f3de08..3ab7d170cb 100644 --- a/gfx/video_crt_switch.c +++ b/gfx/video_crt_switch.c @@ -19,10 +19,22 @@ #include #include #include +#include +#include +#include #include "../retroarch.h" +#include #include "video_crt_switch.h" #include "video_display_server.h" +#include "../core_info.h" +#include "../verbosity.h" +#include "gfx_display.h" + +#if !defined(HAVE_VIDEOCORE) +#include "../deps/switchres/switchres_wrapper.h" +static sr_mode srm; +#endif #ifdef HAVE_CONFIG_H #include "../config.h" @@ -30,231 +42,352 @@ #if defined(HAVE_VIDEOCORE) #include "include/userland/interface/vmcs_host/vc_vchi_gencmd.h" -static void crt_rpi_switch(int width, int height, float hz, int xoffset); +static void crt_rpi_switch(videocrt_switch_t *p_switch,int width, int height, float hz, int xoffset, int native_width); #endif -static void switch_crt_hz(videocrt_switch_t *p_switch) +static bool crt_check_for_changes(videocrt_switch_t *p_switch) { - float ra_core_hz = p_switch->ra_core_hz; + if ((p_switch->ra_tmp_height != p_switch->ra_core_height) || + (p_switch->ra_core_width != p_switch->ra_tmp_width) || + (p_switch->center_adjust != p_switch->tmp_center_adjust|| + p_switch->porch_adjust != p_switch->tmp_porch_adjust ) || + (p_switch->ra_core_hz != p_switch->ra_tmp_core_hz)) + return true; + - /* set hz float to an int for windows switching */ - if (ra_core_hz < 100) - { - if (ra_core_hz < 53) - p_switch->ra_set_core_hz = 50; - if (ra_core_hz >= 53 && ra_core_hz < 57) - p_switch->ra_set_core_hz = 55; - if (ra_core_hz >= 57) - p_switch->ra_set_core_hz = 60; - } - - if (ra_core_hz > 100) - { - if (ra_core_hz < 106) - p_switch->ra_set_core_hz = 120; - if (ra_core_hz >= 106 && ra_core_hz < 114) - p_switch->ra_set_core_hz = 110; - if (ra_core_hz >= 114) - p_switch->ra_set_core_hz = 120; - } - - video_monitor_set_refresh_rate(p_switch->ra_set_core_hz); - - p_switch->ra_tmp_core_hz = ra_core_hz; + return false; } -static void crt_aspect_ratio_switch( - videocrt_switch_t *p_switch, - unsigned width, unsigned height) +static void crt_store_temp_changes(videocrt_switch_t *p_switch) { - /* send aspect float to video_driver */ - p_switch->fly_aspect = (float)width / height; - video_driver_set_aspect_ratio_value((float)p_switch->fly_aspect); -} - -static void switch_res_crt( - videocrt_switch_t *p_switch, - unsigned width, unsigned height) -{ - video_display_server_set_resolution(width, height, - p_switch->ra_set_core_hz, - p_switch->ra_core_hz, - p_switch->center_adjust, - p_switch->index, - p_switch->center_adjust, - p_switch->porch_adjust); - -#if defined(HAVE_VIDEOCORE) - crt_rpi_switch(width, height, - p_switch->ra_core_hz, - p_switch->center_adjust); - video_monitor_set_refresh_rate(p_switch->ra_core_hz); - crt_switch_driver_reinit(); -#endif - video_driver_apply_state_changes(); -} - -/* Create correct aspect to fit video - * if resolution does not exist */ -static void crt_screen_setup_aspect( - videocrt_switch_t *p_switch, - unsigned width, unsigned height) -{ -#if defined(HAVE_VIDEOCORE) - if (height > 300) - height = height/2; -#endif - - if (p_switch->ra_core_hz != p_switch->ra_tmp_core_hz) - switch_crt_hz(p_switch); - - /* Get original resolution of core */ - if (height == 4) - { - /* Detect menu only */ - if (width < 700) - width = 320; - - height = 240; - - crt_aspect_ratio_switch(p_switch, width, height); - } - - if (height < 200 && height != 144) - { - crt_aspect_ratio_switch(p_switch, width, height); - height = 200; - } - - if (height > 200) - crt_aspect_ratio_switch(p_switch, width, height); - - if (height == 144 && p_switch->ra_set_core_hz == 50) - { - height = 288; - crt_aspect_ratio_switch(p_switch, width, height); - } - - if (height > 200 && height < 224) - { - crt_aspect_ratio_switch(p_switch, width, height); - height = 224; - } - - if (height > 224 && height < 240) - { - crt_aspect_ratio_switch(p_switch, width, height); - height = 240; - } - - if (height > 240 && height < 255) - { - crt_aspect_ratio_switch(p_switch, width, height); - height = 254; - } - - if (height == 528 && p_switch->ra_set_core_hz == 60) - { - crt_aspect_ratio_switch(p_switch, width, height); - height = 480; - } - - if (height >= 240 && height < 255 && p_switch->ra_set_core_hz == 55) - { - crt_aspect_ratio_switch(p_switch, width, height); - height = 254; - } - - switch_res_crt(p_switch, width, height); -} - -static int crt_compute_dynamic_width( - videocrt_switch_t *p_switch, - int width) -{ - unsigned i; - int dynamic_width = 0; - unsigned min_height = 261; - -#if defined(HAVE_VIDEOCORE) - p_switch->p_clock = 32000000; -#else - p_switch->p_clock = 21000000; -#endif - - for (i = 0; i < 10; i++) - { - dynamic_width = width * i; - if ((dynamic_width * min_height * p_switch->ra_core_hz) - > p_switch->p_clock) - break; - } - return dynamic_width; -} - -void crt_switch_res_core( - videocrt_switch_t *p_switch, - unsigned width, unsigned height, - float hz, unsigned crt_mode, - int crt_switch_center_adjust, - int crt_switch_porch_adjust, - int monitor_index, bool dynamic) -{ - /* ra_core_hz float passed from within - * video_driver_monitor_adjust_system_rates() */ - if (width == 4) - { - width = 320; - height = 240; - } - - p_switch->porch_adjust = crt_switch_porch_adjust; - p_switch->ra_core_height = height; - p_switch->ra_core_hz = hz; - - if (dynamic) - p_switch->ra_core_width = crt_compute_dynamic_width(p_switch, width); - else - p_switch->ra_core_width = width; - - p_switch->center_adjust = crt_switch_center_adjust; - p_switch->index = monitor_index; - - if (crt_mode == 2) - { - if (hz > 53) - p_switch->ra_core_hz = hz * 2; - if (hz <= 53) - p_switch->ra_core_hz = 120.0f; - } - - /* Detect resolution change and switch */ - if ( - (p_switch->ra_tmp_height != p_switch->ra_core_height) || - (p_switch->ra_core_width != p_switch->ra_tmp_width) || - (p_switch->center_adjust != p_switch->tmp_center_adjust|| - p_switch->porch_adjust != p_switch->tmp_porch_adjust ) - ) - crt_screen_setup_aspect( - p_switch, - p_switch->ra_core_width, - p_switch->ra_core_height); - p_switch->ra_tmp_height = p_switch->ra_core_height; p_switch->ra_tmp_width = p_switch->ra_core_width; p_switch->tmp_center_adjust = p_switch->center_adjust; - p_switch->tmp_porch_adjust = p_switch->porch_adjust; + p_switch->tmp_porch_adjust = p_switch->porch_adjust; + p_switch->ra_tmp_core_hz = p_switch->ra_core_hz; - /* Check if aspect is correct, if not change */ - if (video_driver_get_aspect_ratio() != p_switch->fly_aspect) - { - video_driver_set_aspect_ratio_value((float)p_switch->fly_aspect); - video_driver_apply_state_changes(); - } } +static void switch_crt_hz(videocrt_switch_t *p_switch) +{ + video_monitor_set_refresh_rate(p_switch->sr_core_hz); + +} + + +static void crt_aspect_ratio_switch( + videocrt_switch_t *p_switch, + unsigned width, unsigned height, unsigned srm_width, unsigned srm_height) +{ + + /* send aspect float to video_driver */ + RARCH_LOG("[CRT]: Setting Video Screen Size to: %dx%d \n", width, height); + video_driver_set_size(srm_width , srm_height); + video_driver_set_viewport(srm_width , srm_height,1,1); + + p_switch->fly_aspect = (float)width / (float)height; + video_driver_set_aspect_ratio_value((float)p_switch->fly_aspect); + RARCH_LOG("[CRT]: Setting Aspect Ratio: %f \n", (float)p_switch->fly_aspect); + + video_driver_apply_state_changes(); + +} + +static void set_aspect(videocrt_switch_t *p_switch, unsigned int width, + unsigned int height, unsigned int srm_width, unsigned srm_height, + unsigned int srm_xscale, unsigned srm_yscale) +{ + unsigned int patched_width = 0; + unsigned int patched_height = 0; + int scaled_width = 0; + int scaled_height = 0; + /* used to fix aspect shoule SR not find a resolution */ + if (srm_width == 0) + { + video_driver_get_size(&patched_width, &patched_height); + }else{ + patched_width = width; + patched_height = height; + } + scaled_width = roundf(patched_width*srm_xscale); + scaled_height = roundf(patched_height*srm_yscale); + + crt_aspect_ratio_switch(p_switch, scaled_width, scaled_height, srm_width, srm_height); +} +#if !defined(HAVE_VIDEOCORE) +static bool crt_sr2_init(videocrt_switch_t *p_switch, int monitor_index, unsigned int crt_mode, unsigned int super_width) +{ + const char* err_msg; + char* mode; + char index = 0; + char mindex[1]; + + if (monitor_index+1 >= 0 && monitor_index+1 < 10) + index = monitor_index+48; + else + index = '0'; + + mindex[0] = index; + + if (!p_switch->sr2_active) + { + + RARCH_LOG("[CRT]: SR init \n"); + + + sr_init(); + #if (__STDC_VERSION__ >= 199409L) /* no logs for C98 or less */ + sr_set_log_callback_info(RARCH_LOG); + sr_set_log_callback_debug(RARCH_DBG); + sr_set_log_callback_error(RARCH_ERR); + #endif + + if (crt_mode == 1) + { + sr_set_monitor("arcade_15"); + RARCH_LOG("[CRT]: CRT Mode: %d - arcade_15 \n", crt_mode) ; + }else if (crt_mode == 2) + { + sr_set_monitor("arcade_31"); + RARCH_LOG("[CRT]: CRT Mode: %d - arcade_31 \n", crt_mode) ; + }else if (crt_mode == 3) + { + sr_set_monitor("pc_31_120"); + RARCH_LOG("[CRT]: CRT Mode: %d - pc_31_120 \n", crt_mode) ; + }else if (crt_mode == 4) + { + RARCH_LOG("[CRT]: CRT Mode: %d - Selected from ini \n", crt_mode) ; + } + + + if (super_width >2 ) + sr_set_user_mode(super_width, 0, 0); + + RARCH_LOG("[CRT]: SR init_disp \n"); + if (monitor_index+1 > 0) + { + RARCH_LOG("SRobj: RA Monitor Index: %s\n",mindex); + p_switch->rtn = sr_init_disp(mindex); + RARCH_LOG("[CRT]: SR Disp Monitor Index: %s \n", mindex); + } + + if (monitor_index == -1) + { + RARCH_LOG("SRobj: RA Monitor Index: %s\n",NULL); + p_switch->rtn = sr_init_disp(NULL); + RARCH_LOG("[CRT]: SR Disp Monitor Index: Auto \n"); + } + + RARCH_LOG("[CRT]: SR rtn %d \n", p_switch->rtn); + + } + + if (p_switch->rtn == 1) + { + p_switch->sr2_active = true; + return true; + }else{ + RARCH_LOG("[CRT]: SR failed to init \n"); + sr_deinit(); + p_switch->sr2_active = false; + } + + return false; +} + + +static void switch_res_crt( + videocrt_switch_t *p_switch, + unsigned width, unsigned height, unsigned crt_mode, unsigned native_width, int monitor_index, int super_width) +{ + unsigned char interlace = 0, ret; + const char* err_msg; + int w = native_width, h = height; + double rr = p_switch->ra_core_hz; + + if (crt_sr2_init(p_switch, monitor_index, crt_mode, super_width)) /* Checked SR2 is loded if not Load it */ + { + + ret = sr_switch_to_mode(w, h, rr, interlace, &srm); + if(!ret) + { + RARCH_LOG("[CRT]: SR failed to switch mode"); + /*sr_deinit();*/ + + } + p_switch->sr_core_hz = srm.refresh; + + set_aspect(p_switch, w , h, srm.width, srm.height, srm.x_scale, srm.y_scale); + + RARCH_LOG("[CRT]: SR scaled X:%d Y:%d \n",srm.x_scale, srm.y_scale); + + }else { + set_aspect(p_switch, width , height, width, height ,1,1); + video_driver_set_size(width , height); + video_driver_apply_state_changes(); + + } +} +#endif +void crt_destroy_modes(videocrt_switch_t *p_switch) +{ + + if (p_switch->sr2_active == true) + { + p_switch->sr2_active = false; + sr_deinit(); + /*RARCH_LOG("[CRT]: SR Destroyed \n"); */ + + } + +} + +static void crt_check_hh_core(videocrt_switch_t *p_switch) +{ + /* + char* handheld[8] = {"mGBA","Gambatte","gpSP","Gearboy","VBA Next","VBA-M","SameBoy","TGB Dual"}; + int i = 0; + for(i = 0; i < 7; i++) + { + if (strcmp(handheld[i],p_switch->core_name) == 0) + { + RARCH_LOG("[CRT]: Handheld core detected %s adjusting resolutions.\n", p_switch->core_name); + p_switch->hh_core = true; + break; + } + else + { + p_switch->hh_core = false; + } + + + } + + */ + p_switch->hh_core = false; + +} +#if !defined(HAVE_VIDEOCORE) +static void crt_fix_hh_res(videocrt_switch_t *p_switch, int native_width, int width, + int height, int crt_mode, int monitor_index, int super_width) +{ + int corrected_width = 320; + int corrected_height = 240; + + switch_res_crt(p_switch, corrected_width, corrected_height , crt_mode, corrected_width, monitor_index-1, super_width); + set_aspect(p_switch, native_width , height, native_width, height ,1,1); + video_driver_set_size(native_width , height); + + +} +#endif +/* +static void crt_menu_restore(videocrt_switch_t *p_switch) +{ + + video_driver_get_size(&p_switch->fb_width, &p_switch->fb_height); + RARCH_LOG("[CRT]: Menu Only Restoring Aspect: %dx%d \n", p_switch->fb_width, p_switch->fb_height); + crt_aspect_ratio_switch(p_switch, p_switch->fb_width, p_switch->fb_height, p_switch->fb_width, p_switch->fb_height); + +} + +static bool crt_get_desktop_res(videocrt_switch_t *p_switch, unsigned width, unsigned height, float hz) +{ + if (p_switch->menu_active == false) + { + if (p_switch->fb_width == 0) + video_driver_get_size(&p_switch->fb_width, &p_switch->fb_height); + + p_switch->fb_ra_core_hz = 60.0; + RARCH_LOG("[CRT]: Storing Desktop Resolution: %dx%d@%f \n", p_switch->fb_width, p_switch->fb_height, p_switch->fb_ra_core_hz); + crt_menu_restore(p_switch); + p_switch->menu_active = true; + return true; + } + return false; +} +*/ + +void crt_switch_res_core( + videocrt_switch_t *p_switch, + unsigned native_width, unsigned width, unsigned height, + float hz, unsigned crt_mode, + int crt_switch_center_adjust, + int crt_switch_porch_adjust, + int monitor_index, bool dynamic, + int super_width, bool hires_menu) +{ + + if (height <= 4) + { + if (hires_menu == true) + { + native_width = 640; + width = 640; + height = 480; + hz = 60; + }else{ + native_width = 320; + width = 320; + height = 240; + hz = 60; + } + } + + + if (height != 4 ) + { + + p_switch->menu_active = false; + p_switch->porch_adjust = crt_switch_porch_adjust; + p_switch->ra_core_height = height; + p_switch->ra_core_hz = hz; + + p_switch->ra_core_width = width; + + p_switch->center_adjust = crt_switch_center_adjust; + p_switch->index = monitor_index; + + if (p_switch->core_name != crt_switch_core_name()) + { + p_switch->core_name = crt_switch_core_name(); + RARCH_LOG("[CRT]: Current running core %s \n", p_switch->core_name); + crt_check_hh_core(p_switch); + + } + + /* Detect resolution change and switch */ + if (crt_check_for_changes(p_switch)) + { + RARCH_LOG("[CRT]: Requested Reolution: %dx%d@%f \n", native_width, height, hz); + #if defined(HAVE_VIDEOCORE) + crt_rpi_switch(p_switch, width, height, hz, 0, native_width); + #else + + if (p_switch->hh_core == false) + switch_res_crt(p_switch, p_switch->ra_core_width, p_switch->ra_core_height , crt_mode, native_width, monitor_index-1, super_width); + else + crt_fix_hh_res(p_switch, native_width, width, height, crt_mode, monitor_index, super_width); + + #endif + switch_crt_hz(p_switch); + crt_store_temp_changes(p_switch); + } + + if (video_driver_get_aspect_ratio() != p_switch->fly_aspect) + { + RARCH_LOG("[CRT]: Restoring Aspect Ratio: %f \n", (float)p_switch->fly_aspect); + video_driver_set_aspect_ratio_value((float)p_switch->fly_aspect); + video_driver_apply_state_changes(); + } + + } + +} + +/* only used for RPi3 */ #if defined(HAVE_VIDEOCORE) -static void crt_rpi_switch(int width, int height, float hz, int xoffset) +static void crt_rpi_switch(videocrt_switch_t *p_switch, int width, int height, float hz, int xoffset, int native_width) { char buffer[1024]; VCHI_INSTANCE_T vchi_instance; @@ -279,25 +412,37 @@ static void crt_rpi_switch(int width, int height, float hz, int xoffset) float roundw = 0.0f; float roundh = 0.0f; float pixel_clock = 0.0f; + int xscale = 1; + int yscale = 1; + + if (height > 300) + height = height/2; /* set core refresh from hz */ video_monitor_set_refresh_rate(hz); - /* following code is the mode line generator */ - hsp = (width * 0.117) - (xoffset*4); - if (width < 700) + set_aspect(p_switch, width, + height, width, height, + 1, 1); + int w = width; + while (w < 1920) { - hfp = (width * 0.065); - hbp = width * 0.35-hsp-hfp; - } - else - { - hfp = (width * 0.033) + (width / 112); - hbp = (width * 0.225) + (width /58); - xoffset = xoffset*2; + w = w+width; } - hmax = hbp; + if (w > 2000) + w =w- width; + + width = w; + + crt_aspect_ratio_switch(p_switch, width,height); + + /* following code is the mode line generator */ + hfp = ((width * 0.044) + (width / 112)); + hbp = ((width * 0.172) + (width /64)); + + + hsp = (width * 0.117); if (height < 241) vmax = 261; @@ -376,5 +521,7 @@ static void crt_rpi_switch(int width, int height, float hz, int xoffset) "fbset -g %d %d %d %d 24 > /dev/null", width, height, width, height); system(output2); + + crt_switch_driver_refresh(); } #endif diff --git a/gfx/video_crt_switch.h b/gfx/video_crt_switch.h index 68a90aa905..68742bfc8b 100644 --- a/gfx/video_crt_switch.h +++ b/gfx/video_crt_switch.h @@ -25,6 +25,7 @@ #include #include + RETRO_BEGIN_DECLS typedef struct videocrt_switch @@ -35,21 +36,33 @@ typedef struct videocrt_switch int porch_adjust; int tmp_porch_adjust; int tmp_center_adjust; + int rtn; unsigned ra_core_width; unsigned ra_core_height; unsigned ra_tmp_width; unsigned ra_tmp_height; unsigned ra_set_core_hz; unsigned index; + unsigned int fb_width; + unsigned int fb_height; float ra_core_hz; + float sr_core_hz; float ra_tmp_core_hz; float fly_aspect; + float fb_ra_core_hz; + + bool sr2_active; + bool menu_active; + char* core_name; + bool hh_core; + } videocrt_switch_t; void crt_switch_res_core( videocrt_switch_t *p_switch, + unsigned naitive_width, unsigned width, unsigned height, float hz, @@ -57,7 +70,11 @@ void crt_switch_res_core( int crt_switch_center_adjust, int crt_switch_porch_adjust, int monitor_index, - bool dynamic); + bool dynamic, + int super_width, + bool hires_menu); + +void crt_destroy_modes(videocrt_switch_t *p_switch); RETRO_END_DECLS diff --git a/griffin/griffin.c b/griffin/griffin.c index d9bc61da3a..4407f9f06c 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -971,7 +971,7 @@ MIDI /*============================================================ DRIVERS ============================================================ */ -#include "../gfx/video_crt_switch.c" +/*#include "../gfx/video_crt_switch.c" */ #include "../gfx/gfx_animation.c" #include "../gfx/gfx_display.c" #include "../gfx/gfx_thumbnail_path.c" diff --git a/input/drivers/winraw_input.c b/input/drivers/winraw_input.c index be2c0c2209..c50d7cfe8c 100644 --- a/input/drivers/winraw_input.c +++ b/input/drivers/winraw_input.c @@ -62,6 +62,7 @@ typedef struct /* TODO/FIXME - static globals */ static winraw_mouse_t *g_mice = NULL; +static RECT *prev_rect = NULL; /* Needed to store RECT to checking for a windows size change */ #define WINRAW_KEYBOARD_PRESSED(wr, key) (wr->keyboard.keys[rarch_keysym_lut[(enum retro_key)(key)]]) @@ -337,6 +338,19 @@ static void winraw_update_mouse_state(winraw_input_t *wr, winraw_mouse_t *mouse, RAWMOUSE *state) { POINT crs_pos; + RECT *tmp_rect = NULL; + /* used for fixing cordinates after switching resolutions */ + GetClientRect((HWND)video_driver_window_get(), tmp_rect); + if (!prev_rect) + { + GetClientRect((HWND)video_driver_window_get(), prev_rect); + winraw_init_mouse_xy_mapping(wr); + } + else if (tmp_rect != prev_rect) + { + GetClientRect((HWND)video_driver_window_get(), prev_rect); + winraw_init_mouse_xy_mapping(wr); + } if (state->usFlags & MOUSE_MOVE_ABSOLUTE) { diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 5a626c4b9e..0d48d4b155 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -4472,6 +4472,10 @@ MSG_HASH( MENU_ENUM_LABEL_CHEAT_DELETE_MATCH, "cheat_delete_match" ) +MSG_HASH( + MENU_ENUM_LABEL_CRT_SWITCH_HIRES_MENU, + "crt_switch_hires_menu" + ) MSG_HASH( MENU_ENUM_LABEL_CRT_SWITCH_RESOLUTION_USE_CUSTOM_REFRESH_RATE, "crt_switch_resolution_use_custom_refresh_rate" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 6fa127a0ac..80a4f6cd87 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -1437,9 +1437,17 @@ MSG_HASH( MENU_ENUM_SUBLABEL_CRT_SWITCH_PORCH_ADJUST, "Cycle through these options to adjust the porch settings to change the image size." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CRT_SWITCH_HIRES_MENU, + "Use high resolution menu" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CRT_SWITCH_HIRES_MENU, + "Use high resolution menu" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_CRT_SWITCH_RESOLUTION_USE_CUSTOM_REFRESH_RATE, - "Use Custom Refresh Rate" + "Use a custom refresh rate specified in the configuration file if needed." ) MSG_HASH( MENU_ENUM_SUBLABEL_CRT_SWITCH_RESOLUTION_USE_CUSTOM_REFRESH_RATE, diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index f0103d59fb..3506c0f45f 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -137,6 +137,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_crt_switchres_super, MENU_ENUM DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_crt_switchres_x_axis_centering, MENU_ENUM_SUBLABEL_CRT_SWITCH_X_AXIS_CENTERING) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_crt_switchres_porch_adjust, MENU_ENUM_SUBLABEL_CRT_SWITCH_PORCH_ADJUST) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_crt_switchres_use_custom_refresh_rate, MENU_ENUM_SUBLABEL_CRT_SWITCH_RESOLUTION_USE_CUSTOM_REFRESH_RATE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_crt_switchres_hires_menu, MENU_ENUM_SUBLABEL_CRT_SWITCH_HIRES_MENU) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_driver_settings_list, MENU_ENUM_SUBLABEL_DRIVER_SETTINGS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_retro_achievements_settings_list, MENU_ENUM_SUBLABEL_RETRO_ACHIEVEMENTS_SETTINGS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_saving_settings_list, MENU_ENUM_SUBLABEL_SAVING_SETTINGS) @@ -1839,6 +1840,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CRT_SWITCH_RESOLUTION_USE_CUSTOM_REFRESH_RATE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_crt_switchres_use_custom_refresh_rate); break; + case MENU_ENUM_LABEL_CRT_SWITCH_HIRES_MENU: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_crt_switchres_hires_menu); + break; case MENU_ENUM_LABEL_AUDIO_RESAMPLER_QUALITY: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_audio_resampler_quality); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index b2f26bad9a..e8388a309a 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -7799,6 +7799,7 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_CRT_SWITCH_X_AXIS_CENTERING, PARSE_ONLY_INT }, {MENU_ENUM_LABEL_CRT_SWITCH_PORCH_ADJUST, PARSE_ONLY_INT }, {MENU_ENUM_LABEL_CRT_SWITCH_RESOLUTION_USE_CUSTOM_REFRESH_RATE, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_CRT_SWITCH_HIRES_MENU, PARSE_ONLY_BOOL}, }; for (i = 0; i < ARRAY_SIZE(build_list); i++) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 1d5067ca64..fd26c65e07 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -6132,7 +6132,13 @@ static void setting_get_string_representation_uint_crt_switch_resolutions( strlcpy(s, "15 KHz", len); break; case CRT_SWITCH_31KHZ: - strlcpy(s, "31 KHz", len); + strlcpy(s, "31 KHz, Standard", len); + break; + case CRT_SWITCH_32_120: + strlcpy(s, "31 KHz, 120Hz", len); + break; + case CRT_SWITCH_INI: + strlcpy(s, "INI", len); break; } } @@ -11661,7 +11667,7 @@ static bool setting_append_list( (*list)[list_info->index - 1].get_string_representation = &setting_get_string_representation_uint_crt_switch_resolutions; SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ADVANCED); - menu_settings_list_current_add_range(list, list_info, CRT_SWITCH_NONE, CRT_SWITCH_31KHZ, 1.0, true, true); + menu_settings_list_current_add_range(list, list_info, CRT_SWITCH_NONE, CRT_SWITCH_INI, 1.0, true, true); CONFIG_UINT( list, list_info, @@ -11730,6 +11736,22 @@ static bool setting_append_list( SD_FLAG_NONE ); + CONFIG_BOOL( + list, list_info, + &settings->bools.crt_switch_hires_menu, + MENU_ENUM_LABEL_CRT_SWITCH_HIRES_MENU, + MENU_ENUM_LABEL_VALUE_CRT_SWITCH_HIRES_MENU, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE + ); + END_SUB_GROUP(list, list_info, parent_group); END_GROUP(list, list_info, parent_group); break; diff --git a/msg_hash.h b/msg_hash.h index 8a9ec5d700..c5f6e6e74a 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1014,6 +1014,7 @@ enum msg_hash_enums MENU_LABEL(CRT_SWITCH_RESOLUTION), MENU_LABEL(CRT_SWITCH_RESOLUTION_SUPER), MENU_LABEL(CRT_SWITCH_RESOLUTION_OUTPUT_DISPLAY_ID), + MENU_LABEL(CRT_SWITCH_HIRES_MENU), MENU_LABEL(CRT_SWITCH_RESOLUTION_USE_CUSTOM_REFRESH_RATE), MENU_LABEL(CRT_SWITCH_X_AXIS_CENTERING), MENU_LABEL(CRT_SWITCH_PORCH_ADJUST), diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 361766246b..cd27f772bc 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -262,6 +262,7 @@ if [ "$OS" = 'Darwin' ]; then check_lib '' AL "-framework OpenAL" alcOpenDevice HAVE_X11=no # X11 breaks on recent OSXes even if present. HAVE_SDL=no + HAVE_SW2=no else check_lib '' AL -lopenal alcOpenDevice fi @@ -676,3 +677,11 @@ fi check_enabled 'ZLIB BUILTINZLIB' RPNG RPNG 'zlib is' false check_enabled V4L2 VIDEOPROCESSOR 'video processor' 'Video4linux2 is' true + +if [ "$HAVE_CXX11" = 'yes' ]; then + if [ "$OS" = 'Linux' ]; then + check_enabled 'VIDEOCORE X11' SR2 'CRT modeswitching' 'CRT is' true + else + check_platform Win32 SR2 'CRT modeswitching is' true + fi +fi diff --git a/qb/config.params.sh b/qb/config.params.sh index c7f2d8ce72..386f5929dc 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -195,3 +195,5 @@ HAVE_ODROIDGO2=no # ODROID-GO Advance rotation support (requires librga HAVE_LIBSHAKE=no # libShake haptic feedback support HAVE_CHECK=no # check support for unit tests HAVE_WIFI=no # wifi driver support +HAVE_CRTSWITCHRES=auto # CRT mode switching support +C89_CRTSWITCHRES=no diff --git a/retroarch.c b/retroarch.c index 5ab1ba30a6..a7f56cf2a1 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2,9 +2,9 @@ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2012-2015 - Michael Lelli - * Copyright (C) 2014-2017 - Jean-André Santoni + * Copyright (C) 2014-2017 - Jean-Andr� Santoni * Copyright (C) 2016-2019 - Brad Parker - * Copyright (C) 2016-2019 - Andrés Suárez (input mapper/Discord code) + * Copyright (C) 2016-2019 - Andr�s Su�rez (input mapper/Discord code) * Copyright (C) 2016-2017 - Gregor Richards (network code) * * RetroArch is free software: you can redistribute it and/or modify it under the terms @@ -226,7 +226,9 @@ #include "gfx/video_thread_wrapper.h" #endif #include "gfx/video_display_server.h" -#include "gfx/video_crt_switch.h" +#ifdef HAVE_SR2 + #include "gfx/video_crt_switch.h" +#endif #include "bluetooth/bluetooth_driver.h" #include "wifi/wifi_driver.h" #include "misc/cpufreq/cpufreq.h" @@ -31600,6 +31602,7 @@ static void video_driver_frame(const void *data, unsigned width, #if defined(HAVE_GFX_WIDGETS) bool widgets_active = p_rarch->widgets_active; #endif + static int native_width = 0; status_text[0] = '\0'; video_driver_msg[0] = '\0'; @@ -31969,7 +31972,7 @@ static void video_driver_frame(const void *data, unsigned width, if (video_info.crt_switch_resolution) { p_rarch->video_driver_crt_switching_active = true; - + native_width = width; switch (video_info.crt_switch_resolution_super) { case 2560: @@ -31986,27 +31989,36 @@ static void video_driver_frame(const void *data, unsigned width, p_rarch->video_driver_crt_dynamic_super_width = false; break; } - + #if defined(HAVE_SR2) && !defined(ANDROID) crt_switch_res_core( &p_rarch->crt_switch_st, - width, + native_width, width, height, p_rarch->video_driver_core_hz, video_info.crt_switch_resolution, video_info.crt_switch_center_adjust, video_info.crt_switch_porch_adjust, video_info.monitor_index, - p_rarch->video_driver_crt_dynamic_super_width); + p_rarch->video_driver_crt_dynamic_super_width, + video_info.crt_switch_resolution_super, + video_info.crt_switch_hires_menu); + #endif } else if (!video_info.crt_switch_resolution) p_rarch->video_driver_crt_switching_active = false; } -void crt_switch_driver_reinit(void) +void crt_switch_driver_refresh(void) { + /*video_context_driver_reset();*/ video_driver_reinit(DRIVERS_CMD_ALL); } +char* crt_switch_core_name(void) +{ + return (char*)runloop_state.system.info.library_name; +} + void video_driver_display_type_set(enum rarch_display_type type) { struct rarch_state *p_rarch = &rarch_st; @@ -32106,7 +32118,8 @@ void video_driver_build_info(video_frame_info_t *video_info) video_info->crt_switch_resolution = settings->uints.crt_switch_resolution; video_info->crt_switch_resolution_super = settings->uints.crt_switch_resolution_super; video_info->crt_switch_center_adjust = settings->ints.crt_switch_center_adjust; - video_info->crt_switch_porch_adjust = settings->ints.crt_switch_porch_adjust; + video_info->crt_switch_porch_adjust = settings->ints.crt_switch_porch_adjust; + video_info->crt_switch_hires_menu = settings->bools.crt_switch_hires_menu; video_info->black_frame_insertion = settings->uints.video_black_frame_insertion; video_info->hard_sync = settings->bools.video_hard_sync; video_info->hard_sync_frames = settings->uints.video_hard_sync_frames; @@ -33504,6 +33517,15 @@ static void retroarch_deinit_drivers( } #endif + /* Switchres deinit */ + if (p_rarch->video_driver_crt_switching_active) { + /* RARCH_LOG("[CRT]: Getting video info\n"); + RARCH_LOG("[CRT]: About to destroy SR\n"); + */ + #ifdef HAVE_SR2 + crt_destroy_modes(&p_rarch->crt_switch_st); + #endif + } /* Video */ video_display_server_destroy(); @@ -33551,6 +33573,7 @@ static void retroarch_deinit_drivers( cbs->state_cb = NULL; p_rarch->current_core.inited = false; + } bool driver_ctl(enum driver_ctl_state state, void *data) diff --git a/retroarch.h b/retroarch.h index 547d14ba51..682fc115eb 100644 --- a/retroarch.h +++ b/retroarch.h @@ -1237,6 +1237,8 @@ typedef struct video_frame_info bool menu_is_alive; bool menu_screensaver_active; bool msg_bgcolor_enable; + bool crt_switch_hires_menu; + } video_frame_info_t; typedef void (*update_window_title_cb)(void*); @@ -1715,7 +1717,9 @@ void video_monitor_set_refresh_rate(float hz); bool video_monitor_fps_statistics(double *refresh_rate, double *deviation, unsigned *sample_points); -void crt_switch_driver_reinit(void); +void crt_switch_driver_refresh(void); + +char* crt_switch_core_name(void); #define video_driver_translate_coord_viewport_wrap(vp, mouse_x, mouse_y, res_x, res_y, res_screen_x, res_screen_y) \ (video_driver_get_viewport_info(vp) ? video_driver_translate_coord_viewport(vp, mouse_x, mouse_y, res_x, res_y, res_screen_x, res_screen_y) : false) diff --git a/retroarch_data.h b/retroarch_data.h index f038725499..5bd5c9236c 100644 --- a/retroarch_data.h +++ b/retroarch_data.h @@ -1719,8 +1719,9 @@ struct rarch_state double audio_source_ratio_original; double audio_source_ratio_current; struct retro_system_av_info video_driver_av_info; /* double alignment */ + #ifdef HAVE_SR2 videocrt_switch_t crt_switch_st; /* double alignment */ - + #endif retro_time_t frame_limit_minimum_time; retro_time_t frame_limit_last_time; retro_time_t libretro_core_runtime_last; @@ -2626,7 +2627,7 @@ struct key_desc key_descriptors[RARCH_MAX_KEYS] = {RETROK_BREAK, "Break"}, {RETROK_MENU, "Menu"}, {RETROK_POWER, "Power"}, - {RETROK_EURO, {-30, -126, -84, 0}}, /* "€" */ + {RETROK_EURO, {-30, -126, -84, 0}}, /* "�" */ {RETROK_UNDO, "Undo"}, {RETROK_OEM_102, "OEM-102"} };