view src/Modules/Output/Graphics/Devices/GraphicsOutputDevicewxGLCanvas.cc @ 232:af531fc3f280

- Massive refactoring to make module tree stuff work. In theory we now support configuration files again. The graphics stuff is untested as yet.
author tomwalters
date Mon, 18 Oct 2010 04:42:28 +0000
parents 82e0dc3dfd16
children af02b6addf7a
line wrap: on
line source
// Copyright 2006, Willem van Engen
//
// AIM-C: A C++ implementation of the Auditory Image Model
// http://www.acousticscale.org/AIMC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*!
 * \file
 * \brief Output device for output to a wxWidgets OpenGL canvas
 *
 * \author Willem van Engen <cnbh@willem.engen.nl>
 * \date created 2006/09/21
 * \version \$Id: $
 */

#include "Support/Common.h"

/*! \class GraphicsOutputDevicewxGLCanvas
 *
 *  Graphics output takes a large part of the application's performance at the
 *  moment when it is inline with the Process() loop. Much is gained by
 *  putting it in a separate thread, which can be done using ShotTargetThreaded.
 *
 *  OpenGL-related documents:
 *   - http://www.opengl.org/
 *   - http://www.sgi.com/products/software/opengl/
 *   - http://developer.apple.com/graphicsimaging/opengl/
 *   - http://developer.nvidia.com/page/documentation.html
 *   - Vertex arrays
 *     - http://www.opengl.org/registry/specs/EXT/vertex_array.txt
 *     - http://www.awprofessional.com/articles/article.asp?p=461848&seqNum=2&rl=1
 *     - http://jdobry.webpark.cz/opengl/opengl_maximum_performance.html
 *   - Fonts and OpenGL
 *     - http://gltt.sourceforge.net/
 */

// And finally our own
#include "Support/util.h"
#include "Output/GraphicsOutputDevice.h"
#include "Output/GraphicsOutputDevicewxGLCanvas.h"

BEGIN_EVENT_TABLE(GraphicsOutputDevicewxGLCanvas, wxGLCanvas)
    EVT_SIZE(GraphicsOutputDevicewxGLCanvas::OnSize)
    EVT_PAINT(GraphicsOutputDevicewxGLCanvas::OnPaint)
    EVT_ERASE_BACKGROUND(GraphicsOutputDevicewxGLCanvas::OnEraseBackground)
END_EVENT_TABLE()

// wxGLCanvas attributes
int GraphicsOutputDevicewxGLCanvas::GLAttrlist[] = {
  WX_GL_RGBA, 1,
  WX_GL_DOUBLEBUFFER, 1,
  WX_GL_MIN_RED, 5,
  WX_GL_MIN_GREEN, 5,
  WX_GL_MIN_BLUE, 5,
  WX_GL_MIN_ALPHA, 3,
  WX_GL_DEPTH_SIZE, 16,
  0
};

// OpenGL get procaddress function pointer, differs across platforms
typedef void (*(*glGetProcAddressPtr_t)(const char*))();

GraphicsOutputDevicewxGLCanvas::GraphicsOutputDevicewxGLCanvas(Parameters *pParam,
                                                               wxWindow *parent,
                                                               wxWindowID id,
                                                               const wxPoint& pos,
                                                               const wxSize& size,
                                                               long style,
                                                               const wxString& name)
  : wxGLCanvas(parent, (wxGLCanvas*) NULL, id, pos, size,
               style|wxFULL_REPAINT_ON_RESIZE, name, GLAttrlist),
    GraphicsOutputDevice(pParam) {
  m_init = false;
  m_gllist = 0;
  m_pWorkerContext = NULL;
  m_bAntialiasing = true;
  m_pFont = NULL;
  m_sFontFile = NULL;
  m_iFontsize = -1;
#if !defined(_MACOSX)
  s_bWorkerNeedsInit = false;
#endif

#ifdef WITH_GL_VERTEX_ARRAYS
  m_iVertexType = 0xffff; // no gBegin() has happened yet
  m_bStaticColor = false;
  m_pVertices = NULL;
  m_iVerticesMax = 0;
  // Enable vertex arrays if possible
#ifdef _MACOSX
  m_glLockArraysEXT = ::glLockArraysEXT;
  m_glUnlockArraysEXT = ::glUnlockArraysEXT;
  m_bVertexArrayLock = true;
#else
  m_bVertexArrayLock = false;
  // OpenGL command needed to fetch entry point, do it in InitGL()
#endif /* _MACOSX */
#endif /* WITH_GL_VERTEX_ARRAYS */
}

GraphicsOutputDevicewxGLCanvas::~GraphicsOutputDevicewxGLCanvas() {
  // Cleanup OpenGL display list
  if (m_init) {
    glDeleteLists(m_gllist, 1);
  }
  DELETE_IF_NONNULL(m_pWorkerContext);
  DELETE_IF_NONNULL(m_pFont);
#ifdef WITH_GL_VERTEX_ARRAYS
  DELETE_ARRAY_IF_NONNULL(m_pVertices);
#endif
}

void GraphicsOutputDevicewxGLCanvas::Start() {
  // This seems to be needed to prevent a crash on windows, but why????
  SetCurrent();
  return GraphicsOutputDevice::Start();
}

bool GraphicsOutputDevicewxGLCanvas::Initialize(unsigned int iVerticesMax) {
  AIM_ASSERT(m_pParam);
  // Give a chance to update anti-aliasing settings
  if (m_bAntialiasing != m_pParam->GetBool("output.antialias")) {
    m_bAntialiasing = m_pParam->GetBool("output.antialias");
    if (SetCurrent()) {
      InitGL();
#if !defined(_MACOSX)
      {
        wxMutexLocker lock(s_mutexOpenGL);
        s_bWorkerNeedsInit = true;
      }
#endif
    }
  }

#ifdef WITH_GL_VERTEX_ARRAYS
  // Re-allocate vertices
  if (iVerticesMax > m_iVerticesMax) {
    DELETE_IF_NONNULL(m_pVertices);
    m_iVerticesMax = iVerticesMax;
    // If color is static, we need not store the color
    if (m_bStaticColor)
      m_pVertices = new GLfloat[(iVerticesMax+1)*3];
    else
      m_pVertices = new GLfloat[(iVerticesMax+1)*6];
  }
#endif

  // Change font if requested
  const char *sFontFile = m_pParam->GetString("output.gl.fontfile");
  unsigned int iFontsize = m_pParam->GetUInt("output.fontsize");
  if (!m_sFontFile
      || !strcmp(m_sFontFile,sFontFile)==0
      || m_iFontsize!=(int)iFontsize) {
    wxMutexLocker lock(s_mutexOpenGL);
    DELETE_IF_NONNULL(m_pFont);
    wxString sWorkingFontFilename = wxString::FromAscii(sFontFile);
    if (!wxFileExists(sWorkingFontFilename)) {
      sWorkingFontFilename = wxString::FromAscii(aimDataDir());
      sWorkingFontFilename += _T("/");
      sWorkingFontFilename += wxString::FromAscii(sFontFile);
    }
    //if (!wxFileExists(sWorkingFontFilename))
    //sWorkingFontFilename.replace("Font:").append(sFontFile);
    m_pFont = static_cast<FTFont*>(new FTGLBitmapFont(sWorkingFontFilename.fn_str()));
    if (!m_pFont || m_pFont->Error()) {
      aimERROR(_T("Couldn't load font '%s'"), sFontFile);
      DELETE_IF_NONNULL(m_pFont);
    } else {
      // Display lists don't mix with our own usage :(
      // May not be needed for a Bitmap font
      //m_pFont->UseDisplayList(false);
      if ( !m_pFont->FaceSize(iFontsize) ) {
        AIM_ERROR(_T("Couldn't select font size %u on font '%s'"), iFontsize, sFontFile);
        DELETE_IF_NONNULL(m_pFont);
      }
    }
    m_sFontFile = sFontFile;
    m_iFontsize = iFontsize;
  }
  return true;
}
bool GraphicsOutputDevicewxGLCanvas::Initialize() {
  return Initialize(0);
}

void GraphicsOutputDevicewxGLCanvas::Render() {
  wxPaintDC dc(this);
  // We want to initialize first from main thread.
  if (!m_init) {
    if (!SetCurrent()) return;
    InitGL();
  }
  // Render saved list only if not animating (redrawn anyway in that case)
  if (!m_bRunning) {
    if (!SetCurrent()) {
      return;
    }
    glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
    glCallList(m_gllist);
    SwapBuffers();
  }
}

void GraphicsOutputDevicewxGLCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
  Render();
}

void GraphicsOutputDevicewxGLCanvas::OnSize(wxSizeEvent& event) {
  // this is also necessary to update the context on some platforms
  wxGLCanvas::OnSize(event);

  // set GL viewport
  // (not called by wxGLCanvas::OnSize on all platforms...)
  if (SetCurrent()) {
    DoResize();
    // It is only sensible to update the other thread when it's running
    // Don't acquire the mutex when s_bWorkerNeedsInit already to avoid deadlock
    if (/*m_bRunning &&*/ !s_bWorkerNeedsInit) {
      wxMutexLocker lock(s_mutexOpenGL);
      s_bWorkerNeedsInit = true;
    }
  }
}

void GraphicsOutputDevicewxGLCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event)) {
}

bool GraphicsOutputDevicewxGLCanvas::SetCurrent() {
  bool bRet=true;

#ifndef __WXMOTIF__
  bRet = (GetContext()!=NULL);
  if (bRet)
#endif
  {
    wxGLCanvas::SetCurrent();
  }
  return bRet;
}

void GraphicsOutputDevicewxGLCanvas::DoResize() {
  int w, h;
  GetClientSize(&w, &h);
  glViewport(0, 0, (GLint)w, (GLint)h);
}

void GraphicsOutputDevicewxGLCanvas::InitGL() {
  /* No SetCurrent() here, because this can be called from different GL contexts.
   * Convenient for multi-threaded operation. */
  //aimERROR(_T("InitGL Called"));
#if defined(WITH_GL_VERTEX_ARRAYS) && !defined(_MACOSX)
  if (!m_init) {
    /* This needs to be done here, because OpenGL commands may need SetCurrent()
     * and an already shown window. */
    char *extensions = (char *)glGetString(GL_EXTENSIONS);
    if (!extensions) {
      AIM_INFO(_T("Could not query OpenGL extensions, vertex arrays disabled"));
    }  else if (strstr(extensions, "GL_EXT_compiled_vertex_array")) {
      m_glLockArraysEXT = (LOCAL_PFNGLLOCKARRAYSEXTPROC)GL_GET_PROC_ADDRESS("glLockArraysEXT");
      m_glUnlockArraysEXT = (LOCAL_PFNGLUNLOCKARRAYSEXTPROC)GL_GET_PROC_ADDRESS("glUnlockArraysEXT");
      if(!m_glLockArraysEXT || !m_glUnlockArraysEXT)
        AIM_ERROR(_T("OpenGL error on GL_EXT_compiled_vertex_array"));
      else
        m_bVertexArrayLock = true;
    }
  }
#endif
  DoResize();
  glClearColor(0, 0, 0, 1);
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity( );

  glEnable(GL_VERTEX_ARRAY);

  // Window limits in OpenGL co-ordiantes
  //! \todo Make this configurable, or change and document fixed values
  glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
  glTranslatef(0.0, 0.0, 0.0);

  if (m_bAntialiasing) {
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //glBlendFunc(GL_ONE, GL_ONE);
    glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
  } else {
    glDisable(GL_LINE_SMOOTH);
    glDisable(GL_BLEND);
  }
  glLineWidth(1.0);

  // Get a free display list only the first time
  if (!m_init) {
#if !defined(_MACOSX)
    // Windows and Linux need a separate worker context
    aimASSERT(wxIsMainThread());
#if wxCHECK_VERSION(2,8,0)
    m_pWorkerContext = new wxGLContext(this, m_glContext);
#else
    m_pWorkerContext = new wxGLContext(true,
                                       this,
                                       wxNullPalette,
                                       m_glContext);
#endif
    aimASSERT(m_pWorkerContext);
    s_bWorkerNeedsInit = true;
#endif
    m_gllist = glGenLists(1);
    aimASSERT(m_gllist);
    // Empty window at start
    glNewList(m_gllist, GL_COMPILE_AND_EXECUTE);
    glEndList();
    m_init = true;
  }
}

// Call before any other render* functions
void GraphicsOutputDevicewxGLCanvas::gGrab() {
  AimwxGuiLocker __lock__;
#if !defined(_MACOSX)
  // Detect if we're the main thread or not.
  if (!wxIsMainThread()) {
    // We're called by a worker thread, make sure there's a right context
    AIM_ASSERT(m_pWorkerContext);
#if wxCHECK_VERSION(2,8,0)
    m_pWorkerContext->SetCurrent(*this);
#else
    m_pWorkerContext->SetCurrent();
#endif
    // Update OpenGL settings if needed
    wxMutexLocker lock(s_mutexOpenGL);
    if (s_bWorkerNeedsInit) {
      InitGL();
      s_bWorkerNeedsInit = false;
    }
  } else
#endif
  {
    // Either called by main thread, or we need no special worker glContext
    if (!SetCurrent()) {
      return;
    }
    // Init OpenGL once, but after SetCurrent
    if (!m_init) {
      InitGL();
    }
  }
  glClear(GL_COLOR_BUFFER_BIT);

  // Start and store in a display list for redrawing
  glNewList(m_gllist, GL_COMPILE);
}

void GraphicsOutputDevicewxGLCanvas::gBeginLineStrip() {
#ifdef WITH_GL_VERTEX_ARRAYS
  aimASSERT(m_iVertexType == 0xffff); // Previous gBegin*() must be gEnd()ed
  // New lines vertex array
  m_iVertexCount = 0;
  m_iVertexType = GL_LINE_STRIP;
#else
  AimwxGuiLocker __lock__;
  glBegin(GL_LINE_STRIP);
#endif
}

void GraphicsOutputDevicewxGLCanvas::gBeginQuadStrip() {
#ifdef WITH_GL_VERTEX_ARRAYS
  aimASSERT(m_iVertexType == 0xffff); // Previous gBegin*() must be gEnd()ed
  // New quads vertex array
  m_iVertexCount = 0;
  m_iVertexType = GL_QUAD_STRIP;
#else
  AimwxGuiLocker __lock__;
  glBegin(GL_QUAD_STRIP);
#endif
}

void GraphicsOutputDevicewxGLCanvas::gVertex3f(float x, float y, float z) {
#ifdef WITH_GL_VERTEX_ARRAYS
  aimASSERT(m_iVertexType != 0xffff); // Must be inside gBegin*()
  if (m_iVertexCount>=m_iVerticesMax) {
    static bool errShown=false;
    if (!errShown) {
      aimERROR(_T("Error: max vertex count reached: %d"), m_iVertexCount);
      errShown=true;
    }
    return;
  }
  if (m_bStaticColor) {
    m_pVertices[m_iVertexCount*3+0] = x;
    m_pVertices[m_iVertexCount*3+1] = y;
    m_pVertices[m_iVertexCount*3+2] = z;
  } else {
    m_pVertices[m_iVertexCount*6+0] = m_fCurColorR;
    m_pVertices[m_iVertexCount*6+1] = m_fCurColorG;
    m_pVertices[m_iVertexCount*6+2] = m_fCurColorB;
    m_pVertices[m_iVertexCount*6+3] = x;
    m_pVertices[m_iVertexCount*6+4] = y;
    m_pVertices[m_iVertexCount*6+5] = z;
  }
  m_iVertexCount++;
#else
  AimwxGuiLocker __lock__;
  glVertex3f(x,y,z);
#endif
}

void GraphicsOutputDevicewxGLCanvas::gColor3f(float r, float g, float b) {
#ifdef WITH_GL_VERTEX_ARRAYS
  if (m_iVertexType==0xffff || m_bStaticColor) {
    // If not inside vertex array run, use the ordinary command
    glColor3f(r, g, b);
  }
  if (!m_bStaticColor) {
    // Set current color for vertex array usage
    m_fCurColorR = r;
    m_fCurColorG = g;
    m_fCurColorB = b;
  }
#else
  AimwxGuiLocker __lock__;
    glColor3f(r, g, b);
#endif
}

void GraphicsOutputDevicewxGLCanvas::gEnd() {
#ifdef WITH_GL_VERTEX_ARRAYS
  aimASSERT(m_iVertexType != 0xffff); // Must be inside gBegin*()
  AimwxGuiLocker __lock__;

  // Draw the vertex array
  glEnableClientState(GL_VERTEX_ARRAY);

  // Draw vertices
  if (m_bStaticColor)
    glVertexPointer(3, GL_FLOAT, 0, m_pVertices);
  else
    glInterleavedArrays(GL_C3F_V3F, 0, m_pVertices);
  if (m_bVertexArrayLock) m_glLockArraysEXT(0, m_iVertexCount);
  glDrawArrays(m_iVertexType, 0, m_iVertexCount);
  if (m_bVertexArrayLock) m_glUnlockArraysEXT();

  glDisableClientState(GL_VERTEX_ARRAY);

  // Remember we're outside a gBegin()..gEnd() loop
  m_iVertexType = 0xffff;
#else
  AimwxGuiLocker __lock__;
  glEnd();
#endif
}

void GraphicsOutputDevicewxGLCanvas::gText3f(float x,
                                             float y,
                                             float z,
                                             const char *sStr,
                                             bool bRotated) {
#ifdef WITH_GL_VERTEX_ARRAYS
  aimASSERT(m_iVertexType == 0xffff); // Must be outside gBegin*()
#endif

  if (!m_pFont)
    return;

  //! \todo make rotation work
  if (bRotated)
    return;

  {
    AimwxGuiLocker __lock__;
    /*
    if (bRotated) {
      glPushMatrix();
      glTranslatef(x,y,z);
      glRotatef(90.0f, 0, 0, 1.0f);
      glRasterPos3f(0,0,0);
      m_pFont->Render(sStr);
      glPopMatrix();
    } else {
    */
    glRasterPos3f(x, y, z);
    m_pFont->Render(sStr);
  }
}

void GraphicsOutputDevicewxGLCanvas::gRelease() {
#ifdef WITH_GL_VERTEX_ARRAYS
  aimASSERT(m_iVertexType == 0xffff); // Must be gEnd()ed
#endif
  AimwxGuiLocker __lock__;
  glEndList();
  glCallList(m_gllist);
  //glFlush();
   SwapBuffers(); // Doesn't matter in what context
}