Anonymous avatar Anonymous committed b9c0c02 Merge

Merge with latest lindenlab/viewer-development

Comments (0)

Files changed (485)

 0c4d0c24278074f219e5a32e72b449e78301d11b 2.7.1-beta1
 9f79a6ed8fdcd2f3dac33ea6b3236eeb278dccfe 2.7.2-start
 6a3e7e403bd19e45fdfc2fcc716867af3ab80861 2.7.3-start
+6af10678de4736222b2c3f7e010e984fb5b327de 2.7.4-start
 mesh-development.build_viewer_update_version_manager = false
 
 # ========================================
+# mesh-asset-deprecation
+# ========================================
+mesh-asset-deprecation.viewer_channel = "Project Viewer - Mesh Asset Deprecation"
+mesh-asset-deprecation.login_channel = "Project Viewer - Mesh Asset Deprecation"
+mesh-asset-deprecation.viewer_grid = aditi
+mesh-asset-deprecation.build_debug_release_separately = true
+mesh-asset-deprecation.build_CYGWIN_Debug = false
+mesh-asset-deprecation.build_viewer_update_version_manager = false
+
+# ========================================
 # viewer-mesh
 # ========================================
 
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>930bdd987a321eda1838caba8cd6098f</string>
+              <string>b2fe1c860613a68e74d4384be418ffee</string>
               <key>url</key>
-              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-glod/rev/230348/arch/Darwin/installer/glod-1.0pre4-darwin-20110519.tar.bz2</string>
+              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-glod/rev/232684/arch/Darwin/installer/glod-1.0pre4-darwin-20110610.tar.bz2</string>
             </map>
             <key>name</key>
             <string>darwin</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>fb33b6cac2e6b98f93c5efa2af2e5a00</string>
+              <string>c0c64dae149d0892343e2ff300fd06b9</string>
               <key>url</key>
-              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-glod/rev/230348/arch/Linux/installer/glod-1.0pre4-linux-20110519.tar.bz2</string>
+              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-glod/rev/232684/arch/Linux/installer/glod-1.0pre4-linux-20110611.tar.bz2</string>
             </map>
             <key>name</key>
             <string>linux</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>388cf0e292f756b4bb37696622f0bbc5</string>
+              <string>842208365f5b108dac4c7c733b99da9c</string>
               <key>url</key>
-              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-glod/rev/230348/arch/CYGWIN/installer/glod-1.0pre4-windows-20110519.tar.bz2</string>
+              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-glod/rev/232684/arch/CYGWIN/installer/glod-1.0pre4-windows-20110610.tar.bz2</string>
             </map>
             <key>name</key>
             <string>windows</string>
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>c81bacf922bb3b540d92b660364c48ce</string>
+              <string>9bf7a96c1d2fadb180fda91740c945c6</string>
               <key>url</key>
-              <string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/ndofdev-linux-0.2-20101013.tar.bz2</string>
+              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-libndofdev-linux/rev/233137/arch/Linux/installer/libndofdev-0.3-linux-20110617.tar.bz2</string>
             </map>
             <key>name</key>
             <string>linux</string>
               <key>hash</key>
               <string>bb0abe962b3b8208ed2dab0424aab33d</string>
               <key>url</key>
-              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-pcre/rev/228822/arch/Linux/installer/pcre-7.6-linux-20110504.tar.bz2</string>              
+              <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-pcre/rev/228822/arch/Linux/installer/pcre-7.6-linux-20110504.tar.bz2</string>
             </map>
             <key>name</key>
             <string>linux</string>

indra/llcommon/llaccountingquota.h

 
 struct ParcelQuota
 {
-	ParcelQuota( F32 ownerRenderCost, F32 ownerPhysicsCost, F32 ownerNetworkCost, F32 ownerSimulationCost,
-				F32 groupRenderCost, F32 groupPhysicsCost, F32 groupNetworkCost, F32 groupSimulationCost,
-				F32 otherRenderCost, F32 otherPhysicsCost, F32 otherNetworkCost, F32 otherSimulationCost,
-				F32 totalRenderCost, F32 totalPhysicsCost, F32 totalNetworkCost, F32 totalSimulationCost)
+	ParcelQuota( F32 ownerRenderCost,	 F32 ownerPhysicsCost,	  F32 ownerNetworkCost,	   F32 ownerSimulationCost,
+				 F32 groupRenderCost,	 F32 groupPhysicsCost,	  F32 groupNetworkCost,	   F32 groupSimulationCost,
+				 F32 otherRenderCost,	 F32 otherPhysicsCost,	  F32 otherNetworkCost,	   F32 otherSimulationCost,
+				 F32 tempRenderCost,	 F32 tempPhysicsCost,	  F32 tempNetworkCost,	   F32 tempSimulationCost,
+				 F32 selectedRenderCost, F32 selectedPhysicsCost, F32 selectedNetworkCost, F32 selectedSimulationCost,
+				 F32 parcelCapacity )
 	: mOwnerRenderCost( ownerRenderCost ), mOwnerPhysicsCost( ownerPhysicsCost ) 
 	, mOwnerNetworkCost( ownerNetworkCost ), mOwnerSimulationCost( ownerSimulationCost )
 	, mGroupRenderCost( groupRenderCost ), mGroupPhysicsCost( groupPhysicsCost )
 	, mGroupNetworkCost( groupNetworkCost ), mGroupSimulationCost( groupSimulationCost )
 	, mOtherRenderCost( otherRenderCost ), mOtherPhysicsCost( otherPhysicsCost )
 	, mOtherNetworkCost( otherNetworkCost ), mOtherSimulationCost( otherSimulationCost )
-	, mTotalRenderCost( totalRenderCost ), mTotalPhysicsCost( totalPhysicsCost ) 
-	, mTotalNetworkCost( totalNetworkCost ), mTotalSimulationCost( totalSimulationCost )
+	, mTempRenderCost( tempRenderCost ), mTempPhysicsCost( tempPhysicsCost ) 
+	, mTempNetworkCost( tempNetworkCost ), mTempSimulationCost( tempSimulationCost )
+	, mSelectedRenderCost( tempRenderCost ), mSelectedPhysicsCost( tempPhysicsCost ) 
+	, mSelectedNetworkCost( tempNetworkCost ), mSelectedSimulationCost( selectedSimulationCost )
+	, mParcelCapacity( parcelCapacity )
 	{
 	}
+
 	ParcelQuota(){}			
 	F32 mOwnerRenderCost, mOwnerPhysicsCost, mOwnerNetworkCost, mOwnerSimulationCost;
 	F32 mGroupRenderCost, mGroupPhysicsCost, mGroupNetworkCost, mGroupSimulationCost;
 	F32 mOtherRenderCost, mOtherPhysicsCost, mOtherNetworkCost, mOtherSimulationCost;
-	F32 mTotalRenderCost, mTotalPhysicsCost, mTotalNetworkCost, mTotalSimulationCost;
+	F32 mTempRenderCost,  mTempPhysicsCost,  mTempNetworkCost,  mTempSimulationCost;
+	F32 mSelectedRenderCost, mSelectedPhysicsCost, mSelectedNetworkCost, mSelectedSimulationCost;
+	F32 mParcelCapacity;
 };
 
 struct SelectionQuota
 {
-	SelectionQuota( S32 localId, F32 renderCost, F32 physicsCost, F32 networkCost, F32 simulationCost )
+	SelectionQuota( LLUUID localId, F32 renderCost, F32 physicsCost, F32 networkCost, F32 simulationCost )
 	: mLocalId( localId)
 	, mRenderCost( renderCost )
 	, mPhysicsCost( physicsCost )
 	SelectionQuota() {}
 	
 	F32 mRenderCost, mPhysicsCost, mNetworkCost, mSimulationCost;	
-	S32 mLocalId;
+	LLUUID mLocalId;
 };
 
 #endif

indra/llcommon/llsdserialize.cpp

 		{ //copy result into output
 			if (strm.avail_out >= CHUNK)
 			{
-				llerrs << "WTF?" << llendl;
+				free(output);
+				llwarns << "Failed to compress LLSD block." << llendl;
+				return std::string();
 			}
 
 			have = CHUNK-strm.avail_out;

indra/llcommon/llstat.cpp

             }
 		}
 		else
-		{	// WTF?  Shouldn't have a NULL pointer in the map.
+		{	// Shouldn't have a NULL pointer in the map.
             llwarns << "Unexpected NULL dynamic stat at '" << stats_full_path << "'" << llendl;
 		}
 	}	

indra/llcommon/llversionviewer.h

 
 const S32 LL_VERSION_MAJOR = 2;
 const S32 LL_VERSION_MINOR = 7;
-const S32 LL_VERSION_PATCH = 4;
+const S32 LL_VERSION_PATCH = 5;
 const S32 LL_VERSION_BUILD = 0;
 
 const char * const LL_CHANNEL = "Second Life Developer";

indra/llmath/lloctree.h

 
 #define OCT_ERRS LL_WARNS("OctreeErrors")
 
-#define LL_OCTREE_PARANOIA_CHECK 0
+
+extern U32 gOctreeMaxCapacity;
+/*#define LL_OCTREE_PARANOIA_CHECK 0
 #if LL_DARWIN
 #define LL_OCTREE_MAX_CAPACITY 32
 #else
 #define LL_OCTREE_MAX_CAPACITY 128
-#endif
+#endif*/
 
 template <class T> class LLOctreeNode;
 
 class LLOctreeNode : public LLTreeNode<T>
 {
 public:
+
 	typedef LLOctreeTraveler<T>									oct_traveler;
 	typedef LLTreeTraveler<T>									tree_traveler;
 	typedef typename std::set<LLPointer<T> >					element_list;
 		//is it here?
 		if (isInside(data->getPositionGroup()))
 		{
-			if ((getElementCount() < LL_OCTREE_MAX_CAPACITY && contains(data->getBinRadius()) ||
-				(data->getBinRadius() > getSize()[0] &&	parent && parent->getElementCount() >= LL_OCTREE_MAX_CAPACITY))) 
+			if ((getElementCount() < gOctreeMaxCapacity && contains(data->getBinRadius()) ||
+				(data->getBinRadius() > getSize()[0] &&	parent && parent->getElementCount() >= gOctreeMaxCapacity))) 
 			{ //it belongs here
 #if LL_OCTREE_PARANOIA_CHECK
 				//if this is a redundant insertion, error out (should never happen)

indra/llmath/llvolume.cpp

 	uintptr_t t = (uintptr_t) ptr;
 	if (t%alignment != 0)
 	{
-		llerrs << "WTF?" << llendl;
+		llerrs << "Alignment check failed." << llendl;
 	}
 #endif
 }
 		}
 		else
 		{
-			llerrs << "WTF? Empty leaf" << llendl;
+			llerrs << "Empty leaf" << llendl;
 		}
 
 		for (S32 i = 0; i < branch->getChildCount(); ++i)
 	return face;
 }
 
+//static
+S32 LLProfile::getNumNGonPoints(const LLProfileParams& params, S32 sides, F32 offset, F32 bevel, F32 ang_scale, S32 split)
+{ // this is basically LLProfile::genNGon stripped down to only the operations that influence the number of points
+	LLMemType m1(LLMemType::MTYPE_VOLUME);
+	S32 np = 0;
+
+	// Generate an n-sided "circular" path.
+	// 0 is (1,0), and we go counter-clockwise along a circular path from there.
+	F32 t, t_step, t_first, t_fraction;
+	
+	F32 begin  = params.getBegin();
+	F32 end    = params.getEnd();
+
+	t_step = 1.0f / sides;
+	
+	t_first = floor(begin * sides) / (F32)sides;
+
+	// pt1 is the first point on the fractional face.
+	// Starting t and ang values for the first face
+	t = t_first;
+	
+	// Increment to the next point.
+	// pt2 is the end point on the fractional face
+	t += t_step;
+	
+	t_fraction = (begin - t_first)*sides;
+
+	// Only use if it's not almost exactly on an edge.
+	if (t_fraction < 0.9999f)
+	{
+		np++;
+	}
+
+	// There's lots of potential here for floating point error to generate unneeded extra points - DJS 04/05/02
+	while (t < end)
+	{
+		// Iterate through all the integer steps of t.
+		np++;
+
+		t += t_step;
+	}
+
+	t_fraction = (end - (t - t_step))*sides;
+
+	// Find the fraction that we need to add to the end point.
+	t_fraction = (end - (t - t_step))*sides;
+	if (t_fraction > 0.0001f)
+	{
+		np++;
+	}
+
+	// If we're sliced, the profile is open.
+	if ((end - begin)*ang_scale < 0.99f)
+	{
+		if (params.getHollow() <= 0)
+		{
+			// put center point if not hollow.
+			np++;
+		}
+	}
+	
+	return np;
+}
+
 // What is the bevel parameter used for? - DJS 04/05/02
 // Bevel parameter is currently unused but presumedly would support
 // filleted and chamfered corners
 	return face;
 }
 
+//static
+S32 LLProfile::getNumPoints(const LLProfileParams& params, BOOL path_open,F32 detail, S32 split,
+						 BOOL is_sculpted, S32 sculpt_size)
+{ // this is basically LLProfile::generate stripped down to only operations that influence the number of points
+	LLMemType m1(LLMemType::MTYPE_VOLUME);
+	
+	if (detail < MIN_LOD)
+	{
+		detail = MIN_LOD;
+	}
+
+	// Generate the face data
+	F32 hollow = params.getHollow();
+
+	S32 np = 0;
+
+	switch (params.getCurveType() & LL_PCODE_PROFILE_MASK)
+	{
+	case LL_PCODE_PROFILE_SQUARE:
+		{
+			np = getNumNGonPoints(params, 4,-0.375, 0, 1, split);
+		
+			if (hollow)
+			{
+				np *= 2;
+			}
+		}
+		break;
+	case  LL_PCODE_PROFILE_ISOTRI:
+	case  LL_PCODE_PROFILE_RIGHTTRI:
+	case  LL_PCODE_PROFILE_EQUALTRI:
+		{
+			np = getNumNGonPoints(params, 3,0, 0, 1, split);
+						
+			if (hollow)
+			{
+				np *= 2;
+			}
+		}
+		break;
+	case LL_PCODE_PROFILE_CIRCLE:
+		{
+			// If this has a square hollow, we should adjust the
+			// number of faces a bit so that the geometry lines up.
+			U8 hole_type=0;
+			F32 circle_detail = MIN_DETAIL_FACES * detail;
+			if (hollow)
+			{
+				hole_type = params.getCurveType() & LL_PCODE_HOLE_MASK;
+				if (hole_type == LL_PCODE_HOLE_SQUARE)
+				{
+					// Snap to the next multiple of four sides,
+					// so that corners line up.
+					circle_detail = llceil(circle_detail / 4.0f) * 4.0f;
+				}
+			}
+
+			S32 sides = (S32)circle_detail;
+
+			if (is_sculpted)
+				sides = sculpt_size;
+			
+			np = getNumNGonPoints(params, sides);
+			
+			if (hollow)
+			{
+				np *= 2;
+			}
+		}
+		break;
+	case LL_PCODE_PROFILE_CIRCLE_HALF:
+		{
+			// If this has a square hollow, we should adjust the
+			// number of faces a bit so that the geometry lines up.
+			U8 hole_type=0;
+			// Number of faces is cut in half because it's only a half-circle.
+			F32 circle_detail = MIN_DETAIL_FACES * detail * 0.5f;
+			if (hollow)
+			{
+				hole_type = params.getCurveType() & LL_PCODE_HOLE_MASK;
+				if (hole_type == LL_PCODE_HOLE_SQUARE)
+				{
+					// Snap to the next multiple of four sides (div 2),
+					// so that corners line up.
+					circle_detail = llceil(circle_detail / 2.0f) * 2.0f;
+				}
+			}
+			np = getNumNGonPoints(params, llfloor(circle_detail), 0.5f, 0.f, 0.5f);
+			
+			if (hollow)
+			{
+				np *= 2;
+			}
+
+			// Special case for openness of sphere
+			if ((params.getEnd() - params.getBegin()) < 1.f)
+			{
+			}
+			else if (!hollow)
+			{
+				np++;
+			}
+		}
+		break;
+	default:
+	   break;
+	};
+
+	
+	return np;
+}
 
 
 BOOL LLProfile::generate(const LLProfileParams& params, BOOL path_open,F32 detail, S32 split,
 {
 }
 
+S32 LLPath::getNumNGonPoints(const LLPathParams& params, S32 sides, F32 startOff, F32 end_scale, F32 twist_scale)
+{ //this is basically LLPath::genNGon stripped down to only operations that influence the number of points added
+	S32 ret = 0;
+
+	F32 step= 1.0f / sides;
+	F32 t	= params.getBegin();
+	ret = 1;
+	
+	t+=step;
+
+	// Snap to a quantized parameter, so that cut does not
+	// affect most sample points.
+	t = ((S32)(t * sides)) / (F32)sides;
+
+	// Run through the non-cut dependent points.
+	while (t < params.getEnd())
+	{
+		ret++;
+		t+=step;
+	}
+
+	ret++;
+
+	return ret;
+}
+
 void LLPath::genNGon(const LLPathParams& params, S32 sides, F32 startOff, F32 end_scale, F32 twist_scale)
 {
 	// Generates a circular path, starting at (1, 0, 0), counterclockwise along the xz plane.
 	return end_scale;
 }
 
+S32 LLPath::getNumPoints(const LLPathParams& params, F32 detail)
+{ // this is basically LLPath::generate stripped down to only the operations that influence the number of points
+	LLMemType m1(LLMemType::MTYPE_VOLUME);
+	
+	if (detail < MIN_LOD)
+	{
+		detail = MIN_LOD;
+	}
+
+	S32 np = 2; // hardcode for line
+
+	// Is this 0xf0 mask really necessary?  DK 03/02/05
+
+	switch (params.getCurveType() & 0xf0)
+	{
+	default:
+	case LL_PCODE_PATH_LINE:
+		{
+			// Take the begin/end twist into account for detail.
+			np    = llfloor(fabs(params.getTwistBegin() - params.getTwist()) * 3.5f * (detail-0.5f)) + 2;
+		}
+		break;
+
+	case LL_PCODE_PATH_CIRCLE:
+		{
+			// Increase the detail as the revolutions and twist increase.
+			F32 twist_mag = fabs(params.getTwistBegin() - params.getTwist());
+
+			S32 sides = (S32)llfloor(llfloor((MIN_DETAIL_FACES * detail + twist_mag * 3.5f * (detail-0.5f))) * params.getRevolutions());
+
+			np = sides;
+		}
+		break;
+
+	case LL_PCODE_PATH_CIRCLE2:
+		{
+			//genNGon(params, llfloor(MIN_DETAIL_FACES * detail), 4.f, 0.f);
+			np = getNumNGonPoints(params, llfloor(MIN_DETAIL_FACES * detail));
+		}
+		break;
+
+	case LL_PCODE_PATH_TEST:
+
+		np     = 5;
+		break;
+	};
+
+	return np;
+}
+
 BOOL LLPath::generate(const LLPathParams& params, F32 detail, S32 split,
 					  BOOL is_sculpted, S32 sculpt_size)
 {
 		U32 face_count = mdl.size();
 
 		if (face_count == 0)
-		{
-			llerrs << "WTF?" << llendl;
+		{ //no faces unpacked, treat as failed decode
+			llwarns << "found no faces!" << llendl;
+			return false;
 		}
 
 		mVolumeFaces.resize(face_count);
 
 		for (U32 i = 0; i < face_count; ++i)
 		{
+			LLVolumeFace& face = mVolumeFaces[i];
+
+			if (mdl[i].has("NoGeometry"))
+			{ //face has no geometry, continue
+				face.resizeIndices(3);
+				face.resizeVertices(1);
+				memset(face.mPositions, 0, sizeof(LLVector4a));
+				memset(face.mNormals, 0, sizeof(LLVector4a));
+				memset(face.mTexCoords, 0, sizeof(LLVector2));
+				memset(face.mIndices, 0, sizeof(U16)*3);
+				continue;
+			}
+
 			LLSD::Binary pos = mdl[i]["Position"];
 			LLSD::Binary norm = mdl[i]["Normal"];
 			LLSD::Binary tc = mdl[i]["TexCoord0"];
 			LLSD::Binary idx = mdl[i]["TriangleList"];
 
-			LLVolumeFace& face = mVolumeFaces[i];
+			
 
 			//copy out indices
 			face.resizeIndices(idx.size()/2);
 			
 			if (idx.empty() || face.mNumIndices < 3)
 			{ //why is there an empty index list?
-				llerrs <<"WTF?" << llendl;
+				llwarns <<"Empty face present!" << llendl;
 				continue;
 			}
 
 			LLVector4a& min = face.mExtents[0];
 			LLVector4a& max = face.mExtents[1];
 
-			min.clear();
-			max.clear();
-			min = max = face.mPositions[0];
-
-			for (S32 i = 1; i < face.mNumVertices; ++i)
+			if (face.mNumVertices < 3)
+			{ //empty face, use a dummy 1cm (at 1m scale) bounding box
+				min.splat(-0.005f);
+				max.splat(0.005f);
+			}
+			else
 			{
-				min.setMin(min, face.mPositions[i]);
-				max.setMax(max, face.mPositions[i]);
+				min = max = face.mPositions[0];
+
+				for (S32 i = 1; i < face.mNumVertices; ++i)
+				{
+					min.setMin(min, face.mPositions[i]);
+					max.setMax(max, face.mPositions[i]);
+				}
 			}
 		}
 	}
 		// don't test lowest LOD to support legacy content DEV-33670
 		if (mDetail > SCULPT_MIN_AREA_DETAIL)
 		{
-			if (sculptGetSurfaceArea() < SCULPT_MIN_AREA)
+			F32 area = sculptGetSurfaceArea();
+
+			const F32 SCULPT_MAX_AREA = 32.f;
+
+			if (area < SCULPT_MIN_AREA || area > SCULPT_MAX_AREA)
 			{
 				data_is_empty = TRUE;
 			}
 	return index;
 }
 
+void LLVolume::getLoDTriangleCounts(const LLVolumeParams& params, S32* counts)
+{ //attempt to approximate the number of triangles that will result from generating a volume LoD set for the 
+	//supplied LLVolumeParams -- inaccurate, but a close enough approximation for determining streaming cost
+	F32 detail[] = {1.f, 1.5f, 2.5f, 4.f};	
+	for (S32 i = 0; i < 4; i++)
+	{
+		S32 count = 0;
+		S32 path_points = LLPath::getNumPoints(params.getPathParams(), detail[i]);
+		S32 profile_points = LLProfile::getNumPoints(params.getProfileParams(), false, detail[i]);
+
+		count = (profile_points-1)*2*(path_points-1);
+		count += profile_points*2;
+
+		counts[i] = count;
+	}
+}
+
 S32 LLVolume::getNumTriangleIndices() const
 {
 	BOOL profile_open = getProfile().isOpen();
 	mOctree(NULL)
 {
 	mExtents = (LLVector4a*) ll_aligned_malloc_16(sizeof(LLVector4a)*3);
+	mExtents[0].splat(-0.5f);
+	mExtents[1].splat(0.5f);
 	mCenter = mExtents+2;
 }
 
 	
 	LLVCacheLRU cache;
 	
+	if (mNumVertices < 3)
+	{ //nothing to do
+		return;
+	}
+
 	//mapping of vertices to triangles and indices
 	std::vector<LLVCacheVertexData> vertex_data;
 

indra/llmath/llvolume.h

 	BOOL isFlat(S32 face) const							{ return (mFaces[face].mCount == 2); }
 	BOOL isOpen() const									{ return mOpen; }
 	void setDirty()										{ mDirty     = TRUE; }
+
+	static S32 getNumPoints(const LLProfileParams& params, BOOL path_open, F32 detail = 1.0f, S32 split = 0,
+				  BOOL is_sculpted = FALSE, S32 sculpt_size = 0);
 	BOOL generate(const LLProfileParams& params, BOOL path_open, F32 detail = 1.0f, S32 split = 0,
 				  BOOL is_sculpted = FALSE, S32 sculpt_size = 0);
 	BOOL isConcave() const								{ return mConcave; }
 
 protected:
 	void genNormals(const LLProfileParams& params);
+	static S32 getNumNGonPoints(const LLProfileParams& params, S32 sides, F32 offset=0.0f, F32 bevel = 0.0f, F32 ang_scale = 1.f, S32 split = 0);
 	void genNGon(const LLProfileParams& params, S32 sides, F32 offset=0.0f, F32 bevel = 0.0f, F32 ang_scale = 1.f, S32 split = 0);
 
 	Face* addHole(const LLProfileParams& params, BOOL flat, F32 sides, F32 offset, F32 box_hollow, F32 ang_scale, S32 split = 0);
 
 	virtual ~LLPath();
 
+	static S32 getNumPoints(const LLPathParams& params, F32 detail);
+	static S32 getNumNGonPoints(const LLPathParams& params, S32 sides, F32 offset=0.0f, F32 end_scale = 1.f, F32 twist_scale = 1.f);
+
 	void genNGon(const LLPathParams& params, S32 sides, F32 offset=0.0f, F32 end_scale = 1.f, F32 twist_scale = 1.f);
 	virtual BOOL generate(const LLPathParams& params, F32 detail=1.0f, S32 split = 0,
 						  BOOL is_sculpted = FALSE, S32 sculpt_size = 0);
 
 	// returns number of triangle indeces required for path/profile mesh
 	S32 getNumTriangleIndices() const;
+	static void getLoDTriangleCounts(const LLVolumeParams& params, S32* counts);
 
 	S32 getNumTriangles() const;
 

indra/llmessage/message_prehash.cpp

 char const* const _PREHASH_ObjectDeselect = LLMessageStringTable::getInstance()->getString("ObjectDeselect");
 char const* const _PREHASH_NewAssetID = LLMessageStringTable::getInstance()->getString("NewAssetID");
 char const* const _PREHASH_ObjectAdd = LLMessageStringTable::getInstance()->getString("ObjectAdd");
+char const* const _PREHASH_SimulatorFeatures = LLMessageStringTable::getInstance()->getString("SimulatorFeatures");
 char const* const _PREHASH_RayEndIsIntersection = LLMessageStringTable::getInstance()->getString("RayEndIsIntersection");
 char const* const _PREHASH_CompleteAuction = LLMessageStringTable::getInstance()->getString("CompleteAuction");
 char const* const _PREHASH_CircuitCode = LLMessageStringTable::getInstance()->getString("CircuitCode");

indra/llmessage/message_prehash.h

 extern char const* const _PREHASH_ObjectDeselect;
 extern char const* const _PREHASH_NewAssetID;
 extern char const* const _PREHASH_ObjectAdd;
+extern char const* const _PREHASH_SimulatorFeatures;
 extern char const* const _PREHASH_RayEndIsIntersection;
 extern char const* const _PREHASH_CompleteAuction;
 extern char const* const _PREHASH_CircuitCode;

indra/llprimitive/llmodel.cpp

 	"low_lod",
 	"medium_lod",
 	"high_lod",
-	"physics_shape"
+	"physics_mesh"
 };
 
 const int MODEL_NAMES_LENGTH = sizeof(model_names) / sizeof(std::string);
 			domInputLocal_Array& v_inp = vertices->getInput_array();
 			if (inputs[j]->getOffset() != 0)
 			{
-				llerrs << "WTF?" << llendl;
+				llerrs << "Vertex array offset MUST be zero." << llendl;
 			}
 
 			for (U32 k = 0; k < v_inp.getCount(); ++k)
 
 					if (src->getTechnique_common()->getAccessor()->getStride() != 3)
 					{
-						llerrs << "WTF?" << llendl;
+						llerrs << "Vertex array stride MUST be three." << llendl;
 					}
 
 					domListOfFloats& v = src->getFloat_array()->getValue();
 	}
 }
 
-void get_dom_sources(const domInputLocalOffset_Array& inputs, S32& pos_offset, S32& tc_offset, S32& norm_offset, S32 &idx_stride,
+bool get_dom_sources(const domInputLocalOffset_Array& inputs, S32& pos_offset, S32& tc_offset, S32& norm_offset, S32 &idx_stride,
 					 domSource* &pos_source, domSource* &tc_source, domSource* &norm_source)
 {
+	
 	idx_stride = 0;
 
 	for (U32 j = 0; j < inputs.getCount(); ++j)
 			const domURIFragmentType& uri = inputs[j]->getSource();
 			daeElementRef elem = uri.getElement();
 			domVertices* vertices = (domVertices*) elem.cast();
-
+			if ( !vertices )
+			{
+				return false;
+			}
+				
 			domInputLocal_Array& v_inp = vertices->getInput_array();
 			
 			
 	}
 
 	idx_stride += 1;
+	
+	return true;
 }
 
 LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domTrianglesRef& tri)
 
 	S32 idx_stride = 0;
 
-	get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source);
+	if ( !get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source) || !pos_source )
+	{
+		return LLModel::BAD_ELEMENT;
+	}
 
+	
 	domPRef p = tri->getP();
 	domListOfUInts& idx = p->getValue();
 	
 
 	S32 idx_stride = 0;
 
-	get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source);
+	if (!get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source))
+	{
+		return LLModel::BAD_ELEMENT;
+	}
 
 	LLVolumeFace face;
 
 			const domURIFragmentType& uri = inputs[i]->getSource();
 			daeElementRef elem = uri.getElement();
 			domVertices* vertices = (domVertices*) elem.cast();
-
+			if (!vertices)
+			{
+				return LLModel::BAD_ELEMENT;
+			}
 			domInputLocal_Array& v_inp = vertices->getInput_array();
 
 			for (U32 k = 0; k < v_inp.getCount(); ++k)
 					const domURIFragmentType& uri = v_inp[k]->getSource();
 					daeElementRef elem = uri.getElement();
 					domSource* src = (domSource*) elem.cast();
+					if (!src)
+					{
+						return LLModel::BAD_ELEMENT;
+					}
 					v = &(src->getFloat_array()->getValue());
 				}
 			}
 			const domURIFragmentType& uri = inputs[i]->getSource();
 			daeElementRef elem = uri.getElement();
 			domSource* src = (domSource*) elem.cast();
+			if (!src)
+			{
+				return LLModel::BAD_ELEMENT;
+			}
 			n = &(src->getFloat_array()->getValue());
 		}
 		else if (strcmp(COMMON_PROFILE_INPUT_TEXCOORD, inputs[i]->getSemantic()) == 0 && inputs[i]->getSet() == 0)
 			const domURIFragmentType& uri = inputs[i]->getSource();
 			daeElementRef elem = uri.getElement();
 			domSource* src = (domSource*) elem.cast();
+			if (!src)
+			{
+				return LLModel::BAD_ELEMENT;
+			}
 			t = &(src->getFloat_array()->getValue());
 		}
 	}
 		}
 	}
 
-	if (cur_idx != vert_idx.size())
-	{
-		llerrs << "WTF?" << llendl;
-	}
-
 	//build vertex array from map
 	std::vector<LLVolumeFace::VertexData> new_verts;
 	new_verts.resize(vert_idx.size());
 //static
 std::string LLModel::getStatusString(U32 status)
 {
-	const static std::string status_strings[(S32)INVALID_STATUS] = {"status_no_error", "status_vertex_number_overflow"};
+	const static std::string status_strings[(S32)INVALID_STATUS] = {"status_no_error", "status_vertex_number_overflow","bad_element"};
 
 	if(status < INVALID_STATUS)
 	{
 	for (U32 i = 0; i < polys.getCount(); ++i)
 	{
 		domPolylistRef& poly = polys.get(i);
-
 		mStatus = load_face_from_dom_polylist(mVolumeFaces, mMaterialList, poly);
 
 		if(mStatus != NO_ERRORS)
 			return ; //abort
 		}
 	}
-
+	
 	domPolygons_Array& polygons = mesh->getPolygons_array();
+	
 	for (U32 i = 0; i < polygons.getCount(); ++i)
 	{
 		domPolygonsRef& poly = polygons.get(i);
-
 		mStatus = load_face_from_dom_polygons(mVolumeFaces, mMaterialList, poly);
 
 		if(mStatus != NO_ERRORS)
 			return ; //abort
 		}
 	}
-
+ 
 }
 
 BOOL LLModel::createVolumeFacesFromDomMesh(domMesh* mesh)
 	{
 		LLVector4a min, max;
 		
-		if (mVolumeFaces[0].mNumVertices <= 0)
-		{
-			llerrs << "WTF?" << llendl;
-		}
-
 		// For all of the volume faces
 		// in the model, loop over
 		// them and see what the extents
 		{
 			LLVolumeFace& face = mVolumeFaces[i];
 
-			if (face.mNumVertices <= 0)
-			{
-				llerrs << "WTF?" << llendl;
-			}
-
 			update_min_max(min, max, face.mExtents[0]);
 			update_min_max(min, max, face.mExtents[1]);
 		}
 				{
 					LLVector4a& n = iter->second[k].getNormal();
 
-					if (!iter->second[k].getPosition().equals3(new_face.mPositions[i]))
-					{
-						llerrs << "WTF?" << llendl;
-					}
-
 					F32 cur = n.dot3(ref_norm).getF32();
 
 					if (cur > best)
 	if (!decomp.mBaseHull.empty() ||
 		!decomp.mHull.empty())		
 	{
-		mdl["decomposition"] = decomp.asLLSD();
+		mdl["physics_convex"] = decomp.asLLSD();
+		if (!decomp.mHull.empty())
+		{ //convex decomposition exists, physics mesh will not be used
+			model[LLModel::LOD_PHYSICS] = NULL;
+		}
 	}
 
 	for (U32 idx = 0; idx < MODEL_NAMES_LENGTH; ++idx)
 			for (S32 i = 0; i < model[idx]->getNumVolumeFaces(); ++i)
 			{ //for each face
 				const LLVolumeFace& face = model[idx]->getVolumeFace(i);
-				if (!face.mNumVertices)
+				if (face.mNumVertices < 3)
 				{ //don't export an empty face
+					mdl[model_names[idx]][i]["NoGeometry"] = true;
 					continue;
 				}
 				LLSD::Binary verts(face.mNumVertices*3*2);
 					//position + normal
 					for (U32 k = 0; k < 3; ++k)
 					{ //for each component
-
 						//convert to 16-bit normalized across domain
 						U16 val = (U16) (((pos[k]-min_pos.mV[k])/pos_range.mV[k])*65535);
 
 				//write out face data
 				mdl[model_names[idx]][i]["PositionDomain"]["Min"] = min_pos.getValue();
 				mdl[model_names[idx]][i]["PositionDomain"]["Max"] = max_pos.getValue();
-
 				mdl[model_names[idx]][i]["TexCoord0Domain"]["Min"] = min_tc.getValue();
 				mdl[model_names[idx]][i]["TexCoord0Domain"]["Max"] = max_tc.getValue();
 
 
 						weight_list& weights = high->getJointInfluences(pos);
 
-						if (weights.size() > 4)
-						{
-							llerrs << "WTF?" << llendl;
-						}
-
 						S32 count = 0;
 						for (weight_list::iterator iter = weights.begin(); iter != weights.end(); ++iter)
 						{
 			cur_offset += size;
 			bytes += size;
 		}
-		else
-		{
-			llerrs << "WTF?" << llendl;
-		}
 	}
 
 	std::string decomposition;
 
-	if (mdl.has("decomposition"))
+	if (mdl.has("physics_convex"))
 	{ //write out convex decomposition
-		decomposition = zip_llsd(mdl["decomposition"]);
+		decomposition = zip_llsd(mdl["physics_convex"]);
 
 		U32 size = decomposition.size();
 		if (size > 0)
 		{
-			header["decomposition"]["offset"] = (LLSD::Integer) cur_offset;
-			header["decomposition"]["size"] = (LLSD::Integer) size;
+			header["physics_convex"]["offset"] = (LLSD::Integer) cur_offset;
+			header["physics_convex"]["size"] = (LLSD::Integer) size;
 			cur_offset += size;
 			bytes += size;
 		}
 			cur_offset += size;
 			bytes += size;
 		}
-		else
-		{
-			header[model_names[i]]["offset"] = -1;
-			header[model_names[i]]["size"] = 0;
-		}
 	}
 
 	if (!nowrite)
 
 		if (!decomposition.empty())
 		{ //write decomposition block
-			ostr.write((const char*) decomposition.data(), header["decomposition"]["size"].asInteger());
+			ostr.write((const char*) decomposition.data(), header["physics_convex"]["size"].asInteger());
 		}
 
 		for (S32 i = 0; i < MODEL_NAMES_LENGTH; i++)
 	{
 		if ((iter->first - pos).magVec() > 0.1f)
 		{
-			llerrs << "WTF?" << llendl;
+			llerrs << "Couldn't find weight list." << llendl;
 		}
 
 		return iter->second;
 	if (mHullPoints > 0)
 	{
 		mCenterOfHullCenters *= 1.f / mHullPoints;
-		llassert(mPhysics.asLLSD().has("HullList"));
+		llassert(mPhysics.hasHullList());
 	}
 }
 
 		"low_lod",
 		"medium_lod",
 		"high_lod",
-		"physics_shape",
+		"physics_mesh",
 	};
 
 	const S32 MODEL_LODS = 5;
 
 bool LLModel::loadDecomposition(LLSD& header, std::istream& is)
 {
-	S32 offset = header["decomposition"]["offset"].asInteger();
-	S32 size = header["decomposition"]["size"].asInteger();
+	S32 offset = header["physics_convex"]["offset"].asInteger();
+	S32 size = header["physics_convex"]["size"].asInteger();
 
 	if (offset >= 0 && size > 0)
 	{
 	{
 		// updated for const-correctness. gcc is picky about this type of thing - Nyx
 		const LLSD::Binary& hulls = decomp["HullList"].asBinary();
-		const LLSD::Binary& position = decomp["Position"].asBinary();
+		const LLSD::Binary& position = decomp["Positions"].asBinary();
 
 		U16* p = (U16*) &position[0];
 
 		LLVector3 max;
 		LLVector3 range;
 
-		min.setValue(decomp["Min"]);
-		max.setValue(decomp["Max"]);
+		if (decomp.has("Min"))
+		{
+			min.setValue(decomp["Min"]);
+			max.setValue(decomp["Max"]);
+		}
+		else
+		{
+			min.set(-0.5f, -0.5f, -0.5f);
+			max.set(0.5f, 0.5f, 0.5f);
+		}
+
 		range = max-min;
 
-		
 		for (U32 i = 0; i < hulls.size(); ++i)
 		{
 			U16 count = (hulls[i] == 0) ? 256 : hulls[i];
 				//point must be unique
 				//llassert(valid.find(test) == valid.end());
 				valid.insert(test);
+
 				mHull[i].push_back(LLVector3(
 					(F32) p[0]/65535.f*range.mV[0]+min.mV[0],
 					(F32) p[1]/65535.f*range.mV[1]+min.mV[1],
 		}
 	}
 
-	if (decomp.has("Hull"))
+	if (decomp.has("BoundingVerts"))
 	{
-		const LLSD::Binary& position = decomp["Hull"].asBinary();
+		const LLSD::Binary& position = decomp["BoundingVerts"].asBinary();
 
 		U16* p = (U16*) &position[0];
 
 	{
 		//empty base hull mesh to indicate decomposition has been loaded
 		//but contains no base hull
-		mBaseHullMesh.clear();;
+		mBaseHullMesh.clear();
 	}
 }
 
+bool LLModel::Decomposition::hasHullList() const
+{
+	return !mHull.empty() ;
+}
+
 LLSD LLModel::Decomposition::asLLSD() const
 {
 	LLSD ret;
 	}
 
 	//write decomposition block
-	// ["decomposition"]["HullList"] -- list of 8 bit integers, each entry represents a hull with specified number of points
-	// ["decomposition"]["PositionDomain"]["Min"/"Max"]
-	// ["decomposition"]["Position"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points
-	// ["decomposition"]["Hull"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points representing a single hull approximation of given shape
-	
+	// ["physics_convex"]["HullList"] -- list of 8 bit integers, each entry represents a hull with specified number of points
+	// ["physics_convex"]["Position"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points
+	// ["physics_convex"]["BoundingVerts"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points representing a single hull approximation of given shape
 	
 	//get minimum and maximum
 	LLVector3 min;
 	{
 		LLSD::Binary p(total*3*2);
 
+		LLVector3 min(-0.5f, -0.5f, -0.5f);
+		LLVector3 max(0.5f, 0.5f, 0.5f);
 		LLVector3 range = max-min;
 
 		U32 vert_idx = 0;
 				U64 test = 0;
 				for (U32 k = 0; k < 3; k++)
 				{
+					F32* src = (F32*) (mHull[i][j].mV);
+
+					llassert(src[k] <= 0.501f && src[k] >= -0.501f);
+
 					//convert to 16-bit normalized across domain
-					U16 val = (U16) (((mHull[i][j].mV[k]-min.mV[k])/range.mV[k])*65535);
+					U16 val = (U16) (((src[k]-min.mV[k])/range.mV[k])*65535);
 
-					switch (k)
+					if(valid.size() < 3)
 					{
-						case 0: test = test | (U64) val; break;
-						case 1: test = test | ((U64) val << 16); break;
-						case 2: test = test | ((U64) val << 32); break;
-					};
+						switch (k)
+						{
+							case 0: test = test | (U64) val; break;
+							case 1: test = test | ((U64) val << 16); break;
+							case 2: test = test | ((U64) val << 32); break;
+						};
 
-					valid.insert(test);
+						valid.insert(test);
+					}
 					
 					U8* buff = (U8*) &val;
 					//write to binary buffer
 				}
 			}
 
-			//must have at least 4 unique points
-			llassert(valid.size() > 3);
+			//must have at least 3 unique points
+			llassert(valid.size() > 2);
 		}
 
-		ret["Position"] = p;
+		ret["Positions"] = p;
 	}
 
+	//llassert(!mBaseHull.empty());
+
 	if (!mBaseHull.empty())
 	{
 		LLSD::Binary p(mBaseHull.size()*3*2);
 
+		LLVector3 min(-0.5f, -0.5f, -0.5f);
+		LLVector3 max(0.5f, 0.5f, 0.5f);
 		LLVector3 range = max-min;
 
 		U32 vert_idx = 0;
 		{
 			for (U32 k = 0; k < 3; k++)
 			{
+				llassert(mBaseHull[j].mV[k] <= 0.51f && mBaseHull[j].mV[k] >= -0.51f);
+
 				//convert to 16-bit normalized across domain
 				U16 val = (U16) (((mBaseHull[j].mV[k]-min.mV[k])/range.mV[k])*65535);
 
 
 				if (vert_idx > p.size())
 				{
-					llerrs << "WTF?" << llendl;
+					llerrs << "Index out of bounds" << llendl;
 				}
 			}
 		}
 		
-		ret["Hull"] = p;
+		ret["BoundingVerts"] = p;
 	}
 
 	return ret;
 	{ //take physics shape mesh from rhs
 		mPhysicsShapeMesh = rhs->mPhysicsShapeMesh;
 	}
-
-	if (!mHull.empty())
-	{ //verify
-		llassert(asLLSD().has("HullList"));
-	}
 }
 

indra/llprimitive/llmodel.h

 	{
 		NO_ERRORS = 0,
 		VERTEX_NUMBER_OVERFLOW, //vertex number is >= 65535.
+		BAD_ELEMENT,
 		INVALID_STATUS
 	} ;
 
 		Decomposition(LLSD& data);
 		void fromLLSD(LLSD& data);
 		LLSD asLLSD() const;
+		bool hasHullList() const;
 
 		void merge(const Decomposition* rhs);
 

indra/llrender/llgl.cpp

 PFNGLGETBUFFERPARAMETERIVARBPROC	glGetBufferParameterivARB = NULL;
 PFNGLGETBUFFERPOINTERVARBPROC		glGetBufferPointervARB = NULL;
 
+// GL_ARB_map_buffer_range
+PFNGLMAPBUFFERRANGEPROC			glMapBufferRange;
+PFNGLFLUSHMAPPEDBUFFERRANGEPROC	glFlushMappedBufferRange;
+
+
 // vertex object prototypes
 PFNGLNEWOBJECTBUFFERATIPROC			glNewObjectBufferATI = NULL;
 PFNGLISOBJECTBUFFERATIPROC			glIsObjectBufferATI = NULL;
 PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample = NULL;
 PFNGLFRAMEBUFFERTEXTURELAYERPROC glFramebufferTextureLayer = NULL;
 
+//GL_ARB_texture_multisample
+PFNGLTEXIMAGE2DMULTISAMPLEPROC glTexImage2DMultisample;
+PFNGLTEXIMAGE3DMULTISAMPLEPROC glTexImage3DMultisample;
+PFNGLGETMULTISAMPLEFVPROC glGetMultisamplefv;
+PFNGLSAMPLEMASKIPROC glSampleMaski;
+
 // GL_EXT_blend_func_separate
 PFNGLBLENDFUNCSEPARATEEXTPROC glBlendFuncSeparateEXT = NULL;
 
 	mHasMipMapGeneration(FALSE),
 	mHasCompressedTextures(FALSE),
 	mHasFramebufferObject(FALSE),
+	mMaxSamples(0),
 	mHasBlendFuncSeparate(FALSE),
 
 	mHasVertexBufferObject(FALSE),
+	mHasMapBufferRange(FALSE),
 	mHasPBuffer(FALSE),
 	mHasShaderObjects(FALSE),
 	mHasVertexShader(FALSE),
 	mHasPointParameters(FALSE),
 	mHasDrawBuffers(FALSE),
 	mHasTextureRectangle(FALSE),
+	mHasTextureMultisample(FALSE),
+	mMaxSampleMaskWords(0),
+	mMaxColorTextureSamples(0),
+	mMaxDepthTextureSamples(0),
+	mMaxIntegerSamples(0),
 
 	mHasAnisotropic(FALSE),
 	mHasARBEnvCombine(FALSE),
 	{
 		GLint num_tex_image_units;
 		glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &num_tex_image_units);
-		mNumTextureImageUnits = num_tex_image_units;
+		mNumTextureImageUnits = llmin(num_tex_image_units, 32);
+	}
+
+	if (mHasTextureMultisample)
+	{
+		glGetIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &mMaxColorTextureSamples);
+		glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &mMaxDepthTextureSamples);
+		glGetIntegerv(GL_MAX_INTEGER_SAMPLES, &mMaxIntegerSamples);
+		glGetIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &mMaxSampleMaskWords);
+	}
+
+	if (mHasFramebufferObject)
+	{
+		glGetIntegerv(GL_MAX_SAMPLES, &mMaxSamples);
 	}
 	
 	setToDebugGPU();
 	return gl_string;
 }
 
+U32 LLGLManager::getNumFBOFSAASamples(U32 samples)
+{
+	samples = llmin(samples, (U32) mMaxColorTextureSamples);
+	samples = llmin(samples, (U32) mMaxDepthTextureSamples);
+	samples = llmin(samples, (U32) 4);
+	return samples;
+}
+
 void LLGLManager::shutdownGL()
 {
 	if (mInited)
 	mHasOcclusionQuery = ExtensionExists("GL_ARB_occlusion_query", gGLHExts.mSysExts);
 	mHasOcclusionQuery2 = ExtensionExists("GL_ARB_occlusion_query2", gGLHExts.mSysExts);
 	mHasVertexBufferObject = ExtensionExists("GL_ARB_vertex_buffer_object", gGLHExts.mSysExts);
+	mHasMapBufferRange = ExtensionExists("GL_ARB_map_buffer_range", gGLHExts.mSysExts);
 	mHasDepthClamp = ExtensionExists("GL_ARB_depth_clamp", gGLHExts.mSysExts) || ExtensionExists("GL_NV_depth_clamp", gGLHExts.mSysExts);
 	// mask out FBO support when packed_depth_stencil isn't there 'cause we need it for LLRenderTarget -Brad
 #ifdef GL_ARB_framebuffer_object
 	mHasDrawBuffers = ExtensionExists("GL_ARB_draw_buffers", gGLHExts.mSysExts);
 	mHasBlendFuncSeparate = ExtensionExists("GL_EXT_blend_func_separate", gGLHExts.mSysExts);
 	mHasTextureRectangle = ExtensionExists("GL_ARB_texture_rectangle", gGLHExts.mSysExts);
+	mHasTextureMultisample = ExtensionExists("GL_ARB_texture_multisample", gGLHExts.mSysExts);
 #if !LL_DARWIN
 	mHasPointParameters = !mIsATI && ExtensionExists("GL_ARB_point_parameters", gGLHExts.mSysExts);
 #endif
 			mHasVertexBufferObject = FALSE;
 		}
 	}
+	if (mHasMapBufferRange)
+	{
+		glMapBufferRange = (PFNGLMAPBUFFERRANGEPROC) GLH_EXT_GET_PROC_ADDRESS("glMapBufferRange");
+		glFlushMappedBufferRange = (PFNGLFLUSHMAPPEDBUFFERRANGEPROC) GLH_EXT_GET_PROC_ADDRESS("glFlushMappedBufferRange");
+	}
 	if (mHasFramebufferObject)
 	{
 		llinfos << "initExtensions() FramebufferObject-related procs..." << llendl;
 	{
 		glBlendFuncSeparateEXT = (PFNGLBLENDFUNCSEPARATEEXTPROC) GLH_EXT_GET_PROC_ADDRESS("glBlendFuncSeparateEXT");
 	}
+	if (mHasTextureMultisample)
+	{
+		glTexImage2DMultisample = (PFNGLTEXIMAGE2DMULTISAMPLEPROC) GLH_EXT_GET_PROC_ADDRESS("glTexImage2DMultisample");
+		glTexImage3DMultisample = (PFNGLTEXIMAGE3DMULTISAMPLEPROC) GLH_EXT_GET_PROC_ADDRESS("glTexImage3DMultisample");
+		glGetMultisamplefv = (PFNGLGETMULTISAMPLEFVPROC) GLH_EXT_GET_PROC_ADDRESS("glGetMultisamplefv");
+		glSampleMaski = (PFNGLSAMPLEMASKIPROC) GLH_EXT_GET_PROC_ADDRESS("glSampleMaski");
+	}	
 #if (!LL_LINUX && !LL_SOLARIS) || LL_LINUX_NV_GL_HEADERS
 	// This is expected to be a static symbol on Linux GL implementations, except if we use the nvidia headers - bah
 	glDrawRangeElements = (PFNGLDRAWRANGEELEMENTSPROC)GLH_EXT_GET_PROC_ADDRESS("glDrawRangeElements");
 		}
 	}
 
-	GLint maxTextureUnits = 0;
-	glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &maxTextureUnits);
-	stop_glerror();
-
 	static const char* label[] =
 	{