This API adds to Qt a "pure" OpenGL ES 2.0 interface with specific functionalities to aid in game development and game porting. Over a standard OpenGL ES interface in Qt this API adds:
API is fairly self-explanatory and covers mainly only OpenGL ES initialization. Other additional features are controlled via compiler defines and C++ namespace based API wrapping.
IMPORTANT NOTE: in order for the debug functionalities to work, OpenGL ES headers MUST NOT be included directly from anywhere in your code and all code calling OpenGL ES must be C++ (wrappers use namespaces). Include this header only. It includes the GL function prototypes.
This API uses C++ namespace wrapping to enable some of the additional functionality without having developers to change their GL code. GL headers coming with this API have C++ preprocessor tricks that define GL functions either in global namespace or in namespace 'qgamegl2wrapper'. When application developer includes the headers, the resulting code will call some GL functions direct to GL driver, while others go to the wrapper. Which functions get routed to wrapper, depend on enabled functionality.
In addition to defining GL functions on two different namespaces, the GL API has a default parameter in the initialization function that is used to pass in the "wrapper mode". This all is done automatically in the header files (depending on what defines are enabled) and developer doesn't have to worry about it.
In desktop builds, some of the wrapping is always enabled so that "GL ES" to "GL" emulation can be implemented. This emulation is not intended for FULL emulation, but rather enable content to be ported easily across. For this reason some behaviour may differ slightly. Especially if vendor specific extensions are in use, changes are that there is no implementation of those extensions in the emulator layer.
See <qgameopenglescommon.h> header file for more precise explanations, defines are listed here for quick reference:
Debug features include things such as checking for out-of-bounds accesses of VBO's (GL error doesn't flag them, behaviour UNDEFINED as per specification), reporting shader compiler errors, etc. Generally the intent of the debugging functionality to let developer debug GL related error cases (where error is not only GL errors, but, e.g., hitting conditions that are UNDEFINED by specification) exactly where the error happened.
Rendering surface is initialized with a 'screen configuration'. Screen configuration includes a render resolution. With this mechanism game developer can choose a resolution it wants to render into with OpenGL ES. This API automatically scales the render result to the screen. There are two reasons why you might want to do this: easy porting of existing assets (that were developed for different resolution) or increasing frame rate.
Not all platforms have support for zero-cost HW scaling, so the results may vary from platform to another as to what to expect. For platforms that have zero-cost HW scaling (e.g., Nokia MeeGo devices), the performance impact of switching to rendering in 640x360 resolution instead of the native 854x480 is pretty much equivalent of performance on a device that has otherwise exactly same SW & HW but has a display panel of 640x360. Usually this would mean roughly 1.8x increase in performance/frame rate (resolution ratio).
On devices that don't have any kind of HW scaling enablers at all, rendering is directed to an off-screen render surface (EGL pixmap or pbuffer) and a bilinear filtered scaling blit is issued to scale the graphics to the full screen window. On most HW this approach doesn't produce big performance benefits. It is provided for making the porting easier.
Since this API onws the underlying GL widget / OS window, normal Qt C++ inheritance can't be used for event handling using this API. To handle events, this API exposes the QWidget by returning it through the API (GetWidget()) so that developers can install event filter ( GetWidget()->installEventFilter(myeventfilter); ) to the widget and get access to the events delivered to the widget. See the example code for reference.
Because HW scaling is controlled by the API (and also in case of S60, the switching of the orientation modes), the best information about how the touch coordinates should be mapped is in this API itself. To facilitate mapping of the touch coordinates (expressed in current system specific orientation in full screen resolution) to the desired surface resolution. If you have, e.g., 640x360 render surface, typically you want touch coordinates between [0,0] - [639, 359]. API to call for this is "MapTouchCoordinate".
For Symbian^3 platform also, it is IMPORTANT that developer implements "symbianEventFilter" and passes the Symbian events to this API via "handleSymbianEvent". If this is not done, the graphics memory handling will not work correctly.
While it would be technically feasible to totally own the QApplication inside this API implementation and "hide" all of this OS specific stuff, decision was made to keep the existing Qt application structure in place as much as possible.
When the debugging features are enabled and the program hits an error condition a "stop-n-go" breakpoint is executed. Since there is no reliable way of inserting a SW breakpoint that the debugger would stop to without errors (debugger doesn't know about that breakpoint as it is not inserted through the UI). For making debugging possible at the exact state when the error occured, this API has a function for registering a callback function that will be called when error occurs. The typical usage pattern is to create a simple function that does nothing (perhaps has a volatile expression in it to avoid optimization) and a breakpoint is insterted to that function. Callback can be set by calling to RegisterAssert(). See also the supplied example on the usage.
Sometimes the debugger doesn't have enough information to decode the stack trace to figure out where exactly in game code the error occured. To get around this, you need to step in debugger through some assembly code to get back to the game code. Usually 5-6 presses on F10 (step over) is enough to get back to the offending code line and the surrounding state can be examined.
#include <QtGui/QApplication> #include <QtGui/QMainWindow> #include "qgameopengles2test.h" #if defined(Q_WS_X11) #include <X11/Xlib.h> #endif int main(int argc, char *argv[]) { #if defined(Q_WS_X11) XInitThreads(); #endif QCoreApplication::setAttribute(Qt::AA_NativeWindows, true); QCoreApplication::setAttribute(Qt::AA_ImmediateWidgetCreation, true); QTestApplication app(argc, argv); return app.exec(); }
#include <QTimer> #include <QtGui/QMouseEvent> #include <QtGui> #include <QtGui/QTouchEvent> #include "qgameopengles2test.h" #include "qgameopengles2.h" QGameOpenGLES2 *gles2; OurEventFilter *filter; QTimer *timer; #define SURFACE_WIDTH 640 #define SURFACE_HEIGHT 360 // INSERT breakpoint here to debug OpenGL ES error cases // // This function needs to be registered with the RegisterAssert through the // QGameOpenGLES2 API. // // NOTE: to get back to your program to find out where this call was made, // use "step over" to get to your program (stack unwind). // // See qgameopenglescommon.h for definitions on debug modes. For full debug, // add: // DEFINES += QGAMEOPENGLES_FULL_DEBUG // // To your QMAKE project file. // // NOTE: for handling the Symbian graphics memory issues automatically, enable // also QGAMEOPENGLES_SHADOWING void assertfunc(char *str) { volatile char *t = str; t[0] = t[0]; } // With QGameOpenGLES2 you need to receive the Qt events through the // EventFilter mechanism of Qt. int posx = 0; int posy = 0; bool OurEventFilter::eventFilter(QObject *obj, QEvent *event) { switch(event->type()) { case(QEvent::TouchEnd): { QTouchEvent *t = static_cast<QTouchEvent*>(event); QTouchEvent::TouchPoint p = t->touchPoints()[0]; QPoint pp = QPoint((int)(p.pos().x()), (int)(p.pos().y())); gles2->MapTouchCoordinate(pp); posx = pp.x(); posy = pp.y(); if((posx < 30) && (posy < 30)) exit(0); return true; } case(QEvent::MouseButtonPress): { QMouseEvent *tt = static_cast<QMouseEvent*>(event); QPoint p = tt->pos(); gles2->MapTouchCoordinate(p); posx = p.x(); posy = p.y(); if((posx < 30) && (posy < 30)) exit(0); return true; } default: // standard event processing return obj->eventFilter(obj, event); } } QTestApplication::QTestApplication(int &argc, char **argv, int version) : QApplication(argc, argv, version) { int i; // Create API instance gles2 = QGameOpenGLES2::Create(this); // Scan through render configurations and find the one we need int rcount = gles2->RenderConfigurationCount(); int rconf = -1; for(i=0;i<rcount;i++) { QGameOpenGLES2::RenderConfig config; gles2->RenderConfiguration(i, config); if((config.mAAQuality == QGameOpenGLES2::EANTI_ALIASING_NONE) && (config.mColorFormat == QGameOpenGLES2::ECOLOR_FORMAT_RGB565) && (config.mDepthSize == QGameOpenGLES2::EDEPTH_MINIMUM_16BITS)&& (config.mStencilSize == QGameOpenGLES2::ESTENCIL_NONE)) { rconf = i; break; } } if(rconf == -1) { qDebug("Render configuration not found!"); exit(0); } // Scan through screen configurations and find the one we need int scount = gles2->ScreenConfigurationCount(); int sconf = -1; for(i=0;i<scount;i++) { QGameOpenGLES2::ScreenConfig config; gles2->ScreenConfiguration(i, config); if((config.mHeightInPixels == SURFACE_HEIGHT) && (config.mWidthInPixels == SURFACE_WIDTH)) { sconf = i; break; } } if(sconf == -1) { qDebug("Screen configuration not found!"); exit(0); } // Initialize the API with render and screen config gles2->Initialize(rconf, sconf); // Register the function to call to for GL debug asserts gles2->RegisterAssert(assertfunc); // Install our event filter to the widget to receive events filter = new OurEventFilter(); gles2->GetWidget()->installEventFilter(filter); // Set window title gles2->GetWidget()->setWindowTitle(tr("ES test")); // Setup a idle callback to run the gameloop from. NOTE: while we are in // the idle callback, no events are processed etc. (event loop of Qt is // blocked) timer = new QTimer(this); timer->setInterval(0); timer->setSingleShot(false); timer->start(); QObject::connect(timer, SIGNAL(timeout()), this, SLOT(idletimer())); // Indicate that we want active mode to be on (we own the screen) gles2->SetActiveMode(true); } QTestApplication::~QTestApplication() { } // IMPORTANT: // // For Symbian targets we need to pass Symbian events to the QGameOpenGLES2 // API so that it can handle graphics memory usage correctly #if defined(Q_WS_S60) bool QTestApplication::symbianEventFilter( const QSymbianEvent *event) { return gles2->handleSymbianEvent(event); } #endif int frame = 0; void QTestApplication::idletimer() { // GL rendering code (frame loop) glClearColor(((float)(frame&255))/255, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); glClearColor(1.f, 1.f, 1.f, 1.f); glScissor(0,0,40,40); glClear(GL_COLOR_BUFFER_BIT); glClearColor(1.f, 0.f, 1.f, 1.f); glScissor(60,60,40,40); glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.f, 1.f, 0.f, 1.f); glScissor(posx-15,SURFACE_HEIGHT-posy-15,30,30); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); // When frame hits 250, we on purpose call glEnable(GL_TEXTURE_2D) // to set off the GL debug feature (glEnable for GL_TEXTURE_2D is // invalid for OpenGL ES 2.0). if(frame == 250) { glClearColor(2,3,4,5); glEnable(GL_TEXTURE_2D); glClearColor(4,5,6,7); } // Finally we swap buffers to get the contents to display gles2->SwapBuffers(); frame++; }
#ifndef QGAMEOPENGLES2TEST_H #define QGAMEOPENGLES2TEST_H #include <QWidget> #include <QApplication> class QTestApplication : public QApplication { Q_OBJECT public: QTestApplication(int &argc, char **argv, int = QT_VERSION); ~QTestApplication(); public slots: void idletimer(); // For Symbian targets we need to pass Symbian events to the QGameOpenGLES2 API // so that it can handle graphics memory usage correctly #if defined(Q_WS_S60) public: virtual bool symbianEventFilter( const QSymbianEvent *event); #endif }; class OurEventFilter : public QObject { Q_OBJECT protected: bool eventFilter(QObject *obj, QEvent *event); }; #endif // QGAMEOPENGLES2TEST_H
LIBS += -lQGameOpenGLES2 QT += core gui QT -= opengl LIBS += -lGLESv2 -lEGL TARGET = QGameOpenGLES2Test TEMPLATE = app SOURCES += main.cpp qgameopengles2test.cpp HEADERS += qgameopengles2test.h INCLUDEPATH += ../inc_release # DEBUGGING ENABLERS (modify if needed.. disable stuff for release builds!!) #DEFINES += QGAMEOPENGLES_FULL_DEBUG # This needs to be enabled for "auto handling" of graphics memory issue on S60 # (keeps shadow copies of textures and VBOs on the RAM) symbian: DEFINES += QGAMEOPENGLES_SHADOWING target.path = /usr/local/bin INSTALLS += target symbian { TARGET.UID3 = 0xeb48d878 TARGET.EPOCSTACKSIZE = 0x14000 TARGET.EPOCHEAPSIZE = 0x020000 0x1800000 # we need to have enough space for storing the SHADOWED textures.. }
Copyright (C) 2010-2011 Nokia Corporation Nokia Proprietary |
MeeGo 1.2 Harmattan API
|