0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RG35XX Hで動くアプリを作れないか (1)

Posted at

背景

以前より安価なLinuxベースの中華ゲーム機をシンセサイザーに転用することを目論んできた。JUCEを使ったシンセサイザープラグインはLinuxではX11が必須となるので、まずX11を動作させなければいけないという大きなハードルがあった。そこで、これらのゲーム機で動いているゲームエミュレーター同様、SDL (Simple DirectMedia Layer) を使ってアプリを作れば良いかもしれないと思いそれを試すことにした。

基本的な作戦としては、簡単なコードが実機で動くことが確認できたら、あとはMac上でほとんど全ての開発および動作確認を行い、ちょいちょい実機での動作も確認する、という方式。実機ではデバッガを使った確認はできないと思うくらいの気持ちで。なんならprintfデバッグすらできないかもしれないという想定。

準備

Anbernic RG35XX H (他のRG35XXシリーズとかRG28XXシリーズとかでもいけそう)
Mac (開発に使う)
microSDカード (32GBくらいでいいと思う。速度優先おすすめ)

Anbernicのサイトからファームウェアのイメージをダウンロード。
https://jp.anbernic.com/pages/firmware
自分の持ってるデバイス用の32GBのイメージで。
microSDカードに入れる。
本体に刺して起動することを確認。

https://github.com/exdial/anbernic-apps からSSH-Enablerをダウンロード。
Place the contents of the SSH-Enabler directory in Roms/APPS inside the internal(TF1) or external(TF2) SD card and start it from the Anbernic APPS menu.
と書いてあるとおり、このようにmicroSDカードのRoms>APPSの中にコピーする。Imgに入っている2つの画像ファイルも入れておく。

image.png

アプリはシステムのSDカードでも2枚目のSDカードでも好きな方のAPPSフォルダに入れればよい。実行時にTF1かTF2かを選択する画面になる。

TF1とTF2へのアクセス方法

TF1 -> /mnt/mmc
TF2 -> /mnt/sdcarsd

WiFiに接続

SSH-Enablerを入れたSDカードを差した状態でRG35XXを起動し、メニューボタンを押してネットワーク接続設定から。SSIDとパスワードを入れて繋がったらIPアドレスが表示される。

SSHを有効にする

AppsからEnable SSHを選択する。しばらくすると戻ってくるのでそれでSSHが有効になる。無効にしたい場合はAppsに行きDisable SSH。
Macでターミナルを起動してSSH接続を試す。

ssh root@IPアドレス

パスワードはroot。繋がったら下記のように出てくるはず。Ubuntuベースだということがわかる。

Welcome to Ubuntu 22.04 LTS (GNU/Linux 4.9.170 aarch64)


The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@ANBERNIC:~# 

必要なツールをインストール

まずはアップデートとアップグレード。アップグレードはかなり時間かかる。正直やらなくてもいいのかもしれないが。

apt update -y
apt upgrade -y

コンパイルに必要なもの一式をインストール。

apt install -y build-essential cmake g++ nano git

SDLはSDL2の基本パッケージ以外に下記も入れておく。

  • SDL2_mixer(オーディオミキシング)
  • SDL2_image(画像読み込み)
  • SDL2_ttf(フォントレンダリング)
  • SDL2_net(ネットワーク)
apt install -y libsdl2-dev libsdl2-mixer-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-net-dev

OpenGLのために下記も入れる。

apt install -y libgl1-mesa-dev

ImGuiというGUIライブラリを使うので。これはプロジェクトのフォルダに入れた方がいいかも。

git clone https://github.com/ocornut/imgui.git

SDLとOpenGLを使ったアプリの確認

次のコードをmain.cppとして作成。ちなみにこのコードは100%AIが書いた。

main.cpp
#include <SDL2/SDL.h>
#include <GLES2/gl2.h>
#include <string>
#include <vector>

// シェーダーは変更なし
const char* vertexShaderSource = R"(
    attribute vec2 position;
    attribute vec4 color;
    varying vec4 v_color;
    void main() {
        gl_Position = vec4(position, 0.0, 1.0);
        v_color = color;
    }
)";

const char* fragmentShaderSource = R"(
    precision mediump float;
    varying vec4 v_color;
    void main() {
        gl_FragColor = v_color;
    }
)";

// 7セグメントの定義(各数字でどのセグメントが点灯するか)
// セグメントの順序: 上、右上、右下、下、左下、左上、中央
const bool SEGMENTS[10][7] = {
    {1,1,1,1,1,1,0}, // 0
    {0,1,1,0,0,0,0}, // 1
    {1,1,0,1,1,0,1}, // 2
    {1,1,1,1,0,0,1}, // 3
    {0,1,1,0,0,1,1}, // 4
    {1,0,1,1,0,1,1}, // 5
    {1,0,1,1,1,1,1}, // 6
    {1,1,1,0,0,0,0}, // 7
    {1,1,1,1,1,1,1}, // 8
    {1,1,1,1,0,1,1}  // 9
};

// セグメントを描画する関数
void addSegment(std::vector<float>& vertices, float x, float y, float width, float height, int segmentType) {
    float thickness = width * 0.15f; // セグメントの太さ
    float gap = thickness * 0.5f;    // セグメント間のギャップ

    switch(segmentType) {
        case 0: // 上横
            vertices.push_back(x + gap);         vertices.push_back(y);
            vertices.push_back(x + width - gap); vertices.push_back(y);
            break;
        case 1: // 右上縦
            vertices.push_back(x + width); vertices.push_back(y - gap);
            vertices.push_back(x + width); vertices.push_back(y - height/2 + gap);
            break;
        case 2: // 右下縦
            vertices.push_back(x + width); vertices.push_back(y - height/2 - gap);
            vertices.push_back(x + width); vertices.push_back(y - height + gap);
            break;
        case 3: // 下横
            vertices.push_back(x + gap);         vertices.push_back(y - height);
            vertices.push_back(x + width - gap); vertices.push_back(y - height);
            break;
        case 4: // 左下縦
            vertices.push_back(x); vertices.push_back(y - height/2 - gap);
            vertices.push_back(x); vertices.push_back(y - height + gap);
            break;
        case 5: // 左上縦
            vertices.push_back(x); vertices.push_back(y - gap);
            vertices.push_back(x); vertices.push_back(y - height/2 + gap);
            break;
        case 6: // 中央横
            vertices.push_back(x + gap);         vertices.push_back(y - height/2);
            vertices.push_back(x + width - gap); vertices.push_back(y - height/2);
            break;
    }
}

// 数字一つを描画するための頂点を生成
void addNumber(std::vector<float>& vertices, float x, float y, float size, int number) {
    float width = size * 0.6f;
    float height = size;

    for (int i = 0; i < 7; i++) {
        if (SEGMENTS[number][i]) {
            addSegment(vertices, x, y, width, height, i);
        }
    }
}

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) return 1;

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_Window* window = SDL_CreateWindow("FPS Test",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);

    if (!window) {
        SDL_Quit();
        return 1;
    }

    SDL_GLContext glContext = SDL_GL_CreateContext(window);
    if (!glContext) {
        SDL_DestroyWindow(window);
        SDL_Quit();
        return 1;
    }

    SDL_GL_SetSwapInterval(1);

    // シェーダープログラムのセットアップ
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);

    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    // バッファの作成
    GLuint VBO;
    glGenBuffers(1, &VBO);

    // 三角形の頂点データ
    float triangleVertices[] = {
         0.0f,  0.5f,
        -0.5f, -0.5f,
         0.5f, -0.5f
    };

    // メインループ
    bool quit = false;
    SDL_Event event;
    Uint32 frameStart = SDL_GetTicks();
    int frameCount = 0;
    int fps = 0;

    glLineWidth(3.0f); // 線を太くする

    while (!quit) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT || 
                (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
                quit = true;
            }
        }

        // FPS計算
        frameCount++;
        Uint32 currentTime = SDL_GetTicks();
        if (currentTime - frameStart >= 1000) {
            fps = frameCount * 1000 / (currentTime - frameStart);
            frameCount = 0;
            frameStart = currentTime;
        }

        glClearColor(0.2f, 0.3f, 0.8f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shaderProgram);

        GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
        GLint colorAttrib = glGetAttribLocation(shaderProgram, "color");
        glEnableVertexAttribArray(posAttrib);

        // 三角形の描画
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(triangleVertices), triangleVertices, GL_STATIC_DRAW);
        glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
        float red[] = {1.0f, 0.0f, 0.0f, 1.0f};
        glVertexAttrib4fv(colorAttrib, red);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // FPS数字の描画
        std::vector<float> numberVertices;
        std::string fpsStr = std::to_string(fps);
        float startX = -0.95f;
        float startY = 0.9f;
        float size = 0.15f;

        for (char digit : fpsStr) {
            addNumber(numberVertices, startX, startY, size, digit - '0');
            startX += size * 0.8f;
        }

        if (!numberVertices.empty()) {
            glBufferData(GL_ARRAY_BUFFER, numberVertices.size() * sizeof(float),
                        numberVertices.data(), GL_STATIC_DRAW);
            glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
            float white[] = {1.0f, 1.0f, 1.0f, 1.0f};
            glVertexAttrib4fv(colorAttrib, white);
            glDrawArrays(GL_LINES, 0, numberVertices.size() / 2);
        }

        SDL_GL_SwapWindow(window);

        Uint32 frameTime = SDL_GetTicks() - currentTime;
        if (frameTime < 16) {
            SDL_Delay(16 - frameTime);
        }
    }

    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);
    glDeleteShader(fragmentShader);
    glDeleteShader(vertexShader);

    SDL_GL_DeleteContext(glContext);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

コンパイルする。

g++ -o sdltest main.cpp -lSDL2 -lGLESv2 -lEGL

このままリモートから実行するとエラーになるので、アプリを/mnt/mmc/Roms/APPS/にコピー。

cp sdltest /mnt/mmc/Roms/APPS/

実機で動作確認。

68504CD5-190E-49F0-AB8E-D20AA6C46959_1_102_o.jpeg

終了する方法がないので、プロセスを探してkillする。

root@ANBERNIC:/mnt/sdcard/home# ps -A | grep sdltest
  23330 ?        00:00:25 sdltest
root@ANBERNIC:/mnt/sdcard/home# kill 23330
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?