2022/09/09(金) [n年前の日記]
#1 [prog][windows] OpenGLを使ったWindows用スクリーンセーバをMinGWでコンパイル
Windows10 x64 21H2上で、OpenGLを使ったWindows用スクリーンセーバのサンプルを、MinGW を使ってコンパイル/ビルドしたい。
環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0) + freeglut 3.0.0-1。
MinGW-w64 (+ MSYS2) ではなくて、MinGW (+ MSYS) で作業。何度も何度も書くけれど、MinGW-w64 は libscrnsave.a (libscrnsavw.a) の中身が空っぽなのでスクリーンセーバをビルドできない。
参考にしたページは以下。OpenGL を使って、緑色の四角が円を描いて回るスクリーンセーバのソースが紹介されてる。リソースファイルやコンパイル手順までは紹介されてない。
_How to Scr: Writing an OpenGL Screensaver for Windows
_How to Scr: Sample Code for an OpenGL Screensaver
動作結果は以下。解像度が荒いけど雰囲気ぐらいは伝わるかと…。
ちなみに、このスクリーンセーバは OpenGL を使っているせいか、デスクトップキャプチャツール (OBS Studio 28.0.1) で数秒ほどキャプチャできなかった。デスクトップ画面が通常状態から OpenGL に切り替わる際にキャプチャできなくなるっぽい…。
環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0) + freeglut 3.0.0-1。
MinGW-w64 (+ MSYS2) ではなくて、MinGW (+ MSYS) で作業。何度も何度も書くけれど、MinGW-w64 は libscrnsave.a (libscrnsavw.a) の中身が空っぽなのでスクリーンセーバをビルドできない。
参考にしたページは以下。OpenGL を使って、緑色の四角が円を描いて回るスクリーンセーバのソースが紹介されてる。リソースファイルやコンパイル手順までは紹介されてない。
_How to Scr: Writing an OpenGL Screensaver for Windows
_How to Scr: Sample Code for an OpenGL Screensaver
動作結果は以下。解像度が荒いけど雰囲気ぐらいは伝わるかと…。
ちなみに、このスクリーンセーバは OpenGL を使っているせいか、デスクトップキャプチャツール (OBS Studio 28.0.1) で数秒ほどキャプチャできなかった。デスクトップ画面が通常状態から OpenGL に切り替わる際にキャプチャできなくなるっぽい…。
◎ MinGWにfreeglutを導入。 :
◎ ソース。 :
ソースファイル群とアイコンをzipにして置いておく。
_greensquare_src.zip
中身は以下。
一応、各ファイルも載せておきます。
リソースファイルは以下。ちなみに、元の参考ページではリソースファイルが掲載されてなかったので、Resource Hacker 5.1.8 を使って .scr からリソースを逆算(?)して書いてみた。
_resource.rc
リソースファイルのヘッダファイルは以下。
_resource.h
C++ソースファイルは以下。ちなみにオリジナル版はレジストリの読み書きにも対応してるのだけど、いきなりレジストリに書き込むのはちょっと怖いので、以下のソースではレジストリ関連処理( GetConfig()、WriteConfig() ) は if (0) { 〜 } でコメントアウト/無効化してある。
_greensquare.cpp
アイコンファイルは以下。中に icon.ico が入ってる。
_icon.cio.zip
_greensquare_src.zip
中身は以下。
- greensquare.cpp
- icon.ico
- Makefile
- resource.h
- resource.rc
一応、各ファイルも載せておきます。
リソースファイルは以下。ちなみに、元の参考ページではリソースファイルが掲載されてなかったので、Resource Hacker 5.1.8 を使って .scr からリソースを逆算(?)して書いてみた。
_resource.rc
/* resource.rc */ #include <windows.h> #include <scrnsave.h> #include "resource.h" #define IDC_STATIC_ICON 2300 #define IDC_STATIC_TEXT 2301 /* LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US */ /* Screensaver title */ STRINGTABLE BEGIN IDS_DESCRIPTION "Green Square Example Scr" END /* Icon */ ID_APP ICON "icon.ico" /* Dialog */ DLG_SCRNSAVECONFIGURE DIALOG DISCARDABLE 0, 0, 186, 82 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "GreenSquare Setup" FONT 11, "Segoe UI" BEGIN DEFPUSHBUTTON "OK", IDOK, 39, 61, 50, 14 PUSHBUTTON "Cancel", IDCANCEL, 107, 61, 50, 14 AUTOCHECKBOX "Square tumbles as it revolves", IDC_TUMBLE, 25, 35, 137, 12 LTEXT "One option... that's all you get.", IDC_STATIC_TEXT, 17, 15, 150, 13 END※ この日記システムの問題で、FONT指定行の見た目がおかしくなってます。行頭の空白を削除してもらえればと。
リソースファイルのヘッダファイルは以下。
_resource.h
#ifndef _RESOURCE_H_ #define _RESOURCE_H_ #define IDC_TUMBLE 1000 #define IDC_STATIC_ICON 2300 #define IDC_STATIC_TEXT 2301 #endif
C++ソースファイルは以下。ちなみにオリジナル版はレジストリの読み書きにも対応してるのだけど、いきなりレジストリに書き込むのはちょっと怖いので、以下のソースではレジストリ関連処理( GetConfig()、WriteConfig() ) は if (0) { 〜 } でコメントアウト/無効化してある。
_greensquare.cpp
// greensquare.cpp is an open-source example screensaver by Rachel Grey, lemming@alum.mit.edu. // Paste into an IDE to compile if desired. // I haven't chosen to include the resource file, so you'd need to provide a description string /and so forth. #include <windows.h> #include <scrnsave.h> #include <GL/gl.h> #include <GL/glu.h> #include "resource.h" // get rid of these warnings: truncation from const double to float conversion from double to float // #pragma warning(disable: 4305 4244) //Define a Windows timer #define TIMER 1 #define FPS 60 // globals for size of screen static int Width, Height; static bool bTumble = true; // a global to keep track of the square's spinning static GLfloat spin = 0; ///////////////////////////////////////////////// //// INFRASTRUCTURE ENDS, SPECIFICS BEGIN /// //// /// //// In a more complex scr, I'd put all /// //// the following into other files. /// ///////////////////////////////////////////////// // Initialize OpenGL static void InitGL(HWND hWnd, HDC &hDC, HGLRC &hRC) { PIXELFORMATDESCRIPTOR pfd; ZeroMemory(&pfd, sizeof pfd); pfd.nSize = sizeof pfd; pfd.nVersion = 1; //pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; //blaine's pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; hDC = GetDC(hWnd); int i = ChoosePixelFormat(hDC, &pfd); SetPixelFormat(hDC, i, &pfd); hRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hRC); } // Shut down OpenGL static void CloseGL(HWND hWnd, HDC hDC, HGLRC hRC) { wglMakeCurrent(NULL, NULL); wglDeleteContext(hRC); ReleaseDC(hWnd, hDC); } void SetupAnimation(int Width, int Height) { //window resizing stuff glViewport(0, 0, (GLsizei) Width, (GLsizei) Height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (0) { glOrtho(-300, 300, -240, 240, 25, 75); } else { float w = 300; float h = ((w * 2) * Height / Width) / 2; glOrtho(-w, w, -h, h, 25, 75); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); //camera xyz, the xyz to look at, and the up vector (+y is up) //background glClearColor(0.0, 0.0, 0.0, 0.0); //0.0s is black glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // glShadeModel(GL_FLAT); glShadeModel(GL_SMOOTH); } void OnTimer(HDC hDC) //increment and display { float xvals[] = {-30.0, -30.0, 30.0, 30.0}; float yvals[] = {-30.0, 30.0, 30.0, -30.0}; spin = spin + 1; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glRotatef(spin, 0.0, 0.0, 1.0); glTranslatef(120, 0, 0); glRotatef(spin * ((bTumble) ? -4.0 : -1.0), 0.0, 0.0, 1.0); //draw the square (rotated to be a diamond) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glColor3f(0.1, 1.0, 0.3); // set green color glBegin(GL_POLYGON); for (int i = 0; i < 4; i++) glVertex2f(xvals[i], yvals[i]); glEnd(); glPopMatrix(); glFlush(); SwapBuffers(hDC); } void CleanupAnimation() { // didn't create any objects, so no need to clean them up } ///////// REGISTRY ACCESS FUNCTIONS /////////// void GetConfig() { if (0) { // get configuration from registry HKEY key; //DWORD lpdw; if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\GreenSquare", //lpctstr 0, //reserved KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) { DWORD dsize = sizeof(bTumble); DWORD dwtype = 0; RegQueryValueEx(key, "Tumble", NULL, &dwtype, (BYTE*)&bTumble, &dsize); //Finished with key RegCloseKey(key); } else //key isn't there yet--set defaults { bTumble = true; } } else { bTumble = true; } } void WriteConfig(HWND hDlg) { if (0) { HKEY key; DWORD lpdw; const char* null_str = ""; LPSTR str = const_cast<LPSTR>(null_str); if (RegCreateKeyEx(HKEY_CURRENT_USER, "Software\\GreenSquare", // lpctstr 0, // reserved str, // ptr to null-term string specifying the object type of this key REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &key, &lpdw) == ERROR_SUCCESS) { RegSetValueEx(key, "Tumble", 0, REG_DWORD, (BYTE*)&bTumble, sizeof(bTumble)); //Finished with keys RegCloseKey(key); } } } ////////////////////////////////////////////////// //// INFRASTRUCTURE -- THE THREE FUNCTIONS /// ////////////////////////////////////////////////// // Screen Saver Procedure LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static HDC hDC; static HGLRC hRC; static RECT rect; switch (message) { case WM_CREATE: // get window dimensions GetClientRect(hWnd, &rect); Width = rect.right; Height = rect.bottom; // get configuration from registry GetConfig(); // setup OpenGL, then animation InitGL(hWnd, hDC, hRC); SetupAnimation(Width, Height); // set timer to tick every 10 ms SetTimer(hWnd, TIMER, (int)(1000 / FPS), NULL); return 0; case WM_TIMER: OnTimer(hDC); // animate! return 0; case WM_DESTROY: KillTimer(hWnd, TIMER); CleanupAnimation(); CloseGL(hWnd, hDC, hRC); return 0; } return DefScreenSaverProc(hWnd, message, wParam, lParam); } BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { //InitCommonControls(); //would need this for slider bars or other common controls HWND aCheck; switch (message) { case WM_INITDIALOG: LoadString(hMainInstance, IDS_DESCRIPTION, szAppName, 40); GetConfig(); aCheck = GetDlgItem(hDlg, IDC_TUMBLE); SendMessage(aCheck, BM_SETCHECK, (bTumble) ? BST_CHECKED : BST_UNCHECKED, 0); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_TUMBLE: bTumble = (IsDlgButtonChecked(hDlg, IDC_TUMBLE) == BST_CHECKED); return TRUE; //cases for other controls would go here case IDOK: WriteConfig(hDlg); // get info from controls EndDialog(hDlg, LOWORD(wParam) == IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, LOWORD(wParam) == IDOK); return TRUE; } return FALSE; } //end command switch return FALSE; } // needed for SCRNSAVE.LIB BOOL WINAPI RegisterDialogClasses(HANDLE hInst) { return TRUE; }
アイコンファイルは以下。中に icon.ico が入ってる。
_icon.cio.zip
◎ コンパイルの仕方。 :
Makefile は以下。
_Makefile
Makefile があれば、make と打つだけでコンパイル/ビルドできる。ちなみに、make clean で *.o と *.scr を削除できる。
一応説明しておくと…。
リンク時の指定については以下。
_Makefile
greensquare.scr: greensquare.o resource.o g++ greensquare.o resource.o -o greensquare.scr -static -lstdc++ -lgcc -lscrnsave -lopengl32 -lglu32 -lgdi32 -lcomctl32 -lwinmm -mwindows greensquare.o: greensquare.cpp resource.h g++ -c greensquare.cpp resource.o: resource.rc resource.h icon.ico windres resource.rc resource.o .PHONY: clean clean: rm -f *.scr rm -f *.o
Makefile があれば、make と打つだけでコンパイル/ビルドできる。ちなみに、make clean で *.o と *.scr を削除できる。
一応説明しておくと…。
- windres resource.rc resource.o で、リソースファイル resource.rc を .o に変換する。
- g++ -c greensquare.cpp で、.cpp をコンパイルして .o を生成。
- g++ greensquare.o resource.o -o greensquare.scr ... で、.oファイル群をリンクして .scr を生成。
リンク時の指定については以下。
- -static : スタティックリンクを指定。
- -lstdc++ -lgcc : C言語の標準的なライブラリをリンク。
- -lscrnsave : スクリーンセーバ用ライブラリをリンク。libscrnsave.a (libscrnsavw.a) をリンクする。
- -lopengl32 -lglu32 : OpenGLのライブラリをリンク。
- -lgdi32 : GDIライブラリをリンク。
- -lcomctl32 : よく分かってないけれど、これをつけないと「*.dll が見つからない」と言われて .scr が動作しなかった。
- -lwinmm : Windowsのタイマー関係ライブラリ、らしい。
- -mwindows : Windows用アプリケーション(GUIアプリ)であることを指定。
◎ 動作確認。 :
greensquare.scr を右クリックして動作確認。
MinGW で生成したので、この .scr は32bit版。それをWindows10 64bit上で動かしているので、C:\Windows\SysWOW64\ 以下に .scr をコピーして動作確認。スクリーンセーバとしても動いてくれた。
とりあえず、これで OpenGL を使ったスクリーンセーバを MinGW でコンパイル/ビルドできることは分かった。後は、OpenGL を使った描画処理部分を ―― OnTimer() のあたりを改造していけば、それらしいスクリーンセーバを作れそうな予感。
- 「Test」を選ぶと、フルスクリーン表示の動作確認ができる。
- 「構成」を選ぶと、スクリーンセーバ設定画面で「設定」をクリックした時の設定ダイアログが表示される。
MinGW で生成したので、この .scr は32bit版。それをWindows10 64bit上で動かしているので、C:\Windows\SysWOW64\ 以下に .scr をコピーして動作確認。スクリーンセーバとしても動いてくれた。
とりあえず、これで OpenGL を使ったスクリーンセーバを MinGW でコンパイル/ビルドできることは分かった。後は、OpenGL を使った描画処理部分を ―― OnTimer() のあたりを改造していけば、それらしいスクリーンセーバを作れそうな予感。
[ ツッコむ ]
以上です。