Packages

Scenic.Driver.Nerves.Rpi - Scenic driver providing drawing (only) on a Raspberry Pi under Nerves.

Current section

Files

Jump to
Raw

c_src/main.c

/*
# Created by Boyd Multerer on 05/17/18.
# Copyright © 2018 Kry10 Industries. All rights reserved.
#
*/
#include <unistd.h>
#include <stdio.h>
// #include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <stdint.h>
#include <assert.h>
#include <bcm_host.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#define NANOVG_GLES2_IMPLEMENTATION
#include "nanovg/nanovg.h"
#include "nanovg/nanovg_gl.h"
#include "types.h"
#include "comms.h"
#include "render_script.h"
#define STDIN_FILENO 0
#define DEFAULT_SCREEN 0
typedef struct {
EGLDisplay display;
EGLConfig config;
EGLSurface surface;
EGLContext context;
int screen_width;
int screen_height;
int major_version;
int minor_version;
NVGcontext* p_ctx;
} egl_data_t;
#define MSG_OUT_PUTS 0x02
//---------------------------------------------------------
// setup the video core
void init_video_core( egl_data_t* p_data, int debug_mode ) {
int screen_width, screen_height;
// initialize the bcm_host from broadcom
bcm_host_init();
// query the monitor attached to HDMI
if ( graphics_get_display_size( DEFAULT_SCREEN, &screen_width, &screen_height) < 0 ) {
send_puts("RPI driver error: Unable to query the default screen on HDMI");
return;
}
p_data->screen_width = screen_width;
p_data->screen_height = screen_height;
//-----------------------------------
// get an EGL display connection
EGLBoolean result;
// get a handle to the display
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if ( display == EGL_NO_DISPLAY ) {
send_puts("RPI driver error: Unable get handle to the default screen on HDMI");
return;
}
p_data->display = display;
// initialize the EGL display connection
EGLint major_version;
EGLint minor_version;
// returns a pass/fail boolean
if ( eglInitialize(display, &major_version, &minor_version) == EGL_FALSE ) {
send_puts("RPI driver error: Unable initialize EGL");
return;
}
p_data->major_version = major_version;
p_data->minor_version = minor_version;
// prepare an appropriate EGL frame buffer configuration request
static const EGLint attribute_list[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_STENCIL_SIZE, 1,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};
static const EGLint context_attributes[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLConfig config;
EGLint num_config;
// get an appropriate EGL frame buffer configuration
if ( eglChooseConfig(display, attribute_list, &config, 1, &num_config) == EGL_FALSE ) {
send_puts("RPI driver error: Unable to get usable display config");
return;
}
p_data->config = config;
// use open gl es
if ( eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE ) {
send_puts("RPI driver error: Unable to bind to GLES");
return;
}
// create an EGL graphics context
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes);
if ( context == EGL_NO_CONTEXT ) {
send_puts("RPI driver error: Failed to create EGL context");
return;
}
p_data->context = context;
//-------------------
// create the native window and bind it
static EGL_DISPMANX_WINDOW_T nativewindow;
DISPMANX_UPDATE_HANDLE_T dispman_update;
VC_RECT_T dst_rect;
VC_RECT_T src_rect;
dst_rect.x = 0;
dst_rect.y = 0;
if ( debug_mode ) {
dst_rect.width = screen_width / 2;
dst_rect.height = screen_height / 2;
} else {
dst_rect.width = screen_width;
dst_rect.height = screen_height;
}
src_rect.x = 0;
src_rect.y = 0;
src_rect.width = screen_width << 16;
src_rect.height = screen_height << 16;
// start the display manager
DISPMANX_DISPLAY_HANDLE_T dispman_display = vc_dispmanx_display_open(0 /* LCD */);
dispman_update = vc_dispmanx_update_start(0 /* LCD */);
// create the screen element (will be full-screen)
VC_DISPMANX_ALPHA_T alpha =
{
DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS,
255, /*alpha 0->255*/
0
};
DISPMANX_ELEMENT_HANDLE_T dispman_element = vc_dispmanx_element_add (dispman_update, dispman_display,
0/*layer*/, &dst_rect, 0/*src*/,
&src_rect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, 0/*transform*/);
result = vc_dispmanx_update_submit_sync(dispman_update);
if (result != 0) {
send_puts("RPI driver error: Unable to start dispmanx element");
return;
}
// create the native window surface
nativewindow.element = dispman_element;
nativewindow.width = screen_width;
nativewindow.height = screen_height;
EGLSurface surface = eglCreateWindowSurface(display, config, &nativewindow, NULL);
if (surface == EGL_NO_SURFACE) {
send_puts("RPI driver error: Unable create the native window surface");
return;
}
p_data->surface = surface;
// connect the context to the surface and make it current
if ( eglMakeCurrent(display, surface, surface, context) == EGL_FALSE ) {
send_puts("RPI driver error: Unable make the surface current");
return;
}
//-------------------
// config gles
// set the view port to the new size passed in
glViewport(0, 0, screen_width, screen_height);
// This turns on/off depth test.
// With this ON, whatever we draw FIRST is
// "on top" and each subsequent draw is BELOW
// the draw calls before it.
// With this OFF, whatever we draw LAST is
// "on top" and each subsequent draw is ABOVE
// the draw calls before it.
glDisable(GL_DEPTH_TEST);
// Probably need this on, enables Gouraud Shading
// glShadeModel(GL_SMOOTH);
// Turn on Alpha Blending
// There are some efficiencies to be gained by ONLY
// turning this on when we have a primitive with a
// style that has an alpha channel != 1.0f but we
// don't have code to detect that. Easy to do if we need it!
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//-------------------
// initialize nanovg
p_data->p_ctx = nvgCreateGLES2(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
if (p_data->p_ctx == NULL) {
send_puts("RPI driver error: failed nvgCreateGLES2");
return;
}
}
void test_draw(egl_data_t* p_data) {
//-----------------------------------
// Set background color and clear buffers
// glClearColor(0.15f, 0.25f, 0.35f, 1.0f);
// glClearColor(0.098f, 0.098f, 0.439f, 1.0f); // midnight blue
// glClearColor(0.545f, 0.000f, 0.000f, 1.0f); // dark red
// glClearColor(0.184f, 0.310f, 0.310f, 1.0f); // dark slate gray
// glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // black
// glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
NVGcontext* p_ctx = p_data->p_ctx;
int screen_width = p_data->screen_width;
int screen_height = p_data->screen_height;
// nvgBeginFrame(p_ctx, screen_width, screen_height, 1.0f);
// Next, draw graph line
nvgBeginPath(p_ctx);
nvgMoveTo(p_ctx, 0, 0);
nvgLineTo(p_ctx, screen_width, screen_height);
nvgStrokeColor(p_ctx, nvgRGBA(0, 160, 192, 255));
nvgStrokeWidth(p_ctx, 3.0f);
nvgStroke(p_ctx);
nvgBeginPath(p_ctx);
nvgMoveTo(p_ctx, screen_width, 0);
nvgLineTo(p_ctx, 0, screen_height);
nvgStrokeColor(p_ctx, nvgRGBA(0, 160, 192, 255));
nvgStrokeWidth(p_ctx, 3.0f);
nvgStroke(p_ctx);
nvgBeginPath(p_ctx);
nvgCircle(p_ctx, screen_width / 2, screen_height / 2, 50);
nvgFillColor(p_ctx, nvgRGBAf(0.545f, 0.000f, 0.000f, 1.0f));
nvgFill(p_data->p_ctx);
nvgStroke(p_ctx);
// nvgEndFrame(p_ctx);
// eglSwapBuffers(p_data->display, p_data->surface);
}
//---------------------------------------------------------
// return true if the caller side of the stdin pipe is open and in
// business. If it closes, then return false
// http://pubs.opengroup.org/onlinepubs/7908799/xsh/poll.html
// see https://stackoverflow.com/questions/25147181/pollhup-vs-pollnval-or-what-is-pollhup
bool isCallerDown()
{
struct pollfd ufd;
memset(&ufd, 0, sizeof ufd);
ufd.fd = STDIN_FILENO;
ufd.events = POLLIN;
if ( poll(&ufd, 1, 0) < 0 )
return true;
return ufd.revents & POLLHUP;
}
//---------------------------------------------------------
int main(int argc, char **argv) {
driver_data_t data;
egl_data_t egl_data;
test_endian();
// super simple arg check
if ( argc != 3 ) {
send_puts("Argument check failed!");
printf("\r\nscenic_driver_nerves_rpi should be launched via the Scenic.Driver.Nerves.Rpi library.\r\n\r\n");
return 0;
}
int num_scripts = atoi(argv[1]);
int debug_mode = atoi(argv[2]);
// init graphics
init_video_core( &egl_data, debug_mode );
// set up the scripts table
memset(&data, 0, sizeof(driver_data_t));
data.p_scripts = malloc( sizeof(void*) * num_scripts );
memset(data.p_scripts, 0, sizeof(void*) * num_scripts );
data.keep_going = true;
data.num_scripts = num_scripts;
data.p_ctx = egl_data.p_ctx;
data.screen_width = egl_data.screen_width;
data.screen_height = egl_data.screen_height;
// signal the app that the window is ready
send_ready( 0, egl_data.screen_width, egl_data.screen_height );
/* Loop until the calling app closes the window */
while ( data.keep_going && !isCallerDown() ) {
// check for incoming messages - blocks with a timeout
if ( handle_stdio_in(&data) ) {
// clear the buffer
glClear(GL_COLOR_BUFFER_BIT);
// render the scene
nvgBeginFrame( egl_data.p_ctx, egl_data.screen_width, egl_data.screen_height, 1.0f);
if ( data.root_script >= 0 ) {
run_script( data.root_script, &data );
}
nvgEndFrame(data.p_ctx);
// Swap front and back buffers
eglSwapBuffers(egl_data.display, egl_data.surface);
}
// wait for events - timeout is in seconds
// the timeout is the max time the app will stay alive
// after the host BEAM environment shuts down.
// glfwWaitEventsTimeout(1.01f);
// poll for events and return immediately
// glfwPollEvents();
}
return 0;
}