sl@0: /* sl@0: * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). sl@0: * All rights reserved. sl@0: * This component and the accompanying materials are made available sl@0: * under the terms of the License "Eclipse Public License v1.0" sl@0: * which accompanies this distribution, and is available sl@0: * at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: * sl@0: * Initial Contributors: sl@0: * Nokia Corporation - initial contribution. sl@0: * sl@0: * Contributors: sl@0: * sl@0: * Description: EGL rendering context management functions sl@0: * sl@0: */ sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \file sl@0: * \brief EGL rendering context management functions sl@0: */ sl@0: sl@0: #if defined(M3G_NGL_CONTEXT_API) sl@0: # error This file is for the OES API only sl@0: #endif sl@0: sl@0: #include sl@0: #include "m3g_image.h" sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Private functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Queries for an EGL configuration matching given M3G format sl@0: * parameters sl@0: */ sl@0: static EGLConfig m3gQueryEGLConfig(M3Genum format, sl@0: M3Gbitmask bufferBits, sl@0: EGLint surfaceBits, sl@0: M3GNativeBitmap bitmapHandle) sl@0: { sl@0: struct { int attrib, value; } attribs[10]; sl@0: int samples; sl@0: sl@0: /* Determine color depth */ sl@0: sl@0: attribs[0].attrib = EGL_RED_SIZE; sl@0: attribs[1].attrib = EGL_GREEN_SIZE; sl@0: attribs[2].attrib = EGL_BLUE_SIZE; sl@0: attribs[3].attrib = EGL_ALPHA_SIZE; sl@0: sl@0: switch (format) { sl@0: case M3G_RGB4: sl@0: attribs[0].value = 4; sl@0: attribs[1].value = 4; sl@0: attribs[2].value = 4; sl@0: attribs[3].value = 0; sl@0: break; sl@0: case M3G_RGB565: sl@0: attribs[0].value = 5; sl@0: attribs[1].value = 6; sl@0: attribs[2].value = 5; sl@0: attribs[3].value = 0; sl@0: break; sl@0: case M3G_RGB8: sl@0: case M3G_BGR8_32: sl@0: attribs[0].value = 8; sl@0: attribs[1].value = 8; sl@0: attribs[2].value = 8; sl@0: attribs[3].value = 0; sl@0: break; sl@0: case M3G_RGBA8: sl@0: case M3G_BGRA8: sl@0: attribs[0].value = 8; sl@0: attribs[1].value = 8; sl@0: attribs[2].value = 8; sl@0: attribs[3].value = 8; sl@0: break; sl@0: default: sl@0: return NULL; sl@0: } sl@0: sl@0: /* Set up the depth buffer */ sl@0: sl@0: attribs[4].attrib = EGL_DEPTH_SIZE; sl@0: attribs[4].value = (bufferBits & M3G_DEPTH_BUFFER_BIT) ? 16 : 0; sl@0: sl@0: /* Set target surface type mask */ sl@0: sl@0: attribs[5].attrib = EGL_SURFACE_TYPE; sl@0: attribs[5].value = surfaceBits; sl@0: sl@0: sl@0: if (bitmapHandle) { sl@0: /* This attribute is matched only for pixmap targets */ sl@0: attribs[6].attrib = EGL_MATCH_NATIVE_PIXMAP; sl@0: attribs[6].value = bitmapHandle; sl@0: sl@0: /* Try to get multisampling if requested */ sl@0: sl@0: attribs[7].attrib = EGL_SAMPLE_BUFFERS; sl@0: attribs[8].attrib = EGL_SAMPLES; sl@0: sl@0: attribs[9].attrib = EGL_NONE; sl@0: } else { sl@0: /* Try to get multisampling if requested */ sl@0: sl@0: attribs[6].attrib = EGL_SAMPLE_BUFFERS; sl@0: attribs[7].attrib = EGL_SAMPLES; sl@0: sl@0: attribs[8].attrib = EGL_NONE; sl@0: } sl@0: sl@0: sl@0: /* Try 4 samples if multisampling enabled, then 2, then 1 */ sl@0: sl@0: samples = (bufferBits & M3G_MULTISAMPLE_BUFFER_BIT) ? 4 : 1; sl@0: for ( ; samples > 0; samples >>= 1) { sl@0: sl@0: if (bitmapHandle) { sl@0: if (samples > 1) { sl@0: attribs[7].value = 1; sl@0: attribs[8].value = samples; sl@0: } sl@0: else { sl@0: attribs[7].value = EGL_FALSE; sl@0: attribs[8].value = 0; sl@0: } sl@0: } else { sl@0: if (samples > 1) { sl@0: attribs[6].value = 1; sl@0: attribs[7].value = samples; sl@0: } sl@0: else { sl@0: attribs[6].value = EGL_FALSE; sl@0: attribs[7].value = 0; sl@0: } sl@0: } sl@0: sl@0: /* Get the first matching config; according to EGL sorting sl@0: * rules, this should be an accelerated one if possible */ sl@0: { sl@0: EGLConfig config; sl@0: int numConfigs; sl@0: EGLint error; sl@0: sl@0: eglChooseConfig(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: (const int *) attribs, sl@0: &config, 1, sl@0: &numConfigs); sl@0: sl@0: error = eglGetError(); sl@0: if (error != EGL_SUCCESS) { sl@0: M3G_LOG1(M3G_LOG_FATAL_ERRORS, "eglChooseConfig failed: %d\n", error); sl@0: } sl@0: sl@0: sl@0: M3G_ASSERT(error == EGL_SUCCESS); sl@0: sl@0: /* If we got a config, return that; otherwise, drop the sl@0: * number of multisampling samples and try again, or sl@0: * return NULL for no config if we already have zero sl@0: * samples */ sl@0: sl@0: if (numConfigs > 0) { sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Selected EGL config #%d\n", config); sl@0: return config; sl@0: } sl@0: sl@0: if (samples == 2) { sl@0: M3G_LOG(M3G_LOG_WARNINGS, "Warning: multisampling not available\n"); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* No matching configuration found */ sl@0: sl@0: return NULL; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Initializes EGL sl@0: */ sl@0: static void m3gInitializeEGL(void) sl@0: { sl@0: M3G_LOG(M3G_LOG_INTERFACE, "Initializing EGL\n"); sl@0: eglInitialize(eglGetDisplay(EGL_DEFAULT_DISPLAY), NULL, NULL); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Terminates EGL sl@0: */ sl@0: static void m3gTerminateEGL(void) sl@0: { sl@0: M3G_LOG(M3G_LOG_INTERFACE, "Shutting down EGL\n"); sl@0: eglTerminate(eglGetDisplay(EGL_DEFAULT_DISPLAY)); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Creates a new EGL context sl@0: */ sl@0: /*static EGLContext m3gCreateGLContext(M3Genum format, sl@0: M3Gbitmask bufferBits, sl@0: M3Gbitmask reqSurfaceBits, sl@0: EGLContext share, sl@0: M3Gbitmask *outSurfaceBits) sl@0: { sl@0: EGLContext ctx; sl@0: EGLConfig config; sl@0: sl@0: M3G_ASSERT((reqSurfaceBits & ~(EGL_PIXMAP_BIT|EGL_PBUFFER_BIT|EGL_WINDOW_BIT)) == 0); sl@0: sl@0: config = m3gQueryEGLConfig(format, bufferBits, reqSurfaceBits, NULL); sl@0: sl@0: if (!config || !eglGetConfigAttrib(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: config, sl@0: EGL_SURFACE_TYPE, sl@0: (EGLint *) outSurfaceBits)) { sl@0: return NULL; sl@0: } sl@0: sl@0: ctx = eglCreateContext(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: config, sl@0: share, sl@0: NULL); sl@0: sl@0: # if defined(M3G_DEBUG) sl@0: { sl@0: EGLint err = eglGetError(); sl@0: M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC); sl@0: } sl@0: # endif sl@0: sl@0: M3G_LOG1(M3G_LOG_OBJECTS, "New GL context 0x%08X\n", (unsigned) ctx); sl@0: return ctx; sl@0: } sl@0: */ sl@0: /*! sl@0: * \internal sl@0: * \brief Deletes an EGL context sl@0: */ sl@0: static void m3gDeleteGLContext(EGLContext ctx) sl@0: { sl@0: eglDestroyContext(eglGetDisplay(EGL_DEFAULT_DISPLAY), ctx); sl@0: # if defined(M3G_DEBUG) sl@0: { sl@0: EGLint err = eglGetError(); sl@0: if (err != EGL_SUCCESS) { sl@0: M3G_LOG1(M3G_LOG_FATAL_ERRORS, "EGL error 0x%08X\n", (unsigned) err); sl@0: } sl@0: M3G_ASSERT(err == EGL_SUCCESS); sl@0: } sl@0: # endif sl@0: M3G_LOG1(M3G_LOG_OBJECTS, "Destroyed GL context 0x%08X\n", sl@0: (unsigned) ctx); sl@0: } sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Creates a new EGL window surface sl@0: */ sl@0: static EGLSurface m3gCreateWindowSurface(M3Genum format, sl@0: M3Gbitmask bufferBits, sl@0: M3GNativeWindow wnd) sl@0: { sl@0: EGLSurface surf; sl@0: EGLConfig config = m3gQueryEGLConfig(format, bufferBits, EGL_WINDOW_BIT, NULL); sl@0: sl@0: if (!config) { sl@0: return NULL; sl@0: } sl@0: sl@0: surf = eglCreateWindowSurface(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: config, sl@0: (NativeWindowType) wnd, sl@0: NULL); sl@0: sl@0: # if defined(M3G_DEBUG) sl@0: { sl@0: EGLint err = eglGetError(); sl@0: M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC); sl@0: } sl@0: # endif sl@0: sl@0: if (surf != EGL_NO_SURFACE) { sl@0: M3G_LOG1(M3G_LOG_OBJECTS, "New GL window surface 0x%08X\n", sl@0: (unsigned) surf); sl@0: return surf; sl@0: } sl@0: return NULL; sl@0: } sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Creates a new EGL pixmap surface sl@0: */ sl@0: static EGLSurface m3gCreateBitmapSurface(M3Genum format, sl@0: M3Gbitmask bufferBits, sl@0: M3GNativeBitmap bmp) sl@0: { sl@0: EGLSurface surf; sl@0: EGLConfig config = m3gQueryEGLConfig(format, bufferBits, EGL_PIXMAP_BIT, bmp); sl@0: sl@0: if (!config) { sl@0: return NULL; sl@0: } sl@0: sl@0: surf = eglCreatePixmapSurface(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: config, sl@0: (NativePixmapType) bmp, sl@0: NULL); sl@0: sl@0: # if defined(M3G_DEBUG) sl@0: { sl@0: EGLint err = eglGetError(); sl@0: M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC); sl@0: } sl@0: # endif sl@0: sl@0: if (surf != EGL_NO_SURFACE) { sl@0: M3G_LOG1(M3G_LOG_OBJECTS, "New GL pixmap surface 0x%08X\n", sl@0: (unsigned) surf); sl@0: return surf; sl@0: } sl@0: return NULL; sl@0: } sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Creates a new PBuffer sl@0: */ sl@0: static EGLSurface m3gCreatePBufferSurface(M3Genum format, sl@0: M3Gbitmask bufferBits, sl@0: M3Gint width, M3Gint height) sl@0: { sl@0: EGLSurface surf; sl@0: EGLConfig config; sl@0: EGLint attrib[5]; sl@0: sl@0: attrib[0] = EGL_WIDTH; sl@0: attrib[1] = width; sl@0: attrib[2] = EGL_HEIGHT; sl@0: attrib[3] = height; sl@0: attrib[4] = EGL_NONE; sl@0: sl@0: config = m3gQueryEGLConfig(format, bufferBits, EGL_PBUFFER_BIT, NULL); sl@0: if (!config) { sl@0: return NULL; sl@0: } sl@0: sl@0: surf = eglCreatePbufferSurface(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: config, sl@0: attrib); sl@0: # if defined(M3G_DEBUG) sl@0: { sl@0: EGLint err = eglGetError(); sl@0: M3G_ASSERT(err == EGL_SUCCESS || err == EGL_BAD_ALLOC); sl@0: } sl@0: # endif sl@0: sl@0: if (surf != EGL_NO_SURFACE) { sl@0: M3G_LOG1(M3G_LOG_OBJECTS, "New GL pbuffer surface 0x%08X\n", sl@0: (unsigned) surf); sl@0: return surf; sl@0: } sl@0: return NULL; sl@0: } sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Deletes an EGL surface sl@0: */ sl@0: static void m3gDeleteGLSurface(EGLSurface surface) sl@0: { sl@0: eglDestroySurface(eglGetDisplay(EGL_DEFAULT_DISPLAY), surface); sl@0: sl@0: # if defined(M3G_DEBUG) sl@0: { sl@0: EGLint err = eglGetError(); sl@0: M3G_ASSERT(err == EGL_SUCCESS); sl@0: } sl@0: # endif sl@0: sl@0: M3G_LOG1(M3G_LOG_OBJECTS, "Destroyed GL surface 0x%08X\n", sl@0: (unsigned) surface); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Swap buffers on a rendering surface sl@0: */ sl@0: static M3Gbool m3gSwapBuffers(EGLSurface surface) sl@0: { sl@0: EGLBoolean success = eglSwapBuffers(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: surface); sl@0: sl@0: # if defined(M3G_DEBUG) sl@0: EGLint err = eglGetError(); sl@0: M3G_ASSERT(err == EGL_SUCCESS); sl@0: # endif sl@0: sl@0: return (M3Gbool) success; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Does a sub-blit of a frame buffer blit operation sl@0: */ sl@0: static void m3gBlitFrameBufferPixels2(RenderContext *ctx, sl@0: M3Gint xOffset, M3Gint yOffset, sl@0: M3Gint width, M3Gint height, sl@0: M3GPixelFormat internalFormat, sl@0: M3Gsizei stride, sl@0: const M3Gubyte *pixels) sl@0: { sl@0: # define MAX_TEMP_TEXTURES 8 sl@0: GLuint glFormat; sl@0: static const int MAX_TILE_SIZE = 256; /* -> 256 KB temp buffer(s) */ sl@0: static const M3Gbyte tc[8] = { 0, 0, 0, 1, 1, 0, 1, 1 }; sl@0: GLshort pos[8]; sl@0: int tileWidth = MAX_TILE_SIZE, tileHeight = MAX_TILE_SIZE; sl@0: M3Gbool mustConvert = M3G_FALSE; sl@0: M3Gubyte *tempPixels = 0; /* initialize to avoid compiler warnings */ sl@0: GLuint tempTexObj[MAX_TEMP_TEXTURES]; sl@0: GLint tempTexCount; sl@0: sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: M3G_ASSERT_GL; sl@0: sl@0: /* Analyze source and destination formats for possible conversion */ sl@0: sl@0: glFormat = m3gGetGLFormat(internalFormat); sl@0: if (!glFormat) { sl@0: M3G_ASSERT(M3G_FALSE); /* internal format not supported in GL */ sl@0: return; sl@0: } sl@0: if (internalFormat == M3G_RGB8_32) { sl@0: glFormat = GL_RGBA; sl@0: } sl@0: if (internalFormat == M3G_BGR8_32 || internalFormat == M3G_ARGB8) { sl@0: glFormat = GL_RGBA; sl@0: mustConvert = M3G_TRUE; sl@0: } sl@0: sl@0: /* Tweak tile size to avoid using excessive amounts of memory for sl@0: * portions outside the blit area */ sl@0: sl@0: M3G_ASSERT((width > 0) && (height > 0)); sl@0: sl@0: while (tileWidth >= width * 2) { sl@0: tileWidth >>= 1; sl@0: tileHeight <<= 1; sl@0: } sl@0: while (tileHeight >= height * 2) { sl@0: tileHeight >>= 1; sl@0: } sl@0: sl@0: /* Allocate temp memory for conversion or adjust tile size for sl@0: * optimal direct download to GL */ sl@0: sl@0: if (mustConvert) { sl@0: tempPixels = m3gAllocTemp(M3G_INTERFACE(ctx), sl@0: tileWidth * tileHeight * 4); sl@0: if (!tempPixels) { sl@0: return; /* out of memory */ sl@0: } sl@0: } sl@0: else { sl@0: sl@0: /* Attempt to adjust the tile size so that we can copy sl@0: * complete scanlines at a time -- this is because OpenGL ES sl@0: * is missing PixelStore settings that could be used for sl@0: * stride control during image uploading */ sl@0: sl@0: M3Gint maxWidth; sl@0: glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxWidth); sl@0: sl@0: while (tileWidth < width && sl@0: tileWidth < maxWidth && sl@0: tileHeight > 1) { sl@0: tileWidth <<= 1; sl@0: tileHeight >>= 1; sl@0: } sl@0: } sl@0: sl@0: /* Load default images into the temp texture objects */ sl@0: sl@0: glActiveTexture(GL_TEXTURE0); sl@0: glEnable(GL_TEXTURE_2D); sl@0: { sl@0: int ti; sl@0: tempTexCount = ((width + tileWidth - 1) / tileWidth) sl@0: * ((height + tileHeight - 1) / tileHeight); sl@0: tempTexCount = m3gMinInt(tempTexCount, MAX_TEMP_TEXTURES); sl@0: sl@0: glGenTextures(tempTexCount, tempTexObj); sl@0: sl@0: for (ti = 0; ti < tempTexCount; ++ti) { sl@0: glBindTexture(GL_TEXTURE_2D, tempTexObj[ti]); sl@0: glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); sl@0: glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); sl@0: glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); sl@0: M3G_ASSERT_GL; sl@0: sl@0: glTexImage2D(GL_TEXTURE_2D, 0, sl@0: glFormat, sl@0: tileWidth, tileHeight, sl@0: 0, sl@0: glFormat, sl@0: GL_UNSIGNED_BYTE, NULL); sl@0: sl@0: /* Raise out-of-memory if OpenGL ran out of resources */ sl@0: { sl@0: GLint err = glGetError(); sl@0: sl@0: if (err == GL_OUT_OF_MEMORY) { sl@0: m3gRaiseError(M3G_INTERFACE(ctx), M3G_OUT_OF_MEMORY); sl@0: goto CleanUpAndExit; sl@0: } sl@0: else if (err != GL_NO_ERROR) { sl@0: M3G_ASSERT(M3G_FALSE); sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* Set up texture and vertex coordinate arrays for the image tiles */ sl@0: sl@0: glClientActiveTexture(GL_TEXTURE0); sl@0: glTexCoordPointer(2, GL_BYTE, 0, tc); sl@0: glEnableClientState(GL_TEXTURE_COORD_ARRAY); sl@0: glVertexPointer(2, GL_SHORT, 0, pos); sl@0: glEnableClientState(GL_VERTEX_ARRAY); sl@0: glMatrixMode(GL_TEXTURE); sl@0: glLoadIdentity(); sl@0: glMatrixMode(GL_MODELVIEW); sl@0: M3G_ASSERT_GL; sl@0: sl@0: /* Load each image tile into a texture and draw */ sl@0: sl@0: { sl@0: M3Gint nextTexTile = 0; sl@0: M3Gint x, y, bpp; sl@0: bpp = m3gBytesPerPixel(internalFormat); sl@0: if (stride == 0) { sl@0: stride = bpp * width; sl@0: } sl@0: sl@0: for (y = 0; y < height; y += tileHeight) { sl@0: for (x = 0; x < width; x += tileWidth) { sl@0: M3Gint w, h; sl@0: sl@0: w = M3G_MIN(tileWidth, width - x); sl@0: h = M3G_MIN(tileHeight, height - y); sl@0: sl@0: glBindTexture(GL_TEXTURE_2D, tempTexObj[nextTexTile]); sl@0: nextTexTile = (nextTexTile + 1) % MAX_TEMP_TEXTURES; sl@0: sl@0: if (mustConvert) { sl@0: m3gConvertPixelRect(internalFormat, sl@0: pixels + y * stride + x * bpp, sl@0: stride, sl@0: w, h, sl@0: M3G_RGBA8, tempPixels, w * 4); sl@0: glTexSubImage2D(GL_TEXTURE_2D, 0, sl@0: 0, 0, sl@0: w, h, sl@0: GL_RGBA, GL_UNSIGNED_BYTE, tempPixels); sl@0: } sl@0: else { sl@0: if (w*bpp == stride) { sl@0: glTexSubImage2D(GL_TEXTURE_2D, 0, sl@0: 0, 0, sl@0: w, h, sl@0: glFormat, sl@0: GL_UNSIGNED_BYTE, sl@0: pixels + y * stride + x * bpp); sl@0: } sl@0: else { sl@0: int k; sl@0: for (k = 0; k < h; ++k) { sl@0: glTexSubImage2D(GL_TEXTURE_2D, 0, sl@0: 0, k, sl@0: w, 1, sl@0: glFormat, sl@0: GL_UNSIGNED_BYTE, sl@0: pixels + (y+k) * stride + x * bpp); sl@0: } sl@0: } sl@0: } sl@0: sl@0: pos[0] = (GLshort)(x + xOffset); sl@0: pos[1] = (GLshort)((height - y) + yOffset); sl@0: pos[2] = pos[0]; sl@0: pos[3] = (GLshort)((height - (y + tileHeight)) + yOffset); sl@0: pos[4] = (GLshort)((x + tileWidth) + xOffset); sl@0: pos[5] = pos[1]; sl@0: pos[6] = pos[4]; sl@0: pos[7] = pos[3]; sl@0: glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); sl@0: } sl@0: } sl@0: M3G_ASSERT_GL; sl@0: } sl@0: sl@0: /* Restore required OpenGL state and release resources */ sl@0: sl@0: CleanUpAndExit: sl@0: if (mustConvert) { sl@0: m3gFreeTemp(M3G_INTERFACE(ctx)); sl@0: } sl@0: sl@0: glDeleteTextures(tempTexCount, tempTexObj); sl@0: sl@0: M3G_ASSERT_GL; sl@0: sl@0: # undef MAX_TEMP_TEXTURES sl@0: } sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Internal functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /* The frame buffer should be the first thing locked and the last one sl@0: * released, so let's mandate that even though it has no real effect sl@0: * with EGL */ sl@0: #define m3gLockFrameBuffer(ctx) M3G_ASSERT_NO_LOCK(M3G_INTERFACE(ctx)) sl@0: #define m3gReleaseFrameBuffer(ctx) M3G_ASSERT_NO_LOCK(M3G_INTERFACE(ctx)) sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Alternate rendering function for two-sided lighting on buggy sl@0: * hardware sl@0: */ sl@0: static M3Gbool m3gSplitDrawMesh(RenderContext *ctx, sl@0: const VertexBuffer *vb, sl@0: const IndexBuffer *ib, sl@0: const Appearance *app, sl@0: const M3GMatrix *modelTransform, sl@0: M3Gint alphaFactor, sl@0: M3Gint scope) sl@0: { sl@0: if (!ctx->inSplitDraw && m3gGetMaterial((M3GAppearance) app) && vb->normals) { sl@0: PolygonMode *pm = m3gGetPolygonMode((M3GAppearance) app); sl@0: if (pm && pm->enableTwoSidedLighting) { sl@0: M3Gint originalCulling = m3gGetCulling(pm); sl@0: if (originalCulling != M3G_CULL_BACK) { sl@0: sl@0: /* OK, we must render the back sides separately with sl@0: * flipped normals */ sl@0: sl@0: Interface *m3g = M3G_INTERFACE(ctx); sl@0: VertexArray *tempNormals; sl@0: sl@0: M3Gint normalCount = vb->vertexCount; sl@0: M3Gint normalStride = vb->normals->stride; sl@0: sl@0: /* Duplicate the normal array */ sl@0: sl@0: m3gReleaseFrameBuffer(ctx); sl@0: tempNormals = m3gCloneVertexArray(vb->normals); sl@0: if (!tempNormals) { sl@0: m3gLockFrameBuffer(ctx); sl@0: return M3G_TRUE; /* automatic out-of-memory */ sl@0: } sl@0: sl@0: /* Flip the signs of the temp normals */ sl@0: sl@0: if (tempNormals->elementType == GL_BYTE) { sl@0: M3Gbyte *p = (M3Gbyte*) m3gMapObject(m3g, tempNormals->data); sl@0: int i; sl@0: for (i = 0; i < normalCount; ++i) { sl@0: p[0] = (M3Gbyte) -m3gClampInt(p[0], -127, 127); sl@0: p[1] = (M3Gbyte) -m3gClampInt(p[1], -127, 127); sl@0: p[2] = (M3Gbyte) -m3gClampInt(p[2], -127, 127); sl@0: p += normalStride; sl@0: } sl@0: } sl@0: else { sl@0: M3Gshort *p = (M3Gshort*) m3gMapObject(m3g, tempNormals->data); sl@0: int i; sl@0: for (i = 0; i < normalCount; ++i) { sl@0: p[0] = (M3Gshort) -m3gClampInt(p[0], -32767, 32767); sl@0: p[1] = (M3Gshort) -m3gClampInt(p[1], -32767, 32767); sl@0: p[2] = (M3Gshort) -m3gClampInt(p[2], -32767, 32767); sl@0: p += normalStride / 2; sl@0: } sl@0: } sl@0: m3gUnmapObject(m3g, tempNormals->data); sl@0: m3gLockFrameBuffer(ctx); sl@0: sl@0: ctx->inSplitDraw = M3G_TRUE; sl@0: sl@0: /* Set culling to front faces only and render with the sl@0: * flipped normals */ sl@0: { sl@0: VertexArray *orgNormals = vb->normals; sl@0: ((VertexBuffer*)vb)->normals = tempNormals; sl@0: m3gSetCulling(pm, M3G_CULL_FRONT); sl@0: m3gDrawMesh(ctx, sl@0: vb, ib, app, sl@0: modelTransform, sl@0: alphaFactor, scope); sl@0: ((VertexBuffer*)vb)->normals = orgNormals; sl@0: } sl@0: sl@0: /* If no culling was enabled, render the front faces sl@0: * with the original normals */ sl@0: sl@0: if (originalCulling == M3G_CULL_NONE) { sl@0: m3gSetCulling(pm, M3G_CULL_BACK); sl@0: m3gDrawMesh(ctx, sl@0: vb, ib, app, sl@0: modelTransform, sl@0: alphaFactor, scope); sl@0: } sl@0: sl@0: /* Restore original culling and free the temp normals */ sl@0: sl@0: m3gSetCulling(pm, originalCulling); sl@0: sl@0: m3gReleaseFrameBuffer(ctx); sl@0: m3gDeleteObject((M3GObject) tempNormals); sl@0: m3gLockFrameBuffer(ctx); sl@0: sl@0: ctx->inSplitDraw = M3G_FALSE; sl@0: return M3G_TRUE; sl@0: } sl@0: } sl@0: } sl@0: return M3G_FALSE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Determines whether a format/mode combination can be directly sl@0: * rendered sl@0: */ sl@0: static M3Gbool m3gCanDirectRender(const RenderContext *ctx) sl@0: { sl@0: M3GPixelFormat format = ctx->target.format; sl@0: M3Gbitmask bufferBits = ctx->bufferBits; sl@0: M3Gbitmask surfaceType = ctx->target.type; sl@0: M3GNativeBitmap bitmapHandle = ctx->target.handle; sl@0: int i; sl@0: sl@0: /* Images always go via pbuffers; EGL surfaces can always be sl@0: * rendered to */ sl@0: sl@0: if (surfaceType == SURFACE_IMAGE) { sl@0: return M3G_FALSE; sl@0: } sl@0: if (surfaceType == SURFACE_EGL || surfaceType == SURFACE_WINDOW) { sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /* First scan the context cache for a matching previously used sl@0: * context; this should be faster than querying EGL */ sl@0: sl@0: for (i = 0; i < M3G_MAX_GL_CONTEXTS; ++i) { sl@0: const GLContextRecord *rc = &ctx->glContext[i]; sl@0: sl@0: if ((rc->surfaceTypeBits & surfaceType) == surfaceType sl@0: && rc->format == format sl@0: && (rc->bufferBits & bufferBits) == bufferBits) { sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: } sl@0: sl@0: /* No dice; must resort to querying from EGL */ sl@0: sl@0: return (m3gQueryEGLConfig(format, bufferBits, (EGLint) surfaceType, bitmapHandle) != NULL); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Ensures that a sufficient back buffer exists sl@0: * sl@0: * Creates a new PBuffer for the back buffer if required. sl@0: */ sl@0: static M3Gbool m3gValidateBackBuffer(RenderContext *ctx) sl@0: { sl@0: BackBuffer *bbuf = &ctx->backBuffer; sl@0: int w = ctx->target.width; sl@0: int h = ctx->target.height; sl@0: sl@0: /* NOTE the EGL specification is fuzzy on eglCopyBuffers when the sl@0: * pbuffer is larger than the target, so we require an exact match sl@0: * (can be relaxed by #undefining the flag, see m3g_defs.h) */ sl@0: sl@0: # if defined(M3G_GL_FORCE_PBUFFER_SIZE) sl@0: if (bbuf->width != w || bbuf->height != h) { sl@0: # else sl@0: if (bbuf->width < w || bbuf->height < h) { sl@0: # endif sl@0: sl@0: M3G_LOG(M3G_LOG_WARNINGS, sl@0: "Warning (performance): Buffered rendering.\n"); sl@0: sl@0: if (bbuf->glSurface != NULL) { sl@0: m3gDeleteGLSurface(bbuf->glSurface); sl@0: } sl@0: sl@0: bbuf->glSurface = m3gCreatePBufferSurface( sl@0: M3G_RGBA8, sl@0: (M3Gbitmask)(M3G_COLOR_BUFFER_BIT|M3G_DEPTH_BUFFER_BIT|M3G_MULTISAMPLE_BUFFER_BIT), sl@0: w, h); sl@0: sl@0: bbuf->width = w; sl@0: bbuf->height = h; sl@0: sl@0: if (!bbuf->glSurface) { sl@0: if (eglGetError() == EGL_BAD_ALLOC) { sl@0: return M3G_FALSE; /* ouf of memory */ sl@0: } sl@0: else { sl@0: M3G_ASSERT(M3G_FALSE); sl@0: } sl@0: } sl@0: } sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Increment the rendering time stamp sl@0: * sl@0: * The time stamp is used to manage the various caches for the sl@0: * context, so it needs to be updated often enough for the caches to sl@0: * function optimally. sl@0: * sl@0: * In the rare case that the time stamp should wrap around(!), we sl@0: * reset the time stamps dependent on it to avoid sub-optimal cache sl@0: * performance. sl@0: */ sl@0: static void m3gIncrementRenderTimeStamp(RenderContext *ctx) sl@0: { sl@0: if (++ctx->cacheTimeStamp == 0) { sl@0: int i; sl@0: for (i = 0; i < M3G_MAX_GL_CONTEXTS; ++i) { sl@0: ctx->glContext[i].lastUseTime = 0; sl@0: } sl@0: for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) { sl@0: ctx->glSurface[i].lastUseTime = 0; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Draws a raw RGB or RGBA pixel rectangle of arbitrary size sl@0: * into the frame buffer sl@0: * sl@0: * The offset only affects the position of the blitted rectangle in sl@0: * the frame buffer. The source data is read starting at the given sl@0: * pointer. sl@0: * sl@0: * \param ctx the rendering context sl@0: * \param xOffset offset from the left edge of the frame buffer sl@0: * \param yOffset offset from the bottom of the frame buffer sl@0: * \param width width of the rectangle sl@0: * \param height height of the rectangle sl@0: * \param internalFormat format of the source pixels sl@0: * \param stride stride of the source data sl@0: * \param pixels pointer to the source pixels in top-to-bottom order sl@0: */ sl@0: static void m3gBlitFrameBufferPixels(RenderContext *ctx, sl@0: M3Gint xOffset, M3Gint yOffset, sl@0: M3Gint width, M3Gint height, sl@0: M3GPixelFormat internalFormat, sl@0: M3Gsizei stride, sl@0: const M3Gubyte *pixels) sl@0: { sl@0: /* Skip this if nothing to copy */ sl@0: sl@0: if (width <= 0 || height <= 0) { sl@0: return; sl@0: } sl@0: sl@0: /* Set viewport, projection and modelview to map coordinates to sl@0: * pixel boundaries */ sl@0: sl@0: glScissor(xOffset, yOffset, width, height); sl@0: glViewport(0, 0, ctx->target.width, ctx->target.height); sl@0: glMatrixMode(GL_PROJECTION); sl@0: glLoadIdentity(); sl@0: glOrthox(0, ctx->target.width << 16, sl@0: 0, ctx->target.height << 16, sl@0: -1 << 16, 1 << 16); sl@0: glMatrixMode(GL_MODELVIEW); sl@0: glLoadIdentity(); sl@0: sl@0: /* Disable any stray state we don't want */ sl@0: sl@0: glDisable(GL_CULL_FACE); sl@0: glDisable(GL_BLEND); sl@0: glDisable(GL_ALPHA_TEST); sl@0: glDisableClientState(GL_NORMAL_ARRAY); sl@0: glDisableClientState(GL_COLOR_ARRAY); sl@0: glDisable(GL_LIGHTING); sl@0: glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); sl@0: glDepthMask(GL_FALSE); sl@0: glDepthFunc(GL_ALWAYS); sl@0: m3gDisableTextures(); sl@0: M3G_ASSERT_GL; sl@0: sl@0: /* Split the large blit operation into smaller chunks that are sl@0: * efficiently taken care of using power-of-two textures */ sl@0: { sl@0: const int MAX_BLIT_SIZE = 256; /* should be power of two */ sl@0: sl@0: int xBlits = (width / MAX_BLIT_SIZE) + 1; sl@0: int yBlits = (height / MAX_BLIT_SIZE) + 1; sl@0: sl@0: int xBlit, yBlit; sl@0: sl@0: for (yBlit = yBlits-1; yBlit >= 0; yBlit--) { sl@0: for (xBlit = 0; xBlit < xBlits; ++xBlit) { sl@0: sl@0: M3Gint xStart = xOffset + xBlit * MAX_BLIT_SIZE; sl@0: M3Gint yStart = yOffset + yBlit * MAX_BLIT_SIZE; sl@0: M3Gint xSize = m3gMinInt(MAX_BLIT_SIZE, width - (xStart - xOffset)); sl@0: M3Gint ySize = m3gMinInt(MAX_BLIT_SIZE, height - (yStart - yOffset)); sl@0: sl@0: M3Gint srcOffset = (height - (yStart - yOffset + ySize)) * stride + (xStart - xOffset) * m3gBytesPerPixel(ctx->target.format); sl@0: sl@0: m3gBlitFrameBufferPixels2(ctx, sl@0: xStart, yStart, sl@0: xSize, ySize, sl@0: internalFormat, sl@0: stride, sl@0: pixels + srcOffset); sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Synchronizes the contents of the back buffer with the target sl@0: * buffer sl@0: */ sl@0: static void m3gUpdateBackBuffer(RenderContext *ctx) sl@0: { sl@0: if (ctx->target.type == SURFACE_IMAGE) { sl@0: m3gDrawFrameBufferImage(ctx, (Image *) ctx->target.handle); sl@0: } sl@0: else if (ctx->target.type == SURFACE_BITMAP || ctx->target.type == SURFACE_MEMORY) { sl@0: sl@0: M3Gubyte *src; sl@0: M3Gsizei stride; sl@0: sl@0: M3Gint clipWidth = ctx->clip.x1 - ctx->clip.x0; sl@0: M3Gint clipHeight = ctx->clip.y1 - ctx->clip.y0; sl@0: M3Gint srcOffset; sl@0: sl@0: if (ctx->target.type == SURFACE_BITMAP) { sl@0: /* Obtain a pointer to the native bitmap and copy the data to sl@0: * the backbuffer from there */ sl@0: if (!m3gglLockNativeBitmap((M3GNativeBitmap) ctx->target.handle, sl@0: &src, &stride)) { sl@0: /* No dice! There's no way that we know of to copy the sl@0: * data between the buffers */ sl@0: M3G_ASSERT(M3G_FALSE); sl@0: } sl@0: } else { sl@0: /* Memory target */ sl@0: src = ctx->target.pixels; sl@0: stride = ctx->target.stride; sl@0: } sl@0: sl@0: srcOffset = sl@0: (ctx->target.height - ctx->clip.y1) * stride sl@0: + ctx->clip.x0 * m3gBytesPerPixel(ctx->target.format); sl@0: sl@0: m3gBlitFrameBufferPixels( sl@0: ctx, sl@0: ctx->clip.x0, ctx->clip.y0, sl@0: clipWidth, clipHeight, sl@0: ctx->target.format, sl@0: stride, sl@0: src + srcOffset); sl@0: sl@0: if (ctx->target.type == SURFACE_BITMAP) { sl@0: m3gglReleaseNativeBitmap((M3GNativeBitmap) ctx->target.handle); sl@0: } sl@0: } sl@0: else { sl@0: /* Buffered rendering is not supported for window and pbuffer sl@0: * targets */ sl@0: M3G_ASSERT(M3G_FALSE); sl@0: } sl@0: ctx->backBuffer.contentsValid = M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Synchronizes the contents of the target buffer with the back sl@0: * buffer sl@0: */ sl@0: static void m3gUpdateTargetBuffer(RenderContext *ctx) sl@0: { sl@0: if (ctx->target.type == SURFACE_IMAGE) { sl@0: m3gCopyFrameBufferImage((Image *) ctx->target.handle); sl@0: } sl@0: else if (ctx->target.type == SURFACE_BITMAP || ctx->target.type == SURFACE_MEMORY) { sl@0: sl@0: M3GPixelFormat format = ctx->target.format; sl@0: M3Gint width = ctx->clip.x1 - ctx->clip.x0; sl@0: M3Gint height = ctx->clip.y1 - ctx->clip.y0; sl@0: M3Gint xOffset = ctx->clip.x0; sl@0: M3Gint yOffset = ctx->clip.y0; sl@0: M3Gint row; sl@0: sl@0: M3Gubyte *dst; sl@0: M3Gsizei stride; sl@0: M3Gubyte *temp; sl@0: sl@0: if (ctx->target.type == SURFACE_BITMAP) { sl@0: /* We must copy the back buffer to a native bitmap: first sl@0: * attempt a fast buffer-to-buffer copy using EGL, but if that sl@0: * fails, obtain a pointer and do the copy ourselves */ sl@0: sl@0: /* We can only do the fast copy for the full buffer */ sl@0: sl@0: M3Gbool fullClip = (ctx->clip.x0 == 0) sl@0: && (ctx->clip.y0 <= ctx->target.height - ctx->display.height) sl@0: && (ctx->clip.x1 >= ctx->display.width) sl@0: && (ctx->clip.y1 >= ctx->clip.y0 + ctx->display.height); sl@0: sl@0: if (fullClip && eglCopyBuffers(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: ctx->backBuffer.glSurface, sl@0: (NativePixmapType) ctx->target.handle)) sl@0: { sl@0: return; sl@0: } sl@0: sl@0: /* Fast copy failed, try the generic approach */ sl@0: if (!m3gglLockNativeBitmap((M3GNativeBitmap) ctx->target.handle, sl@0: &dst, &stride)) { sl@0: /* No dice! There's no way that we know of to copy the sl@0: * data between the buffers */ sl@0: M3G_ASSERT(M3G_FALSE); sl@0: } sl@0: } else { sl@0: /* Memory target */ sl@0: dst = ctx->target.pixels; sl@0: stride = ctx->target.stride; sl@0: } sl@0: sl@0: /* OK, got the pointer; now, copy a few scanlines at a sl@0: * time, and we can pretty much assume conversion since sl@0: * the fast method didn't work */ sl@0: sl@0: #define READPIXELS_BUFFER_SIZE 16384 sl@0: sl@0: if (width > 0 && height > 0) { sl@0: sl@0: M3Gint bufSize = (width * 4 > READPIXELS_BUFFER_SIZE ? width * 4 : READPIXELS_BUFFER_SIZE); sl@0: M3Gint numLinesInBuffer = bufSize/(width * 4); sl@0: M3Gint numLinesRead, line; sl@0: temp = m3gAllocTemp(M3G_INTERFACE(ctx), bufSize); sl@0: sl@0: if (!temp) { sl@0: return; /* out of memory */ sl@0: } sl@0: sl@0: dst += (ctx->target.height - (yOffset + height)) * stride sl@0: + xOffset * m3gBytesPerPixel(format); sl@0: sl@0: for (row = 0; row < height; row += numLinesRead) { sl@0: line = numLinesRead = (row + numLinesInBuffer > height) ? (height - row) : numLinesInBuffer; sl@0: sl@0: glReadPixels(xOffset, sl@0: yOffset + height - row - numLinesRead, sl@0: width, numLinesRead, sl@0: GL_RGBA, GL_UNSIGNED_BYTE, sl@0: temp); sl@0: sl@0: while (line-- > 0) { sl@0: m3gConvertPixels(M3G_RGBA8, &temp[4*line*width], format, dst, width); sl@0: dst += stride; sl@0: } sl@0: } sl@0: m3gFreeTemp(M3G_INTERFACE(ctx)); sl@0: } sl@0: sl@0: if (ctx->target.type == SURFACE_BITMAP) { sl@0: m3gglReleaseNativeBitmap((M3GNativeBitmap) ctx->target.handle); sl@0: } sl@0: sl@0: #undef READPIXELS_BUFFER_SIZE sl@0: sl@0: } sl@0: else { sl@0: /* Buffered rendering is not supported for window and pbuffer sl@0: * targets */ sl@0: M3G_ASSERT(M3G_FALSE); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Selects a GL context matching a given GL surface and a set sl@0: * of rendering parameters sl@0: * sl@0: * If no existing context matches, a new one is created. Contexts are sl@0: * stored in a fixed-size cache and managed using a LRU policy. sl@0: */ sl@0: static EGLContext m3gSelectGLContext(RenderContext *ctx, sl@0: M3GPixelFormat format, sl@0: M3Gbitmask bufferBits, sl@0: M3Gbitmask surfaceTypeBits, sl@0: EGLSurface surface) sl@0: { sl@0: int i; sl@0: sl@0: /* Look for a matching cached context and attempt to make it sl@0: * current; on success, update the time in the context record and sl@0: * return the GL context handle */ sl@0: sl@0: for (i = 0; i < M3G_MAX_GL_CONTEXTS; ++i) { sl@0: GLContextRecord *rc = &ctx->glContext[i]; sl@0: sl@0: if ((rc->surfaceTypeBits & surfaceTypeBits) == surfaceTypeBits sl@0: && rc->format == format sl@0: && (rc->bufferBits & bufferBits) == bufferBits) { sl@0: sl@0: if (eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: surface, surface, rc->handle)) { sl@0: rc->lastUseTime = ctx->cacheTimeStamp; sl@0: return rc->handle; sl@0: } sl@0: else { sl@0: /* NOTE we intentionally clear the error flag, since sl@0: * the MakeCurrent call above can fail in case of a sl@0: * context mismatch */ sl@0: eglGetError(); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* No match found, we must create a new context */ sl@0: { sl@0: GLContextRecord *lru = &ctx->glContext[0]; sl@0: EGLContext shareRc = lru->handle; sl@0: EGLContext glrc; sl@0: sl@0: /* Find the least recently used context entry */ sl@0: sl@0: for (i = 1; i < M3G_MAX_GL_CONTEXTS; ++i) { sl@0: GLContextRecord *rc = &ctx->glContext[i]; sl@0: if (rc->handle) { sl@0: shareRc = rc->handle; /* keep this for sharing */ sl@0: } sl@0: if (!rc->handle || rc->lastUseTime < lru->lastUseTime) { sl@0: lru = rc; sl@0: } sl@0: } sl@0: sl@0: /* Create a new GL context, then delete the LRU one. This is sl@0: * done in this order so that we don't lose any shared texture sl@0: * objects when deleting a context. */ sl@0: sl@0: //if (surfaceTypeBits == SURFACE_EGL) sl@0: { sl@0: EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); sl@0: EGLint configID; sl@0: eglQuerySurface(dpy, sl@0: surface,//(EGLSurface) ctx->target.handle, sl@0: EGL_CONFIG_ID, sl@0: &configID); sl@0: glrc = eglCreateContext(dpy, (EGLConfig) configID, shareRc, NULL); sl@0: //M3G_ASSERT(glrc); sl@0: } sl@0: /*else { sl@0: glrc = m3gCreateGLContext(format, sl@0: bufferBits, sl@0: surfaceTypeBits, sl@0: shareRc, sl@0: &lru->surfaceTypeBits); sl@0: } sl@0: */ sl@0: if (!glrc) { sl@0: m3gRaiseError(M3G_INTERFACE(ctx), M3G_OUT_OF_MEMORY); sl@0: return NULL; sl@0: } sl@0: if (lru->handle) { sl@0: m3gDeleteGLContext(lru->handle); sl@0: } sl@0: sl@0: /* Store the parameters for the new context and make it sl@0: * current */ sl@0: sl@0: lru->handle = glrc; sl@0: lru->surfaceTypeBits = surfaceTypeBits; sl@0: lru->format = format; sl@0: lru->bufferBits = bufferBits; sl@0: lru->modeBits = ctx->modeBits; sl@0: { sl@0: M3Gbool ok = eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), sl@0: surface, surface, glrc); sl@0: M3G_ASSERT(ok); sl@0: if (!ok) { sl@0: return NULL; sl@0: } sl@0: } sl@0: lru->lastUseTime = ctx->cacheTimeStamp; sl@0: m3gSetGLDefaults(); sl@0: return glrc; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Selects a GL surface suitable for rendering into the current sl@0: * target using the currently set rendering parameters sl@0: * sl@0: * If no existing surface matches, a new one is created. Surfaces are sl@0: * stored in a fixed-size LRU cache. sl@0: */ sl@0: static EGLSurface m3gSelectGLSurface(RenderContext *ctx) sl@0: { sl@0: int attempts = 0; sl@0: int i; sl@0: sl@0: /* Quick exit for EGL surfaces */ sl@0: sl@0: if (ctx->target.type == SURFACE_EGL) { sl@0: return (EGLSurface) ctx->target.handle; sl@0: } sl@0: sl@0: /* Buffered rendering is handled elsewhere! */ sl@0: sl@0: if (ctx->target.buffered) { sl@0: M3G_ASSERT(M3G_FALSE); sl@0: return NULL; sl@0: } sl@0: sl@0: /* Find the first matching surface and return it */ sl@0: sl@0: for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) { sl@0: GLSurfaceRecord *surf = &ctx->glSurface[i]; sl@0: sl@0: if ((surf->type == ctx->target.type) sl@0: && (surf->targetHandle == ctx->target.handle) sl@0: && ((ctx->bufferBits & surf->bufferBits) == ctx->bufferBits) sl@0: && (surf->width == ctx->target.width) sl@0: && (surf->height == ctx->target.height) sl@0: && (surf->format == ctx->target.format) sl@0: && (surf->pixels == ctx->target.pixels)) { sl@0: sl@0: surf->lastUseTime = ctx->cacheTimeStamp; sl@0: return surf->handle; sl@0: } sl@0: } sl@0: sl@0: /* No matching surface found; must create a new one. If the cache sl@0: * is fully occupied, or if we run out of memory, one of the sl@0: * existing surfaces is swapped out */ sl@0: sl@0: while (attempts <= 1) { sl@0: sl@0: GLSurfaceRecord *lru = &ctx->glSurface[0]; sl@0: sl@0: /* Find the first entry without a GL surface handle, or the sl@0: * least recently used one if all are occupied. */ sl@0: sl@0: for (i = 1; lru->handle != NULL && i < M3G_MAX_GL_SURFACES; ++i) { sl@0: GLSurfaceRecord *surf = &ctx->glSurface[i]; sl@0: if (!surf->handle || surf->lastUseTime < lru->lastUseTime) { sl@0: lru = surf; sl@0: } sl@0: } sl@0: sl@0: /* Delete the existing surface if we hit an occupied slot */ sl@0: sl@0: if (lru->handle) { sl@0: m3gDeleteGLSurface(lru->handle); sl@0: } sl@0: sl@0: /* Create a new surface depending on the type of the current sl@0: * rendering target */ sl@0: sl@0: switch (ctx->target.type) { sl@0: case SURFACE_BITMAP: sl@0: lru->handle = sl@0: m3gCreateBitmapSurface(ctx->target.format, sl@0: ctx->bufferBits, sl@0: (M3GNativeBitmap) ctx->target.handle); sl@0: break; sl@0: case SURFACE_WINDOW: sl@0: lru->handle = sl@0: m3gCreateWindowSurface(ctx->target.format, sl@0: ctx->bufferBits, sl@0: (M3GNativeWindow) ctx->target.handle); sl@0: break; sl@0: default: sl@0: M3G_ASSERT(M3G_FALSE); sl@0: return NULL; sl@0: } sl@0: sl@0: /* Success, return the new surface */ sl@0: sl@0: if (lru->handle) { sl@0: lru->type = ctx->target.type; sl@0: lru->targetHandle = ctx->target.handle; sl@0: lru->bufferBits = ctx->bufferBits; sl@0: lru->width = ctx->target.width; sl@0: lru->height = ctx->target.height; sl@0: lru->format = ctx->target.format; sl@0: lru->pixels = ctx->target.pixels; sl@0: lru->lastUseTime = ctx->cacheTimeStamp; sl@0: return lru->handle; sl@0: } sl@0: sl@0: /* No surface created, likely due to running out of memory; sl@0: * delete all existing surfaces and try again */ sl@0: sl@0: if (!lru->handle) { sl@0: for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) { sl@0: GLSurfaceRecord *surf = &ctx->glSurface[i]; sl@0: if (surf->handle) { sl@0: m3gDeleteGLSurface(surf->handle); sl@0: surf->handle = NULL; sl@0: surf->type = SURFACE_NONE; sl@0: } sl@0: } sl@0: ++attempts; sl@0: continue; sl@0: } sl@0: } sl@0: sl@0: /* Couldn't create a new surface; must return with an error */ sl@0: sl@0: m3gRaiseError(M3G_INTERFACE(ctx), M3G_OUT_OF_MEMORY); sl@0: return NULL; sl@0: } sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Deletes all native surfaces for a specific target sl@0: * sl@0: * \param ctx rendering context sl@0: * \param type bitmask of the types of surfaces to remove sl@0: * \param handle native target handle of the surfaces to remove sl@0: */ sl@0: static void m3gDeleteGLSurfaces(RenderContext *ctx, sl@0: M3Gbitmask type, sl@0: M3Guint handle) sl@0: { sl@0: int i; sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: for (i = 0; i < M3G_MAX_GL_SURFACES; ++i) { sl@0: GLSurfaceRecord *surf = &ctx->glSurface[i]; sl@0: sl@0: if ((surf->type & type) != 0 && surf->targetHandle == handle) { sl@0: m3gDeleteGLSurface(surf->handle); sl@0: sl@0: surf->type = SURFACE_NONE; sl@0: surf->handle = NULL; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Makes an OpenGL context current to the current rendering target sl@0: */ sl@0: static void m3gMakeGLCurrent(RenderContext *ctx) sl@0: { sl@0: eglBindAPI(EGL_OPENGL_ES_API); sl@0: sl@0: if (ctx != NULL) { sl@0: EGLContext eglCtx = NULL; sl@0: if (ctx->target.buffered) { sl@0: eglCtx = m3gSelectGLContext( sl@0: ctx, sl@0: M3G_RGBA8, sl@0: (M3Gbitmask) M3G_COLOR_BUFFER_BIT | sl@0: M3G_DEPTH_BUFFER_BIT | sl@0: M3G_MULTISAMPLE_BUFFER_BIT, sl@0: (M3Gbitmask) EGL_PBUFFER_BIT, sl@0: ctx->backBuffer.glSurface); sl@0: ctx->target.surface = ctx->backBuffer.glSurface; sl@0: } sl@0: else { sl@0: EGLSurface surface = m3gSelectGLSurface(ctx); sl@0: if (surface) { sl@0: eglCtx = m3gSelectGLContext(ctx, sl@0: ctx->target.format, sl@0: ctx->bufferBits, sl@0: ctx->target.type, sl@0: surface); sl@0: ctx->target.surface = surface; sl@0: } sl@0: } sl@0: /* Synchronize with native rendering in case we're sl@0: rendering to a native bitmap (or window) target */ sl@0: eglWaitNative(EGL_CORE_NATIVE_ENGINE); sl@0: sl@0: /* Update the current acceleration status */ sl@0: sl@0: if (eglCtx) { sl@0: EGLint param; sl@0: eglQueryContext(eglGetCurrentDisplay(), sl@0: eglCtx, EGL_CONFIG_ID, sl@0: ¶m); sl@0: eglGetConfigAttrib(eglGetCurrentDisplay(), sl@0: (EGLConfig) param, EGL_CONFIG_CAVEAT, sl@0: ¶m); sl@0: ctx->accelerated = (param == EGL_NONE); sl@0: } sl@0: } sl@0: else { sl@0: eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), NULL, NULL, NULL); sl@0: } sl@0: } sl@0: sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Public API sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \brief sl@0: */ sl@0: void m3gBindBitmapTarget(M3GRenderContext hCtx, sl@0: M3GNativeBitmap hBitmap) sl@0: { sl@0: M3GPixelFormat format; sl@0: M3Gint width, height, pixels; sl@0: RenderContext *ctx = (RenderContext *) hCtx; sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Binding bitmap 0x%08X\n", (unsigned) hBitmap); sl@0: sl@0: if (!m3gglGetNativeBitmapParams(hBitmap, &format, &width, &height, &pixels)) { sl@0: m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OBJECT); sl@0: return; sl@0: } sl@0: sl@0: if (!m3gBindRenderTarget(ctx, sl@0: SURFACE_BITMAP, sl@0: width, height, sl@0: format, sl@0: hBitmap)) { sl@0: return; /* appropriate error raised automatically */ sl@0: } sl@0: sl@0: /* Set the bitmap target specific parameters */ sl@0: sl@0: ctx->target.pixels = (void*)pixels; sl@0: sl@0: } sl@0: sl@0: /*! sl@0: * \brief Binds an external EGL surface as a rendering target sl@0: * sl@0: * \param context the M3G rendering context sl@0: * \param surface an EGLSurface cast to M3GEGLSurface sl@0: */ sl@0: M3G_API void m3gBindEGLSurfaceTarget(M3GRenderContext context, sl@0: M3GEGLSurface surface) sl@0: { sl@0: RenderContext *ctx = (RenderContext*) context; sl@0: Interface *m3g = M3G_INTERFACE(ctx); sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Binding EGL surface 0x%08X\n", (unsigned) surface); sl@0: { sl@0: EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); sl@0: EGLSurface surf = (EGLSurface) surface; sl@0: M3Gint width, height; sl@0: sl@0: if (!(eglQuerySurface(dpy, surf, EGL_WIDTH, &width) && sl@0: eglQuerySurface(dpy, surf, EGL_HEIGHT, &height))) { sl@0: m3gRaiseError(m3g, M3G_INVALID_OBJECT); sl@0: return; sl@0: } sl@0: sl@0: if (!m3gBindRenderTarget(ctx, sl@0: SURFACE_EGL, sl@0: width, height, sl@0: M3G_RGBA8, sl@0: surface)) { sl@0: return; /* error raised automatically */ sl@0: } sl@0: sl@0: /* placeholder for target type specific setup */ sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Binds a new memory rendering target to this rendering sl@0: * context sl@0: * sl@0: * Upon first binding of a specific target, binding the buffer may sl@0: * require auxiliary data to be allocated, depending on the rendering sl@0: * modes set for this context. In that case, the binding will be sl@0: * canceled, and the function will return a non-zero value giving the sl@0: * number of bytes of additional memory that needs to be supplied for sl@0: * binding of that target to succeed. The function must then be called sl@0: * again and a pointer to a sufficient memory block supplied as the \c sl@0: * mem parameter. sl@0: * sl@0: * \param pixels NULL to signal that the frame buffer is accessed sl@0: * using a callback upon rendering time sl@0: */ sl@0: /*@access M3GGLContext@*/ sl@0: void m3gBindMemoryTarget(M3GRenderContext context, sl@0: /*@shared@*/ void *pixels, sl@0: M3Guint width, M3Guint height, sl@0: M3GPixelFormat format, sl@0: M3Guint stride, sl@0: M3Guint userHandle) sl@0: { sl@0: RenderContext *ctx = (RenderContext*) context; sl@0: Interface *m3g = M3G_INTERFACE(ctx); sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Binding memory buffer 0x%08X\n", sl@0: (unsigned) pixels); sl@0: sl@0: /* Check for bitmap specific errors */ sl@0: sl@0: if (width == 0 || height == 0 || stride < width) { sl@0: m3gRaiseError(m3g, M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: /* Effect the generic target binding */ sl@0: sl@0: if (!m3gBindRenderTarget(ctx, sl@0: SURFACE_MEMORY, sl@0: width, height, sl@0: format, sl@0: userHandle)) { sl@0: return; /* appropriate error raised automatically */ sl@0: } sl@0: sl@0: /* Set the memory target specific parameters */ sl@0: sl@0: ctx->target.pixels = pixels; sl@0: ctx->target.stride = stride; sl@0: } sl@0: sl@0: /*! sl@0: * \brief sl@0: */ sl@0: M3G_API void m3gBindWindowTarget(M3GRenderContext hCtx, sl@0: M3GNativeWindow hWindow) sl@0: { sl@0: M3GPixelFormat format; sl@0: M3Gint width, height; sl@0: RenderContext *ctx = (RenderContext *) hCtx; sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Binding window 0x%08X\n", (unsigned) hWindow); sl@0: sl@0: if (!m3gglGetNativeWindowParams(hWindow, &format, &width, &height)) { sl@0: m3gRaiseError(M3G_INTERFACE(ctx), M3G_INVALID_OBJECT); sl@0: return; sl@0: } sl@0: sl@0: if (!m3gBindRenderTarget(ctx, sl@0: SURFACE_WINDOW, sl@0: width, height, sl@0: format, sl@0: hWindow)) { sl@0: return; /* appropriate error raised automatically */ sl@0: } sl@0: sl@0: /* placeholder for window target specific setup */ sl@0: } sl@0: sl@0: /*! sl@0: * \brief Invalidate a previously bound bitmap target sl@0: * sl@0: * This should be called prior to deleting a native bitmap that has sl@0: * been used as an M3G rendering target in the past. This erases the sl@0: * object from any internal caches and ensures it will not be accessed sl@0: * in the future. sl@0: * sl@0: * \param hCtx M3G rendering context sl@0: * \param hBitmap native handle of the bitmap object sl@0: */ sl@0: M3G_API void m3gInvalidateBitmapTarget(M3GRenderContext hCtx, sl@0: M3GNativeBitmap hBitmap) sl@0: { sl@0: RenderContext *ctx = (RenderContext *) hCtx; sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Invalidating bitmap 0x%08X\n", sl@0: (unsigned) hBitmap); sl@0: sl@0: m3gDeleteGLSurfaces(ctx, (M3Gbitmask) SURFACE_BITMAP, (M3Guint) hBitmap); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Invalidate a previously bound window target sl@0: * sl@0: * This should be called prior to deleting a native window that has sl@0: * been used as an M3G rendering target in the past. This erases the sl@0: * object from any internal caches and ensures it will not be accessed sl@0: * in the future. sl@0: * sl@0: * \param hCtx M3G rendering context sl@0: * \param hWindow native handle of the bitmap object sl@0: */ sl@0: M3G_API void m3gInvalidateWindowTarget(M3GRenderContext hCtx, sl@0: M3GNativeWindow hWindow) sl@0: { sl@0: RenderContext *ctx = (RenderContext *) hCtx; sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Invalidating window 0x%08X\n", sl@0: (unsigned) hWindow); sl@0: sl@0: m3gDeleteGLSurfaces(ctx, (M3Gbitmask) SURFACE_WINDOW, (M3Guint) hWindow); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Invalidate a previously bound memorytarget sl@0: * sl@0: * This should be called prior to deleting a memory buffer that has sl@0: * been used as an M3G rendering target in the past. sl@0: * sl@0: * \param hCtx M3G rendering context sl@0: * \param pixels pointer to the memory buffer sl@0: */ sl@0: M3G_API void m3gInvalidateMemoryTarget(M3GRenderContext hCtx, sl@0: void *pixels) sl@0: { sl@0: RenderContext *ctx = (RenderContext *) hCtx; sl@0: M3G_VALIDATE_OBJECT(ctx); sl@0: sl@0: M3G_LOG1(M3G_LOG_RENDERING, "Invalidating memory target 0x%08X\n", sl@0: (unsigned) pixels); sl@0: sl@0: m3gDeleteGLSurfaces(ctx, (M3Gbitmask) SURFACE_MEMORY, (M3Guint) pixels); sl@0: }