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; | |
} |