Edited at

OpenGLでオフスクリーンレンダリング

More than 1 year has passed since last update.

Windows FormsにOpenGLで描画できるようになったので、オフスクリーンレンダリングを試みました。今回はOpenGLのフレームバッファではなくWin32APIを使用します。

OSGears.gif

シリーズの記事です。

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

【注意】この記事のサンプルはレガシーなAPIを使っています。新しいAPIへの移行は機会を改めることにして、今回はレガシーなまま進めます。


参考

Mesaのサイトに最低限の説明があります。

http://www.mesa3d.org/brianp/sig97/offscrn.htm#wgl


Basically, a bitmap is created with CreateDIBSection. A pixel format with the PFD_DRAW_TO_BITMAP, PFD_SUPPORT_OPENGL, PFD_SUPPORT_GDI flags must be chosen. After creating a WGL context and binding it, OpenGL rendering can proceed.


これだけでは良く分からないため、以下の記事を参照しました。


実装

Win32APIの構造体やP/Invokeを定義します。

[<Struct; StructLayout(LayoutKind.Sequential)>]

type BITMAPINFOHEADER =
val mutable biSize : int
val mutable biWidth : int
val mutable biHeight : int
val mutable biPlanes : int16
val mutable biBitCount : int16
val mutable biCompression : int
val mutable biSizeImage : int
val mutable biXPelsPerMeter : int
val mutable biYPelsPerMeter : int
val mutable biClrUsed : int
val mutable biClrImportant : int

let PFD_DRAW_TO_BITMAP = 8
let PFD_SUPPORT_GDI = 16

let DIB_PAL_COLORS = 1

[<DllImport("gdi32.dll")>]
extern nativeint CreateDIBSection(nativeint hdc, BITMAPINFOHEADER& lpbmi, int usage,
nativeint& ppvBits, nativeint hSection, int offset)
[<DllImport("gdi32.dll")>]
extern nativeint CreateCompatibleDC(nativeint hdc)
[<DllImport("gdi32.dll")>]
extern nativeint SelectObject(nativeint hdc, nativeint h)
[<DllImport("gdi32.dll")>]
extern bool DeleteDC(nativeint hdc)
[<DllImport("gdi32.dll")>]
extern bool DeleteObject(nativeint ho)

使いやすいようにクラスで包みます。

type GLBitmap(width, height) =

let mutable hBMP = 0n
let mutable hDC = 0n
let mutable hOld = 0n
let mutable hGLRC = 0n

do
let mutable bmi =
BITMAPINFOHEADER(
biSize = Marshal.SizeOf<BITMAPINFOHEADER>(),
biWidth = width,
biHeight = height,
biPlanes = 1s,
biBitCount = 32s)
let mutable ppvBits = 0n
hBMP <- CreateDIBSection(0n, &bmi, DIB_PAL_COLORS, &ppvBits, 0n, 0)
let hdc = GetDC 0n
hDC <- CreateCompatibleDC(hdc)
ignore <| ReleaseDC(0n, hdc)
hOld <- SelectObject(hDC, hBMP)
let mutable pfd =
PIXELFORMATDESCRIPTOR(
nSize = (Marshal.SizeOf<PIXELFORMATDESCRIPTOR>() |> int16),
nVersion = 1s,
dwFlags = (PFD_DRAW_TO_BITMAP ||| PFD_SUPPORT_OPENGL ||| PFD_SUPPORT_GDI),
cColorBits = 32uy,
cDepthBits = 24uy,
cStencilBits = 8uy)
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

interface IDisposable with
override x.Dispose() =
ignore <| wglDeleteContext hGLRC
ignore <| SelectObject(hDC, hOld)
ignore <| DeleteDC hDC
ignore <| DeleteObject hBMP

member x.MakeCurrent() =
ignore <| wglMakeCurrent(hDC, hGLRC)
new RAII(fun () -> ignore <| wglMakeCurrent(0n, 0n))

member x.Width = width
member x.Height = height
member x.Handle = hBMP

やっていることは前回の記事とほぼ同じで、ウィンドウに関する記述をビットマップで置き換えています。

以上で実装したコードはGL7.fsxに追加しました。


サンプル

歯車のサンプルをオフスクリーンレンダリングして画像ファイルに書き出すようにしました。主要部を引用します。

let dir = "output"

if not <| Directory.Exists dir then
ignore <| Directory.CreateDirectory dir
let bmp = new GLBitmap(256, 256)
do use raii = bmp.MakeCurrent()
init()
reshape bmp.Width bmp.Height
for i = 0 to 8 do
angle <- float32 i * 2.f
do use raii = bmp.MakeCurrent() in draw()
use img = Image.FromHbitmap bmp.Handle
let fn = sprintf "%02d.png" i
printfn "%s" fn
img.Save(Path.Combine(dir, fn), ImageFormat.Png)

プログラム全体です。

※ 依存しているファイルはリポジトリを参照してください。


画面表示

オフスクリーンレンダリングであることを示すためサンプルではウィンドウに表示しませんでしたが、画面表示の例を示します。

[<EntryPoint; STAThread>] do

let size = Size(256, 256)
let f = new Form(Text = "Gears (Off-Screen Rendering)", ClientSize = size)
let bmp = new GLBitmap(size.Width, size.Height)
do use raii = bmp.MakeCurrent()
init()
reshape size.Width size.Height
let p = new PictureBox(Dock = DockStyle.Fill)
p.Paint.Add <| fun e ->
draw()
angle <- angle + 0.1f //2.0f
do use raii = bmp.MakeCurrent() in draw()
use img = Image.FromHbitmap bmp.Handle
e.Graphics.DrawImage(img, (p.Width - size.Width) / 2, (p.Height - size.Height) / 2)
p.Invalidate()
f.Controls.Add p
Application.Run f