Raspberry Pi 4 is different, book code won't work! - Brian Beuken - 07-03-2019
Well it had to happen, the new Raspi4 is a lovely machine with some impressive graphic potential, but it does not work the same way as all the other Raspberries so the book examples won't work, there has to be a new version of the graphics startup code.
The previous Raspberries used Broadcom IV graphic drivers with binaries and libs located in opt/vc/lib and opt/vc/include
The Pi4 still has these libraries and locations but they don't work on the 4, though you can use the Buster OS with any other Raspberry.
1st things 1st you need mesa libs which can be installed by opening a terminal and typing
sudo apt-get install libgles2-mesa-dev
Now you need to change some of your project library needs and directory access. The increased number of libs needed to run on the older RPis has been reduced to just 3, EGL, GLES2 AND X11 (other display systems are possible though but not right now)
And include libs are now usr/lib for the libraries and usr/include for the h files
Also, and this is very important, do not disable the OpenGL drivers as we had to do in older Raspberry's
If you are using one of the multi build configs on the site, basically Raspberry has reverted to the normal way to build a Linux project, so remove the RASPBERRY pre processor instruction and let it build like a normal Linux build and it should be fine.
Here's the init code to set up an X11 window, EGL and get ready to work with OpenGLES2.0
Code: ///
// CreateEGLContext()
//
// Creates an EGL rendering context and all associated elements
static Display* x_display = NULL;
void Graphics::init_ogl(Target_State *state, int width, int height, int FBResX, int FBResY)
{
#define ES_WINDOW_RGB 0
state->width = width;
state->height = height;
EGLint numConfigs;
EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
EGLContext context;
EGLSurface surface;
EGLConfig config;
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };
/* create a native window */
Window root;
XSetWindowAttributes swa;
XSetWindowAttributes xattr;
Atom wm_state;
XWMHints hints;
XEvent xev;
EGLConfig ecfg;
EGLint num_config;
Window win;
Screen *screen;
/*
* X11 native display initialization
*/
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 Maze3dHunt";
// 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->nativewindow = (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;
}
// Initialize EGL
if (!eglInitialize(display, &majorVersion,&minorVersion))
{
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, &numConfigs))
{
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, &numConfigs))
{
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->nativewindow, NULL);
if (surface == EGL_NO_SURFACE)
{
EGLint err = eglGetError();
return;// EGL_FALSE;
}
// Create a GL context
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
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", majorVersion, minorVersion);
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 GLtest = eglGetConfigAttrib( display,
config,
EGL_MAX_SWAP_INTERVAL,
&minorVersion);
EGLBoolean test = eglSwapInterval(display, 1); // 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
// on xu4 this seems to have no effect
// Some OpenGLES2.0 states that we might need
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDepthMask(TRUE);
glDepthRangef(0.0f, 1.0f);
glClearDepthf(1.0f);
//these are the options you can have for the depth, play with them?
//#define GL_NEVER 0x0200
//#define GL_LESS 0x0201
//#define GL_EQUAL 0x0202
//#define GL_LEQUAL 0x0203
//#define GL_GREATER 0x0204
//#define GL_NOTEQUAL 0x0205
//#define GL_GEQUAL 0x0206
//
glViewport(0, 0, state->width, state->height);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glCullFace(GL_BACK);
if (glGetError() == GL_NO_ERROR) return ;
else
printf("Oh bugger, Some part of the EGL/OGL graphic init failed\n");
}
I will put new builds forPi4 up on the site as soon as I can, if you need help, just ping me on here or drop me a mail
RE: Raspberry Pi 4 is different, book code won't work! - jomoengineer - 09-08-2019
Brian,
Did you ever post an update to the examples that work with the RasPi 4? I have one of these boards and will be looking to migrate from the RasPi 3B+.
Cheers,
Jon
RE: Raspberry Pi 4 is different, book code won't work! - Brian Beuken - 09-08-2019
Essentially you need to use the traditional X11 systems to work. But that has a few issues I'm trying (and failing) to overcome
With X11 you seem to be limited to the resolution of your screen, or a window...With Broadcom we could set the display res to screen size but the display buffer to any size. The screen would scale accordingly.
This had the advanatge of giving you 1080p res on screen but 720p update for 2x the speed and not a massive amount of visual difference.
Since X11 does not allow that (as far as I can tell), I've been looking at other methods, but so far without success.
Thats stopped me doing any updates just yet, as I want both Pi3 and Pi4 systems to work the same way at the same update rate. I'm still trying to come up with a solution, but other things are getting in the way, but I will return to it soon.
RE: Raspberry Pi 4 is different, book code won't work! - Brian Beuken - 12-05-2019
I've now worked out how to do a change resolution system (detailed elsewhere) so while I am going to miss the Broadcom's "auto" stretch systems, we do now have the ability to change the screen res in code so we can once again work on reasonable sized frame buffers. This will now allow me to rework the demo's to inlude Rpi4 functionality.
Note, this is using X11 though which is going out of favour in the Linux world so as soon as I can invest some research time I'll look into other render targets as soon as I can.
RE: Raspberry Pi 4 is different, book code won't work! - jomoengineer - 12-16-2019
Brian,
Is this code working on the RasPi 4 now?
I tried the same on a TinkerBoard but it keeps failing at eglCreateWindowSurface and I am not sure why.
This is what I have thus far and may just try with a RasPi 4 instead.
NOTE: This is actually from the MagPi issue67 code.
Code: /*
* OGL.cpp
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "OGL.h"
#include <assert.h> // assert is a way of making sure that things are what we expect and if not it will stop and tell us.
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define EGL_FALSE 0
#define EGL_TRUE 1
// X11 related local variables
static Display *x_display = NULL;
using namespace std;
/* For beginners this might be a bit too much, but just include the file for now and use the OGL.h file when you need to use
OpenGLES2.0 instructions.
In order to set up a screen, we will need to ask the video system to create a couple of buffer areas with
the right type of pixels and also indicate some of the hardware features we want to switch on. This is done using EGL libs.
The process of setting up a graphic screen is a little technical but mainly we must create an EGLscreen
which itself is optimised on RaspPi to use a special format our Broadcom GPU givs us for its display, all handled in
the bcm_host.h file so we can forget about it, but we do need to tell EGL we are using DISPMANX systems
Once we have our EGL/DISMPANX surface and displays systems it means OpenGLES2.0 has something to work with and we can
draw things using our shaders.
For convenience we will set up a simple default shader, but normally we would have a few different ones for different
effects.
You can totally ignore the contents of this file until you feel you want to explore how to set things up, but do make
sure you have an #include OGL.h in any class that wants to play with OpenGL and that you double check that you create
an instance of OGL and call its init() method to set it all up of us.
Also note this file makes use of fprintf instead of cout, its just a tidier way to format printed text.
*/
OGL::OGL() {};
OGL::~OGL() {};
// this is a simple definition of an attribute list to create an EGL window, there are many variations we could have
static const EGLint attribute_list[] =
{
// these 1st 4, let us define the size of our colour componants and from that the size of a pixel.
// In this case 32bits made up of 8xR, 8xG,8xB and 8xA
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_SAMPLE_BUFFERS,
1, // if you want anti alias at a slight fps cost
EGL_SAMPLES,
4, //keep these 2 lines
// These lines set up some basic internal features that help
EGL_DEPTH_SIZE,
1,
// These lines tell EGL how the surface is made up.
EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_NONE
};
// we will also need to tell our context what we want.
static const EGLint context_attributes[] =
{
EGL_CONTEXT_CLIENT_VERSION,
2,
EGL_NONE
};
// This will set up the Broadcomm GPU version of an EGL display,
//
// CreateEGLContext()
//
// Creates an EGL rendering context and all associated elements
/* We've added a simple routine to create textures
Create a texture with width and height
*/
GLuint OGL::CreateTexture2D(int width, int height, char* TheData)
{
// Texture handle
GLuint textureId;
// Set the alignment
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Generate a texture object
glGenTextures(1, &textureId);
// Bind the texture object
glBindTexture(GL_TEXTURE_2D, textureId);
// set it up
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
width,
height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
TheData);
if (glGetError() != GL_NO_ERROR) printf("We got an error"); // its a good idea to test for errors.
// Set the filtering mode
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return textureId;
}
//void OGL::init_EGL(Target_State *state, int width, int height, int FBResX, int FBResY)
void OGL::init_EGL(Target_State *state, int width, int height)
{
#define ES_WINDOW_RGB 0
std::cout << "init openGLES started\n";
state->width = width;
state->height = height;
EGLint numConfigs;
EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
EGLContext context;
EGLSurface surface;
EGLConfig config;
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };
// EGLint result = eglChooseConfig(state->display, attribute_list, &config, 1, &numConfigs);
/* create a native window */
Window root;
XSetWindowAttributes swa;
XSetWindowAttributes xattr;
Atom wm_state;
XWMHints hints;
XEvent xev;
EGLConfig ecfg;
EGLint num_config;
Window win;
Screen *screen;
eglBindAPI(EGL_OPENGL_ES_API);
/*
* X11 native display initialization
*/
x_display = XOpenDisplay(NULL);
if (x_display == NULL)
{
return; // we need to trap this;
}
root = DefaultRootWindow(x_display);
screen = ScreenOfDisplay(x_display, 0);
width = screen->width;
height = screen->height; // resplution of screen
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);
std::cout << "config openGLES X11 window\n";
char* title = (char*)"x11 window Maze3dHunt";
// 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);
std::cout << "Initialized XInternAtom\n";
state->nativewindow = (EGLNativeWindowType) win;
// Get Display
//display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
display = eglGetDisplay((EGLNativeDisplayType)x_display);
if (display == EGL_NO_DISPLAY)
{
return; // EGL_FALSE;
}
// Initialize EGL
if (!eglInitialize(display, &minorVersion,&majorVersion))
{
EGLint err = eglGetError();
return;// EGL_FALSE;
}
// Get configs
if (!eglGetConfigs(display, NULL, 0, &numConfigs))
{
return;// EGL_FALSE;
}
// Choose config
if (!eglChooseConfig(display, attribute_list, &config, 1, &numConfigs))
{
return;// EGL_FALSE;
}
// Create a surface
surface = eglCreateWindowSurface(display, config, state->nativewindow, NULL);
if (surface == EGL_NO_SURFACE)
{
std::cout << "Failed to Create Window Surface\n";
return;// EGL_FALSE;
}
// Create a GL context
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
if (context == EGL_NO_CONTEXT)
{
return;// EGL_FALSE;
}
// Make the context current
if (!eglMakeCurrent(display, surface, surface, context))
{
return;// EGL_FALSE;
}
state->display = display;
state->surface = surface;
state->context = context;
//return;// EGL_TRUE;
std::cout << "Print CPU info\n";
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));
}
/*
This is a fairly standard Shader loader and generator
enter with the type of shader needed and a pointer to where the code text is
*/
GLuint OGL::LoadShader(GLenum type, const char *shaderSrc)
{
// see if we can allcoate a new shader
GLuint TheShader = glCreateShader(type);
if (TheShader == 0) return FALSE; // return FALSE (0) to indicate we failed to allocate this shader.
// note...allocating a shader is not the same as generating one. It just means a space was made available
// and our next step is to put the code in there.
// pass the source
glShaderSource(TheShader, 1, &shaderSrc, NULL);
// Compile the shader
glCompileShader(TheShader);
GLint IsCompiled; // we need a value to be set if we fail or succeed.
// Compiling can result in errors, so we need to make sure we find out if it compiled,
// but also its useful to report the error
glGetShaderiv(TheShader, GL_COMPILE_STATUS, &IsCompiled); // if compile works, TheShader will have a non 0 value
// a compiler fail means we need to fix the error manually, so output the compiler errors to help us
if (!IsCompiled)
{
GLint RetinfoLen = 0;
glGetShaderiv(TheShader, GL_INFO_LOG_LENGTH, &RetinfoLen);
if (RetinfoLen > 1)
{ // standard output for errors
char* infoLog = (char*) malloc(sizeof(char) * RetinfoLen);
glGetShaderInfoLog(TheShader, RetinfoLen, NULL, infoLog);
fprintf(stderr, "Error compiling this shader:\n%s\n", infoLog); // this is a useful formatting print system
free(infoLog);
}
glDeleteShader(TheShader); // we did allocate a shader, but since we failed to compile we need to remove it.
return FALSE; // indicate our failure to make the shader
}
return TheShader; // if we succeeded we will have a valuer in TheShader, return it.
}
///
// Initialize open GL and create our shaders
//
int OGL::Init()
{
//bcm_host_init(); // make sure we have set up our Broadcom
init_EGL(&state, 1024, 768); // this will set up the EGL stuff, its complex so put in a seperate method
// HERE we will load in our graphic image to play with and convert it into a texture
int Grwidth, Grheight, comp; // these are variables we will use to get info
// this is a strange instruction with * and & symbols which we'll discuss soon, but what it does is simple
// it loads a graphic image into memory and stores its width and height into variables Grwidth and Grheight, don't worry about comp or the 4, but they are needed
unsigned char *data = stbi_load("images/MagPicomp.png", &Grwidth, &Grheight, &comp, 4); // ask it to load 4 componants since its rgba
//now its loaded we have to create a texture, which will have a "handle" ID that can be stored, we have added a nice function to make this easy
textureID = CreateTexture2D(Grwidth, Grheight, (char*) data); //just pass the width and height of the graphic, and where it is located and we can make a texture
/*
*
*we're done making the texture but something to think about, where is the data we made it from? We'll find out soon.
*
**/
// we'll need a base colour to clear the screen this sets it up here (we can change it anytime
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// once the BCM has been initialsed and EGL/DISPMANX is up and runnng, all re really need now is some shaders to work with
// since OpenGLES can't actually draw without them, so we will set up a very simple default pair of shaders, and turn them
// into a Program Object which we will set as the default
// this is our shader code, contained in an array of text
// there are much better ways to do this, we will cover later
GLbyte vShaderStr[] =
"attribute vec4 a_position;\n"
"uniform vec4 u_position;\n"
"attribute vec2 a_texCoord;\n"
"varying vec2 v_texCoord;\n"
"void main()\n"
"{gl_Position = a_position + u_position; \n"
" v_texCoord=a_texCoord;"
"}\n";
GLbyte fShaderStr[] =
"precision mediump float;\n"
"varying vec2 v_texCoord;\n"
"uniform sampler2D s_texture;\n"
"void main()\n"
"{\n"
"gl_FragColor = texture2D( s_texture, v_texCoord );\n"
"}\n";
// shaders next, lets create variables to hold these
GLuint vertexShader, fragmentShader; // we need some variables
// Load and compile the vertex/fragment shaders
vertexShader = LoadShader(GL_VERTEX_SHADER, (char*)vShaderStr);
fragmentShader = LoadShader(GL_FRAGMENT_SHADER, (char*)fShaderStr);
if (vertexShader == 0 || fragmentShader == 0) // test if they both have non 0 values
{
return FALSE; // one of them is false, the report will have been printed, and we cannot continue
}
// Create the program object
programObject = glCreateProgram();
if (programObject == 0)
{
cout << "Unable to create our Program Object " << endl;
return FALSE; // there was a failure here
}
// now we have teh V and F shaders attach them to the progam object
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
// Link the program
glLinkProgram(programObject);
// Check the link status
GLint AreTheylinked;
glGetProgramiv(programObject, GL_LINK_STATUS, &AreTheylinked);
if (!AreTheylinked)
{
GLint RetinfoLen = 0;
// check and report any errors
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &RetinfoLen);
if (RetinfoLen > 1)
{
GLchar* infoLog = (GLchar*)malloc(sizeof(char) * RetinfoLen);
glGetProgramInfoLog(programObject, RetinfoLen, NULL, infoLog);
fprintf(stderr, "Error linking program:\n%s\n", infoLog);
free(infoLog);
}
glDeleteProgram(programObject);
return FALSE;
}
glUseProgram(programObject); // we only plan to use 1 program object so tell openGL to use this one
return TRUE;
}
RE: Raspberry Pi 4 is different, book code won't work! - Brian Beuken - 12-17-2019
You will need to ensure that you enter xhost + on the terminal
and also make sure you have auto sign in. More mature Linux versions than 16.04 are much fussier about allowing our projects to run on them.
I have got this working on all systems now though once xhost and auto sign in are in done.
RE: Raspberry Pi 4 is different, book code won't work! - jomoengineer - 12-17-2019
Brian,
Thanks, but I am running this local and via ssh. I must be missing some initialization or something since the OpenGL ES Book examples work fine.
Thanks.
RE: Raspberry Pi 4 is different, book code won't work! - Brian Beuken - 12-17-2019
hmmm when you say ssh are you using an Xwindow on your PC? Or do you try to get it to appear on the target SBC?
RE: Raspberry Pi 4 is different, book code won't work! - jomoengineer - 12-18-2019
No, I am running it local; HDMI screen and Keyboard and Mouse connected directly to the Tinker Board, and running it straight from the board.
However, I was able to solve the issue and I am now able to run the MagPi Issue 67 code on a Tinker Board.
OGL.h
Code: #ifndef OGL_H_INCLUDED
#define OGL_H_INCLUDED
#pragma once
// these headers give the code here
#include <stdio.h>
#include <iostream>
#include <sys/time.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
// this header is very important
#define FALSE 0
#define TRUE 1
/// esCreateWindow flag - RGB color buffer
#define ES_WINDOW_RGB 0
/// esCreateWindow flag - ALPHA color buffer
#define ES_WINDOW_ALPHA 1
/// esCreateWindow flag - depth buffer
#define ES_WINDOW_DEPTH 2
/// esCreateWindow flag - stencil buffer
#define ES_WINDOW_STENCIL 4
/// esCreateWindow flat - multi-sample buffer
#define ES_WINDOW_MULTISAMPLE 8
class OGL
{
public:
OGL();
~OGL();
// out Pi to have an OGLES
typedef struct Target_State
{
GLint width;
GLint height;
/// Put your user data here...
//void* userData;
EGLDisplay display;
EGLSurface surface;
EGLContext context;
//UserData userData;
EGLNativeWindowType nativewindow;
//EGL_DISPMANX_WINDOW_T nativewindow;
} Target_State;
GLuint LoadShader(GLenum type, const char *shaderSrc);
int Init();
void init_EGL(Target_State *state, int width, int height);
void esInitContext(Target_State *state);
GLuint CreateTexture2D(int width, int height, char* TheData); // this is a handy routine for making textures
/*Now that we have created a type called a Target_State, we can use it in code. We can create it here */
Target_State state;
GLint programObject; // having only one global P0 is limiting, but for now it will make sense
GLuint textureID;
};
#endif // OGL_H_INCLUDED
OLG.cpp
Code: /*
* OGL.cpp
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "OGL.h"
#include <assert.h> // assert is a way of making sure that things are what we expect and if not it will stop and tell us.
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define EGL_FALSE 0
#define EGL_TRUE 1
// X11 related local variables
static Display *x_display = NULL;
using namespace std;
/* For beginners this might be a bit too much, but just include the file for now and use the OGL.h file when you need to use
OpenGLES2.0 instructions.
In order to set up a screen, we will need to ask the video system to create a couple of buffer areas with
the right type of pixels and also indicate some of the hardware features we want to switch on. This is done using EGL libs.
The process of setting up a graphic screen is a little technical but mainly we must create an EGLscreen
which itself is optimised on RaspPi to use a special format our Broadcom GPU givs us for its display, all handled in
the bcm_host.h file so we can forget about it, but we do need to tell EGL we are using DISPMANX systems
Once we have our EGL/DISMPANX surface and displays systems it means OpenGLES2.0 has something to work with and we can
draw things using our shaders.
For convenience we will set up a simple default shader, but normally we would have a few different ones for different
effects.
You can totally ignore the contents of this file until you feel you want to explore how to set things up, but do make
sure you have an #include OGL.h in any class that wants to play with OpenGL and that you double check that you create
an instance of OGL and call its init() method to set it all up of us.
Also note this file makes use of fprintf instead of cout, its just a tidier way to format printed text.
*/
OGL::OGL() {};
OGL::~OGL() {};
// this is a simple definition of an attribute list to create an EGL window, there are many variations we could have
static const EGLint attribute_list[] =
{
// these 1st 4, let us define the size of our colour componants and from that the size of a pixel.
// In this case 32bits made up of 8xR, 8xG,8xB and 8xA
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_ALPHA_SIZE,
8,
EGL_SAMPLE_BUFFERS,
1, // if you want anti alias at a slight fps cost
EGL_SAMPLES,
4, //keep these 2 lines
// These lines set up some basic internal features that help
EGL_DEPTH_SIZE,
1,
// These lines tell EGL how the surface is made up.
EGL_SURFACE_TYPE,
EGL_WINDOW_BIT,
EGL_NONE
};
// we will also need to tell our context what we want.
static const EGLint context_attributes[] =
{
EGL_CONTEXT_CLIENT_VERSION,
2,
EGL_NONE
};
// This will set up the Broadcomm GPU version of an EGL display,
//
// CreateEGLContext()
//
// Creates an EGL rendering context and all associated elements
/* We've added a simple routine to create textures
Create a texture with width and height
*/
GLuint OGL::CreateTexture2D(int width, int height, char* TheData)
{
// Texture handle
GLuint textureId;
// Set the alignment
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Generate a texture object
glGenTextures(1, &textureId);
// Bind the texture object
glBindTexture(GL_TEXTURE_2D, textureId);
// set it up
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
width,
height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
TheData);
if (glGetError() != GL_NO_ERROR) printf("We got an error"); // its a good idea to test for errors.
// Set the filtering mode
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return textureId;
}
void OGL::esInitContext(Target_State *state)
{
if ( state != NULL )
{
memset( state, 0, sizeof( Target_State) );
}
else
{
std::cout << "Failed to memset state" << std::endl;
}
}
//void OGL::init_EGL(Target_State *state, int width, int height, int FBResX, int FBResY)
void OGL::init_EGL(Target_State *state, int width, int height)
{
#define ES_WINDOW_RGB 0
std::cout << "init openGLES started\n";
state->width = width;
state->height = height;
EGLint numConfigs;
EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
EGLContext context;
EGLSurface surface;
EGLConfig config;
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };
EGLint attribute_list[] =
{
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_ALPHA_SIZE, (ES_WINDOW_RGB & ES_WINDOW_ALPHA) ? 8 : EGL_DONT_CARE,
EGL_DEPTH_SIZE, (ES_WINDOW_RGB & ES_WINDOW_DEPTH) ? 8 : EGL_DONT_CARE,
EGL_STENCIL_SIZE, (ES_WINDOW_RGB & ES_WINDOW_STENCIL) ? 8 : EGL_DONT_CARE,
EGL_SAMPLE_BUFFERS, (ES_WINDOW_RGB & ES_WINDOW_MULTISAMPLE) ? 1 : 0,
EGL_NONE
};
// EGLint result = eglChooseConfig(state->display, attribute_list, &config, 1, &numConfigs);
/* create a native window */
Window root;
XSetWindowAttributes swa;
XSetWindowAttributes xattr;
Atom wm_state;
XWMHints hints;
XEvent xev;
EGLConfig ecfg;
EGLint num_config;
Window win;
Screen *screen;
eglBindAPI(EGL_OPENGL_ES_API);
/*
* X11 native display initialization
*/
// WinCreate
x_display = XOpenDisplay(NULL);
if (x_display == NULL)
{
return; // we need to trap this;
}
root = DefaultRootWindow(x_display);
screen = ScreenOfDisplay(x_display, 0);
width = screen->width;
height = screen->height; // resplution of screen
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 = FALSE;
XChangeWindowAttributes(x_display, win, CWOverrideRedirect, &xattr);
hints.input = TRUE;
hints.flags = InputHint;
XSetWMHints(x_display, win, &hints);
std::cout << "config openGLES X11 window\n";
char* title = (char*)"x11 window Maze3dHunt";
// 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);
std::cout << "Initialized XInternAtom\n";
state->nativewindow = (EGLNativeWindowType) win;
// CreateEGLContext
// Get Display
//display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
display = eglGetDisplay((EGLNativeDisplayType)x_display);
if (display == EGL_NO_DISPLAY)
{
return; // EGL_FALSE;
}
// Initialize EGL
if (!eglInitialize(display, &minorVersion,&majorVersion))
{
EGLint err = eglGetError();
return;// EGL_FALSE;
}
// Get configs
if (!eglGetConfigs(display, NULL, 0, &numConfigs))
{
return;// EGL_FALSE;
}
// Choose config
if (!eglChooseConfig(display, attribute_list, &config, 1, &numConfigs))
{
return;// EGL_FALSE;
}
// Create a surface
surface = eglCreateWindowSurface(display, config, state->nativewindow, NULL);
if (surface == EGL_NO_SURFACE)
{
std::cout << "Failed to Create Window Surface\n";
return;// EGL_FALSE;
}
// Create a GL context
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
if (context == EGL_NO_CONTEXT)
{
return;// EGL_FALSE;
}
// Make the context current
if (!eglMakeCurrent(display, surface, surface, context))
{
return;// EGL_FALSE;
}
state->display = display;
state->surface = surface;
state->context = context;
//return;// EGL_TRUE;
std::cout << "Print CPU info\n";
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));
}
/*
This is a fairly standard Shader loader and generator
enter with the type of shader needed and a pointer to where the code text is
*/
GLuint OGL::LoadShader(GLenum type, const char *shaderSrc)
{
// see if we can allcoate a new shader
GLuint TheShader = glCreateShader(type);
if (TheShader == 0) return FALSE; // return FALSE (0) to indicate we failed to allocate this shader.
// note...allocating a shader is not the same as generating one. It just means a space was made available
// and our next step is to put the code in there.
// pass the source
glShaderSource(TheShader, 1, &shaderSrc, NULL);
// Compile the shader
glCompileShader(TheShader);
GLint IsCompiled; // we need a value to be set if we fail or succeed.
// Compiling can result in errors, so we need to make sure we find out if it compiled,
// but also its useful to report the error
glGetShaderiv(TheShader, GL_COMPILE_STATUS, &IsCompiled); // if compile works, TheShader will have a non 0 value
// a compiler fail means we need to fix the error manually, so output the compiler errors to help us
if (!IsCompiled)
{
GLint RetinfoLen = 0;
glGetShaderiv(TheShader, GL_INFO_LOG_LENGTH, &RetinfoLen);
if (RetinfoLen > 1)
{ // standard output for errors
char* infoLog = (char*) malloc(sizeof(char) * RetinfoLen);
glGetShaderInfoLog(TheShader, RetinfoLen, NULL, infoLog);
fprintf(stderr, "Error compiling this shader:\n%s\n", infoLog); // this is a useful formatting print system
free(infoLog);
}
glDeleteShader(TheShader); // we did allocate a shader, but since we failed to compile we need to remove it.
return FALSE; // indicate our failure to make the shader
}
return TheShader; // if we succeeded we will have a valuer in TheShader, return it.
}
///
// Initialize open GL and create our shaders
//
int OGL::Init()
{
//bcm_host_init(); // make sure we have set up our Broadcom
//ESContext esContext;
//programObject userData;
esInitContext(&state);
//state.userData = &userData;
//init_EGL(&state, 1024, 768); // this will set up the EGL stuff, its complex so put in a seperate method
init_EGL(&state, 320, 240); // this will set up the EGL stuff, its complex so put in a seperate method
// HERE we will load in our graphic image to play with and convert it into a texture
int Grwidth, Grheight, comp; // these are variables we will use to get info
// this is a strange instruction with * and & symbols which we'll discuss soon, but what it does is simple
// it loads a graphic image into memory and stores its width and height into variables Grwidth and Grheight, don't worry about comp or the 4, but they are needed
unsigned char *data = stbi_load("images/MagPicomp.png", &Grwidth, &Grheight, &comp, 4); // ask it to load 4 componants since its rgba
//now its loaded we have to create a texture, which will have a "handle" ID that can be stored, we have added a nice function to make this easy
textureID = CreateTexture2D(Grwidth, Grheight, (char*) data); //just pass the width and height of the graphic, and where it is located and we can make a texture
/*
*
*we're done making the texture but something to think about, where is the data we made it from? We'll find out soon.
*
**/
// we'll need a base colour to clear the screen this sets it up here (we can change it anytime
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// once the BCM has been initialsed and EGL/DISPMANX is up and runnng, all re really need now is some shaders to work with
// since OpenGLES can't actually draw without them, so we will set up a very simple default pair of shaders, and turn them
// into a Program Object which we will set as the default
// this is our shader code, contained in an array of text
// there are much better ways to do this, we will cover later
GLbyte vShaderStr[] =
"attribute vec4 a_position;\n"
"uniform vec4 u_position;\n"
"attribute vec2 a_texCoord;\n"
"varying vec2 v_texCoord;\n"
"void main()\n"
"{gl_Position = a_position + u_position; \n"
" v_texCoord=a_texCoord;"
"}\n";
GLbyte fShaderStr[] =
"precision mediump float;\n"
"varying vec2 v_texCoord;\n"
"uniform sampler2D s_texture;\n"
"void main()\n"
"{\n"
"gl_FragColor = texture2D( s_texture, v_texCoord );\n"
"}\n";
// shaders next, lets create variables to hold these
GLuint vertexShader, fragmentShader; // we need some variables
// Load and compile the vertex/fragment shaders
vertexShader = LoadShader(GL_VERTEX_SHADER, (char*)vShaderStr);
fragmentShader = LoadShader(GL_FRAGMENT_SHADER, (char*)fShaderStr);
if (vertexShader == 0 || fragmentShader == 0) // test if they both have non 0 values
{
return FALSE; // one of them is false, the report will have been printed, and we cannot continue
}
// Create the program object
programObject = glCreateProgram();
if (programObject == 0)
{
cout << "Unable to create our Program Object " << endl;
return FALSE; // there was a failure here
}
// now we have teh V and F shaders attach them to the progam object
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
// Link the program
glLinkProgram(programObject);
// Check the link status
GLint AreTheylinked;
glGetProgramiv(programObject, GL_LINK_STATUS, &AreTheylinked);
if (!AreTheylinked)
{
GLint RetinfoLen = 0;
// check and report any errors
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &RetinfoLen);
if (RetinfoLen > 1)
{
GLchar* infoLog = (GLchar*)malloc(sizeof(char) * RetinfoLen);
glGetProgramInfoLog(programObject, RetinfoLen, NULL, infoLog);
fprintf(stderr, "Error linking program:\n%s\n", infoLog);
free(infoLog);
}
glDeleteProgram(programObject);
return FALSE;
}
glUseProgram(programObject); // we only plan to use 1 program object so tell openGL to use this one
return TRUE;
}
RE: Raspberry Pi 4 is different, book code won't work! - Brian Beuken - 12-18-2019
what was the specific fix?
|