mirror of
https://port.numenaute.org/aleajactaest/khanat-code-old.git
synced 2024-11-19 13:36:13 +00:00
1787 lines
51 KiB
C++
1787 lines
51 KiB
C++
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdopengl.h"
|
|
|
|
#include "driver_opengl.h"
|
|
#include "nel/3d/index_buffer.h"
|
|
#include "driver_opengl_vertex_buffer_hard.h"
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
// Flags for software vertex skinning.
|
|
#define NL3D_DRV_SOFTSKIN_VNEEDCOMPUTE 3
|
|
#define NL3D_DRV_SOFTSKIN_VMUSTCOMPUTE 1
|
|
#define NL3D_DRV_SOFTSKIN_VCOMPUTED 0
|
|
// 3 means "vertex may need compute".
|
|
// 1 means "Primitive say vertex must be computed".
|
|
// 0 means "vertex is computed".
|
|
|
|
|
|
// 500K min.
|
|
#define NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE (512*1024)
|
|
|
|
|
|
|
|
|
|
|
|
namespace NL3D
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
|
|
CVBDrvInfosGL::CVBDrvInfosGL(CDriverGL *drv, ItVBDrvInfoPtrList it, CVertexBuffer *vb) : IVBDrvInfos(drv, it, vb)
|
|
{
|
|
H_AUTO_OGL(CVBDrvInfosGL_CVBDrvInfosGL)
|
|
_DriverGL = drv;
|
|
_VBHard = NULL;
|
|
_SystemMemory = NULL;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
CVBDrvInfosGL::~CVBDrvInfosGL()
|
|
{
|
|
H_AUTO_OGL(CVBDrvInfosGL_CVBDrvInfosGLDtor)
|
|
// Restaure non resident memory
|
|
if (VertexBufferPtr)
|
|
{
|
|
VertexBufferPtr->setLocation(CVertexBuffer::NotResident);
|
|
VertexBufferPtr = NULL;
|
|
}
|
|
|
|
if (_VBHard)
|
|
{
|
|
_VBHard->disable();
|
|
_DriverGL->_VertexBufferHardSet.erase(_VBHard);
|
|
}
|
|
if (_SystemMemory)
|
|
{
|
|
delete [] _SystemMemory;
|
|
}
|
|
_SystemMemory = NULL;
|
|
_VBHard = NULL;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
uint8 *CVBDrvInfosGL::lock (uint /* first */, uint /* last */, bool /* readOnly */)
|
|
{
|
|
H_AUTO_OGL(CVBDrvInfosGL_lock)
|
|
if (_VBHard)
|
|
{
|
|
return (uint8*)_VBHard->lock ();
|
|
}
|
|
else
|
|
{
|
|
// Should be a system memory
|
|
nlassert (_SystemMemory);
|
|
return _SystemMemory;
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CVBDrvInfosGL::unlock (uint first, uint last)
|
|
{
|
|
H_AUTO_OGL(CVBDrvInfosGL_unlock)
|
|
if (_VBHard)
|
|
{
|
|
_VBHard->unlock(first, last);
|
|
}
|
|
else
|
|
{
|
|
// Should be a system memory
|
|
nlassert (_SystemMemory);
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CDriverGL::setupVertexBuffer(CVertexBuffer& VB)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupVertexBuffer)
|
|
// 2. If necessary, do modifications.
|
|
//==================================
|
|
const bool touched = (VB.getTouchFlags() & (CVertexBuffer::TouchedReserve|CVertexBuffer::TouchedVertexFormat)) != 0;
|
|
if( touched || (VB.DrvInfos == NULL))
|
|
{
|
|
// delete first
|
|
if(VB.DrvInfos)
|
|
delete VB.DrvInfos;
|
|
VB.DrvInfos = NULL;
|
|
|
|
// create only if some vertices
|
|
if(VB.getNumVertices())
|
|
{
|
|
// 1. Retrieve/Create driver shader.
|
|
//==================================
|
|
// insert into driver list. (so it is deleted when driver is deleted).
|
|
ItVBDrvInfoPtrList it= _VBDrvInfos.insert(_VBDrvInfos.end(), NULL);
|
|
// create and set iterator, for future deletion.
|
|
CVBDrvInfosGL *info = new CVBDrvInfosGL(this, it, &VB);
|
|
*it= VB.DrvInfos = info;
|
|
|
|
// Preferred memory
|
|
CVertexBuffer::TPreferredMemory preferred = VB.getPreferredMemory ();
|
|
if ((preferred == CVertexBuffer::RAMVolatile) || (preferred == CVertexBuffer::AGPVolatile))
|
|
preferred = CVertexBuffer::RAMPreferred;
|
|
const uint size = VB.capacity()*VB.getVertexSize();
|
|
uint preferredMemory = _Extensions.DisableHardwareVertexArrayAGP ? CVertexBuffer::RAMPreferred : preferred;
|
|
while (preferredMemory != CVertexBuffer::RAMPreferred)
|
|
{
|
|
// Vertex buffer hard
|
|
info->_VBHard = createVertexBufferHard(size, VB.capacity(), (CVertexBuffer::TPreferredMemory)preferredMemory, &VB);
|
|
if (info->_VBHard)
|
|
break;
|
|
preferredMemory--;
|
|
}
|
|
|
|
// No memory found ? Use system memory
|
|
if (info->_VBHard == NULL)
|
|
{
|
|
nlassert (info->_SystemMemory == NULL);
|
|
info->_SystemMemory = new uint8[size];
|
|
}
|
|
|
|
// Upload the data
|
|
VB.setLocation ((CVertexBuffer::TLocation)preferredMemory);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CDriverGL::activeVertexBuffer(CVertexBuffer& VB)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_activeVertexBuffer)
|
|
// NB: must duplicate changes in activeVertexBufferHard()
|
|
uint32 flags;
|
|
|
|
// In any case, we'll erase any special vertex setup for Lightmap Material
|
|
_LastVertexSetupIsLightMap= false;
|
|
|
|
// setup
|
|
if (!setupVertexBuffer(VB))
|
|
return false;
|
|
|
|
if (VB.getNumVertices()==0)
|
|
return true;
|
|
|
|
// Fill the buffer if in local memory
|
|
VB.fillBuffer ();
|
|
|
|
// Get VB flags, to setup matrixes and arrays.
|
|
flags=VB.getVertexFormat();
|
|
|
|
|
|
// 2. Setup Arrays.
|
|
//===================
|
|
// For MultiPass Material.
|
|
CVertexBufferInfo::TVBMode lastVBMode = _LastVB.VBMode;
|
|
CVBDrvInfosGL *info= safe_cast<CVBDrvInfosGL*>((IVBDrvInfos*)VB.DrvInfos);
|
|
if (!info->_VBHard || (info->_VBHard && !info->_VBHard->isInvalid()))
|
|
{
|
|
_LastVB.setupVertexBuffer(VB);
|
|
if (lastVBMode == CVertexBufferInfo::HwARB && _LastVB.VBMode != CVertexBufferInfo::HwARB)
|
|
{
|
|
_DriverGLStates.bindARBVertexBuffer(0); // unbind ARB vertex buffer
|
|
}
|
|
}
|
|
if (info->_VBHard == NULL)
|
|
{
|
|
// Fence mgt.
|
|
fenceOnCurVBHardIfNeeded(NULL);
|
|
|
|
// Disable the current vertexBufferHard if setuped.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->disable();
|
|
}
|
|
else
|
|
{
|
|
// 2. Setup Arrays.
|
|
//===================
|
|
|
|
// Fence mgt.
|
|
fenceOnCurVBHardIfNeeded(info->_VBHard);
|
|
|
|
// Enable the vertexArrayRange of this array.
|
|
info->_VBHard->enable();
|
|
}
|
|
if (!info->_VBHard || (info->_VBHard && !info->_VBHard->isInvalid()))
|
|
{
|
|
setupGlArrays(_LastVB);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CDriverGL::activeIndexBuffer(CIndexBuffer& IB)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_activeIndexBuffer)
|
|
_LastIB.setupIndexBuffer(IB);
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CDriverGL::renderLines(CMaterial& mat, uint32 firstIndex, uint32 nlines)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_renderLines)
|
|
// update matrix and Light in OpenGL if needed
|
|
refreshRenderSetup();
|
|
|
|
// setup material
|
|
if ( !setupMaterial(mat) || _LastIB._Values == NULL )
|
|
return false;
|
|
|
|
if (_CurrentVertexBufferHard && _CurrentVertexBufferHard->isInvalid()) return true;
|
|
// render primitives.
|
|
//==============================
|
|
// start multipass.
|
|
uint nPass;
|
|
nPass= beginMultiPass();
|
|
// draw all passes.
|
|
for(uint pass=0;pass<nPass; pass++)
|
|
{
|
|
// setup the pass.
|
|
setupPass(pass);
|
|
// draw the primitives.
|
|
if(nlines)
|
|
{
|
|
if (_LastIB._Format == CIndexBuffer::Indices16)
|
|
{
|
|
glDrawElements(GL_LINES,2*nlines,GL_UNSIGNED_SHORT,((uint16 *) _LastIB._Values)+firstIndex);
|
|
}
|
|
else
|
|
{
|
|
nlassert(_LastIB._Format == CIndexBuffer::Indices32);
|
|
glDrawElements(GL_LINES,2*nlines,GL_UNSIGNED_INT,((uint32 *) _LastIB._Values)+firstIndex);
|
|
}
|
|
}
|
|
}
|
|
// end multipass.
|
|
endMultiPass();
|
|
|
|
|
|
// Profiling.
|
|
_PrimitiveProfileIn.NLines+= nlines;
|
|
_PrimitiveProfileOut.NLines+= nlines;
|
|
|
|
// We have render some prims. inform the VBHard.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->GPURenderingAfterFence= true;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CDriverGL::renderTriangles(CMaterial& mat, uint32 firstIndex, uint32 ntris)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_renderTriangles)
|
|
// update matrix and Light in OpenGL if needed
|
|
refreshRenderSetup();
|
|
// setup material
|
|
if ( !setupMaterial(mat) || _LastIB._Values == NULL )
|
|
return false;
|
|
|
|
if (_CurrentVertexBufferHard && _CurrentVertexBufferHard->isInvalid()) return true;
|
|
|
|
// render primitives.
|
|
//==============================
|
|
// start multipass.
|
|
uint nPass;
|
|
|
|
nPass= beginMultiPass();
|
|
|
|
// draw all passes.
|
|
for(uint pass=0;pass<nPass; pass++)
|
|
{
|
|
// setup the pass.
|
|
setupPass(pass);
|
|
|
|
// draw the primitives.
|
|
if(ntris)
|
|
{
|
|
if (_LastIB._Format == CIndexBuffer::Indices16)
|
|
{
|
|
glDrawElements(GL_TRIANGLES,3*ntris,GL_UNSIGNED_SHORT, ((uint16 *) _LastIB._Values)+firstIndex);
|
|
}
|
|
else
|
|
{
|
|
nlassert(_LastIB._Format == CIndexBuffer::Indices32);
|
|
glDrawElements(GL_TRIANGLES,3*ntris,GL_UNSIGNED_INT, ((uint32 *) _LastIB._Values)+firstIndex);
|
|
}
|
|
}
|
|
}
|
|
// end multipass.
|
|
endMultiPass();
|
|
|
|
|
|
// Profiling.
|
|
_PrimitiveProfileIn.NTriangles+= ntris;
|
|
_PrimitiveProfileOut.NTriangles+= ntris * nPass;
|
|
|
|
// We have render some prims. inform the VBHard.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->GPURenderingAfterFence= true;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CDriverGL::renderSimpleTriangles(uint32 firstTri, uint32 ntris)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_renderSimpleTriangles)
|
|
nlassert(ntris>0);
|
|
|
|
// update matrix and Light in OpenGL if needed
|
|
refreshRenderSetup();
|
|
|
|
|
|
if (_CurrentVertexBufferHard && _CurrentVertexBufferHard->isInvalid()) return true;
|
|
// Don't setup any material here.
|
|
|
|
// render primitives.
|
|
//==============================
|
|
// NO MULTIPASS HERE!!
|
|
// draw the primitives. (nb: ntrsi>0).
|
|
|
|
if (_LastIB._Format == CIndexBuffer::Indices16)
|
|
{
|
|
glDrawElements(GL_TRIANGLES,3*ntris,GL_UNSIGNED_SHORT, ((uint16 *) _LastIB._Values)+firstTri);
|
|
}
|
|
else
|
|
{
|
|
nlassert(_LastIB._Format == CIndexBuffer::Indices32);
|
|
glDrawElements(GL_TRIANGLES,3*ntris,GL_UNSIGNED_INT, ((uint32 *) _LastIB._Values)+firstTri);
|
|
}
|
|
|
|
// Profiling.
|
|
_PrimitiveProfileIn.NTriangles+= ntris;
|
|
_PrimitiveProfileOut.NTriangles+= ntris;
|
|
|
|
// We have render some prims. inform the VBHard.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->GPURenderingAfterFence= true;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CDriverGL::renderRawPoints(CMaterial& mat, uint32 startIndex, uint32 numPoints)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_renderRawPoints)
|
|
// update matrix and Light in OpenGL if needed
|
|
refreshRenderSetup();
|
|
|
|
// setup material
|
|
if ( !setupMaterial(mat) )
|
|
return false;
|
|
|
|
if (_CurrentVertexBufferHard && _CurrentVertexBufferHard->isInvalid()) return true;
|
|
// render primitives.
|
|
//==============================
|
|
// start multipass.
|
|
uint nPass;
|
|
nPass= beginMultiPass();
|
|
// draw all passes.
|
|
for(uint pass=0;pass<nPass; pass++)
|
|
{
|
|
// setup the pass.
|
|
setupPass(pass);
|
|
// draw the primitives.
|
|
if(numPoints)
|
|
glDrawArrays(GL_POINTS, startIndex, numPoints);
|
|
}
|
|
// end multipass.
|
|
endMultiPass();
|
|
|
|
|
|
// Profiling.
|
|
_PrimitiveProfileIn.NPoints+= numPoints;
|
|
_PrimitiveProfileOut.NPoints+= numPoints * nPass;
|
|
|
|
// We have render some prims. inform the VBHard.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->GPURenderingAfterFence= true;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CDriverGL::renderRawLines(CMaterial& mat, uint32 startIndex, uint32 numLines)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_renderRawLines)
|
|
// update matrix and Light in OpenGL if needed
|
|
refreshRenderSetup();
|
|
|
|
// setup material
|
|
if ( !setupMaterial(mat) )
|
|
return false;
|
|
|
|
if (_CurrentVertexBufferHard && _CurrentVertexBufferHard->isInvalid()) return true;
|
|
// render primitives.
|
|
//==============================
|
|
// start multipass.
|
|
uint nPass;
|
|
nPass= beginMultiPass();
|
|
// draw all passes.
|
|
for(uint pass=0;pass<nPass; pass++)
|
|
{
|
|
// setup the pass.
|
|
setupPass(pass);
|
|
// draw the primitives.
|
|
if(numLines)
|
|
glDrawArrays(GL_LINES, startIndex << 1, numLines << 1);
|
|
}
|
|
// end multipass.
|
|
endMultiPass();
|
|
|
|
|
|
// Profiling.
|
|
_PrimitiveProfileIn.NLines += numLines ;
|
|
_PrimitiveProfileOut.NLines += numLines * nPass;
|
|
|
|
// We have render some prims. inform the VBHard.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->GPURenderingAfterFence= true;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CDriverGL::renderRawTriangles(CMaterial& mat, uint32 startIndex, uint32 numTris)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_renderRawTriangles)
|
|
// update matrix and Light in OpenGL if needed
|
|
refreshRenderSetup();
|
|
|
|
// setup material
|
|
if ( !setupMaterial(mat) )
|
|
return false;
|
|
|
|
if (_CurrentVertexBufferHard && _CurrentVertexBufferHard->isInvalid()) return true;
|
|
// render primitives.
|
|
//==============================
|
|
// start multipass.
|
|
uint nPass;
|
|
nPass= beginMultiPass();
|
|
// draw all passes.
|
|
for(uint pass=0;pass<nPass; pass++)
|
|
{
|
|
// setup the pass.
|
|
setupPass(pass);
|
|
// draw the primitives.
|
|
if(numTris)
|
|
{
|
|
glDrawArrays(GL_TRIANGLES, startIndex*3, numTris*3);
|
|
}
|
|
}
|
|
// end multipass.
|
|
endMultiPass();
|
|
|
|
|
|
// Profiling.
|
|
_PrimitiveProfileIn.NTriangles += numTris ;
|
|
_PrimitiveProfileOut.NTriangles += numTris * nPass;
|
|
|
|
// We have render some prims. inform the VBHard.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->GPURenderingAfterFence= true;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CDriverGL::renderRawQuads(CMaterial& mat, uint32 startIndex, uint32 numQuads)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_renderRawQuads)
|
|
if (!numQuads) return true;
|
|
// update matrix and Light in OpenGL if needed
|
|
refreshRenderSetup();
|
|
|
|
// setup material
|
|
if ( !setupMaterial(mat) )
|
|
return false;
|
|
|
|
if (_CurrentVertexBufferHard && _CurrentVertexBufferHard->isInvalid()) return true;
|
|
|
|
const uint32 QUAD_BATCH_SIZE = 2048;
|
|
static GLshort defaultIndices[QUAD_BATCH_SIZE * 6];
|
|
static bool init = false;
|
|
if (!init)
|
|
{
|
|
// setup the base index buffer
|
|
for(uint k = 0; k < QUAD_BATCH_SIZE; ++k)
|
|
{
|
|
// first tri
|
|
defaultIndices[k * 6] = (GLshort) (k * 4);
|
|
defaultIndices[k * 6 + 1] = (GLshort) (k * 4 + 1);
|
|
defaultIndices[k * 6 + 2] = (GLshort) (k * 4 + 2);
|
|
// second tri
|
|
defaultIndices[k * 6 + 3] = (GLshort) (k * 4);
|
|
defaultIndices[k * 6 + 4] = (GLshort) (k * 4 + 2);
|
|
defaultIndices[k * 6 + 5] = (GLshort) (k * 4 + 3);
|
|
|
|
}
|
|
init = true;
|
|
}
|
|
|
|
// render primitives.
|
|
//==============================
|
|
// start multipass.
|
|
uint nPass;
|
|
nPass= beginMultiPass();
|
|
// draw all passes.
|
|
for(uint pass=0;pass<nPass; pass++)
|
|
{
|
|
// setup the pass.
|
|
setupPass(pass);
|
|
|
|
uint32 currIndex = startIndex;
|
|
uint32 numLeftQuads = numQuads;
|
|
|
|
// draw first batch of quads using the static setupped array
|
|
if (startIndex < QUAD_BATCH_SIZE)
|
|
{
|
|
// draw first quads (as pair of tri to have guaranteed orientation)
|
|
uint numQuadsToDraw = std::min(QUAD_BATCH_SIZE - startIndex, numQuads);
|
|
glDrawElements(GL_TRIANGLES, 6 * numQuadsToDraw, GL_UNSIGNED_SHORT, defaultIndices + 6 * startIndex);
|
|
numLeftQuads -= numQuadsToDraw;
|
|
currIndex += 4 * numQuadsToDraw;
|
|
}
|
|
|
|
// draw remaining quads
|
|
while (numLeftQuads)
|
|
{
|
|
// TODO : resetting vertex pointer would avoid the need to rebuild indices each times
|
|
uint32 numQuadsToDraw = std::min(numLeftQuads, QUAD_BATCH_SIZE);
|
|
// draw all quads
|
|
if (4 * numQuadsToDraw + currIndex <= (1 << 16))
|
|
{
|
|
// indices fits on 16 bits
|
|
GLshort indices[QUAD_BATCH_SIZE * 6];
|
|
GLshort *curr = indices;
|
|
GLshort *end = indices + 6 * numQuadsToDraw;
|
|
uint16 vertexIndex = (uint16) currIndex;
|
|
do
|
|
{
|
|
*curr++ = vertexIndex;
|
|
*curr++ = vertexIndex + 1;
|
|
*curr++ = vertexIndex + 2;
|
|
*curr++ = vertexIndex;
|
|
*curr++ = vertexIndex + 2;
|
|
*curr++ = vertexIndex + 3;
|
|
vertexIndex += 4;
|
|
}
|
|
while(curr != end);
|
|
glDrawElements(GL_TRIANGLES, 6 * numQuadsToDraw, GL_UNSIGNED_SHORT, indices);
|
|
}
|
|
else
|
|
{
|
|
// indices fits on 32 bits
|
|
GLint indices[QUAD_BATCH_SIZE];
|
|
GLint *curr = indices;
|
|
GLint *end = indices + 6 * numQuadsToDraw;
|
|
uint32 vertexIndex = currIndex;
|
|
do
|
|
{
|
|
*curr++ = vertexIndex;
|
|
*curr++ = vertexIndex + 1;
|
|
*curr++ = vertexIndex + 2;
|
|
*curr++ = vertexIndex;
|
|
*curr++ = vertexIndex + 2;
|
|
*curr++ = vertexIndex + 3;
|
|
vertexIndex += 4;
|
|
}
|
|
while(curr != end);
|
|
glDrawElements(GL_TRIANGLES, 6 * numQuadsToDraw, GL_UNSIGNED_INT, indices);
|
|
}
|
|
numLeftQuads -= numQuadsToDraw;
|
|
currIndex += 4 * numQuadsToDraw;
|
|
}
|
|
}
|
|
// end multipass.
|
|
endMultiPass();
|
|
|
|
|
|
// Profiling.
|
|
_PrimitiveProfileIn.NQuads += numQuads ;
|
|
_PrimitiveProfileOut.NQuads += numQuads * nPass;
|
|
|
|
// We have render some prims. inform the VBHard.
|
|
if(_CurrentVertexBufferHard)
|
|
_CurrentVertexBufferHard->GPURenderingAfterFence= true;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CDriverGL::setupUVPtr(uint stage, CVertexBufferInfo &VB, uint uvId)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupUVPtr)
|
|
// sould not be called with vertex program Array setuped.
|
|
nlassert(!_LastSetupGLArrayVertexProgram);
|
|
|
|
_DriverGLStates.clientActiveTextureARB(stage);
|
|
if (VB.VertexFormat & (CVertexBuffer::TexCoord0Flag<<uvId))
|
|
{
|
|
// Check type, if not supported, just ignore
|
|
CVertexBuffer::TType uvType = VB.Type[CVertexBuffer::TexCoord0+uvId];
|
|
if (uvType == CVertexBuffer::Float2 ||
|
|
uvType == CVertexBuffer::Float3)
|
|
{
|
|
_DriverGLStates.enableTexCoordArray(true);
|
|
uint numTexCoord = (uvType == CVertexBuffer::Float2) ? 2 : 3;
|
|
// Setup ATI VBHard or std ptr.
|
|
switch(VB.VBMode)
|
|
{
|
|
case CVertexBufferInfo::HwATI:
|
|
nglArrayObjectATI(GL_TEXTURE_COORD_ARRAY, numTexCoord, GL_FLOAT, VB.VertexSize, VB.VertexObjectId,
|
|
(ptrdiff_t) VB.ValuePtr[CVertexBuffer::TexCoord0+uvId]);
|
|
break;
|
|
case CVertexBufferInfo::HwARB:
|
|
_DriverGLStates.bindARBVertexBuffer(VB.VertexObjectId);
|
|
// with arb buffers, position is relative to the start of the stream
|
|
glTexCoordPointer(numTexCoord,GL_FLOAT,VB.VertexSize, VB.ValuePtr[CVertexBuffer::TexCoord0+uvId]);
|
|
break;
|
|
case CVertexBufferInfo::SysMem:
|
|
case CVertexBufferInfo::HwNVIDIA:
|
|
glTexCoordPointer(numTexCoord,GL_FLOAT,VB.VertexSize, VB.ValuePtr[CVertexBuffer::TexCoord0+uvId]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_DriverGLStates.enableTexCoordArray(false);
|
|
}
|
|
}
|
|
else
|
|
_DriverGLStates.enableTexCoordArray(false);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::mapTextureStageToUV(uint stage, uint uv)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_mapTextureStageToUV)
|
|
// Just call it for last VertexBuffer setuped.
|
|
setupUVPtr(stage, _LastVB, uv);
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
// VertexBufferHard
|
|
// ***************************************************************************
|
|
// ***************************************************************************
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CDriverGL::supportVertexBufferHard() const
|
|
{
|
|
H_AUTO_OGL(CDriverGL_supportVertexBufferHard)
|
|
return _SupportVBHard;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CDriverGL::supportVolatileVertexBuffer() const
|
|
{
|
|
H_AUTO_OGL(CDriverGL_supportVolatileVertexBuffer)
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CDriverGL::slowUnlockVertexBufferHard() const
|
|
{
|
|
H_AUTO_OGL(CDriverGL_slowUnlockVertexBufferHard)
|
|
return _SlowUnlockVBHard;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
uint CDriverGL::getMaxVerticesByVertexBufferHard() const
|
|
{
|
|
H_AUTO_OGL(CDriverGL_getMaxVerticesByVertexBufferHard)
|
|
return _MaxVerticesByVBHard;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
IVertexBufferHardGL *CDriverGL::createVertexBufferHard(uint size, uint numVertices, CVertexBuffer::TPreferredMemory vbType, CVertexBuffer *vb)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_createVertexBufferHard)
|
|
// choose the VertexArrayRange of good type
|
|
IVertexArrayRange *vertexArrayRange= NULL;
|
|
switch(vbType)
|
|
{
|
|
case CVertexBuffer::AGPPreferred:
|
|
vertexArrayRange= _AGPVertexArrayRange;
|
|
break;
|
|
case CVertexBuffer::StaticPreferred:
|
|
if (getStaticMemoryToVRAM())
|
|
vertexArrayRange= _VRAMVertexArrayRange;
|
|
else
|
|
vertexArrayRange= _AGPVertexArrayRange;
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
|
|
// If this one at least created (an extension support it).
|
|
if( !vertexArrayRange )
|
|
return NULL;
|
|
else
|
|
{
|
|
// check max vertex
|
|
if(numVertices > _MaxVerticesByVBHard)
|
|
return NULL;
|
|
|
|
// Create a CVertexBufferHardGL
|
|
IVertexBufferHardGL *vbHard;
|
|
// let the VAR create the vbhard.
|
|
vbHard= vertexArrayRange->createVBHardGL(size, vb);
|
|
// if fails
|
|
if(!vbHard)
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
// insert in list.
|
|
return _VertexBufferHardSet.insert(vbHard);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
const uint CDriverGL::NumCoordinatesType[CVertexBuffer::NumType]=
|
|
{
|
|
1, // Double1
|
|
1, // Float1
|
|
1, // Short1
|
|
2, // Double2
|
|
2, // Float2
|
|
2, // Short2
|
|
3, // Double3
|
|
3, // Float3
|
|
3, // Short3
|
|
4, // Double4
|
|
4, // Float4
|
|
4, // Short4
|
|
4 // UChar4
|
|
};
|
|
|
|
|
|
// ***************************************************************************
|
|
const uint CDriverGL::GLType[CVertexBuffer::NumType]=
|
|
{
|
|
GL_DOUBLE, // Double1
|
|
GL_FLOAT, // Float1
|
|
GL_SHORT, // Short1
|
|
GL_DOUBLE, // Double2
|
|
GL_FLOAT, // Float2
|
|
GL_SHORT, // Short2
|
|
GL_DOUBLE, // Double3
|
|
GL_FLOAT, // Float3
|
|
GL_SHORT, // Short3
|
|
GL_DOUBLE, // Double4
|
|
GL_FLOAT, // Float4
|
|
GL_SHORT, // Short4
|
|
GL_UNSIGNED_BYTE // UChar4
|
|
};
|
|
|
|
// ***************************************************************************
|
|
const bool CDriverGL::GLTypeIsIntegral[CVertexBuffer::NumType] =
|
|
{
|
|
false, // Double1
|
|
false, // Float1
|
|
true, // Short1
|
|
false, // Double2
|
|
false, // Float2
|
|
true, // Short2
|
|
false, // Double3
|
|
false, // Float3
|
|
true, // Short3
|
|
false, // Double4
|
|
false, // Float4
|
|
true, // Short4
|
|
true // UChar4
|
|
};
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
const uint CDriverGL::GLVertexAttribIndex[CVertexBuffer::NumValue]=
|
|
{
|
|
0, // Position
|
|
2, // Normal
|
|
8, // TexCoord0
|
|
9, // TexCoord1
|
|
10, // TexCoord2
|
|
11, // TexCoord3
|
|
12, // TexCoord4
|
|
13, // TexCoord5
|
|
14, // TexCoord6
|
|
15, // TexCoord7
|
|
3, // PrimaryColor
|
|
4, // SecondaryColor
|
|
1, // Weight
|
|
6, // Empty (PaletteSkin)
|
|
5, // Fog
|
|
7, // Empty
|
|
};
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::setupGlArraysStd(CVertexBufferInfo &vb)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupGlArraysStd)
|
|
uint32 flags= vb.VertexFormat;
|
|
|
|
if (vb.VBMode == CVertexBufferInfo::HwARB)
|
|
{
|
|
_DriverGLStates.bindARBVertexBuffer(vb.VertexObjectId);
|
|
}
|
|
|
|
switch(vb.VBMode)
|
|
{
|
|
case CVertexBufferInfo::SysMem:
|
|
case CVertexBufferInfo::HwNVIDIA:
|
|
case CVertexBufferInfo::HwARB:
|
|
{
|
|
// setup vertex ptr.
|
|
//-----------
|
|
uint numVertexCoord = CVertexBuffer::NumComponentsType[vb.Type[CVertexBuffer::Position]];
|
|
nlassert (numVertexCoord >= 2);
|
|
_DriverGLStates.enableVertexArray(true);
|
|
glVertexPointer(numVertexCoord, GL_FLOAT, vb.VertexSize, vb.ValuePtr[CVertexBuffer::Position]);
|
|
// setup normal ptr.
|
|
//-----------
|
|
// Check for normal param in vertex buffer
|
|
if (flags & CVertexBuffer::NormalFlag)
|
|
{
|
|
// Check type
|
|
nlassert (vb.Type[CVertexBuffer::Normal]==CVertexBuffer::Float3);
|
|
|
|
_DriverGLStates.enableNormalArray(true);
|
|
glNormalPointer(GL_FLOAT, vb.VertexSize, vb.ValuePtr[CVertexBuffer::Normal]);
|
|
}
|
|
else
|
|
{
|
|
_DriverGLStates.enableNormalArray(false);
|
|
}
|
|
// Setup Color
|
|
//-----------
|
|
// Check for color param in vertex buffer
|
|
if (flags & CVertexBuffer::PrimaryColorFlag)
|
|
{
|
|
// Check type
|
|
nlassert (vb.Type[CVertexBuffer::PrimaryColor]==CVertexBuffer::UChar4);
|
|
_DriverGLStates.enableColorArray(true);
|
|
// Setup ATI VBHard or std ptr.
|
|
glColorPointer(4,GL_UNSIGNED_BYTE, vb.VertexSize, vb.ValuePtr[CVertexBuffer::PrimaryColor]);
|
|
}
|
|
else
|
|
_DriverGLStates.enableColorArray(false);
|
|
}
|
|
break;
|
|
case CVertexBufferInfo::HwATI:
|
|
{
|
|
// setup vertex ptr.
|
|
//-----------
|
|
uint numVertexCoord = CVertexBuffer::NumComponentsType[vb.Type[CVertexBuffer::Position]];
|
|
nlassert (numVertexCoord >= 2);
|
|
|
|
_DriverGLStates.enableVertexArray(true);
|
|
nglArrayObjectATI(GL_VERTEX_ARRAY, numVertexCoord, GL_FLOAT, vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::Position]);
|
|
// setup normal ptr.
|
|
//-----------
|
|
// Check for normal param in vertex buffer
|
|
if (flags & CVertexBuffer::NormalFlag)
|
|
{
|
|
// Check type
|
|
nlassert (vb.Type[CVertexBuffer::Normal]==CVertexBuffer::Float3);
|
|
_DriverGLStates.enableNormalArray(true);
|
|
nglArrayObjectATI(GL_NORMAL_ARRAY, 3, GL_FLOAT, vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::Normal]);
|
|
}
|
|
else
|
|
{
|
|
_DriverGLStates.enableNormalArray(false);
|
|
}
|
|
|
|
|
|
// Setup Color
|
|
//-----------
|
|
// Check for color param in vertex buffer
|
|
if (flags & CVertexBuffer::PrimaryColorFlag)
|
|
{
|
|
// Check type
|
|
nlassert (vb.Type[CVertexBuffer::PrimaryColor]==CVertexBuffer::UChar4);
|
|
|
|
_DriverGLStates.enableColorArray(true);
|
|
nglArrayObjectATI(GL_COLOR_ARRAY, 4, GL_UNSIGNED_BYTE, vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::PrimaryColor]);
|
|
}
|
|
else
|
|
_DriverGLStates.enableColorArray(false);
|
|
}
|
|
break;
|
|
default:
|
|
nlassert(0);
|
|
break;
|
|
}
|
|
|
|
// Setup Uvs
|
|
//-----------
|
|
// Get the routing
|
|
for(uint i=0; i<inlGetNumTextStages(); i++)
|
|
{
|
|
// normal behavior: each texture has its own UV.
|
|
setupUVPtr(i, vb, vb.UVRouting[i]);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::toggleGlArraysForNVVertexProgram()
|
|
{
|
|
H_AUTO_OGL(CDriverGL_toggleGlArraysForNVVertexProgram)
|
|
// If change of setup type, must disable olds.
|
|
//=======================
|
|
|
|
// If last was a VertexProgram setup, and now it is a standard GL array setup.
|
|
if( _LastSetupGLArrayVertexProgram && !isVertexProgramEnabled () )
|
|
{
|
|
|
|
// Disable all VertexAttribs.
|
|
for (uint value=0; value<CVertexBuffer::NumValue; value++)
|
|
{
|
|
// Index
|
|
uint glIndex=GLVertexAttribIndex[value];
|
|
_DriverGLStates.enableVertexAttribArray(glIndex, false);
|
|
}
|
|
_DriverGLStates.enableColorArray(false);
|
|
_DriverGLStates.enableSecondaryColorArray(false);
|
|
|
|
// no more a vertex program setup.
|
|
_LastSetupGLArrayVertexProgram= false;
|
|
}
|
|
|
|
// If last was a standard GL array setup, and now it is a VertexProgram setup.
|
|
if( !_LastSetupGLArrayVertexProgram && isVertexProgramEnabled () )
|
|
{
|
|
// Disable all standards ptrs.
|
|
_DriverGLStates.enableVertexArray(false);
|
|
_DriverGLStates.enableNormalArray(false);
|
|
_DriverGLStates.enableColorArray(false);
|
|
for(uint i=0; i<inlGetNumTextStages(); i++)
|
|
{
|
|
_DriverGLStates.clientActiveTextureARB(i);
|
|
_DriverGLStates.enableTexCoordArray(false);
|
|
}
|
|
|
|
// now, vertex program setup.
|
|
_LastSetupGLArrayVertexProgram= true;
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::toggleGlArraysForARBVertexProgram()
|
|
{
|
|
H_AUTO_OGL(CDriverGL_toggleGlArraysForARBVertexProgram)
|
|
// If change of setup type, must disable olds.
|
|
//=======================
|
|
|
|
// If last was a VertexProgram setup, and now it is a standard GL array setup.
|
|
if( _LastSetupGLArrayVertexProgram && !isVertexProgramEnabled () )
|
|
{
|
|
if (_Extensions.ATITextureEnvCombine3)
|
|
{
|
|
// fix for ATI : when switching from Vertex Program to fixed Pipe, must clean texture, otherwise texture may be disabled in next render
|
|
// (seems to be a driver bug)
|
|
ITexture *oldTex[IDRV_MAT_MAXTEXTURES];
|
|
for(uint stage=0 ; stage < inlGetNumTextStages() ; stage++)
|
|
{
|
|
oldTex[stage] = _CurrentTexture[stage];
|
|
// activate the texture, or disable texturing if NULL.
|
|
activateTexture(stage, NULL);
|
|
}
|
|
glBegin(GL_QUADS);
|
|
glVertex4f(0.f, 0.f, 0.f, 1.f);
|
|
glVertex4f(0.f, 0.f, 0.f, 1.f);
|
|
glVertex4f(0.f, 0.f, 0.f, 1.f);
|
|
glVertex4f(0.f, 0.f, 0.f, 1.f);
|
|
glEnd();
|
|
for(uint stage=0 ; stage<inlGetNumTextStages() ; stage++)
|
|
{
|
|
// activate the texture, or disable texturing if NULL.
|
|
activateTexture(stage, oldTex[stage]);
|
|
}
|
|
}
|
|
|
|
// Disable all VertexAttribs.
|
|
for (uint value=0; value<CVertexBuffer::NumValue; value++)
|
|
{
|
|
// Index
|
|
uint glIndex=GLVertexAttribIndex[value];
|
|
_DriverGLStates.enableVertexAttribArrayARB(glIndex, false);
|
|
}
|
|
// no more a vertex program setup.
|
|
_LastSetupGLArrayVertexProgram= false;
|
|
}
|
|
|
|
// If last was a standard GL array setup, and now it is a VertexProgram setup.
|
|
if( !_LastSetupGLArrayVertexProgram && isVertexProgramEnabled () )
|
|
{
|
|
// Disable all standards ptrs.
|
|
_DriverGLStates.enableVertexArray(false);
|
|
_DriverGLStates.enableNormalArray(false);
|
|
_DriverGLStates.enableColorArray(false);
|
|
_DriverGLStates.enableSecondaryColorArray(false);
|
|
for(uint i=0; i<inlGetNumTextStages(); i++)
|
|
{
|
|
_DriverGLStates.clientActiveTextureARB(i);
|
|
_DriverGLStates.enableTexCoordArray(false);
|
|
}
|
|
|
|
// now, vertex program setup.
|
|
_LastSetupGLArrayVertexProgram= true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::toggleGlArraysForEXTVertexShader()
|
|
{
|
|
H_AUTO_OGL(CDriverGL_toggleGlArraysForEXTVertexShader)
|
|
// If change of setup type, must disable olds.
|
|
//=======================
|
|
|
|
|
|
// If last was a VertexProgram setup, and now it is a standard GL array setup.
|
|
if( _LastSetupGLArrayVertexProgram && !isVertexProgramEnabled () )
|
|
{
|
|
CVertexProgram *vp = _LastSetuppedVP;
|
|
if (vp)
|
|
{
|
|
CVertexProgamDrvInfosGL *drvInfo = NLMISC::safe_cast<CVertexProgamDrvInfosGL *>((IVertexProgramDrvInfos *) vp->_DrvInfo);
|
|
if (drvInfo)
|
|
{
|
|
// Disable all VertexAttribs.
|
|
for (uint value=0; value<CVertexBuffer::NumValue; value++)
|
|
{
|
|
_DriverGLStates.enableVertexAttribArrayForEXTVertexShader(value, false, drvInfo->Variants);
|
|
}
|
|
}
|
|
}
|
|
// no more a vertex program setup.
|
|
_LastSetupGLArrayVertexProgram= false;
|
|
}
|
|
|
|
// If last was a standard GL array setup, and now it is a VertexProgram setup.
|
|
if( !_LastSetupGLArrayVertexProgram && isVertexProgramEnabled () )
|
|
{
|
|
// Disable all standards ptrs.
|
|
_DriverGLStates.enableVertexArray(false);
|
|
_DriverGLStates.enableNormalArray(false);
|
|
_DriverGLStates.enableColorArray(false);
|
|
_DriverGLStates.enableSecondaryColorArray(false);
|
|
for(uint i=0; i<inlGetNumTextStages(); i++)
|
|
{
|
|
_DriverGLStates.clientActiveTextureARB(i);
|
|
_DriverGLStates.enableTexCoordArray(false);
|
|
}
|
|
|
|
// now, vertex program setup.
|
|
_LastSetupGLArrayVertexProgram= true;
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::setupGlArraysForNVVertexProgram(CVertexBufferInfo &vb)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupGlArraysForNVVertexProgram)
|
|
uint16 flags= vb.VertexFormat;
|
|
|
|
if (vb.VBMode == CVertexBufferInfo::HwARB)
|
|
_DriverGLStates.bindARBVertexBuffer(vb.VertexObjectId);
|
|
|
|
// For each value
|
|
for (uint value=0; value<CVertexBuffer::NumValue; value++)
|
|
{
|
|
//nldebug("3D: value = %d", value);
|
|
// Flag
|
|
uint16 flag=1<<value;
|
|
|
|
// Type
|
|
CVertexBuffer::TType type=vb.Type[value];
|
|
|
|
// Index
|
|
uint glIndex=GLVertexAttribIndex[value];
|
|
|
|
// Not setuped value and used
|
|
if (flags & flag)
|
|
{
|
|
/* OpenGL Driver Bug with VertexProgram, UChar4 type, and VertexArrayRange.
|
|
Don't work and lead to very poor performance (1/10) (VAR is "disabled").
|
|
*/
|
|
// Test if can use glColorPointer() / glSecondaryColorPointerEXT() instead.
|
|
if( (glIndex==3 || glIndex==4) )
|
|
{
|
|
if( type == CVertexBuffer::UChar4 )
|
|
{
|
|
// Must disable VertexAttrib array.
|
|
_DriverGLStates.enableVertexAttribArray(glIndex, false);
|
|
|
|
// Active this value, with standard gl calls
|
|
if(glIndex==3)
|
|
{
|
|
// Primary color
|
|
_DriverGLStates.enableColorArray(true);
|
|
glColorPointer(4,GL_UNSIGNED_BYTE, vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
else
|
|
{
|
|
// Secondary color
|
|
_DriverGLStates.enableSecondaryColorArray(true);
|
|
nglSecondaryColorPointerEXT(4,GL_UNSIGNED_BYTE, vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Can use normal VertexAttribArray.
|
|
// Disable first standard Color Array.
|
|
if(glIndex==3)
|
|
_DriverGLStates.enableColorArray(false);
|
|
else
|
|
_DriverGLStates.enableSecondaryColorArray(false);
|
|
|
|
// Active this value
|
|
_DriverGLStates.enableVertexAttribArray(glIndex, true);
|
|
nglVertexAttribPointerNV (glIndex, NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
}
|
|
// Else normal case, can't do anything for other values with UChar4....
|
|
else
|
|
{
|
|
// Active this value
|
|
_DriverGLStates.enableVertexAttribArray(glIndex, true);
|
|
nglVertexAttribPointerNV (glIndex, NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_DriverGLStates.enableVertexAttribArray(glIndex, false);
|
|
/* OpenGL Driver Bug with VertexProgram, UChar4 type, and VertexArrayRange.
|
|
Must also disable colorArray in standard gl calls.
|
|
*/
|
|
if(glIndex==3)
|
|
_DriverGLStates.enableColorArray(false);
|
|
else if(glIndex==4)
|
|
_DriverGLStates.enableSecondaryColorArray(false);
|
|
}
|
|
}
|
|
|
|
if (vb.VBMode == CVertexBufferInfo::HwARB)
|
|
_DriverGLStates.bindARBVertexBuffer(0);
|
|
|
|
}
|
|
|
|
// tells for each vertex argument if it must be normalized when it is an integral type
|
|
static const GLboolean ARBVertexProgramMustNormalizeAttrib[] =
|
|
{
|
|
GL_FALSE, // Position
|
|
GL_TRUE, // Normal
|
|
GL_FALSE, // TexCoord0
|
|
GL_FALSE, // TexCoord1
|
|
GL_FALSE, // TexCoord2
|
|
GL_FALSE, // TexCoord3
|
|
GL_FALSE, // TexCoord4
|
|
GL_FALSE, // TexCoord5
|
|
GL_FALSE, // TexCoord6
|
|
GL_FALSE, // TexCoord7
|
|
GL_TRUE, // PrimaryColor
|
|
GL_TRUE, // SecondaryColor
|
|
GL_TRUE, // Weight
|
|
GL_FALSE, // PaletteSkin
|
|
GL_FALSE, // Fog
|
|
GL_FALSE, // Empty
|
|
};
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::setupGlArraysForARBVertexProgram(CVertexBufferInfo &vb)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupGlArraysForARBVertexProgram)
|
|
|
|
uint32 flags= vb.VertexFormat;
|
|
|
|
nlctassert(CVertexBuffer::NumValue == sizeof(ARBVertexProgramMustNormalizeAttrib) / sizeof(ARBVertexProgramMustNormalizeAttrib[0]));
|
|
|
|
if (vb.VBMode == CVertexBufferInfo::HwARB)
|
|
{
|
|
_DriverGLStates.bindARBVertexBuffer(vb.VertexObjectId);
|
|
}
|
|
|
|
// special case if the buffer is an ATI_vertex_array_object
|
|
if (vb.VBMode == CVertexBufferInfo::HwATI)
|
|
{
|
|
// For each value
|
|
for (uint value=0; value<CVertexBuffer::NumValue; value++)
|
|
{
|
|
// Flag
|
|
uint16 flag=1<<value;
|
|
|
|
// Type
|
|
CVertexBuffer::TType type=vb.Type[value];
|
|
{
|
|
// Index
|
|
uint glIndex=GLVertexAttribIndex[value];
|
|
// Not setuped value and used
|
|
if (flags & flag)
|
|
{
|
|
_DriverGLStates.enableVertexAttribArrayARB(glIndex, true);
|
|
GLboolean mustNormalize = GL_FALSE;
|
|
if (GLTypeIsIntegral[type])
|
|
{
|
|
mustNormalize = ARBVertexProgramMustNormalizeAttrib[value];
|
|
}
|
|
nglVertexAttribArrayObjectATI(glIndex, NumCoordinatesType[type], GLType[type], mustNormalize, vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[value]);
|
|
}
|
|
else
|
|
{
|
|
_DriverGLStates.enableVertexAttribArrayARB(glIndex, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For each value
|
|
for (uint value=0; value<CVertexBuffer::NumValue; value++)
|
|
{
|
|
// Flag
|
|
uint16 flag=1<<value;
|
|
|
|
// Type
|
|
CVertexBuffer::TType type=vb.Type[value];
|
|
{
|
|
// Index
|
|
uint glIndex=GLVertexAttribIndex[value];
|
|
// Not setuped value and used
|
|
if (flags & flag)
|
|
{
|
|
_DriverGLStates.enableVertexAttribArrayARB(glIndex, true);
|
|
GLboolean mustNormalize = GL_FALSE;
|
|
if (GLTypeIsIntegral[type])
|
|
{
|
|
mustNormalize = ARBVertexProgramMustNormalizeAttrib[value];
|
|
}
|
|
nglVertexAttribPointerARB(glIndex, NumCoordinatesType[type], GLType[type], mustNormalize, vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
else
|
|
{
|
|
_DriverGLStates.enableVertexAttribArrayARB(glIndex, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::setupGlArraysForEXTVertexShader(CVertexBufferInfo &vb)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupGlArraysForEXTVertexShader)
|
|
|
|
|
|
CVertexProgram *vp = _LastSetuppedVP;
|
|
if (!vp) return;
|
|
CVertexProgamDrvInfosGL *drvInfo = NLMISC::safe_cast<CVertexProgamDrvInfosGL *>((IVertexProgramDrvInfos *) vp->_DrvInfo);
|
|
if (!drvInfo) return;
|
|
|
|
uint32 flags= vb.VertexFormat;
|
|
|
|
|
|
if (vb.VBMode == CVertexBufferInfo::HwARB)
|
|
{
|
|
_DriverGLStates.bindARBVertexBuffer(vb.VertexObjectId);
|
|
}
|
|
|
|
// For each value
|
|
for (uint value=0; value<CVertexBuffer::NumValue; value++)
|
|
{
|
|
// Flag
|
|
uint16 flag=1<<value;
|
|
|
|
// Type
|
|
CVertexBuffer::TType type=vb.Type[value];
|
|
|
|
// Index
|
|
uint glIndex=GLVertexAttribIndex[value];
|
|
|
|
// Not setuped value and used
|
|
if (flags & flag & drvInfo->UsedVertexComponents)
|
|
{
|
|
_DriverGLStates.enableVertexAttribArrayForEXTVertexShader(glIndex, true, drvInfo->Variants);
|
|
// use variant or open gl standard array
|
|
if (vb.VBMode == CVertexBufferInfo::HwATI)
|
|
{
|
|
switch(value)
|
|
{
|
|
case CVertexBuffer::Position: // position
|
|
{
|
|
nlassert(NumCoordinatesType[type] >= 2);
|
|
nglArrayObjectATI(GL_VERTEX_ARRAY, NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::Position]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Weight: // skin weight
|
|
{
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantArrayObjectATI(drvInfo->Variants[CDriverGL::EVSSkinWeightVariant], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::Weight]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Normal: // normal
|
|
{
|
|
nlassert(NumCoordinatesType[type] == 3); // must have 3 components for normals
|
|
nglArrayObjectATI(GL_NORMAL_ARRAY, 3, GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::PrimaryColor: // color
|
|
{
|
|
nlassert(NumCoordinatesType[type] >= 3); // must have 3 or 4 components for primary color
|
|
nglArrayObjectATI(GL_COLOR_ARRAY, NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::PrimaryColor]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::SecondaryColor: // secondary color
|
|
{
|
|
// implemented using a variant, as not available with EXTVertexShader
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantArrayObjectATI(drvInfo->Variants[CDriverGL::EVSSecondaryColorVariant], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::SecondaryColor]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Fog: // fog coordinate
|
|
{
|
|
// implemented using a variant
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantArrayObjectATI(drvInfo->Variants[CDriverGL::EVSFogCoordsVariant], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::Fog]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::PaletteSkin: // palette skin
|
|
{
|
|
// implemented using a variant
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantArrayObjectATI(drvInfo->Variants[CDriverGL::EVSPaletteSkinVariant], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[CVertexBuffer::PaletteSkin]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Empty: // empty
|
|
nlstop;
|
|
break;
|
|
case CVertexBuffer::TexCoord0:
|
|
case CVertexBuffer::TexCoord1:
|
|
case CVertexBuffer::TexCoord2:
|
|
case CVertexBuffer::TexCoord3:
|
|
case CVertexBuffer::TexCoord4:
|
|
case CVertexBuffer::TexCoord5:
|
|
case CVertexBuffer::TexCoord6:
|
|
case CVertexBuffer::TexCoord7:
|
|
{
|
|
_DriverGLStates.clientActiveTextureARB(value - CVertexBuffer::TexCoord0);
|
|
nglArrayObjectATI(GL_TEXTURE_COORD_ARRAY, NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
default:
|
|
nlstop; // invalid value
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(value)
|
|
{
|
|
case CVertexBuffer::Position: // position
|
|
{
|
|
nlassert(NumCoordinatesType[type] >= 2);
|
|
glVertexPointer(NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Weight: // skin weight
|
|
{
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantPointerEXT(drvInfo->Variants[CDriverGL::EVSSkinWeightVariant], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Normal: // normal
|
|
{
|
|
nlassert(NumCoordinatesType[type] == 3); // must have 3 components for normals
|
|
glNormalPointer(GLType[type], vb.VertexSize, vb.ValuePtr[CVertexBuffer::Normal]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::PrimaryColor: // color
|
|
{
|
|
nlassert(NumCoordinatesType[type] >= 3); // must have 3 or 4 components for primary color
|
|
glColorPointer(NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::SecondaryColor: // secondary color
|
|
{
|
|
// implemented using a variant, as not available with EXTVertexShader
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantPointerEXT(drvInfo->Variants[CDriverGL::EVSSecondaryColorVariant], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Fog: // fog coordinate
|
|
{
|
|
// implemented using a variant
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantPointerEXT(drvInfo->Variants[CDriverGL::EVSFogCoordsVariant], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::PaletteSkin: // palette skin
|
|
{
|
|
// implemented using a variant
|
|
nlassert(NumCoordinatesType[type] == 4); // variant, only 4 component supported
|
|
nglVariantPointerEXT(drvInfo->Variants[CDriverGL::EVSPaletteSkinVariant], GLType[type], vb.VertexSize, vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
case CVertexBuffer::Empty: // empty
|
|
nlstop;
|
|
break;
|
|
case CVertexBuffer::TexCoord0:
|
|
case CVertexBuffer::TexCoord1:
|
|
case CVertexBuffer::TexCoord2:
|
|
case CVertexBuffer::TexCoord3:
|
|
case CVertexBuffer::TexCoord4:
|
|
case CVertexBuffer::TexCoord5:
|
|
case CVertexBuffer::TexCoord6:
|
|
case CVertexBuffer::TexCoord7:
|
|
{
|
|
_DriverGLStates.clientActiveTextureARB(value - CVertexBuffer::TexCoord0);
|
|
nglArrayObjectATI(GL_TEXTURE_COORD_ARRAY, NumCoordinatesType[type], GLType[type], vb.VertexSize, vb.VertexObjectId, (ptrdiff_t) vb.ValuePtr[value]);
|
|
}
|
|
break;
|
|
default:
|
|
nlstop; // invalid value
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_DriverGLStates.enableVertexAttribArrayForEXTVertexShader(glIndex, false, drvInfo->Variants);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::setupGlArrays(CVertexBufferInfo &vb)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupGlArrays)
|
|
|
|
// Standard case (NVVertexProgram or no vertex program case)
|
|
if (_Extensions.NVVertexProgram)
|
|
{
|
|
toggleGlArraysForNVVertexProgram();
|
|
// Use a vertex program ?
|
|
if (!isVertexProgramEnabled ())
|
|
{
|
|
setupGlArraysStd(vb);
|
|
}
|
|
else
|
|
{
|
|
setupGlArraysForNVVertexProgram(vb);
|
|
}
|
|
}
|
|
else if (_Extensions.ARBVertexProgram)
|
|
{
|
|
toggleGlArraysForARBVertexProgram();
|
|
// Use a vertex program ?
|
|
if (!isVertexProgramEnabled ())
|
|
{
|
|
setupGlArraysStd(vb);
|
|
}
|
|
else
|
|
{
|
|
setupGlArraysForARBVertexProgram(vb);
|
|
}
|
|
}
|
|
else if (_Extensions.EXTVertexShader)
|
|
{
|
|
toggleGlArraysForEXTVertexShader();
|
|
// Use a vertex program ?
|
|
if (!isVertexProgramEnabled ())
|
|
{
|
|
setupGlArraysStd(vb);
|
|
}
|
|
else
|
|
{
|
|
setupGlArraysForEXTVertexShader(vb);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no vertex programs
|
|
setupGlArraysStd(vb);
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CVertexBufferInfo::setupVertexBuffer(CVertexBuffer &vb)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_setupVertexBuffer)
|
|
sint i;
|
|
VertexFormat= vb.getVertexFormat();
|
|
VertexSize= vb.getVertexSize();
|
|
NumVertices= vb.getNumVertices();
|
|
|
|
// Lock the buffer
|
|
CVertexBufferReadWrite access;
|
|
uint8 *ptr;
|
|
CVBDrvInfosGL *info= safe_cast<CVBDrvInfosGL*>((IVBDrvInfos*)vb.DrvInfos);
|
|
nlassert (info);
|
|
if (info->_VBHard)
|
|
{
|
|
ptr = (uint8*)info->_VBHard->getPointer();
|
|
info->_VBHard->setupVBInfos(*this);
|
|
}
|
|
else
|
|
{
|
|
nlassert (info->_SystemMemory);
|
|
ptr = info->_SystemMemory;
|
|
VBMode = SysMem;
|
|
}
|
|
|
|
// Get value pointer
|
|
for (i=0; i<CVertexBuffer::NumValue; i++)
|
|
{
|
|
// Value used ?
|
|
if (VertexFormat&(1<<i))
|
|
{
|
|
// Get the pointer
|
|
ValuePtr[i]= ptr+vb.getValueOffEx((CVertexBuffer::TValue)i);
|
|
|
|
// Type of the value
|
|
Type[i]=vb.getValueType (i);
|
|
}
|
|
}
|
|
|
|
// Copy the UVRouting table
|
|
const uint8 *uvRouting = vb.getUVRouting();
|
|
for (i=0; i<CVertexBuffer::MaxStage; i++)
|
|
{
|
|
UVRouting[i] = uvRouting[i];
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::resetVertexArrayRange()
|
|
{
|
|
H_AUTO_OGL(CDriverGL_resetVertexArrayRange)
|
|
if(_CurrentVertexBufferHard)
|
|
{
|
|
// Must ensure it has ended any drawing
|
|
_CurrentVertexBufferHard->lock();
|
|
_CurrentVertexBufferHard->unlock();
|
|
// disable it
|
|
_CurrentVertexBufferHard->disable();
|
|
}
|
|
// Clear any VertexBufferHard created.
|
|
_VertexBufferHardSet.clear();
|
|
|
|
// After, Clear the 2 vertexArrayRange, if any.
|
|
if(_AGPVertexArrayRange)
|
|
_AGPVertexArrayRange->free();
|
|
if(_VRAMVertexArrayRange)
|
|
_VRAMVertexArrayRange->free();
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CDriverGL::initVertexBufferHard(uint agpMem, uint vramMem)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_initVertexBufferHard)
|
|
if(!supportVertexBufferHard())
|
|
return false;
|
|
|
|
// must be supported
|
|
if(!_AGPVertexArrayRange || !_VRAMVertexArrayRange)
|
|
return false;
|
|
|
|
// First, reset any VBHard created.
|
|
resetVertexArrayRange();
|
|
bool ok= true;
|
|
|
|
// Try to allocate AGPMemory.
|
|
if(agpMem>0)
|
|
{
|
|
agpMem&= ~15; // ensure 16-bytes aligned mem count (maybe useful :) ).
|
|
agpMem= max(agpMem, (uint)NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE);
|
|
while(agpMem>= NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE)
|
|
{
|
|
if(_AGPVertexArrayRange->allocate(agpMem, CVertexBuffer::AGPPreferred))
|
|
{
|
|
nlinfo("3D: %.u vertices supported", _MaxVerticesByVBHard);
|
|
nlinfo("3D: Success to allocate %.1f Mo of AGP VAR Ram", agpMem / 1000000.f);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
agpMem/=2;
|
|
agpMem &=~15;
|
|
}
|
|
}
|
|
|
|
if(agpMem< NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE)
|
|
{
|
|
nlinfo("3D: %.u vertices supported", _MaxVerticesByVBHard);
|
|
nlinfo("3D: Failed to allocate %.1f Mo of AGP VAR Ram", NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE / 1000000.f);
|
|
ok= false;
|
|
}
|
|
}
|
|
|
|
|
|
// Try to allocate VRAMMemory.
|
|
if(vramMem>0)
|
|
{
|
|
vramMem&= ~15; // ensure 16-bytes aligned mem count (maybe useful :) ).
|
|
vramMem= max(vramMem, (uint)NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE);
|
|
while(vramMem>= NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE)
|
|
{
|
|
if(_VRAMVertexArrayRange->allocate(vramMem, CVertexBuffer::StaticPreferred))
|
|
break;
|
|
else
|
|
{
|
|
vramMem/=2;
|
|
vramMem &=~15;
|
|
}
|
|
}
|
|
|
|
if(vramMem< NL3D_DRV_VERTEXARRAY_MINIMUM_SIZE)
|
|
{
|
|
ok= false;
|
|
}
|
|
}
|
|
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
uint32 CDriverGL::getAvailableVertexAGPMemory ()
|
|
{
|
|
H_AUTO_OGL(CDriverGL_getAvailableVertexAGPMemory )
|
|
if (_AGPVertexArrayRange)
|
|
return _AGPVertexArrayRange->sizeAllocated();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
uint32 CDriverGL::getAvailableVertexVRAMMemory ()
|
|
{
|
|
H_AUTO_OGL(CDriverGL_getAvailableVertexVRAMMemory )
|
|
if (_VRAMVertexArrayRange)
|
|
return _VRAMVertexArrayRange->sizeAllocated();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CDriverGL::fenceOnCurVBHardIfNeeded(IVertexBufferHardGL *newVBHard)
|
|
{
|
|
H_AUTO_OGL(CDriverGL_fenceOnCurVBHardIfNeeded)
|
|
// If old is not a VBHard, or if not a NVidia VBHard, no-op.
|
|
if( _CurrentVertexBufferHard==NULL || !_CurrentVertexBufferHard->VBType == IVertexBufferHardGL::NVidiaVB)
|
|
return;
|
|
|
|
// if we do not activate the same (NB: newVBHard==NULL if not a VBHard).
|
|
if(_CurrentVertexBufferHard!=newVBHard)
|
|
{
|
|
// get NVidia interface
|
|
CVertexBufferHardGLNVidia *vbHardNV= static_cast<CVertexBufferHardGLNVidia*>(_CurrentVertexBufferHard);
|
|
|
|
// If some render() have been done with this VB.
|
|
if( vbHardNV->GPURenderingAfterFence )
|
|
{
|
|
/*
|
|
Since we won't work with this VB for a long time, we set a fence.
|
|
|
|
NB: if the fence was previously set. NV_Fence Specification says that the new ONE replaces it.
|
|
This is EXACTLY what we wants, since the old one is no more interesting.
|
|
|
|
NB: never insert a fence for said "Static Lock" VBHard. Those VBHard are said to be "static"
|
|
therefore, user should never modify them (else lock() is much slower...)
|
|
*/
|
|
if( !vbHardNV->getLockHintStatic() )
|
|
vbHardNV->setFence();
|
|
// Since we have set a new Fence, we won't need to do it at next vbHardNV->lock()
|
|
vbHardNV->GPURenderingAfterFence= false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
CIndexBufferInfo::CIndexBufferInfo()
|
|
{
|
|
H_AUTO_OGL(CIndexBufferInfo_CIndexBufferInfo)
|
|
_Values = NULL;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CIndexBufferInfo::setupIndexBuffer(CIndexBuffer &ib)
|
|
{
|
|
H_AUTO_OGL(CIndexBufferInfo_setupIndexBuffer)
|
|
CIndexBufferReadWrite access;
|
|
ib.lock (access);
|
|
_Values = access.getPtr();
|
|
_Format = access.getFormat();
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
} // NL3D
|