| |
| |
|
|
| #include "texception.h" |
| #include "tvectorgl.h" |
| #include "tvectorimage.h" |
| #include "trasterimage.h" |
| #include "tgl.h" |
| #include "tthreadmessage.h" |
| #include "tsystem.h" |
| #include "trop.h" |
| |
| |
| #if defined(LINUX) |
| |
| #include <X11/Xlib.h> |
| #include <GL/glx.h> |
| |
| #include "xscopedlock.h" |
| #include "tthread.h" |
| |
| #elif MACOSX |
| |
| #include "qtofflinegl.h" |
| |
| #endif |
| |
| #include "tofflinegl.h" |
| |
| #ifndef checkErrorsByGL |
| #define checkErrorsByGL \ |
| { \ |
| GLenum err = glGetError(); \ |
| assert(err != GL_INVALID_ENUM); \ |
| assert(err != GL_INVALID_VALUE); \ |
| assert(err != GL_INVALID_OPERATION); \ |
| assert(err != GL_STACK_OVERFLOW); \ |
| assert(err != GL_STACK_UNDERFLOW); \ |
| assert(err != GL_OUT_OF_MEMORY); \ |
| assert(err == GL_NO_ERROR); \ |
| } |
| #endif |
| |
| #undef checkErrorsByGL |
| #define checkErrorsByGL /**/ |
| |
| using namespace std; |
| |
| TGLContextManager *currentContextManager = 0; |
| |
| void TOfflineGL::setContextManager(TGLContextManager *contextManager) |
| { |
| currentContextManager = contextManager; |
| if (contextManager) |
| contextManager->store(); |
| } |
| |
| |
| |
| |
| |
| #ifdef _WIN32 |
| |
| namespace |
| { |
| |
| |
| |
| |
| static QMutex win32ImpMutex; |
| } |
| |
| |
| |
| class WIN32Implementation : public TOfflineGL::Imp |
| { |
| public: |
| HDC m_offDC; |
| HGDIOBJ m_oldobj; |
| HGLRC m_hglRC; |
| HBITMAP m_offDIB; |
| void *m_offData; |
| |
| |
| |
| WIN32Implementation(TDimension rasterSize, std::shared_ptr<TOfflineGL::Imp> shared) |
| : TOfflineGL::Imp(rasterSize.lx, rasterSize.ly) |
| { |
| m_offData = 0; |
| createContext(rasterSize, std::move(shared)); |
| |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| doneCurrent(); |
| } |
| |
| |
| |
| ~WIN32Implementation() |
| { |
| QMutexLocker locker(&win32ImpMutex); |
| |
| BOOL ret = wglMakeCurrent(m_offDC, NULL); |
| assert(ret == TRUE); |
| wglDeleteContext(m_hglRC); |
| SelectObject(m_offDC, m_oldobj); |
| DeleteObject(m_offDC); |
| |
| |
| |
| |
| |
| DeleteObject(m_offDIB); |
| } |
| |
| |
| |
| void makeCurrent() |
| { |
| QMutexLocker locker(&win32ImpMutex); |
| |
| int ret = wglMakeCurrent(m_offDC, m_hglRC); |
| assert(ret == TRUE); |
| } |
| |
| |
| |
| void doneCurrent() |
| { |
| QMutexLocker locker(&win32ImpMutex); |
| |
| glFlush(); |
| glFinish(); |
| assert(glGetError() == 0); |
| wglMakeCurrent(NULL, NULL); |
| } |
| |
| |
| |
| void initBITMAPINFO(BITMAPINFO &info, const TDimension rasterSize) |
| { |
| memset(&info, 0, sizeof(BITMAPINFOHEADER)); |
| |
| info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); |
| info.bmiHeader.biWidth = rasterSize.lx; |
| info.bmiHeader.biHeight = rasterSize.ly; |
| info.bmiHeader.biPlanes = 1; |
| info.bmiHeader.biBitCount = 32; |
| info.bmiHeader.biCompression = BI_RGB; |
| info.bmiHeader.biSizeImage = 0; |
| info.bmiHeader.biXPelsPerMeter = 1000; |
| info.bmiHeader.biYPelsPerMeter = 1000; |
| info.bmiHeader.biClrUsed = 0; |
| info.bmiHeader.biClrImportant = 0; |
| } |
| |
| |
| |
| void createContext(TDimension rasterSize, std::shared_ptr<TOfflineGL::Imp> shared) |
| { |
| QMutexLocker locker(&win32ImpMutex); |
| |
| BITMAPINFO info; |
| |
| initBITMAPINFO(info, rasterSize); |
| |
| |
| m_offDC = CreateCompatibleDC(NULL); |
| |
| |
| m_offDIB = CreateDIBSection(m_offDC, &info, DIB_RGB_COLORS, &m_offData, NULL, 0); |
| |
| assert(m_offDIB); |
| assert(m_offData); |
| |
| if (!m_offDIB || !m_offData) |
| throw TException("cannot create OpenGL context. Check system resources!"); |
| |
| int dataSize = rasterSize.lx * rasterSize.ly * 4; |
| |
| memset(m_offData, 0, dataSize); |
| |
| m_oldobj = SelectObject(m_offDC, m_offDIB); |
| |
| static PIXELFORMATDESCRIPTOR pfd = |
| { |
| sizeof(PIXELFORMATDESCRIPTOR), |
| 1, |
| 0 | (false ? (PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER) : (PFD_DRAW_TO_BITMAP | PFD_SUPPORT_GDI)) | PFD_SUPPORT_OPENGL, |
| PFD_TYPE_RGBA, |
| 32, |
| 0, 0, 0, 0, 0, 0, |
| 8, |
| 0, |
| 0, |
| 0, 0, 0, 0, |
| 32, |
| 32, |
| 0, |
| PFD_MAIN_PLANE, |
| 0, |
| 0, 0, 0 |
| }; |
| |
| |
| int iPixelFormat = ChoosePixelFormat(m_offDC, &pfd); |
| assert(iPixelFormat != 0); |
| |
| |
| int ret = SetPixelFormat(m_offDC, iPixelFormat, &pfd); |
| assert(ret == TRUE); |
| |
| |
| |
| m_hglRC = wglCreateContext(m_offDC); |
| assert(m_hglRC); |
| |
| if (!m_hglRC) |
| throw TException("cannot create OpenGL context. Check system resources!"); |
| |
| if (shared) { |
| |
| const WIN32Implementation *sharedImp = dynamic_cast<const WIN32Implementation *>(shared.get()); |
| assert(sharedImp); |
| |
| bool ok = wglShareLists(sharedImp->m_hglRC, m_hglRC); |
| assert(ok); |
| } |
| |
| ret = wglMakeCurrent(m_offDC, m_hglRC); |
| assert(ret == TRUE); |
| } |
| |
| |
| |
| void swapRedBlueChannels(void *buffer, int bufferSize) |
| { |
| void *b = buffer; |
| |
| #ifdef x64 |
| int size = bufferSize; |
| UCHAR *pix = (UCHAR *)b; |
| while (size > 0) { |
| UCHAR r = *pix; |
| UCHAR b = *(pix + 2); |
| *pix = b; |
| *(pix + 2) = r; |
| pix += 4; |
| size--; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #else |
| __asm |
| { |
| mov ecx, bufferSize |
| mov ebx, b |
| label: |
| mov al,[ebx+0] |
| mov ah,[ebx+2] |
| mov [ebx+2],al |
| mov [ebx+0],ah |
| |
| add ebx,4 |
| dec ecx |
| jnz label |
| } |
| #endif |
| } |
| |
| |
| |
| void getRaster(TRaster32P raster) |
| { |
| makeCurrent(); |
| glFlush(); |
| |
| int lx = raster->getLx(); |
| int ly = raster->getLy(); |
| |
| raster->lock(); |
| glReadPixels(0, 0, lx, ly, |
| GL_RGBA , GL_UNSIGNED_BYTE, |
| raster->getRawData()); |
| |
| swapRedBlueChannels(raster->getRawData(), lx * ly); |
| raster->unlock(); |
| } |
| }; |
| |
| |
| std::shared_ptr<TOfflineGL::Imp> defaultOfflineGLGenerator(const TDimension &dim, std::shared_ptr<TOfflineGL::Imp> shared) |
| { |
| return std::make_shared<WIN32Implementation>(dim, shared); |
| } |
| |
| |
| |
| |
| |
| #elif defined(LINUX) |
| |
| class XImplementation : public TOfflineGL::Imp |
| { |
| public: |
| Display *m_dpy; |
| GLXContext m_context; |
| GLXPixmap m_pixmap; |
| Pixmap m_xpixmap; |
| |
| |
| |
| XImplementation(TDimension rasterSize) |
| { |
| createContext(rasterSize); |
| |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| } |
| |
| |
| |
| ~XImplementation() |
| { |
| glXDestroyContext(m_dpy, m_context); |
| m_context = 0; |
| safeGlXMakeCurrent(true); |
| XCloseDisplay(m_dpy); |
| } |
| |
| |
| |
| bool safeGlXMakeCurrent(bool isDtor = false) |
| { |
| static std::map<pthread_t, GLXContext> m_glxContext; |
| static TThread::Mutex mutex; |
| |
| QMutexLocker sl(&mutex); |
| pthread_t self = pthread_self(); |
| std::map<pthread_t, GLXContext>::iterator it = m_glxContext.find(self); |
| if (((it != m_glxContext.end()) && (it->second != m_context)) || (it == m_glxContext.end())) { |
| |
| Bool ret; |
| if (!isDtor) |
| ret = glXMakeCurrent(m_dpy, m_pixmap, m_context); |
| m_glxContext[self] = m_context; |
| return ret; |
| } |
| |
| return true; |
| } |
| |
| |
| |
| void makeCurrent() |
| { |
| XScopedLock xsl; |
| |
| |
| |
| Bool ret = safeGlXMakeCurrent(); |
| assert(ret == True); |
| } |
| |
| |
| |
| |
| |
| void doneCurrent() |
| { |
| } |
| |
| |
| |
| void createContext(TDimension rasterSize) |
| { |
| m_dpy = XOpenDisplay(NULL); |
| Window win = DefaultRootWindow(m_dpy); |
| int attribList[] = { |
| GLX_RGBA, |
| GLX_RED_SIZE, 1, |
| GLX_GREEN_SIZE, 1, |
| GLX_BLUE_SIZE, 1, |
| |
| GLX_STENCIL_SIZE, 8, |
| |
| |
| |
| None}; |
| |
| int dbAttrib[] = {GLX_RGBA, |
| GLX_RED_SIZE, 1, |
| GLX_GREEN_SIZE, 1, |
| GLX_BLUE_SIZE, 1, |
| GLX_STENCIL_SIZE, 8, |
| GLX_DOUBLEBUFFER, |
| None}; |
| |
| int w = rasterSize.lx; |
| int h = rasterSize.ly; |
| |
| XVisualInfo *vis = glXChooseVisual(m_dpy, DefaultScreen(m_dpy), attribList); |
| if (!vis) { |
| std::cout << "unable to create sb visual" << std::endl; |
| vis = glXChooseVisual(m_dpy, DefaultScreen(m_dpy), dbAttrib); |
| assert(vis && "unable to create db visual"); |
| } |
| |
| m_context = glXCreateContext(m_dpy, vis, 0, False); |
| |
| |
| if (!m_context) |
| assert("not m_context" && false); |
| TRaster32P raster(w, h); |
| |
| m_xpixmap = XCreatePixmap(m_dpy, win, w, h, vis->depth); |
| |
| assert(m_xpixmap && "not m_xpixmap"); |
| |
| m_pixmap = glXCreateGLXPixmap(m_dpy, vis, m_xpixmap); |
| if (!m_pixmap) |
| assert("not m_pixmap" && m_pixmap); |
| |
| |
| |
| |
| |
| Bool ret = safeGlXMakeCurrent(); |
| assert(ret); |
| |
| m_raster = raster; |
| } |
| |
| |
| |
| #if defined(MACOSX) |
| #if defined(powerpc) |
| void rightRotateBits(UCHAR *buf, int bufferSize) |
| { |
| UINT *buffer = (UINT *)buf; |
| UINT app; |
| for (int i = 0; i < bufferSize; i++, buffer++) { |
| app = *buffer; |
| *buffer = app >> 8 | app << 24; |
| } |
| } |
| #else |
| void rightRotateBits(UCHAR *buf, int bufferSize) |
| { |
| UINT *buffer = (UINT *)buf; |
| UINT app; |
| for (int i = 0; i < bufferSize; i++, buffer++) { |
| app = *buffer; |
| *buffer = (app >> 16 & 0x000000ff) | (app << 16 & 0x00ff0000) | (app & 0xff00ff00); |
| } |
| } |
| #endif |
| #endif |
| |
| |
| const TRaster32P &getRaster() |
| { |
| makeCurrent(); |
| glFlush(); |
| |
| int lx = m_raster->getLx(); |
| int ly = m_raster->getLy(); |
| m_raster->lock(); |
| glReadPixels(0, 0, lx, ly, |
| GL_RGBA , GL_UNSIGNED_BYTE, |
| m_raster->getRawData()); |
| |
| #if defined(MACOSX) |
| rightRotateBits(m_raster->getRawData(), lx * ly); |
| #warning "to do" |
| #endif |
| m_raster->unlock(); |
| return m_raster; |
| } |
| |
| |
| |
| int getLx() const |
| { |
| return m_raster->getLx(); |
| } |
| |
| |
| |
| int getLy() const |
| { |
| return m_raster->getLy(); |
| } |
| }; |
| |
| std::shared_ptr<TOfflineGL::Imp> defaultOfflineGLGenerator(const TDimension &dim, std::shared_ptr<TOfflineGL::Imp> shared) |
| { |
| return std::make_shared<XImplementation>(dim); |
| } |
| |
| #elif MACOSX |
| |
| std::shared_ptr<TOfflineGL::Imp> defaultOfflineGLGenerator(const TDimension &dim, std::shared_ptr<TOfflineGL::Imp> shared) |
| { |
| return std::make_shared<QtOfflineGL>(dim, shared); |
| } |
| |
| #endif |
| |
| |
| |
| |
| |
| |
| namespace |
| { |
| TOfflineGL::ImpGenerator *currentImpGenerator = defaultOfflineGLGenerator; |
| } |
| |
| |
| |
| |
| |
| |
| |
| class MessageCreateContext : public TThread::Message |
| { |
| friend class TOfflineGL; |
| |
| TOfflineGL *m_ogl; |
| TDimension m_size; |
| std::shared_ptr<TOfflineGL::Imp> m_shared; |
| |
| public: |
| MessageCreateContext(TOfflineGL *ogl, const TDimension &size, std::shared_ptr<TOfflineGL::Imp> shared) |
| : m_ogl(ogl), m_size(size), m_shared(std::move(shared)) {} |
| |
| void onDeliver() |
| { |
| m_ogl->m_imp = currentImpGenerator(m_size, m_shared); |
| } |
| |
| TThread::Message *clone() const |
| { |
| return new MessageCreateContext(*this); |
| } |
| }; |
| |
| |
| |
| |
| |
| TOfflineGL::TOfflineGL(TDimension dim, const TOfflineGL *shared) |
| { |
| #if defined(LINUX) |
| XScopedLock xsl; |
| #endif |
| |
| std::shared_ptr<Imp> sharedImp = shared ? shared->m_imp : 0; |
| |
| |
| |
| |
| |
| m_imp = currentImpGenerator(dim, std::move(sharedImp)); |
| |
| initMatrix(); |
| } |
| |
| |
| |
| TOfflineGL::TOfflineGL(const TRaster32P &raster, const TOfflineGL *shared) |
| { |
| #if defined(LINUX) |
| XScopedLock xsl; |
| #endif |
| |
| m_imp = currentImpGenerator(raster->getSize(), shared->m_imp); |
| |
| initMatrix(); |
| |
| glRasterPos2d(0, 0); |
| raster->lock(); |
| glDrawPixels(raster->getLx(), raster->getLy(), GL_BGRA_EXT, GL_UNSIGNED_BYTE, raster->getRawData()); |
| raster->unlock(); |
| } |
| |
| |
| |
| TOfflineGL::~TOfflineGL() |
| { |
| } |
| |
| |
| |
| TOfflineGL::ImpGenerator *TOfflineGL::defineImpGenerator(TOfflineGL::ImpGenerator *impGenerator) |
| { |
| TOfflineGL::ImpGenerator *ret = currentImpGenerator; |
| currentImpGenerator = impGenerator; |
| return ret; |
| } |
| |
| |
| |
| void TOfflineGL::makeCurrent() |
| { |
| if (currentContextManager) |
| currentContextManager->store(); |
| |
| m_imp->makeCurrent(); |
| assert(glGetError() == GL_NO_ERROR); |
| } |
| |
| |
| |
| void TOfflineGL::doneCurrent() |
| { |
| m_imp->doneCurrent(); |
| if (currentContextManager) { |
| currentContextManager->restore(); |
| } |
| } |
| |
| |
| |
| void TOfflineGL::initMatrix() |
| { |
| m_imp->makeCurrent(); |
| |
| |
| glViewport(0, 0, m_imp->getLx(), m_imp->getLy()); |
| glMatrixMode(GL_PROJECTION); |
| glLoadIdentity(); |
| gluOrtho2D(0, m_imp->getLx(), 0, m_imp->getLy()); |
| glMatrixMode(GL_MODELVIEW); |
| glLoadIdentity(); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| void TOfflineGL::clear(TPixel32 color) |
| { |
| const double maxValue = 255.0; |
| makeCurrent(); |
| glClearColor( |
| (double)color.r / maxValue, |
| (double)color.g / maxValue, |
| (double)color.b / maxValue, |
| (double)color.m / maxValue); |
| glClear(GL_COLOR_BUFFER_BIT); |
| } |
| |
| |
| |
| void TOfflineGL::draw(TVectorImageP image, const TVectorRenderData &rd, bool doInitMatrix) |
| { |
| checkErrorsByGL; |
| makeCurrent(); |
| checkErrorsByGL; |
| |
| if (doInitMatrix) { |
| initMatrix(); |
| checkErrorsByGL; |
| } |
| |
| if (image) { |
| checkErrorsByGL; |
| tglDraw(rd, image.getPointer()); |
| checkErrorsByGL; |
| } |
| |
| checkErrorsByGL; |
| glFlush(); |
| checkErrorsByGL; |
| } |
| |
| |
| |
| void TOfflineGL::draw(TRasterImageP ri, const TAffine &aff, bool doInitMatrix) |
| { |
| makeCurrent(); |
| |
| if (doInitMatrix) |
| initMatrix(); |
| |
| TRaster32P ras32 = ri->getRaster(); |
| if (!ras32) |
| return; |
| |
| int lx = ras32->getLx(); |
| int ly = ras32->getLy(); |
| |
| assert((lx & (lx - 1)) == 0); |
| assert((ly & (ly - 1)) == 0); |
| |
| glPushMatrix(); |
| tglMultMatrix(aff); |
| |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| |
| glTexEnvf(GL_TEXTURE_ENV, |
| GL_TEXTURE_ENV_MODE, |
| GL_DECAL); |
| |
| glEnable(GL_TEXTURE_2D); |
| |
| |
| |
| |
| |
| |
| |
| |
| GLenum fmt = TGL_FMT; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| GLuint texId; |
| glGenTextures(1, &texId); |
| |
| glBindTexture(GL_TEXTURE_2D, texId); |
| |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, |
| ras32->getWrap() != ras32->getLx() ? ras32->getWrap() : 0); |
| |
| ras32->lock(); |
| |
| glTexImage2D( |
| GL_TEXTURE_2D, |
| 0, |
| GL_RGB, |
| lx, |
| ly, |
| 0, |
| fmt, |
| GL_UNSIGNED_BYTE, |
| ras32->getRawData()); |
| |
| ras32->unlock(); |
| |
| double halfWidth = 0.5 * lx; |
| double halfHeight = 0.5 * ly; |
| |
| double dpix = 1, dpiy = 1; |
| ri->getDpi(dpix, dpiy); |
| |
| if (dpix != 0 && dpiy != 0) { |
| double unit = 100; |
| halfWidth *= unit / dpix; |
| halfHeight *= unit / dpiy; |
| } |
| |
| glBegin(GL_QUAD_STRIP); |
| glTexCoord2d(0, 0); |
| glVertex2d(-halfWidth, -halfHeight); |
| glTexCoord2d(1, 0); |
| glVertex2d(halfWidth, -halfHeight); |
| glTexCoord2d(0, 1); |
| glVertex2d(-halfWidth, halfHeight); |
| glTexCoord2d(1, 1); |
| glVertex2d(halfWidth, halfHeight); |
| glEnd(); |
| glDisable(GL_TEXTURE_2D); |
| |
| glPopMatrix(); |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
| |
| |
| glDeleteTextures(1, &texId); |
| |
| glFlush(); |
| } |
| |
| |
| |
| void TOfflineGL::flush() |
| { |
| makeCurrent(); |
| glFlush(); |
| } |
| |
| |
| |
| void TOfflineGL::getRaster(TRaster32P raster) |
| { |
| assert(raster->getLx() <= getLx() && raster->getLy() <= getLy()); |
| if (raster->getWrap() == raster->getLx()) { |
| m_imp->getRaster(raster); |
| } else { |
| |
| |
| |
| |
| |
| TRaster32P ras32(raster->getSize()); |
| m_imp->getRaster(ras32); |
| TRop::copy(raster, ras32); |
| } |
| } |
| |
| |
| |
| void TOfflineGL::getRaster(TRasterP raster) |
| { |
| assert(raster->getLx() <= getLx() && raster->getLy() <= getLy()); |
| TRaster32P ras32 = raster; |
| if (ras32 && (raster->getWrap() == raster->getLx())) { |
| m_imp->getRaster(ras32); |
| } else { |
| ras32 = TRaster32P(raster->getSize()); |
| m_imp->getRaster(ras32); |
| TRop::copy(raster, ras32); |
| } |
| } |
| |
| |
| |
| TRaster32P TOfflineGL::getRaster() |
| { |
| TRaster32P raster(getLx(), getLy()); |
| m_imp->getRaster(raster); |
| return raster; |
| } |
| |
| |
| |
| int TOfflineGL::getLx() const |
| { |
| return m_imp->getLx(); |
| } |
| |
| |
| |
| int TOfflineGL::getLy() const |
| { |
| return m_imp->getLy(); |
| } |
| |
| |
| |
| |
| |
| namespace |
| { |
| |
| struct DimensionLess : public std::binary_function<TDimension, TDimension, bool> { |
| bool operator()(const TDimension &d1, const TDimension &d2) const |
| { |
| return d1.lx < d2.lx || (d1.lx == d2.lx && d1.ly < d2.ly); |
| } |
| }; |
| |
| |
| |
| class OglStock |
| { |
| |
| typedef std::map<const TDimension, TOfflineGL *, DimensionLess> ContextMap; |
| ContextMap m_table; |
| |
| OglStock() {} |
| |
| public: |
| ~OglStock() |
| { |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| TOfflineGL *get(const TDimension &d) |
| { |
| ContextMap::iterator it = m_table.find(d); |
| if (it == m_table.end()) { |
| TOfflineGL *glContext; |
| glContext = new TOfflineGL(d); |
| pair<ContextMap::iterator, bool> result = m_table.insert(ContextMap::value_type(d, glContext)); |
| assert(result.second); |
| assert(m_table.size() < 15); |
| return glContext; |
| } |
| return it->second; |
| } |
| |
| static OglStock *instance() |
| { |
| static OglStock singleton; |
| return &singleton; |
| } |
| }; |
| |
| } |
| |
| |
| |
| TOfflineGL *TOfflineGL::getStock(const TDimension dim) |
| { |
| return OglStock::instance()->get(dim); |
| } |
| |