toge's diary

コンピュータ関連の趣味をつらつらと。

軽い画面キャプチャ

ちょっとはまったけれどまずは馬鹿みたいにBMPファイルに書き込む動作は出来た。これだけでも結構使える。

はまったのはプログラムでVBOを使っていることを考えて、glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);としておかないと異常終了する場合があること。

こんな単純なプログラムでも、CPUの性能かHDDのキャッシュが効いているのか、ほとんど動作が遅くなることがなくキャプチャできてしまうのはびっくり。
早くも今回の目的達成の予感・・・。

でも他のプロセスと書き込みがかち合うとがくっと遅くなるため、動作がカクカクしてしまう。
別スレッドにするなり、非同期IOを採用するなりして、ゲームのメインスレッドがストールしないように実装してみます。

#include <cstdio>
#include <dlfcn.h>

#include "SDL.h"
#include "GL/glx.h"

#include "Writer.h"
#include "ScreenCapture.h"

extern "C" {
  const int SPAN = 33;

  static void (*_SDLGLSwapBuffers)() = NULL;
  static void (*_glXSwapBuffers)(Display*,GLXDrawable) = NULL;

  void __attribute__((constructor)) init_SDLGLSwapBuffers() {
    _SDLGLSwapBuffers = (void(*)())dlsym(RTLD_NEXT, "SDL_GL_SwapBuffers");
    _glXSwapBuffers = (void(*)(Display*,GLXDrawable))dlsym(RTLD_NEXT, "glXSwapBuffers");
  }

  void tryCapture(ScreenCapture& capture) {
    static Uint32 previous = 0;
    Uint32 now = SDL_GetTicks();

    if (previous + SPAN > now)
      return;
    previous = now + SPAN;

    char buffer[sizeof("test.bmp") + 5];
    {
      static int index = 0;
      std::sprintf(buffer, "test%05d.bmp", index++);
    }
    
    WriterFile writer;
    writer.open(buffer);
    
    capture();
    writeBMP(capture, writer);
    writer.close();
  }

  void glXSwapBuffers(Display* dpy, GLXDrawable drawable) {
    static ScreenCapture capture;
    if (capture.isInited() == false) {
      int viewport[4];
      glGetIntegerv(GL_VIEWPORT, viewport);
      capture.init(viewport[2], viewport[3]);
    }

    tryCapture(capture);

    _glXSwapBuffers(dpy, drawable);
  }

  void SDL_GL_SwapBuffers() {
    static ScreenCapture capture;
    if (capture.isInited() == false) {
      SDL_Surface* surface = SDL_GetVideoSurface();
      capture.init(surface->w, surface->h);
    }
    
    tryCapture(capture);

    _SDLGLSwapBuffers();
  }
}