2018/03/18(日) [n年前の日記]
#1 [ruby][mruby] MSYS2上でCのプログラムにmrubyを組み込んでSDL2を使う
Windows10 x64 + MSYS2 上で、SDL2を使うCプログラムに、mrruby 1.4.0 を組み込んで、スプライト相当を描画できそうか実験。
DXRuby作者様が、mruby + SDL をビルドする記事を以前公開されていたので、参考にして作業。ありがたや。
_SDL_imageとmrubyを使ってみた - mirichiの日記
ビルドできたら実行してみる。
実行結果。
結構滑らかに動いてるような気がする。
スタティックリンクをしたので、exe と使用画像のみを他のPC(Windows8.1機。Intel Atom CPU搭載)に持っていっただけで動いてくれた。dllファイルをごっそりコピーして持っていかなくても済むあたりはイイ感じ。もっとも、glmrubysdl2.exe のファイルサイズが 10MBを超えるサイズになってるけど…。
一応、exeも含めたファイル一式を zip にして置いておきます。
_glmrubysdl2_20180318.zip
とりあえず、
DXRuby作者様が、mruby + SDL をビルドする記事を以前公開されていたので、参考にして作業。ありがたや。
_SDL_imageとmrubyを使ってみた - mirichiの日記
◎ MSYS2上にSDL2関係をインストール。 :
MSYS2上でSDL2関係のアレコレをインストールする手順については、
_2018/03/13の日記
にメモしてあるけど…。MSYS2は pacman というパッケージ管理ツールが入ってるので…。
- pacman -Ss SDL2 で関連パッケージをリストアップ。
- pacman -S 〜 でパッケージをインストール。
◎ mruby 1.4.0のファイル一式を入手。 :
_Releases - mruby/mruby
から、mruby 1.4.0 の zip をダウンロード。
◎ フォルダ構成。 :
今回は以下のフォルダ構成にした。
~/prg
|
+---- mruby
|
+---- glmrubysdl2
|
+---- mruby
- ~/prg/mruby/mruby/ 以下に、mruby 1.4.0 のファイル一式を入れる。
- ~/prg/mruby/glmrubysdl2/ 以下に、今回書いたプログラム群を入れる。
◎ mruby 1.4.0 をビルド。 :
cd ~/prg/mruby/mruby make
◎ 今回試したソース。 :
glmrubysdl2/ 以下に、
_glmrubysdl2.c
_main.rb
_Makefile
使用画像。
_sample.png
- glmrubysdl2.c ... Cで書いた、SDL2を使うプログラムのソース
- main.rb ... mruby に渡す、mrubyスクリプト
- Makefile ... ビルドするための Makefile
- sample.png ... 使用画像
_glmrubysdl2.c
#include <stdio.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <mruby.h>
#include <mruby/class.h>
#include <mruby/data.h>
#include <mruby/compile.h>
#include <mruby/variable.h>
SDL_Window *mwindow;
SDL_Renderer *renderer;
struct RClass *cSprite;
// class work
struct glmrb_sprite
{
SDL_Surface *surface;
SDL_Texture *texture;
int width;
int height;
};
static void glmrb_sprite_free(mrb_state *mrb, void *ptr);
static struct mrb_data_type mrb_sprite_type = {"Sprite", glmrb_sprite_free};
struct Timer
{
Sint64 now, interval;
// Uint32 now, wit, lev;
} timer;
/* Sprite#initialize */
static mrb_value
glmrb_sprite_initialize(mrb_state *mrb, mrb_value self)
{
struct glmrb_sprite *sprite;
mrb_value vx, vy;
char *s;
sprite = (struct glmrb_sprite *)mrb_malloc(mrb, sizeof(struct glmrb_sprite));
if (sprite == NULL)
{
SDL_Log("Out of memory.\n");
mrb_raise(mrb, E_RUNTIME_ERROR, "out of memory.");
}
DATA_TYPE(self) = &mrb_sprite_type;
DATA_PTR(self) = sprite;
sprite->width = 0;
sprite->height = 0;
sprite->surface = NULL;
sprite->texture = NULL;
// get paramater, o:Object [mrb_value], z:String [mrb_value]
mrb_get_args(mrb, "ooz", &vx, &vy, &s);
mrb_iv_set(mrb, self, mrb_intern(mrb, "@x", 2), vx);
mrb_iv_set(mrb, self, mrb_intern(mrb, "@y", 2), vy);
// load image file
sprite->surface = IMG_Load(s);
if (sprite->surface == NULL)
{
SDL_Log("Cannot create SDL_Surface.\n");
mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create SDL_Surface.");
}
else
{
// get image width and height
sprite->width = sprite->surface->w;
sprite->height = sprite->surface->h;
}
// create texture
sprite->texture = SDL_CreateTextureFromSurface(renderer, sprite->surface);
if (sprite->texture == NULL)
{
SDL_Log("Cannot create SDL_Texture.\n");
mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create SDL_Texture.");
}
return self;
}
/* Sprite#draw */
static mrb_value glmrb_sprite_draw(mrb_state *mrb, mrb_value self)
{
struct glmrb_sprite *sprite = DATA_PTR(self);
SDL_Rect src, dst;
src.x = 0;
src.y = 0;
src.w = sprite->width;
src.h = sprite->height;
dst.x = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern(mrb, "@x", 2)));
dst.y = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern(mrb, "@y", 2)));
dst.w = src.w;
dst.h = src.h;
// SDL_BlitSurface(sprite->surface, &src, mwindow, &dst);
SDL_RenderCopy(renderer, sprite->texture, &src, &dst);
return self;
}
/* Sprite#width */
static mrb_value glmrb_sprite_width(mrb_state *mrb, mrb_value self)
{
struct glmrb_sprite *sprite = DATA_PTR(self);
return mrb_fixnum_value(sprite->width);
}
/* Sprite#height */
static mrb_value glmrb_sprite_height(mrb_state *mrb, mrb_value self)
{
struct glmrb_sprite *sprite = DATA_PTR(self);
return mrb_fixnum_value(sprite->height);
}
static void glmrb_sprite_free(mrb_state *mrb, void *ptr)
{
struct glmrb_sprite *sprite = ptr;
if (sprite->texture != NULL)
SDL_DestroyTexture(sprite->texture);
if (sprite->surface != NULL)
SDL_FreeSurface(sprite->surface);
mrb_free(mrb, sprite);
}
// quit check
int quits(void)
{
SDL_Event events;
if (SDL_PollEvent(&events))
{
switch (events.type)
{
case SDL_QUIT:
return 0;
break;
case SDL_KEYDOWN:
if (events.key.keysym.sym == SDLK_ESCAPE ||
events.key.keysym.sym == SDLK_q)
return 0;
break;
default:
break;
}
}
return 1;
}
int main(int argc, char *argv[])
{
FILE *fp;
mrb_value mrb_obj;
// mruby state open
mrb_state *mrb = mrb_open();
if (mrb == 0)
{
SDL_Log("Cannot open mruby state.\n");
return -2;
}
// define class
cSprite = mrb_define_class(mrb, "Sprite", mrb->object_class);
MRB_SET_INSTANCE_TT(cSprite, MRB_TT_DATA);
// define class method
mrb_define_method(mrb, cSprite, "initialize",
glmrb_sprite_initialize, MRB_ARGS_REQ(3));
mrb_define_method(mrb, cSprite, "draw",
glmrb_sprite_draw, MRB_ARGS_NONE());
mrb_define_method(mrb, cSprite, "width",
glmrb_sprite_width, MRB_ARGS_NONE());
mrb_define_method(mrb, cSprite, "height",
glmrb_sprite_height, MRB_ARGS_NONE());
// SDL2 init
if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
{
SDL_Log("Failure SDL_INIT()\n");
mrb_close(mrb);
return -1;
}
// mwindow = SDL_SetVideoMode(640, 480, 0, 0);
// SDL_WM_SetCaption("glmruby application", NULL);
mwindow = SDL_CreateWindow("glmrubysdl2", 100, 100, 640, 480, 0);
if (mwindow == NULL)
{
SDL_Log("Cannot create window.\n");
SDL_Quit();
mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create window.");
exit(-1);
}
// create renderer
renderer = SDL_CreateRenderer(mwindow, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL)
{
SDL_Log("Cannot create renderer.\n");
SDL_DestroyWindow(mwindow);
SDL_Quit();
mrb_raise(mrb, E_RUNTIME_ERROR, "cannot create renderer.");
exit(-1);
}
// load and run mruby script. execute after create renderer
fp = fopen("main.rb", "r");
if (fp == NULL)
{
SDL_Log("Cannot load main.rb.\n");
mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot load main.rb");
exit(-2);
}
mrb_obj = mrb_load_file(mrb, fp);
fclose(fp);
timer.interval = (Sint64)SDL_GetPerformanceFrequency() / 60;
timer.now = 0;
int framecounter = 0;
// main loop
while (quits() != 0)
{
mrb_funcall(mrb, mrb_obj, "update", 0);
// clear with background color
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// SDL_FillRect(mwindow, NULL, SDL_MapRGB(mwindow->format, 0, 0, 0));
mrb_funcall(mrb, mrb_obj, "draw", 0);
SDL_RenderPresent(renderer);
// timer wait
while (SDL_TRUE)
{
if (((Sint64)SDL_GetPerformanceCounter() - timer.now) >= timer.interval)
break;
SDL_Delay(0);
}
timer.now = SDL_GetPerformanceCounter();
// timer.now = SDL_GetTicks();
// timer.wit = timer.now - timer.lev;
// if (timer.wit < 16)
// SDL_Delay(16 - timer.wit);
// timer.lev = SDL_GetTicks();
// SDL_UpdateRect(mwindow, 0, 0, 0, 0);
framecounter++;
// if (framecounter % 60 == 0)
// printf("%d\n", framecounter);
}
mrb_close(mrb);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(mwindow);
SDL_Quit();
return 0;
}
_main.rb
#!ruby
class TestSprite < Sprite
def initialize(x, y, imagefile, dx, dy)
super(x, y, imagefile)
@bx = x
@by = y
@dx = dx
@dy = dy
end
def update
wh = width / 2
hh = height / 2
@bx += @dx
@by += @dy
@dx *= -1 if (@bx <= wh or @bx >= 640 - wh)
@dy *= -1 if (@by <= hh or @by >= 480 - hh)
@x = (@bx - wh).to_i
@y = (@by - hh).to_i
end
end
class Scene
def initialize
@sprs = []
num = 128
ang = 0
ang_d = 360.0 / num
num.times do
rad = ang * Math::PI / 180.0
r = rand * 4.0 + 1.0
dx = r * Math.cos(rad)
dy = r * Math.sin(rad)
s = TestSprite.new(320, 240, "sample.png", dx, dy)
@sprs.push(s)
ang += ang_d
end
end
def update
@sprs.each { |s| s.update }
end
def draw
@sprs.each { |s| s.draw }
end
end
Scene.new
_Makefile
TARGETS = glmrubysdl2 all: $(TARGETS) SDL_PREFIX = /mingw32 # SDL_PREFIX = /mingw MRB_HEAD = ../mruby/include MRB_LIBS = ../mruby/build/host/lib # SDL_CONFIG = $(SDL_PREFIX)/bin/sdl2-config SDL_CONFIG = sdl2-config CG_LIBS = CROSS_COMPILE = $(SDL_PREFIX)/bin/ CC = $(CROSS_COMPILE)gcc CXX = $(CROSS_COMPILE)g++ # SDLCFLAGS = `$(SDL_CONFIG) --cflags` SDLCFLAGS = -I$(SDL_PREFIX)/include/SDL2 -Dmain=SDL_main CFLAGS = -g -Wall $(SDLCFLAGS) -I$(MRB_HEAD) CXXFLAGS = -g -Wall $(SDLCFLAGS) -I$(MRB_HEAD) # SDLLIBS = `$(SDL_CONFIG) --libs` # SDLLIBS = -L$(SDL_PREFIX)/lib -lmingw32 -lSDL2main -lSDL2 -mwindows SDLLIBS = -L$(SDL_PREFIX)/lib -lmingw32 -lSDL2main -lSDL2 # LDFLAGS = -Wl,-rpath,$(SDL_PREFIX)/lib -static -static-libgcc -static-libstdc++ -mwindows LDFLAGS = -Wl,-rpath,$(SDL_PREFIX)/lib -static -static-libgcc -static-libstdc++ # LIBS = -L$(MRB_LIBS) -lmruby -lm $(SDLLIBS) -lopengl32 -lglu32 -lm -lSDL2_image LIBS = -L$(MRB_LIBS) -lmruby -lm $(SDLLIBS) \ -lSDL2_image -lpng -lz -ljpeg -ltiff -lwebp -llzma \ -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm \ -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid \ -lopengl32 -lglu32 -lpthread clean: rm -f *.o *.a *~ $(TARGETS) $(TARGETS): $(TARGETS).o $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS)
使用画像。
_sample.png
◎ ビルドしてみる。 :
$ cd ~/prg/mruby/glmrubysdl2/ $ make /mingw32/bin/gcc -g -Wall -I/mingw32/include/SDL2 -Dmain=SDL_main -I../mruby/include -c -o glmrubysdl2.o glmrubysdl2.c /mingw32/bin/g++ -o glmrubysdl2 glmrubysdl2.o -L../mruby/build/host/lib -lmruby -lm -L/mingw32/lib -lmingw32 -lSDL2main -lSDL2 -lSDL2_image -lpng -lz -ljpeg -ltiff -lwebp -llzma -ldinput8 -ldxguid -ldxerr8 -luser32 -lgdi32 -lwinmm -limm32 -lole32 -loleaut32 -lshell32 -lversion -luuid -lopengl32 -lglu32 -lpthread -Wl,-rpath,/mingw32/lib -static -static-libgcc -static-libstdc++
ビルドできたら実行してみる。
$ ./glmrubysdl2.exe
実行結果。
結構滑らかに動いてるような気がする。
スタティックリンクをしたので、exe と使用画像のみを他のPC(Windows8.1機。Intel Atom CPU搭載)に持っていっただけで動いてくれた。dllファイルをごっそりコピーして持っていかなくても済むあたりはイイ感じ。もっとも、glmrubysdl2.exe のファイルサイズが 10MBを超えるサイズになってるけど…。
一応、exeも含めたファイル一式を zip にして置いておきます。
_glmrubysdl2_20180318.zip
とりあえず、
- SDL2を使うプログラムをCで書く際のアレコレ。
- Cで書いたプログラムに mruby を組み込む方法。
- MSYS2上でビルドする方法。
◎ 課題。 :
せっかくだから、DXRuby の Image っぽい感じで画像を読み込んで Sprite に渡すように…と思ったけれど、DXRuby の Image のようなものをどうやって作ればいいのか分からず。DXRuby の Image は、Image.new(w, h, color) で生成できる上に、Image.load("画像ファイル名") でも生成できる…。.new() 以外でも生成できる何か、ってどうやって作るのかしら。
また、mrubyスクリプト内で何かエラーが発生した場合に、エラーが出ていることを表示する方法も分かってなくて。当初、実行しても何も画面に表示されなくて、Cのソースとにらめっこしたのだけど、結局 mrubyスクリプト側で記述が間違っていた、てな場面があって。mrb_load_file() で mrubyスクリプトを読み込んで実行した際、エラーが出てるかどうかを調べる何かが用意されているのだろうな、とは思うのだけど…。
また、mrubyスクリプト内で何かエラーが発生した場合に、エラーが出ていることを表示する方法も分かってなくて。当初、実行しても何も画面に表示されなくて、Cのソースとにらめっこしたのだけど、結局 mrubyスクリプト側で記述が間違っていた、てな場面があって。mrb_load_file() で mrubyスクリプトを読み込んで実行した際、エラーが出てるかどうかを調べる何かが用意されているのだろうな、とは思うのだけど…。
◎ 参考ページ。 :
[ ツッコむ ]
以上、1 日分です。