// Copyright (C) 2010, Guy Barrand. All rights reserved. // See the file tools.license for terms. #ifndef tools_sg_GL_manager #define tools_sg_GL_manager #include "gl" #include "../glbuf" #include #include #include #include //::sqrt #ifdef TOOLS_MEM #include #endif namespace tools { namespace sg { class GL_manager : public virtual tools::sg::render_manager { typedef tools::sg::render_manager parent; public: TOOLS_SCLASS(tools::sg::GL_manager) virtual void* cast(const std::string& a_class) const { if(void* p = tools::cmp_cast(this,a_class)) {return p;} else return 0; } public: virtual bool begin_render(int a_x,int a_y,unsigned int a_ww,unsigned int a_wh, float a_r,float a_g,float a_b,float a_a,bool a_clear = true) { gl_clear_errors(); #if TARGET_OS_IPHONE #elif defined(ANDROID) #elif _WIN32 #elif __APPLE__ // Cocoa // to avoid a 0x506 error message : #ifdef TOOLS_HAS_GL_VBO #if GL_ARB_framebuffer_object if(::glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE) { //m_out << "tools::sg::GL_manager::begin_render :" // << " frame buffer not complete." // << std::endl; return false; } #endif //GL_ARB_framebuffer_object #endif //TOOLS_HAS_GL_VBO #else #endif // WARNING : the values set here must match the default values in sg::state. // Antialiasing : #if TARGET_OS_IPHONE #elif defined(ANDROID) ::glEnable(GL_MULTISAMPLE); #elif _WIN32 #elif __APPLE__ ::glEnable(GL_MULTISAMPLE); //Cocoa #else #endif /* NOTE : not GL-ES : // ::glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); // The upper is cruely lacking with povama solid+light. // ::glEnable(GL_LINE_STIPPLE); ::glDisable(GL_POLYGON_SMOOTH); ::glAccum(GL_LOAD,1.0f); ::glAccum(GL_RETURN,1.0f); ::glReadBuffer(GL_FRONT); NOTE : GL_POLYGON_SMOOTH is here on Cocoa/AGL, Windows/WGL but it does nothing. */ #if TARGET_OS_IPHONE // GL-ES #elif defined(ANDROID) // GL-ES #else ::glDisable(GL_POLYGON_STIPPLE); //CoinGL : reading a .wrl having Material::transparency may enable GL_POLYGON_STIPPLE. #endif /* ::printf("debug : is enabled %d\n",::glIsEnabled(GL_COLOR_MATERIAL)); {GLfloat v[4]; ::glGetMaterialfv(GL_FRONT,GL_AMBIENT,v); ::printf("debug : ambient : %g %g %g %g\n",v[0],v[1],v[2],v[3]);} {GLfloat v[4]; ::glGetMaterialfv(GL_FRONT,GL_DIFFUSE,v); ::printf("debug : diffuse : %g %g %g %g\n",v[0],v[1],v[2],v[3]);} {GLfloat v[4]; ::glGetMaterialfv(GL_FRONT,GL_SPECULAR,v); ::printf("debug : specular : %g %g %g %g\n",v[0],v[1],v[2],v[3]);} {GLfloat v[4]; ::glGetMaterialfv(GL_FRONT,GL_EMISSION,v); ::printf("debug : emission : %g %g %g %g\n",v[0],v[1],v[2],v[3]);} {GLfloat shine; ::glGetMaterialfv(GL_FRONT,GL_SHININESS,&shine); ::printf("debug : shine : %g\n",shine);} debug : is enabled 0 debug : ambient : 0.2 0.2 0.2 1 debug : diffuse : 0.8 0.8 0.8 1 debug : specular : 0 0 0 1 debug : emission : 0 0 0 1 debug : shine : 0 */ ::glEnable(GL_NORMALIZE); ::glShadeModel(GL_FLAT); //::glShadeModel(GL_SMOOTH); // GL-ES : ::glMaterialfv does not work. We then use : // ::glEnable(GL_COLOR_MATERIAL) and ::glColor. //::glColorMaterial(GL_FRONT, GL_DIFFUSE); //? ::glEnable(GL_COLOR_MATERIAL); /* debug : is enabled 1 debug : ambient : 1 1 1 1 debug : diffuse : 1 1 1 1 debug : specular : 0 0 0 1 debug : emission : 0 0 0 1 debug : shine : 0 */ // to handle transparency (same as SoGLRenderAction::SCREEN_DOOR ?) : //::glEnable(GL_BLEND); // NOTE : with Cocoa+AppleGL on macOS-10.14 (Mojave), it appears that points are blended ! (Even if alpha color is 1). // Seen with some simulated catalog of galaxies done for LSST/DC2. // To master this, we disable GL_BLEND by default. Then people wanting some transparency have to // use the tools::sg::blend node in their scene graph. ::glDisable(GL_BLEND); //must be in sync with tools::sg::state.m_GL_BLEND and sg::blend node default. ::glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); //WARNING : the below glEnable/glDisable corresponds // to defaults in tools::sg::state. ::glEnable(GL_DEPTH_TEST); ::glDisable(GL_LIGHTING); ::glFrontFace(GL_CCW); ::glEnable(GL_CULL_FACE); ::glDisable(GL_POLYGON_OFFSET_FILL); ::glDisable(GL_TEXTURE_2D); ::glDisable(GL_POINT_SMOOTH); ::glPointSize(1); ::glDisable(GL_LINE_SMOOTH); //NOTE : it does not work on all platforms ! ::glLineWidth(1); ::glViewport(a_x,a_y,a_ww,a_wh); // NOTE : iOS : glScissor logic does not work properly with Apple multisampling. // But it appears that it is not needed for VR split views. // Here a combination glViewport+[correct camera lrbt] does the clipping job (???). // (But the clear color should be the same for both views). //::glEnable(GL_SCISSOR_TEST); //::glScissor(a_x,a_y,a_ww,a_wh); //NOTE : a=0 coworks with the below logic to handle transparent background. if(a_clear) { ::glClearColor(a_r,a_g,a_b,0); ::glClear(GL_COLOR_BUFFER_BIT); ::glClear(GL_DEPTH_BUFFER_BIT); } //WARNING : Android, iPhone : only one glPushMatrix ok ! ::glMatrixMode(GL_PROJECTION); ::glLoadIdentity(); //WARNING : we take the convention that the current mode is MODELVIEW ::glMatrixMode(GL_MODELVIEW); ::glLoadIdentity(); //m_clear_color.set_a(0.2); // to handle a transparent background : //NOTE : not tested on Android and iOS. // dst color : // cr * alpha + cr * (1-alpha) = cr // cg * alpha + cg * (1-alpha) = cg // cb * alpha + cb * (1-alpha) = cb // alpha*alpha + 0 * (1-alpha) = ca => alpha = sqrt(ca) {::glColor4f(a_r,a_g,a_b,::sqrt(a_a)); float xyzs[12]; xyzs[0] = -1;xyzs[ 1] = -1;xyzs[ 2] = 0; xyzs[3] = 1;xyzs[ 4] = -1;xyzs[ 5] = 0; xyzs[6] = 1;xyzs[ 7] = 1;xyzs[ 8] = 0; xyzs[9] = -1;xyzs[10] = 1;xyzs[11] = 0; ::glDisable(GL_DEPTH_TEST); ::glEnableClientState(GL_VERTEX_ARRAY); ::glVertexPointer(3,GL_FLOAT,0,xyzs); ::glDrawArrays(GL_TRIANGLE_FAN,0,4); ::glDisableClientState(GL_VERTEX_ARRAY); ::glEnable(GL_DEPTH_TEST);} return true; } virtual void end_render() { ::glFinish(); gl_dump_if_errors(m_out,"tools::sg::GL_manager::end_render :"); } //////////////////////////////////////////////// /// texture : ////////////////////////////////// //////////////////////////////////////////////// virtual unsigned int create_texture(const tools::img_byte& a_img,bool a_NEAREST) { unsigned int gl_id; ::glGenTextures(1,&gl_id); if(!gl_id) return 0; #ifdef TOOLS_MEM tools::mem::increment(tools::s_tex().c_str()); #endif unsigned int gsto_size = a_img.size(); ::glBindTexture(GL_TEXTURE_2D,gl_id); bool status = true; {int sz; ::glGetIntegerv(GL_MAX_TEXTURE_SIZE,&sz); // MacBookPro : it returns 8192. // Android : it returns 2048. // iPad1 : it returns 2048. // iPod : it returns ? //::printf("debug : GL_MAX_TEXTURE_SIZE : %d\n",sz); tools::img_byte res; if(!sz) { m_out << "tools::sg::gl::tex_img : warning : GL_MAX_TEXTURE_SIZE is zero." << std::endl; status = gl_tex_img(m_out,a_img); } else { if(a_img.check_gl_limit(sz,res)) { if(res.is_empty()) { //a_img does not exceed. status = gl_tex_img(m_out,a_img); } else { m_out << "tools::sg::gl::tex_img : warning : img size > GL_MAX_TEXTURE_SIZE (" << sz << ")." << std::endl; status = gl_tex_img(m_out,res); gsto_size = res.size(); } } else { m_out << "tools::sg::gl::tex_img :" << " warning : img size > GL_MAX_TEXTURE_SIZE (" << sz << ") but can't reduce." << std::endl; status = false; }}} if(a_NEAREST) { ::glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); //good to see astro images pixels. ::glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); //idem. } else { ::glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); ::glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); } ::glBindTexture(GL_TEXTURE_2D,0); if(!status) { gl_dump_if_errors(m_out,"tools::sg::GL_manager::create_texture (1) :"); ::glDeleteTextures(1,&gl_id); #ifdef TOOLS_MEM tools::mem::decrement(tools::s_tex().c_str()); #endif gl_id = 0; gl_clear_errors(); return 0; } if(gl_dump_if_errors(m_out,"tools::sg::GL_manager::create_texture (2) :")) { ::glDeleteTextures(1,&gl_id); #ifdef TOOLS_MEM tools::mem::decrement(tools::s_tex().c_str()); #endif gl_id = 0; gl_clear_errors(); return 0; } unsigned int _id = m_gen_id;m_gen_id++; m_gstos[_id] = new gsto_t(gsto_t::kind_texture,gl_id,gsto_size,0); return _id; } //////////////////////////////////////////////// /// VBO //////////////////////////////////////// //////////////////////////////////////////////// virtual unsigned int create_gsto_from_data(size_t a_floatn,const float* a_data) { if(!a_floatn) return 0; switch(m_gsto_mode) { case tools::sg::gsto_gl_vbo:{ #ifdef TOOLS_HAS_GL_VBO unsigned int gl_id = 0; ::glGenBuffers(1,&gl_id); if(!gl_id) { if(!m_warned) { m_warned = true; m_out << "tools::sg::GL_manager::create_gsto_from_data : glGenBuffers failed ()." << std::endl; } return 0; } #ifdef TOOLS_MEM tools::mem::increment(tools::s_gsto().c_str()); #endif ::glBindBuffer(GL_ARRAY_BUFFER,gl_id); ::glBufferData(GL_ARRAY_BUFFER, a_floatn*sizeof(float),a_data, GL_STATIC_DRAW); ::glBindBuffer(GL_ARRAY_BUFFER,0); if(gl_dump_if_errors(m_out,"tools::sg::GL_manager::create_gsto_from_data :")) { ::glDeleteBuffers(1,&gl_id); #ifdef TOOLS_MEM tools::mem::decrement(tools::s_gsto().c_str()); #endif gl_id = 0; gl_clear_errors(); return 0; } unsigned int _id = m_gen_id;m_gen_id++; m_gstos[_id] = new gsto_t(gsto_t::kind_buffer,gl_id,a_floatn*sizeof(float),0); return _id; #else //!TOOLS_HAS_GL_VBO m_out << "tools::sg::GL_manager::create_gsto_from_data :" << " gsto mode is gl_vbo but class not compiled with TOOLS_HAS_GL_VBO." << std::endl; return 0; #endif //TOOLS_HAS_GL_VBO }break; case tools::sg::gsto_gl_list:{ unsigned int gl_id = 0; unsigned int _id = m_gen_id;m_gen_id++; m_gstos[_id] = new gsto_t(gsto_t::kind_list,gl_id,a_floatn*sizeof(float),a_data); return _id; }break; case tools::sg::gsto_memory:{ unsigned int gl_id = 0; unsigned int _id = m_gen_id;m_gen_id++; m_gstos[_id] = new gsto_t(gsto_t::kind_memory,gl_id,a_floatn*sizeof(float),a_data); return _id; }break; } return 0; } virtual bool is_gsto_id_valid(unsigned int a_id) const { std::map::const_iterator it = m_gstos.find(a_id); if(it==m_gstos.end()) return false; return (*it).second->is_valid(); } virtual void delete_gsto(unsigned int a_id){ tools::delete_key(m_gstos,a_id); } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// float* gsto_data(unsigned int a_id) const { std::map::const_iterator it = m_gstos.find(a_id); if(it==m_gstos.end()) return 0; return (*it).second->m_data; } unsigned int gsto_gl_list_id(unsigned int a_id,bool& a_created) const { std::map::const_iterator it = m_gstos.find(a_id); if(it==m_gstos.end()) {a_created = false;return 0;} if((*it).second->m_kind!=gsto_t::kind_list) {a_created = false;return 0;} if((*it).second->m_gl_id) { a_created = false; return (*it).second->m_gl_id; } else { #ifdef TOOLS_HAS_GL_LIST unsigned int _id = ::glGenLists(1); if(!_id) {a_created = false;return 0;} #ifdef TOOLS_MEM tools::mem::increment(tools::s_gsto().c_str()); #endif a_created = true; (*it).second->m_gl_id = _id; return _id; #else a_created = false; return 0; #endif } } //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// virtual tools::sg::gsto_mode get_gsto_mode() const {return m_gsto_mode;} virtual void set_gsto_mode(tools::sg::gsto_mode a_v) { if(a_v==m_gsto_mode) return; tools::safe_clear(m_gstos); switch(a_v) { case tools::sg::gsto_gl_vbo:{ #ifdef TOOLS_HAS_GL_VBO m_gsto_mode = a_v; #else m_gsto_mode = tools::sg::gsto_memory; #endif }break; case tools::sg::gsto_gl_list:{ #ifdef TOOLS_HAS_GL_LIST m_gsto_mode = a_v; #else m_gsto_mode = tools::sg::gsto_memory; #endif }break; case tools::sg::gsto_memory:{ m_gsto_mode = tools::sg::gsto_memory; }break; } } virtual void available_gsto_modes(std::vector& a_vs) { a_vs.clear(); #ifdef TOOLS_HAS_GL_VBO a_vs.push_back(tools::sg::s_gsto_gl_vbo()); #endif #ifdef TOOLS_HAS_GL_LIST a_vs.push_back(tools::sg::s_gsto_gl_list()); #endif a_vs.push_back(tools::sg::s_gsto_memory()); } virtual void available_not_memory_gsto_mode(std::string& a_v) const { a_v.clear(); #ifdef TOOLS_HAS_GL_VBO a_v = tools::sg::s_gsto_gl_vbo(); #endif #ifdef TOOLS_HAS_GL_LIST if(a_v.empty()) a_v = tools::sg::s_gsto_gl_list(); #endif } virtual size_t used_texture_memory() const { size_t sz = 0; std::map::const_iterator it; for(it=m_gstos.begin();it!=m_gstos.end();++it) { if((*it).second->m_kind==gsto_t::kind_texture) sz += (*it).second->m_size; } return sz; } virtual size_t gstos_size() const { size_t sz = 0; std::map::const_iterator it; for(it=m_gstos.begin();it!=m_gstos.end();++it) sz += (*it).second->m_size; return sz; } public: GL_manager(std::ostream& a_out) :m_out(a_out) ,m_gen_id(1) #ifdef TOOLS_HAS_GL_VBO ,m_gsto_mode(tools::sg::gsto_gl_vbo) //priority to GL VBOs. #elif TOOLS_HAS_GL_LIST ,m_gsto_mode(tools::sg::gsto_gl_list) #else ,m_gsto_mode(tools::sg::gsto_memory) #endif ,m_warned(false) {} virtual ~GL_manager(){ tools::safe_clear(m_gstos); } public: GL_manager(const GL_manager& a_from) :parent(a_from) ,m_out(a_from.m_out) ,m_gen_id(a_from.m_gen_id) ,m_gsto_mode(a_from.m_gsto_mode) ,m_warned(false) {} GL_manager& operator=(const GL_manager& a_from){ if(&a_from==this) return *this; m_gen_id = a_from.m_gen_id; m_gsto_mode = a_from.m_gsto_mode; tools::safe_clear(m_gstos); m_warned = false; return *this; } public: void bind_gsto(unsigned int a_id) { std::map::const_iterator it = m_gstos.find(a_id); if(it==m_gstos.end()) return; (*it).second->bind(); } void delete_gstos() { tools::safe_clear(m_gstos); } public: static unsigned char* get_rgbas(unsigned int a_w,unsigned int a_h) { //WARNING : it does OpenGL. Under Android it should be executed // in the OpenGL thread. //NOTE : Android, iOS : RGB produces a black image. unsigned char* rgbas = new unsigned char[4 * a_w * a_h]; if(!rgbas) return 0; ::glPixelStorei(GL_PACK_ALIGNMENT,1); //needed with Cocoa. ::glReadPixels(0,0,a_w,a_h,GL_RGBA,GL_UNSIGNED_BYTE,rgbas); /*{size_t number = 4 * a_w * a_h; size_t count_not_255 = 0; for(size_t item=3;item(rgbas,a_w,a_h); delete [] rgbas; return rgbs; } #else static unsigned char* get_rgbs(unsigned int a_w,unsigned int a_h) { //WARNING : it does OpenGL. Under Android it should be executed // in the OpenGL thread. //NOTE : Android, iOS : RGB produces a black image. unsigned char* rgbs = new unsigned char[3 * a_w * a_h]; if(!rgbs) return 0; ::glPixelStorei(GL_PACK_ALIGNMENT,1); //needed with Cocoa. ::glReadPixels(0,0,a_w,a_h,GL_RGB,GL_UNSIGNED_BYTE,rgbs); return rgbs; } #endif protected: std::ostream& m_out; class gsto_t { TOOLS_SCLASS(GL_manager::gsto_t) public: enum kind { kind_texture, kind_buffer, kind_list, kind_memory }; public: gsto_t(kind a_kind,int a_gl_id,size_t a_size,const float* a_data) :m_gl_id(a_gl_id) ,m_kind(a_kind) ,m_size(a_size) ,m_data(0) { #ifdef TOOLS_MEM tools::mem::increment(s_class().c_str()); #endif if(a_data) { size_t num = m_size/sizeof(float); m_data = new float[num]; #ifdef TOOLS_MEM tools::mem::increment(tools::s_new().c_str()); #endif ::memcpy(m_data,a_data,m_size); } } virtual ~gsto_t() { if(m_kind==kind_texture) { ::glDeleteTextures(1,&m_gl_id); #ifdef TOOLS_MEM tools::mem::decrement(tools::s_tex().c_str()); #endif } else if(m_kind==kind_buffer) { #ifdef TOOLS_HAS_GL_VBO ::glDeleteBuffers(1,&m_gl_id); #ifdef TOOLS_MEM tools::mem::decrement(tools::s_gsto().c_str()); #endif #endif } else if(m_kind==kind_list) { if(m_gl_id) { #ifdef TOOLS_HAS_GL_LIST ::glDeleteLists(m_gl_id,1); #ifdef TOOLS_MEM tools::mem::decrement(tools::s_gsto().c_str()); #endif #endif } } if(m_data) { delete [] m_data; #ifdef TOOLS_MEM tools::mem::decrement(tools::s_new().c_str()); #endif } #ifdef TOOLS_MEM tools::mem::decrement(s_class().c_str()); #endif } private: gsto_t(const gsto_t& a_from) :m_gl_id(a_from.m_gl_id) ,m_kind(a_from.m_kind) ,m_size(a_from.m_size) ,m_data(0) { #ifdef TOOLS_MEM tools::mem::increment(s_class().c_str()); #endif if(a_from.m_data) { size_t num = m_size/sizeof(float); m_data = new float[num]; #ifdef TOOLS_MEM tools::mem::increment(tools::s_new().c_str()); #endif ::memcpy(m_data,a_from.m_data,m_size); } } gsto_t& operator=(const gsto_t& a_from){ if(&a_from==this) return *this; m_gl_id = a_from.m_gl_id; m_kind = a_from.m_kind; m_size = a_from.m_size; if(m_data) { delete [] m_data; #ifdef TOOLS_MEM tools::mem::decrement(tools::s_new().c_str()); #endif m_data = 0; } if(a_from.m_data) { size_t num = m_size/sizeof(float); m_data = new float[num]; #ifdef TOOLS_MEM tools::mem::increment(tools::s_new().c_str()); #endif ::memcpy(m_data,a_from.m_data,m_size); } return *this; } public: bool is_valid() const { if(m_kind==kind_texture) { return (::glIsTexture(m_gl_id)==GL_TRUE?true:false); } else if(m_kind==kind_buffer) { #ifdef TOOLS_HAS_GL_VBO return (::glIsBuffer(m_gl_id)==GL_TRUE?true:false); #endif } else if(m_kind==kind_list) { #ifdef TOOLS_HAS_GL_LIST return (::glIsList(m_gl_id)==GL_TRUE?true:false); #endif } else if(m_kind==kind_memory) { return true; } return false; } void bind() const { if(m_kind==kind_texture) { ::glBindTexture(GL_TEXTURE_2D,m_gl_id); } else if(m_kind==kind_buffer) { #ifdef TOOLS_HAS_GL_VBO ::glBindBuffer(GL_ARRAY_BUFFER,m_gl_id); #endif } } public: unsigned int m_gl_id; kind m_kind; size_t m_size; float* m_data; }; std::map m_gstos; bool m_gsto_on_card; unsigned int m_gen_id; tools::sg::gsto_mode m_gsto_mode; bool m_warned; }; }} #endif