// TnzCore includes #include "texception.h" #include "tvectorgl.h" #include "tvectorimage.h" #include "trasterimage.h" #include "tgl.h" #include "tthreadmessage.h" #include "tsystem.h" #include "trop.h" // Platform-specific includes #if defined(LINUX) #include #include #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(); } //============================================================================= // WIN32Implementation : implementazione offlineGL WIN32 //----------------------------------------------------------------------------- #ifdef _WIN32 namespace { //We found out that our implementation of win32 opengl contexts can be someway //not thread-safe. Deadlocks and errors could happen for wgl* and GDI functions //on particular configurations (notably, Windows 7). So we mutex them as //a temporary workaround. 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 shared) : TOfflineGL::Imp(rasterSize.lx, rasterSize.ly) { m_offData = 0; createContext(rasterSize, std::move(shared)); //makeCurrent is called at the end of this glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); doneCurrent(); //doneCurrent must therefore be called here } //----------------------------------------------------------------------------- ~WIN32Implementation() { QMutexLocker locker(&win32ImpMutex); BOOL ret = wglMakeCurrent(m_offDC, NULL); assert(ret == TRUE); wglDeleteContext(m_hglRC); SelectObject(m_offDC, m_oldobj); DeleteObject(m_offDC); // si potrebbe passare un parametro che evita di distruggere la bitmap. // In tal caso il raster dovrebbe diventare owner del buffer, ma attualmente // questo non e' settabile in TRaster: quando gli si passa da fuori un buffer, // automaticamente bufferOwner viene settato a false e non e' modificabile! 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 shared) { QMutexLocker locker(&win32ImpMutex); BITMAPINFO info; initBITMAPINFO(info, rasterSize); // open an offscreen device m_offDC = CreateCompatibleDC(NULL); // and a bitmap image 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; // number of byte of raster memset(m_offData, 0, dataSize); m_oldobj = SelectObject(m_offDC, m_offDIB); // select BIB to write static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd 1, // version number 0 | (false ? (PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER) : (PFD_DRAW_TO_BITMAP | PFD_SUPPORT_GDI)) | PFD_SUPPORT_OPENGL, // support OpenGL PFD_TYPE_RGBA, // RGBA type 32, // 32-bit color depth 0, 0, 0, 0, 0, 0, // color bits ignored 8, // no alpha buffer /*===*/ 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accum bits ignored 32, // 32-bit z-buffer 32, // max stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main layer 0, // reserved 0, 0, 0 // layer masks ignored }; // get the best available match of pixel format for the device context int iPixelFormat = ChoosePixelFormat(m_offDC, &pfd); assert(iPixelFormat != 0); // make that the pixel format of the device context int ret = SetPixelFormat(m_offDC, iPixelFormat, &pfd); assert(ret == TRUE); // make a valid context for OpenGL rendering m_hglRC = wglCreateContext(m_offDC); assert(m_hglRC); if (!m_hglRC) throw TException("cannot create OpenGL context. Check system resources!"); if (shared) { // Share shared's display lists const WIN32Implementation *sharedImp = dynamic_cast(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) // Flips The Red And Blue Bytes (WidthxHeight) { void *b = buffer; // Pointer To The 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--; } /*unsigned long ebx = (unsigned long)b; while(size>0) { unsigned char al =__readgsbyte(ebx); unsigned char ah =__readgsbyte(ebx+2); __writegsbyte(ebx+2,al); __writegsbyte(ebx,ah); ebx+=4; size--; }*/ #else __asm // Assembler Code To Follow { mov ecx, bufferSize // Counter Set To Dimensions Of Our Memory Block mov ebx, b // Points ebx To Our Data (b) label: // Label Used For Looping mov al,[ebx+0] // Loads Value At ebx Into al mov ah,[ebx+2] // Loads Value At ebx+2 Into ah mov [ebx+2],al // Stores Value In al At ebx+2 mov [ebx+0],ah // Stores Value In ah At ebx add ebx,4 // Moves Through The Data By 4 Bytes dec ecx // Decreases Our Loop Counter jnz label // If Not Zero Jump Back To 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_BGRA_EXT*/, GL_UNSIGNED_BYTE, raster->getRawData()); swapRedBlueChannels(raster->getRawData(), lx * ly); raster->unlock(); } }; // default imp generator std::shared_ptr defaultOfflineGLGenerator(const TDimension &dim, std::shared_ptr shared) { return std::make_shared(dim, shared); } //============================================================================= // XImplementation : implementazione offlineGL Server X (MACOSX & LINUX) //----------------------------------------------------------------------------- #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 m_glxContext; static TThread::Mutex mutex; QMutexLocker sl(&mutex); pthread_t self = pthread_self(); std::map::iterator it = m_glxContext.find(self); if (((it != m_glxContext.end()) && (it->second != m_context)) || (it == m_glxContext.end())) { // cout << "calling GLXMakeCurrent " << self << " " << m_context << endl; Bool ret; if (!isDtor) ret = glXMakeCurrent(m_dpy, m_pixmap, m_context); m_glxContext[self] = m_context; return ret; } // cout << "don't call GLXMakeCurrent " << self << " " << m_context << endl; return true; } //----------------------------------------------------------------------------- void makeCurrent() { XScopedLock xsl; //Bool ret = glXMakeCurrent(m_dpy,m_pixmap,m_context); Bool ret = safeGlXMakeCurrent(); assert(ret == True); } //----------------------------------------------------------------------------- // DA IMPLEMENTARE !!! 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_ALPHA_SIZE, 1, GLX_STENCIL_SIZE, 8, // GLX_DEPTH_SIZE, 24, 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); // std::cout << "Direct rendering: " << (glXIsDirect(m_dpy, m_context) ? "Yes" : "No" )<< std::endl; 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 = glXMakeCurrent(m_dpy, m_pixmap, m_context); */ 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_BGRA_EXT*/, 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 defaultOfflineGLGenerator(const TDimension &dim, std::shared_ptr shared) { return std::make_shared(dim); } #elif MACOSX std::shared_ptr defaultOfflineGLGenerator(const TDimension &dim, std::shared_ptr shared) { return std::make_shared(dim, shared); } #endif //============================================================================= //----------------------------------------------------------------------------- // current imp generator namespace { TOfflineGL::ImpGenerator *currentImpGenerator = defaultOfflineGLGenerator; } // namespace //============================================================================= // TOfflineGL //----------------------------------------------------------------------------- //namespace { class MessageCreateContext : public TThread::Message { friend class TOfflineGL; TOfflineGL *m_ogl; TDimension m_size; std::shared_ptr m_shared; public: MessageCreateContext(TOfflineGL *ogl, const TDimension &size, std::shared_ptr 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); } }; //} // namespace //-------------------------------------------------- TOfflineGL::TOfflineGL(TDimension dim, const TOfflineGL *shared) { #if defined(LINUX) XScopedLock xsl; #endif std::shared_ptr sharedImp = shared ? shared->m_imp : 0; /* 元のコードは(別スレッドから呼び出すための) offline renderer を作って main thread に dispatch するという訳のわからないことをしていたが Q*GLContext は thread context を超えられないので直接生成してこのコンテキストで閉じる. 別スレッドには dispatch しない. */ 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(); // Tutto il codice è stato spostato dentro Imp m_imp->makeCurrent(); assert(glGetError() == GL_NO_ERROR); } //----------------------------------------------------------------------------- void TOfflineGL::doneCurrent() { m_imp->doneCurrent(); if (currentContextManager) { currentContextManager->restore(); } } //----------------------------------------------------------------------------- void TOfflineGL::initMatrix() { m_imp->makeCurrent(); // cfr. help: OpenGL/Programming tip/OpenGL Correctness Tips 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(); //glTranslatef(0.375, 0.375, 0.0); //WRONG /* (From Daniele) Quoting from the aforementioned source: "An optimum compromise that allows all primitives to be specified at integer positions, while still ensuring predictable rasterization, is to translate x and y by 0.375, as shown in the following code sample. Such a translation keeps polygon and pixel image edges safely away from the centers of pixels, while moving line vertices close enough to the pixel centers" NOTE: This is not an acceptable excuse in our case - as we're NOT USING INTEGER COORDINATES ONLY. OpenGL has all the rights to render pixels at integer coordinates across the neighbouring 4 pixels - and their (0.5, 0.5) translations at the EXACT screen pixel. */ } //----------------------------------------------------------------------------- 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(); // lx e ly devono essere potenze di due 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); /* TNZ_MACHINE_CHANNEL_ORDER_BGRM TNZ_MACHINE_CHANNEL_ORDER_MBGR TNZ_MACHINE_CHANNEL_ORDER_RGBM TNZ_MACHINE_CHANNEL_ORDER_MRGB */ GLenum fmt = TGL_FMT; /* #ifdef TNZ_MACHINE_CHANNEL_ORDER_BGRM GL_BGRA_EXT; #elif TNZ_MACHINE_CHANNEL_ORDER_MBGR GL_ABGR_EXT; #elif TNZ_MACHINE_CHANNEL_ORDER_RGBM GL_RGBA; #elif TNZ_MACHINE_CHANNEL_ORDER_MRGB #warning "to do" GL_ABGR_EXT; #else Error PLATFORM NOT SUPPORTED #endif */ // Generate a texture id and bind it. 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, // target (is a 2D texture) 0, // is one level only GL_RGB, // number of component of a pixel lx, // size width ly, // height 0, // size of a border 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); // Delete texture 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 { //There are 2 possible solutions: use glReadPixels multiple times for each row of input raster, //OR allocate a new contiguous buffer, use glReadPixels once, and then memcpy each row. //It actually seems that the *latter* is actually the fastest solution, although it requires //allocating a temporary buffer of the same size of the requested raster... //I also could not actually manage to make the former work - the code seemed right but results were weird... 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 { bool operator()(const TDimension &d1, const TDimension &d2) const { return d1.lx < d2.lx || (d1.lx == d2.lx && d1.ly < d2.ly); } }; //----------------------------------------------------------------------------- class OglStock { // singleton typedef std::map ContextMap; ContextMap m_table; OglStock() {} public: ~OglStock() { /* // PER ADESSO, LASCIAMO IL MEMORY LEAK DATO CHE ALTRIMENTI VA IN CRASH ContextMap::iterator it = m_table.begin(); for(; it!=m_table.end(); ++it) { delete it->second; } */ } TOfflineGL *get(const TDimension &d) { ContextMap::iterator it = m_table.find(d); if (it == m_table.end()) { TOfflineGL *glContext; glContext = new TOfflineGL(d); pair 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; } }; } // namespace //----------------------------------------------------------------------------- TOfflineGL *TOfflineGL::getStock(const TDimension dim) { return OglStock::instance()->get(dim); }