Created
January 30, 2020 08:49
-
-
Save jvcleave/3271576bbe0f43ddead626f795e22c22 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "ofAppEGLWindow.h" | |
#include "ofGraphics.h" // used in runAppViaInfiniteLoop() | |
#include "ofAppRunner.h" | |
#include "ofUtils.h" | |
#include "ofFileUtils.h" | |
#include "ofGLProgrammableRenderer.h" | |
#include "ofGLRenderer.h" | |
#include "ofVectorMath.h" | |
#include <assert.h> | |
// x11 | |
#include <X11/Xutil.h> | |
#include <EGL/egl.h> | |
// include includes for both native and X11 possibilities | |
#include <libudev.h> | |
#include <stdbool.h> | |
#include <stdio.h> // sprintf | |
#include <stdlib.h> // malloc | |
#include <math.h> | |
#include <fcntl.h> // open fcntl | |
#include <unistd.h> // read close | |
#include <linux/joystick.h> | |
#include "linux/kd.h" // keyboard stuff... | |
#include "termios.h" | |
#include "sys/ioctl.h" | |
#include <string.h> // strlen | |
using namespace std; | |
#if !defined(USING_DRM) | |
// native events | |
struct udev* udev; | |
struct udev_monitor* mon; | |
static int udev_fd = -1; | |
typedef map<string, int> device; | |
static device inputDevices; | |
// minimal map | |
const int lowercase_map[] = { | |
0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', | |
'-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', | |
'o', 'p', '[', ']', '\n', 0, 'a', 's', 'd', 'f', 'g', 'h', | |
'j', 'k', 'l', ';', '\'', '\n', 0, '\\', 'z', 'x', 'c', 'v', | |
'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ', 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\r' | |
}; | |
// minimal keyboard map | |
const int uppercase_map[] = { | |
0, 0, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', | |
'_', '+', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', | |
'O', 'P', '{', '}', '\n', 0, 'A', 'S', 'D', 'F', 'G', 'H', | |
'J', 'K', 'L', ':', '"', '\n', 0, '\\', 'Z', 'X', 'C', 'V', | |
'B', 'N', 'M', '<', '>', '?', 0, '*', 0, ' ', 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\r' | |
}; | |
// keep track of a few things ... | |
typedef struct { | |
bool shiftPressed; | |
bool capsLocked; | |
} KeyboardState; | |
static KeyboardState kb; | |
static struct termios tc; | |
static struct termios ots; | |
typedef struct { | |
int mouseButtonState; | |
} MouseState; | |
// TODO, make this match the upcoming additions to ofWindow | |
#define MOUSE_BUTTON_LEFT_MASK 1 | |
#define MOUSE_BUTTON_MIDDLE_MASK 1 << 1 | |
#define MOUSE_BUTTON_RIGHT_MASK 2 << 1 | |
static MouseState mb; | |
ofAppEGLWindow* ofAppEGLWindow::instance = NULL; | |
static int string_ends_with(const char *str, const char *suffix) { | |
if (!str || !suffix) | |
return 0; | |
size_t lenstr = strlen(str); | |
size_t lensuffix = strlen(suffix); | |
if (lensuffix > lenstr) | |
return 0; | |
return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0; | |
} | |
static int string_begins_with(const char *str, const char *prefix) { | |
if (!str || !prefix) | |
return 0; | |
size_t lenstr = strlen(str); | |
size_t lenprefix = strlen(prefix); | |
if (lenprefix > lenstr) | |
return 0; | |
return strncmp(str, prefix, lenprefix) == 0; | |
} | |
// native | |
#define MOUSE_CURSOR_RUN_LENGTH_DECODE(image_buf, rle_data, size, bpp) do \ | |
{ unsigned int __bpp; unsigned char *__ip; const unsigned char *__il, *__rd; \ | |
__bpp = (bpp); __ip = (image_buf); __il = __ip + (size) * __bpp; \ | |
__rd = (rle_data); \ | |
while (__ip < __il) { unsigned int __l = *(__rd++); \ | |
if (__l & 128) { __l = __l - 128; \ | |
do { memcpy (__ip, __rd, 4); __ip += 4; } while (--__l); __rd += 4; \ | |
} else { __l *= 4; memcpy (__ip, __rd, __l); \ | |
__ip += __l; __rd += __l; } } \ | |
} while (0) | |
static const struct { | |
unsigned int width; | |
unsigned int height; | |
unsigned int bpp; /* 2:RGB16, 3:RGB, 4:RGBA */ | |
unsigned char rle_pixel_data[382 + 1]; | |
} mouse_cursor_data = { | |
12, 19, 4, | |
"\1\0\0\0\377\213\377\377\377\0\202\0\0\0\377\212\377\377\377\0\3\0\0\0\377" | |
"\377\377\377\377\0\0\0\377\211\377\377\377\0\1\0\0\0\377\202\377\377\377" | |
"\377\1\0\0\0\377\210\377\377\377\0\1\0\0\0\377\203\377\377\377\377\1\0\0" | |
"\0\377\207\377\377\377\0\1\0\0\0\377\204\377\377\377\377\1\0\0\0\377\206" | |
"\377\377\377\0\1\0\0\0\377\205\377\377\377\377\1\0\0\0\377\205\377\377\377" | |
"\0\1\0\0\0\377\206\377\377\377\377\1\0\0\0\377\204\377\377\377\0\1\0\0\0" | |
"\377\207\377\377\377\377\1\0\0\0\377\203\377\377\377\0\1\0\0\0\377\210\377" | |
"\377\377\377\1\0\0\0\377\202\377\377\377\0\1\0\0\0\377\211\377\377\377\377" | |
"\3\0\0\0\377\377\377\377\0\0\0\0\377\212\377\377\377\377\202\0\0\0\377\206" | |
"\377\377\377\377\206\0\0\0\377\203\377\377\377\377\1\0\0\0\377\202\377\377" | |
"\377\377\1\0\0\0\377\204\377\377\377\0\1\0\0\0\377\202\377\377\377\377\3" | |
"\0\0\0\377\377\377\377\0\0\0\0\377\202\377\377\377\377\1\0\0\0\377\203\377" | |
"\377\377\0\3\0\0\0\377\377\377\377\377\0\0\0\377\202\377\377\377\0\1\0\0" | |
"\0\377\202\377\377\377\377\1\0\0\0\377\203\377\377\377\0\202\0\0\0\377\204" | |
"\377\377\377\0\1\0\0\0\377\202\377\377\377\377\1\0\0\0\377\210\377\377\377" | |
"\0\1\0\0\0\377\202\377\377\377\377\1\0\0\0\377\211\377\377\377\0\202\0\0" | |
"\0\377\203\377\377\377\0", | |
}; | |
// from http://cantuna.googlecode.com/svn-history/r16/trunk/src/screen.cpp | |
#define CASE_STR(x,y) case x: str = y; break | |
static const char* eglErrorString(EGLint err) { | |
string str; | |
switch (err) { | |
CASE_STR(EGL_SUCCESS, "no error"); | |
CASE_STR(EGL_NOT_INITIALIZED, "EGL not, or could not be, initialized"); | |
CASE_STR(EGL_BAD_ACCESS, "access violation"); | |
CASE_STR(EGL_BAD_ALLOC, "could not allocate resources"); | |
CASE_STR(EGL_BAD_ATTRIBUTE, "invalid attribute"); | |
CASE_STR(EGL_BAD_CONTEXT, "invalid context specified"); | |
CASE_STR(EGL_BAD_CONFIG, "invald frame buffer configuration specified"); | |
CASE_STR(EGL_BAD_CURRENT_SURFACE, "current window, pbuffer or pixmap surface is no longer valid"); | |
CASE_STR(EGL_BAD_DISPLAY, "invalid display specified"); | |
CASE_STR(EGL_BAD_SURFACE, "invalid surface specified"); | |
CASE_STR(EGL_BAD_MATCH, "bad argument match"); | |
CASE_STR(EGL_BAD_PARAMETER, "invalid paramater"); | |
CASE_STR(EGL_BAD_NATIVE_PIXMAP, "invalid NativePixmap"); | |
CASE_STR(EGL_BAD_NATIVE_WINDOW, "invalid NativeWindow"); | |
CASE_STR(EGL_CONTEXT_LOST, "APM event caused context loss"); | |
default: str = "unknown error " + err; break; | |
} | |
return str.c_str(); | |
} | |
// X11 events | |
#include <X11/XKBlib.h> | |
#ifdef TARGET_RASPBERRY_PI | |
// TODO: remove these when they enter system headers | |
// From : https://github.com/raspberrypi/userland/blob/master/interface/vmcs_host/vc_vchi_dispmanx.h | |
#ifndef ELEMENT_CHANGE_LAYER | |
#define ELEMENT_CHANGE_LAYER (1<<0) | |
#endif | |
#ifndef ELEMENT_CHANGE_OPACITY | |
#define ELEMENT_CHANGE_OPACITY (1<<1) | |
#endif | |
#ifndef ELEMENT_CHANGE_DEST_RECT | |
#define ELEMENT_CHANGE_DEST_RECT (1<<2) | |
#endif | |
#ifndef ELEMENT_CHANGE_SRC_RECT | |
#define ELEMENT_CHANGE_SRC_RECT (1<<3) | |
#endif | |
#ifndef ELEMENT_CHANGE_MASK_RESOURCE | |
#define ELEMENT_CHANGE_MASK_RESOURCE (1<<4) | |
#endif | |
#ifndef ELEMENT_CHANGE_TRANSFORM | |
#define ELEMENT_CHANGE_TRANSFORM (1<<5) | |
#endif | |
#endif | |
//------------------------------------------------------------------------------------- | |
ofAppEGLWindowSettings::ofAppEGLWindowSettings() | |
:ofGLESWindowSettings(){ | |
eglWindowPreference = OF_APP_WINDOW_AUTO; | |
eglWindowOpacity = 255; | |
// these are usually set as default, but set them here just to be sure | |
frameBufferAttributes[EGL_RED_SIZE] = 8; // 8 bits for red | |
frameBufferAttributes[EGL_GREEN_SIZE] = 8; // 8 bits for green | |
frameBufferAttributes[EGL_BLUE_SIZE] = 8; // 8 bits for blue | |
frameBufferAttributes[EGL_ALPHA_SIZE] = 8; // 8 bits for alpha | |
frameBufferAttributes[EGL_LUMINANCE_SIZE] = EGL_DONT_CARE; // 8 bits for alpha | |
frameBufferAttributes[EGL_DEPTH_SIZE] = 24; // 24 bits for depth | |
frameBufferAttributes[EGL_STENCIL_SIZE] = 8; // 8 bits for stencil | |
frameBufferAttributes[EGL_SAMPLES] = 1; | |
initialClearColor = ofColor(0.15 * 255, 0.15 * 255, 0.15 * 255, 255); | |
screenNum = 0; /* 0 = LCD on the raspberry pi */ | |
layer = 0; | |
} | |
ofAppEGLWindowSettings::ofAppEGLWindowSettings(const ofGLESWindowSettings & settings) | |
:ofGLESWindowSettings(settings){ | |
eglWindowPreference = OF_APP_WINDOW_AUTO; | |
eglWindowOpacity = 255; | |
// these are usually set as default, but set them here just to be sure | |
frameBufferAttributes[EGL_RED_SIZE] = 8; // 8 bits for red | |
frameBufferAttributes[EGL_GREEN_SIZE] = 8; // 8 bits for green | |
frameBufferAttributes[EGL_BLUE_SIZE] = 8; // 8 bits for blue | |
frameBufferAttributes[EGL_ALPHA_SIZE] = 8; // 8 bits for alpha | |
frameBufferAttributes[EGL_LUMINANCE_SIZE] = EGL_DONT_CARE; // 8 bits for alpha | |
frameBufferAttributes[EGL_DEPTH_SIZE] = 24; // 24 bits for depth | |
frameBufferAttributes[EGL_STENCIL_SIZE] = 8; // 8 bits for stencil | |
frameBufferAttributes[EGL_SAMPLES] = 1; | |
initialClearColor = ofColor(0.15 * 255, 0.15 * 255, 0.15 * 255, 255); | |
screenNum = 0; /* 0 = LCD on the raspberry pi */ | |
layer = 0; | |
} | |
//------------------------------------------------------------ | |
ofAppEGLWindow::ofAppEGLWindow() { | |
keyboardDetected = false; | |
mouseDetected = false; | |
threadTimeout = ofThread::INFINITE_JOIN_TIMEOUT; | |
bNewScreenMode = false; | |
buttonInUse = -1; | |
bEnableSetupScreen = false; | |
bShowCursor = true; | |
nFramesSinceWindowResized = 0; | |
mouseScaleX = 2.0f; | |
mouseScaleY = 2.0f; | |
isUsingX11 = false; | |
isWindowInited = false; | |
isSurfaceInited = false; | |
x11Display = NULL; | |
x11Screen = NULL; | |
x11ScreenNum = 0l; | |
glesVersion = 1; | |
if(instance!=NULL){ | |
ofLogError("ofAppEGLWindow") << "trying to create more than one instance"; | |
} | |
instance = this; | |
} | |
//------------------------------------------------------------ | |
ofAppEGLWindow::~ofAppEGLWindow() { | |
close(); | |
} | |
//------------------------------------------------------------ | |
EGLDisplay ofAppEGLWindow::getEglDisplay() const { | |
return eglDisplay; | |
} | |
//------------------------------------------------------------ | |
EGLSurface ofAppEGLWindow::getEglSurface() const { | |
return eglSurface; | |
} | |
//------------------------------------------------------------ | |
EGLContext ofAppEGLWindow::getEglContext() const { | |
return eglContext; | |
} | |
#ifndef TARGET_RASPBERRY_PI_LEGACY | |
//------------------------------------------------------------ | |
Display* ofAppEGLWindow::getX11Display(){ | |
return x11Display; | |
} | |
//------------------------------------------------------------ | |
Window ofAppEGLWindow::getX11Window(){ | |
return x11Window; | |
} | |
#endif | |
//------------------------------------------------------------ | |
EGLConfig ofAppEGLWindow::getEglConfig() const { | |
return eglConfig; | |
} | |
//------------------------------------------------------------ | |
EGLint ofAppEGLWindow::getEglVersionMajor () const { | |
return eglVersionMajor; | |
} | |
//------------------------------------------------------------ | |
EGLint ofAppEGLWindow::getEglVersionMinor() const { | |
return eglVersionMinor; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::initNative() { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
initRPiNative(); | |
#endif | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::exitNative() { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
exitRPiNative(); | |
#endif | |
} | |
//------------------------------------------------------------ | |
EGLNativeWindowType ofAppEGLWindow::getNativeWindow() { | |
if(!isWindowInited) { | |
ofLogWarning("ofAppEGLWindow") << "getNativeDisplay(): window not initialized, returning NULL"; | |
return NULL; | |
} | |
if(isUsingX11) { | |
return (EGLNativeWindowType)x11Window; | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
return (EGLNativeWindowType)&dispman_native_window; | |
#else | |
ofLogNotice("ofAppEGLWindow") << "getNativeWindow(): no native window type for this system, perhaps try X11?"; | |
return NULL; | |
#endif | |
} | |
} | |
//------------------------------------------------------------ | |
EGLNativeDisplayType ofAppEGLWindow::getNativeDisplay() { | |
if(!isWindowInited) { | |
ofLogWarning("ofAppEGLWindow") << "getNativeDisplay(): window not initialized, returning NULL"; | |
return 0; | |
} | |
if(isUsingX11) { | |
return (EGLNativeDisplayType)x11Display; | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
return (EGLNativeDisplayType)NULL; | |
#else | |
ofLogNotice("ofAppEGLWindow") << "getNativeDisplay(): no native window type for this system, perhaps try X11?"; | |
return 0; | |
#endif | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setup(const ofGLESWindowSettings & settings){ | |
const Settings * glSettings = dynamic_cast<const Settings*>(&settings); | |
if(glSettings){ | |
setup(*glSettings); | |
}else{ | |
setup(Settings(settings)); | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setup(const ofAppEGLWindowSettings & _settings) { | |
settings = _settings; | |
windowMode = OF_WINDOW; | |
bNewScreenMode = true; | |
nFramesSinceWindowResized = 0; | |
buttonInUse = 0; | |
bEnableSetupScreen = true; | |
eglDisplayString = ""; | |
orientation = OF_ORIENTATION_DEFAULT; | |
//TODO: 2.0f is an arbitrary factor that makes mouse speed ok at 1024x768, | |
// to be totally correct we might need to take into account screen size | |
// and add acceleration | |
mouseScaleX = 2.0f; | |
mouseScaleY = 2.0f; | |
isUsingX11 = false; | |
isWindowInited = false; | |
isSurfaceInited = false; | |
eglDisplay = NULL; | |
eglSurface = NULL; | |
eglContext = NULL; | |
eglConfig = NULL; | |
eglVersionMajor = -1; | |
eglVersionMinor = -1; | |
glesVersion = 1; | |
// X11 check | |
// char * pDisplay; | |
// pDisplay = getenv ("DISPLAY"); | |
// bool bIsX11Available = (pDisplay != NULL); | |
bool bIsX11Available = getenv("DISPLAY") != NULL; | |
if(settings.eglWindowPreference == OF_APP_WINDOW_AUTO) { | |
if(bIsX11Available) { | |
isUsingX11 = true; | |
} else { | |
isUsingX11 = false; | |
} | |
} else if(settings.eglWindowPreference == OF_APP_WINDOW_NATIVE) { | |
isUsingX11 = false; | |
} else if(settings.eglWindowPreference == OF_APP_WINDOW_X11) { | |
isUsingX11 = true; | |
if(!bIsX11Available) { | |
isUsingX11 = false; | |
ofLogError("ofAppEGLWindow") << "init(): X11 window requested, but X11 is not available"; | |
} | |
} | |
//////////////// | |
// TODO remove the following ifdef once x11 is accelerated on RPI | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
if(isUsingX11) { | |
isUsingX11 = false; | |
ofLogWarning("ofAppEGLWindow") << "init(): X11 not availble on RPI yet, using a native window instead"; | |
} | |
#endif | |
//////////////// | |
initNative(); | |
glesVersion = settings.glesVersion; | |
// we set this here, and if we need to make a fullscreen | |
// app, we do it during the first loop. | |
windowMode = settings.windowMode; | |
bShowCursor = true; | |
nonFullscreenWindowRect.set(0,0,settings.getWidth(),settings.getHeight()); | |
nonFullscreenWindowRect.standardize(); | |
ofRectangle startRect = nonFullscreenWindowRect; | |
bNewScreenMode = false; | |
if(windowMode == OF_GAME_MODE) { | |
ofLogWarning("ofAppEGLWindow") << "setupOpenGL(): OF_GAME_MODE not supported, using OF_WINDOW"; | |
startRect = nonFullscreenWindowRect; | |
} else if(windowMode == OF_FULLSCREEN) { | |
startRect = getScreenRect(); | |
} | |
isWindowInited = createWindow(startRect); | |
isSurfaceInited = createSurface(); | |
if(!isWindowInited) { | |
ofLogError("ofAppEGLWindow") << "setupOpenGL(): screen creation failed, window not inited"; | |
} | |
setupPeripherals(); | |
nFramesSinceWindowResized = 0; | |
if(settings.glesVersion>1){ | |
currentRenderer = make_shared<ofGLProgrammableRenderer>(this); | |
}else{ | |
currentRenderer = make_shared<ofGLRenderer>(this); | |
} | |
makeCurrent(); | |
if(currentRenderer->getType()==ofGLProgrammableRenderer::TYPE){ | |
static_cast<ofGLProgrammableRenderer*>(currentRenderer.get())->setup(settings.glesVersion,0); | |
}else{ | |
static_cast<ofGLRenderer*>(currentRenderer.get())->setup(); | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setupPeripherals() { | |
if(!isUsingX11) { | |
// roll our own cursor! | |
mouseCursor.allocate(mouse_cursor_data.width,mouse_cursor_data.height,OF_IMAGE_COLOR_ALPHA); | |
MOUSE_CURSOR_RUN_LENGTH_DECODE(mouseCursor.getPixels().getData(),mouse_cursor_data.rle_pixel_data,mouse_cursor_data.width*mouse_cursor_data.height,mouse_cursor_data.bpp); | |
mouseCursor.update(); | |
ofLogNotice("ofAppEGLWindow") << "setupPeripherals(): peripheral setup complete"; | |
setupNativeEvents(); | |
ofLogNotice("ofAppEGLWindow") << "setupPeripherals(): native event setup complete"; | |
} else { | |
ofLogError("ofAppEGLWindow") << "setupPeripherals(): peripherals not supported on X11"; | |
} | |
} | |
//------------------------------------------------------------ | |
bool ofAppEGLWindow::createSurface() { | |
EGLNativeWindowType nativeWindow = getNativeWindow(); | |
EGLNativeDisplayType display = getNativeDisplay(); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): setting up EGL Display"; | |
// get an EGL eglDisplay connection | |
isSurfaceInited = false; | |
EGLint result; | |
if(display==0){ | |
eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); | |
}else{ | |
eglDisplay = eglGetDisplay(display); | |
} | |
if(eglDisplay == EGL_NO_DISPLAY) { | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): eglGetDisplay returned: " << eglDisplay; | |
return false; | |
}else{ | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL Display correctly set " << eglDisplay; | |
} | |
// initialize the EGL eglDisplay connection | |
result = eglInitialize(eglDisplay, | |
&eglVersionMajor, | |
&eglVersionMinor); | |
if(result == EGL_BAD_DISPLAY) { | |
// eglDisplay is not an EGL connection | |
ofLogError("ofAppEGLWindow") << "createSurface(): eglInitialize returned EGL_BAD_DISPLAY"; | |
return false; | |
} else if(result == EGL_NOT_INITIALIZED) { | |
// eglDisplay cannot be intitialized | |
ofLogError("ofAppEGLWindow") << "createSurface(): eglInitialize returned EGL_NOT_INITIALIZED"; | |
return false; | |
} else if(result == EGL_FALSE) { | |
// eglinitialize was not initialiezd | |
ofLogError("ofAppEGLWindow") << "createSurface(): eglInitialize returned EGL_FALSE"; | |
return false; | |
} else { | |
// result == EGL_TRUE | |
// success! | |
} | |
EGLint glesVersion; | |
int glesVersionForContext; | |
if(ofGetCurrentRenderer()) { | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): current renderer type: " << ofGetCurrentRenderer()->getType(); | |
} else { | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): no current renderer selected"; | |
} | |
if(this->glesVersion==2){ | |
glesVersion = EGL_OPENGL_ES2_BIT; | |
glesVersionForContext = 2; | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): GLES2 renderer detected"; | |
}else{ | |
glesVersion = EGL_OPENGL_ES_BIT; | |
glesVersionForContext = 1; | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): default renderer detected"; | |
} | |
ofEGLAttributeListIterator iter, iterEnd; | |
int i; | |
// each attribute has 2 values, and we need one extra for the EGL_NONE terminator | |
EGLint attribute_list_framebuffer_config[settings.frameBufferAttributes.size() * 2 + 3]; | |
iter = settings.frameBufferAttributes.begin(); | |
iterEnd = settings.frameBufferAttributes.end(); | |
i = 0; | |
for(; iter != iterEnd; iter++) { | |
attribute_list_framebuffer_config[i++] = iter->first; | |
attribute_list_framebuffer_config[i++] = iter->second; | |
} | |
attribute_list_framebuffer_config[i++] = EGL_RENDERABLE_TYPE; | |
attribute_list_framebuffer_config[i++] = glesVersion; //openGL ES version | |
attribute_list_framebuffer_config[i] = EGL_NONE; // add the terminator | |
EGLint num_configs; | |
// get an appropriate EGL frame buffer configuration | |
// http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/eglChooseConfig.html | |
result = eglChooseConfig(eglDisplay, | |
attribute_list_framebuffer_config, | |
&eglConfig, | |
1, // we only want the first one. if we want more, | |
// we need to pass in an array. | |
// we are optimistic and don't give it more chances | |
// to find a good configuration | |
&num_configs); | |
if(result == EGL_FALSE) { | |
EGLint error = eglGetError(); | |
ofLogError("ofAppEGLWindow") << "createSurface(): error finding valid configuration based on settings: " << eglErrorString(error); | |
return false; | |
} | |
if(num_configs <= 0 || eglConfig == NULL) { | |
ofLogError("ofAppEGLWindow") << "createSurface(): no matching configs were found, num_configs: " << num_configs; | |
return false; | |
} | |
// each attribute has 2 values, and we need one extra for the EGL_NONE terminator | |
EGLint attribute_list_window_surface[settings.windowSurfaceAttributes.size() * 2 + 1]; | |
iter = settings.windowSurfaceAttributes.begin(); | |
iterEnd = settings.windowSurfaceAttributes.end(); | |
i = 0; | |
for(; iter != iterEnd; iter++) { | |
attribute_list_window_surface[i++] = iter->first; | |
attribute_list_window_surface[i++] = iter->second; | |
} | |
attribute_list_window_surface[i] = EGL_NONE; // add the terminator | |
// create a surface | |
eglSurface = eglCreateWindowSurface( eglDisplay, // our display handle | |
eglConfig, // our first config | |
nativeWindow, // our native window | |
attribute_list_window_surface); // surface attribute list | |
if(eglSurface == EGL_NO_SURFACE) { | |
EGLint error = eglGetError(); | |
switch(error) { | |
case EGL_BAD_MATCH: | |
ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_MATCH " << eglErrorString(error); | |
ofLogError("ofAppEGLWindow") << "createSurface(): check window and EGLConfig attributes to determine compatibility, "; | |
ofLogError("ofAppEGLWindow") << "createSurface(): or verify that the EGLConfig supports rendering to a window"; | |
break; | |
case EGL_BAD_CONFIG: | |
ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_CONFIG " << eglErrorString(error); | |
ofLogError("ofAppEGLWindow") << "createSurface(): verify that provided EGLConfig is valid"; | |
break; | |
case EGL_BAD_NATIVE_WINDOW: | |
ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_NATIVE_WINDOW " << eglErrorString(error); | |
ofLogError("ofAppEGLWindow") << "createSurface(): verify that provided EGLNativeWindow is valid"; | |
break; | |
case EGL_BAD_ALLOC: | |
ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: EGL_BAD_ALLOC " << eglErrorString(error); | |
ofLogError("ofAppEGLWindow") << "createSurface(): not enough resources available"; | |
break; | |
default: | |
ofLogError("ofAppEGLWindow") << "createSurface(): error creating surface: << " << error << eglErrorString(error); | |
} | |
return false; | |
}else{ | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): surface created correctly"; | |
} | |
// get an appropriate EGL frame buffer configuration | |
result = eglBindAPI(EGL_OPENGL_ES_API); | |
if(result == EGL_FALSE) { | |
ofLogError("ofAppEGLWindow") << "createSurface(): error binding API: " << eglErrorString(eglGetError()); | |
return false; | |
}else{ | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): API bound correctly"; | |
} | |
// create an EGL rendering eglContext | |
EGLint attribute_list_surface_context[] = { | |
EGL_CONTEXT_CLIENT_VERSION, glesVersionForContext, | |
EGL_NONE | |
}; | |
eglContext = eglCreateContext(eglDisplay, | |
eglConfig, | |
EGL_NO_CONTEXT, | |
attribute_list_surface_context); | |
if(eglContext == EGL_NO_CONTEXT) { | |
EGLint error = eglGetError(); | |
if(error == EGL_BAD_CONFIG) { | |
ofLogError("ofAppEGLWindow") << "createSurface(): error creating context: EGL_BAD_CONFIG " << eglErrorString(error); | |
return false; | |
} else { | |
ofLogError("ofAppEGLWindow") << "createSurface(): error creating context: " << error << " " << eglErrorString(error); | |
return false; | |
} | |
} | |
// connect the eglContext to the eglSurface | |
result = eglMakeCurrent(eglDisplay, | |
eglSurface, // draw surface | |
eglSurface, // read surface | |
eglContext); | |
if(eglContext == nullptr) { | |
EGLint error = eglGetError(); | |
ofLogError("ofAppEGLWindow") << "createSurface(): couldn't making current surface: " << eglErrorString(error); | |
return false; | |
} | |
// Set background color and clear buffers | |
glClearColor(settings.initialClearColor.r / 255.0f, | |
settings.initialClearColor.g / 255.0f, | |
settings.initialClearColor.b / 255.0f, | |
settings.initialClearColor.a / 255.0f); | |
glClear( GL_COLOR_BUFFER_BIT ); | |
glClear( GL_DEPTH_BUFFER_BIT ); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): -----EGL-----"; | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VERSION_MAJOR = " << eglVersionMajor; | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VERSION_MINOR = " << eglVersionMinor; | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_CLIENT_APIS = " << eglQueryString(eglDisplay, EGL_CLIENT_APIS); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VENDOR = " << eglQueryString(eglDisplay, EGL_VENDOR); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_VERSION = " << eglQueryString(eglDisplay, EGL_VERSION); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): EGL_EXTENSIONS = " << eglQueryString(eglDisplay, EGL_EXTENSIONS); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): GL_RENDERER = " << glGetString(GL_RENDERER); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): GL_VERSION = " << glGetString(GL_VERSION); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): GL_VENDOR = " << glGetString(GL_VENDOR); | |
ofLogNotice("ofAppEGLWindow") << "createSurface(): -------------"; | |
isSurfaceInited = true; | |
return true; | |
} | |
//------------------------------------------------------------ | |
bool ofAppEGLWindow::destroySurface() { | |
if(isSurfaceInited) { | |
ofLogNotice("ofAppEGLWindow") << "destroySurface(): destroying EGL surface"; | |
eglSwapBuffers(eglDisplay, eglSurface); | |
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |
eglDestroySurface(eglDisplay, eglSurface); | |
eglDestroyContext(eglDisplay, eglContext); | |
eglTerminate(eglDisplay); | |
isSurfaceInited = false; | |
eglDisplay = NULL; | |
eglSurface = NULL; | |
eglContext = NULL; | |
eglConfig = NULL; | |
eglVersionMinor = -1; | |
eglVersionMinor = -1; | |
return true; | |
} else { | |
ofLogError("ofAppEGLWindow") << "destroySurface(): attempted to destroy uninitialized window"; | |
return false; | |
} | |
} | |
//------------------------------------------------------------ | |
bool ofAppEGLWindow::destroyWindow() { | |
if(isWindowInited) { | |
if(isUsingX11) { | |
// TODO: double check | |
XDestroyWindow(x11Display,x11Window); // or XCloseWindow? | |
XFree(x11Screen); | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
dispman_update = vc_dispmanx_update_start(0); | |
if (dispman_element != DISPMANX_NO_HANDLE) { | |
vc_dispmanx_element_remove(dispman_update, dispman_element); | |
dispman_element = DISPMANX_NO_HANDLE; | |
} | |
vc_dispmanx_update_submit_sync(dispman_update); | |
if (dispman_display != DISPMANX_NO_HANDLE) { | |
vc_dispmanx_display_close(dispman_display); | |
dispman_display = DISPMANX_NO_HANDLE; | |
} | |
#else | |
ofLogNotice("ofAppEGLWindow") << "destroyWindow(): no native window type for this system, perhaps try X11?"; | |
#endif | |
} | |
} else { | |
ofLogNotice("ofAppEGLWindow") << "destroyWindow(): destroying (uninited) native window (not implemented yet)"; | |
} | |
return true; | |
} | |
void ofAppEGLWindow::close(){ | |
if(!isUsingX11) { | |
destroyNativeEvents(); | |
} | |
// we got a terminate ... so clean up. | |
destroySurface(); | |
destroyWindow(); | |
exitNative(); | |
events().notifyExit(); | |
events().disable(); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::makeCurrent(){ | |
eglMakeCurrent(eglDisplay, | |
eglSurface, // draw surface | |
eglSurface, // read surface | |
eglContext); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::swapBuffers(){ | |
EGLBoolean success = eglSwapBuffers(eglDisplay, eglSurface); | |
if(!success) { | |
GLint error = eglGetError(); | |
ofLogNotice("ofAppEGLWindow") << "display(): eglSwapBuffers failed: " << eglErrorString(error); | |
} | |
} | |
//-------------------------------------------- | |
void ofAppEGLWindow::startRender() { | |
renderer()->startRender(); | |
} | |
//-------------------------------------------- | |
void ofAppEGLWindow::finishRender() { | |
renderer()->finishRender(); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::update() { | |
coreEvents.notifyUpdate(); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::draw() { | |
// take care of any requests for a new screen mode | |
if (windowMode != OF_GAME_MODE && bNewScreenMode){ | |
if( windowMode == OF_FULLSCREEN){ | |
setWindowRect(getScreenRect()); | |
} else if( windowMode == OF_WINDOW ){ | |
setWindowRect(nonFullscreenWindowRect); | |
} | |
bNewScreenMode = false; | |
} | |
currentRenderer->startRender(); | |
if( bEnableSetupScreen ) currentRenderer->setupScreen(); | |
coreEvents.notifyDraw(); | |
if(!isUsingX11) { | |
if(bShowCursor){ | |
GLboolean bIsDepthTestEnabled = GL_FALSE; | |
glGetBooleanv(GL_DEPTH_TEST, &bIsDepthTestEnabled); | |
if(bIsDepthTestEnabled == GL_TRUE) { | |
glDisable(GL_DEPTH_TEST); | |
} | |
bool isUsingNormalizedTexCoords = ofGetUsingNormalizedTexCoords(); | |
if(isUsingNormalizedTexCoords) { | |
ofDisableNormalizedTexCoords(); | |
} | |
currentRenderer->pushStyle(); | |
currentRenderer->setBlendMode(OF_BLENDMODE_ADD); | |
currentRenderer->setColor(255); | |
mouseCursor.draw(ofGetMouseX(),ofGetMouseY()); | |
currentRenderer->popStyle(); | |
if(bIsDepthTestEnabled == GL_TRUE) { | |
glEnable(GL_DEPTH_TEST); | |
} | |
if(isUsingNormalizedTexCoords) { | |
ofEnableNormalizedTexCoords(); | |
} | |
} | |
} | |
currentRenderer->finishRender(); | |
EGLBoolean success = eglSwapBuffers(eglDisplay, eglSurface); | |
if(!success) { | |
GLint error = eglGetError(); | |
ofLogNotice("ofAppEGLWindow") << "display(): eglSwapBuffers failed: " << eglErrorString(error); | |
} | |
nFramesSinceWindowResized++; | |
} | |
//------------------------------------------------------------ | |
ofCoreEvents & ofAppEGLWindow::events(){ | |
return coreEvents; | |
} | |
//------------------------------------------------------------ | |
shared_ptr<ofBaseRenderer> & ofAppEGLWindow::renderer(){ | |
return currentRenderer; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setupNativeEvents() { | |
setupNativeUDev(); | |
setupNativeInput(); | |
startThread(); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::destroyNativeEvents() { | |
destroyNativeUDev(); | |
destroyNativeInput(); | |
waitForThread(true, threadTimeout); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setWindowRect(const ofRectangle& requestedWindowRect) { | |
if(!isWindowInited) { | |
ofLogError("ofAppEGLWindow") << "setWindowRect(): window not inited"; | |
return; | |
} | |
ofRectangle newRect = requestedWindowRect.getStandardized(); | |
if(newRect != currentWindowRect) { | |
ofRectangle oldWindowRect = currentWindowRect; | |
if(isUsingX11) { | |
int ret = XMoveResizeWindow(x11Display, | |
x11Window, | |
(int)newRect.x, | |
(int)newRect.y, | |
(unsigned int)newRect.width, | |
(unsigned int)newRect.height); | |
if(ret == BadValue) { | |
ofLogError("ofAppEGLWindow") << "setWindowRect(): XMoveResizeWindow returned BadValue"; | |
} else if(ret == BadWindow) { | |
ofLogError("ofAppEGLWindow") << "setWindowRect(): XMoveResizeWindow returned BadWindow"; | |
} else { | |
// all is good | |
currentWindowRect = newRect; | |
} | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
VC_RECT_T dst_rect; | |
dst_rect.x = (int32_t)newRect.x; | |
dst_rect.y = (int32_t)newRect.y; | |
dst_rect.width = (int32_t)newRect.width; | |
dst_rect.height = (int32_t)newRect.height; | |
VC_RECT_T src_rect; | |
src_rect.x = 0; | |
src_rect.y = 0; | |
src_rect.width = (int32_t)newRect.width << 16; | |
src_rect.height = (int32_t)newRect.height << 16; | |
DISPMANX_UPDATE_HANDLE_T dispman_update = vc_dispmanx_update_start(0); | |
vc_dispmanx_element_change_attributes(dispman_update, | |
dispman_element, | |
ELEMENT_CHANGE_SRC_RECT|ELEMENT_CHANGE_DEST_RECT, // we do both when resizing | |
0, // layer (we aren't changing it here) | |
0, // opactiy (we aren't changing it here) | |
&dst_rect, | |
&src_rect, | |
0, // mask (we aren't changing it here) | |
(DISPMANX_TRANSFORM_T)0); | |
vc_dispmanx_update_submit_sync(dispman_update); | |
// next time swapBuffers is called, it will be resized based on this eglwindow size data | |
dispman_native_window.element = dispman_element; | |
dispman_native_window.width = (int32_t)newRect.width; | |
dispman_native_window.height = (int32_t)newRect.height; // don't forget! | |
currentWindowRect = newRect; | |
#else | |
ofLogError("ofAppEGLWindow") << "createEGLWindow(): no native window type for this system, perhaps try X11?"; | |
#endif | |
} | |
if(oldWindowRect.width != currentWindowRect.width || oldWindowRect.height != currentWindowRect.height) { | |
coreEvents.notifyWindowResized(currentWindowRect.width, currentWindowRect.height); | |
nFramesSinceWindowResized = 0; | |
} | |
} | |
} | |
//------------------------------------------------------------ | |
bool ofAppEGLWindow::createWindow(const ofRectangle& requestedWindowRect) { | |
if(isUsingX11) { | |
return createX11NativeWindow(requestedWindowRect); | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
return createRPiNativeWindow(requestedWindowRect); | |
#else | |
ofLogError("ofAppEGLWindow") << "createEGLWindow(): no native window type for this system, perhaps try X11?"; | |
return false; | |
#endif | |
} | |
} | |
//------------------------------------------------------------ | |
int ofAppEGLWindow::getWindowWidth() { | |
return currentWindowRect.width; | |
} | |
//------------------------------------------------------------ | |
int ofAppEGLWindow::getWindowHeight() { | |
return currentWindowRect.height; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::pollEvents(){ | |
if(!instance) return; | |
if(instance->isUsingX11) { | |
while(1){ | |
XEvent event; | |
if (::XCheckWindowEvent(instance->x11Display, instance->x11Window, -1, &event)){ | |
handleX11Event(event); | |
}else if (::XCheckTypedEvent(instance->x11Display, ClientMessage, &event)){ | |
handleX11Event(event); | |
}else{ | |
break; | |
} | |
} | |
} else { | |
queue<ofMouseEventArgs> mouseEventsCopy; | |
instance->lock(); | |
mouseEventsCopy = instance->mouseEvents; | |
while(!instance->mouseEvents.empty()){ | |
instance->mouseEvents.pop(); | |
} | |
instance->unlock(); | |
while(!mouseEventsCopy.empty()){ | |
instance->coreEvents.notifyMouseEvent(mouseEventsCopy.front()); | |
mouseEventsCopy.pop(); | |
} | |
// KEYBOARD EVENTS | |
queue<ofKeyEventArgs> keyEventsCopy; | |
instance->lock(); | |
keyEventsCopy = instance->keyEvents; | |
while(!instance->keyEvents.empty()){ | |
instance->keyEvents.pop(); | |
} | |
instance->unlock(); | |
while(!keyEventsCopy.empty()){ | |
instance->coreEvents.notifyKeyEvent(keyEventsCopy.front()); | |
keyEventsCopy.pop(); | |
} | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::hideCursor(){ | |
bShowCursor = false; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::showCursor(){ | |
bShowCursor = true; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setWindowTitle(string title) { | |
ofLogNotice("ofAppEGLWindow") << "setWindowTitle(): not implemented"; | |
} | |
//------------------------------------------------------------ | |
glm::vec2 ofAppEGLWindow::getWindowSize(){ | |
return {currentWindowRect.width, currentWindowRect.height}; | |
} | |
//------------------------------------------------------------ | |
glm::vec2 ofAppEGLWindow::getWindowPosition(){ | |
return glm::vec2(currentWindowRect.getPosition()); | |
} | |
//------------------------------------------------------------ | |
glm::vec2 ofAppEGLWindow::getScreenSize(){ | |
unsigned int screenWidth = 0; | |
unsigned int screenHeight = 0; | |
if(isUsingX11) { | |
// TODO, there must be a way to get screensize if the window is not inited | |
if(isWindowInited && x11Screen) { | |
screenWidth = XWidthOfScreen(x11Screen); | |
screenHeight = XHeightOfScreen(x11Screen); | |
} else { | |
ofLogError("ofAppEGLWindow") << "getScreenSize(): tried to get display size but failed, x11Screen is not inited"; | |
} | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
int success = graphics_get_display_size(settings.screenNum, &screenWidth, &screenHeight); | |
if(success < 0) { | |
ofLogError("ofAppEGLWindow") << "getScreenSize(): tried to get display size but failed"; | |
} | |
#else | |
ofLogError("ofAppEGLWindow") << "getScreenSize(): no native window type for this system, perhaps try X11?"; | |
#endif | |
} | |
return {screenWidth, screenHeight}; | |
} | |
//------------------------------------------------------------ | |
int ofAppEGLWindow::getWidth(){ | |
if( orientation == OF_ORIENTATION_DEFAULT || orientation == OF_ORIENTATION_180 ){ | |
return currentWindowRect.width; | |
} | |
return currentWindowRect.height; | |
} | |
//------------------------------------------------------------ | |
int ofAppEGLWindow::getHeight(){ | |
if( orientation == OF_ORIENTATION_DEFAULT || orientation == OF_ORIENTATION_180 ){ | |
return currentWindowRect.height; | |
} | |
return currentWindowRect.width; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setOrientation(ofOrientation orientationIn){ | |
orientation = orientationIn; | |
} | |
//------------------------------------------------------------ | |
ofOrientation ofAppEGLWindow::getOrientation(){ | |
return orientation; | |
} | |
//------------------------------------------------------------ | |
bool ofAppEGLWindow::doesHWOrientation() { | |
return false; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setWindowPosition(int x, int y){ | |
if(!isWindowInited) { | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): window not inited"; | |
return; | |
} | |
if(isUsingX11) { | |
int ret = XMoveWindow(x11Display, | |
x11Window, | |
x, | |
y); | |
if(ret == BadValue) { | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadValue"; | |
} else if(ret == BadWindow) { | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadWindow"; | |
} else { | |
currentWindowRect.x = x; | |
currentWindowRect.y = y; | |
nonFullscreenWindowRect = currentWindowRect; | |
} | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
// keep it in bounds | |
auto screenSize = getScreenSize(); | |
x = ofClamp(x, 0, screenSize.x - currentWindowRect.width); | |
y = ofClamp(y, 0, screenSize.y - currentWindowRect.height); | |
VC_RECT_T dst_rect; | |
dst_rect.x = (int32_t)x; | |
dst_rect.y = (int32_t)y; | |
dst_rect.width = (int32_t)currentWindowRect.width; | |
dst_rect.height = (int32_t)currentWindowRect.height; | |
dispman_update = vc_dispmanx_update_start(0); | |
vc_dispmanx_element_change_attributes(dispman_update, | |
dispman_native_window.element, | |
ELEMENT_CHANGE_DEST_RECT, | |
0, | |
0, | |
&dst_rect, | |
NULL, | |
0, | |
(DISPMANX_TRANSFORM_T)0); | |
vc_dispmanx_update_submit_sync(dispman_update); | |
currentWindowRect.x = x; | |
currentWindowRect.y = y; | |
nonFullscreenWindowRect = currentWindowRect; | |
#else | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): no native window type for this system, perhaps try X11?"; | |
#endif | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setWindowShape(int w, int h){ | |
if(!isWindowInited) { | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): window not inited"; | |
return; | |
} | |
if(isUsingX11) { | |
int ret = XResizeWindow(x11Display, | |
x11Window, | |
(unsigned int)w, | |
(unsigned int)h); | |
if(ret == BadValue) { | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadValue"; | |
} else if(ret == BadWindow) { | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): XMoveWindow returned BadWindow"; | |
} else { | |
currentWindowRect.width = w; | |
currentWindowRect.height = h; | |
nonFullscreenWindowRect = currentWindowRect; | |
} | |
} else { | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
setWindowRect(ofRectangle(currentWindowRect.x,currentWindowRect.y,w,h)); | |
nonFullscreenWindowRect = currentWindowRect; | |
#else | |
ofLogError("ofAppEGLWindow") << "setWindowPosition(): no native window type for this system, perhaps try X11?"; | |
#endif | |
} | |
} | |
//------------------------------------------------------------ | |
ofWindowMode ofAppEGLWindow::getWindowMode(){ | |
return windowMode; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::toggleFullscreen(){ | |
if( windowMode == OF_GAME_MODE) return; | |
if( windowMode == OF_WINDOW ){ | |
setFullscreen(true); | |
}else{ | |
setFullscreen(false); | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setFullscreen(bool fullscreen){ | |
if( windowMode == OF_GAME_MODE) return; | |
if(fullscreen && windowMode != OF_FULLSCREEN){ | |
bNewScreenMode = true; | |
windowMode = OF_FULLSCREEN; | |
}else if(!fullscreen && windowMode != OF_WINDOW) { | |
bNewScreenMode = true; | |
windowMode = OF_WINDOW; | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::enableSetupScreen(){ | |
bEnableSetupScreen = true; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::disableSetupScreen(){ | |
bEnableSetupScreen = false; | |
} | |
//------------------------------------------------------------ | |
ofRectangle ofAppEGLWindow::getScreenRect(){ | |
auto screenSize = getScreenSize(); | |
return ofRectangle(0,0,screenSize.x,screenSize.y); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setVerticalSync(bool enabled){ | |
eglSwapInterval(eglDisplay, enabled ? 1 : 0); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::threadedFunction(){ | |
// TODO: a way to setup mouse and keyboard if | |
// they are not plugged in upon start | |
// This can be done with our udev device callbacks | |
while(isThreadRunning()) { | |
readNativeUDevEvents(); | |
readNativeInputEvents(); | |
// sleep briefly | |
ofSleepMillis(20); | |
} | |
} | |
//------------------------------------------------------------ | |
// PLATFORM SPECIFIC RPI | |
//------------------------------------------------------------ | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::setupNativeUDev() { | |
udev = udev_new(); // create new udev object | |
if(!udev) { | |
ofLogError("ofAppEGLWindow") << "setupNativeUDev(): couldn't create udev object"; | |
} else { | |
ofLogNotice("ofAppEGLWindow") << "setupNativeUDev(): created udev object"; | |
// setup udev to monitor for input devices | |
mon = udev_monitor_new_from_netlink(udev, "udev"); | |
// just listen for input devices | |
udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL); | |
udev_monitor_enable_receiving(mon); | |
// get the file descriptor for the mon (used w/ select); | |
udev_fd = udev_monitor_get_fd(mon); | |
} | |
if(udev_fd < 0) { | |
ofLogError("ofAppEGLWindow") << "setupNativeUDev(): did not create udev object, udev_fd < 0"; | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::destroyNativeUDev() { | |
udev_unref(udev); // clean up | |
} | |
void ofAppEGLWindow::setupNativeInput(){ | |
struct udev_enumerate *enumerate; | |
struct udev_list_entry *devices, *entry; | |
struct udev_device *dev; | |
bool isMouse; | |
ofLogNotice("ofAppEGLWindow") << "setupNativeInput()"; | |
/* Create a list of the devices in the 'input' subsystem. */ | |
enumerate = udev_enumerate_new(udev); | |
udev_enumerate_add_match_subsystem(enumerate, "input"); | |
udev_enumerate_scan_devices(enumerate); | |
devices = udev_enumerate_get_list_entry(enumerate); | |
udev_list_entry_foreach(entry, devices) | |
{ | |
/* Get the filename of the /sys entry for the device | |
and create a udev_device object (dev) representing it */ | |
const char * name = udev_list_entry_get_name(entry); | |
dev = udev_device_new_from_syspath(udev, name); | |
const char * sysname = udev_device_get_sysname(dev); | |
const char * devnode = udev_device_get_devnode(dev); | |
const char * devpath = udev_device_get_devpath(dev); | |
const char * devtype = udev_device_get_devtype(dev); | |
dev_t devnum = udev_device_get_devnum(dev); | |
const char * driver = udev_device_get_driver(dev); | |
const char * prop_keyboard = udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD"); | |
const char * prop_mouse = udev_device_get_property_value(dev, "ID_INPUT_MOUSE"); | |
ofLogNotice() << "Got device"; | |
ofLogNotice() << " - node: " << devnode; | |
ofLogNotice() << " - sysname: " << sysname; | |
ofLogNotice() << " - devpath: " << devpath; | |
ofLogNotice() << " - devtype: " << devtype; | |
ofLogNotice() << " - driver: " << driver; | |
ofLogNotice() << " - devnum: " << devnum; | |
ofLogNotice() << " - ID_INPUT_KEYBOARD: " << prop_keyboard; | |
ofLogNotice() << " - ID_INPUT_MOUSE: " << prop_mouse; | |
if(prop_mouse){ | |
isMouse = true; | |
}else{ | |
isMouse = false; | |
} | |
if(devnode && (prop_keyboard || prop_mouse) && string_begins_with(sysname, "event")){ | |
addInput(devnode, isMouse); | |
} | |
if(prop_keyboard){ | |
keyboardDetected = true; | |
} | |
if(prop_mouse){ | |
mouseDetected = true; | |
} | |
udev_device_unref(dev); | |
} | |
/* Free the enumerator object */ | |
udev_enumerate_unref(enumerate); | |
if(!mouseDetected){ | |
ofLogError("ofAppEGLWindow") << "setupNativeInput(): did not open mouse"; | |
} | |
if(!keyboardDetected){ | |
ofLogError("ofAppEGLWindow") << "setupKeyboard(): did not open keyboard"; | |
} | |
// save current terminal settings | |
tcgetattr (STDIN_FILENO, &tc); | |
ots = tc; | |
// disable echo on our temporary settings | |
tc.c_lflag &= ~ECHO; | |
tc.c_lflag |= ECHONL; | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &tc); | |
mb.mouseButtonState = 0; | |
kb.shiftPressed = false; | |
kb.capsLocked = false; | |
printInput(); | |
} | |
void ofAppEGLWindow::addInput(const char * node, bool isMouse){ | |
if(node == NULL){ | |
return; | |
} | |
removeInput(node); | |
int fd = open(node, O_RDONLY | O_NONBLOCK); | |
if(fd >= 0){ | |
char deviceNameBuffer[256] = "Unknown Device"; | |
ioctl(fd, EVIOCGNAME(sizeof(deviceNameBuffer)), deviceNameBuffer); | |
ofLogNotice("ofAppEGLWindow") << "addInput(): input device name = " << deviceNameBuffer; | |
if(isMouse){ | |
struct input_absinfo mabsx; | |
if(ioctl(fd, EVIOCGABS(0), &mabsx) < 0){ | |
ofLogError("ofAppEGLWindow") << "ioctl GABS failed"; | |
} else { | |
mouseAbsXMin = mabsx.minimum; | |
mouseAbsXMax = mabsx.maximum; | |
ofLogNotice("ofAppEGLWindow") << "mouse x axis min, max: " << mouseAbsXMin << ", " << mouseAbsXMax; | |
} | |
// Do that for the y axis. EVIOCGABS(1): 1 stands for y axis. | |
struct input_absinfo mabsy; | |
if(ioctl(fd, EVIOCGABS(1), &mabsy) < 0){ | |
ofLogError("ofAppEGLWindow") << "ioctl GABS failed"; | |
}else{ | |
mouseAbsYMin = mabsy.minimum; | |
mouseAbsYMax = mabsy.maximum; | |
ofLogNotice("ofAppEGLWindow") << "mouse y axis min, max: " << mouseAbsYMin << ", " << mouseAbsYMax; | |
} | |
} | |
inputDevices[node] = fd; | |
} | |
} | |
void ofAppEGLWindow::removeInput(const char * node){ | |
if(node == NULL) | |
return; | |
device::iterator iter = inputDevices.find(node); | |
if(iter != inputDevices.end()){ | |
::close(iter->second); | |
inputDevices.erase(iter); | |
} | |
} | |
void ofAppEGLWindow::printInput(){ | |
ofLogNotice("--- Input Device List ---"); | |
for(device::iterator iter = inputDevices.begin(); iter != inputDevices.end(); iter++){ | |
ofLogNotice() << " - " << iter->first; | |
} | |
ofLogNotice("-------------------------"); | |
} | |
void ofAppEGLWindow::destroyNativeInput(){ | |
ofLogNotice("ofAppEGLWindow") << "destroyNativeInput()"; | |
for(device::iterator iter = inputDevices.begin(); iter != inputDevices.end(); iter++){ | |
if(iter->second >= 0){ | |
::close(iter->second); | |
} | |
} | |
inputDevices.clear(); | |
tcsetattr (STDIN_FILENO, TCSAFLUSH, &ots); | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::processInput(int fd, const char * node){ | |
// http://www.diegm.uniud.it/loghi/CE2/kbd.pdf | |
// http://cgit.freedesktop.org/~whot/evtest/plain/evtest.c | |
// https://strcpy.net/b/archives/2010/11/17/abusing_the_linux_input_subsystem/index.html | |
static ofKeyEventArgs keyEvent; | |
static ofMouseEventArgs mouseEvent; | |
struct input_event ev; | |
char key = 0; | |
bool pushKeyEvent = false; | |
bool pushMouseEvent = false; | |
bool axisValuePending = false; | |
int nBytesRead = read(fd, &ev, sizeof(struct input_event)); | |
while(nBytesRead >= 0){ | |
// ofLogNotice("Input event ") << "dev: " << node << " ,type: " << ev.type << " ,code: " << ev.code << " ,value: " << ev.value << " ,pending: " << axisValuePending; | |
if(ev.type == EV_KEY){ | |
if(ev.code == BTN_LEFT){ | |
if(ev.value == 0){ // release | |
mouseEvent.button = OF_MOUSE_BUTTON_LEFT; | |
mouseEvent.type = ofMouseEventArgs::Released; | |
mb.mouseButtonState &= ~MOUSE_BUTTON_LEFT_MASK; | |
pushMouseEvent = true; | |
}else if(ev.value == 1){ // press | |
mb.mouseButtonState |= MOUSE_BUTTON_LEFT_MASK; | |
mouseEvent.type = ofMouseEventArgs::Pressed; | |
mouseEvent.button = OF_MOUSE_BUTTON_LEFT; | |
pushMouseEvent = true; | |
} | |
}else if(ev.code == BTN_MIDDLE){ | |
if(ev.value == 0){ // release | |
mouseEvent.button = OF_MOUSE_BUTTON_MIDDLE; | |
mouseEvent.type = ofMouseEventArgs::Released; | |
mb.mouseButtonState &= ~MOUSE_BUTTON_MIDDLE_MASK; | |
pushMouseEvent = true; | |
}else if(ev.value == 1){ // press | |
mb.mouseButtonState |= MOUSE_BUTTON_MIDDLE_MASK; | |
mouseEvent.type = ofMouseEventArgs::Pressed; | |
mouseEvent.button = OF_MOUSE_BUTTON_MIDDLE; | |
pushMouseEvent = true; | |
} | |
}else if(ev.code == BTN_RIGHT){ | |
if(ev.value == 0){ // release | |
mouseEvent.button = OF_MOUSE_BUTTON_RIGHT; | |
mouseEvent.type = ofMouseEventArgs::Released; | |
mb.mouseButtonState &= ~MOUSE_BUTTON_RIGHT_MASK; | |
pushMouseEvent = true; | |
}else if(ev.value == 1){ // press | |
mb.mouseButtonState |= MOUSE_BUTTON_RIGHT_MASK; | |
mouseEvent.type = ofMouseEventArgs::Pressed; | |
mouseEvent.button = OF_MOUSE_BUTTON_RIGHT; | |
pushMouseEvent = true; | |
} | |
}else{ | |
if(ev.value == 0){ | |
// key released | |
keyEvent.type = ofKeyEventArgs::Released; | |
}else if(ev.value == 1){ | |
// key pressed | |
keyEvent.type = ofKeyEventArgs::Pressed; | |
}else if(ev.value == 2){ | |
// key repeated | |
keyEvent.type = ofKeyEventArgs::Pressed; | |
}else{ | |
// unknown ev.value | |
} | |
switch (ev.code) { | |
case KEY_RIGHTSHIFT: | |
case KEY_LEFTSHIFT: | |
kb.shiftPressed = ev.value; | |
break; | |
case KEY_RIGHTCTRL: | |
case KEY_LEFTCTRL: | |
break; | |
case KEY_CAPSLOCK: | |
if (ev.value == 1) { | |
if (kb.capsLocked) { | |
kb.capsLocked = 0; | |
} else { | |
kb.capsLocked = 1; | |
} | |
} | |
break; | |
case KEY_ESC: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_ESC; | |
break; | |
case KEY_BACKSPACE: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_BACKSPACE; | |
break; | |
case KEY_DELETE: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_DEL; | |
break; | |
case KEY_F1: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F1; | |
break; | |
case KEY_F2: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F2; | |
break; | |
case KEY_F3: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F3; | |
break; | |
case KEY_F4: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F4; | |
break; | |
case KEY_F5: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F5; | |
break; | |
case KEY_F6: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F6; | |
break; | |
case KEY_F7: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F7; | |
break; | |
case KEY_F8: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F8; | |
break; | |
case KEY_F9: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F9; | |
break; | |
case KEY_F10: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F10; | |
break; | |
case KEY_F11: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F11; | |
break; | |
case KEY_F12: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_F12; | |
break; | |
case KEY_LEFT: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_LEFT; | |
break; | |
case KEY_UP: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_UP; | |
break; | |
case KEY_RIGHT: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_RIGHT; | |
break; | |
case KEY_DOWN: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_DOWN; | |
break; | |
case KEY_PAGEUP: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_PAGE_UP; | |
break; | |
case KEY_PAGEDOWN: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_PAGE_DOWN; | |
break; | |
case KEY_HOME: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_HOME; | |
break; | |
case KEY_END: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_END; | |
break; | |
case KEY_INSERT: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_INSERT; | |
break; | |
case KEY_ENTER: | |
case KEY_KPENTER: | |
pushKeyEvent = true; | |
keyEvent.key = OF_KEY_RETURN; | |
break; | |
default: | |
// VERY RUDIMENTARY KEY MAPPING WITH MAPS ABOVE | |
if(ev.code < sizeof(lowercase_map)){ | |
if(kb.shiftPressed){ | |
key = uppercase_map[ev.code]; | |
if(kb.capsLocked) keyEvent.key = tolower(key); | |
keyEvent.key = key; | |
pushKeyEvent = true; | |
}else{ | |
key = lowercase_map[ev.code]; | |
if(kb.capsLocked) key = toupper(key); | |
keyEvent.key = key; | |
pushKeyEvent = true; | |
} | |
}else{ | |
ofLogNotice("ofAppEGLWindow") << "readKeyboardEvents(): input_event.code is outside of our small range"; | |
} | |
} | |
} | |
}else if(ev.type == EV_REL || ev.type == EV_ABS){ | |
int axis = ev.code; | |
int amount = ev.value; | |
switch(axis) { | |
case 0: | |
if(ev.type == EV_REL){ | |
mouseEvent.x += amount * mouseScaleX; | |
}else{ | |
mouseEvent.x = amount * (float)currentWindowRect.width / (float)mouseAbsXMax; | |
} | |
mouseEvent.x = ofClamp(mouseEvent.x, 0, currentWindowRect.width); | |
axisValuePending = true; | |
break; | |
case 1: | |
if(ev.type == EV_REL){ | |
mouseEvent.y += amount * mouseScaleY; | |
}else{ | |
mouseEvent.y = amount * (float)currentWindowRect.height / (float)mouseAbsYMax; | |
} | |
mouseEvent.y = ofClamp(mouseEvent.y, 0, currentWindowRect.height); | |
axisValuePending = true; | |
break; | |
default: | |
ofLogNotice("ofAppEGLWindow") << "readMouseEvents(): unknown mouse axis (perhaps it's the scroll wheel?)"; | |
break; | |
} | |
}else if(ev.type == EV_MSC){ | |
}else if(ev.type == EV_SYN){ | |
// EV_SYN Used as markers to separate events. Events may be | |
// separated in time or in space, such as with the multitouch protocol. | |
// EV_SYN events are sent when axis value (one or a pair) are changed | |
if(axisValuePending){ | |
// ofLog() << "EV_SYN pending end : " << iter->second; | |
// TODO, this state doesn't make as much sense when the mouse is not dragging | |
if(mb.mouseButtonState > 0){ | |
// dragging (what if dragging w/ more than one button?) | |
mouseEvent.type = ofMouseEventArgs::Dragged; | |
}else{ | |
// just moving | |
mouseEvent.type = ofMouseEventArgs::Moved; | |
} | |
mouseEvent.button = mb.mouseButtonState; | |
pushMouseEvent = true; | |
axisValuePending = false; | |
} | |
//ofLogVerbose("ofAppEGLWindow") << "readMouseEvents(): EV_SYN"; | |
} | |
if(pushKeyEvent){ | |
lock(); | |
keyEvents.push(keyEvent); | |
unlock(); | |
pushKeyEvent = false; | |
} | |
if(pushMouseEvent){ | |
// lock the thread for a moment while we copy the data | |
lock(); | |
mouseEvents.push(mouseEvent); | |
unlock(); | |
pushMouseEvent = false; | |
} | |
nBytesRead = read(fd, &ev,sizeof(struct input_event)); | |
} | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::readNativeUDevEvents() { | |
// look for devices being attatched / detatched | |
fd_set fds; | |
struct timeval tv; | |
int ret; | |
struct udev_device *dev; | |
bool is_mouse = false; | |
FD_ZERO(&fds); | |
FD_SET(udev_fd, &fds); | |
tv.tv_sec = 0; | |
tv.tv_usec = 0; | |
ret = select(udev_fd+1, &fds, NULL, NULL, &tv); | |
/* Check if our file descriptor has received data. */ | |
if(ret > 0 && FD_ISSET(udev_fd, &fds)){ | |
/* Make the call to receive the device. | |
select() ensured that this will not block. */ | |
dev = udev_monitor_receive_device(mon); | |
if(dev){ | |
const char * devnode = udev_device_get_devnode(dev); | |
const char * devpath = udev_device_get_devpath(dev); | |
const char * sysname = udev_device_get_sysname(dev); | |
const char * subsystem = udev_device_get_subsystem(dev); | |
const char * devtype = udev_device_get_devtype(dev); | |
dev_t devnum = udev_device_get_devnum(dev); | |
const char * driver = udev_device_get_driver(dev); | |
const char * action = udev_device_get_action(dev); | |
const char * prop_keyboard = udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD"); | |
const char * prop_mouse = udev_device_get_property_value(dev, "ID_INPUT_MOUSE"); | |
ofLogNotice("readNativeUDevEvents") << "udev monitor receive devixe"; | |
ofLogNotice() << " - node: " << devnode; | |
ofLogNotice() << " - devpath: " << devpath; | |
ofLogNotice() << " - sysname: " << sysname; | |
ofLogNotice() << " - subsystem: " << subsystem; | |
ofLogNotice() << " - devtype: " << devtype; | |
ofLogNotice() << " - devnum: " << devnum; | |
ofLogNotice() << " - driver: " << driver; | |
ofLogNotice() << " - action: " << action; | |
ofLogNotice() << " - ID_INPUT_KEYBOARD: " << prop_keyboard; | |
ofLogNotice() << " - ID_INPUT_MOUSE: " << prop_mouse; | |
if(prop_mouse){ | |
is_mouse = true; | |
} | |
if(devnode && (prop_keyboard || prop_mouse) && string_begins_with(sysname, "event")){ | |
if(strcmp(action, "add") == 0){ | |
addInput(devnode, is_mouse); | |
}else if(strcmp(action, "remove") == 0){ | |
removeInput(devnode); | |
} | |
} | |
udev_device_unref(dev); | |
}else{ | |
ofLogNotice("ofAppEGLWindow") << "readNativeUDevEvents(): device returned by receive_device() is NULL"; | |
} | |
} | |
} | |
void ofAppEGLWindow::readNativeInputEvents(){ | |
for(device::iterator iter = inputDevices.begin(); iter != inputDevices.end(); iter++){ | |
processInput(iter->second, iter->first.c_str()); | |
} | |
} | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::initRPiNative() { | |
bcm_host_init(); | |
memset(&dispman_native_window, 0x0, sizeof(EGL_DISPMANX_WINDOW_T)); | |
dispman_element = DISPMANX_NO_HANDLE; | |
dispman_display = DISPMANX_NO_HANDLE; | |
dispman_update = DISPMANX_NO_HANDLE; | |
memset(&dispman_clamp, 0x0, sizeof(DISPMANX_CLAMP_T)); | |
dispman_transform = DISPMANX_NO_ROTATE; | |
memset(&dispman_alpha, 0x0, sizeof(VC_DISPMANX_ALPHA_T)); // zero dispman_alpha | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::exitRPiNative() { | |
bcm_host_deinit(); | |
} | |
//------------------------------------------------------------ | |
bool ofAppEGLWindow::createRPiNativeWindow(const ofRectangle& requestedWindowRect){ | |
ofRectangle screenRect = getScreenRect(); | |
// make sure our requested window rectangle does not exceed the native | |
// screen size, or start outside of it. | |
ofRectangle windowRect = screenRect.getIntersection(requestedWindowRect); | |
ofLogNotice("ofAppEGLWindow") << "setupRPiNativeWindow(): screenRect: " << screenRect.width << "x" << screenRect.height; | |
ofLogNotice("ofAppEGLWindow") << "setupRPiNativeWindow(): windowRect: " << windowRect.width << "x" << windowRect.height; | |
////////////////////////// | |
VC_RECT_T dst_rect; | |
dst_rect.x = (int32_t)windowRect.x; | |
dst_rect.y = (int32_t)windowRect.y; | |
dst_rect.width = (int32_t)windowRect.width; | |
dst_rect.height = (int32_t)windowRect.height; | |
VC_RECT_T src_rect; | |
src_rect.x = 0; | |
src_rect.y = 0; | |
src_rect.width = dst_rect.width << 16; | |
src_rect.height = dst_rect.height << 16; | |
memset(&dispman_alpha, 0x0, sizeof(VC_DISPMANX_ALPHA_T)); // zero dispman_alpha | |
dispman_alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; | |
dispman_alpha.opacity = ofClamp(settings.eglWindowOpacity,0,255); | |
dispman_alpha.mask = 0; | |
memset(&dispman_clamp, 0x0, sizeof(DISPMANX_CLAMP_T)); | |
// there are other values for dispman_transform, but they do not seem to have an effect | |
dispman_transform = DISPMANX_NO_ROTATE; | |
// get the zero display | |
dispman_display = vc_dispmanx_display_open(settings.screenNum); | |
// begin the display manager interaction | |
dispman_update = vc_dispmanx_update_start( 0 ); | |
// add a "display manager element" with our parameters so | |
// that it can fill in the structures. we will pass this | |
// filled dispman_element to our native window, which will | |
// be used to construct the EGL surface, etc. | |
dispman_element = vc_dispmanx_element_add ( dispman_update, | |
dispman_display, | |
settings.layer, // layer | |
&dst_rect, // dst rect | |
(DISPMANX_RESOURCE_HANDLE_T)0, // src | |
&src_rect, // src rect | |
DISPMANX_PROTECTION_NONE, // ? | |
&dispman_alpha, // alpha | |
&dispman_clamp, // clamp | |
dispman_transform // transform | |
); | |
if(dispman_element == DISPMANX_NO_HANDLE) { | |
ofLogError("ofAppEGLWindow") << "setupRPiNativeWindow(): dispman_element == DISPMANX_NO_HANDLE"; | |
return false; | |
} else if(dispman_element == (unsigned)DISPMANX_INVALID) { | |
ofLogError("ofAppEGLWindow") << "setupRPiNativeWindow(): dispman_element == DISPMANX_INVALID"; | |
return false; | |
} | |
// set dispman_native_window to zero | |
memset(&dispman_native_window, 0x0, sizeof(EGL_DISPMANX_WINDOW_T)); | |
dispman_native_window.element = dispman_element; | |
dispman_native_window.width = (int32_t)windowRect.width; | |
dispman_native_window.height = (int32_t)windowRect.height; | |
// set background to black (not required) | |
vc_dispmanx_display_set_background(dispman_update, dispman_display, 0x00, 0x00, 0x00); | |
// finished with display manager update, so sync | |
vc_dispmanx_update_submit_sync( dispman_update ); | |
currentWindowRect = windowRect; | |
return true; | |
} | |
#endif | |
//------------------------------------------------------------ | |
// X11 BELOW | |
//------------------------------------------------------------ | |
bool ofAppEGLWindow::createX11NativeWindow(const ofRectangle& requestedWindowRect){ | |
// X11 variables | |
x11Window = 0; | |
x11Display = 0; | |
x11ScreenNum = 0; // TODO: settings.screenNum? | |
x11Screen = 0; | |
XVisualInfo* x11Visual = 0; // TODO does this need to be deleted? | |
Colormap x11Colormap = 0; | |
/* | |
Step 0 - Create a NativeWindowType that we can use it for OpenGL ES output | |
*/ | |
Window sRootWindow; | |
XSetWindowAttributes sWA; | |
unsigned int ui32Mask; | |
int i32Depth; | |
//ofRectangle screenRect = getScreenRect(); | |
// make sure our requested window rectangle does not exceed the native | |
// screen size, or start outside of it. | |
ofRectangle windowRect = requestedWindowRect.getStandardized();//screenRect.getIntersection(requestedWindowRect); | |
// Initializes the display and screen | |
x11Display = XOpenDisplay( 0 ); | |
if (!x11Display) { | |
ofLogError("ofAppEGLWindow") << "unable to open X display"; | |
return false; | |
} | |
x11ScreenNum = XDefaultScreen( x11Display ); | |
x11Screen = XDefaultScreenOfDisplay(x11Display); | |
// Gets the window parameters | |
sRootWindow = RootWindow(x11Display, x11ScreenNum); | |
i32Depth = DefaultDepth(x11Display, x11ScreenNum); | |
x11Visual = new XVisualInfo(); | |
XMatchVisualInfo( x11Display, | |
x11ScreenNum, | |
i32Depth, | |
TrueColor, | |
x11Visual); | |
if (!x11Visual) { | |
ofLogError("ofAppEGLWindow") << "unable to acquire XVisualInfo"; | |
return false; | |
} | |
x11Colormap = XCreateColormap( x11Display, sRootWindow, x11Visual->visual, AllocNone ); | |
delete x11Visual; | |
// set the colormap window attribuet | |
sWA.colormap = x11Colormap; | |
// Add to these for handling other events | |
sWA.event_mask = 0; | |
sWA.event_mask |= StructureNotifyMask; | |
sWA.event_mask |= ExposureMask; | |
sWA.event_mask |= ButtonPressMask; | |
sWA.event_mask |= ButtonReleaseMask; | |
sWA.event_mask |= PointerMotionMask; | |
sWA.event_mask |= KeyPressMask; | |
sWA.event_mask |= KeyReleaseMask; | |
// setup background pixel attributes | |
ui32Mask = 0; | |
ui32Mask |= CWBackPixel; | |
ui32Mask |= CWBorderPixel; | |
ui32Mask |= CWEventMask; | |
ui32Mask |= CWColormap; | |
// Creates the X11 window | |
x11Window = XCreateWindow(x11Display, // Specifies the connection to the X server. | |
sRootWindow, // Specifies the parent window. | |
(int)windowRect.x, (int)windowRect.y, // Specify the x and y coordinates, | |
// which are the top-left outside corner | |
// of the window's borders and are relative | |
// to the inside of the parent window's borders. | |
(unsigned int)windowRect.width, (unsigned int)windowRect.height, // Specify the width and height, which are the | |
// created window's inside dimensions and do | |
// not include the created window's borders. | |
0, // Specifies the width of the created | |
// window's border in pixels. | |
CopyFromParent, // Specifies the window's depth. | |
// A depth of CopyFromParent means | |
// the depth is taken from the parent. | |
InputOutput, // Specifies the created window's class. | |
// You can pass InputOutput, InputOnly, | |
// or CopyFromParent. A class of CopyFromParent | |
// means the class is taken from the parent. | |
CopyFromParent, // Specifies the visual type. | |
// A visual of CopyFromParent means the visual type | |
// is taken from the parent. | |
ui32Mask, // Specifies which window attributes are | |
// defined in the attributes argument. This mask is | |
// the bitwise inclusive OR of the valid attribute | |
// mask bits. If valuemask is zero, the attributes | |
// are ignored and are not referenced. | |
&sWA //Specifies the background pixel value of the window. | |
); | |
XMapWindow(x11Display, x11Window); | |
XFlush(x11Display); | |
// check success? | |
currentWindowRect = windowRect; | |
return true; | |
} | |
//------------------------------------------------------------ | |
static KeySym KeyCodeToKeySym(Display * display, KeyCode keycode, unsigned int event_mask) { | |
KeySym keysym = NoSymbol; | |
//Get the map | |
XkbDescPtr keyboard_map = XkbGetMap(display, XkbAllClientInfoMask, XkbUseCoreKbd); | |
if (keyboard_map) { | |
//What is diff between XkbKeyGroupInfo and XkbKeyNumGroups? | |
unsigned char info = XkbKeyGroupInfo(keyboard_map, keycode); | |
unsigned int num_groups = XkbKeyNumGroups(keyboard_map, keycode); | |
//Get the group | |
unsigned int group = 0x00; | |
switch (XkbOutOfRangeGroupAction(info)) { | |
case XkbRedirectIntoRange: | |
/* If the RedirectIntoRange flag is set, the four least significant | |
* bits of the groups wrap control specify the index of a group to | |
* which all illegal groups correspond. If the specified group is | |
* also out of range, all illegal groups map to Group1. | |
*/ | |
group = XkbOutOfRangeGroupInfo(info); | |
if (group >= num_groups) { | |
group = 0; | |
} | |
break; | |
case XkbClampIntoRange: | |
/* If the ClampIntoRange flag is set, out-of-range groups correspond | |
* to the nearest legal group. Effective groups larger than the | |
* highest supported group are mapped to the highest supported group; | |
* effective groups less than Group1 are mapped to Group1 . For | |
* example, a key with two groups of symbols uses Group2 type and | |
* symbols if the global effective group is either Group3 or Group4. | |
*/ | |
group = num_groups - 1; | |
break; | |
case XkbWrapIntoRange: | |
/* If neither flag is set, group is wrapped into range using integer | |
* modulus. For example, a key with two groups of symbols for which | |
* groups wrap uses Group1 symbols if the global effective group is | |
* Group3 or Group2 symbols if the global effective group is Group4. | |
*/ | |
default: | |
if (num_groups != 0) { | |
group %= num_groups; | |
} | |
break; | |
} | |
XkbKeyTypePtr key_type = XkbKeyKeyType(keyboard_map, keycode, group); | |
unsigned int active_mods = event_mask & key_type->mods.mask; | |
int i, level = 0; | |
for (i = 0; i < key_type->map_count; i++) { | |
if (key_type->map[i].active && key_type->map[i].mods.mask == active_mods) { | |
level = key_type->map[i].level; | |
} | |
} | |
keysym = XkbKeySymEntry(keyboard_map, keycode, level, group); | |
XkbFreeClientMap(keyboard_map, XkbAllClientInfoMask, true); | |
} | |
return keysym; | |
} | |
//------------------------------------------------------------ | |
void ofAppEGLWindow::handleX11Event(const XEvent& event){ | |
ofMouseEventArgs mouseEvent; | |
ofKeyEventArgs keyEvent; | |
switch (event.type){ | |
case KeyPress: | |
case KeyRelease: | |
{ | |
KeySym key = KeyCodeToKeySym(instance->x11Display,event.xkey.keycode,event.xkey.state); | |
keyEvent.key = key; | |
if (event.type == KeyPress) { | |
keyEvent.type = ofKeyEventArgs::Pressed; | |
if(key == 65307){ | |
keyEvent.key = OF_KEY_ESC; | |
} | |
} else if (event.type == KeyRelease){ | |
keyEvent.type = ofKeyEventArgs::Released; | |
} | |
instance->coreEvents.notifyKeyEvent(keyEvent); | |
} | |
break; | |
case ButtonPress: | |
case ButtonRelease: | |
mouseEvent.x = static_cast<float>(event.xbutton.x); | |
mouseEvent.y = static_cast<float>(event.xbutton.y); | |
mouseEvent.button = event.xbutton.button; | |
if (event.type == ButtonPress){ | |
mouseEvent.type = ofMouseEventArgs::Pressed; | |
} else { | |
mouseEvent.type = ofMouseEventArgs::Released; | |
} | |
instance->coreEvents.notifyMouseEvent(mouseEvent); | |
break; | |
case MotionNotify: | |
//cout << "motion notify" << endl; | |
mouseEvent.x = static_cast<float>(event.xmotion.x); | |
mouseEvent.y = static_cast<float>(event.xmotion.y); | |
mouseEvent.button = event.xbutton.button; | |
if(ofGetMousePressed()) { | |
mouseEvent.type = ofMouseEventArgs::Dragged; | |
} else { | |
mouseEvent.type = ofMouseEventArgs::Moved; | |
} | |
instance->coreEvents.notifyMouseEvent(mouseEvent); | |
break; | |
case ConfigureNotify: | |
instance->currentWindowRect.x = event.xconfigure.x; | |
instance->currentWindowRect.y = event.xconfigure.y; | |
instance->currentWindowRect.width = event.xconfigure.width; | |
instance->currentWindowRect.height = event.xconfigure.height; | |
instance->nonFullscreenWindowRect = instance->currentWindowRect; | |
instance->coreEvents.notifyWindowResized(event.xconfigure.width,event.xconfigure.height); | |
break; | |
/*case ClientMessage:{ | |
if (event.xclient.message_type == wmProtocols_ && | |
event.xclient.format == 32 && | |
event.xclient.data.l[0] == (long) wmDeleteWindow_) | |
{ | |
if (listener()) | |
{ | |
if (listener()->onClose(wrapper() ? *wrapper() : *(WindowInterface*)this)) | |
isShuttingDown_ = true; | |
} | |
else | |
{ | |
isShuttingDown_ = true; | |
} | |
} | |
break; | |
}*/ | |
} | |
} | |
#else | |
#include <xf86drm.h> | |
#include <xf86drmMode.h> | |
#include <drm_fourcc.h> | |
#include <gbm.h> | |
//#include <GLES2/gl2.h> | |
#include <GLES2/gl2ext.h> | |
#include <EGL/egl.h> | |
#include <EGL/eglext.h> | |
#ifndef EGL_KHR_platform_gbm | |
#define EGL_KHR_platform_gbm 1 | |
#define EGL_PLATFORM_GBM_KHR 0x31D7 | |
#endif /* EGL_KHR_platform_gbm */ | |
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC) (EGLenum platform, void *native_display, const EGLint *attrib_list); | |
class CRTC | |
{ | |
public: | |
drmModeCrtc *crtc; | |
drmModeObjectProperties *props; | |
drmModePropertyRes **props_info; | |
CRTC() | |
{ | |
crtc = NULL; | |
props = NULL; | |
props_info = NULL; | |
} | |
}; | |
class Connector { | |
public: | |
drmModeConnector *connector; | |
drmModeObjectProperties *props; | |
drmModePropertyRes **props_info; | |
Connector() | |
{ | |
connector = NULL; | |
props = NULL; | |
props_info = NULL; | |
} | |
}; | |
class DRM | |
{ | |
public: | |
int fd; | |
CRTC crtc; | |
Connector connector; | |
int crtc_index; | |
int kms_in_fence_fd; | |
int kms_out_fence_fd; | |
drmModeModeInfo *mode; | |
uint32_t crtc_id; | |
uint32_t connector_id; | |
int (*run)(const struct gbm *gbm, const struct egl *egl); | |
DRM() | |
{ | |
fd = 0; | |
mode = NULL; | |
} | |
}; | |
class GBM | |
{ | |
public: | |
struct gbm_device *dev; | |
struct gbm_surface *surface; | |
uint32_t format; | |
int width, height; | |
GBM() | |
{ | |
format = 0; | |
width = 0; | |
height = 0; | |
} | |
}; | |
class EGL { | |
public: | |
EGLDisplay display; | |
EGLConfig config; | |
EGLContext context; | |
EGLSurface surface; | |
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT; | |
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; | |
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; | |
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; | |
PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; | |
PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR; | |
PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR; | |
bool modifiers_supported; | |
void (*draw)(unsigned i); | |
EGL() | |
{ | |
display = NULL; | |
config = NULL; | |
context = NULL; | |
surface = NULL; | |
} | |
}; | |
static bool has_ext(const char *extension_list, const char *ext) | |
{ | |
ofLog() << "extension_list: " << extension_list; | |
ofLog() << "ext: " << ext; | |
const char *ptr = extension_list; | |
int len = strlen(ext); | |
if (ptr == NULL || *ptr == '\0') | |
return false; | |
while (true) { | |
ptr = strstr(ptr, ext); | |
if (!ptr) | |
return false; | |
if (ptr[len] == ' ' || ptr[len] == '\0') | |
return true; | |
ptr += len; | |
} | |
} | |
static int | |
match_config_to_visual(EGLDisplay egl_display, | |
EGLint visual_id, | |
EGLConfig *configs, | |
int count) | |
{ | |
int i; | |
for (i = 0; i < count; ++i) { | |
EGLint id; | |
if (!eglGetConfigAttrib(egl_display, | |
configs[i], EGL_NATIVE_VISUAL_ID, | |
&id)) | |
continue; | |
if (id == visual_id) | |
return i; | |
} | |
return -1; | |
} | |
static bool | |
egl_choose_config(EGLDisplay egl_display, const EGLint *attribs, | |
EGLint visual_id, EGLConfig *config_out) | |
{ | |
EGLint count = 0; | |
EGLint matched = 0; | |
int config_index = -1; | |
if (!eglGetConfigs(egl_display, NULL, 0, &count) || count < 1) { | |
printf("No EGL configs to choose from.\n"); | |
return false; | |
} | |
EGLConfig configs[count]; | |
if (!eglChooseConfig(egl_display, attribs, configs, | |
count, &matched) || !matched) { | |
printf("No EGL configs with appropriate attributes.\n"); | |
goto out; | |
} | |
if (!visual_id) | |
config_index = 0; | |
if (config_index == -1) | |
config_index = match_config_to_visual(egl_display, | |
visual_id, | |
configs, | |
matched); | |
if (config_index != -1) | |
*config_out = configs[config_index]; | |
out: | |
if (config_index == -1) | |
return false; | |
return true; | |
} | |
#define CASE_STR(x,y) case x: str = y; break | |
static string eglErrorString(EGLint err) { | |
string str; | |
switch (err) { | |
CASE_STR(EGL_SUCCESS, "EGL_SUCCESS"); | |
CASE_STR(EGL_NOT_INITIALIZED, "EGL_NOT_INITIALIZED"); | |
CASE_STR(EGL_BAD_ACCESS, "EGL_BAD_ACCESS"); | |
CASE_STR(EGL_BAD_ALLOC, "EGL_BAD_ACCESS"); | |
CASE_STR(EGL_BAD_ATTRIBUTE, "EGL_BAD_ATTRIBUTE"); | |
CASE_STR(EGL_BAD_CONTEXT, "EGL_BAD_CONTEXT"); | |
CASE_STR(EGL_BAD_CONFIG, "EGL_BAD_CONFIG"); | |
CASE_STR(EGL_BAD_CURRENT_SURFACE, "EGL_BAD_CURRENT_SURFACE"); | |
CASE_STR(EGL_BAD_DISPLAY, "EGL_BAD_DISPLAY"); | |
CASE_STR(EGL_BAD_SURFACE, "EGL_BAD_SURFACE"); | |
CASE_STR(EGL_BAD_MATCH, "EGL_BAD_MATCH"); | |
CASE_STR(EGL_BAD_PARAMETER, "EGL_BAD_PARAMETER"); | |
CASE_STR(EGL_BAD_NATIVE_PIXMAP, "EGL_BAD_NATIVE_PIXMAP"); | |
CASE_STR(EGL_BAD_NATIVE_WINDOW, "EGL_BAD_NATIVE_WINDOW"); | |
CASE_STR(EGL_CONTEXT_LOST, "EGL_CONTEXT_LOST"); | |
default: str = "unknown error " + err; break; | |
} | |
return str; | |
} | |
/* | |
void get_proc_client(EGL* egl, const char *ext, const char *name) | |
{ | |
if (has_ext(egl_exts_client, ext)) | |
{ | |
egl->name = (void *)eglGetProcAddress(name); | |
} | |
}*/ | |
EGL init_egl(GBM& gbm, int samples) | |
{ | |
EGL egl; | |
EGLint major, minor; | |
static const EGLint context_attribs[] = { | |
EGL_CONTEXT_CLIENT_VERSION, 2, | |
EGL_NONE | |
}; | |
const EGLint config_attribs[] = { | |
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, | |
EGL_RED_SIZE, 1, | |
EGL_GREEN_SIZE, 1, | |
EGL_BLUE_SIZE, 1, | |
EGL_ALPHA_SIZE, 0, | |
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, | |
EGL_SAMPLES, samples, | |
EGL_NONE | |
}; | |
const char *egl_exts_client, *egl_exts_dpy, *gl_exts; | |
egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); | |
egl.modifiers_supported = has_ext(egl_exts_dpy, | |
"EGL_EXT_image_dma_buf_import_modifiers"); | |
egl.eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); | |
egl.eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); | |
egl.eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); | |
egl.glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); | |
egl.eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR"); | |
egl.eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR"); | |
egl.eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR"); | |
egl.display = egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm.dev, NULL); | |
if (!eglInitialize(egl.display, &major, &minor)) { | |
ofLog(OF_LOG_VERBOSE, "failed to initialize\n"); | |
} | |
egl_exts_dpy = eglQueryString(egl.display, EGL_EXTENSIONS); | |
/*get_proc_dpy(EGL_KHR_image_base, eglCreateImageKHR); | |
get_proc_dpy(EGL_KHR_image_base, eglDestroyImageKHR); | |
get_proc_dpy(EGL_KHR_fence_sync, eglCreateSyncKHR); | |
get_proc_dpy(EGL_KHR_fence_sync, eglDestroySyncKHR); | |
get_proc_dpy(EGL_KHR_fence_sync, eglWaitSyncKHR); | |
get_proc_dpy(EGL_KHR_fence_sync, eglClientWaitSyncKHR); | |
get_proc_dpy(EGL_ANDROID_native_fence_sync, eglDupNativeFenceFDANDROID);*/ | |
egl.modifiers_supported = has_ext(egl_exts_dpy, | |
"EGL_EXT_image_dma_buf_import_modifiers"); | |
ofLog() << "egl.modifiers_supported: " << egl.modifiers_supported; | |
ofLog(OF_LOG_VERBOSE, "Using display %p with EGL version %d.%d\n", | |
egl.display, major, minor); | |
ofLog(OF_LOG_VERBOSE, "===================================\n"); | |
ofLog(OF_LOG_VERBOSE, "EGL information:\n"); | |
ofLog(OF_LOG_VERBOSE, " version: \"%s\"\n", eglQueryString(egl.display, EGL_VERSION)); | |
ofLog(OF_LOG_VERBOSE, " vendor: \"%s\"\n", eglQueryString(egl.display, EGL_VENDOR)); | |
ofLog(OF_LOG_VERBOSE, " client extensions: \"%s\"\n", egl_exts_client); | |
ofLog(OF_LOG_VERBOSE, " display extensions: \"%s\"\n", egl_exts_dpy); | |
ofLog(OF_LOG_VERBOSE, "===================================\n"); | |
if (!eglBindAPI(EGL_OPENGL_ES_API)) { | |
ofLog(OF_LOG_VERBOSE, "failed to bind api EGL_OPENGL_ES_API\n"); | |
} | |
if (!egl_choose_config(egl.display, config_attribs, gbm.format, | |
&egl.config)) { | |
ofLog(OF_LOG_VERBOSE, "failed to choose config\n"); | |
}else | |
{ | |
ofLog() << "egl_choose_config PASS"; | |
} | |
EGLint attribute_list_surface_context[] = { | |
EGL_CONTEXT_CLIENT_VERSION, 2, | |
EGL_NONE | |
}; | |
egl.context = eglCreateContext(egl.display, | |
egl.config, | |
EGL_NO_CONTEXT, | |
attribute_list_surface_context); | |
if(egl.context == EGL_NO_CONTEXT) { | |
EGLint error = eglGetError(); | |
if(error == EGL_BAD_CONFIG) { | |
ofLogError() << "error creating context: EGL_BAD_CONFIG: " << eglErrorString(error); | |
//return false; | |
} else { | |
ofLogError() << "error creating context: " << eglErrorString(error); | |
//return false; | |
} | |
}else | |
{ | |
ofLog() << "egl.context IS VALID: ", egl.context; | |
} | |
egl.surface = eglCreateWindowSurface(egl.display, egl.config, | |
(EGLNativeWindowType)gbm.surface, NULL); | |
if (egl.surface == EGL_NO_SURFACE) { | |
EGLint error = eglGetError(); | |
ofLogError() << "error creating surface: " << eglErrorString(error) << " error: " << error; | |
}else | |
{ | |
ofLog() << "egl.surface IS VALID: ", egl.surface; | |
} | |
/* connect the context to the surface */ | |
return egl; | |
} | |
int get_resources(int fd, drmModeRes **resources) | |
{ | |
*resources = drmModeGetResources(fd); | |
if (*resources == NULL) | |
return -1; | |
return 0; | |
} | |
#define MAX_DRM_DEVICES 64 | |
uint32_t find_crtc_for_encoder(const drmModeRes *resources, | |
const drmModeEncoder *encoder) { | |
int i; | |
for (i = 0; i < resources->count_crtcs; i++) { | |
/* possible_crtcs is a bitmask as described here: | |
* https://dvdhrm.wordpress.com/2012/09/13/linux-drm-mode-setting-api | |
*/ | |
const uint32_t crtc_mask = 1 << i; | |
const uint32_t crtc_id = resources->crtcs[i]; | |
if (encoder->possible_crtcs & crtc_mask) { | |
return crtc_id; | |
} | |
} | |
/* no match found */ | |
return -1; | |
} | |
uint32_t find_crtc_for_connector(DRM& drm, drmModeRes *resources, drmModeConnector *connector) { | |
int i; | |
for (i = 0; i < connector->count_encoders; i++) { | |
const uint32_t encoder_id = connector->encoders[i]; | |
drmModeEncoder *encoder = drmModeGetEncoder(drm.fd, encoder_id); | |
if (encoder) { | |
const uint32_t crtc_id = find_crtc_for_encoder(resources, encoder); | |
drmModeFreeEncoder(encoder); | |
if (crtc_id != 0) { | |
return crtc_id; | |
} | |
} | |
} | |
/* no match found */ | |
return -1; | |
} | |
int find_drm_device(drmModeRes **resources) | |
{ | |
drmDevicePtr devices[MAX_DRM_DEVICES] = { NULL }; | |
int num_devices, fd = -1; | |
num_devices = drmGetDevices2(0, devices, MAX_DRM_DEVICES); | |
if (num_devices < 0) { | |
ofLog(OF_LOG_VERBOSE, "drmGetDevices2 failed: %s\n", strerror(-num_devices)); | |
return -1; | |
} | |
ofLog() << "num_devices: " << num_devices; | |
for (int i = 0; i < num_devices; i++) { | |
drmDevicePtr device = devices[i]; | |
int ret; | |
if (!(device->available_nodes & (1 << DRM_NODE_PRIMARY))) | |
continue; | |
/* OK, it's a primary device. If we can get the | |
* drmModeResources, it means it's also a | |
* KMS-capable device. | |
*/ | |
fd = open(device->nodes[DRM_NODE_PRIMARY], O_RDWR); | |
if (fd < 0) | |
continue; | |
ret = get_resources(fd, resources); | |
if (!ret) | |
break; | |
close(fd); | |
fd = -1; | |
} | |
drmFreeDevices(devices, num_devices); | |
if (fd < 0) | |
ofLog(OF_LOG_VERBOSE, "no drm device found!\n"); | |
ofLog() << "fd: " << fd; | |
return fd; | |
} | |
int init_drm(DRM& drm, const char *device, const char *mode_str, unsigned int vrefresh) | |
{ | |
drmModeRes *resources; | |
drmModeConnector *connector = NULL; | |
drmModeEncoder *encoder = NULL; | |
int i, ret, area; | |
if (device) { | |
drm.fd = open(device, O_RDWR); | |
ret = get_resources(drm.fd, &resources); | |
if (ret < 0 && errno == EOPNOTSUPP) | |
ofLog(OF_LOG_VERBOSE, "%s does not look like a modeset device\n", device); | |
} else { | |
drm.fd = find_drm_device(&resources); | |
} | |
if (drm.fd < 0) { | |
ofLog(OF_LOG_VERBOSE, "could not open drm device\n"); | |
return -1; | |
} | |
if (!resources) { | |
ofLog(OF_LOG_VERBOSE, "drmModeGetResources failed: %s\n", strerror(errno)); | |
return -1; | |
} | |
/* find a connected connector: */ | |
for (i = 0; i < resources->count_connectors; i++) { | |
connector = drmModeGetConnector(drm.fd, resources->connectors[i]); | |
if (connector->connection == DRM_MODE_CONNECTED) { | |
/* it's connected, let's use this! */ | |
ofLog() << "FOUND CONNECTION"; | |
break; | |
} | |
drmModeFreeConnector(connector); | |
connector = NULL; | |
} | |
if (!connector) { | |
/* we could be fancy and listen for hotplug events and wait for | |
* a connector.. | |
*/ | |
ofLog(OF_LOG_VERBOSE, "no connected connector!\n"); | |
return -1; | |
} | |
/* find user requested mode: */ | |
/* | |
if (mode_str && *mode_str) { | |
for (i = 0; i < connector->count_modes; i++) { | |
drmModeModeInfo *current_mode = &connector->modes[i]; | |
if (strcmp(current_mode->name, mode_str) == 0) { | |
if (vrefresh == 0 || current_mode->vrefresh == vrefresh) { | |
drm.mode = current_mode; | |
break; | |
} | |
} | |
} | |
if (!drm.mode) | |
{ | |
ofLog(OF_LOG_VERBOSE, "requested mode not found, using default mode!\n"); | |
}else | |
{ | |
ofLog() << "FOUND drm.mode: " << drm.mode; | |
} | |
}*/ | |
/* find preferred mode or the highest resolution mode: */ | |
if (!drm.mode) { | |
for (i = 0, area = 0; i < connector->count_modes; i++) { | |
drmModeModeInfo *current_mode = &connector->modes[i]; | |
if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { | |
drm.mode = current_mode; | |
if(current_mode->hdisplay) | |
{ | |
ofLog() << "current_mode hdisplay: " << current_mode->hdisplay; | |
}else | |
{ | |
ofLog() << "NO current_mode hdisplay"; | |
} | |
break; | |
} | |
int current_area = current_mode->hdisplay * current_mode->vdisplay; | |
if (current_area > area) { | |
drm.mode = current_mode; | |
area = current_area; | |
} | |
ofLog() << "current_area: " << current_area; | |
} | |
} | |
if (!drm.mode) { | |
ofLog(OF_LOG_VERBOSE, "could not find mode!\n"); | |
return -1; | |
} | |
/* find encoder: */ | |
for (i = 0; i < resources->count_encoders; i++) { | |
encoder = drmModeGetEncoder(drm.fd, resources->encoders[i]); | |
if (encoder->encoder_id == connector->encoder_id) | |
break; | |
drmModeFreeEncoder(encoder); | |
encoder = NULL; | |
} | |
if (encoder) { | |
drm.crtc_id = encoder->crtc_id; | |
} else { | |
uint32_t crtc_id = find_crtc_for_connector(drm, resources, connector); | |
if (crtc_id == 0) { | |
ofLog(OF_LOG_VERBOSE, "no crtc found!\n"); | |
return -1; | |
}else | |
{ | |
ofLog() << "FOUND crtc_id: " << crtc_id; | |
} | |
drm.crtc_id = crtc_id; | |
} | |
for (i = 0; i < resources->count_crtcs; i++) { | |
if (resources->crtcs[i] == drm.crtc_id) { | |
drm.crtc_index = i; | |
break; | |
} | |
} | |
drmModeFreeResources(resources); | |
drm.connector_id = connector->connector_id; | |
ofLog() << "FOUND drm.connector_id: " << drm.connector_id; | |
return 0; | |
} | |
DRM init_drm_legacy(const char *device, const char *mode_str, unsigned int vrefresh) | |
{ | |
DRM drm; | |
auto ret = init_drm(drm, device, mode_str, vrefresh); | |
ofLog() << "ret: " << ret; | |
return drm; | |
} | |
GBM init_gbm(int drm_fd, int w, int h, uint32_t format, uint64_t modifier) | |
{ | |
GBM gbm; | |
ofLog() << "drm_fd: " << drm_fd; | |
gbm.dev = gbm_create_device(drm_fd); | |
gbm.format = format; | |
gbm.surface = NULL; | |
gbm.surface = gbm_surface_create_with_modifiers(gbm.dev, w, h, | |
gbm.format, | |
&modifier, 1); | |
if (!gbm.surface) | |
{ | |
if (modifier != DRM_FORMAT_MOD_LINEAR) | |
{ | |
fprintf(stderr, "Modifiers requested but support isn't available\n"); | |
}else | |
{ | |
gbm.surface = gbm_surface_create(gbm.dev, w, h, | |
gbm.format, | |
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); | |
} | |
} | |
if (!gbm.surface) { | |
ofLog(OF_LOG_VERBOSE, "failed to create gbm surface\n"); | |
}else | |
{ | |
ofLog() << "gbm SURFACE PASS"; | |
} | |
gbm.width = w; | |
gbm.height = h; | |
ofLog() << "gbm.width : " << gbm.width; | |
ofLog() << "gbm.height : " << gbm.height; | |
return gbm; | |
} | |
ofAppEGLWindow::ofAppEGLWindow() { | |
} | |
void ofAppEGLWindow::setup(const ofAppEGLWindowSettings & settings) | |
{ | |
ofLog() << "setup ofAppEGLWindowSettings"; | |
} | |
void ofAppEGLWindow::setup(const ofGLESWindowSettings & settings) | |
{ | |
ofLog() << "setup ofGLESWindowSettings"; | |
ofLog() << "DRM_DISPLAY_MODE_LEN: " << DRM_DISPLAY_MODE_LEN; | |
char mode_str[DRM_DISPLAY_MODE_LEN] = ""; | |
char *device = NULL; | |
uint32_t format = DRM_FORMAT_XRGB8888; | |
uint64_t modifier = DRM_FORMAT_MOD_LINEAR; | |
unsigned int vrefresh = 0; | |
auto drm = init_drm_legacy(device, mode_str, vrefresh); | |
if(drm.mode) | |
{ | |
ofLog() << "setup drm.mode: " << drm.mode; | |
if(drm.mode->hdisplay) | |
{ | |
ofLog() << "hdisplay: " << drm.mode->hdisplay; | |
}else | |
{ | |
ofLog() << "NO hdisplay"; | |
} | |
if(drm.mode->vdisplay) | |
{ | |
ofLog() << "vdisplay: " << drm.mode->vdisplay; | |
}else | |
{ | |
ofLog() << "NO vdisplay"; | |
} | |
} | |
if(drm.mode->hdisplay && drm.mode->vdisplay) | |
{ | |
auto gbm = init_gbm(drm.fd, drm.mode->hdisplay, drm.mode->vdisplay, format, modifier); | |
int samples = 0; | |
auto egl = init_egl(gbm, samples); | |
currentRenderer = make_shared<ofGLProgrammableRenderer>(this); | |
eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context); | |
auto gl_exts = (char *) glGetString(GL_EXTENSIONS); | |
ofLog(OF_LOG_VERBOSE, "OpenGL ES 2.x information:\n"); | |
ofLog(OF_LOG_VERBOSE, " version: \"%s\"\n", glGetString(GL_VERSION)); | |
ofLog(OF_LOG_VERBOSE, " shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); | |
ofLog(OF_LOG_VERBOSE, " vendor: \"%s\"\n", glGetString(GL_VENDOR)); | |
ofLog(OF_LOG_VERBOSE, " renderer: \"%s\"\n", glGetString(GL_RENDERER)); | |
ofLog(OF_LOG_VERBOSE, " extensions: \"%s\"\n", gl_exts); | |
ofLog(OF_LOG_VERBOSE, "===================================\n"); | |
//get_proc_gl(GL_OES_EGL_image, glEGLImageTargetTexture2DOES); | |
} | |
} | |
void ofAppEGLWindow::update() | |
{ | |
coreEvents.notifyUpdate(); | |
} | |
void ofAppEGLWindow::draw() | |
{ | |
//ofLog() << "draw"; | |
} | |
void ofAppEGLWindow::pollEvents() | |
{ | |
} | |
ofCoreEvents & ofAppEGLWindow::events(){ | |
return coreEvents; | |
} | |
ofAppEGLWindow::~ofAppEGLWindow() | |
{ | |
} | |
shared_ptr<ofBaseRenderer> & ofAppEGLWindow::renderer(){ | |
return currentRenderer; | |
} | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#define USING_DRM 1 | |
#include "ofConstants.h" | |
#include "ofAppBaseWindow.h" | |
#include "ofThread.h" | |
#include "ofImage.h" | |
#include "ofEvents.h" | |
#include "ofRectangle.h" | |
#include <queue> | |
#include <map> | |
#include <X11/Xlib.h> | |
#if !defined(USING_DRM) | |
enum ofAppEGLWindowType { | |
OF_APP_WINDOW_AUTO, | |
OF_APP_WINDOW_NATIVE, | |
OF_APP_WINDOW_X11 | |
}; | |
typedef struct _XIM * XIM; | |
typedef struct _XIC * XIC; | |
typedef unsigned long Window; | |
struct _XDisplay; | |
typedef struct _XDisplay Display; | |
typedef unsigned int EGLBoolean; | |
typedef int32_t EGLint; | |
typedef void *EGLDisplay; | |
typedef void *EGLConfig; | |
typedef void *EGLSurface; | |
typedef void *EGLContext; | |
typedef std::map<EGLint,EGLint> ofEGLAttributeList; | |
typedef std::map<EGLint,EGLint>::iterator ofEGLAttributeListIterator; | |
struct ofAppEGLWindowSettings: public ofGLESWindowSettings { | |
public: | |
ofAppEGLWindowType eglWindowPreference; ///< what window type is preferred? | |
EGLint eglWindowOpacity; ///< 0-255 window alpha value | |
ofEGLAttributeList frameBufferAttributes; | |
// surface creation | |
ofEGLAttributeList windowSurfaceAttributes; | |
ofColor initialClearColor; | |
int screenNum; | |
int layer; | |
ofAppEGLWindowSettings(); | |
ofAppEGLWindowSettings(const ofGLESWindowSettings & settings); | |
}; | |
class ofAppEGLWindow : public ofAppBaseGLESWindow, public ofThread { | |
public: | |
/// ofAppEGLWindow::Settings is currently deprecated in favor of | |
/// the ofAppEGLWindowSettings struct | |
typedef ofAppEGLWindowSettings Settings; | |
ofAppEGLWindow(); | |
virtual ~ofAppEGLWindow(); | |
static void loop(){}; | |
static bool doesLoop(){ return false; } | |
static bool allowsMultiWindow(){ return false; } | |
static bool needsPolling(){ return true; } | |
static void pollEvents(); | |
using ofAppBaseGLESWindow::setup; | |
void setup(const ofAppEGLWindowSettings & settings); | |
void setup(const ofGLESWindowSettings & settings); | |
void update(); | |
void draw(); | |
void close(); | |
void makeCurrent(); | |
void swapBuffers(); | |
void startRender(); | |
void finishRender(); | |
ofCoreEvents & events(); | |
std::shared_ptr<ofBaseRenderer> & renderer(); | |
void setThreadTimeout(long timeOut){ threadTimeout = timeOut; } | |
virtual void hideCursor(); | |
virtual void showCursor(); | |
virtual void setWindowPosition(int x, int y); | |
virtual void setWindowShape(int w, int h); | |
virtual glm::vec2 getWindowPosition(); | |
virtual glm::vec2 getWindowSize(); | |
virtual glm::vec2 getScreenSize(); | |
virtual void setOrientation(ofOrientation orientation); | |
virtual ofOrientation getOrientation(); | |
virtual bool doesHWOrientation(); | |
//this is used by ofGetWidth and now determines the window width based on orientation | |
virtual int getWidth(); | |
virtual int getHeight(); | |
virtual void setWindowTitle(std::string title); // TODO const correct | |
virtual ofWindowMode getWindowMode(); | |
virtual void setFullscreen(bool fullscreen); | |
virtual void toggleFullscreen(); | |
virtual void enableSetupScreen(); | |
virtual void disableSetupScreen(); | |
virtual void setVerticalSync(bool enabled); | |
EGLDisplay getEglDisplay() const; | |
EGLSurface getEglSurface() const; | |
EGLContext getEglContext() const; | |
#ifndef TARGET_RASPBERRY_PI_LEGACY | |
Display* getX11Display(); | |
Window getX11Window(); | |
#endif | |
EGLConfig getEglConfig() const; | |
EGLint getEglVersionMajor () const; | |
EGLint getEglVersionMinor() const; | |
protected: | |
void setWindowRect(const ofRectangle& requestedWindowRect); | |
// bool create | |
virtual void setupPeripherals(); | |
virtual ofRectangle getScreenRect(); | |
int getWindowWidth(); | |
int getWindowHeight(); | |
ofWindowMode windowMode; | |
bool bNewScreenMode; ///< \brief This indicates if a (new) window rectangle has to be adjusted. | |
int buttonInUse; ///< \brief Mouse button currently in use. | |
bool bEnableSetupScreen; ///< \brief This indicates the need/intent to draw a setup screen. | |
bool bShowCursor; ///< \brief Indicate the visibility of the (mouse) cursor. | |
std::string eglDisplayString; | |
int nFramesSinceWindowResized; ///< \brief The number of frames passed/shown since the window got resized. | |
ofOrientation orientation; | |
void threadedFunction(); | |
std::queue<ofMouseEventArgs> mouseEvents; | |
std::queue<ofKeyEventArgs> keyEvents; | |
void checkEvents(); | |
ofImage mouseCursor; | |
// TODO: getters and setters? OR automatically set based on | |
// OS or screen size? Should be changed when screen is resized? | |
float mouseScaleX; ///< \brief Amount by which to mouse movements along the X axis. | |
float mouseScaleY; ///< \brief Amount by which to mouse movements along the Y axis. | |
// float getMouseScaleX() const; | |
// void setMouseScaleX(float x); | |
// float getMouseScaleY() const; | |
// void setMouseScaleY(float y); | |
// For absolute input devices that send ABS_X and ABS_Y events, we want to store | |
// information about the min and max axis values. | |
int mouseAbsXMin; | |
int mouseAbsXMax; | |
int mouseAbsYMin; | |
int mouseAbsYMax; | |
bool hasMouse() { return mouseDetected; } | |
bool hasKeyboard() { return keyboardDetected; } | |
//------------------------------------------------------------ | |
// EGL | |
//------------------------------------------------------------ | |
bool createSurface(); | |
bool destroySurface(); | |
// bool resizeSurface(); | |
EGLDisplay eglDisplay; // EGL display connection | |
EGLSurface eglSurface; | |
EGLContext eglContext; | |
EGLConfig eglConfig; | |
EGLint eglVersionMajor; | |
EGLint eglVersionMinor; | |
//------------------------------------------------------------ | |
// PLATFORM SPECIFIC WINDOWING | |
//------------------------------------------------------------ | |
//------------------------------------------------------------ | |
// WINDOWING | |
//------------------------------------------------------------ | |
// EGL window | |
ofRectangle nonFullscreenWindowRect; // the rectangle describing the non-fullscreen window | |
ofRectangle currentWindowRect; // the rectangle describing the current device | |
bool createWindow(const ofRectangle& requestedWindowRect); | |
bool destroyWindow(); | |
bool isUsingX11; ///< \brief Indicate the use of the X Window System. | |
bool isWindowInited; ///< \brief Indicate that the window is (properly) initialized. | |
bool isSurfaceInited; ///< \brief Indicate that the surface is (properly) initialized. | |
void initNative(); | |
void exitNative(); | |
EGLNativeWindowType getNativeWindow(); | |
EGLNativeDisplayType getNativeDisplay(); | |
#ifdef TARGET_RASPBERRY_PI_LEGACY | |
void initRPiNative(); | |
void exitRPiNative(); | |
EGL_DISPMANX_WINDOW_T dispman_native_window; // rpi | |
DISPMANX_UPDATE_HANDLE_T dispman_update; | |
DISPMANX_ELEMENT_HANDLE_T dispman_element; | |
DISPMANX_DISPLAY_HANDLE_T dispman_display; | |
DISPMANX_CLAMP_T dispman_clamp; | |
DISPMANX_TRANSFORM_T dispman_transform; | |
VC_DISPMANX_ALPHA_T dispman_alpha; | |
bool createRPiNativeWindow(const ofRectangle& requestedWindowRect); | |
#else | |
// if you are not raspberry pi, you will not be able to | |
// create a window without using x11. | |
#endif | |
Display* x11Display; ///< \brief Indicate which X11 display is in use (currently). | |
Screen* x11Screen; ///< \brief Indicate which X11 screen is in use (currently). | |
Window x11Window; | |
long x11ScreenNum; ///< \brief The number of the X11 screen is in use (currently). | |
bool createX11NativeWindow(const ofRectangle& requestedWindowRect); | |
//------------------------------------------------------------ | |
// EVENTS | |
//------------------------------------------------------------ | |
void setupNativeEvents(); | |
void destroyNativeEvents(); | |
void setupNativeUDev(); | |
void destroyNativeUDev(); | |
void setupNativeInput(); | |
void destroyNativeInput(); | |
void readNativeUDevEvents(); | |
void readNativeInputEvents(); | |
void processInput(int fd, const char * node); | |
void addInput(const char * node, bool isMouse); | |
void removeInput(const char * node); | |
void printInput(); | |
static void handleX11Event(const XEvent& event); | |
private: | |
ofAppEGLWindowSettings settings; | |
int glesVersion; ///< \brief Indicate the version of OpenGL for Embedded Systems. | |
bool keyboardDetected; | |
bool mouseDetected; | |
long threadTimeout; | |
ofCoreEvents coreEvents; | |
std::shared_ptr<ofBaseRenderer> currentRenderer; | |
static ofAppEGLWindow * instance; | |
}; | |
#else | |
enum ofAppEGLWindowType { | |
OF_APP_WINDOW_AUTO, | |
OF_APP_WINDOW_NATIVE, | |
OF_APP_WINDOW_X11 | |
}; | |
typedef std::map<EGLint,EGLint> ofEGLAttributeList; | |
struct ofAppEGLWindowSettings: public ofGLESWindowSettings { | |
public: | |
ofAppEGLWindowType eglWindowPreference; ///< what window type is preferred? | |
EGLint eglWindowOpacity; ///< 0-255 window alpha value | |
ofEGLAttributeList frameBufferAttributes; | |
// surface creation | |
ofEGLAttributeList windowSurfaceAttributes; | |
ofColor initialClearColor; | |
int screenNum; | |
int layer; | |
ofAppEGLWindowSettings(); | |
ofAppEGLWindowSettings(const ofGLESWindowSettings & settings); | |
}; | |
class ofAppEGLWindow : public ofAppBaseGLESWindow, public ofThread { | |
public: | |
static bool allowsMultiWindow(){ return false; } | |
static bool doesLoop(){ return false; } | |
static void loop(){}; | |
static bool needsPolling(){ return true; } | |
static void pollEvents(); | |
ofAppEGLWindow(); | |
void update(); | |
void draw(); | |
ofCoreEvents coreEvents; | |
ofCoreEvents & events(); | |
std::shared_ptr<ofBaseRenderer> & renderer(); | |
using ofAppBaseGLESWindow::setup; | |
void setup(const ofAppEGLWindowSettings & settings); | |
void setup(const ofGLESWindowSettings & settings); | |
std::shared_ptr<ofBaseRenderer> currentRenderer; | |
virtual ~ofAppEGLWindow(); | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment