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スクリプトを読み込んで実行した際、エラーが出てるかどうかを調べる何かが用意されているのだろうな、とは思うのだけど…。
◎ 参考ページ。 :
[ ツッコむ ]
以上です。