/*****
* glrender.cc
* John Bowman, Orest Shardt, and Supakorn "Jamie" Rassameemasmuang
* Render 3D Bezier paths and surfaces.
*****/
#include <stdlib.h>
#include <fstream>
#include <cstring>
#include <cmath>
#include <chrono>
#include <thread>
#if !defined(_WIN32)
#include <sys/time.h>
#include <unistd.h>
#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 <GL/freeglut_ext.h>
#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<Material> 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<class T>
inline T min(T a, T b)
{
return (a < b) ? a : b;
}
template<class T>
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<float,GL_FLOAT> IBLbrdfTex;
GLTexture2<float,GL_FLOAT> irradiance;
GLTexture3<float,GL_FLOAT> reflTextures;
GLTexture2<float,GL_FLOAT> fromEXR(string const& EXRFile, GLTexturesFmt const& fmt, GLint const& textureNumber)
{
camp::IEXRFile fil(EXRFile);
return GLTexture2<float,GL_FLOAT> {fil.getData(),fil.size(),textureNumber,fmt};
}
GLTexture3<float,GL_FLOAT> fromEXR3(
mem::vector<string> const& EXRFiles, GLTexturesFmt const& fmt, GLint const& textureNumber)
{
// 3d reflectance textures
std::vector<float> 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<float,GL_FLOAT> {
data.data(),
std::tuple<int,int,int>(wi,ht,static_cast<int>(count)),textureNumber,
fmt
};
}
void initIBL()
{
GLTexturesFmt fmt;
fmt.internalFmt=GL_RGB16F;
string imageDir=locateFile(getSetting<string>("imageDir"))+"/";
string imagePath=imageDir+getSetting<string>("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<string> 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<ShaderfileModePair> shaders(1);
std::vector<std::string> 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<ShaderfileModePair> shaders(2);
std::vector<std::string> 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<ShaderfileModePair> shaders(2);
std::vector<std::string> 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<bool>("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<bool>("animating");
if(animating)
Setting("interrupt")=true;
home();
Animate=getSetting<bool>("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<bool>("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<pair>("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<double>("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<double>("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<double>(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<double>("zoomfactor");
if(zoomFactor > 0.0) {
double zoomStep=getSetting<double>("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<double>("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<double>(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<array *>("leftbutton");
array *middle=getSetting<array *>("middlebutton");
array *right=getSetting<array *>("rightbutton");
array *wheelup=getSetting<array *>("wheelup");
array *wheeldown=getSetting<array *>("wheeldown");
array *Buttons[]={left,middle,right,wheelup,wheeldown};
array *a=Buttons[button];
size_t size=checkArray(a);
if(Mod < size)
return read<string>(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<double>("spinstep")*spinTimer.seconds(true);
}
void xspin()
{
rotateX(spinstep());
}
void yspin()
{
rotateY(spinstep());
}
void zspin()
{
rotateZ(spinstep());
}
void expand()
{
double resizeStep=getSetting<double>("resizestep");
if(resizeStep > 0.0)
setsize((int) (Width*resizeStep+0.5),(int) (Height*resizeStep+0.5));
}
void shrink()
{
double resizeStep=getSetting<double>("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<bool>("reverse")) Animate=false;
Setting("reverse")=Step=false;
animate();
break;
case 'r':
if(!getSetting<bool>("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<string> cmd;
cmd.push_back(settings::argv0);
if(!interact::interactive && Iconify)
cmd.push_back("-iconic");
push_split(cmd,getSetting<string>("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<bool>("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<pair>("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<bool>("animating")))) {
antialias=getSetting<Int>("antialias") > 1;
double expand;
if(format3d)
expand=1.0;
else {
expand=getSetting<double>("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<pair>("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<Int>("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<bool>("GPUindexing");
GPUcompress=getSetting<bool>("GPUcompress");
#else
GPUindexing=false;
GPUcompress=false;
#endif
GLint val;
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,&val);
if(GPUindexing) {
gl::localSize=getSetting<Int>("GPUlocalSize");
gl::blockSize=getSetting<Int>("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<bool>("ibl");
initShaders();
setBuffers();
}
glClearColor(args.background[0],args.background[1],args.background[2],args.background[3]);
#ifdef HAVE_LIBGLUT
#ifndef HAVE_LIBOSMESA
Animate=getSetting<bool>("autoplay") && glthread;
if(View) {
if(!getSetting<bool>("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<class T>
void registerBuffer(const std::vector<T>& 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<bool>("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 */