Run SDL2 application on RetroPie for Raspberry Pi
-
Hi, a while ago I made a little game with SDL2/OpenGL that I now want to port to the Raspberry Pi (running a pure RetroPie image ), so that it can be run via the EmulationStation. However I made a small test application to check, whether basic window-creation and rendering with GLES works on the Raspberry, but the application only shows a black screen and the mouse cursor.
#include <SDL2/SDL.h> #include <SDL2/SDL_opengles2.h> #define TRIANGLE_SIZE 0.9f // Shader sources const GLchar* vertexSource = "\n" "attribute vec2 position;\n" "attribute vec4 color;\n" "varying vec4 out_color;\n" "uniform mat4 proj_mat;\n" "uniform mat4 model_mat;\n" "void main()\n" "{\n" " gl_Position = proj_mat*model_mat*vec4(position, 0, 1.0);\n" " out_color = color;\n" "}\n"; const GLchar* fragmentSource = "varying vec4 out_color;\n" "void main()\n" "{\n" " gl_FragColor = out_color;\n" "}\n"; typedef struct{ float x; float y; float r; float g; float b; float a; }Vertex; void resize(int w, int h, GLint proj_mat_location); void setRotationMatrix(float rad, GLint model_mat_location); void setOrthoMatrix(float left, float right, float bottom, float top, float n, float f, GLint proj_mat_location); int main() { printf("Initializing Framework...\n"); //initialize SDL if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("ERROR: Failed to initialize SDL: %s\n", SDL_GetError()); return 1; } //setting flags unsigned int sdl_flags = SDL_WINDOW_OPENGL; SDL_DisplayMode current; current.w = 800; current.h = 600; if(sdl_flags & SDL_WINDOW_FULLSCREEN){ //getting current display-resolution if (SDL_GetDesktopDisplayMode(0, ¤t) != 0){ printf("Could not retrieve current display resolution: %s\n", SDL_GetError()); } } else{ sdl_flags |= SDL_WINDOW_RESIZABLE; } printf("Setting resolution to %dx%d\n", current.w, current.h); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32); 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("Triangles!", 0,0, current.w, current.h, sdl_flags); if(window == NULL) { printf("Error while creating window: %s\n", SDL_GetError()); return 1; } printf("Creating OpenGL context...\n"); //create gl-context if(!SDL_GL_CreateContext(window)) { printf("Error while creating OpenGL Context: %s\n", SDL_GetError()); return 1; } //init GL parameters glClearColor(1.f, 1.f, 1.f, 1.f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); // Create a Vertex Buffer Object and copy the vertex data to it GLuint vbo; glGenBuffers(1, &vbo); Vertex vertices[3]; for(int i = 0; i < 3; i++){ vertices[i].x = cos(i*(2*M_PI/3.0f))*TRIANGLE_SIZE; vertices[i].y = sin(i*(2*M_PI/3.0f))*TRIANGLE_SIZE; vertices[i].r = 0.0f; vertices[i].g = 0.0f; vertices[i].b = 0.0f; vertices[i].a = 1.0f; } vertices[0].r = 1.0f; vertices[1].g = 1.0f; vertices[2].b = 1.0f; glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); char buffer[1024]; buffer[0] = '\0'; // Create and compile the vertex shader GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexSource, NULL); glCompileShader(vertexShader); glGetShaderInfoLog(vertexShader, 1024, 0, buffer); if(buffer[0] != '\0')//non-empty { printf("Error while compiling vertex shader:\n%s\n", (const char*)buffer); } buffer[0] = '\0'; // Create and compile the fragment shader GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentSource, NULL); glCompileShader(fragmentShader); glGetShaderInfoLog(fragmentShader, 1024, 0, buffer); if(buffer[0] != '\0')//non-empty { printf("Error while compiling fragment shader:\n%s\n", (const char*)buffer); } buffer[0] = '\0'; // Link the vertex and fragment shader into a shader program GLuint shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); // glBindFragDataLocation(shaderProgram, 0, "outColor"); glLinkProgram(shaderProgram); //Program Error-Log glGetProgramInfoLog(shaderProgram, 1024, 0, buffer); if(buffer[0] != '\0')//non-empty { printf("Error while linking shader program:\n%s\n", (const char*)buffer); } int status; glGetShaderiv(shaderProgram, GL_LINK_STATUS, &status); if(status == GL_FALSE){ return 1; } glUseProgram(shaderProgram); // Specify the layout of the vertex data GLint posAttrib = glGetAttribLocation(shaderProgram, "position"); glEnableVertexAttribArray(posAttrib); glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); GLint colorAttrib = glGetAttribLocation(shaderProgram, "color"); glEnableVertexAttribArray(colorAttrib); glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)(2*sizeof(float))); GLint proj_mat_location = glGetUniformLocation(shaderProgram, "proj_mat"); GLint model_mat_location = glGetUniformLocation(shaderProgram, "model_mat"); resize(current.w, current.h, proj_mat_location); printf("Running...\n"); // run main loop while(1){ SDL_Event e; while(SDL_PollEvent(&e)){ if(e.type == SDL_QUIT) return 0; else if(e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_RESIZED) resize(e.window.data1, e.window.data2, proj_mat_location); } glClear(GL_COLOR_BUFFER_BIT); setRotationMatrix(SDL_GetTicks()/1000.f * M_PI/2.f, model_mat_location); glDrawArrays(GL_TRIANGLES, 0, 3); SDL_GL_SwapWindow(window); SDL_Delay(10); if(SDL_GetTicks() > 5000)// quit after 5 seconds return 0; } return 0; } void setRotationMatrix(float rad, GLint model_mat_location) { float sin_angle = sin(rad); float cos_angle = cos(rad); float mat[16]; mat[0] = cos_angle; mat[1] = sin_angle; mat[2] = 0; mat[3] = 0; mat[4] = -sin_angle; mat[5] = cos_angle; mat[6] = 0; mat[7] = 0; mat[8] = 0; mat[9] = 0; mat[10] = 1; mat[11] = 0; mat[12] = 0; mat[13] = 0; mat[14] = 0; mat[15] = 1; glUniformMatrix4fv(model_mat_location, 1, GL_FALSE, mat); } void resize(int w, int h, GLint proj_mat_location) { glViewport(0, 0, w, h); if(w > h){ float f = w/(float)h; setOrthoMatrix(-f, f, -1, 1, -1, 1, proj_mat_location); }else{ float f = h/(float)w; setOrthoMatrix(-1, 1, -f, f, -1, 1, proj_mat_location); } } void setOrthoMatrix(float left, float right, float bottom, float top, float n, float f, GLint proj_mat_location) { float mat[16]; mat[0] = 2.0f/(right-left); mat[1] = 0.f; mat[2] = 0.f; mat[3] = 0.f; mat[4] = 0.f; mat[5] = 2.0f/(top-bottom); mat[6] = 0.f; mat[7] = 0.f; mat[8] = 0.f; mat[9] = 0.f; mat[10] =-2.f/(f-n); mat[11] = 0.f; mat[12] =-(right+left)/(right-left); mat[13] =-(top+bottom)/(top-bottom); mat[14] =-(f+n)/(f-n); mat[15] = 1.f; glUniformMatrix4fv(proj_mat_location, 1, GL_FALSE, mat); }
The code can be compiled with gcc:
gcc main.c -o gles_test -lSDL2 -lSDL2main -lGLESv2 -lm
On my linux machine (manjaro) this code works as expected, but not on the Raspberry Pi with RetroPie though. I tried launching in windowed and fullscreen-mode (SDL-flag) but nothing is shown on my Raspberry.
Any clues? -
Depending on the Pi model, you'll need to use a different GLES implementation. On the RetroPie Pi3 image, where the legacy/proprietary GLES driver is used, you need to link against
libbrcmGLESv2
:gcc main.c -o gles_test -lSDL2 -lSDL2main -L/opt/vc/lib -lbrcmGLESv2 -lm
On the Pi4, where the Mesa GL driver is used for GL, the program works without any changes.
-
Thanks, the incorrect library was the main issue with this program! Another few things I noticed while experimenting with this code snippet:
- apparently Vertex Arrays don't work with GLESv2 (stackoverflow), so you have to use Vertex Buffers
- errors in vertex/fragment shader are only detected when linking the program, not on compilation
- when using opengl extensions (e.g. GL_OES_mapbuffer, see Raspberry Pi Video Core API for available extensions) you have to put
#define GL_GLEXT_PROTOTYPES
before includingSDL2/SDL_opengles2.h
and additionally link with-lbrcmEGL
in order to get the functions - if you don't call
SDL_Quit()
before exiting your program, the raspberry pi's console will be blocked (it seems like the input focus is still on the SDL window otherwise)
I updated the code with a few more comments and fixed some stuff:
main.c:#include <SDL2/SDL.h> #include <SDL2/SDL_opengles2.h> #define FULLSCREEN 1 #define TRIANGLE_SIZE 0.9f #define INFO_LOG_BUFFER_SIZE 1024 // Shader sources const GLchar* vertexSource = "attribute vec2 position;\n" "attribute vec4 color;\n" "varying vec4 out_color;\n" "uniform mat4 proj_mat;\n" "uniform mat4 model_mat;\n" "void main()\n" "{\n" " gl_Position = proj_mat*model_mat*vec4(position, 0, 1.0);\n" " out_color = color;\n" "}\n"; const GLchar* fragmentSource = "varying vec4 out_color;\n" "void main()\n" "{\n" " gl_FragColor = out_color;\n" "}\n"; // struct representing a colored 2D vertex typedef struct{ float x; float y; float r; float g; float b; float a; }Vertex; // adjusting viewport and projection matrix to fit current window dimensions void resize(int w, int h, GLint proj_mat_location); // create rotation matrix (z axis) and upload to shader at given uniform location void setRotationMatrix(float rad, GLint model_mat_location); // create orthogonal matrix and upload to shader at given uniform location void setOrthoMatrix(float left, float right, float bottom, float top, float n, float f, GLint proj_mat_location); int main() { // initialize SDL printf("Initializing SDL...\n"); if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("ERROR: Failed to initialize SDL: %s\n", SDL_GetError()); return 1; } // setting SDL flags unsigned int sdl_flags = SDL_WINDOW_OPENGL; SDL_DisplayMode current; current.w = 800; current.h = 600; if(FULLSCREEN){ sdl_flags |= SDL_WINDOW_FULLSCREEN; // getting current display-resolution if (SDL_GetDesktopDisplayMode(0, ¤t) != 0){ printf("Could not retrieve current display resolution: %s\n", SDL_GetError()); } }else{ sdl_flags |= SDL_WINDOW_RESIZABLE; } const char * fullscreen_string = FULLSCREEN ? "(fullscreen)" : ""; printf("Creating window %dx%d%s...\n", current.w, current.h, fullscreen_string); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); // use GLESv2 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // activate V-Sync SDL_GL_SetSwapInterval(1); SDL_Window * window = SDL_CreateWindow("Hello GLES!", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, current.w, current.h, sdl_flags); if(window == NULL) { printf("Error while creating window: %s\n", SDL_GetError()); SDL_Quit(); return 1; } // create gl context printf("Creating OpenGL context...\n"); SDL_GLContext context = SDL_GL_CreateContext(window); if(context == NULL) { printf("Error while creating OpenGL Context: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); return 1; } // hide mouse cursor SDL_ShowCursor(0); // set gl parameters glClearColor(1.f, 1.f, 1.f, 1.f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); // create RGB-triangle data and copy to vertex buffer GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); Vertex vertices[3]; for(int i = 0; i < 3; i++){ vertices[i].x = cos(i*(2*M_PI/3.0f))*TRIANGLE_SIZE; vertices[i].y = sin(i*(2*M_PI/3.0f))*TRIANGLE_SIZE; vertices[i].r = 0.0f; vertices[i].g = 0.0f; vertices[i].b = 0.0f; vertices[i].a = 1.0f; } vertices[0].r = 1.0f; vertices[1].g = 1.0f; vertices[2].b = 1.0f; glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); printf("Compiling Shader...\n"); // compile the vertex shader GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexSource, NULL); glCompileShader(vertexShader); // compile the fragment shader GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentSource, NULL); glCompileShader(fragmentShader); // link vertex and fragment shader into shader program GLuint shader_program = glCreateProgram(); glAttachShader(shader_program, vertexShader); glAttachShader(shader_program, fragmentShader); glLinkProgram(shader_program); // program info log char info_log_buffer[INFO_LOG_BUFFER_SIZE]; info_log_buffer[0] = '\0'; glGetProgramInfoLog(shader_program, INFO_LOG_BUFFER_SIZE, 0, info_log_buffer); if(info_log_buffer[0] != '\0') printf("Shader-Program-Info:\n%s\n", info_log_buffer); // linking successfull? int status; glGetProgramiv(shader_program, GL_LINK_STATUS, &status); if(status == GL_FALSE){ SDL_GL_DeleteContext(context); SDL_DestroyWindow(window); SDL_Quit(); return 1; } // use custom shader glUseProgram(shader_program); // feed vertex attributes with triangle-data // position GLint posAttrib = glGetAttribLocation(shader_program, "position"); glEnableVertexAttribArray(posAttrib); glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); // color GLint colorAttrib = glGetAttribLocation(shader_program, "color"); glEnableVertexAttribArray(colorAttrib); glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)(2*sizeof(float))); // retrieve uniform locations GLint proj_mat_location = glGetUniformLocation(shader_program, "proj_mat"); GLint model_mat_location = glGetUniformLocation(shader_program, "model_mat"); // inital resize to set viewport and projection mastrix resize(current.w, current.h, proj_mat_location); // run main loop printf("Rendering triangle...\n"); while(1){ int quit = 0; SDL_Event e; while(SDL_PollEvent(&e)){ if(e.type == SDL_QUIT || e.type == SDL_KEYDOWN) quit = 1; else if(e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_RESIZED) resize(e.window.data1, e.window.data2, proj_mat_location); } if(quit){ printf("Program was quit by user!\n"); break; } glClearColor(1,1,1,1); glClear(GL_COLOR_BUFFER_BIT); setRotationMatrix(SDL_GetTicks()/1000.f * M_PI/2.f, model_mat_location); glDrawArrays(GL_TRIANGLES, 0, 3); SDL_GL_SwapWindow(window); } // clean up SDL_GL_DeleteContext(context); SDL_DestroyWindow(window); SDL_Quit(); return 0; } void setRotationMatrix(float rad, GLint model_mat_location) { // rotation around z axis float sin_angle = sin(rad); float cos_angle = cos(rad); float mat[16]; mat[0] = cos_angle; mat[1] = sin_angle; mat[2] = 0; mat[3] = 0; mat[4] = -sin_angle; mat[5] = cos_angle; mat[6] = 0; mat[7] = 0; mat[8] = 0; mat[9] = 0; mat[10] = 1; mat[11] = 0; mat[12] = 0; mat[13] = 0; mat[14] = 0; mat[15] = 1; glUniformMatrix4fv(model_mat_location, 1, GL_FALSE, mat); } void resize(int w, int h, GLint proj_mat_location) { glViewport(0, 0, w, h); // set orthogonal view so that coordinates [-1, 1] area always visible and proportional on x and y axis if(w > h){ float f = w/(float)h; setOrthoMatrix(-f, f, -1, 1, -1, 1, proj_mat_location); }else{ float f = h/(float)w; setOrthoMatrix(-1, 1, -f, f, -1, 1, proj_mat_location); } } void setOrthoMatrix(float left, float right, float bottom, float top, float n, float f, GLint proj_mat_location) { // set orthogonal matrix float mat[16]; mat[0] = 2.0f/(right-left); mat[1] = 0.f; mat[2] = 0.f; mat[3] = 0.f; mat[4] = 0.f; mat[5] = 2.0f/(top-bottom); mat[6] = 0.f; mat[7] = 0.f; mat[8] = 0.f; mat[9] = 0.f; mat[10] =-2.f/(f-n); mat[11] = 0.f; mat[12] =-(right+left)/(right-left); mat[13] =-(top+bottom)/(top-bottom); mat[14] =-(f+n)/(f-n); mat[15] = 1.f; glUniformMatrix4fv(proj_mat_location, 1, GL_FALSE, mat); }
Compiles and runs on Raspberry Pi 3:
gcc main.c -o gles_test -lSDL2 -lSDL2main -L/opt/vc/lib -lbrcmGLESv2 -lm ./gles_test
I hope this might be usefull to someone :)
@mitu Thanks again for your quick reply!
Contributions to the project are always appreciated, so if you would like to support us with a donation you can do so here.
Hosting provided by Mythic-Beasts. See the Hosting Information page for more information.