上星期在做一個功能:如果電腦接兩個以上的螢幕,讓遊戲的全螢幕模式正常,順便研究取得螢幕解析度的方法。
首先關於全螢幕模式的做法,早期的硬體為了減少效能消耗,會修改螢幕本身的解析度和frame rate,例如原本1366×768的螢幕改成640×480。
個人不太建議這個方法,這樣會有些問題,而且現在的硬體比起那個時候要好很多了。
我的做法是不修改螢幕解析度,而是開一個跟螢幕一樣大的視窗,再用double buffer處理內定解析度不同的問題。
本篇目的是讓全螢幕模式在每個螢幕都可以用,例如視窗在螢幕2的時候將遊戲調成全螢幕模式,就讓視窗剛好佔滿螢幕2,其他螢幕不影響。
電腦接兩個螢幕的時候,人類看到的是下圖紅色的兩個螢幕,但電子妖精看到的(程式裡的坐標)只有包含全部的一個螢幕,人看到的螢幕是其中的一個範圍,如何把坐標對應到兩個螢幕是作業系統和驅動程式處理掉。
Windows主螢幕左上角是(0,0),所以螢幕坐標有可能是負數。
X Window最左上的螢幕是(0,0),至少我測試時如此,不知道有沒有其他情況。
之前用如下方法取得螢幕大小
Windows:
DEVMODE devMode; devMode.dmSize=sizeof(DEVMODE); devMode.dmDriverExtra=0; EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode); |
此時devMode.dmPelsWidth、devMode.dmPelsHeight是螢幕寬高。
EnumDisplaySettings第一參數填NULL只能取得主螢幕的範圍,所以如果在副螢幕上開全螢幕模式,也會變成在主螢幕開。
X Window:
Display* dsp; int screen=DefaultScreen(dsp); int width = DisplayWidth(dsp,screen); int height = DisplayHeight(dsp,screen); |
dsp是X Window的物件,在建立視窗的時候設好。
這個方法取得的是上圖電子妖精看到的螢幕,所以遊戲畫面會變成如下跨兩個螢幕。
雖然有screen這個參數,但不是用在一台電腦接多個螢幕,即使接多個螢幕screen還是只有一個,手上沒有實際環境看這個參數到底是做什麼用的。
(Linux版我目前直接用X window,沒有用Gtk、Qt等視窗函式庫)
本來想找有沒有函式自動檢查視窗位於哪個螢幕,然後指定螢幕就放大到剛好蓋滿螢幕…………發現辦不到,必須自己取得每個螢幕的範圍做計算。
改成如下的方法
Windows
#define UNICODE #include<windows.h>
//要用的變數和struct BOOL ret; DISPLAY_DEVICE displayDevice; displayDevice.cb=sizeof(DISPLAY_DEVICE); DEVMODE devMode; devMode.dmSize=sizeof(DEVMODE); devMode.dmDriverExtra=0;
for(int i=0;;i++){ ret=EnumDisplayDevices(NULL,i,&displayDevice,0); //傳回false代表i>目前螢幕數量,所有螢幕都檢查完了 if(!ret){ break; } if(!(displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE)){ continue; }
EnumDisplaySettings(displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &devMode); int isPrimaryScreen = (displayDevice.StateFlags&DISPLAY_DEVICE_PRIMARY_DEVICE); /* 此時devMode.dmPosition.x、devMode.dmPosition.y、devMode.dmPelsWidth、devMode.dmPelsHeight就是螢幕範圍了 在這裡檢查視窗是否在螢幕裡 另外有對主螢幕做特別處理 */ } |
用EnumDisplayDevices取得螢幕的DeviceName,再用EnumDisplaySettings取得目前的解析度設定。
好像沒有函式可以直接取得螢幕數量,要跑迴圈直到EnumDisplayDevices傳回false。
附帶一提修改螢幕解析度是用ChangeDisplaySettingsEx(),有興趣自己試試看吧。
X Window
要使用Xrandr這個擴充。
有另一個擴充Xinerama也可以取得螢幕大小,這是較舊的規格,功能也沒有Xrandr多,只用Xrandr就好了。
#include<X11/extensions/Xrandr.h>
Display* dsp; Window window; int number; XRRMonitorInfo* monitorInfo=XRRGetMonitors(dsp, window, 1, &number); //此時number是螢幕數量,跑迴圈檢查各個螢幕 for(int i=0;i<number;i++){ XRRMonitorInfo* tempInfo=monitorInfo+i; /* 此時tempInfo->x、tempInfo->y、tempInfo->width、tempInfo->height是螢幕範圍 tempInfo->primary是是否為主螢幕 在此檢查視窗是否在螢幕裡 */ } |
window也是X Window的物件,在建立視窗時建好。
XRRGetMonitors第三參數是get_active,填1代表只取得有在使用的螢幕。
如上取得螢幕範圍之後要檢查視窗是否在螢幕裡,我用不精確但是容易做的方法:視窗中心點是否在範圍內,如果中心點在所有螢幕外面,無法判定就用主螢幕代替。
知道在哪個螢幕後就要修改視窗大小和位置,還有把標題和邊框消除,Windows很簡單。
HWND window; //初始化時建好的視窗handle int screenRect[4]; //用上面的方法求出螢幕的範圍
SetWindowLongPtr(window, GWL_STYLE, WS_POPUP|WS_VISIBLE); SetWindowPos(window, 0, 0,0,0,0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOMOVE|SWP_FRAMECHANGED); //沒有上面這行滑鼠坐標會錯 SetWindowPos(window, HWND_TOP, screenRect[0],screenRect[1],screenRect[2],screenRect[3], 0); |
但X Window麻煩很多,是另一門學問,以後有空再介紹。
為了方便作業買了另一個螢幕,才發現引擎要對多螢幕做處理,雖然不知道有多少玩家會用多螢幕,但至少在作者的電腦上不能出問題,否則就不能製作了。
做了才發現又是個難關,要自己取得所有螢幕的大小做計算。
尤其X Window討厭的地方是說明文件很少,常常只能看header猜函式的用途,做這部分的時候也一樣。
另外試了幾個遊戲的全螢幕模式想參考一下,發現很多還是用舊式改解析度的做法,這樣不能在多螢幕用,而且其他視窗會被亂移,我的似乎是獨創的做法。
同時繼續研究Fedora,Fedora有個好處是可以同時裝32 bit和64 bit的開發用套件,Mint和Mageia就不行。
但是筆電會莫名其妙變得很燙,明明沒執行程式,用系統監控看CPU佔用率也很低,推測是顯卡在做什麼多餘的工但我找不到原因,把特效關掉、移除nouveau驅動程式也一樣,Mint就沒有這個問題。
裝軟體和設定也比較麻煩,有時更新了某個套件之後就進不了圖形介面,要按Ctrl+Alt+F2進命令列模式去救。