Windows: Dual Monitor Support -monitor2 cmdline param
Posted: Tue Apr 12, 2011 11:21 pm
Requires: Microsoft Visual Studio.NET 2003 or later. Won't work in MSVC6 without an SDK update (Windows 2003 SP1 SDK) that is no longer available via internet from Microsoft.
This will enable an engine to run on the secondary monitor of a dual monitor system by adding -monitor2 to the command line. What this actually does is switch the primary monitor to the secondary in Vid_Init and restore it back in Vid_Shutdown (which is called by Host_Error). So this is achieves dual monitor support for a client by cheating a little, but it isn't much different than how Quake sets everything to fullscreen and resets it back on shutdown/exit. Reason: Windows and OpenGL on dual monitors may or may not have hardware acceleration on the 2nd monitor. This method works around the issue.
Instructions
1. In the beginning of Vid_Init do something like this (in gl_vidnt.c or vid_wgl.c depending on your engine):
2. At the end of Vid_Shutdown do something like this (in gl_vidnt.c or vid_wgl.c depending on your engine):
3. Create monitors.c
In Closing
This code is only trivially locked to Quake. In fact, I think the above only uses va (...) from Quake. So it could actually be used in an Windows project pretty much unchanged.
There IS a way to do this "naturally" and just create a Window on the 2nd display, but since you will likely lose hardware acceleration I've found it isn't particularly useful (2 fps sucks). See thread ...
This will enable an engine to run on the secondary monitor of a dual monitor system by adding -monitor2 to the command line. What this actually does is switch the primary monitor to the secondary in Vid_Init and restore it back in Vid_Shutdown (which is called by Host_Error). So this is achieves dual monitor support for a client by cheating a little, but it isn't much different than how Quake sets everything to fullscreen and resets it back on shutdown/exit. Reason: Windows and OpenGL on dual monitors may or may not have hardware acceleration on the 2nd monitor. This method works around the issue.
Instructions
1. In the beginning of Vid_Init do something like this (in gl_vidnt.c or vid_wgl.c depending on your engine):
Code: Select all
if (COM_CheckParm("-monitor2"))
{
CollectMonitorInformation ();
if (num_monitors == 2) // This is written in a 2-monitor way, don't have a third monitor to test triple monitor
SwapPrimaryMonitor (); // Set monitors_swapped to 1
}
Code: Select all
if (monitors_swapped)
RestorePrimaryMonitor ();
Code: Select all
#define POINTER_64 __ptr64 //Baker
#include "quakedef.h" // Almost optional!
#include <windows.h>
#define MSGBOX(x) MessageBox(NULL, x, "Info", MB_OK); // Developer message
typedef struct
{
char DeviceName[32]; // wingdi.h --> CCHFORMNAME which is 32
int original_x;
int original_y;
int original_width;
int original_height;
} monitor_def_t;
#define MAX_NUM_MONITORS 2
monitor_def_t monitor_info[MAX_NUM_MONITORS];
int original_primary = -1;
int original_secondary = -1;
int num_monitors;
int monitors_swapped;
DISPLAY_DEVICE DisplayDevice;
char monitorstring[2048];
DEVMODE deviceMode[MAX_NUM_MONITORS];
void CollectMonitorInformation (void)
{
int i;
{
// Reset monitor info
num_monitors = 0; // Just in case
original_primary = -1; // Just in case
ZeroMemory(&DisplayDevice, sizeof(DISPLAY_DEVICE));
DisplayDevice.cb = sizeof(DisplayDevice);
}
for(i = 0; EnumDisplayDevices(NULL, i, &DisplayDevice, 0) && num_monitors < MAX_NUM_MONITORS; i++)
{
if(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER || !(DisplayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP))
continue; // Non-monitor
// Ok we found a monitor
deviceMode[num_monitors].dmSize = sizeof(DEVMODE);
deviceMode[num_monitors].dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_POSITION | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
EnumDisplaySettings(DisplayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &deviceMode[num_monitors]);
// Populate fields
strcpy (monitor_info[num_monitors].DeviceName, DisplayDevice.DeviceName);
monitor_info[num_monitors].original_x = deviceMode[num_monitors].dmPosition.x;
monitor_info[num_monitors].original_y = deviceMode[num_monitors].dmPosition.y;
monitor_info[num_monitors].original_width = deviceMode[num_monitors].dmPelsWidth;
monitor_info[num_monitors].original_height = deviceMode[num_monitors].dmPelsHeight;
if (deviceMode[num_monitors].dmPosition.x == 0 && deviceMode[num_monitors].dmPosition.y ==0)
original_primary = num_monitors; // Primary monitor is one with 0,0 xy position
else
original_secondary = num_monitors;
strcat (monitorstring, va("Monitor #%i %s %s x y w h %i %i %i %i\n",
num_monitors+1,
monitor_info[num_monitors].DeviceName,
num_monitors == original_primary ? "(Primary)" : num_monitors == original_secondary ? "(Secondary)" : "(Neither)",
monitor_info[num_monitors].original_x,
monitor_info[num_monitors].original_y,
monitor_info[num_monitors].original_width,
monitor_info[num_monitors].original_height));
num_monitors ++;
}
MSGBOX (va("Num monitors is %i", num_monitors));
MSGBOX (va("Monitor infos \n\n%s", monitorstring));
MSGBOX (va("Primary Monitor is %i and secondary is %i", original_primary, original_secondary));
}
void SetPrimaryMonitorOnDeviceName (const char *NewPrimaryMonitor, const char *NewSecondaryMonitor, int SecondaryX, int SecondaryY)
{
DEVMODE deviceMode_newSecondary;
DEVMODE deviceMode_newPrimary;
if (num_monitors == 0)
{
MSGBOX ("No monitors ... need to call CollectMonitorInformation first")
return;
}
MSGBOX (va("Setting monitor %s to primary monitor", NewPrimaryMonitor))
// Get primary infos
deviceMode_newPrimary.dmSize = sizeof(DEVMODE);
deviceMode_newPrimary.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_POSITION | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
EnumDisplaySettings(NewPrimaryMonitor, ENUM_CURRENT_SETTINGS, &deviceMode_newPrimary);
// Get secondary infos
deviceMode_newSecondary.dmSize = sizeof(DEVMODE);
deviceMode_newSecondary.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_POSITION | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS;
EnumDisplaySettings(NewSecondaryMonitor, ENUM_CURRENT_SETTINGS, &deviceMode_newSecondary);
// move old primary display to new position
deviceMode_newSecondary.dmFields = DM_POSITION;
deviceMode_newSecondary.dmPosition.x = deviceMode_newPrimary.dmPelsWidth;
deviceMode_newSecondary.dmPosition.y = 0;
ChangeDisplaySettingsEx(NewSecondaryMonitor, &deviceMode_newSecondary, NULL, CDS_UPDATEREGISTRY | CDS_NORESET, NULL);
// move old secondary display to 0,0
deviceMode_newPrimary.dmFields = DM_POSITION;
deviceMode_newPrimary.dmPosition.x = 0;
deviceMode_newPrimary.dmPosition.y = 0;
// CDS_UPDATEREGISTRY | CDS_NORESET = Have settings updated in registry, but CDS_NORESET means it won't take effect yet
ChangeDisplaySettingsEx(NewPrimaryMonitor, &deviceMode_newPrimary, NULL, CDS_SET_PRIMARY | CDS_UPDATEREGISTRY | CDS_NORESET , NULL);
ChangeDisplaySettingsEx (NULL, NULL, NULL, 0, NULL); // Force it to take effect
// Sleep (500); // Let it think a minute
}
void SwapPrimaryMonitor (void) // In a dual monitor situation, use 0 = first monitor or 1 = second monitor, etc.
{
// Set secondary monitor to primary
SetPrimaryMonitorOnDeviceName (monitor_info[original_secondary].DeviceName, monitor_info[original_primary].DeviceName, monitor_info[original_secondary].original_width, monitor_info[original_secondary].original_height);
monitors_swapped = 1;
}
void RestorePrimaryMonitor (void)
{
// Set original primary monitor back to primary
SetPrimaryMonitorOnDeviceName (monitor_info[original_primary].DeviceName, monitor_info[original_secondary].DeviceName, monitor_info[original_primary].original_width, monitor_info[original_primary].original_height);
monitors_swapped = 0;
}
This code is only trivially locked to Quake. In fact, I think the above only uses va (...) from Quake. So it could actually be used in an Windows project pretty much unchanged.
There IS a way to do this "naturally" and just create a Window on the 2nd display, but since you will likely lose hardware acceleration I've found it isn't particularly useful (2 fps sucks). See thread ...