mieki256's diary



2026/03/03(火) [n年前の日記]

#1 [nim] Nimの勉強中

プログラミング言語Nimの勉強中。環境は Windows11 x64 25H2 + Nim 2.2.8 64bit。

OpenGLが使えるかどうか確認 :

Nim で OpenGLを使いたい場合、opengl というライブラリを import すればいいようで、そのライブラリ/パッケージについては公式でサポートしているらしい。ただ、Nimに標準添付されてるわけでもないようで、別途インストールが必要っぽい? 以下でインストールできた…ような気がする。
nimble install opengl

ただ、OpenGLは描画関係の機能しか持たないので、ウインドウを生成したり、キー入力を検出したりする処理は別の何かで実現しないといけない。

一般的にOpenGLの勉強をする際は、GLUT、freeglut、GLFW あたりを使うことが多い印象だけど…。まあ、GLUT は古いし、バグも放置されたままらしいので、freeglut を使うようになるのだろうけど…。

_OpenGL Utility Toolkit - Wikipedia
_The freeglut Project :: About
_An OpenGL library | GLFW

しかしそのあたりは、別途 DLL が必要になりそうな気がする…。できれば exeファイル1つで動く何かを作りたい。

そこで今回は Windows API を利用してウインドウを生成したり、キー入力等を検出してみることにした。そういう構成なら別途DLLは不要のはず。Windowsに含まれてるから。


Nim から Windows API を利用したい場合、winim というライブラリを利用すると記述が楽になるらしい。

_khchen/winim: Windows API, COM, and CLR Module for Nim

インストールは以下。
nimble install winim

AI(Google Gemini)に、空のウインドウを表示するだけのサンプルを提示してもらって動作確認した。

_main.nim
import winim/lean

# アプリの状態を保持する型(今回はウィンドウハンドルのみ)
type App = object
  hwnd: HWND

# --- 1. ウィンドウプロシージャ(イベント処理) ---
proc windowProc(
    hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
): LRESULT {.stdcall.} =
  case uMsg
  of WM_DESTROY:
    PostQuitMessage(0)
    return 0
  of WM_KEYDOWN:
    if wParam == VK_ESCAPE:
      # ESCキーで終了
      PostMessage(hwnd, WM_CLOSE, 0, 0)
      # DestroyWindow(hwnd)
    return 0
  else:
    return DefWindowProc(hwnd, uMsg, wParam, lParam)

# --- 2. ウィンドウの生成メソッド ---
proc initApp(title: string, width, height: int): App =
  let hInstance = GetModuleHandle(nil)
  let className = "NimSimpleWindow"

  var wc: WNDCLASS
  wc.lpfnWndProc = windowProc
  wc.hInstance = hInstance
  wc.lpszClassName = className
  wc.hCursor = LoadCursor(0, IDC_ARROW)
  wc.hbrBackground = COLOR_WINDOW + 1

  RegisterClass(addr wc)

  let hwnd = CreateWindowEx(
    0,
    className,
    title,
    WS_OVERLAPPEDWINDOW or WS_VISIBLE,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    width.int32,
    height.int32,
    0,
    0,
    hInstance,
    nil,
  )

  # 作成したハンドルを App 型に包んで返す
  result = App(hwnd: hwnd)

# --- 3. メインループメソッド ---
proc run(app: App) =
  var msg: MSG
  # GetMessageは WM_QUIT を受け取ると 0 を返し、ループが終了する
  while GetMessage(addr msg, 0, 0, 0) != 0:
    TranslateMessage(addr msg)
    DispatchMessage(addr msg)

proc main() =
  let myApp = initApp("Nim Win32 Application", 640, 480)
  
  if myApp.hwnd == 0:
    MessageBox(0, "Failed to create the application window.", "Fatal Error", MB_OK or MB_ICONERROR)
    return

  run(myApp)

main()

以下でビルド。main.exe が生成される。
nim c -d:release main.nim
or
nim c -d:release --app:gui main.nim
  • -d:release の指定はデバッグ情報を削除してファイルサイズを小さくしたり、最適化して処理速度を向上させる指定。
  • --app:gui の指定はコンソールを出さない指定。

以下のようなウインドウが表示された。

01_hellowinim_ss01.png


ウインドウの表示ができたので、OpenGLが使えるかテストした。これもAI(Google Gemini)にサンプルを提示してもらって動作確認した。

_main.nim
import winim/lean
import opengl
import std/monotimes

# 必要な情報をまとめる構造体
type AppWindow = object
  hwnd: HWND
  hdc: HDC
  hrc: HGLRC

# --- 1. ウィンドウプロシージャ(イベント処理) ---
proc windowProc(
    hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
): LRESULT {.stdcall.} =
  case uMsg
  of WM_DESTROY:
    PostQuitMessage(0)
    return 0
  of WM_KEYDOWN:
    if wParam == VK_ESCAPE:
      PostMessage(hwnd, WM_CLOSE, 0, 0)
    return 0
  else:
    return DefWindowProc(hwnd, uMsg, wParam, lParam)

# --- 2. ウィンドウとOpenGLの初期化 ---
proc initWindow(width, height: int): AppWindow =
  let hInstance = GetModuleHandle(nil)
  let className = "NimGLWindow"

  var wc: WNDCLASS
  wc.lpfnWndProc = windowProc
  wc.hInstance = hInstance
  wc.lpszClassName = className
  wc.hCursor = LoadCursor(0, IDC_ARROW)
  RegisterClass(addr wc)

  let hwnd = CreateWindowEx(
    0,
    className,
    "Nim OpenGL - Single EXE Framework",
    WS_OVERLAPPEDWINDOW or WS_VISIBLE,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    width.int32,
    height.int32,
    0,
    0,
    hInstance,
    nil,
  )

  # OpenGL初期化
  let hdc = GetDC(hwnd)
  var pfd: PIXELFORMATDESCRIPTOR
  pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR).uint16
  pfd.nVersion = 1
  pfd.dwFlags = PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
  pfd.iPixelType = PFD_TYPE_RGBA
  pfd.cColorBits = 32

  let pixFormat = ChoosePixelFormat(hdc, addr pfd)
  SetPixelFormat(hdc, pixFormat, addr pfd)

  let hrc = wglCreateContext(hdc)
  wglMakeCurrent(hdc, hrc)
  loadExtensions() # モダンな関数を使えるようにロード

  result = AppWindow(hwnd: hwnd, hdc: hdc, hrc: hrc)

# --- 3. 描画ロジック(アスペクト比補正付き) ---
proc render(app: AppWindow, angle: float32) =
  # ウィンドウサイズの取得
  var rect: RECT
  GetClientRect(app.hwnd, addr rect)
  let w = (rect.right - rect.left).float32
  let h = (rect.bottom - rect.top).float32

  # ビューポートと投影行列の設定(引き伸ばし防止)
  glViewport(0, 0, w.int32, h.int32)
  glMatrixMode(GL_PROJECTION)
  glLoadIdentity()

  let aspect =
    if h != 0:
      w / h
    else:
      1.0
  if w >= h:
    glOrtho(-aspect, aspect, -1.0, 1.0, -1.0, 1.0)
  else:
    glOrtho(-1.0, 1.0, -1.0 / aspect, 1.0 / aspect, -1.0, 1.0)

  # モデルビュー行列のリセットと描画
  glMatrixMode(GL_MODELVIEW)
  glLoadIdentity()

  glClearColor(0.1, 0.2, 0.3, 1.0)
  glClear(GL_COLOR_BUFFER_BIT)

  # 回転の適用
  glRotatef(angle, 0, 0, 1)

  # 三角形の描画
  glBegin(GL_TRIANGLES)
  glColor3f(1.0, 0.0, 0.0)
  glVertex2f(0.0, 0.8)
  glColor3f(0.0, 1.0, 0.0)
  glVertex2f(-0.8, -0.8)
  glColor3f(0.0, 0.0, 1.0)
  glVertex2f(0.8, -0.8)
  glEnd()

# --- 4. メインループ ---
proc mainLoop(app: AppWindow) =
  var msg: MSG
  var angle: float32 = 0.0
  let rotationSpeed: float32 = 90.0 # 1秒間に90度

  var lastTicks = getMonoTime().ticks

  while true:
    if PeekMessage(addr msg, 0, 0, 0, PM_REMOVE) != 0:
      if msg.message == WM_QUIT:
        break
      TranslateMessage(addr msg)
      DispatchMessage(addr msg)
    else:
      # デルタタイム計算 (.ticks を使用)
      let currentTicks = getMonoTime().ticks
      let dt = (currentTicks - lastTicks).float32 / 1_000_000_000.0
      lastTicks = currentTicks

      angle += rotationSpeed * dt
      # if angle > 360.0:
      #   angle -= 360.0

      render(app, angle)
      SwapBuffers(app.hdc)
      Sleep(15)

# --- 5. メイン ---
proc main() =
  let app = initWindow(800, 600)

  # 使用中のOpenGLのバージョンを表示
  # echo "Vendor:   ", $glGetString(GL_VENDOR)
  # echo "Renderer: ", $glGetString(GL_RENDERER)
  # echo "Version:  ", $glGetString(GL_VERSION)

  mainLoop(app)

main()

ビルドは以下。
nim c -d:release main.nim
or
nim c -d:release --app:gui main.nim

01_rotate_triangle_ss01.png

三角形が回転するアニメーションができた。

CreateWindowEx() を呼んでウインドウを生成するとHWND(ウインドウハンドル)が得られるので、それを使って GetDC(hwnd) すれば、OpenGLを描画するためのデバイスコンテキストが得られるらしい。


ということで、どうやら Nim でも OpenGL は使えそうだなと…。

以上です。

過去ログ表示

Prev - 2026/03 -
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 31

カテゴリで表示

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


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

Powered by hns-2.19.6, HyperNikkiSystem Project