mieki256's diary



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の日記

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 ... 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上でビルドする方法。
この3つは、少し分かってきた気がする。

課題。 :

せっかくだから、DXRuby の Image っぽい感じで画像を読み込んで Sprite に渡すように…と思ったけれど、DXRuby の Image のようなものをどうやって作ればいいのか分からず。DXRuby の Image は、Image.new(w, h, color) で生成できる上に、Image.load("画像ファイル名") でも生成できる…。.new() 以外でも生成できる何か、ってどうやって作るのかしら。

また、mrubyスクリプト内で何かエラーが発生した場合に、エラーが出ていることを表示する方法も分かってなくて。当初、実行しても何も画面に表示されなくて、Cのソースとにらめっこしたのだけど、結局 mrubyスクリプト側で記述が間違っていた、てな場面があって。mrb_load_file() で mrubyスクリプトを読み込んで実行した際、エラーが出てるかどうかを調べる何かが用意されているのだろうな、とは思うのだけど…。

参考ページ。 :


以上です。

過去ログ表示

Prev - 2018/03 -
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project