/** * @file llvertexbuffer.cpp * @brief LLVertexBuffer implementation * * $LicenseInfo:firstyear=2003&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * 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 along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include #include "llsys.h" #include "llvertexbuffer.h" // #include "llrender.h" #include "llglheaders.h" #include "llmemtype.h" #include "llrender.h" //============================================================================ //static LLVBOPool LLVertexBuffer::sStreamVBOPool; LLVBOPool LLVertexBuffer::sDynamicVBOPool; LLVBOPool LLVertexBuffer::sStreamIBOPool; LLVBOPool LLVertexBuffer::sDynamicIBOPool; U32 LLVertexBuffer::sBindCount = 0; U32 LLVertexBuffer::sSetCount = 0; S32 LLVertexBuffer::sCount = 0; S32 LLVertexBuffer::sGLCount = 0; S32 LLVertexBuffer::sMappedCount = 0; BOOL LLVertexBuffer::sDisableVBOMapping = FALSE ; BOOL LLVertexBuffer::sEnableVBOs = TRUE; U32 LLVertexBuffer::sGLRenderBuffer = 0; U32 LLVertexBuffer::sGLRenderIndices = 0; U32 LLVertexBuffer::sLastMask = 0; BOOL LLVertexBuffer::sVBOActive = FALSE; BOOL LLVertexBuffer::sIBOActive = FALSE; U32 LLVertexBuffer::sAllocatedBytes = 0; BOOL LLVertexBuffer::sMapped = FALSE; BOOL LLVertexBuffer::sUseStreamDraw = TRUE; std::vector LLVertexBuffer::sDeleteList; S32 LLVertexBuffer::sTypeOffsets[LLVertexBuffer::TYPE_MAX] = { sizeof(LLVector3), // TYPE_VERTEX, sizeof(LLVector3), // TYPE_NORMAL, sizeof(LLVector2), // TYPE_TEXCOORD0, sizeof(LLVector2), // TYPE_TEXCOORD1, sizeof(LLVector2), // TYPE_TEXCOORD2, sizeof(LLVector2), // TYPE_TEXCOORD3, sizeof(LLColor4U), // TYPE_COLOR, sizeof(LLVector3), // TYPE_BINORMAL, sizeof(F32), // TYPE_WEIGHT, sizeof(LLVector4), // TYPE_CLOTHWEIGHT, }; U32 LLVertexBuffer::sGLMode[LLRender::NUM_MODES] = { GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_QUADS, GL_LINE_LOOP, }; //static void LLVertexBuffer::setupClientArrays(U32 data_mask) { /*if (LLGLImmediate::sStarted) { llerrs << "Cannot use LLGLImmediate and LLVertexBuffer simultaneously!" << llendl; }*/ if (sLastMask != data_mask) { U32 mask[] = { MAP_VERTEX, MAP_NORMAL, MAP_TEXCOORD0, MAP_COLOR, }; GLenum array[] = { GL_VERTEX_ARRAY, GL_NORMAL_ARRAY, GL_TEXTURE_COORD_ARRAY, GL_COLOR_ARRAY, }; BOOL error = FALSE; for (U32 i = 0; i < 4; ++i) { if (sLastMask & mask[i]) { //was enabled if (!(data_mask & mask[i]) && i > 0) { //needs to be disabled glDisableClientState(array[i]); } else if (gDebugGL) { //needs to be enabled, make sure it was (DEBUG TEMPORARY) if (i > 0 && !glIsEnabled(array[i])) { if (gDebugSession) { error = TRUE; gFailLog << "Bad client state! " << array[i] << " disabled." << std::endl; } else { llerrs << "Bad client state! " << array[i] << " disabled." << llendl; } } } } else { //was disabled if (data_mask & mask[i]) { //needs to be enabled glEnableClientState(array[i]); } else if (gDebugGL && glIsEnabled(array[i])) { //needs to be disabled, make sure it was (DEBUG TEMPORARY) if (gDebugSession) { error = TRUE; gFailLog << "Bad client state! " << array[i] << " enabled." << std::endl; } else { llerrs << "Bad client state! " << array[i] << " enabled." << llendl; } } } } if (error) { ll_fail("LLVertexBuffer::setupClientArrays failed"); } U32 map_tc[] = { MAP_TEXCOORD1, MAP_TEXCOORD2, MAP_TEXCOORD3 }; for (U32 i = 0; i < 3; i++) { if (sLastMask & map_tc[i]) { if (!(data_mask & map_tc[i])) { glClientActiveTextureARB(GL_TEXTURE1_ARB+i); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0_ARB); } } else if (data_mask & map_tc[i]) { glClientActiveTextureARB(GL_TEXTURE1_ARB+i); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0_ARB); } } if (sLastMask & MAP_BINORMAL) { if (!(data_mask & MAP_BINORMAL)) { glClientActiveTextureARB(GL_TEXTURE2_ARB); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0_ARB); } } else if (data_mask & MAP_BINORMAL) { glClientActiveTextureARB(GL_TEXTURE2_ARB); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0_ARB); } sLastMask = data_mask; } } void LLVertexBuffer::drawRange(U32 mode, U32 start, U32 end, U32 count, U32 indices_offset) const { llassert(mRequestedNumVerts >= 0); if (start >= (U32) mRequestedNumVerts || end >= (U32) mRequestedNumVerts) { llerrs << "Bad vertex buffer draw range: [" << start << ", " << end << "]" << llendl; } llassert(mRequestedNumIndices >= 0); if (indices_offset >= (U32) mRequestedNumIndices || indices_offset + count > (U32) mRequestedNumIndices) { llerrs << "Bad index buffer draw range: [" << indices_offset << ", " << indices_offset+count << "]" << llendl; } if (mGLIndices != sGLRenderIndices) { llerrs << "Wrong index buffer bound." << llendl; } if (mGLBuffer != sGLRenderBuffer) { llerrs << "Wrong vertex buffer bound." << llendl; } if (mode >= LLRender::NUM_MODES) { llerrs << "Invalid draw mode: " << mode << llendl; return; } stop_glerror(); glDrawRangeElements(sGLMode[mode], start, end, count, GL_UNSIGNED_SHORT, ((U16*) getIndicesPointer()) + indices_offset); stop_glerror(); } void LLVertexBuffer::draw(U32 mode, U32 count, U32 indices_offset) const { llassert(mRequestedNumIndices >= 0); if (indices_offset >= (U32) mRequestedNumIndices || indices_offset + count > (U32) mRequestedNumIndices) { llerrs << "Bad index buffer draw range: [" << indices_offset << ", " << indices_offset+count << "]" << llendl; } if (mGLIndices != sGLRenderIndices) { llerrs << "Wrong index buffer bound." << llendl; } if (mGLBuffer != sGLRenderBuffer) { llerrs << "Wrong vertex buffer bound." << llendl; } if (mode >= LLRender::NUM_MODES) { llerrs << "Invalid draw mode: " << mode << llendl; return; } stop_glerror(); glDrawElements(sGLMode[mode], count, GL_UNSIGNED_SHORT, ((U16*) getIndicesPointer()) + indices_offset); stop_glerror(); } void LLVertexBuffer::drawArrays(U32 mode, U32 first, U32 count) const { llassert(mRequestedNumVerts >= 0); if (first >= (U32) mRequestedNumVerts || first + count > (U32) mRequestedNumVerts) { llerrs << "Bad vertex buffer draw range: [" << first << ", " << first+count << "]" << llendl; } if (mGLBuffer != sGLRenderBuffer || useVBOs() != sVBOActive) { llerrs << "Wrong vertex buffer bound." << llendl; } if (mode >= LLRender::NUM_MODES) { llerrs << "Invalid draw mode: " << mode << llendl; return; } stop_glerror(); glDrawArrays(sGLMode[mode], first, count); stop_glerror(); } //static void LLVertexBuffer::initClass(bool use_vbo, bool no_vbo_mapping) { sEnableVBOs = use_vbo && gGLManager.mHasVertexBufferObject ; if(sEnableVBOs) { llassert_always(glBindBufferARB) ; //double check the extention for VBO is loaded. llinfos << "VBO is enabled." << llendl ; } else { llinfos << "VBO is disabled." << llendl ; } sDisableVBOMapping = sEnableVBOs && no_vbo_mapping ; LLGLNamePool::registerPool(&sDynamicVBOPool); LLGLNamePool::registerPool(&sDynamicIBOPool); LLGLNamePool::registerPool(&sStreamVBOPool); LLGLNamePool::registerPool(&sStreamIBOPool); } //static void LLVertexBuffer::unbind() { if (sVBOActive) { glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); sVBOActive = FALSE; } if (sIBOActive) { glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); sIBOActive = FALSE; } sGLRenderBuffer = 0; sGLRenderIndices = 0; setupClientArrays(0); } //static void LLVertexBuffer::cleanupClass() { LLMemType mt2(LLMemType::MTYPE_VERTEX_CLEANUP_CLASS); unbind(); clientCopy(); // deletes GL buffers } void LLVertexBuffer::clientCopy(F64 max_time) { if (!sDeleteList.empty()) { glDeleteBuffersARB(sDeleteList.size(), (GLuint*) &(sDeleteList[0])); sDeleteList.clear(); } } //---------------------------------------------------------------------------- LLVertexBuffer::LLVertexBuffer(U32 typemask, S32 usage) : LLRefCount(), mNumVerts(0), mNumIndices(0), mRequestedNumVerts(-1), mRequestedNumIndices(-1), mUsage(usage), mGLBuffer(0), mGLIndices(0), mMappedData(NULL), mMappedIndexData(NULL), mVertexLocked(FALSE), mIndexLocked(FALSE), mFinal(FALSE), mFilthy(FALSE), mEmpty(TRUE), mResized(FALSE), mDynamicSize(FALSE) { LLMemType mt2(LLMemType::MTYPE_VERTEX_CONSTRUCTOR); if (!sEnableVBOs) { mUsage = 0 ; } if (mUsage == GL_STREAM_DRAW_ARB && !sUseStreamDraw) { mUsage = 0; } if (mUsage == GL_STREAM_DRAW_ARB && !sUseStreamDraw) { mUsage = 0; } S32 stride = calcStride(typemask, mOffsets); mTypeMask = typemask; mStride = stride; sCount++; } //static S32 LLVertexBuffer::calcStride(const U32& typemask, S32* offsets) { S32 stride = 0; for (S32 i=0; i= 0); if (nverts >= 65535) { llwarns << "Vertex buffer overflow!" << llendl; nverts = 65535; } mRequestedNumVerts = nverts; if (!mDynamicSize) { mNumVerts = nverts; } else if (mUsage == GL_STATIC_DRAW_ARB || nverts > mNumVerts || nverts < mNumVerts/2) { if (mUsage != GL_STATIC_DRAW_ARB && nverts + nverts/4 <= 65535) { nverts += nverts/4; } mNumVerts = nverts; } } void LLVertexBuffer::updateNumIndices(S32 nindices) { LLMemType mt2(LLMemType::MTYPE_VERTEX_UPDATE_INDICES); llassert(nindices >= 0); mRequestedNumIndices = nindices; if (!mDynamicSize) { mNumIndices = nindices; } else if (mUsage == GL_STATIC_DRAW_ARB || nindices > mNumIndices || nindices < mNumIndices/2) { if (mUsage != GL_STATIC_DRAW_ARB) { nindices += nindices/4; } mNumIndices = nindices; } } void LLVertexBuffer::allocateBuffer(S32 nverts, S32 nindices, bool create) { LLMemType mt2(LLMemType::MTYPE_VERTEX_ALLOCATE_BUFFER); updateNumVerts(nverts); updateNumIndices(nindices); if (mMappedData) { llerrs << "LLVertexBuffer::allocateBuffer() called redundantly." << llendl; } if (create && (nverts || nindices)) { createGLBuffer(); createGLIndices(); } sAllocatedBytes += getSize() + getIndicesSize(); } void LLVertexBuffer::resizeBuffer(S32 newnverts, S32 newnindices) { llassert(newnverts >= 0); llassert(newnindices >= 0); mRequestedNumVerts = newnverts; mRequestedNumIndices = newnindices; LLMemType mt2(LLMemType::MTYPE_VERTEX_RESIZE_BUFFER); mDynamicSize = TRUE; if (mUsage == GL_STATIC_DRAW_ARB) { //always delete/allocate static buffers on resize destroyGLBuffer(); destroyGLIndices(); allocateBuffer(newnverts, newnindices, TRUE); mFinal = FALSE; } else if (newnverts > mNumVerts || newnindices > mNumIndices || newnverts < mNumVerts/2 || newnindices < mNumIndices/2) { sAllocatedBytes -= getSize() + getIndicesSize(); S32 oldsize = getSize(); S32 old_index_size = getIndicesSize(); updateNumVerts(newnverts); updateNumIndices(newnindices); S32 newsize = getSize(); S32 new_index_size = getIndicesSize(); sAllocatedBytes += newsize + new_index_size; if (newsize) { if (!mGLBuffer) { //no buffer exists, create a new one createGLBuffer(); } else { //delete old buffer, keep GL buffer for now if (!useVBOs()) { U8* old = mMappedData; mMappedData = new U8[newsize]; if (old) { memcpy(mMappedData, old, llmin(newsize, oldsize)); if (newsize > oldsize) { memset(mMappedData+oldsize, 0, newsize-oldsize); } delete [] old; } else { memset(mMappedData, 0, newsize); mEmpty = TRUE; } } mResized = TRUE; } } else if (mGLBuffer) { destroyGLBuffer(); } if (new_index_size) { if (!mGLIndices) { createGLIndices(); } else { if (!useVBOs()) { //delete old buffer, keep GL buffer for now U8* old = mMappedIndexData; mMappedIndexData = new U8[new_index_size]; if (old) { memcpy(mMappedIndexData, old, llmin(new_index_size, old_index_size)); if (new_index_size > old_index_size) { memset(mMappedIndexData+old_index_size, 0, new_index_size - old_index_size); } delete [] old; } else { memset(mMappedIndexData, 0, new_index_size); mEmpty = TRUE; } } mResized = TRUE; } } else if (mGLIndices) { destroyGLIndices(); } } if (mResized && useVBOs()) { freeClientBuffer() ; setBuffer(0); } } BOOL LLVertexBuffer::useVBOs() const { //it's generally ineffective to use VBO for things that are streaming on apple #if LL_DARWIN if (!mUsage || mUsage == GL_STREAM_DRAW_ARB) { return FALSE; } #else if (!mUsage) { return FALSE; } #endif return TRUE; } //---------------------------------------------------------------------------- void LLVertexBuffer::freeClientBuffer() { if(useVBOs() && sDisableVBOMapping && (mMappedData || mMappedIndexData)) { delete[] mMappedData ; delete[] mMappedIndexData ; mMappedData = NULL ; mMappedIndexData = NULL ; } } void LLVertexBuffer::allocateClientVertexBuffer() { if(!mMappedData) { U32 size = getSize() ; mMappedData = new U8[size]; memset(mMappedData, 0, size); } } void LLVertexBuffer::allocateClientIndexBuffer() { if(!mMappedIndexData) { U32 size = getIndicesSize(); mMappedIndexData = new U8[size]; memset(mMappedIndexData, 0, size); } } // Map for data access U8* LLVertexBuffer::mapVertexBuffer(S32 type, S32 access) { LLMemType mt2(LLMemType::MTYPE_VERTEX_MAP_BUFFER); if (mFinal) { llerrs << "LLVertexBuffer::mapVeretxBuffer() called on a finalized buffer." << llendl; } if (!useVBOs() && !mMappedData && !mMappedIndexData) { llerrs << "LLVertexBuffer::mapVertexBuffer() called on unallocated buffer." << llendl; } if (!mVertexLocked && useVBOs()) { { LLMemType mt_v(LLMemType::MTYPE_VERTEX_MAP_BUFFER_VERTICES); setBuffer(0, type); mVertexLocked = TRUE; stop_glerror(); if(sDisableVBOMapping) { allocateClientVertexBuffer() ; } else { mMappedData = (U8*) glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB); } stop_glerror(); } if (!mMappedData) { log_glerror(); //check the availability of memory U32 avail_phy_mem, avail_vir_mem; LLMemoryInfo::getAvailableMemoryKB(avail_phy_mem, avail_vir_mem) ; llinfos << "Available physical mwmory(KB): " << avail_phy_mem << llendl ; llinfos << "Available virtual memory(KB): " << avail_vir_mem << llendl; if(!sDisableVBOMapping) { //-------------------- //print out more debug info before crash llinfos << "vertex buffer size: (num verts : num indices) = " << getNumVerts() << " : " << getNumIndices() << llendl ; GLint size ; glGetBufferParameterivARB(GL_ARRAY_BUFFER_ARB, GL_BUFFER_SIZE_ARB, &size) ; llinfos << "GL_ARRAY_BUFFER_ARB size is " << size << llendl ; //-------------------- GLint buff; glGetIntegerv(GL_ARRAY_BUFFER_BINDING_ARB, &buff); if ((GLuint)buff != mGLBuffer) { llerrs << "Invalid GL vertex buffer bound: " << buff << llendl; } llerrs << "glMapBuffer returned NULL (no vertex data)" << llendl; } else { llerrs << "memory allocation for vertex data failed." << llendl ; } } sMappedCount++; } return mMappedData; } U8* LLVertexBuffer::mapIndexBuffer(S32 access) { LLMemType mt2(LLMemType::MTYPE_VERTEX_MAP_BUFFER); if (mFinal) { llerrs << "LLVertexBuffer::mapIndexBuffer() called on a finalized buffer." << llendl; } if (!useVBOs() && !mMappedData && !mMappedIndexData) { llerrs << "LLVertexBuffer::mapIndexBuffer() called on unallocated buffer." << llendl; } if (!mIndexLocked && useVBOs()) { { LLMemType mt_v(LLMemType::MTYPE_VERTEX_MAP_BUFFER_INDICES); setBuffer(0, TYPE_INDEX); mIndexLocked = TRUE; stop_glerror(); if(sDisableVBOMapping) { allocateClientIndexBuffer() ; } else { mMappedIndexData = (U8*) glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB); } stop_glerror(); } if (!mMappedIndexData) { log_glerror(); if(!sDisableVBOMapping) { GLint buff; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB, &buff); if ((GLuint)buff != mGLIndices) { llerrs << "Invalid GL index buffer bound: " << buff << llendl; } llerrs << "glMapBuffer returned NULL (no index data)" << llendl; } else { llerrs << "memory allocation for Index data failed. " << llendl ; } } sMappedCount++; } return mMappedIndexData ; } void LLVertexBuffer::unmapBuffer(S32 type) { LLMemType mt2(LLMemType::MTYPE_VERTEX_UNMAP_BUFFER); if (!useVBOs()) { return ; //nothing to unmap } bool updated_all = false ; if (mMappedData && mVertexLocked && type != TYPE_INDEX) { updated_all = (mIndexLocked && type < 0) ; //both vertex and index buffers done updating if(sDisableVBOMapping) { stop_glerror(); glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, 0, getSize(), mMappedData); stop_glerror(); } else { stop_glerror(); glUnmapBufferARB(GL_ARRAY_BUFFER_ARB); stop_glerror(); mMappedData = NULL; } mVertexLocked = FALSE ; sMappedCount--; } if(mMappedIndexData && mIndexLocked && (type < 0 || type == TYPE_INDEX)) { if(sDisableVBOMapping) { stop_glerror(); glBufferSubDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0, getIndicesSize(), mMappedIndexData); stop_glerror(); } else { stop_glerror(); glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB); stop_glerror(); mMappedIndexData = NULL ; } mIndexLocked = FALSE ; sMappedCount--; } if(updated_all) { if(mUsage == GL_STATIC_DRAW_ARB) { //static draw buffers can only be mapped a single time //throw out client data (we won't be using it again) mEmpty = TRUE; mFinal = TRUE; if(sDisableVBOMapping) { freeClientBuffer() ; } } else { mEmpty = FALSE; } } } //---------------------------------------------------------------------------- template struct VertexBufferStrider { typedef LLStrider strider_t; static bool get(LLVertexBuffer& vbo, strider_t& strider, S32 index) { if (type == LLVertexBuffer::TYPE_INDEX) { S32 stride = sizeof(T); if (vbo.mapIndexBuffer() == NULL) { llwarns << "mapIndexBuffer failed!" << llendl; return FALSE; } strider = (T*)(vbo.getMappedIndices() + index*stride); strider.setStride(0); return TRUE; } else if (vbo.hasDataType(type)) { S32 stride = vbo.getStride(); if (vbo.mapVertexBuffer(type) == NULL) { llwarns << "mapVertexBuffer failed!" << llendl; return FALSE; } strider = (T*)(vbo.getMappedData() + vbo.getOffset(type) + index*stride); strider.setStride(stride); return TRUE; } else { llerrs << "VertexBufferStrider could not find valid vertex data." << llendl; } return FALSE; } }; bool LLVertexBuffer::getVertexStrider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getIndexStrider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getTexCoord0Strider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getTexCoord1Strider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } /*bool LLVertexBuffer::getTexCoord2Strider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getTexCoord3Strider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); }*/ bool LLVertexBuffer::getNormalStrider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getBinormalStrider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getColorStrider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getWeightStrider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } bool LLVertexBuffer::getClothWeightStrider(LLStrider& strider, S32 index) { return VertexBufferStrider::get(*this, strider, index); } void LLVertexBuffer::setStride(S32 type, S32 new_stride) { LLMemType mt2(LLMemType::MTYPE_VERTEX_SET_STRIDE); if (mNumVerts) { llerrs << "LLVertexBuffer::setOffset called with mNumVerts = " << mNumVerts << llendl; } // This code assumes that setStride() will only be called once per VBO per type. S32 delta = new_stride - sTypeOffsets[type]; for (S32 i=type+1; i