Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Getting started on Raspberry Pi 4 with Debian Bullseye
#1
I've recently purchased the book and am trying to get the initial triangle code running. I've some experience with normal OpenGL but I'm by no means a graphics programmer. I have no experience with SBC and thought this would be really fun. But I'm struggling to figure out what needs to change to work on the current hardware, despite the helpful posts dotted around the forum.

Specifically, I currently get squiggly red errors that EGL and GLES cannot be found when included. Here's my list of libraries and includes from VisualGDB:

Include directories:
  • /usr/include
  • /usr/include/interface/vcos/pthreads
  • /usr/include/interface
  • /usr/include/interface/vmcs_host/linux

Library directories:
  • /usr/lib

Library names:
  • GLESv2
  • EGL
  • X11
  • bcm_host (although I don't think I need this)
  • pthread

I've installed the following packages:
  • libgles2-mesa-dev
  • libxrandr-dev
  • mesa-utils

I've got a Raspberry Pi 4 on Debian 11 / Bullseye (latest Raspberry OS).

Here's some helpful glxinfo output:

Code:
glxinfo | grep version
server glx version string: 1.4
client glx version string: 1.4
GLX version: 1.4
    Max core profile version: 0.0
    Max compat profile version: 2.1
    Max GLES1 profile version: 1.1
    Max GLES[23] profile version: 3.1
OpenGL version string: 2.1 Mesa 20.3.5
OpenGL shading language version string: 1.20
OpenGL ES profile version string: OpenGL ES 3.1 Mesa 20.3.5
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.10
    GL_EXT_separate_shader_objects, GL_EXT_shader_implicit_conversions,

I saw the comment about disabling OpenGL in raspi-config but there is no option to do so under advance or, as far as I could see, any other menu.

I don't know where to go from here to get things working. If anyone can let me know what packages need to be installed, what libraries and includes, etc, I can figure the rest. I'm probably even happy to try GLES3 if need be.

Cheers.

If I change the libraries to:
  • GLESv2_static
  • EGL_static
  • vchiq_arm
  • vcos khrn_static
  • bcm_host
  • pthread
I get the following error:

Code:
Run "gcc -Wl,--start-group "/tmp/VisualGDB_ToolchainTestProgram.o" -o "/tmp/VisualGDB_ToolchainTestProgram" -Wl,-gc-sections -L/usr/lib -lGLESv2_static -lEGL_static -lvchiq_arm -lvcos -lkhrn_static -lbcm_host -lpthread -Wl,--end-group " in directory "/tmp/VisualGDB/d/SBC_Game_Dev/PiGame1/PiGame1/PiGame1" on local computer
--------------------------
/usr/bin/ld: cannot find -lGLESv2_static
/usr/bin/ld: cannot find -lEGL_static
/usr/bin/ld: cannot find -lkhrn_static
collect2: error: ld returned 1 exit status
--------------------------
Command exited with code 1
Reply
#2
yeah things have changed quite a bit since the book was written and even the updates listed when the Pi4 came out don't apply now, the latest OS no longer has the same libs we have to use the mesa libs now...
I will try to give you a fuller answer and walk through the new set up with you later today. I've got a bit too much to do this afternoon.
Brian Beuken
Lecturer in Game Programming at Breda University of Applied Sciences.
Author of The Fundamentals of C/C++ Game Programming: Using Target-based Development on SBC's 



Reply
#3
Okay, I found the example code from Brian and jomoengineer - http://www.scratchpadgames.net/forums/sh...hp?tid=507

It looks like I just need to include <X11/XLib.h> and maybe a few other headers instead of those for DispmanX.

I'll report back how I get on porting this. I'm thinking I might try and convert it to GLES3 also, possibly as a separate codebase.

Brian, do you have a GitHub repo for the book where I could raise a PR once I get the triangle and any other examples working?
Reply
#4
The issue I had with the includes was that I needed to refresh the VisualGDB cache after installing the libraries.

Okay, here's the working updated code for the Hello Triangle example (Debian Bullseye, Raspberry Pi 4, OpenGLESv2).

Libraries I installed on the Raspberry Pi (some might not be necessary, I was having a bit of a Hail Mary moment yesterday trying to get this working):
- libgles2-mesa-dev
- libxrandr-dev
- mesa-utils (probably not needed)
- libegl1-mesa-dev (dependency of libgles2-mesa-dev so should be installed automagically)

Makefile settings:
Include directories - /usr/include /usr/include/interface/vcos/pthreads /usr/include/interface /usr/include/interface/vmcs_host/linux /usr/include/EGL /usr/include/GLES2
Library directories - /usr/lib
Library names - GLESv2 EGL X11 pthread

Code:
/*
* Hello triangle
*/

#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TRUE 1
#define FALSE 0

typedef struct {
    GLuint program_object;
} UserData;

typedef struct TargetState {
    uint32_t width;
    uint32_t height;

    Display* x_display;
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLNativeWindowType native_window;

    UserData* user_data;
    void (*draw_func)(struct TargetState*);
} TargetState;

TargetState state;
TargetState* p_state = &state;

static const EGLint attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
    EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE };

GLuint load_shader(GLenum type, const char* shader_src)
{
    GLuint shader = glCreateShader(type);

    if (shader == 0) {
        return 0;
    }
    glShaderSource(shader, 1, &shader_src, NULL);
    glCompileShader(shader);

    GLint is_it_compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &is_it_compiled);
    if (!is_it_compiled) {
        GLint info_len = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
        if (info_len > 1) {
            char* info_log = (char*)malloc(sizeof(char) * info_len);
            glGetShaderInfoLog(shader, info_len, NULL, info_log);
            fprintf(stderr, "Error compiling this shader:\n%s\n", info_log);
            free(info_log);
        }
        glDeleteShader(shader);
        return 0;
    }
    return shader;
}

int init(TargetState* p_state)
{
    p_state->user_data = (UserData*)malloc(sizeof(UserData));

    GLbyte v_shader_str[] = "attribute vec4 a_position;\n"
                            "attribute vec2 a_texCoord;\n"
                            "varying vec2 v_texCoord;\n"
                            "void main()\n"
                            "{gl_Position=a_position;\n"
                            "v_texCoord = a_texCoord;}\n";

    GLbyte f_shader_str[] = "precision mediump float;\n"
                            "varying vec2 v_texCoord;\n"
                            "uniform sampler2D s_texture;\n"
                            "void main()\n"
                            "{gl_FragColor=vec4 (1.0, 0.0, 0.0, 1.0);}\n";

    GLuint program_object, vertex_shader, fragment_shader;

    vertex_shader = load_shader(GL_VERTEX_SHADER, (char*)v_shader_str);
    fragment_shader = load_shader(GL_FRAGMENT_SHADER, (char*)f_shader_str);

    program_object = glCreateProgram();
    if (program_object == 0) {
        return 0;
    }

    glAttachShader(program_object, vertex_shader);
    glAttachShader(program_object, fragment_shader);

    glLinkProgram(program_object);

    GLint are_they_linked;
    glGetProgramiv(program_object, GL_LINK_STATUS, &are_they_linked);
    if (!are_they_linked) {
        GLint info_len = 0;
        glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len);
        if (info_len > 1) {
            char* info_log = (char*)malloc(sizeof(char) * info_len);
            glGetProgramInfoLog(program_object, info_len, NULL, info_log);
            fprintf(stderr, "Error linking program:\n%s\n", info_log);
            free(info_log);
        }
        glDeleteProgram(program_object);
        return FALSE;
    }

    p_state->user_data->program_object = program_object;

    return TRUE;
}

void init_ogl(TargetState* state, int width, int height)
{
    state->width = width;
    state->height = height;

    EGLint num_configs;
    EGLint major_version;
    EGLint minor_version;

    EGLDisplay display;
    EGLContext context;
    EGLSurface surface;
    EGLConfig config;
    EGLint context_atttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };

    Window root;
    XSetWindowAttributes swa;
    XSetWindowAttributes xattr;
    Atom wm_state;
    XWMHints hints;
    XEvent xev;
    EGLConfig ecfg;
    EGLint num_config;
    Window win;
    Screen* screen;

    Display* x_display = state->x_display;
    x_display = XOpenDisplay(NULL);
    if (x_display == NULL) {
        printf("Sorry to say we can't create an Xwindow and this will fail");
        exit(0); // return; we need to trap this.
    }
    eglBindAPI(EGL_OPENGL_ES_API);
    root = DefaultRootWindow(x_display);
    screen = ScreenOfDisplay(x_display, 0);

    state->width = width;
    state->height = height;

    swa.event_mask = ExposureMask | PointerMotionMask | KeyPressMask | KeyReleaseMask;
    swa.background_pixmap = None;
    swa.background_pixel = 0;
    swa.border_pixel = 0;
    swa.override_redirect = TRUE;

    win = XCreateWindow(
        x_display, root, 0, 0, width, height, 0, CopyFromParent, InputOutput, CopyFromParent, CWEventMask, &swa);

    XSelectInput(x_display, win, KeyPressMask | KeyReleaseMask);

    xattr.override_redirect = TRUE;
    XChangeWindowAttributes(x_display, win, CWOverrideRedirect, &xattr);

    hints.input = TRUE;
    hints.flags = InputHint;
    XSetWMHints(x_display, win, &hints);

    char* title = (char*)"x11 window Triangle Example";
    // Make the window visible on the screen
    XMapWindow(x_display, win);
    XStoreName(x_display, win, title);

    // Get identifiers for the provided atom name strings
    wm_state = XInternAtom(x_display, "_NET_WM_STATE", FALSE);

    memset(&xev, 0, sizeof(xev));
    xev.type = ClientMessage;
    xev.xclient.window = win;
    xev.xclient.message_type = wm_state;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 1;
    xev.xclient.data.l[1] = FALSE;
    XSendEvent(x_display, DefaultRootWindow(x_display), FALSE, SubstructureNotifyMask, &xev);

    state->native_window = (EGLNativeWindowType)win;

    // Get display
    display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        printf("Sorry to say we have an EGLinit error and this will fail");
        EGLint err = eglGetError();
        return; // EGL_FALSE;
    }

    // Initialise EGL
    if (!eglInitialize(display, &major_version, &minor_version)) {
        printf("Sorry to say we have an EGLinit error and this will fail");
        EGLint err = eglGetError();
        return; // EGL_FALSE;
    }

    // Get configs
    if (!eglGetConfigs(display, NULL, 0, &num_configs)) {
        printf("Sorry to say we have EGL config errors and this will fail");
        EGLint err = eglGetError();
        return; // EGL_FALSE;
    }

    // Choose config
    if (!eglChooseConfig(display, attribute_list, &config, 1, &num_configs)) {
        printf("Sorry to say we have config choice issues and this will fail");
        EGLint err = eglGetError();
        return; // EGL_FALSE;
    }

    // Create a surface
    surface = eglCreateWindowSurface(display, config, state->native_window, NULL);
    if (surface == EGL_NO_SURFACE) {
        EGLint err = eglGetError();
        return; // EGL_FALSE;
    }

    // Create a GL context
    context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_atttribs);
    if (context == EGL_NO_CONTEXT) {
        EGLint err = eglGetError();
        return; // EGL_FALSE;
    }

    // Make the context current
    if (!eglMakeCurrent(display, surface, surface, context)) {
        EGLint err = eglGetError();
        return; // EGL_FALSE;
    }

    state->display = display;
    state->surface = surface;
    state->context = context;

    // just for fun lets see what we can do with this GPU
    printf("This SBC supports version %i.%i of EGL\n", major_version, minor_version);
    printf("This GPU supplied by  :%s\n", glGetString(GL_VENDOR));
    printf("This GPU supports    :%s\n", glGetString(GL_VERSION));
    printf("This GPU Renders with :%s\n", glGetString(GL_RENDERER));
    printf("This GPU supports    :%s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
    printf("This GPU supports these extensions    :%s\n", glGetString(GL_EXTENSIONS));

    EGLBoolean gl_test = eglGetConfigAttrib(display, config, EGL_MAX_SWAP_INTERVAL, &minor_version);

    // 1 to lock speed to 60fps (assuming we are able to maintain it)
    // 0 for immediate swap (may cause tearing) which will indicate actual frame rate
    EGLBoolean test = eglSwapInterval(display, 1);

    if (glGetError() == GL_NO_ERROR) {
        return;
    } else {
        printf("Oh bugger, Some part of the EGL/OGL graphic init failed\n");
    }
}

void draw(TargetState* p_state)
{
    UserData* user_data = p_state->user_data;
    GLfloat triangle_vertices[] = { 0.0f, 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f };

    glViewport(0, 0, p_state->width, p_state->height);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(user_data->program_object);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, triangle_vertices);
    glEnableVertexAttribArray(0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    if (glGetError() != GL_NO_ERROR) {
        printf("Oh bugger");
    }
}

void es_init_context(TargetState* p_state)
{
    if (p_state != NULL) {
        memset(p_state, 0, sizeof(TargetState));
    }
}

void es_register_draw_func(TargetState* p_state, void (*draw_func)(TargetState*)) { p_state->draw_func = draw_func; }

void es_main_loop(TargetState* es_context)
{
    int counter = 0;
    while (counter++ < 200) {
        if (es_context->draw_func != NULL) {
            es_context->draw_func(es_context);
        }
        eglSwapBuffers(es_context->display, es_context->surface);
    }
}

int main(int argc, char* argv[])
{
    UserData user_data;
    es_init_context(p_state);
    init_ogl(p_state, 1024, 720);
    p_state->user_data = &user_data;

    if (!init(p_state)) {
        return 0;
    }
    es_register_draw_func(p_state, draw);

    es_main_loop(p_state);
}
Reply
#5
so you got it working? Big Grin
Brian Beuken
Lecturer in Game Programming at Breda University of Applied Sciences.
Author of The Fundamentals of C/C++ Game Programming: Using Target-based Development on SBC's 



Reply
#6
(09-03-2022, 12:38 PM)junglie85 Wrote: Okay, I found the example code from Brian and jomoengineer - http://www.scratchpadgames.net/forums/sh...hp?tid=507

It looks like I just need to include <X11/XLib.h> and maybe a few other headers instead of those for DispmanX.

I'll report back how I get on porting this. I'm thinking I might try and convert it to GLES3 also, possibly as a separate codebase.

Brian, do you have a GitHub repo for the book where I could raise a PR once I get the triangle and any other examples working?
No sorry, just downloads on teh website
Brian Beuken
Lecturer in Game Programming at Breda University of Applied Sciences.
Author of The Fundamentals of C/C++ Game Programming: Using Target-based Development on SBC's 



Reply
#7
I did, yes, thanks :-D

Brian Beuken Wrote:No sorry, just downloads on teh website

Would you have any problems with me putting the code on a public GitHub as I work through the book? I realise there might be copyright/IP type issues and that you don't want me to do so, which is fine. If okay, what info do you want in citations (in the README or in all files)?
Reply
#8
no not at all, feel free, there's no copyright issue with the code, its freely availabe and downloadable from my site and others.
Brian Beuken
Lecturer in Game Programming at Breda University of Applied Sciences.
Author of The Fundamentals of C/C++ Game Programming: Using Target-based Development on SBC's 



Reply
#9
Great, thanks
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)