From 41d3436ebfcb1882ec2609ef1a12e067088a9536 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 12:25:24 +0200 Subject: [PATCH] generic-pc: add openGL based framebuffer implementation Origin commit data ------------------ Branch: master Commit: https://github.com/neutrino-images/ni-libstb-hal/commit/06e5987797330ce629bc5e8d7857d409c138518d Author: Stefan Seyfried Date: 2013-05-04 (Sat, 04 May 2013) ------------------ No further description and justification available within origin commit message! ------------------ This commit was generated by Migit --- generic-pc/Makefile.am | 1 + generic-pc/glfb.cpp | 508 +++++++++++++++++++++++++++++++++++++++++ generic-pc/glfb.h | 89 ++++++++ include/glfb.h | 6 + 4 files changed, 604 insertions(+) create mode 100644 generic-pc/glfb.cpp create mode 100644 generic-pc/glfb.h create mode 100644 include/glfb.h diff --git a/generic-pc/Makefile.am b/generic-pc/Makefile.am index 0b8b4f8..a5be1e2 100644 --- a/generic-pc/Makefile.am +++ b/generic-pc/Makefile.am @@ -11,6 +11,7 @@ libgeneric_la_SOURCES = \ dmx.cpp \ video.cpp \ audio.cpp \ + glfb.cpp \ init.cpp \ playback.cpp \ pwrmngr.cpp \ diff --git a/generic-pc/glfb.cpp b/generic-pc/glfb.cpp new file mode 100644 index 0000000..e2f62b2 --- /dev/null +++ b/generic-pc/glfb.cpp @@ -0,0 +1,508 @@ +/* + Copyright 2010 Carsten Juttner + Copyright 2012,2013 Stefan Seyfried + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + openGL based framebuffer implementation + based on Carjay's neutrino-hd-dvbapi work, see + http://gitorious.org/neutrino-hd/neutrino-hd-dvbapi + + TODO: AV-Sync code is not existent yet + cleanup carjay's crazy 3D stuff :-) + video mode setting (4:3, 16:9, panscan...) +*/ + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include "glfb.h" +#include "video_lib.h" + +#include "lt_debug.h" + +#define lt_debug_c(args...) _lt_debug(HAL_DEBUG_INIT, NULL, args) +#define lt_info_c(args...) _lt_info(HAL_DEBUG_INIT, NULL, args) +#define lt_debug(args...) _lt_debug(HAL_DEBUG_INIT, this, args) +#define lt_info(args...) _lt_info(HAL_DEBUG_INIT, this, args) + + +extern cVideo *videoDecoder; + +static GLFramebuffer *gThiz = 0; /* GLUT does not allow for an arbitrary argument to the render func */ + +GLFramebuffer::GLFramebuffer(int x, int y): mReInit(true), mShutDown(false), mInitDone(false) +{ + mState.width = x; + mState.height = y; + mX = y * 16 / 9; /* hard coded 16:9 initial aspect ratio for now */ + mY = y; + + mState.blit = true; + + /* linux framebuffer compat mode */ + screeninfo.bits_per_pixel = 32; + screeninfo.xres = mState.width; + screeninfo.xres_virtual = screeninfo.xres; + screeninfo.yres = mState.height; + screeninfo.yres_virtual = screeninfo.yres; + screeninfo.blue.length = 8; + screeninfo.blue.offset = 0; + screeninfo.green.length = 8; + screeninfo.green.offset = 8; + screeninfo.red.length = 8; + screeninfo.red.offset = 16; + screeninfo.transp.length = 8; + screeninfo.transp.offset = 24; + + unlink("/tmp/neutrino.input"); + mkfifo("/tmp/neutrino.input", 0600); + input_fd = open("/tmp/neutrino.input", O_RDWR|O_CLOEXEC|O_NONBLOCK); + if (input_fd < 0) + lt_info("%s: could not open /tmp/neutrino.input FIFO: %m\n", __func__); + initKeys(); + OpenThreads::Thread::start(); + while (!mInitDone) + usleep(1); +} + +GLFramebuffer::~GLFramebuffer() +{ + mShutDown = true; + OpenThreads::Thread::join(); + if (input_fd >= 0) + close(input_fd); +} + +void GLFramebuffer::initKeys() +{ + mSpecialMap[GLUT_KEY_UP] = KEY_UP; + mSpecialMap[GLUT_KEY_DOWN] = KEY_DOWN; + mSpecialMap[GLUT_KEY_LEFT] = KEY_LEFT; + mSpecialMap[GLUT_KEY_RIGHT] = KEY_RIGHT; + + mSpecialMap[GLUT_KEY_F1] = KEY_RED; + mSpecialMap[GLUT_KEY_F2] = KEY_GREEN; + mSpecialMap[GLUT_KEY_F3] = KEY_YELLOW; + mSpecialMap[GLUT_KEY_F4] = KEY_BLUE; + + mSpecialMap[GLUT_KEY_PAGE_UP] = KEY_PAGEUP; + mSpecialMap[GLUT_KEY_PAGE_DOWN] = KEY_PAGEDOWN; + + mKeyMap[0x0d] = KEY_OK; + mKeyMap[0x1b] = KEY_EXIT; + mKeyMap['i'] = KEY_INFO; + mKeyMap['m'] = KEY_MENU; + + mKeyMap['+'] = KEY_VOLUMEUP; + mKeyMap['-'] = KEY_VOLUMEDOWN; + mKeyMap['.'] = KEY_MUTE; + mKeyMap['h'] = KEY_HELP; + mKeyMap['p'] = KEY_POWER; + + mKeyMap['0'] = KEY_0; + mKeyMap['1'] = KEY_1; + mKeyMap['2'] = KEY_2; + mKeyMap['3'] = KEY_3; + mKeyMap['4'] = KEY_4; + mKeyMap['5'] = KEY_5; + mKeyMap['6'] = KEY_6; + mKeyMap['7'] = KEY_7; + mKeyMap['8'] = KEY_8; + mKeyMap['9'] = KEY_9; +} + +void GLFramebuffer::run() +{ + setupCtx(); + setupOSDBuffer(); + mInitDone = true; /* signal that setup is finished */ + + /* init the good stuff */ + GLenum err = glewInit(); + if(err == GLEW_OK) + { + if((!GLEW_VERSION_1_5)||(!GLEW_EXT_pixel_buffer_object)||(!GLEW_ARB_texture_non_power_of_two)) + { + lt_info("GLFB: Sorry, your graphics card is not supported. " + "Needs at least OpenGL 1.5, pixel buffer objects and NPOT textures.\n"); + lt_info("incompatible graphics card: %m"); + _exit(1); /* Life is hard */ + } + else + { + gThiz = this; + glutDisplayFunc(GLFramebuffer::rendercb); + glutKeyboardFunc(GLFramebuffer::keyboardcb); + glutSpecialFunc(GLFramebuffer::specialcb); + setupGLObjects(); /* needs GLEW prototypes */ + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); + glutMainLoop(); + releaseGLObjects(); + } + } + else + lt_info("GLFB: error initializing glew: %d\n", err); + lt_info("GLFB: GL thread stopping\n"); +} + + +void GLFramebuffer::setupCtx() +{ + int argc = 1; + /* some dummy commandline for GLUT to be happy */ + char const *argv[2] = { "neutrino", 0 }; + lt_info("GLFB: GL thread starting\n"); + glutInit(&argc, const_cast(argv)); + glutInitWindowSize(mX, mY); + glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); + glutCreateWindow("Neutrino"); +} + +void GLFramebuffer::setupOSDBuffer() +{ /* the OSD buffer size can be decoupled from the actual + window size since the GL can blit-stretch with no + trouble at all, ah, the luxury of ignorance... */ + // mMutex.lock(); + if (mState.width && mState.height) + { + /* 32bit FB depth, *2 because tuxtxt uses a shadow buffer */ + int fbmem = mState.width * mState.height * 4 * 2; + mOSDBuffer.resize(fbmem); + lt_info("GLFB: OSD buffer set to %d bytes\n", fbmem); + } +} + +void GLFramebuffer::setupGLObjects() +{ + unsigned char buf[4] = { 0, 0, 0, 0 }; /* 1 black pixel */ + glGenTextures(1, &mState.osdtex); + glGenTextures(1, &mState.displaytex); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mState.width, mState.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glBindTexture(GL_TEXTURE_2D, mState.displaytex); /* we do not yet know the size so will set that inline */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glGenBuffers(1, &mState.pbo); + glGenBuffers(1, &mState.displaypbo); + + /* hack to start with black video buffer instead of white */ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(buf), buf, GL_STREAM_DRAW_ARB); + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + + +void GLFramebuffer::releaseGLObjects() +{ + glDeleteBuffers(1, &mState.pbo); + glDeleteBuffers(1, &mState.displaypbo); + glDeleteTextures(1, &mState.osdtex); + glDeleteTextures(1, &mState.displaytex); +} + + +/* static */ void GLFramebuffer::rendercb() +{ + gThiz->render(); +} + + +/* static */ void GLFramebuffer::keyboardcb(unsigned char key, int /*x*/, int /*y*/) +{ + lt_debug_c("GLFB::%s: 0x%x\n", __func__, key); + struct input_event ev; + std::map::const_iterator i = gThiz->mKeyMap.find(key); + if (i == gThiz->mKeyMap.end()) + return; + ev.code = i->second; + ev.value = 1; /* key own */ + ev.type = EV_KEY; + gettimeofday(&ev.time, NULL); + lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code); + write(gThiz->input_fd, &ev, sizeof(ev)); + ev.value = 0; /* neutrino is stupid, so push key up directly after key down */ + write(gThiz->input_fd, &ev, sizeof(ev)); +} + +/* static */ void GLFramebuffer::specialcb(int key, int /*x*/, int /*y*/) +{ + lt_debug_c("GLFB::%s: 0x%x\n", __func__, key); + struct input_event ev; + std::map::const_iterator i = gThiz->mSpecialMap.find(key); + if (i == gThiz->mSpecialMap.end()) + return; + ev.code = i->second; + ev.value = 1; + ev.type = EV_KEY; + gettimeofday(&ev.time, NULL); + lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code); + write(gThiz->input_fd, &ev, sizeof(ev)); + ev.value = 0; + write(gThiz->input_fd, &ev, sizeof(ev)); +} + +int sleep_us = 30000; + +void GLFramebuffer::render() +{ + if (!mReInit) /* for example if window is resized */ + checkReinit(); + + if(mShutDown) + glutLeaveMainLoop(); + + if (mReInit) + { + mReInit = false; + glViewport(0, 0, mX, mY); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + float aspect = static_cast(mX)/mY; + float osdaspect = 1.0/aspect; //(static_cast(mState.width)/mState.height); +// if(!mState.go3d) + { + glOrtho(aspect*-osdaspect, aspect*osdaspect, -1.0, 1.0, -1.0, 1.0 ); + glClearColor(0.0, 0.0, 0.0, 1.0); + } +#if 0 + else + { /* carjay is crazy... :-) */ + gluPerspective(45.0, static_cast(mX)/mY, 0.05, 1000.0); + glTranslatef(0.0, 0.0, -2.0); + glClearColor(0.25, 0.25, 0.25, 1.0); + } +#endif + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glDisable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + bltDisplayBuffer(); /* decoded video stream */ + if (mState.blit) { + /* only blit manually after fb->blit(), this helps to find missed blit() calls */ + mState.blit = false; + lt_debug("GLFB::%s blit!\n", __func__); + bltOSDBuffer(); /* OSD */ + } + + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +#if 0 + // cube test + if(mState.go3d) + { + glEnable(GL_DEPTH_TEST); + static float ydeg = 0.0; + glPushMatrix(); + glRotatef(ydeg, 0.0, 1.0, 0.0); + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + drawCube(0.5); + glScalef(1.01, 1.01, 1.01); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + drawCube(0.5); + glPopMatrix(); + ydeg += 0.75f; + } + else +#endif + { + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + drawSquare(1.0); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + drawSquare(1.0); + } + + glFlush(); + glutSwapBuffers(); + + GLuint err = glGetError(); + if (err != 0) + lt_info("GLFB::%s: GLError:%d 0x%04x\n", __func__, err, err); + + /* this "rate control" is crap and way too naive, feel free to improve */ + if (videoDecoder) { + int tmp, w, h; + videoDecoder->getPictureInfo(w, h, tmp); + if (tmp) { + tmp = 900000/tmp; /* 90% of the sleep time */ + if (abs(tmp - sleep_us) > 10000) { + lt_info("GLFB::%s: sleep_us %d -> %d\n", __func__, sleep_us, tmp); + sleep_us = tmp; + } + } + w = videoDecoder->buf_num; + if (w != 8) + sleep_us += 10*(8 - w); + } + usleep(sleep_us); + glutPostRedisplay(); +} + + +void GLFramebuffer::checkReinit() +{ + int x = glutGet(GLUT_WINDOW_WIDTH); + int y = glutGet(GLUT_WINDOW_HEIGHT); + if ( x != mX || y != mY ) + { + mX = x; + mY = y; + mReInit = true; + } +} + +#if 0 +void GLFramebuffer::drawCube(float size) +{ + GLfloat vertices[] = { + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f + }; + + GLubyte indices[] = { + 0, 1, 2, 3, /* front */ + 0, 3, 4, 5, /* right */ + 0, 5, 6, 1, /* top */ + 1, 6, 7, 2, /* left */ + 7, 4, 3, 2, /* bottom */ + 4, 7, 6, 5 /* back */ + }; + + GLfloat texcoords[] = { + 1.0, 0.0, // v0 + 0.0, 0.0, // v1 + 0.0, 1.0, // v2 + 1.0, 1.0, // v3 + 0.0, 1.0, // v4 + 0.0, 0.0, // v5 + 1.0, 0.0, // v6 + 1.0, 1.0 // v7 + }; + + glPushMatrix(); + glScalef(size, size, size); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texcoords); + glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, indices); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glPopMatrix(); +} +#endif + +void GLFramebuffer::drawSquare(float size) +{ + GLfloat vertices[] = { + 1.0f, 1.0f, + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f, + }; + + GLubyte indices[] = { 0, 1, 2, 3 }; + + GLfloat texcoords[] = { + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + }; + + glPushMatrix(); + glScalef(size, size, size); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texcoords); + glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, indices); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glPopMatrix(); +} + + +void GLFramebuffer::bltOSDBuffer() +{ + /* FIXME: copy each time */ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, mOSDBuffer.size(), &mOSDBuffer[0], GL_STREAM_DRAW_ARB); + + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mState.width, mState.height, GL_BGRA, GL_UNSIGNED_BYTE, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +void GLFramebuffer::bltDisplayBuffer() +{ + if (!videoDecoder) /* cannot start yet */ + return; + static bool warn = true; + cVideo::SWFramebuffer *buf = videoDecoder->getDecBuf(); + if (!buf) { + if (warn) + lt_info("GLFB::%s did not get a buffer...\n", __func__); + warn = false; + return; + } + warn = true; + int w = buf->width(), h = buf->height(); + if (w == 0 || h == 0) + return; + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, buf->size(), &(*buf)[0], GL_STREAM_DRAW_ARB); + + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +void GLFramebuffer::clear() +{ + /* clears front and back buffer */ + memset(&mOSDBuffer[0], 0, mOSDBuffer.size()); +} diff --git a/generic-pc/glfb.h b/generic-pc/glfb.h new file mode 100644 index 0000000..2549991 --- /dev/null +++ b/generic-pc/glfb.h @@ -0,0 +1,89 @@ +/* + Copyright 2010 Carsten Juttner + Copyright 2012,2013 Stefan Seyfried + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __glthread__ +#define __glthread__ +#include +#include +#include +#include +#include +#include +#include /* for screeninfo etc. */ + +class GLFramebuffer : public OpenThreads::Thread +{ +public: + GLFramebuffer(int x, int y); + ~GLFramebuffer(); + + void run(); + std::vector *getOSDBuffer() { return &mOSDBuffer; } /* pointer to OSD bounce buffer */ + + int getOSDWidth() { return mState.width; } + int getOSDHeight() { return mState.height; } + void blit() { mState.blit = true; } + + void clear(); + fb_var_screeninfo getScreenInfo() { return screeninfo; } + +private: + fb_var_screeninfo screeninfo; + int mX; /* window size */ + int mY; + bool mReInit; /* setup things for GL */ + bool mShutDown; /* if set main loop is left */ + bool mInitDone; /* condition predicate */ + // OpenThreads::Condition mInitCond; /* condition variable for init */ + // mutable OpenThreads::Mutex mMutex; /* lock our data */ + + std::vector mOSDBuffer; /* silly bounce buffer */ + + std::map mKeyMap; + std::map mSpecialMap; + int input_fd; + + void checkReinit(); /* e.g. in case window was resized */ + static void rendercb(); /* callback for GLUT */ + void render(); /* actual render function */ + static void keyboardcb(unsigned char key, int x, int y); + static void specialcb(int key, int x, int y); + + void initKeys(); /* setup key bindings for window */ + void setupCtx(); /* create the window and make the context current */ + void setupOSDBuffer(); /* create the OSD buffer */ + void setupGLObjects(); /* PBOs, textures and stuff */ + void releaseGLObjects(); + // void drawCube(float size); /* cubes are the building blocks of our society */ + void drawSquare(float size); /* do not be square */ + + struct { + int width; /* width and height, fixed for a framebuffer instance */ + int height; + GLuint osdtex; /* holds the OSD texture */ + GLuint pbo; /* PBO we use for transfer to texture */ + GLuint displaytex; /* holds the display texture */ + GLuint displaypbo; + //int go3d; + bool blit; + } mState; + + void bltOSDBuffer(); + void bltDisplayBuffer(); +}; +#endif diff --git a/include/glfb.h b/include/glfb.h new file mode 100644 index 0000000..037248a --- /dev/null +++ b/include/glfb.h @@ -0,0 +1,6 @@ +#include +#if HAVE_GENERIC_HARDWARE +#include "../generic-pc/glfb.h" +#else +#error glfb.h only works with HAVE_GENERIC_HARDWARE defined +#endif