F#でOpenGLを試してみました。外部ライブラリは使わずWindows Formsに描画します。

次の記事に掲載されたC言語のサンプルをF#に移植しました。

.NET移植の際に参考にした記事です。

シリーズの記事です。

関連するコードをまとめたリポジトリです。

実装

完成したコードを説明します。

Win32APIをインポートしてOpenGL用にFormを継承します。

ディレクティブなど

構造体の警告を抑制します。

#nowarn "9"

Windows Forms関係を読み込みます。

#r "System"
#r "System.Drawing"
#r "System.Windows.Forms"

open System
open System.Drawing
open System.Runtime.InteropServices
open System.Windows.Forms

Win32API

定数を定義します。

let CS_VREDRAW =  1
let CS_HREDRAW =  2
let CS_OWNDC   = 32

let PFD_DOUBLEBUFFER   =  1
let PFD_DRAW_TO_WINDOW =  4
let PFD_SUPPORT_OPENGL = 32

必要な構造体を定義します。#nowarn "9" を指定しないとここで警告されます。

[<Struct; StructLayout(LayoutKind.Sequential)>]
type PIXELFORMATDESCRIPTOR =
    val mutable nSize           : int16
    val mutable nVersion        : int16
    val mutable dwFlags         : int
    val mutable iPixelType      : byte
    val mutable cColorBits      : byte
    val mutable cRedBits        : byte
    val mutable cRedShift       : byte
    val mutable cGreenBits      : byte
    val mutable cGreenShift     : byte
    val mutable cBlueBits       : byte
    val mutable cBlueShift      : byte
    val mutable cAlphaBits      : byte
    val mutable cAlphaShift     : byte
    val mutable cAccumBits      : byte
    val mutable cAccumRedBits   : byte
    val mutable cAccumGreenBits : byte
    val mutable cAccumBlueBits  : byte
    val mutable cAccumAlphaBits : byte
    val mutable cDepthBits      : byte
    val mutable cStencilBits    : byte
    val mutable cAuxBuffers     : byte
    val mutable iLayerType      : byte
    val mutable bReserved       : byte
    val mutable dwLayerMask     : int
    val mutable dwVisibleMask   : int
    val mutable dwDamageMask    : int

必要な関数のP/Invokeを定義します。

[<DllImport("user32.dll")>]
extern nativeint GetDC(nativeint hWnd)
[<DllImport("user32.dll")>]
extern int ReleaseDC(nativeint hWnd, nativeint hDC)
[<DllImport("gdi32.dll")>]
extern int ChoosePixelFormat(nativeint hDC, PIXELFORMATDESCRIPTOR& ppfd)
[<DllImport("gdi32.dll", SetLastError = true)>]
extern bool SetPixelFormat(nativeint hDC, int format, PIXELFORMATDESCRIPTOR& ppfd)
[<DllImport("gdi32.dll")>]
extern bool SwapBuffers(nativeint hDC)
[<DllImport("opengl32.dll")>]
extern nativeint wglCreateContext(nativeint hDC)
[<DllImport("opengl32.dll")>]
extern bool wglMakeCurrent(nativeint hDC, nativeint hGLRC)
[<DllImport("opengl32.dll")>]
extern bool wglDeleteContext(nativeint hGLRC)

GLForm

OpenGLを有効にしたFormを継承して実装します。Paintイベント時にOpenGLで描画できるようにします。Windows FormsでOpenGLを使うために最低限必要なことが凝縮されています。

type GLForm() =
    inherit Form()

    let mutable hDC   = 0n
    let mutable hGLRC = 0n

    override x.CreateParams =
        x.SetStyle(ControlStyles.Opaque, true)
        let cp = base.CreateParams
        cp.ClassStyle <- cp.ClassStyle ||| CS_VREDRAW ||| CS_HREDRAW ||| CS_OWNDC
        cp

    override x.OnHandleCreated e =
        base.OnHandleCreated e
        let mutable pfd =
            PIXELFORMATDESCRIPTOR(
                nSize        = (Marshal.SizeOf<PIXELFORMATDESCRIPTOR>() |> int16),
                nVersion     = 1s,
                dwFlags      = (PFD_DOUBLEBUFFER ||| PFD_DRAW_TO_WINDOW ||| PFD_SUPPORT_OPENGL),
                cColorBits   = 32uy,
                cDepthBits   = 24uy,
                cStencilBits = 8uy)
        hDC <- GetDC x.Handle
        let format = ChoosePixelFormat(hDC, &pfd)
        if format = 0 then
            failwith "Can not choose format"
        if not <| SetPixelFormat(hDC, format, &pfd) then
            raise <| ComponentModel.Win32Exception(Marshal.GetLastWin32Error())
        hGLRC <- wglCreateContext hDC

    override x.Dispose disposing =
        ignore <| wglDeleteContext hGLRC
        ignore <| ReleaseDC(x.Handle, hDC)
        base.Dispose disposing

    override x.OnPaint e =
        ignore <| wglMakeCurrent(hDC, hGLRC)
        base.OnPaint e
        ignore <| SwapBuffers hDC
        ignore <| wglMakeCurrent(0n, 0n)

ここまではWin32の世界でした。

OpenGL

ここからはOpenGLの世界です。

今回必要なものだけ定数やP/Invokeを定義します。

let GL_COLOR_BUFFER_BIT = 0x00004000

[<DllImport("opengl32.dll")>]
extern void glClearColor(float32 red, float32 green, float32 blue, float32 alpha)
[<DllImport("opengl32.dll")>]
extern void glClear(int mask)
[<DllImport("opengl32.dll")>]
extern void glFlush()
[<DllImport("opengl32.dll")>]
extern void glRectf(float32 x1, float32 y1, float32 x2, float32 y2)

サンプル

GLFormのPaintイベントでOpenGLによって描画します。

[<EntryPoint; STAThread>] do
let f = new GLForm(Text = "OpenGLテスト", ClientSize = Size(640, 480))
f.Paint.Add <| fun _ ->
    glClearColor(0.0f, 0.5f, 1.0f, 1.0f)
    glClear(GL_COLOR_BUFFER_BIT)
    glRectf(-0.5f, -0.5f, 0.5f, 0.5f)
    glFlush()
Application.Run f

OpenGL.png

無事に描画できました。