Requires https://github.com/hzeller/rpi-rgb-led-matrix
Render a pixel shader on a led matrix attached to a rapsberry pi
Requires https://github.com/hzeller/rpi-rgb-led-matrix
Render a pixel shader on a led matrix attached to a rapsberry pi
| project(shaderpanel) | |
| FIND_LIBRARY(GLESV2 brcmGLESv2 HINTS /opt/vc/lib) | |
| FIND_LIBRARY(EGL brcmEGL HINTS /opt/vc/lib) | |
| FIND_LIBRARY(RGBMATRIX rgbmatrix HINTS ${PROJECT_SOURCE_DIR}/matrix/lib) | |
| include_directories(/opt/vc/include) | |
| include_directories(${PROJECT_SOURCE_DIR}/matrix/include) | |
| add_executable(shaderpanel main.cpp) | |
| target_link_libraries(shaderpanel ${GLESV2} ${EGL} ${RGBMATRIX} pthread) |
| #include <EGL/egl.h> | |
| #include <GLES2/gl2.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <chrono> | |
| #include <math.h> | |
| #include "led-matrix.h" | |
| using rgb_matrix::RGBMatrix; | |
| using rgb_matrix::Canvas; | |
| volatile bool interrupt_received = false; | |
| static void InterruptHandler(int signo) { | |
| interrupt_received = true; | |
| } | |
| static const EGLint configAttribs[] = { | |
| EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, | |
| EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 8, | |
| // Uncomment the following to enable MSAA | |
| // EGL_SAMPLE_BUFFERS, 1, // <-- Must be set to 1 to enable multisampling! | |
| // EGL_SAMPLES, 4, // <-- Number of samples | |
| // Uncomment the following to enable stencil buffer | |
| // EGL_STENCIL_SIZE, 1, | |
| EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE}; | |
| // Width and height of the desired framebuffer | |
| static const EGLint pbufferAttribs[] = { | |
| EGL_WIDTH, | |
| 64, | |
| EGL_HEIGHT, | |
| 32, | |
| EGL_NONE, | |
| }; | |
| static const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, | |
| EGL_NONE}; | |
| // The following array holds vec3 data of | |
| // three vertex positions | |
| static const GLfloat vertices[] = { | |
| -1.0f, 1.0f, 0.0f, 0.0f,// Top-left | |
| 1.0f, 1.0f, 1.0f, 0.0f,// Top-right | |
| 1.0f, -1.0f, 1.0f, 1.0f,// Bottom-right | |
| 1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right | |
| -1.0f, -1.0f, 0.0f, 1.0f,// Bottom-left | |
| -1.0f, 1.0f, 0.0f, 0.0f, // Top-left | |
| }; | |
| #define GLSL(version, shader) "#version " #version "\n" #shader | |
| // The following are GLSL shaders for rendering a triangle on the screen | |
| #define STRINGIFY(x) #x | |
| static const char *vertexShaderCode = STRINGIFY( | |
| attribute vec2 pos; | |
| attribute vec2 texcoord; | |
| varying vec2 texCoord; | |
| void main() { | |
| gl_Position = vec4(pos, 0.0, 1.0); | |
| texCoord = texcoord; | |
| } | |
| ); | |
| static const char *fragmentShaderCode = | |
| "uniform vec4 color; \n" | |
| "uniform float time; \n" | |
| "varying vec2 texCoord; \n" | |
| "void main() { \n" | |
| "vec2 uv = -1. +2. * texCoord;\n" | |
| " gl_FragColor = vec4( \n" | |
| "abs(sin(cos(time + 3.0 * uv.y)*2.0 * uv.x + time )),\n" | |
| "abs(cos(sin(time+2.0 * uv.x) * 3.0 * uv.y + time)),\n" | |
| " 1.0,\n" | |
| "1.0\n" | |
| " );\n" | |
| "}\n"; | |
| static const char *eglGetErrorStr() | |
| { | |
| switch (eglGetError()) | |
| { | |
| case EGL_SUCCESS: | |
| return "The last function succeeded without error."; | |
| case EGL_NOT_INITIALIZED: | |
| return "EGL is not initialized, or could not be initialized, for the " | |
| "specified EGL display connection."; | |
| case EGL_BAD_ACCESS: | |
| return "EGL cannot access a requested resource (for example a context " | |
| "is bound in another thread)."; | |
| case EGL_BAD_ALLOC: | |
| return "EGL failed to allocate resources for the requested operation."; | |
| case EGL_BAD_ATTRIBUTE: | |
| return "An unrecognized attribute or attribute value was passed in the " | |
| "attribute list."; | |
| case EGL_BAD_CONTEXT: | |
| return "An EGLContext argument does not name a valid EGL rendering " | |
| "context."; | |
| case EGL_BAD_CONFIG: | |
| return "An EGLConfig argument does not name a valid EGL frame buffer " | |
| "configuration."; | |
| case EGL_BAD_CURRENT_SURFACE: | |
| return "The current surface of the calling thread is a window, pixel " | |
| "buffer or pixmap that is no longer valid."; | |
| case EGL_BAD_DISPLAY: | |
| return "An EGLDisplay argument does not name a valid EGL display " | |
| "connection."; | |
| case EGL_BAD_SURFACE: | |
| return "An EGLSurface argument does not name a valid surface (window, " | |
| "pixel buffer or pixmap) configured for GL rendering."; | |
| case EGL_BAD_MATCH: | |
| return "Arguments are inconsistent (for example, a valid context " | |
| "requires buffers not supplied by a valid surface)."; | |
| case EGL_BAD_PARAMETER: | |
| return "One or more argument values are invalid."; | |
| case EGL_BAD_NATIVE_PIXMAP: | |
| return "A NativePixmapType argument does not refer to a valid native " | |
| "pixmap."; | |
| case EGL_BAD_NATIVE_WINDOW: | |
| return "A NativeWindowType argument does not refer to a valid native " | |
| "window."; | |
| case EGL_CONTEXT_LOST: | |
| return "A power management event has occurred. The application must " | |
| "destroy all contexts and reinitialise OpenGL ES state and " | |
| "objects to continue rendering."; | |
| default: | |
| break; | |
| } | |
| return "Unknown error!"; | |
| } | |
| static void DrawOnCanvas(Canvas *canvas) { | |
| /* | |
| * Let's create a simple animation. We use the canvas to draw | |
| * pixels. We wait between each step to have a slower animation. | |
| */ | |
| canvas->Fill(0, 0, 255); | |
| int center_x = canvas->width() / 2; | |
| int center_y = canvas->height() / 2; | |
| float radius_max = canvas->width() / 2; | |
| float angle_step = 1.0 / 360; | |
| for (float a = 0, r = 0; r < radius_max; a += angle_step, r += angle_step) { | |
| if (interrupt_received) | |
| return; | |
| float dot_x = cos(a * 2 * M_PI) * r; | |
| float dot_y = sin(a * 2 * M_PI) * r; | |
| canvas->SetPixel(center_x + dot_x, center_y + dot_y, | |
| 255, 0, 0); | |
| usleep(1 * 1000); // wait a little to slow down things. | |
| } | |
| } | |
| int main(int argc, char *argv[]) { | |
| fprintf(stderr, "%s", vertexShaderCode); | |
| RGBMatrix::Options defaults; | |
| defaults.hardware_mapping = "adafruit-hat"; // or e.g. "adafruit-hat" | |
| defaults.rows = 32; | |
| defaults.cols = 64; | |
| defaults.chain_length = 1; | |
| defaults.parallel = 1; | |
| defaults.show_refresh_rate = true; | |
| defaults.brightness = 80; | |
| Canvas *canvas = RGBMatrix::CreateFromFlags(&argc, &argv, &defaults); | |
| if (canvas == NULL) | |
| return 1; | |
| EGLDisplay display; | |
| int major, minor; | |
| int desiredWidth, desiredHeight; | |
| GLuint program, vert, frag, vbo; | |
| GLint timeLoc, posLoc, colorLoc, texCoord, result; | |
| if ((display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) | |
| { | |
| fprintf(stderr, "Failed to get EGL display! Error: %s\n", | |
| eglGetErrorStr()); | |
| return EXIT_FAILURE; | |
| } | |
| if (eglInitialize(display, &major, &minor) == EGL_FALSE) | |
| { | |
| fprintf(stderr, "Failed to get EGL version! Error: %s\n", | |
| eglGetErrorStr()); | |
| eglTerminate(display); | |
| return EXIT_FAILURE; | |
| } | |
| printf("Initialized EGL version: %d.%d\n", major, minor); | |
| EGLint numConfigs; | |
| EGLConfig config; | |
| if (!eglChooseConfig(display, configAttribs, &config, 1, &numConfigs)) | |
| { | |
| fprintf(stderr, "Failed to get EGL config! Error: %s\n", | |
| eglGetErrorStr()); | |
| eglTerminate(display); | |
| return EXIT_FAILURE; | |
| } | |
| EGLSurface surface = | |
| eglCreatePbufferSurface(display, config, pbufferAttribs); | |
| if (surface == EGL_NO_SURFACE) | |
| { | |
| fprintf(stderr, "Failed to create EGL surface! Error: %s\n", | |
| eglGetErrorStr()); | |
| eglTerminate(display); | |
| return EXIT_FAILURE; | |
| } | |
| eglBindAPI(EGL_OPENGL_API); | |
| EGLContext context = | |
| eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); | |
| if (context == EGL_NO_CONTEXT) | |
| { | |
| fprintf(stderr, "Failed to create EGL context! Error: %s\n", | |
| eglGetErrorStr()); | |
| eglDestroySurface(display, surface); | |
| eglTerminate(display); | |
| return EXIT_FAILURE; | |
| } | |
| eglMakeCurrent(display, surface, surface, context); | |
| desiredWidth = 64; | |
| desiredHeight = 32; | |
| // Set GL Viewport size, always needed! | |
| glViewport(0, 0, desiredWidth, desiredHeight); | |
| // Get GL Viewport size and test if it is correct. | |
| // NOTE! DO NOT UPDATE EGL LIBRARY ON RASPBERRY PI AS IT WILL INSTALL FAKE | |
| // EGL! If you have fake/faulty EGL library, the glViewport and | |
| // glGetIntegerv won't work! The following piece of code checks if the gl | |
| // functions are working as intended! | |
| GLint viewport[4]; | |
| glGetIntegerv(GL_VIEWPORT, viewport); | |
| // viewport[2] and viewport[3] are viewport width and height respectively | |
| printf("GL Viewport size: %dx%d\n", viewport[2], viewport[3]); | |
| // Test if the desired width and height match the one returned by | |
| // glGetIntegerv | |
| if (desiredWidth != viewport[2] || desiredHeight != viewport[3]) | |
| { | |
| fprintf(stderr, "Error! The glViewport/glGetIntegerv are not working! " | |
| "EGL might be faulty!\n"); | |
| } | |
| // Clear whole screen (front buffer) | |
| glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| // Create a shader program | |
| // NO ERRRO CHECKING IS DONE! (for the purpose of this example) | |
| // Read an OpenGL tutorial to properly implement shader creation | |
| program = glCreateProgram(); | |
| glUseProgram(program); | |
| vert = glCreateShader(GL_VERTEX_SHADER); | |
| glShaderSource(vert, 1, &vertexShaderCode, NULL); | |
| glCompileShader(vert); | |
| GLint status; | |
| glGetShaderiv(vert, GL_COMPILE_STATUS, &status); | |
| if (status != GL_TRUE) { | |
| GLint infoLogLength; | |
| glGetShaderiv(vert, GL_INFO_LOG_LENGTH, &infoLogLength); | |
| GLchar* strInfoLog = new GLchar[infoLogLength + 1]; | |
| glGetShaderInfoLog(vert, infoLogLength, NULL, strInfoLog); | |
| fprintf(stderr, "Compilation error in shader vert: %s\n", strInfoLog); | |
| delete[] strInfoLog; | |
| } | |
| frag = glCreateShader(GL_FRAGMENT_SHADER); | |
| glShaderSource(frag, 1, &fragmentShaderCode, NULL); | |
| glCompileShader(frag); | |
| glGetShaderiv(frag, GL_COMPILE_STATUS, &status); | |
| if (status != GL_TRUE) { | |
| GLint infoLogLength; | |
| glGetShaderiv(frag, GL_INFO_LOG_LENGTH, &infoLogLength); | |
| GLchar* strInfoLog = new GLchar[infoLogLength + 1]; | |
| glGetShaderInfoLog(frag, infoLogLength, NULL, strInfoLog); | |
| fprintf(stderr, "Compilation error in shader %s\n", strInfoLog); | |
| delete[] strInfoLog; | |
| } | |
| glAttachShader(program, frag); | |
| glAttachShader(program, vert); | |
| glLinkProgram(program); | |
| GLint isLinked = 0; | |
| glGetProgramiv(program, GL_LINK_STATUS, &isLinked); | |
| if (isLinked == GL_FALSE) | |
| { | |
| GLint infoLogLength; | |
| glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); | |
| GLchar* strInfoLog = new GLchar[infoLogLength +1]; | |
| glGetProgramInfoLog(program, infoLogLength, NULL, strInfoLog); | |
| fprintf(stderr, "PROGRAM FAILED: %s\n", strInfoLog); | |
| // The program is useless now. So delete it. | |
| glDeleteProgram(program); | |
| // Provide the infolog in whatever manner you deem best. | |
| // Exit with failure. | |
| return -1; | |
| } | |
| glUseProgram(program); | |
| // Create Vertex Buffer Object | |
| // Again, NO ERRRO CHECKING IS DONE! (for the purpose of this example) | |
| glGenBuffers(1, &vbo); | |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); | |
| // Get vertex attribute and uniform locations | |
| posLoc = glGetAttribLocation(program, "pos"); | |
| texCoord = glGetAttribLocation(program, "texcoord"); | |
| colorLoc = glGetUniformLocation(program, "color"); | |
| timeLoc = glGetUniformLocation(program, "time"); | |
| fprintf(stderr, "%d\n", posLoc); | |
| fprintf(stderr, "%d\n", texCoord); | |
| fprintf(stderr, "%d\n", colorLoc); | |
| // Set the desired color of the triangle to pink | |
| // 100% red, 0% green, 50% blue, 100% alpha | |
| glUniform4f(colorLoc, 1.0, 0.0f, 0.5, 1.0); | |
| // Set our vertex data | |
| glEnableVertexAttribArray(posLoc); | |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
| glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), | |
| (void *)0); | |
| glEnableVertexAttribArray(texCoord); | |
| glVertexAttribPointer(texCoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), | |
| (void *)(2*sizeof(float))); | |
| // Render a triangle consisting of 3 vertices: | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| // Create buffer to hold entire front buffer pixels | |
| // We multiply width and height by 3 to because we use RGB! | |
| unsigned char *buffer = | |
| (unsigned char *)malloc(desiredWidth * desiredHeight * 3); | |
| // Copy entire screen | |
| glReadPixels(0, 0, desiredWidth, desiredHeight, GL_RGB, GL_UNSIGNED_BYTE, | |
| buffer); | |
| // It is always good to set up a signal handler to cleanly exit when we | |
| // receive a CTRL-C for instance. The DrawOnCanvas() routine is looking | |
| // for that. | |
| signal(SIGTERM, InterruptHandler); | |
| signal(SIGINT, InterruptHandler); | |
| // DrawOnCanvas(canvas); // Using the canvas. | |
| // | |
| auto t_start = std::chrono::high_resolution_clock::now(); | |
| while (!interrupt_received) { | |
| auto t_now = std::chrono::high_resolution_clock::now(); | |
| float time = std::chrono::duration_cast<std::chrono::duration<float>>(t_now - t_start).count(); | |
| glUniform1f(timeLoc, time); | |
| glDrawArrays(GL_TRIANGLES, 0, 6); | |
| glReadPixels(0,0,desiredWidth, desiredHeight, GL_RGB, GL_UNSIGNED_BYTE, buffer); | |
| for (int y=0; y<32; ++y) { | |
| for (int x=0; x<64; ++x) { | |
| uint8_t* data = buffer + (y * 64 * 3); | |
| canvas->SetPixel(x, y, data[x*3] , data[x*3+1], data[x*3+2]); | |
| } | |
| } | |
| usleep(35 * 1000); | |
| } | |
| // Animation finished. Shut down the RGB matrix. | |
| canvas->Clear(); | |
| delete canvas; | |
| return 0; | |
| } |