/***** * glrender.cc * John Bowman, Orest Shardt, and Supakorn "Jamie" Rassameemasmuang * Render 3D Bezier paths and surfaces. *****/ #ifdef __CYGWIN__ #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include #include #if !defined(_WIN32) #include #include #endif #include "common.h" #include "locate.h" #include "seconds.h" #include "statistics.h" #include "bezierpatch.h" #include "beziercurve.h" #include "picture.h" #include "bbox3.h" #include "drawimage.h" #include "interact.h" #include "fpu.h" extern uint32_t CLZ(uint32_t a); bool GPUindexing; bool GPUcompress; namespace gl { #ifdef HAVE_PTHREAD pthread_t mainthread; #endif } #ifdef HAVE_GL #include "tr.h" #ifdef HAVE_LIBGLUT #ifdef __MSDOS__ #ifndef FGAPI #define FGAPI GLUTAPI #endif #ifndef FGAPIENTRY #define FGAPIENTRY APIENTRY #endif #endif #define GLUT_BUILDING_LIB #ifdef FREEGLUT #include #endif #endif // HAVE_LIBGLUT #include "shaders.h" #include "GLTextures.h" #include "EXRFiles.h" using settings::locateFile; using utils::stopWatch; #endif // HAVE_GL #ifdef HAVE_LIBGLM namespace camp { Billboard BB; GLint pixelShader; GLint materialShader[2]; GLint colorShader[2]; GLint generalShader[2]; GLint countShader; GLint transparentShader; GLint blendShader; GLint zeroShader; GLint compressShader; GLint sum1Shader; GLint sum2Shader; GLint sum2fastShader; GLint sum3Shader; GLuint fragments; GLuint offsetBuffer; GLuint indexBuffer; GLuint elementsBuffer; GLuint countBuffer; GLuint globalSumBuffer; GLuint fragmentBuffer; GLuint depthBuffer; GLuint opaqueBuffer; GLuint opaqueDepthBuffer; GLuint feedbackBuffer; bool ssbo; bool interlock; } #endif #ifdef HAVE_LIBGLM using camp::Material; using camp::Maxmaterials; using camp::Nmaterials; using camp::nmaterials; using camp::MaterialMap; namespace camp { bool initSSBO; GLuint maxFragments; vertexBuffer material0Data(GL_POINTS); vertexBuffer material1Data(GL_LINES); vertexBuffer materialData; vertexBuffer colorData; vertexBuffer transparentData; vertexBuffer triangleData; const size_t Nbuffer=10000; const size_t nbuffer=1000; std::vector materials; MaterialMap materialMap; size_t materialIndex; size_t Maxmaterials; size_t Nmaterials=1; size_t nmaterials=48; unsigned int Opaque=0; void clearCenters() { camp::drawElement::centers.clear(); camp::drawElement::centermap.clear(); } void clearMaterials() { materials.clear(); materials.reserve(nmaterials); materialMap.clear(); material0Data.partial=false; material1Data.partial=false; materialData.partial=false; colorData.partial=false; triangleData.partial=false; transparentData.partial=false; } } extern void exitHandler(int); namespace gl { GLint gs2; GLint gs; GLint g; GLuint processors; GLuint localSize; GLuint blockSize; GLuint groupSize; //GLint maxgroups; GLuint maxSize; bool outlinemode=false; bool ibl=false; bool glthread=false; bool glupdate=false; bool glexit=false; bool initialize=true; using camp::picture; using camp::drawRawImage; using camp::transform; using camp::pair; using camp::triple; using vm::array; using vm::read; using camp::bbox3; using settings::getSetting; using settings::Setting; bool Iconify=false; bool ignorezoom; int Fitscreen=1; bool firstFit; bool queueExport=false; bool readyAfterExport=false; bool remesh; bool copied; int Mode; double Aspect; bool View; bool ViewExport; int Oldpid; string Prefix; const picture* Picture; string Format; int fullWidth,fullHeight; int Width,Height; GLuint pixels; GLuint elements; GLuint lastpixels; double oWidth,oHeight; int screenWidth,screenHeight; int maxTileWidth; int maxTileHeight; double Angle; bool orthographic; double H; double xmin,xmax; double ymin,ymax; double Xmin,Xmax; double Ymin,Ymax; double Zmin,Zmax; bool haveScene; pair Shift; pair Margin; double X,Y; int x0,y0; double cx,cy; double Xfactor,Yfactor; double ArcballFactor; static const double pi=acos(-1.0); static const double degrees=180.0/pi; static const double radians=1.0/degrees; double Background[4]; size_t Nlights=1; // Maximum number of lights compiled in shader size_t nlights; // Actual number of lights size_t nlights0; triple *Lights; double *Diffuse; double *Specular; bool antialias; double Zoom; double Zoom0; double lastzoom; GLint lastshader=-1; bool format3dWait=false; using glm::dvec3; using glm::dmat3; using glm::mat3; using glm::mat4; using glm::dmat4; using glm::value_ptr; using glm::translate; using camp::interlock; using camp::ssbo; mat3 normMat; dmat3 dnormMat; mat4 projViewMat; mat4 viewMat; dmat4 dprojMat; dmat4 dprojViewMat; dmat4 dviewMat; dmat4 drotateMat; const double *dprojView; const double *dView; double BBT[9]; unsigned int framecount; template inline T min(T a, T b) { return (a < b) ? a : b; } template inline T max(T a, T b) { return (a > b) ? a : b; } glm::vec4 vec4(triple v) { return glm::vec4(v.getx(),v.gety(),v.getz(),0); } glm::vec4 vec4(double *v) { return glm::vec4(v[0],v[1],v[2],v[3]); } void setDimensions(int Width, int Height, double X, double Y) { double Aspect=((double) Width)/Height; double xshift=(X/Width+Shift.getx()*Xfactor)*Zoom; double yshift=(Y/Height+Shift.gety()*Yfactor)*Zoom; double Zoominv=1.0/Zoom; if(orthographic) { double xsize=Xmax-Xmin; double ysize=Ymax-Ymin; if(xsize < ysize*Aspect) { double r=0.5*ysize*Aspect*Zoominv; double X0=2.0*r*xshift; double Y0=(Ymax-Ymin)*Zoominv*yshift; xmin=-r-X0; xmax=r-X0; ymin=Ymin*Zoominv-Y0; ymax=Ymax*Zoominv-Y0; } else { double r=0.5*xsize*Zoominv/Aspect; double X0=(Xmax-Xmin)*Zoominv*xshift; double Y0=2.0*r*yshift; xmin=Xmin*Zoominv-X0; xmax=Xmax*Zoominv-X0; ymin=-r-Y0; ymax=r-Y0; } } else { double r=H*Zoominv; double rAspect=r*Aspect; double X0=2.0*rAspect*xshift; double Y0=2.0*r*yshift; xmin=-rAspect-X0; xmax=rAspect-X0; ymin=-r-Y0; ymax=r-Y0; } } void updateProjection() { dprojViewMat=dprojMat*dviewMat; projViewMat=mat4(dprojViewMat); dprojView=value_ptr(dprojViewMat); } void frustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal) { dprojMat=glm::frustum(left,right,bottom,top,nearVal,farVal); updateProjection(); } void ortho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal) { dprojMat=glm::ortho(left,right,bottom,top,nearVal,farVal); updateProjection(); } void setProjection() { setDimensions(Width,Height,X,Y); if(haveScene) { if(orthographic) ortho(xmin,xmax,ymin,ymax,-Zmax,-Zmin); else frustum(xmin,xmax,ymin,ymax,-Zmax,-Zmin); } } void updateModelViewData() { // Like Fortran, OpenGL uses transposed (column-major) format! dnormMat=dmat3(glm::inverse(dviewMat)); double *T=value_ptr(dnormMat); for(size_t i=0; i < 9; ++i) BBT[i]=T[i]; normMat=mat3(dnormMat); } bool Xspin,Yspin,Zspin; bool Animate; bool Step; #ifdef HAVE_GL stopWatch spinTimer; void idleFunc(void (*f)()) { spinTimer.reset(); glutIdleFunc(f); } void idle() { idleFunc(NULL); Xspin=Yspin=Zspin=Animate=Step=false; } #endif void home(bool webgl=false) { X=Y=cx=cy=0.0; #ifdef HAVE_GL #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA if(!webgl) idle(); #endif #endif #endif dviewMat=dmat4(1.0); if(!camp::ssbo) dView=value_ptr(dviewMat); viewMat=mat4(dviewMat); drotateMat=dmat4(1.0); updateModelViewData(); remesh=true; lastzoom=Zoom=Zoom0; setDimensions(Width,Height,0,0); framecount=0; } double T[16]; double Tup[16]; #ifdef HAVE_GL #ifdef HAVE_LIBGLUT int oldWidth,oldHeight; bool queueScreen=false; string Action; double lastangle; int window; #endif using utils::statistics; statistics S; GLTexture2 IBLbrdfTex; GLTexture2 irradiance; GLTexture3 reflTextures; GLTexture2 fromEXR(string const& EXRFile, GLTexturesFmt const& fmt, GLint const& textureNumber) { camp::IEXRFile fil(EXRFile); return GLTexture2 {fil.getData(),fil.size(),textureNumber,fmt}; } GLTexture3 fromEXR3( mem::vector const& EXRFiles, GLTexturesFmt const& fmt, GLint const& textureNumber) { // 3d reflectance textures std::vector data; size_t count=EXRFiles.size(); int wi=0, ht=0; for(string const& EXRFile : EXRFiles) { camp::IEXRFile fil3(EXRFile); std::tie(wi,ht)=fil3.size(); size_t imSize=4*wi*ht; std::copy(fil3.getData(),fil3.getData()+imSize,std::back_inserter(data)); } return GLTexture3 { data.data(), std::tuple(wi,ht,static_cast(count)),textureNumber, fmt }; } void initIBL() { GLTexturesFmt fmt; fmt.internalFmt=GL_RGB16F; string imageDir=locateFile(getSetting("imageDir"))+"/"; string imagePath=imageDir+getSetting("image")+"/"; irradiance=fromEXR(imagePath+"diffuse.exr",fmt,1); GLTexturesFmt fmtRefl; fmtRefl.internalFmt=GL_RG16F; IBLbrdfTex=fromEXR(imageDir+"refl.exr",fmtRefl,2); GLTexturesFmt fmt3; fmt3.internalFmt=GL_RGB16F; fmt3.wrapS=GL_REPEAT; fmt3.wrapR=GL_CLAMP_TO_EDGE; fmt3.wrapT=GL_CLAMP_TO_EDGE; mem::vector files; mem::string prefix=imagePath+"refl"; for(unsigned int i=0; i <= 10; ++i) { mem::stringstream mss; mss << prefix << i << ".exr"; files.emplace_back(mss.str()); } reflTextures=fromEXR3(files,fmt3,3); } void *glrenderWrapper(void *a); #ifdef HAVE_LIBOSMESA OSMesaContext ctx; unsigned char *osmesa_buffer; #endif #ifdef HAVE_PTHREAD pthread_cond_t initSignal=PTHREAD_COND_INITIALIZER; pthread_mutex_t initLock=PTHREAD_MUTEX_INITIALIZER; pthread_cond_t readySignal=PTHREAD_COND_INITIALIZER; pthread_mutex_t readyLock=PTHREAD_MUTEX_INITIALIZER; void endwait(pthread_cond_t& signal, pthread_mutex_t& lock) { pthread_mutex_lock(&lock); pthread_cond_signal(&signal); pthread_mutex_unlock(&lock); } void wait(pthread_cond_t& signal, pthread_mutex_t& lock) { pthread_mutex_lock(&lock); pthread_cond_signal(&signal); pthread_cond_wait(&signal,&lock); pthread_mutex_unlock(&lock); } #endif void noShaders() { cerr << "GLSL shaders not found." << endl; exit(-1); } void initComputeShaders() { string sum1=locateFile("shaders/sum1.glsl"); string sum2=locateFile("shaders/sum2.glsl"); string sum2fast=locateFile("shaders/sum2fast.glsl"); string sum3=locateFile("shaders/sum3.glsl"); if(sum1.empty() || sum2.empty() || sum2fast.empty() || sum3.empty()) noShaders(); std::vector shaders(1); std::vector shaderParams; shaders[0]=ShaderfileModePair(sum1.c_str(),GL_COMPUTE_SHADER); ostringstream s,s2; s << "LOCALSIZE " << gl::localSize << "u" << endl; shaderParams.push_back(s.str().c_str()); s2 << "BLOCKSIZE " << gl::blockSize << "u" << endl; shaderParams.push_back(s2.str().c_str()); GLuint rc=compileAndLinkShader(shaders,shaderParams,true,false,true,true); if(rc == 0) { GPUindexing=false; // Compute shaders are unavailable. if(settings::verbose > 2) cout << "No compute shader support" << endl; } else { // glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT,0,&maxgroups); // maxgroups=min(1024,maxgroups/(GLint) (localSize*localSize)); camp::sum1Shader=rc; shaders[0]=ShaderfileModePair(sum2.c_str(),GL_COMPUTE_SHADER); camp::sum2Shader=compileAndLinkShader(shaders,shaderParams,true,false, true); shaders[0]=ShaderfileModePair(sum2fast.c_str(),GL_COMPUTE_SHADER); camp::sum2fastShader=compileAndLinkShader(shaders,shaderParams,true,false, true); shaders[0]=ShaderfileModePair(sum3.c_str(),GL_COMPUTE_SHADER); camp::sum3Shader=compileAndLinkShader(shaders,shaderParams,true,false, true); } } void initBlendShader() { string screen=locateFile("shaders/screen.glsl"); string blend=locateFile("shaders/blend.glsl"); if(screen.empty() || blend.empty()) noShaders(); std::vector shaders(2); std::vector shaderParams; ostringstream s; s << "ARRAYSIZE " << maxSize << "u" << endl; shaderParams.push_back(s.str().c_str()); if(GPUindexing) shaderParams.push_back("GPUINDEXING"); if(GPUcompress) shaderParams.push_back("GPUCOMPRESS"); shaders[0]=ShaderfileModePair(screen.c_str(),GL_VERTEX_SHADER); shaders[1]=ShaderfileModePair(blend.c_str(),GL_FRAGMENT_SHADER); camp::blendShader=compileAndLinkShader(shaders,shaderParams,ssbo); } // Return the smallest power of 2 greater than or equal to n. inline GLuint ceilpow2(GLuint n) { --n; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; return ++n; } void initShaders() { Nlights=nlights == 0 ? 0 : max(Nlights,nlights); Nmaterials=max(Nmaterials,nmaterials); string zero=locateFile("shaders/zero.glsl"); string compress=locateFile("shaders/compress.glsl"); string vertex=locateFile("shaders/vertex.glsl"); string count=locateFile("shaders/count.glsl"); string fragment=locateFile("shaders/fragment.glsl"); string screen=locateFile("shaders/screen.glsl"); if(zero.empty() || compress.empty() || vertex.empty() || fragment.empty() || screen.empty() || count.empty()) noShaders(); if(GPUindexing) initComputeShaders(); std::vector shaders(2); std::vector shaderParams; if(ibl) { shaderParams.push_back("USE_IBL"); initIBL(); } shaders[0]=ShaderfileModePair(vertex.c_str(),GL_VERTEX_SHADER); #ifdef HAVE_SSBO if(GPUindexing) shaderParams.push_back("GPUINDEXING"); if(GPUcompress) shaderParams.push_back("GPUCOMPRESS"); shaders[1]=ShaderfileModePair(count.c_str(),GL_FRAGMENT_SHADER); camp::countShader=compileAndLinkShader(shaders,shaderParams, true,false,false,true); if(camp::countShader) shaderParams.push_back("HAVE_SSBO"); #else camp::countShader=0; #endif ssbo=camp::countShader; #ifdef HAVE_LIBOSMESA interlock=false; #else interlock=ssbo && getSetting("GPUinterlock"); #endif if(!ssbo && settings::verbose > 2) cout << "No SSBO support; order-independent transparency unavailable" << endl; shaders[1]=ShaderfileModePair(fragment.c_str(),GL_FRAGMENT_SHADER); shaderParams.push_back("MATERIAL"); if(orthographic) shaderParams.push_back("ORTHOGRAPHIC"); ostringstream lights,materials,opaque; lights << "Nlights " << Nlights; shaderParams.push_back(lights.str().c_str()); materials << "Nmaterials " << Nmaterials; shaderParams.push_back(materials.str().c_str()); shaderParams.push_back("WIDTH"); camp::pixelShader=compileAndLinkShader(shaders,shaderParams,ssbo); shaderParams.pop_back(); shaderParams.push_back("NORMAL"); if(interlock) shaderParams.push_back("HAVE_INTERLOCK"); camp::materialShader[0]=compileAndLinkShader(shaders,shaderParams, ssbo,interlock,false,true); if(interlock && !camp::materialShader[0]) { shaderParams.pop_back(); interlock=false; camp::materialShader[0]=compileAndLinkShader(shaders,shaderParams,ssbo); if(settings::verbose > 2) cout << "No fragment shader interlock support" << endl; } shaderParams.push_back("OPAQUE"); camp::materialShader[1]=compileAndLinkShader(shaders,shaderParams,ssbo); shaderParams.pop_back(); shaderParams.push_back("COLOR"); camp::colorShader[0]=compileAndLinkShader(shaders,shaderParams,ssbo, interlock); shaderParams.push_back("OPAQUE"); camp::colorShader[1]=compileAndLinkShader(shaders,shaderParams,ssbo); shaderParams.pop_back(); shaderParams.push_back("GENERAL"); if(Mode != 0) shaderParams.push_back("WIREFRAME"); camp::generalShader[0]=compileAndLinkShader(shaders,shaderParams,ssbo, interlock); shaderParams.push_back("OPAQUE"); camp::generalShader[1]=compileAndLinkShader(shaders,shaderParams,ssbo); shaderParams.pop_back(); shaderParams.push_back("TRANSPARENT"); camp::transparentShader=compileAndLinkShader(shaders,shaderParams,ssbo, interlock); shaderParams.clear(); if(ssbo) { if(GPUindexing) shaderParams.push_back("GPUINDEXING"); shaders[0]=ShaderfileModePair(screen.c_str(),GL_VERTEX_SHADER); shaders[1]=ShaderfileModePair(compress.c_str(),GL_FRAGMENT_SHADER); camp::compressShader=compileAndLinkShader(shaders,shaderParams,ssbo); if(GPUindexing) shaderParams.pop_back(); else { shaders[1]=ShaderfileModePair(zero.c_str(),GL_FRAGMENT_SHADER); camp::zeroShader=compileAndLinkShader(shaders,shaderParams,ssbo); } maxSize=1; initBlendShader(); } lastshader=-1; } void deleteComputeShaders() { glDeleteProgram(camp::sum1Shader); glDeleteProgram(camp::sum2Shader); glDeleteProgram(camp::sum2fastShader); glDeleteProgram(camp::sum3Shader); } void deleteBlendShader() { glDeleteProgram(camp::blendShader); } void deleteShaders() { if(camp::ssbo) { deleteBlendShader(); if(GPUindexing) deleteComputeShaders(); else glDeleteProgram(camp::zeroShader); glDeleteProgram(camp::countShader); glDeleteProgram(camp::compressShader); } glDeleteProgram(camp::transparentShader); for(unsigned int opaque=0; opaque < 2; ++opaque) { glDeleteProgram(camp::generalShader[opaque]); glDeleteProgram(camp::colorShader[opaque]); glDeleteProgram(camp::materialShader[opaque]); } glDeleteProgram(camp::pixelShader); } void resizeBlendShader(GLuint maxsize) { gl::maxSize=ceilpow2(maxsize); gl::deleteBlendShader(); gl::initBlendShader(); } void setBuffers() { GLuint vao; glGenVertexArrays(1,&vao); glBindVertexArray(vao); camp::material0Data.reserve0(); camp::materialData.reserve(); camp::colorData.Reserve(); camp::triangleData.Reserve(); camp::transparentData.Reserve(); #ifdef HAVE_SSBO glGenBuffers(1, &camp::offsetBuffer); if(GPUindexing) { glGenBuffers(1, &camp::globalSumBuffer); glGenBuffers(1, &camp::feedbackBuffer); } glGenBuffers(1, &camp::countBuffer); if(GPUcompress) { glGenBuffers(1, &camp::indexBuffer); glGenBuffers(1, &camp::elementsBuffer); } glGenBuffers(1, &camp::fragmentBuffer); glGenBuffers(1, &camp::depthBuffer); glGenBuffers(1, &camp::opaqueBuffer); glGenBuffers(1, &camp::opaqueDepthBuffer); #endif } void drawscene(int Width, int Height) { #ifdef HAVE_PTHREAD static bool first=true; if(glthread && first) { wait(initSignal,initLock); endwait(initSignal,initLock); first=false; } if(format3dWait) wait(initSignal,initLock); #endif if((nlights == 0 && Nlights > 0) || nlights > Nlights || nmaterials > Nmaterials) { deleteShaders(); initShaders(); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if(xmin >= xmax || ymin >= ymax || Zmin >= Zmax) return; triple m(xmin,ymin,Zmin); triple M(xmax,ymax,Zmax); double perspective=orthographic ? 0.0 : 1.0/Zmax; double size2=hypot(Width,Height); if(remesh) camp::clearCenters(); Picture->render(size2,m,M,perspective,remesh); if(!outlinemode) remesh=false; } // Return x divided by y rounded up to the nearest integer. int ceilquotient(int x, int y) { return (x+y-1)/y; } bool exporting=false; void Export() { size_t ndata=3*fullWidth*fullHeight; if(ndata == 0) return; glReadBuffer(GL_BACK_LEFT); glPixelStorei(GL_PACK_ALIGNMENT,1); glFinish(); exporting=true; try { unsigned char *data=new unsigned char[ndata]; if(data) { TRcontext *tr=trNew(); int width=ceilquotient(fullWidth, ceilquotient(fullWidth,min(maxTileWidth,Width))); int height=ceilquotient(fullHeight, ceilquotient(fullHeight, min(maxTileHeight,Height))); if(settings::verbose > 1) cout << "Exporting " << Prefix << " as " << fullWidth << "x" << fullHeight << " image" << " using tiles of size " << width << "x" << height << endl; unsigned border=min(min(1,width/2),height/2); trTileSize(tr,width,height,border); trImageSize(tr,fullWidth,fullHeight); trImageBuffer(tr,GL_RGB,GL_UNSIGNED_BYTE,data); setDimensions(fullWidth,fullHeight,X/Width*fullWidth,Y/Width*fullWidth); size_t count=0; if(haveScene) { (orthographic ? trOrtho : trFrustum)(tr,xmin,xmax,ymin,ymax,-Zmax,-Zmin); do { trBeginTile(tr); remesh=true; drawscene(fullWidth,fullHeight); gl::lastshader=-1; ++count; } while (trEndTile(tr)); } else {// clear screen and return drawscene(fullWidth,fullHeight); } if(settings::verbose > 1) cout << count << " tile" << (count != 1 ? "s" : "") << " drawn" << endl; trDelete(tr); picture pic; drawRawImage *Image=NULL; if(haveScene) { double w=oWidth; double h=oHeight; double Aspect=((double) fullWidth)/fullHeight; if(w > h*Aspect) w=(int) (h*Aspect+0.5); else h=(int) (w/Aspect+0.5); // Render an antialiased image. Image=new drawRawImage(data,fullWidth,fullHeight, transform(0.0,0.0,w,0.0,0.0,h), antialias); pic.append(Image); } pic.shipout(NULL,Prefix,Format,false,ViewExport); if(Image) delete Image; delete[] data; } } catch(handled_error const&) { } catch(std::bad_alloc&) { outOfMemory(); } remesh=true; setProjection(); #ifndef HAVE_LIBOSMESA #ifdef HAVE_LIBGLUT glutPostRedisplay(); #endif #ifdef HAVE_PTHREAD if(glthread && readyAfterExport) { readyAfterExport=false; endwait(readySignal,readyLock); } #endif #endif exporting=false; camp::initSSBO=true; } void nodisplay() { } void destroywindow() { glutDestroyWindow(glutGetWindow()); } // Return the greatest power of 2 less than or equal to n. inline unsigned int floorpow2(unsigned int n) { n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; return n-(n >> 1); } void quit() { #ifdef HAVE_LIBOSMESA if(osmesa_buffer) delete[] osmesa_buffer; if(ctx) OSMesaDestroyContext(ctx); exit(0); #endif #ifdef HAVE_LIBGLUT if(glthread) { bool animating=getSetting("animating"); if(animating) Setting("interrupt")=true; home(); Animate=getSetting("autoplay"); #ifdef HAVE_PTHREAD if(!interact::interactive || animating) { idle(); glutDisplayFunc(nodisplay); endwait(readySignal,readyLock); } #endif if(interact::interactive) glutHideWindow(); } else { glutDestroyWindow(window); exit(0); } #endif } void mode() { remesh=true; if(camp::ssbo) camp::initSSBO=true; ++Mode; if(Mode > 2) Mode=0; switch(Mode) { case 0: // regular outlinemode=false; ibl=getSetting("ibl"); nlights=nlights0; lastshader=-1; glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); break; case 1: // outline outlinemode=true; ibl=false; nlights=0; // Force shader recompilation glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); break; case 2: // wireframe outlinemode=false; Nlights=1; // Force shader recompilation break; } #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA glutPostRedisplay(); #endif #endif } // GUI-related functions #ifdef HAVE_LIBGLUT bool capsize(int& width, int& height) { bool resize=false; if(width > screenWidth) { width=screenWidth; resize=true; } if(height > screenHeight) { height=screenHeight; resize=true; } return resize; } void reshape0(int width, int height) { X=(X/Width)*width; Y=(Y/Height)*height; Width=width; Height=height; static int lastWidth=1; static int lastHeight=1; if(View && Width*Height > 1 && (Width != lastWidth || Height != lastHeight) && settings::verbose > 1) { cout << "Rendering " << stripDir(Prefix) << " as " << Width << "x" << Height << " image" << endl; lastWidth=Width; lastHeight=Height; } setProjection(); glViewport(0,0,Width,Height); if(camp::ssbo) camp::initSSBO=true; } void windowposition(int& x, int& y, int width=Width, int height=Height) { pair z=getSetting("position"); x=(int) z.getx(); y=(int) z.gety(); if(x < 0) { x += screenWidth-width; if(x < 0) x=0; } if(y < 0) { y += screenHeight-height; if(y < 0) y=0; } } void setsize(int w, int h, bool reposition=true) { int x,y; capsize(w,h); if(reposition) { windowposition(x,y,w,h); glutPositionWindow(x,y); } else glutPositionWindow(max(glutGet(GLUT_WINDOW_X)-2,0), max(glutGet(GLUT_WINDOW_Y)-2,0)); glutReshapeWindow(w,h); reshape0(w,h); glutPostRedisplay(); } void capzoom() { static double maxzoom=sqrt(DBL_MAX); static double minzoom=1.0/maxzoom; if(Zoom <= minzoom) Zoom=minzoom; if(Zoom >= maxzoom) Zoom=maxzoom; if(Zoom != lastzoom) remesh=true; lastzoom=Zoom; } void fullscreen(bool reposition=true) { Width=screenWidth; Height=screenHeight; if(firstFit) { if(Width < Height*Aspect) Zoom *= Width/(Height*Aspect); capzoom(); setProjection(); firstFit=false; } Xfactor=((double) screenHeight)/Height; Yfactor=((double) screenWidth)/Width; reshape0(Width,Height); if(reposition) glutPositionWindow(0,0); glutReshapeWindow(Width,Height); glutPostRedisplay(); } void fitscreen(bool reposition=true) { if(Animate && Fitscreen == 2) Fitscreen=0; switch(Fitscreen) { case 0: // Original size { Xfactor=Yfactor=1.0; double pixelRatio=getSetting("devicepixelratio"); setsize(oldWidth*pixelRatio,oldHeight*pixelRatio,reposition); break; } case 1: // Fit to screen in one dimension { int w=screenWidth; int h=screenHeight; if(w > h*Aspect) w=min((int) ceil(h*Aspect),w); else h=min((int) ceil(w/Aspect),h); setsize(w,h,reposition); break; } case 2: // Full screen { fullscreen(reposition); break; } } } void togglefitscreen() { ++Fitscreen; if(Fitscreen > 2) Fitscreen=0; fitscreen(); } void screen() { if(glthread && !interact::interactive) fitscreen(false); } stopWatch frameTimer; void nextframe() { #ifdef HAVE_PTHREAD endwait(readySignal,readyLock); #endif double delay=getSetting("framerate"); if(delay != 0.0) delay=1.0/delay; double seconds=frameTimer.seconds(true); delay -= seconds; if(delay > 0) { std::this_thread::sleep_for(std::chrono::duration(delay)); } if(Step) Animate=false; } stopWatch Timer; void display() { if(queueScreen) { if(!Animate) screen(); queueScreen=false; } bool fps=settings::verbose > 2; drawscene(Width,Height); if(fps) { if(framecount < 20) // Measure steady-state framerate Timer.reset(); else { double s=Timer.seconds(true); if(s > 0.0) { double rate=1.0/s; S.add(rate); if(framecount % 20 == 0) cout << "FPS=" << rate << "\t" << S.mean() << " +/- " << S.stdev() << endl; } } ++framecount; } glutSwapBuffers(); #ifdef HAVE_PTHREAD if(glthread && Animate) { queueExport=false; nextframe(); } #endif if(queueExport) { Export(); queueExport=false; } if(!glthread) { #if !defined(_WIN32) if(Oldpid != 0 && waitpid(Oldpid,NULL,WNOHANG) != Oldpid) { kill(Oldpid,SIGHUP); Oldpid=0; } #endif } } void update() { glutDisplayFunc(display); glutShowWindow(); if(Zoom != lastzoom) remesh=true; lastzoom=Zoom; double cz=0.5*(Zmin+Zmax); dviewMat=translate(translate(dmat4(1.0),dvec3(cx,cy,cz))*drotateMat, dvec3(0,0,-cz)); if(!camp::ssbo) dView=value_ptr(dviewMat); viewMat=mat4(dviewMat); setProjection(); updateModelViewData(); glutPostRedisplay(); } void updateHandler(int) { queueScreen=true; remesh=true; update(); if(interact::interactive || !Animate) { glutShowWindow(); } } void poll(int) { if(glupdate) { updateHandler(0); glupdate=false; } if(glexit) { exitHandler(0); glexit=false; } glutTimerFunc(100.0,poll,0); } void animate() { Animate=!Animate; if(Animate) { if(Fitscreen == 2) { togglefitscreen(); togglefitscreen(); } update(); } else idle(); } void reshape(int width, int height) { if(glthread) { static bool initialize=true; if(initialize) { initialize=false; #if !defined(_WIN32) Signal(SIGUSR1,updateHandler); #endif } } if(capsize(width,height)) glutReshapeWindow(width,height); reshape0(width,height); remesh=true; } void shift(int x, int y) { double Zoominv=1.0/Zoom; X += (x-x0)*Zoominv; Y += (y0-y)*Zoominv; x0=x; y0=y; update(); } void pan(int x, int y) { if(orthographic) shift(x,y); else { cx += (x-x0)*(xmax-xmin)/Width; cy += (y0-y)*(ymax-ymin)/Height; x0=x; y0=y; update(); } } void zoom(int x, int y) { if(ignorezoom) {ignorezoom=false; y0=y; return;} double zoomFactor=getSetting("zoomfactor"); if(zoomFactor > 0.0) { double zoomStep=getSetting("zoomstep"); const double limit=log(0.1*DBL_MAX)/log(zoomFactor); double stepPower=zoomStep*(y0-y); if(fabs(stepPower) < limit) { Zoom *= pow(zoomFactor,stepPower); capzoom(); y0=y; setProjection(); glutPostRedisplay(); } } } void mousewheel(int wheel, int direction, int x, int y) { double zoomFactor=getSetting("zoomfactor"); if(zoomFactor > 0.0) { if(direction > 0) Zoom *= zoomFactor; else Zoom /= zoomFactor; capzoom(); setProjection(); glutPostRedisplay(); } } struct arcball { double angle; triple axis; arcball(double x0, double y0, double x, double y) { triple v0=norm(x0,y0); triple v1=norm(x,y); double Dot=dot(v0,v1); angle=Dot > 1.0 ? 0.0 : Dot < -1.0 ? pi : acos(Dot); axis=unit(cross(v0,v1)); } triple norm(double x, double y) { double norm=hypot(x,y); if(norm > 1.0) { double denom=1.0/norm; x *= denom; y *= denom; } return triple(x,y,sqrt(max(1.0-x*x-y*y,0.0))); } }; inline double glx(int x) { return 2.0*x/Width-1.0; } inline double gly(int y) { return 1.0-2.0*y/Height; } void rotate(int x, int y) { if(x != x0 || y != y0) { arcball A(glx(x0),gly(y0),glx(x),gly(y)); triple v=A.axis; drotateMat=glm::rotate(2*A.angle/Zoom*ArcballFactor, glm::dvec3(v.getx(),v.gety(),v.getz()))* drotateMat; x0=x; y0=y; update(); } } double Degrees(int x, int y) { return atan2(0.5*Height-y-Y,x-0.5*Width-X)*degrees; } void rotateX(double step) { dmat4 tmpRot(1.0); tmpRot=glm::rotate(tmpRot,glm::radians(step),dvec3(1,0,0)); drotateMat=tmpRot*drotateMat; update(); } void rotateY(double step) { dmat4 tmpRot(1.0); tmpRot=glm::rotate(tmpRot,glm::radians(step),dvec3(0,1,0)); drotateMat=tmpRot*drotateMat; update(); } void rotateZ(double step) { dmat4 tmpRot(1.0); tmpRot=glm::rotate(tmpRot,glm::radians(step),dvec3(0,0,1)); drotateMat=tmpRot*drotateMat; update(); } void rotateX(int x, int y) { double angle=Degrees(x,y); rotateX(angle-lastangle); lastangle=angle; } void rotateY(int x, int y) { double angle=Degrees(x,y); rotateY(angle-lastangle); lastangle=angle; } void rotateZ(int x, int y) { double angle=Degrees(x,y); rotateZ(angle-lastangle); lastangle=angle; } #ifndef GLUT_WHEEL_UP #define GLUT_WHEEL_UP 3 #endif #ifndef GLUT_WHEEL_DOWN #define GLUT_WHEEL_DOWN 4 #endif string action(int button, int mod) { size_t Button; size_t nButtons=5; switch(button) { case GLUT_LEFT_BUTTON: Button=0; break; case GLUT_MIDDLE_BUTTON: Button=1; break; case GLUT_RIGHT_BUTTON: Button=2; break; case GLUT_WHEEL_UP: Button=3; break; case GLUT_WHEEL_DOWN: Button=4; break; default: Button=nButtons; } size_t Mod; size_t nMods=4; switch(mod) { case 0: Mod=0; break; case GLUT_ACTIVE_SHIFT: Mod=1; break; case GLUT_ACTIVE_CTRL: Mod=2; break; case GLUT_ACTIVE_ALT: Mod=3; break; default: Mod=nMods; } if(Button < nButtons) { array *left=getSetting("leftbutton"); array *middle=getSetting("middlebutton"); array *right=getSetting("rightbutton"); array *wheelup=getSetting("wheelup"); array *wheeldown=getSetting("wheeldown"); array *Buttons[]={left,middle,right,wheelup,wheeldown}; array *a=Buttons[button]; size_t size=checkArray(a); if(Mod < size) return read(a,Mod); } return ""; } void timeout(int) { } void mouse(int button, int state, int x, int y) { int mod=glutGetModifiers(); string Action=action(button,mod); if(Action == "zoomin") { glutMotionFunc(NULL); mousewheel(0,1,x,y); return; } if(Action == "zoomout") { glutMotionFunc(NULL); mousewheel(0,-1,x,y); return; } if(state == GLUT_DOWN) { if(Action == "rotate") { x0=x; y0=y; glutMotionFunc(rotate); } else if(Action == "shift") { x0=x; y0=y; glutMotionFunc(shift); } else if(Action == "pan") { x0=x; y0=y; glutMotionFunc(pan); } else if(Action == "zoom" || Action == "zoom/menu") { y0=y; glutMotionFunc(zoom); } else if(Action == "rotateX") { lastangle=Degrees(x,y); glutMotionFunc(rotateX); } else if(Action == "rotateY") { lastangle=Degrees(x,y); glutMotionFunc(rotateY); } else if(Action == "rotateZ") { lastangle=Degrees(x,y); glutMotionFunc(rotateZ); } } else { glutMotionFunc(NULL); } } double spinstep() { return getSetting("spinstep")*spinTimer.seconds(true); } void xspin() { rotateX(spinstep()); } void yspin() { rotateY(spinstep()); } void zspin() { rotateZ(spinstep()); } void expand() { double resizeStep=getSetting("resizestep"); if(resizeStep > 0.0) setsize((int) (Width*resizeStep+0.5),(int) (Height*resizeStep+0.5)); } void shrink() { double resizeStep=getSetting("resizestep"); if(resizeStep > 0.0) setsize(max((int) (Width/resizeStep+0.5),1), max((int) (Height/resizeStep+0.5),1)); } void spinx() { if(Xspin) idle(); else { idleFunc(xspin); Xspin=true; Yspin=Zspin=false; } } void spiny() { if(Yspin) idle(); else { idleFunc(yspin); Yspin=true; Xspin=Zspin=false; } } void spinz() { if(Zspin) idle(); else { idleFunc(zspin); Zspin=true; Xspin=Yspin=false; } } void showCamera() { projection P=camera(); string projection=P.orthographic ? "orthographic(" : "perspective("; string indent(2+projection.length(),' '); cout << endl << "currentprojection=" << endl << " " << projection << "camera=" << P.camera << "," << endl << indent << "up=" << P.up << "," << endl << indent << "target=" << P.target << "," << endl << indent << "zoom=" << P.zoom; if(!orthographic) cout << "," << endl << indent << "angle=" << P.angle; if(P.viewportshift != pair(0.0,0.0)) cout << "," << endl << indent << "viewportshift=" << P.viewportshift*Zoom; if(orthographic) cout << ",center=false"; else cout << "," << endl << indent << "autoadjust=false"; cout << ");" << endl; } void keyboard(unsigned char key, int x, int y) { switch(key) { case 'h': home(); update(); break; case 'f': togglefitscreen(); break; case 'x': spinx(); break; case 'y': spiny(); break; case 'z': spinz(); break; case 's': idle(); break; case 'm': mode(); break; case 'e': Export(); break; case 'c': showCamera(); break; case '+': case '=': case '>': expand(); break; case '-': case '_': case '<': shrink(); break; case 'p': if(getSetting("reverse")) Animate=false; Setting("reverse")=Step=false; animate(); break; case 'r': if(!getSetting("reverse")) Animate=false; Setting("reverse")=true; Step=false; animate(); break; case ' ': Step=true; animate(); break; case 17: // Ctrl-q case 'q': if(!Format.empty()) Export(); quit(); break; } } void setosize() { oldWidth=(int) ceil(oWidth); oldHeight=(int) ceil(oHeight); } #endif // end of GUI-related functions void exportHandler(int=0) { #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA if(!Iconify) glutShowWindow(); #endif #endif readyAfterExport=true; Export(); #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA if(!Iconify) glutHideWindow(); glutDisplayFunc(nodisplay); #endif #endif } static bool glinitialize=true; projection camera(bool user) { if(glinitialize) return projection(); camp::Triple vCamera,vUp,vTarget; double cz=0.5*(Zmin+Zmax); double *Rotate=value_ptr(drotateMat); if(user) { for(int i=0; i < 3; ++i) { double sumCamera=0.0, sumTarget=0.0, sumUp=0.0; int i4=4*i; for(int j=0; j < 4; ++j) { int j4=4*j; double R0=Rotate[j4]; double R1=Rotate[j4+1]; double R2=Rotate[j4+2]; double R3=Rotate[j4+3]; double T4ij=T[i4+j]; sumCamera += T4ij*(R3-cx*R0-cy*R1-cz*R2); sumUp += Tup[i4+j]*R1; sumTarget += T4ij*(R3-cx*R0-cy*R1); } vCamera[i]=sumCamera; vUp[i]=sumUp; vTarget[i]=sumTarget; } } else { for(int i=0; i < 3; ++i) { int i4=4*i; double R0=Rotate[i4]; double R1=Rotate[i4+1]; double R2=Rotate[i4+2]; double R3=Rotate[i4+3]; vCamera[i]=R3-cx*R0-cy*R1-cz*R2; vUp[i]=R1; vTarget[i]=R3-cx*R0-cy*R1; } } return projection(orthographic,vCamera,vUp,vTarget,Zoom, 2.0*atan(tan(0.5*Angle)/Zoom)/radians, pair(X/Width+Shift.getx(), Y/Height+Shift.gety())); } void init() { #ifdef HAVE_LIBGLUT mem::vector cmd; cmd.push_back(settings::argv0); if(!interact::interactive && Iconify) cmd.push_back("-iconic"); push_split(cmd,getSetting("glOptions")); char **argv=args(cmd,true); int argc=cmd.size(); #ifndef __APPLE__ glutInitContextVersion(4,0); glutInitContextProfile(GLUT_CORE_PROFILE); #endif fpu_trap(false); // Work around FE_INVALID glutInit(&argc,argv); fpu_trap(settings::trap()); #ifdef FREEGLUT glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE,GLUT_ACTION_GLUTMAINLOOP_RETURNS); #endif screenWidth=glutGet(GLUT_SCREEN_WIDTH); screenHeight=glutGet(GLUT_SCREEN_HEIGHT); #endif } void init_osmesa() { #ifdef HAVE_LIBOSMESA // create context and buffer if(settings::verbose > 1) cout << "Allocating osmesa_buffer of size " << screenWidth << "x" << screenHeight << "x4x" << sizeof(GLubyte) << endl; osmesa_buffer=new unsigned char[screenWidth*screenHeight*4*sizeof(GLubyte)]; if(!osmesa_buffer) { cerr << "Cannot allocate image buffer." << endl; exit(-1); } const int attribs[]={ OSMESA_FORMAT,OSMESA_RGBA, OSMESA_DEPTH_BITS,16, OSMESA_STENCIL_BITS,0, OSMESA_ACCUM_BITS,0, OSMESA_PROFILE,OSMESA_COMPAT_PROFILE, OSMESA_CONTEXT_MAJOR_VERSION,4, OSMESA_CONTEXT_MINOR_VERSION,3, 0,0 }; ctx=OSMesaCreateContextAttribs(attribs,NULL); if(!ctx) { ctx=OSMesaCreateContextExt(OSMESA_RGBA,16,0,0,NULL); if(!ctx) { cerr << "OSMesaCreateContextExt failed." << endl; exit(-1); } } if(!OSMesaMakeCurrent(ctx,osmesa_buffer,GL_UNSIGNED_BYTE, screenWidth,screenHeight )) { cerr << "OSMesaMakeCurrent failed." << endl; exit(-1); } int z=0, s=0, a=0; glGetIntegerv(GL_DEPTH_BITS,&z); glGetIntegerv(GL_STENCIL_BITS,&s); glGetIntegerv(GL_ACCUM_RED_BITS,&a); if(settings::verbose > 1) cout << "Offscreen context settings: Depth=" << z << " Stencil=" << s << " Accum=" << a << endl; if(z <= 0) { cerr << "Error initializing offscreen context: Depth=" << z << endl; exit(-1); } #endif // HAVE_LIBOSMESA } bool NVIDIA() { #ifdef GL_SHADING_LANGUAGE_VERSION const char *GLSL_VERSION=(const char *) glGetString(GL_SHADING_LANGUAGE_VERSION); #else const char *GLSL_VERSION=""; #endif return string(GLSL_VERSION).find("NVIDIA") != string::npos; } #endif /* HAVE_GL */ // angle=0 means orthographic. void glrender(GLRenderArgs const& args, int oldpid) { Iconify=getSetting("iconify"); auto zoomVal=std::fpclassify(args.zoom) == FP_NORMAL ? args.zoom : 1.0; Prefix=args.prefix; Picture=args.pic; Format=args.format; nlights0=nlights=args.nlights; Lights=args.lights; Diffuse=args.diffuse; Specular=args.specular; View=args.view; Angle=args.angle*radians; Zoom0=zoomVal; Oldpid=oldpid; Shift=args.shift/zoomVal; Margin=args.margin; for(size_t i=0; i < 4; ++i) Background[i]=args.background[i]; Xmin=args.m.getx(); Xmax=args.M.getx(); Ymin=args.m.gety(); Ymax=args.M.gety(); Zmin=args.m.getz(); Zmax=args.M.getz(); haveScene=Xmin < Xmax && Ymin < Ymax && Zmin < Zmax; orthographic=Angle == 0.0; H=orthographic ? 0.0 : -tan(0.5*Angle)*Zmax; ignorezoom=false; Xfactor=Yfactor=1.0; pair maxtile=getSetting("maxtile"); maxTileWidth=(int) maxtile.getx(); maxTileHeight=(int) maxtile.gety(); if(maxTileWidth <= 0) maxTileWidth=1024; if(maxTileHeight <= 0) maxTileHeight=768; bool v3d=args.format == "v3d"; bool webgl=args.format == "html"; bool format3d=webgl || v3d; #ifdef HAVE_GL #ifdef HAVE_PTHREAD #ifndef HAVE_LIBOSMESA static bool initializedView=false; #endif #endif #ifdef HAVE_LIBOSMESA if(!webgl) { screenWidth=maxTileWidth; screenHeight=maxTileHeight; static bool osmesa_initialized=false; if(!osmesa_initialized) { osmesa_initialized=true; fpu_trap(false); // Work around FE_INVALID. init_osmesa(); fpu_trap(settings::trap()); } } #else if(glinitialize) { if(!format3d) init(); Fitscreen=1; } #endif #endif for(int i=0; i < 16; ++i) T[i]=args.t[i]; for(int i=0; i < 16; ++i) Tup[i]=args.tup[i]; static bool initialized=false; if(!(initialized && (interact::interactive || getSetting("animating")))) { antialias=getSetting("antialias") > 1; double expand; if(format3d) expand=1.0; else { expand=getSetting("render"); if(expand < 0) expand *= (Format.empty() || Format == "eps" || Format == "pdf") ? -2.0 : -1.0; if(antialias) expand *= 2.0; } oWidth=args.width; oHeight=args.height; Aspect=args.width/args.height; // Force a hard viewport limit to work around direct rendering bugs. // Alternatively, one can use -glOptions=-indirect (with a performance // penalty). pair maxViewport=getSetting("maxviewport"); int maxWidth=maxViewport.getx() > 0 ? (int) ceil(maxViewport.getx()) : screenWidth; int maxHeight=maxViewport.gety() > 0 ? (int) ceil(maxViewport.gety()) : screenHeight; if(maxWidth <= 0) maxWidth=max(maxHeight,2); if(maxHeight <= 0) maxHeight=max(maxWidth,2); if(screenWidth <= 0) screenWidth=maxWidth; else screenWidth=min(screenWidth,maxWidth); if(screenHeight <= 0) screenHeight=maxHeight; else screenHeight=min(screenHeight,maxHeight); fullWidth=(int) ceil(expand*args.width); fullHeight=(int) ceil(expand*args.height); if(format3d) { Width=fullWidth; Height=fullHeight; } else { Width=min(fullWidth,screenWidth); Height=min(fullHeight,screenHeight); if(Width > Height*Aspect) Width=min((int) (ceil(Height*Aspect)),screenWidth); else Height=min((int) (ceil(Width/Aspect)),screenHeight); } home(format3d); setProjection(); if(format3d) { remesh=true; return; } camp::maxFragments=0; ArcballFactor=1+8.0*hypot(Margin.getx(),Margin.gety())/hypot(Width,Height); #ifdef HAVE_GL Aspect=((double) Width)/Height; if(maxTileWidth <= 0) maxTileWidth=screenWidth; if(maxTileHeight <= 0) maxTileHeight=screenHeight; #ifdef HAVE_LIBGLUT setosize(); #endif #endif } #ifdef HAVE_GL bool havewindow=initialized && glthread; #ifndef HAVE_LIBOSMESA #ifdef HAVE_LIBGLUT unsigned int displaymode=GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH; #endif #ifdef __APPLE__ displaymode |= GLUT_3_2_CORE_PROFILE; #endif #endif if(glthread && format3d) format3dWait=true; camp::clearMaterials(); #ifndef HAVE_LIBOSMESA #ifdef HAVE_PTHREAD if(glthread && initializedView) { if(View) { #ifdef __MSDOS__ // Signals are unreliable in MSWindows glupdate=true; #else pthread_kill(mainthread,SIGUSR1); #endif } else readyAfterExport=queueExport=true; return; } #endif #ifdef HAVE_LIBGLUT if(View) { int x,y; if(havewindow) glutDestroyWindow(window); windowposition(x,y); glutInitWindowPosition(x,y); glutInitWindowSize(1,1); Int multisample=getSetting("multisample"); if(multisample <= 1) multisample=0; if(multisample) displaymode |= GLUT_MULTISAMPLE; glutInitDisplayMode(displaymode); int samples; #ifdef FREEGLUT #ifdef GLUT_INIT_MAJOR_VERSION for(;;) { if(multisample > 0) glutSetOption(GLUT_MULTISAMPLE,multisample); #endif #endif string title=string(PACKAGE_NAME)+": "+args.prefix; fpu_trap(false); // Work around FE_INVALID window=glutCreateWindow(title.c_str()); fpu_trap(settings::trap()); GLint samplebuf[1]; glGetIntegerv(GL_SAMPLES,samplebuf); samples=samplebuf[0]; #ifdef FREEGLUT #ifdef GLUT_INIT_MAJOR_VERSION if(samples < multisample) { multisample=floorpow2(multisample-1); if(multisample > 1) { glutReshapeWindow(1,1); glutDisplayFunc(destroywindow); glutShowWindow(); glutMainLoopEvent(); continue; } } break; } #endif #endif if(settings::verbose > 1 && samples > 1) cout << "Multisampling enabled with sample width " << samples << endl; glutDisplayFunc(display); glutShowWindow(); } else if(!havewindow) { glutInitWindowSize(maxTileWidth,maxTileHeight); glutInitDisplayMode(displaymode); fpu_trap(false); // Work around FE_INVALID window=glutCreateWindow(Iconify ? "" : "Asymptote rendering window" ); fpu_trap(settings::trap()); glutHideWindow(); } #endif // HAVE_LIBGLUT #endif // HAVE_LIBOSMESA initialized=true; #if defined(HAVE_COMPUTE_SHADER) && !defined(HAVE_LIBOSMESA) GPUindexing=getSetting("GPUindexing"); GPUcompress=getSetting("GPUcompress"); #else GPUindexing=false; GPUcompress=false; #endif GLint val; glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,&val); if(GPUindexing) { gl::localSize=getSetting("GPUlocalSize"); gl::blockSize=getSetting("GPUblockSize"); gl::groupSize=gl::localSize*gl::blockSize; } Maxmaterials=val/sizeof(Material); if(nmaterials > Maxmaterials) nmaterials=Maxmaterials; if(glinitialize) { glinitialize=false; const char *GLSL_VERSION=(const char *) glGetString(GL_SHADING_LANGUAGE_VERSION); GLSLversion=(int) (100*atof(GLSL_VERSION)+0.5); if(GLSLversion < 130) { cerr << "Unsupported GLSL version: " << GLSL_VERSION << "." << endl; exit(-1); } if(settings::verbose > 2) cout << "GLSL version " << GLSL_VERSION << endl; int result = glewInit(); if(result != GLEW_OK) { cerr << "GLEW initialization error." << endl; exit(-1); } ibl=getSetting("ibl"); initShaders(); setBuffers(); } glClearColor(args.background[0],args.background[1],args.background[2],args.background[3]); #ifdef HAVE_LIBGLUT #ifndef HAVE_LIBOSMESA Animate=getSetting("autoplay") && glthread; if(View) { if(!getSetting("fitscreen")) Fitscreen=0; firstFit=true; fitscreen(); setosize(); } #endif #endif glEnable(GL_DEPTH_TEST); glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); glEnable(GL_TEXTURE_3D); if(!camp::ssbo) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); } Mode=2; mode(); ViewExport=View; #ifdef HAVE_LIBOSMESA View=false; #endif if(View) { #ifdef HAVE_LIBGLUT #ifdef HAVE_PTHREAD #ifndef HAVE_LIBOSMESA initializedView=true; #endif #endif glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMouseFunc(mouse); glutDisplayFunc(display); #ifdef __MSDOS__ if(glthread && interact::interactive) poll(0); #endif glutMainLoop(); cout << endl; exitHandler(0); #endif // HAVE_LIBGLUT } else { if(glthread) { if(havewindow) { readyAfterExport=true; #ifdef HAVE_PTHREAD #if !defined(_WIN32) pthread_kill(mainthread,SIGUSR1); #endif #endif } else { initialized=true; readyAfterExport=true; #if !defined(_WIN32) Signal(SIGUSR1,exportHandler); #endif exportHandler(); } } else { exportHandler(); quit(); } } #endif /* HAVE_GL */ } } // namespace gl #endif #ifdef HAVE_GL namespace camp { string getLightIndex(size_t const& index, string const& fieldName) { ostringstream buf; buf << "lights[" << index << "]." << fieldName; return Strdup(buf.str()); } string getCenterIndex(size_t const& index) { ostringstream buf; buf << "Centers[" << index << "]"; return Strdup(buf.str()); } template void registerBuffer(const std::vector& buffervector, GLuint& bufferIndex, bool copy, GLenum type=GL_ARRAY_BUFFER) { if(!buffervector.empty()) { if(bufferIndex == 0) { glGenBuffers(1,&bufferIndex); copy=true; } glBindBuffer(type,bufferIndex); if(copy) glBufferData(type,buffervector.size()*sizeof(T), buffervector.data(),GL_STATIC_DRAW); } } void clearCount() { glUseProgram(zeroShader); gl::lastshader=zeroShader; glUniform1ui(glGetUniformLocation(zeroShader,"width"),gl::Width); fpu_trap(false); // Work around FE_INVALID glDrawArrays(GL_TRIANGLES, 0, 3); fpu_trap(settings::trap()); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); } void compressCount() { glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); glUseProgram(compressShader); gl::lastshader=compressShader; glUniform1ui(glGetUniformLocation(compressShader,"width"),gl::Width); fpu_trap(false); // Work around FE_INVALID glDrawArrays(GL_TRIANGLES, 0, 3); fpu_trap(settings::trap()); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); } void partialSums(bool readSize=false) { // Compute partial sums on the GPU glUseProgram(sum1Shader); glDispatchCompute(gl::g,1,1); if(gl::elements <= gl::groupSize*gl::groupSize) glUseProgram(sum2fastShader); else { glUseProgram(sum2Shader); glUniform1ui(glGetUniformLocation(sum2Shader,"blockSize"), gl::ceilquotient(gl::g,gl::localSize)); } glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glDispatchCompute(1,1,1); glUseProgram(sum3Shader); glUniform1ui(glGetUniformLocation(sum3Shader,"final"),gl::elements-1); glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glDispatchCompute(gl::g,1,1); } void resizeFragmentBuffer() { if(GPUindexing) { glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::feedbackBuffer); GLuint *feedback=(GLuint *) glMapBuffer(GL_SHADER_STORAGE_BUFFER,GL_READ_ONLY); GLuint maxDepth=feedback[0]; if(maxDepth > gl::maxSize) gl::resizeBlendShader(maxDepth); fragments=feedback[1]; glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); } if(fragments > maxFragments) { // Initialize the alpha buffer maxFragments=11*fragments/10; glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::fragmentBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,maxFragments*sizeof(glm::vec4), NULL,GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,4,camp::fragmentBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::depthBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,maxFragments*sizeof(GLfloat), NULL,GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,5,camp::depthBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::feedbackBuffer); } } void refreshBuffers() { GLuint zero=0; gl::pixels=(gl::Width+1)*(gl::Height+1); if(initSSBO) { gl::processors=1; GLuint Pixels; if(GPUindexing) { GLuint G=gl::ceilquotient(gl::pixels,gl::groupSize); Pixels=gl::groupSize*G; GLuint globalSize=gl::localSize*gl::ceilquotient(G,gl::localSize); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::globalSumBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,globalSize*sizeof(GLuint),NULL, GL_DYNAMIC_READ); glClearBufferData(GL_SHADER_STORAGE_BUFFER,GL_R32UI,GL_RED_INTEGER, GL_UNSIGNED_INT,&zero); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,3,camp::globalSumBuffer); } else Pixels=gl::pixels; glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::offsetBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,(Pixels+2)*sizeof(GLuint), NULL,GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,0,camp::offsetBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::countBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,(Pixels+2)*sizeof(GLuint), NULL,GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,2,camp::countBuffer); if(GPUcompress) { GLuint one=1; glBindBuffer(GL_ATOMIC_COUNTER_BUFFER,camp::elementsBuffer); glBufferData(GL_ATOMIC_COUNTER_BUFFER,sizeof(GLuint),&one, GL_DYNAMIC_DRAW); glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER,0,camp::elementsBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::indexBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,gl::pixels*sizeof(GLuint), NULL,GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,1,camp::indexBuffer); } glClearBufferData(GL_SHADER_STORAGE_BUFFER,GL_R32UI,GL_RED_INTEGER, GL_UNSIGNED_INT,&zero); // Clear count or index buffer glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::opaqueBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,gl::pixels*sizeof(glm::vec4),NULL, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,6,camp::opaqueBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::opaqueDepthBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLuint)+gl::pixels*sizeof(GLfloat),NULL, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,7,camp::opaqueDepthBuffer); const GLfloat zerof=0.0; glClearBufferData(GL_SHADER_STORAGE_BUFFER,GL_R32F,GL_RED,GL_FLOAT,&zerof); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::feedbackBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER,2*sizeof(GLuint),NULL, GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER,8,camp::feedbackBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::feedbackBuffer); initSSBO=false; } // Determine the fragment offsets if(gl::exporting && GPUindexing && !GPUcompress) { glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::countBuffer); glClearBufferData(GL_SHADER_STORAGE_BUFFER,GL_R32UI,GL_RED_INTEGER, GL_UNSIGNED_INT,&zero); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::feedbackBuffer); } if(!interlock) { drawBuffer(material1Data,countShader); drawBuffer(materialData,countShader); drawBuffer(colorData,countShader,true); drawBuffer(triangleData,countShader,true); } glDepthMask(GL_FALSE); // Don't write to depth buffer glDisable(GL_MULTISAMPLE); drawBuffer(transparentData,countShader,true); glEnable(GL_MULTISAMPLE); glDepthMask(GL_TRUE); // Write to depth buffer if(GPUcompress) { compressCount(); GLuint *p=(GLuint *) glMapBuffer(GL_ATOMIC_COUNTER_BUFFER,GL_READ_WRITE); gl::elements=GPUindexing ? p[0] : p[0]-1; p[0]=1; glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER); if(gl::elements == 0) return; } else gl::elements=gl::pixels; if(GPUindexing) { gl::g=gl::ceilquotient(gl::elements,gl::groupSize); gl::elements=gl::groupSize*gl::g; if(settings::verbose > 3) { static bool first=true; if(first) { partialSums(); first=false; } unsigned int N=10000; stopWatch Timer; for(unsigned int i=0; i < N; ++i) partialSums(); glFinish(); double T=Timer.seconds()/N; cout << "elements=" << gl::elements << endl; cout << "Tmin (ms)=" << T*1e3 << endl; cout << "Megapixels/second=" << gl::elements/T/1e6 << endl; } partialSums(true); } else { size_t size=gl::elements*sizeof(GLuint); // Compute partial sums on the CPU glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::countBuffer); GLuint *p=(GLuint *) glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0,size+sizeof(GLuint), GL_MAP_READ_BIT); GLuint maxsize=p[0]; GLuint *count=p+1; glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::offsetBuffer); GLuint *offset=(GLuint *) glMapBufferRange(GL_SHADER_STORAGE_BUFFER, sizeof(GLuint),size, GL_MAP_WRITE_BIT); size_t Offset=offset[0]=count[0]; for(size_t i=1; i < gl::elements; ++i) offset[i]=Offset += count[i]; fragments=Offset; glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::offsetBuffer); glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::countBuffer); glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); if(gl::exporting) { glBindBuffer(GL_SHADER_STORAGE_BUFFER,camp::countBuffer); glClearBufferData(GL_SHADER_STORAGE_BUFFER,GL_R32UI,GL_RED_INTEGER, GL_UNSIGNED_INT,&zero); } else clearCount(); if(maxsize > gl::maxSize) gl::resizeBlendShader(maxsize); } gl::lastshader=-1; } void setUniforms(vertexBuffer& data, GLint shader) { bool normal=shader != pixelShader; if(shader != gl::lastshader) { glUseProgram(shader); if(normal) glUniform1ui(glGetUniformLocation(shader,"width"),gl::Width); } glUniformMatrix4fv(glGetUniformLocation(shader,"projViewMat"),1,GL_FALSE, value_ptr(gl::projViewMat)); glUniformMatrix4fv(glGetUniformLocation(shader,"viewMat"),1,GL_FALSE, value_ptr(gl::viewMat)); if(normal) glUniformMatrix3fv(glGetUniformLocation(shader,"normMat"),1,GL_FALSE, value_ptr(gl::normMat)); if(shader == countShader) { gl::lastshader=shader; return; } if(shader != gl::lastshader) { gl::lastshader=shader; glUniform1ui(glGetUniformLocation(shader,"nlights"),gl::nlights); for(size_t i=0; i < gl::nlights; ++i) { triple Lighti=gl::Lights[i]; size_t i4=4*i; glUniform3f(glGetUniformLocation(shader, getLightIndex(i,"direction").c_str()), (GLfloat) Lighti.getx(),(GLfloat) Lighti.gety(), (GLfloat) Lighti.getz()); glUniform3f(glGetUniformLocation(shader, getLightIndex(i,"color").c_str()), (GLfloat) gl::Diffuse[i4],(GLfloat) gl::Diffuse[i4+1], (GLfloat) gl::Diffuse[i4+2]); } if(settings::getSetting("ibl")) { gl::IBLbrdfTex.setUniform(glGetUniformLocation(shader, "reflBRDFSampler")); gl::irradiance.setUniform(glGetUniformLocation(shader, "diffuseSampler")); gl::reflTextures.setUniform(glGetUniformLocation(shader, "reflImgSampler")); } } GLuint binding=0; GLint blockindex=glGetUniformBlockIndex(shader,"MaterialBuffer"); glUniformBlockBinding(shader,blockindex,binding); bool copy=(gl::remesh || data.partial || !data.rendered) && !gl::copied; registerBuffer(data.materials,data.materialsBuffer,copy,GL_UNIFORM_BUFFER); glBindBufferBase(GL_UNIFORM_BUFFER,binding,data.materialsBuffer); } void drawBuffer(vertexBuffer& data, GLint shader, bool color) { if(data.indices.empty()) return; bool normal=shader != pixelShader; const size_t size=sizeof(GLfloat); const size_t intsize=sizeof(GLint); const size_t bytestride=color ? sizeof(VertexData) : (normal ? sizeof(vertexData) : sizeof(vertexData0)); bool copy=(gl::remesh || data.partial || !data.rendered) && !gl::copied; if(color) registerBuffer(data.Vertices,data.VerticesBuffer,copy); else if(normal) registerBuffer(data.vertices,data.verticesBuffer,copy); else registerBuffer(data.vertices0,data.vertices0Buffer,copy); registerBuffer(data.indices,data.indicesBuffer,copy,GL_ELEMENT_ARRAY_BUFFER); camp::setUniforms(data,shader); data.rendered=true; glVertexAttribPointer(positionAttrib,3,GL_FLOAT,GL_FALSE,bytestride, (void *) 0); glEnableVertexAttribArray(positionAttrib); if(normal && gl::Nlights > 0) { glVertexAttribPointer(normalAttrib,3,GL_FLOAT,GL_FALSE,bytestride, (void *) (3*size)); glEnableVertexAttribArray(normalAttrib); } else if(!normal) { glVertexAttribPointer(widthAttrib,1,GL_FLOAT,GL_FALSE,bytestride, (void *) (3*size)); glEnableVertexAttribArray(widthAttrib); } glVertexAttribIPointer(materialAttrib,1,GL_INT,bytestride, (void *) ((normal ? 6 : 4)*size)); glEnableVertexAttribArray(materialAttrib); if(color) { glVertexAttribPointer(colorAttrib,4,GL_FLOAT,GL_FALSE,bytestride, (void *) (6*size+intsize)); glEnableVertexAttribArray(colorAttrib); } fpu_trap(false); // Work around FE_INVALID glDrawElements(data.type,data.indices.size(),GL_UNSIGNED_INT,(void *) 0); fpu_trap(settings::trap()); glDisableVertexAttribArray(positionAttrib); if(normal && gl::Nlights > 0) glDisableVertexAttribArray(normalAttrib); if(!normal) glDisableVertexAttribArray(widthAttrib); glDisableVertexAttribArray(materialAttrib); if(color) glDisableVertexAttribArray(colorAttrib); glBindBuffer(GL_UNIFORM_BUFFER,0); glBindBuffer(GL_ARRAY_BUFFER,0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0); } void drawMaterial0() { drawBuffer(material0Data,pixelShader); material0Data.clear(); } void drawMaterial1() { drawBuffer(material1Data,materialShader[Opaque]); material1Data.clear(); } void drawMaterial() { drawBuffer(materialData,materialShader[Opaque]); materialData.clear(); } void drawColor() { drawBuffer(colorData,colorShader[Opaque],true); colorData.clear(); } void drawTriangle() { drawBuffer(triangleData,generalShader[Opaque],true); triangleData.clear(); } void aBufferTransparency() { // Collect transparent fragments glDepthMask(GL_FALSE); // Disregard depth drawBuffer(transparentData,transparentShader,true); glDepthMask(GL_TRUE); // Respect depth // Blend transparent fragments glDisable(GL_DEPTH_TEST); glUseProgram(blendShader); gl::lastshader=blendShader; glUniform1ui(glGetUniformLocation(blendShader,"width"),gl::Width); glUniform4f(glGetUniformLocation(blendShader,"background"), gl::Background[0],gl::Background[1],gl::Background[2], gl::Background[3]); fpu_trap(false); // Work around FE_INVALID glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); glDrawArrays(GL_TRIANGLES,0,3); fpu_trap(settings::trap()); transparentData.clear(); glEnable(GL_DEPTH_TEST); } void drawTransparent() { if(camp::ssbo) { glDisable(GL_MULTISAMPLE); aBufferTransparency(); glEnable(GL_MULTISAMPLE); } else { sortTriangles(); transparentData.rendered=false; // Force copying of sorted triangles to GPU glDepthMask(GL_FALSE); // Don't write to depth buffer drawBuffer(transparentData,transparentShader,true); glDepthMask(GL_TRUE); // Write to depth buffer transparentData.clear(); } } void drawBuffers() { gl::copied=false; Opaque=transparentData.indices.empty(); bool transparent=!Opaque; if(camp::ssbo) { if(transparent) { refreshBuffers(); if(!interlock) { resizeFragmentBuffer(); gl::copied=true; } } } drawMaterial0(); drawMaterial1(); drawMaterial(); drawColor(); drawTriangle(); if(transparent) { if(camp::ssbo) gl::copied=true; if(interlock) resizeFragmentBuffer(); drawTransparent(); } Opaque=0; } void setMaterial(vertexBuffer& data, draw_t *draw) { if(materialIndex >= data.materialTable.size() || data.materialTable[materialIndex] == -1) { if(data.materials.size() >= Maxmaterials) { data.partial=true; (*draw)(); } size_t size0=data.materialTable.size(); data.materialTable.resize(materialIndex+1); for(size_t i=size0; i < materialIndex; ++i) data.materialTable[i]=-1; data.materialTable[materialIndex]=data.materials.size(); data.materials.push_back(materials[materialIndex]); } materialIndex=data.materialTable[materialIndex]; } } #endif /* HAVE_GL */