mieki256's diary



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 に切り替わる際にキャプチャできなくなるっぽい…。

MinGWにfreeglutを導入。 :

MinGW で freeglut を使えるようにする手順は、昨日メモしたのでそちらを参考に。

_MinGWでfreeglutを使ってみた

ソース。 :

ソースファイル群とアイコンを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
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 を右クリックして動作確認。
  • 「Test」を選ぶと、フルスクリーン表示の動作確認ができる。
  • 「構成」を選ぶと、スクリーンセーバ設定画面で「設定」をクリックした時の設定ダイアログが表示される。

MinGW で生成したので、この .scr は32bit版。それをWindows10 64bit上で動かしているので、C:\Windows\SysWOW64\ 以下に .scr をコピーして動作確認。スクリーンセーバとしても動いてくれた。

とりあえず、これで OpenGL を使ったスクリーンセーバを MinGW でコンパイル/ビルドできることは分かった。後は、OpenGL を使った描画処理部分を ―― OnTimer() のあたりを改造していけば、それらしいスクリーンセーバを作れそうな予感。

#2 [python][windows] PyOpenGLが使うfreeglut.dllの場所を調べた

Windows10 x64 21H2 + Python 3.9.13 x64 + PyOpenGL 3.1.6 で実験をしていたのだけど、freeglut.dll がどこに置いてあるのかが気になったので少し調べてみた。

参考ページは以下。

_【Python】PyOpenGLのインストール方法を解説 | ジコログ

前提条件。 :

まず、自分の環境では、以下から .whl を入手してインストールしている。

_Archived: Python Extension Packages for Windows - Christoph Gohlke
  • PyOpenGL-3.1.6-cp39-cp39-win_amd64.whl
  • PyOpenGL_accelerate-3.1.6-cp39-cp39-win_amd64.whl

インストールは以下。
pip install PyOpenGL-3.1.6-cp39-cp39-win_amd64.whl
pip install PyOpenGL_accelerate-3.1.6-cp39-cp39-win_amd64.whl

この .whl には freeglut*.dll が同梱されているので、巷の解説ページで書かれているように、freeglut.dll を別途入手して C:\Windows\System32\ にコピーしなくても PyOpenGL が動いてくれる。

freeglut.dllの在り処。 :

さておき。その freeglut.dll は、実際はどこにあるのか…。調べた感じでは、どうも以下のフォルダに .dll が入ってるように見えた。

(Python3インストールフォルダ)\Lib\site-packages\OpenGL\DLLS\

以下の2つが入っていた。
  • freeglut64.vc16.dll
  • gle64.vc16.dll

ちなみに、Python 2.7.18 + PyOpenGL 3.1.0 上で確認してみたら、以下のファイルが入っていた。freeglut だけではなく GLUT も入っている。
  • freeglut32.vc9.dll
  • gle32.vc9.dll
  • glut32.vc9.dll


これらの .dll は、以下のスクリプトから読み込まれている。

(Python3インストールフォルダ)\Lib\site-packages\OpenGL\platform\win32.py

処理をしてる部分を抜き出してみる。
    def GLUT( self ):
        for possible in ('freeglut%s.%s'%(size,vc,), 'glut%s.%s'%(size,vc,)):
            # Prefer FreeGLUT if the user has installed it, fallback to the included
            # GLUT if it is installed
            try:
                return ctypesloader.loadLibrary(
                    ctypes.windll, possible, mode = ctypes.RTLD_GLOBAL
                )
            except WindowsError:
                pass
        return None
「freeglut*.*.dll」もしくは「glut*.*.dll」が見つかったらそれらを使うようだなと…。

freeglut.dllを差し替えてみる。 :

試しに、前述の処理部分を以下のように修正してみた。
        for possible in ('freeglut%s.%s'%(size,vc,), 'glut%s.%s'%(size,vc,)):
# ↓
        for possible in ('freeglut', 'freeglut%s.%s' % (size,vc), 'glut%s.%s' % (size,vc)):

これで、freeglut??.vc*.dll ではなく、freeglut.dll を読み込んでくれるのではないかと…。

以下から、freeglut 3.0.0 MSVC Package (freeglut-MSVC-3.0.0-2.mp.zip) を入手。

_freeglut Windows Development Libraries

解凍すると、bin/x64/freeglut.dll が入ってる。

以下のフォルダにコピー。
(Python3インストールフォルダ)\Lib\site-packages\OpenGL\DLLS\
or
(Python3インストールフォルダ)\

加えて、freeglut64.vc16.dll を freeglut64.vc16.dll.orig にリネームした。これで freeglut64.vc16.dll は呼び出されない状態になって、別途導入した freeglut.dll が呼び出されてる状態になるはず。

手元のサンプルスクリプトを動かしてみたところ、この状態でも動いてくれた。

しかし、上記の freeglut.dll はバージョンが 3.0.0 と古い。

MSYS2 + MinGW-w64 で入手できる freeglut.dll (libfreeglut.dll) は 3.2.2 なので、そちらも試してみた。

以下のファイルをコピーしてくる。
(MSYS2インストールフォルダ)\mingw64\bin\libfreeglut.dll

これを、freeglut.dll にリネームして、前述の場所にコピー。この状態でも簡単なサンプルなら動いてくれた。

ところで。手元の環境では、Python 3.9.13 のインストールフォルダ直下に freeglut.dll を置いても動いているわけで…。もし、その場所に置いても問題無く動くのであれば…。巷の解説ページは C:\Windows\System32\ 以下にコピーしてる事例がほとんどだけど、Pythonインストールフォルダに入れたほうが、まだ安全だったりしないのだろうか…。

余談。glutIdleFunc()が動かなくて悩んだ。 :

PyOpenGL関係のサンプルファイルを眺めていると、glutIdleFunc() を使っている事例を結構見かけるのだけど。手元の環境、Windows10 x64 21H2 + Python3.9.13 + PyOpenGL 3.1.6 上ではエラーが出てしまってかなり悩んだ。

> py 03_teapot_draw_idle.py
Traceback (most recent call last):
  File "C:\Python\Python39-64\lib\site-packages\OpenGL\GLUT\special.py", line 130, in safeCall
    return function( *args, **named )
TypeError: idle() missing 1 required positional argument: 'value'
 GLUT Idle callback <function idle at 0x000002A6438D8B80> with (),{} failed: returning None idle() missing 1 required positional argument: 'value'

やりたいことは一定の時間間隔で処理を呼び出したいだけなので、glutIdleFunc() の代わりに glutTimerFunc() を使えば目的は果たせるのだけど…。若干気になる状態なわけで。ググってみても仕様が変わった等の話を見かけないし。この症状が気になって freeglut.dll を色々差し替えていたわけで。結局動作は変わらなかったのだけど。

ふと、渡していた関数に引数があることに気づいた。glutTimerFunc() に渡す関数は引数を一つ用意するのだけど、その関数を安易に流用して、glutIdleFunc() にそのまま渡していたのが問題だった。何の引数も取らない関数を渡したらエラーが解消された。そういうオチか…トホ。

def on_timer(value):
    """timer callback."""
    global yrot
    yrot += 0.5
    glutPostRedisplay()  # Redraw

    glutTimerFunc(int(1000 / FPS), on_timer, 0)


def idle():
    """idle callback."""
    global yrot
    yrot += 0.5
    glutPostRedisplay()  # Redraw

# ...

    if USE_IDLE:
        glutIdleFunc(idle)
    else:
        glutTimerFunc(int(1000 / FPS), on_timer, 0)

以上、1 日分です。

過去ログ表示

Prev - 2022/09 - Next
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project