/**********************************************************************
* *
* cgt - CAMP Graphics Toolbox, Copyright (C) 2012-2015 *
* Chair for Computer Aided Medical Procedures *
* Technische Universitaet Muenchen, Germany. *
* *
* *
* forked from tgt - Tiny Graphics Toolbox, Copyright (C) 2006-2011 *
* Visualization and Computer Graphics Group, Department of *
* Computer Science, University of Muenster, Germany. *
* *
* *
* This file is part of the cgt library. This library is free *
* software; you can redistribute it and/or modify it under the terms *
* of the GNU Lesser General Public License version 2.1 as published *
* by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License in the file "LICENSE.txt" along with this library. *
* If not, see . *
* *
**********************************************************************/
#include "cgt/gpucapabilities.h"
#include
#ifdef WIN32
#include
#else // WIN32 -> must be POSIX
#include // for uname
#endif // WIN32
#include "cgt/logmanager.h"
#include
//using namespace std;
using std::string;
using std::stringstream;
namespace cgt {
const std::string GpuCapabilities::loggerCat_("cgt.GpuCapabilities");
GpuCapabilities::GpuCapabilities(bool detectCaps) {
if (detectCaps) {
detectCapabilities();
detectOS();
}
}
GpuCapabilities::GlVersion GpuCapabilities::getGlVersion() {
return glVersion_;
}
bool GpuCapabilities::isOpenGlVersionSupported(GpuCapabilities::GlVersion version) {
return (glVersion_ >= version);
}
GpuCapabilities::GpuVendor GpuCapabilities::getVendor() {
return vendor_;
}
std::string GpuCapabilities::getVendorAsString() {
switch (vendor_) {
case GPU_VENDOR_NVIDIA:
return "NVIDIA";
case GPU_VENDOR_ATI:
return "ATI";
case GPU_VENDOR_INTEL:
return "Intel";
default:
return "unknown";
}
}
bool GpuCapabilities::isExtensionSupported(string extension) {
return (std::find(glExtensions_.begin(), glExtensions_.end(), extension) != glExtensions_.end());
}
string GpuCapabilities::getGlVersionString() {
return glVersionString_;
}
string GpuCapabilities::getGlVendorString() {
return glVendorString_;
}
string GpuCapabilities::getGlRendererString() {
return glRendererString_;
}
string GpuCapabilities::getShadingLanguageVersionString() {
return glslVersionString_;
}
const std::vector& GpuCapabilities::getGlExtensions() {
return glExtensions_;
}
bool GpuCapabilities::areShadersSupported() {
return shaderSupport_;
}
bool GpuCapabilities::areComputeShadersSupported()
{
return computeShaderSupport_;
}
bool GpuCapabilities::areShadersSupportedARB() {
return shaderSupportARB_;
}
GpuCapabilities::GlVersion GpuCapabilities::getShaderVersion() {
return shaderVersion_;
}
GpuCapabilities::ShaderModel GpuCapabilities::getShaderModel() {
return shaderModel_;
}
bool GpuCapabilities::isShaderModelSupported(GpuCapabilities::ShaderModel shaderModel) {
return (shaderModel_ >= shaderModel);
}
int GpuCapabilities::getMaxTextureSize() {
return maxTexSize_;
}
bool GpuCapabilities::is3DTexturingSupported() {
return texturing3D_;
}
int GpuCapabilities::getMax3DTextureSize() {
return max3DTexSize_;
}
int GpuCapabilities::getNumTextureUnits() {
return numTextureUnits_;
}
int GpuCapabilities::getNumImageUnits()
{
return numImageUnits_;
}
bool GpuCapabilities::isNpotSupported() {
return npotTextures_;
}
bool GpuCapabilities::areTextureRectanglesSupported() {
return textureRectangles_;
}
bool GpuCapabilities::isAnisotropicFilteringSupported() {
return anisotropicFiltering_;
}
float GpuCapabilities::getMaxTextureAnisotropy() {
return maxTextureAnisotropy_;
}
bool GpuCapabilities::isTextureCompressionSupported() {
return textureCompression_;
}
bool GpuCapabilities::areFramebufferObjectsSupported() {
return framebufferObjects_;
}
int GpuCapabilities::getMaxColorAttachments() {
return maxColorAttachments_;
}
void GpuCapabilities::logCapabilities(bool extensionsString, bool osString) {
if (osString)
LINFO("OS version: " << osVersionString_);
LINFO("OpenGL Version: " << glVersionString_);
LINFO("OpenGL Renderer: " << glRendererString_);
if (glRendererString_.find("Cheetah") != string::npos) {
LWARNING("It seems that NVIDIA GPU emulation is running on this system.");
LWARNING("This sometimes leads to strange errors.");
LWARNING("It is therefore strongly recommended to turn the emulation off!");
}
LINFO("GPU Vendor: " << glVendorString_ << " (" << getVendorAsString() << ")");
if (extensionsString) {
std::stringstream exts;
for (auto& s : glExtensions_) {
exts << "\t" << s << std::endl;
}
LINFO("OpenGL Extensions: " << std::endl << exts.str());
}
stringstream features;
features << "Texturing: " << (isOpenGlVersionSupported(GlVersion::CGT_GL_VERSION_1_1) ? "yes" : "no");
if (isOpenGlVersionSupported(GlVersion::CGT_GL_VERSION_1_1))
features << ", max size: " << getMaxTextureSize();
features << ", 3D: " << (is3DTexturingSupported() ? "yes" : "no");
if (is3DTexturingSupported())
features << ", max 3D size: " << getMax3DTextureSize();
LINFO(features.str());
features.clear();
features.str("");
features << "Texture features: " << getNumTextureUnits() << " units, "
<< (isNpotSupported() ? "" : "no ") << "NPOT, "
<< (areTextureRectanglesSupported() ? "" : "no") << "rectangles, "
<< (isTextureCompressionSupported() ? "" : "no") << "compression, ";
if (isAnisotropicFilteringSupported())
features << getMaxTextureAnisotropy() << "x anisotropic";
else
features << "no anisotropic";
LINFO(features.str());
features.clear();
features.str("");
features << "Framebuffer Objects: " << (areFramebufferObjectsSupported() ? "yes" : "no");
if(areFramebufferObjectsSupported())
features << ", max " << getMaxColorAttachments() << " color attachments";
LINFO(features.str());
features.clear();
features.str("");
features << "Shaders: " << (areShadersSupported() ? "yes (OpenGL 2.0)" : "no");
if (!areShadersSupported()) {
LINFO(features.str());
}
else {
features << ", GLSL Version " << shaderVersion_;
features << ", Shader Model ";
if (shaderModel_ == SHADER_MODEL_5)
features << "5.0";
else if (shaderModel_ == SHADER_MODEL_4)
features << "4.0";
else if (shaderModel_ == SHADER_MODEL_3)
features << "3.0";
else if (shaderModel_ == SHADER_MODEL_2)
features << "2.0";
else
features << "unknown";
LINFO(features.str());
if (GLEW_NV_fragment_program2) {
features.clear();
features.str("");
features << "Shader Capabilities: ";
GLint i = -1;
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_LOOP_COUNT_NV, &i);
features << "MAX_PROGRAM_LOOP_COUNT=" << i;
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV, &i);
features << ", MAX_PROGRAM_EXEC_INSTRUCTIONS=" << i;
LDEBUG(features.str());
}
}
}
GpuCapabilities::OSVersion GpuCapabilities::getOSVersion() {
return osVersion_;
}
std::string GpuCapabilities::getOSVersionString() {
return osVersionString_;
}
void GpuCapabilities::detectCapabilities() {
glVersionString_ = string(reinterpret_cast(glGetString(GL_VERSION)));
glVendorString_ = string(reinterpret_cast(glGetString(GL_VENDOR)));
glRendererString_ = string(reinterpret_cast(glGetString(GL_RENDERER)));
// Prevent segfault
const char* glslVS = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION));
if (glslVS)
glslVersionString_ = string(glslVS);
else
glslVersionString_ = "";
if (!glVersion_.parseVersionString(glVersionString_)) {
LERROR("Malformed OpenGL version string: " << glVersionString_);
}
// GPU Vendor
if (glVendorString_.find("NVIDIA") != std::string::npos)
vendor_ = GPU_VENDOR_NVIDIA;
else if (glVendorString_.find("ATI") != std::string::npos)
vendor_ = GPU_VENDOR_ATI;
else if (glVendorString_.find("INTEL") != std::string::npos ||
glVendorString_.find("Intel") != std::string::npos)
vendor_ = GPU_VENDOR_INTEL;
else {
LWARNING("Unknown graphics board vendor: " << glVendorString_);
vendor_ = GPU_VENDOR_UNKNOWN;
}
queryExtensions();
// Shaders
shaderSupport_ = (glVersion_ >= GlVersion::CGT_GL_VERSION_2_0);
shaderSupportARB_ = (isExtensionSupported("GL_ARB_vertex_program") &&
isExtensionSupported("GL_ARB_fragment_program"));
computeShaderSupport_ = (glVersion_ >= GlVersion::CGT_GL_VERSION_4_3);
if (!shaderVersion_.parseVersionString(glslVersionString_)) {
LERROR("Malformed GLSL version string: " << glslVersionString_);
}
// Shader model
// see http://www.opengl.org/wiki/Shading_languages:_How_to_detect_shader_model%3F
// for information about shader models in OpenGL
if (isExtensionSupported("GL_ARB_tessellation_shader"))
shaderModel_ = SHADER_MODEL_5;
else if (isExtensionSupported("GL_ARB_geometry_shader4") ||
isExtensionSupported("GL_EXT_geometry_shader4"))
shaderModel_ = SHADER_MODEL_4;
else if (isExtensionSupported("GL_NV_vertex_program3") ||
isExtensionSupported("GL_ATI_shader_texture_lod"))
shaderModel_ = SHADER_MODEL_3;
else if (glVersion_ >= GlVersion::CGT_GL_VERSION_2_0)
shaderModel_ = SHADER_MODEL_2;
else
shaderModel_ = SHADER_MODEL_UNKNOWN;
// Texturing
glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint *) &maxTexSize_);
texturing3D_ = (glVersion_ >= GlVersion::CGT_GL_VERSION_1_2 ||
isExtensionSupported("GL_EXT_texture3D"));
if (texturing3D_) {
if (glVersion_ >= GlVersion::CGT_GL_VERSION_2_0)
glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, (GLint *) &max3DTexSize_);
else
glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE_EXT, (GLint *) &max3DTexSize_);
}
else {
max3DTexSize_ = 0;
}
numTextureUnits_ = -1;
if (glVersion_ >= GlVersion::CGT_GL_VERSION_3_0)
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (GLint *) &numTextureUnits_);
glGetIntegerv(GL_MAX_IMAGE_UNITS, (GLint*)&numImageUnits_);
npotTextures_ = (isExtensionSupported("GL_ARB_texture_non_power_of_two"));
#if defined(__APPLE__) || defined(__linux__)
// No NPOT support on older ATI Macs and Linux.
// ATI cards up to X1900 report they support this extension, but only do so under certain
// circumstances, else falling back to software.
// See this thread for details:
// http://www.mailinglistarchive.com/mac-opengl@lists.apple.com/msg04204.html
// Therefore we disable it for all Radeon 9*** and X*** cards.
if (getVendor() == GPU_VENDOR_ATI && npotTextures_ &&
(glRendererString_.find("Radeon X") != string::npos ||
glRendererString_.find("RADEON X") != string::npos ||
glRendererString_.find("Radeon 9") != string::npos ||
glRendererString_.find("RADEON 9") != string::npos))
{
LWARNING("Disabling extension ARB_texture_non_power_of_two because it is "
"reported unreliable for ATI cards up to Radeon X1900.");
npotTextures_ = false;
}
#endif
textureRectangles_ = (isExtensionSupported("GL_ARB_texture_rectangle"));
anisotropicFiltering_ = (isExtensionSupported("GL_EXT_texture_filter_anisotropic"));
if (anisotropicFiltering_)
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxTextureAnisotropy_);
else
maxTextureAnisotropy_ = 1.0;
textureCompression_ = (isExtensionSupported("GL_ARB_texture_compression"));
framebufferObjects_ = (isExtensionSupported("GL_EXT_framebuffer_object"));
if(framebufferObjects_) {
glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &maxColorAttachments_);
}
else
maxColorAttachments_ = -1;
}
void GpuCapabilities::queryExtensions()
{
GLint numExtensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
glExtensions_.resize(numExtensions);
if(glGetStringi) {
for(int i = 0; i < numExtensions; ++i) {
const char* strPtr = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i));
if (strPtr && strPtr[0] != '\0')
glExtensions_[i] = string(strPtr);
else
break;
}
}
}
void GpuCapabilities::detectOS() {
osVersion_ = OS_UNKNOWN;
osVersionString_ = "unknown";
std::ostringstream oss;
#ifdef WIN32
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
#pragma warning(push)
#pragma warning(disable:4996) // newer MSVC versions report GetVersionEx() to be deprecated, however the alternative is so poor design, I refuse to implement it... http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx
if (!GetVersionEx((OSVERSIONINFO*)&osvi)) {
oss << "unknown: GetVersionEx() failed";
}
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) {
if (osvi.wProductType == VER_NT_WORKSTATION) {
osVersion_ = OS_WIN_VISTA;
oss << "Windows Vista";
}
else {
osVersion_ = OS_WIN_SERVER_2008;
oss << "Windows Server 2008";
}
}
else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) {
osVersion_ = OS_WIN_SERVER_2003;
oss << "Windows Server 2003";
}
else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) {
osVersion_ = OS_WIN_XP;
oss << "Windows XP";
}
else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) {
osVersion_ = OS_WIN_2000;
oss << "Windows 2000";
}
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) {
osVersion_ = OS_WIN_7;
oss << "Windows 7";
}
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) {
osVersion_ = OS_WIN_8;
oss << "Windows 8";
}
else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) {
osVersion_ = OS_WIN_8_1;
oss << "Windows 8.1";
}
else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0) {
osVersion_ = OS_WIN_10;
oss << "Windows 10";
}
else {
oss << "unknown Windows version " << osvi.dwMajorVersion << "." << osvi.dwMinorVersion;
}
// Include service pack (if any) and build number.
if (strlen(osvi.szCSDVersion) > 0) {
oss << " " << osvi.szCSDVersion;
}
oss << " (build " << osvi.dwBuildNumber << ")";
osVersionString_ = oss.str();
#pragma warning(pop)
#else // WIN32 -> must be UNIX/POSIX
osVersion_ = OS_POSIX;
utsname name;
if (uname(&name) != 0)
return; // command not successful
oss << name.sysname << ' ' << name.release << ' ' << name.version << ' ' << name.machine;
#endif // WIN32
osVersionString_ = oss.str();
}
bool GpuCapabilities::overrideGLSLVersion(const std::string& versionString){
GlVersion overrideVersion;
if (overrideVersion.parseVersionString(versionString)) {
shaderVersion_ = overrideVersion;
return true;
}
else {
LERROR("GLSL version string '" << versionString << "' could not be parsed. "
<< "Keeping detected version: " << shaderVersion_);
return false;
}
}
//-----------------------------------------------------------------------------------
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_1_0(1,0,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_1_1(1,1,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_1_2(1,2,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_1_3(1,3,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_1_4(1,4,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_1_5(1,5,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_2_0(2,0,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_2_1(2,1,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_3_0(3,0,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_3_1(3,1,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_3_2(3,2,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_3_3(3,3,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_4_0(4,0,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_4_1(4,1,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_4_2(4,2,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_4_3(4,3,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_4_4(4,4,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::CGT_GL_VERSION_4_5(4,5,0);
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_110(1,10); ///< GLSL version 1.10
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_120(1,20); ///< GLSL version 1.20
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_130(1,30); ///< GLSL version 1.30
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_140(1,40); ///< GLSL version 1.40
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_150(1,50); ///< GLSL version 1.50
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_330(3,30); ///< GLSL version 3.30
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_400(4, 0); ///< GLSL version 4.00
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_410(4,10); ///< GLSL version 4.10
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_420(4,20); ///< GLSL version 4.20
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_430(4,30); ///< GLSL version 4.30
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_440(4,40); ///< GLSL version 4.40
const GpuCapabilities::GlVersion GpuCapabilities::GlVersion::SHADER_VERSION_450(4,50); ///< GLSL version 4.50
GpuCapabilities::GlVersion::GlVersion(int major, int minor, int release)
: major_(major), minor_(minor), release_(release)
{}
bool GpuCapabilities::GlVersion::parseVersionString(const string& st) {
major_ = -1;
minor_ = -1;
release_ = -1;
string str;
//ignore vendor specific part of the string:
size_t spacePos = st.find_first_of(" ");
if (spacePos != string::npos)
str = st.substr(0, spacePos);
else
str = st;
//explode version string with delimiter ".":
std::vector exploded;
size_t found;
found = str.find_first_of(".");
while(found != string::npos){
if (found > 0){
exploded.push_back(str.substr(0,found));
}
str = str.substr(found+1);
found = str.find_first_of(".");
}
if (str.length() > 0){
exploded.push_back(str);
}
// parse numbers
if (exploded.size() < 2) {
LWARNING("Version string too short to parse!");
return false;
}
stringstream vstr;
vstr << exploded[0];
vstr >> major_;
if (vstr.fail()) {
LERROR("Failed to parse major version! Version string: " << st);
major_ = -1;
return false;
}
vstr.clear();
vstr.str("");
vstr << exploded[1];
vstr >> minor_;
if (vstr.fail()) {
LERROR("Failed to parse minor version! Version string: " << st);
major_ = -1;
minor_ = -1;
return false;
}
vstr.clear();
vstr.str("");
//try to parse release number (not always there):
if (exploded.size() > 2) {
vstr << exploded[2];
vstr >> release_;
if (vstr.fail())
release_ = 0;
}
else
release_ = 0;
return true;
}
bool operator==(const GpuCapabilities::GlVersion& x, const GpuCapabilities::GlVersion& y) {
if ((x.major_ == y.major_) && (x.minor_ == y.minor_) && (x.release_ == y.release_))
return true;
else
return false;
}
bool operator!=(const GpuCapabilities::GlVersion& x, const GpuCapabilities::GlVersion& y) {
if ((x.major_ != y.major_) || (x.minor_ != y.minor_) || (x.release_ != y.release_))
return true;
else
return false;
}
bool operator<(const GpuCapabilities::GlVersion& x, const GpuCapabilities::GlVersion& y) {
if (x.major_ < y.major_)
return true;
else if (x.major_ == y.major_) {
if (x.minor_ < y.minor_)
return true;
else if (x.minor_ == y.minor_)
if (x.release_ < y.release_)
return true;
}
return false;
}
bool operator<=(const GpuCapabilities::GlVersion& x, const GpuCapabilities::GlVersion& y) {
if (x.major_ < y.major_)
return true;
else if (x.major_ == y.major_) {
if (x.minor_ < y.minor_)
return true;
else if (x.minor_ == y.minor_)
if (x.release_ <= y.release_)
return true;
}
return false;
}
bool operator>(const GpuCapabilities::GlVersion& x, const GpuCapabilities::GlVersion& y) {
if (x.major_ > y.major_)
return true;
else if (x.major_ == y.major_) {
if (x.minor_ > y.minor_)
return true;
else if (x.minor_ == y.minor_)
if (x.release_ > y.release_)
return true;
}
return false;
}
bool operator>=(const GpuCapabilities::GlVersion& x, const GpuCapabilities::GlVersion& y) {
if (x.major_ > y.major_)
return true;
else if (x.major_ == y.major_) {
if (x.minor_ > y.minor_)
return true;
else if (x.minor_ == y.minor_)
if (x.release_ >= y.release_)
return true;
}
return false;
}
std::ostream& operator<<(std::ostream& s, const GpuCapabilities::GlVersion& v) {
if (v.major_ == -1)
return (s << "unknown");
else if (v.release_ == 0)
return (s << v.major_ << "." << v.minor_);
else
return (s << v.major_ << "." << v.minor_ << "." << v.release_);
}
} // namespace cgt