2024/02/23(金) [n年前の日記]
#1 [basic] FreeBASICでOpenGLを使ってみる
FreeBASICは、標準状態だとCPUによるソフトウェア処理でグラフィック画面を描画するけれど、OpenGLを使ってハードウェア処理で描画することもできる。と思う。たぶん。どの程度のことができそうなのか少し試してみた。
環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
◎ スクリーンショット :
実行すると以下のようになる。
◎ ソース :
今回書いたソースは、soil.bi という、テクスチャ画像ファイル読み込み用のライブラリを使っている。以下から libsoil.zip を入手できる。
_Simple OpenGL Image Library Windows/Linux 32/64-bit - freebasic.net
Windowsの場合、解凍して中に入っている soil.bi と *.dll をプロジェクトフォルダにコピーすれば利用できる。
ソースは以下。かなり長くなってしまったけど…。
_opengl_sample1.bas
使用画像は以下。
_ufo.png
fbc opengl_sample1.bas でコンパイル。opengl_sample1.exe が生成される。
使い方は以下。
_Simple OpenGL Image Library Windows/Linux 32/64-bit - freebasic.net
Windowsの場合、解凍して中に入っている soil.bi と *.dll をプロジェクトフォルダにコピーすれば利用できる。
ソースは以下。かなり長くなってしまったけど…。
_opengl_sample1.bas
#ifdef __FB_WIN32__
' use mmsystem. Windows only
#include "windows.bi"
#include "win/mmsystem.bi"
#endif
#include once "fbgfx.bi"
Using FB
#include once "GL/gl.bi"
#include once "GL/glu.bi"
#include once "soil.bi"
Const IMG_FILE = "ufo.png"
Const OBJ_MAX = 10000
Const SPRITE_SIZE = 128
Const MAX_FPS = 60.0
Const PI = 3.1415926535897932
Dim Shared As Integer obj_num = OBJ_MAX ' Shared ... global variable
ChDir ExePath()
' get desktop size
Dim shared As Integer scr_w, scr_h, sdepth
ScreenInfo scr_w, scr_h, sdepth
' set window screen size
If Command(1) = "/s" Or Command(2) = "/s" Then
' like fullscreen
ScreenRes scr_w, scr_h, sdepth, ,GFX_OPENGL Or GFX_NO_FRAME
Else
' window style
scr_w *= 0.8
scr_h *= 0.8
ScreenRes scr_w, scr_h, sdepth, ,GFX_OPENGL
End If
' obj work type
Type obj
x As double
y As double
dx As double
dy As Double
w As Integer
h As Integer
Declare Sub init(ByVal x As Double, ByVal y As Double, ByVal dx As Double, ByVal dy As Double, ByVal w As Integer, ByVal h As Integer)
Declare Sub update(ByVal delta As Double)
End Type
' obj init
Sub obj.init(ByVal x As Double, ByVal y As Double, ByVal dx As Double, ByVal dy As Double, ByVal w As Integer, ByVal h As Integer)
this.x = x
this.y = y
this.dx = dx
this.dy = dy
this.w = w
this.h = h
End Sub
' obj update
Sub obj.update(ByVal delta As double)
this.x += (this.dx * MAX_FPS) * delta
this.y += (this.dy * MAX_FPS) * delta
Dim As Integer wh = this.w / 2
Dim As Integer hh = this.h / 2
If this.dx < 0.0 And this.x - wh <= 0 Then this.dx *= -1.0
If this.dx > 0.0 And this.x + wh >= scr_w Then this.dx *= -1.0
If this.dy < 0.0 And this.y - hh <= 0 Then this.dy *= -1.0
If this.dy > 0.0 And this.y + hh >= scr_h Then this.dy *= -1.0
End Sub
#ifdef __FB_WIN32__
' Changed timer precision to 1 msec
timeBeginPeriod(1)
#endif
' init objs work
Dim objs(0 To OBJ_MAX) As obj
For i As Integer = 0 To UBound(objs)
Dim As Double a, spd, dx, dy
a = (Rnd * 360.0) * PI / 180.0
spd = (scr_h / (MAX_FPS * 2)) * (Rnd + 0.25)
dx = spd * Cos(a)
dy = spd * Sin(a)
objs(i).init(scr_w / 2, scr_h / 2, dx, dy, SPRITE_SIZE, SPRITE_SIZE)
Next i
obj_num = UBound(objs) / 2
For i As Integer = 1 To 2
If Command(i) <> "" And Command(i) <> "/s" Then
obj_num = Valint(Command(i))
If obj_num < 0 Then obj_num = 0
If obj_num > UBound(objs) Then obj_num = UBound(objs)
End If
Next i
' load texture image file
var texture = SOIL_load_OGL_texture(IMG_FILE, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_POWER_OF_TWO)
' OpenGL Config
glViewport 0, 0, scr_w, scr_h
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, scr_w, scr_h, 0, 5.0, -5.0)
' gluPerspective 45.0, w/h, 0.1, 100.0
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glClearColor(0.15, 0.3, 0.6, 1)
glShadeModel(GL_SMOOTH)
glDisable(GL_DEPTH_TEST)
' glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, texture)
Dim As Double start_time, prev_time, now_time, next_time, delta
Dim As Integer frame_count, fps_count
Dim As Boolean running
start_time = Timer
prev_time = start_time
next_time = start_time + 1.0 / MAX_FPS
frame_count = 0
fps_count = 0
running = True
' main loop
While running
' get delta time
now_time = Timer
delta = now_time - prev_time
If delta < 0 Then delta = 1.0 / MAX_FPS
prev_time = now_time
next_time = now_time + 1.0 / MAX_FPS
' count fps
If now_time < start_time Then start_time = now_time
If (now_time - start_time) >= 1.0 Then
fps_count = frame_count ' get FPS
start_time += 1.0
frame_count = 0
End If
frame_count += 1
Dim As String k = inkey$
If k = Chr$(27) Or k = "q" Or k = Chr$(255) + "k" Then
running = False
End If
If MultiKey(SC_UP ) Then obj_num += 20
If MultiKey(SC_DOWN) Then obj_num -= 20
If obj_num < 0 Then obj_num = 0
If obj_num > UBound(objs) Then obj_num = UBound(objs)
' update objs
For i As Integer = 0 To obj_num
objs(i).update(delta)
Next i
' draw
glClear GL_COLOR_BUFFER_BIT
For i As Integer = 0 To obj_num
Dim As Integer swh = objs(i).w / 2, shh = objs(i).h / 2
glLoadIdentity()
glTranslatef(objs(i).x, objs(i).y, -1)
glBegin GL_QUADS
glTexCoord2f 0, 0 : glVertex3f -swh, -shh, 0
glTexCoord2f 1, 0 : glVertex3f swh, -shh, 0
glTexCoord2f 1, 1 : glVertex3f swh, shh, 0
glTexCoord2f 0, 1 : glVertex3f -swh, shh, 0
glEnd
Next i
' draw String (10, 10), "FPS " & fps_count, RGB(255, 255, 255)
WindowTitle("FPS:" & fps_count & " obj:" & obj_num)
flip ' double buffer flip
' wait
If Timer < next_time Then
Dim As Double wait_msec = cast( Integer, (next_time - Timer) * 1000.0)
If wait_msec > 0 Then sleep wait_msec
End If
Wend
#ifdef __FB_WIN32__
timeEndPeriod(1)
#endif
使用画像は以下。
_ufo.png
fbc opengl_sample1.bas でコンパイル。opengl_sample1.exe が生成される。
使い方は以下。
Usage:
opengl_sample1.exe [/s] [OBJ_NUM]
* /s : Full screen
* OBJ_NUM : objects number. 0 - 10000
◎ 動作確認してみた :
以下の環境で動作確認してみた。
結果としては…。
サブPCの結果が予想以上に酷かった。ここまで差が出るとは思ってなかった…。
タスクマネージャで確認したところ、CPU負荷はそれほどでもなかったけれど、GPU負荷がすぐに80%を超え始めて60FPSが出ない状態になっているように見えた。
つまり、それなりのスペックのGPUを積んでいるPCなら、OpenGLを使うことで描画速度も改善されるけれど、非力な内蔵GPUでは期待した効果は得られないようだなと…。 OpenGLを使ってもソフトウェア描画と五十歩百歩になる場面もありそうな気配がする…。
ただ、今回、OBS Studio 30.0.2 64bit で画面をキャプチャした際、OpenGL を使ってるはずなのに画面が一時的に暗くなっていない点が気になった。今まで OpenGL を使ったプログラムをキャプチャした際は、最初の数秒はキャプチャできなかったのに…。
そこからの邪推だけど、FreeBASIC で OpenGL を使う場合、OpenGL でVRAM上に描画した結果を、メインメモリ上の画像バッファにキャプチャして、その画像バッファをデスクトップに表示していたりしないかと…。そして、VRAM からメインメモリに持ってくるあたりで負荷がかかってるのではないかと。いや、自信無いけど。ただ、C言語等でOpenGLを利用する時とはちょっと違う仕組みになっていてもおかしくなさそうな気もする。
- メインPC、AMD Ryzen 5 5600X (6C12T, 3.7 - 4.6GHz) + GeForec GTX 1060 6GB, デスクトップ解像度 1920x1080
- サブPC、Athlon 5350 (4C4T, 2GHz, TDP 25W, Jaguar Core) + On-board VGA (Radeon R3, GCN), デスクトップ解像度 1920x1200
結果としては…。
- メインPC上では、128x128のスプライト相当を4,000個ほど出しても60FPSが出た。10,000個出すと50FPS台になった。
- サブPC上では、128x128個のスプライト相当を50個までなら60FPSが出たけれど、60個になると50FPS台になってしまった。100個ほど出すと30FPS台。
サブPCの結果が予想以上に酷かった。ここまで差が出るとは思ってなかった…。
タスクマネージャで確認したところ、CPU負荷はそれほどでもなかったけれど、GPU負荷がすぐに80%を超え始めて60FPSが出ない状態になっているように見えた。
つまり、それなりのスペックのGPUを積んでいるPCなら、OpenGLを使うことで描画速度も改善されるけれど、非力な内蔵GPUでは期待した効果は得られないようだなと…。 OpenGLを使ってもソフトウェア描画と五十歩百歩になる場面もありそうな気配がする…。
ただ、今回、OBS Studio 30.0.2 64bit で画面をキャプチャした際、OpenGL を使ってるはずなのに画面が一時的に暗くなっていない点が気になった。今まで OpenGL を使ったプログラムをキャプチャした際は、最初の数秒はキャプチャできなかったのに…。
そこからの邪推だけど、FreeBASIC で OpenGL を使う場合、OpenGL でVRAM上に描画した結果を、メインメモリ上の画像バッファにキャプチャして、その画像バッファをデスクトップに表示していたりしないかと…。そして、VRAM からメインメモリに持ってくるあたりで負荷がかかってるのではないかと。いや、自信無いけど。ただ、C言語等でOpenGLを利用する時とはちょっと違う仕組みになっていてもおかしくなさそうな気もする。
◎ flipが時間待ちをしてそう :
当初、メインループの最後でうっかり sleep を入れ忘れてしまったのだけど、その状態でも60FPS前後で動いてしまった。sleep を入れてもその状態からさほど変わらず。
もしかすると、ダブルバッファを切り替える flip の中で、独自に時間待ち処理をして 60FPS になるようにしてあるのかも。ひょっとして、処理内容としては vsync をチェックしてるつもり、だったりするのかな…。
もしかすると、ダブルバッファを切り替える flip の中で、独自に時間待ち処理をして 60FPS になるようにしてあるのかも。ひょっとして、処理内容としては vsync をチェックしてるつもり、だったりするのかな…。
◎ 文字描画が悩ましい :
FreeBASIC は、標準グラフィック画面上で文字を描画する際に、 draw string を使える。今回も draw string で画面に文字描画できないかなと期待したけれど、OpenGL利用時はそういうことはできないっぽい。仕方ないので、ウインドウタイトルにFPS等を表示して誤魔化したけど…。
となると、FreeBASIC で OpenGL を利用する時は、文字描画処理を自前で書かないといけないようだなと…。
となると、FreeBASIC で OpenGL を利用する時は、文字描画処理を自前で書かないといけないようだなと…。
◎ 平行投影で2D描画っぽく使う :
OpenGLに、glOrtho() を使って平行投影を指示する際、画面の左上が (0, 0)、画面の右下が (w, h) になるように値を指定すれば、ウインドウサイズの2D描画面がそこにあるように見せかけられると知ったのでメモ。
[ ツッコむ ]
以上です。
