Merge pull request #211 from D-A-Heitbrink/master
Added support for unsigned 64 bit ints + some code for bindless textures
This commit is contained in:
@@ -166,10 +166,9 @@ IF(DYNAMIC_OPENSCENEGRAPH)
|
||||
ADD_SUBDIRECTORY(osgwidgettable)
|
||||
ADD_SUBDIRECTORY(osgwidgetwindow)
|
||||
ADD_SUBDIRECTORY(osguserdata)
|
||||
|
||||
# GL3/GL4 example
|
||||
ADD_SUBDIRECTORY(osgsimplegl3)
|
||||
|
||||
ADD_SUBDIRECTORY(osgbindlesstext)
|
||||
IF(OSG_CPP_EXCEPTIONS_AVAILABLE)
|
||||
ADD_SUBDIRECTORY(osgunittests)
|
||||
ADD_SUBDIRECTORY(osgmemorytest)
|
||||
|
||||
2
examples/osgbindlesstext/CMakeLists.txt
Normal file
2
examples/osgbindlesstext/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
SET(TARGET_SRC osgbindlesstext.cpp )
|
||||
SETUP_EXAMPLE(osgbindlesstext)
|
||||
596
examples/osgbindlesstext/osgbindlesstext.cpp
Normal file
596
examples/osgbindlesstext/osgbindlesstext.cpp
Normal file
@@ -0,0 +1,596 @@
|
||||
/* OpenSceneGraph example, osgbindlesstex.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
///\author David A Heitbrink
|
||||
/// This is an example implementation of the use of bindless textures.
|
||||
/// "bindless" textures are relatively simple concept, basically
|
||||
/// you get a texture handle, then ask the driver to keep said
|
||||
/// handle resident.
|
||||
///
|
||||
/// Once the texture has been made resident, we need to upload
|
||||
/// the handle (a 64 bit unsigned int) to the shader. This can
|
||||
/// be done in a number of ways, through attributes, uniform
|
||||
/// buffer objects, shader buffer objects or just plain uniforms.
|
||||
///
|
||||
/// The basic point of the bindless texture is to remove the need
|
||||
/// to bind a new texture every time we want to render something
|
||||
/// with a different texture. Generally speaking in broad terms
|
||||
/// driver overhead tends to be a huge bottle neck on modern
|
||||
/// hardware (as of late 2016). By using bindless textures
|
||||
/// we can remove alot of calls to the driver to switch active
|
||||
/// textures while rendering. What this also allows us to do
|
||||
/// is to consolidate more objects + draw states as we do
|
||||
/// not need to change textures, this save us alot of calls to
|
||||
/// the driver.
|
||||
///
|
||||
/// This example combines instancing with bindless textures
|
||||
/// to draw 1000 cubes, each with a unique texture. This is
|
||||
/// a pretty simplified example, where each instance ID is
|
||||
/// used as a index into the array of textures.
|
||||
///
|
||||
/// One of the powerfull things about bindless textures is it allows
|
||||
/// many more objects to be combined into a single drawable.
|
||||
/// However to do this you may need to add an attribute to
|
||||
/// use an index into the array of texture handles, and not
|
||||
/// just use the instance ID like in this example.
|
||||
|
||||
#include <osg/Depth>
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/Material>
|
||||
#include <osg/Math>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/PolygonOffset>
|
||||
#include <osg/Projection>
|
||||
#include <osg/ShapeDrawable>
|
||||
#include <osg/StateSet>
|
||||
#include <osg/Switch>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/TextureBuffer>
|
||||
#include <osg/Image>
|
||||
#include <osg/TexEnv>
|
||||
#include <osg/VertexProgram>
|
||||
#include <osg/FragmentProgram>
|
||||
#include <osg/GLExtensions>
|
||||
#include <osg/ContextData>
|
||||
|
||||
#include <osg/TextureBuffer>
|
||||
#include <osg/BufferIndexBinding>
|
||||
|
||||
#include <osgDB/ReadFile>
|
||||
#include <osgDB/FileUtils>
|
||||
|
||||
|
||||
#include <osgText/Text>
|
||||
|
||||
#include <osgViewer/Viewer>
|
||||
#include <osgViewer/ViewerEventHandlers>
|
||||
|
||||
#include <osgGA/StateSetManipulator>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
//Hard coded constant for number unique textures
|
||||
const int TextureCount = 1000;
|
||||
// To use bindless textures, we need to tell the GPU to
|
||||
// enable the use of 64 bit integers and bindless textures
|
||||
//
|
||||
//
|
||||
// At this time (late 2016) NVidia drivers seem to dislike
|
||||
// the ARB, GL_ARB_bindless_texture, if you do not have a
|
||||
// NVidia driver, you will most likely have to change:
|
||||
// GL_NV_gpu_shader5
|
||||
// to:
|
||||
// GL_ARB_gpu_shader5
|
||||
|
||||
//the XXX is so we can replace it with a string from TextureCount
|
||||
std::string vertShader=
|
||||
"#version 450 compatibility \n"
|
||||
"#extension GL_ARB_bindless_texture : require \n"
|
||||
"#extension GL_NV_gpu_shader5 : require // uint64_t \n"
|
||||
"//#extension GL_ARB_gpu_shader5 : require // uint64_t \n"
|
||||
"//#extension GL_ARB_gpu_shader_int64: require // uint64_t \n"
|
||||
"in float osg_FrameTime; \n"
|
||||
"out vec2 TexCoord; \n"
|
||||
"flat out int textureIndex; \n"
|
||||
"void main() { \n"
|
||||
" mat4 scale =mat4(0.01, 0.00, 0.00, 0.00, \n"
|
||||
" 0.00, 0.01, 0.00, 0.00, \n"
|
||||
" 0.00, 0.00, 0.01, 0.00, \n"
|
||||
" 0.00, 0.00, 0.00, 1.00); \n"
|
||||
" vec4 pos = gl_Vertex*scale; \n"
|
||||
" pos.x += ((gl_InstanceID%100)/10)*0.015-0.075; \n"
|
||||
" pos.y += (gl_InstanceID/100)*0.015 - 0.075; \n"
|
||||
" pos.z += (gl_InstanceID%10)*0.015 - 0.075; \n"
|
||||
" pos.w = 1; \n"
|
||||
" gl_Position = gl_ModelViewProjectionMatrix*pos; \n"
|
||||
" TexCoord = gl_MultiTexCoord0.xy; \n"
|
||||
" textureIndex = gl_InstanceID%XXX; \n"
|
||||
"} \n"
|
||||
;
|
||||
//we could setup tex to be of type sampler2D, and not have to do
|
||||
//the type conversion, but I wanted to added code to test if tex
|
||||
//had a value set.
|
||||
//If we get a red cube, we are not getting our handle from our UBO,
|
||||
//if we get a black cube, then we are having an issue with the
|
||||
//texture handle itself
|
||||
std::string fragShader =
|
||||
"#version 450 compatibility \n"
|
||||
"#extension GL_ARB_bindless_texture : require \n"
|
||||
"#extension GL_NV_gpu_shader5 : require // uint64_t \n"
|
||||
"//#extension GL_ARB_gpu_shader5 : require // uint64_t \n"
|
||||
"//#extension GL_ARB_gpu_shader_int64: require // uint64_t \n"
|
||||
"uniform sampler2D TextureId; \n"
|
||||
"in vec2 TexCoord; \n"
|
||||
"flat in int textureIndex; \n"
|
||||
"layout (binding = 0, std140) uniform TEXTURE_BLOCK \n"
|
||||
"{ \n"
|
||||
" uint64_t tex[XXX]; \n"
|
||||
"}; \n"
|
||||
"void main() { \n"
|
||||
" int tIndex = (int)(textureIndex); \n"
|
||||
" sampler2D myText = sampler2D(tex[tIndex]); \n"
|
||||
" gl_FragColor = texture2D(myText,TexCoord); \n"
|
||||
" if (tex[tIndex] == 0) gl_FragColor.r = 1.0; \n"
|
||||
"} \n"
|
||||
;
|
||||
|
||||
///This class provides a basic wraper for a Uniform Buffer Object
|
||||
///or UBO, and provides the storage for the texture handles
|
||||
class BindlessBuffer: public osg::Referenced{
|
||||
public:
|
||||
typedef osg::ref_ptr<osg::UniformBufferObject> UniBufferObjRef;
|
||||
typedef osg::ref_ptr<osg::UniformBufferBinding> UniBufferBindingRef;
|
||||
typedef osg::ref_ptr<osg::UInt64Array> HandleArrayRef;
|
||||
typedef osg::ref_ptr<BindlessBuffer> BindlessBufferRef;
|
||||
static BindlessBufferRef Make(size_t count){
|
||||
BindlessBufferRef val = new BindlessBuffer();
|
||||
val->_count = count;
|
||||
val->_sbbo = new osg::UniformBufferObject;
|
||||
val->_handles = new osg::UInt64Array();
|
||||
val->_handles->resize(count*2,0);
|
||||
val->_handles->setBufferObject(val->_sbbo);
|
||||
val->_ssbb = new osg::UniformBufferBinding(0, val->_sbbo.get(), 0, sizeof(GLuint64)*count);
|
||||
return val;
|
||||
}
|
||||
BindlessBuffer& operator = (const BindlessBuffer& rhs){
|
||||
if (this != &rhs){
|
||||
_count=rhs._count;
|
||||
_sbbo =rhs._sbbo ;
|
||||
_ssbb =rhs._ssbb ;
|
||||
_handles = rhs._handles;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
BindlessBuffer(const BindlessBuffer& rhs):osg::Referenced(rhs){
|
||||
if (this != &rhs){
|
||||
_count=rhs._count;
|
||||
_sbbo =rhs._sbbo ;
|
||||
_ssbb =rhs._ssbb ;
|
||||
_handles = rhs._handles;
|
||||
}
|
||||
}
|
||||
UniBufferObjRef& Object(){return _sbbo;}
|
||||
UniBufferBindingRef& Binding(){return _ssbb;}
|
||||
HandleArrayRef& Handles(){return _handles;}
|
||||
int count(){return _count;}
|
||||
private:
|
||||
int _count;
|
||||
UniBufferObjRef _sbbo;
|
||||
UniBufferBindingRef _ssbb;
|
||||
HandleArrayRef _handles;
|
||||
|
||||
BindlessBuffer():osg::Referenced(),_count(0){
|
||||
}
|
||||
};
|
||||
|
||||
///This class extends a Texture, when this is texture is applied
|
||||
///the first time, it will setup all our texture handles, after that
|
||||
///it will not make any more GL calls until it gets released
|
||||
class BindlessTexture: public osg::Texture2D
|
||||
{
|
||||
public:
|
||||
typedef osg::ref_ptr<BindlessBuffer> BufferRef;
|
||||
typedef std::vector<osg::ref_ptr<osg::Image> > TextureList;
|
||||
typedef std::vector<GLuint64EXT> HandleList;
|
||||
typedef osg::ref_ptr< osg::Texture::TextureObject> TextureObjectRef;
|
||||
typedef std::vector<TextureObjectRef> TextureObjectList;
|
||||
typedef osg::buffered_object<TextureObjectList> TextureObjectBuffer;
|
||||
|
||||
BindlessTexture();
|
||||
BindlessTexture(BufferRef, TextureList);
|
||||
BindlessTexture(const BindlessTexture& rhs, const osg::CopyOp& copy =osg::CopyOp::SHALLOW_COPY);
|
||||
void releaseGLObjects(osg::State* state) const;
|
||||
void resizeGLObjectBuffers(unsigned maxSize);
|
||||
void setBidlessIndex(unsigned int index);
|
||||
META_StateAttribute(osg, BindlessTexture, TEXTURE);
|
||||
|
||||
void apply(osg::State& state) const;
|
||||
protected:
|
||||
void applyOnce(osg::State &state) const;
|
||||
mutable osg::buffered_object<HandleList> _handles;
|
||||
mutable TextureList _textureList;
|
||||
mutable osg::ref_ptr<BindlessBuffer> _buffer;
|
||||
mutable std::vector<bool> _isBound;
|
||||
mutable TextureObjectBuffer _textureBufferList;
|
||||
// array index = texture image unit.
|
||||
unsigned int _bindlessIndex;
|
||||
};
|
||||
|
||||
|
||||
BindlessTexture::BindlessTexture():osg::Texture2D(),_bindlessIndex(0)
|
||||
{
|
||||
_isBound.resize(5,false);
|
||||
}
|
||||
|
||||
BindlessTexture::BindlessTexture(const BindlessTexture& rhs, const osg::CopyOp& copy)
|
||||
:osg::Texture2D( rhs, copy )
|
||||
{
|
||||
_isBound.resize(5,false);
|
||||
_buffer = rhs._buffer;
|
||||
_bindlessIndex = rhs._bindlessIndex;
|
||||
for(unsigned i=0; i<rhs._handles.size(); ++i)
|
||||
_handles[i] = rhs._handles[i];
|
||||
}
|
||||
|
||||
BindlessTexture::BindlessTexture(BufferRef ref,TextureList textureList) :
|
||||
osg::Texture2D( textureList[0] ),_buffer(ref),_bindlessIndex(0),_textureList(textureList)
|
||||
{
|
||||
_isBound.resize(5,false);
|
||||
}
|
||||
|
||||
void BindlessTexture::setBidlessIndex(unsigned int index){
|
||||
_bindlessIndex = index;
|
||||
}
|
||||
/// Just as the name suggest this should be called once per
|
||||
/// context, durring its lifetime. This basically
|
||||
/// just sets up our texture handles, and loads them
|
||||
/// into our UBO. A good portion of this was copied from
|
||||
/// Texture2D::apply, this is in no ways a general solution.
|
||||
void BindlessTexture::applyOnce(osg::State& state) const
|
||||
{
|
||||
if (!_buffer)
|
||||
return;
|
||||
|
||||
TextureObject* textureObject;
|
||||
unsigned contextID = state.getContextID();
|
||||
osg::GLExtensions* extensions = osg::GLExtensions::Get( contextID, true );
|
||||
|
||||
osg::ref_ptr<osg::Image> image = _image;
|
||||
if (_handles[contextID].size() < _textureList.size())
|
||||
_handles[contextID].resize( _textureList.size(),0);
|
||||
if (_textureBufferList[contextID].size() < _textureList.size())
|
||||
_textureBufferList[contextID].resize( _textureList.size());
|
||||
int txtcount = _textureList.size();
|
||||
if (_buffer->count() < txtcount)
|
||||
txtcount = _buffer->count();
|
||||
//for each actual texture we have, bind it, get the texture hande, assign the value to our UBO
|
||||
for (int i = 0; i <txtcount; i++){
|
||||
image = _textureList[i];
|
||||
if (_image.valid())
|
||||
computeInternalFormatWithImage(*image);
|
||||
else
|
||||
continue;
|
||||
// compute the dimensions of the texture.
|
||||
computeRequiredTextureDimensions(state,*image,_textureWidth, _textureHeight, _numMipmapLevels);
|
||||
textureObject = generateAndAssignTextureObject(contextID,GL_TEXTURE_2D,_numMipmapLevels,_internalFormat,_textureWidth,_textureHeight,1,_borderWidth);
|
||||
textureObject->bind();
|
||||
applyTexParameters(GL_TEXTURE_2D,state);
|
||||
|
||||
applyTexImage2D_load(state,GL_TEXTURE_2D,image.get(),
|
||||
_textureWidth, _textureHeight, _numMipmapLevels);
|
||||
textureObject->setAllocated(true);
|
||||
_textureBufferList[contextID][i] = textureObject;
|
||||
|
||||
//Here is where the "magic" happens, we get the texture handle for our texture, copy it to our UBO,
|
||||
//and then tell OpenGL to keep the handle resident
|
||||
_handles[contextID][i] = extensions->glGetTextureHandle( textureObject->id() );
|
||||
std::vector<GLuint64> &vec = _buffer->Handles()->asVector();
|
||||
vec[i*2] = _handles[contextID][i];
|
||||
_buffer->Object()->dirty();
|
||||
_buffer->Handles()->dirty();
|
||||
|
||||
if ( _handles[contextID][i] != 0L || extensions->glIsTextureHandleResident( _handles[contextID][i]) == GL_FALSE)
|
||||
{
|
||||
extensions->glMakeTextureHandleResident( _handles[contextID][i] );
|
||||
}
|
||||
}
|
||||
|
||||
// update the modified tag to show that it is up to date.
|
||||
getModifiedCount(contextID) = image->getModifiedCount();
|
||||
}
|
||||
|
||||
void BindlessTexture::apply(osg::State& state) const
|
||||
{
|
||||
unsigned contextID = state.getContextID();
|
||||
if ( _isBound[contextID] == false )
|
||||
{
|
||||
applyOnce(state);
|
||||
_isBound[contextID] = true;
|
||||
}else{
|
||||
//we should mostly hit this durring the lifetime of this object,
|
||||
//note we basically do nothing......
|
||||
}
|
||||
}
|
||||
/// cleanup, we just need to tell OpenGL to release our texture handle
|
||||
void BindlessTexture::releaseGLObjects(osg::State* state) const
|
||||
{
|
||||
if ( state )
|
||||
{
|
||||
unsigned contextID = state->getContextID();
|
||||
osg::Texture2D::releaseGLObjects( state );
|
||||
osg::GLExtensions* ext = osg::GLExtensions::Get( contextID, true );
|
||||
|
||||
for(unsigned i=0; i<_handles[contextID].size(); ++i)
|
||||
{
|
||||
ext->glMakeTextureHandleNonResident( _handles[contextID][i] );
|
||||
_handles[contextID][i] = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BindlessTexture::resizeGLObjectBuffers(unsigned maxSize)
|
||||
{
|
||||
osg::Texture2D::resizeGLObjectBuffers( maxSize );
|
||||
|
||||
size_t handleSize = _handles.size();
|
||||
size_t txtSize = _textureList.size();
|
||||
size_t boundSize = _isBound.size();
|
||||
if ( handleSize < maxSize ) {
|
||||
_isBound.resize(maxSize,false);
|
||||
}
|
||||
if ( handleSize < maxSize ) {
|
||||
_handles.resize( maxSize );
|
||||
for(unsigned i=handleSize; i<_handles.size(); ++i){
|
||||
for(unsigned j=0; j<txtSize; ++j)
|
||||
_handles[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef osg::ref_ptr<osg::Image> ImageRef;
|
||||
///////////////////////////////////////////////////////
|
||||
///Create an array of images, with checkerboard
|
||||
///pattern with random color and size
|
||||
///
|
||||
void createImageArray(osg::StateSet* attachPnt){
|
||||
BindlessTexture::TextureList images;
|
||||
images.resize(TextureCount);
|
||||
BindlessBuffer::BindlessBufferRef buffer = BindlessBuffer::Make(TextureCount);
|
||||
srand (time(NULL));
|
||||
for (int i =0; i < TextureCount; i++){
|
||||
ImageRef tImage = new osg::Image();
|
||||
int powerOf2 = rand()%6+4;
|
||||
const unsigned int imageSize = 1<<powerOf2;
|
||||
tImage->allocateImage(imageSize,imageSize,1,GL_RGBA,GL_UNSIGNED_BYTE);
|
||||
unsigned char* buff = tImage->data();
|
||||
const int stride = 4;
|
||||
unsigned char primaryColor[4];
|
||||
|
||||
int boxWidth = rand()%15+2;
|
||||
int boxLength = rand()%15+2;
|
||||
|
||||
//light squares
|
||||
primaryColor[0] = rand()%128 + 128;
|
||||
primaryColor[1] = rand()%128 + 128;
|
||||
primaryColor[2] = rand()%128 + 128;
|
||||
//dark squares
|
||||
unsigned char secondaryColor[4];
|
||||
secondaryColor[0] = rand()%128;
|
||||
secondaryColor[1] = rand()%128;
|
||||
secondaryColor[2] = rand()%128;
|
||||
for (int x = 0; x < imageSize; x++){
|
||||
for (int y =0; y<imageSize; y++){
|
||||
unsigned char* pixel = &buff[(x*imageSize+y)*stride];
|
||||
int xSide = x/boxWidth;
|
||||
int ySide = y/boxLength;
|
||||
bool isPrimaryColor = (xSide+ySide)%2>0;
|
||||
if (isPrimaryColor){
|
||||
pixel[0] = primaryColor[0];
|
||||
pixel[1] = primaryColor[1];
|
||||
pixel[2] = primaryColor[2];
|
||||
}else{
|
||||
pixel[0] = secondaryColor[0];
|
||||
pixel[1] = secondaryColor[1];
|
||||
pixel[2] = secondaryColor[2];
|
||||
}
|
||||
pixel[3] = 255;
|
||||
}
|
||||
}
|
||||
images[i] = tImage;
|
||||
|
||||
std::stringstream sstr;
|
||||
sstr<<"Image"<<i;
|
||||
tImage->setName(sstr.str());
|
||||
}
|
||||
BindlessTexture* tex = new BindlessTexture(buffer,images);
|
||||
attachPnt->setTextureAttribute(0,tex,osg::StateAttribute::ON);
|
||||
attachPnt->setAttributeAndModes(buffer->Binding(), osg::StateAttribute::ON);
|
||||
}
|
||||
///Create a cube centered at the origin, with given by size
|
||||
///
|
||||
osg::Geometry* createCube(float scale, osg::Vec3 origin = osg::Vec3(0.0f,0.0f,0.0f) )
|
||||
{
|
||||
osg::Geometry* geometry = new osg::Geometry;
|
||||
geometry->setName("TexturedCubeArray");
|
||||
|
||||
osg::Vec3Array* vertices = new osg::Vec3Array;
|
||||
geometry->setVertexArray(vertices);
|
||||
|
||||
osg::Vec2Array* tcoords = new osg::Vec2Array();
|
||||
geometry->setTexCoordArray(0,tcoords);
|
||||
|
||||
origin -= osg::Vec3(scale/2.0f,scale/2.0f,scale/2.0f);
|
||||
osg::Vec3 dx(scale,0.0f,0.0f);
|
||||
osg::Vec3 dy(0.0f,scale,0.0f);
|
||||
osg::Vec3 dz(0.0f,0.0f,scale);
|
||||
|
||||
{
|
||||
// front face
|
||||
vertices->push_back(origin);
|
||||
vertices->push_back(origin+dx);
|
||||
vertices->push_back(origin+dx+dz);
|
||||
vertices->push_back(origin+dz);
|
||||
|
||||
tcoords->push_back(osg::Vec2(0.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,1.0f));
|
||||
tcoords->push_back(osg::Vec2(0.0f,1.0f));
|
||||
}
|
||||
|
||||
{
|
||||
// back face
|
||||
vertices->push_back(origin+dy);
|
||||
vertices->push_back(origin+dy+dz);
|
||||
vertices->push_back(origin+dy+dx+dz);
|
||||
vertices->push_back(origin+dy+dx);
|
||||
|
||||
tcoords->push_back(osg::Vec2(0.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,1.0f));
|
||||
tcoords->push_back(osg::Vec2(0.0f,1.0f));
|
||||
}
|
||||
|
||||
{
|
||||
// left face
|
||||
vertices->push_back(origin+dy);
|
||||
vertices->push_back(origin);
|
||||
vertices->push_back(origin+dz);
|
||||
vertices->push_back(origin+dy+dz);
|
||||
|
||||
tcoords->push_back(osg::Vec2(0.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,1.0f));
|
||||
tcoords->push_back(osg::Vec2(0.0f,1.0f));
|
||||
}
|
||||
|
||||
{
|
||||
// right face
|
||||
vertices->push_back(origin+dx+dy);
|
||||
vertices->push_back(origin+dx+dy+dz);
|
||||
vertices->push_back(origin+dx+dz);
|
||||
vertices->push_back(origin+dx);
|
||||
|
||||
tcoords->push_back(osg::Vec2(0.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,1.0f));
|
||||
tcoords->push_back(osg::Vec2(0.0f,1.0f));
|
||||
}
|
||||
|
||||
{
|
||||
// top face
|
||||
vertices->push_back(origin+dz);
|
||||
vertices->push_back(origin+dz+dx);
|
||||
vertices->push_back(origin+dz+dx+dy);
|
||||
vertices->push_back(origin+dz+dy);
|
||||
|
||||
tcoords->push_back(osg::Vec2(0.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,1.0f));
|
||||
tcoords->push_back(osg::Vec2(0.0f,1.0f));
|
||||
}
|
||||
|
||||
{
|
||||
// bottom face
|
||||
vertices->push_back(origin);
|
||||
vertices->push_back(origin+dy);
|
||||
vertices->push_back(origin+dx+dy);
|
||||
vertices->push_back(origin+dx);
|
||||
|
||||
tcoords->push_back(osg::Vec2(0.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,0.0f));
|
||||
tcoords->push_back(osg::Vec2(1.0f,1.0f));
|
||||
tcoords->push_back(osg::Vec2(0.0f,1.0f));
|
||||
}
|
||||
osg::DrawArrays* primSet = new osg::DrawArrays(GL_QUADS, 0, vertices->size());
|
||||
geometry->addPrimitiveSet(primSet);
|
||||
|
||||
return geometry;
|
||||
}
|
||||
///
|
||||
///Here we are going to create our scene, basically we need to make sure our
|
||||
///Bindless texture gets applied before our shader programs.
|
||||
///
|
||||
///
|
||||
osg::Group* CreateScene(){
|
||||
osg::Group* sceneRoot= new osg::Group();
|
||||
sceneRoot->setName("Root");
|
||||
osg::Geode *geo = new osg::Geode();
|
||||
geo->setName("Geo");
|
||||
sceneRoot->addChild(geo);
|
||||
float size = 1.0f;
|
||||
osg::StateSet* scene_ss = sceneRoot->getOrCreateStateSet();
|
||||
createImageArray(scene_ss);
|
||||
scene_ss->setMode(GL_DEPTH_TEST,osg::StateAttribute::ON);
|
||||
|
||||
osg::ref_ptr<osg::Geometry> geom = createCube(0.9f);
|
||||
osg::PrimitiveSet *prim = geom->getPrimitiveSet(0);
|
||||
//instanced elements must use VBOs
|
||||
geom->setUseDisplayList(false);
|
||||
geom->setUseVertexBufferObjects(true);
|
||||
geom->setCullingActive(false);
|
||||
prim->setNumInstances(1000);
|
||||
prim->dirty();
|
||||
sceneRoot->addChild(geo);
|
||||
geo->addDrawable(geom.get());
|
||||
|
||||
osg::StateSet* ss = geo->getOrCreateStateSet();
|
||||
|
||||
std::string strTextureCount;
|
||||
std::stringstream ssconv;
|
||||
ssconv<<TextureCount;
|
||||
ssconv>>strTextureCount;
|
||||
std::string::size_type pos = vertShader.find("XXX");
|
||||
vertShader.replace(pos,size_t(3),strTextureCount);
|
||||
|
||||
pos = fragShader.find("XXX");
|
||||
fragShader.replace(pos,size_t(3),strTextureCount);
|
||||
|
||||
osg::Program* program = new osg::Program;
|
||||
osg::Shader* vertex_shader = new osg::Shader(osg::Shader::VERTEX, vertShader);
|
||||
program->addShader(vertex_shader);
|
||||
|
||||
osg::Shader* fragment_shader = new osg::Shader(osg::Shader::FRAGMENT, fragShader);
|
||||
program->addShader(fragment_shader);
|
||||
|
||||
ss->setAttributeAndModes(program, osg::StateAttribute::ON);
|
||||
|
||||
|
||||
return sceneRoot;
|
||||
}
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// set command line options
|
||||
osg::ArgumentParser arguments(&argc,argv);
|
||||
|
||||
// construct the viewer.
|
||||
osgViewer::Viewer viewer(arguments);
|
||||
|
||||
// add the stats handler
|
||||
viewer.addEventHandler(new osgViewer::StatsHandler);
|
||||
|
||||
// add model to viewer.
|
||||
viewer.setSceneData( CreateScene() );
|
||||
|
||||
viewer.getCamera()->getGraphicsContext()->getState()->setUseModelViewAndProjectionUniforms(true);
|
||||
|
||||
return viewer.run();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,10 @@ class OSG_EXPORT Array : public BufferData
|
||||
MatrixArrayType = 33,
|
||||
MatrixdArrayType = 34,
|
||||
|
||||
QuatArrayType = 35
|
||||
QuatArrayType = 35,
|
||||
|
||||
UInt64ArrayType = 36,
|
||||
Int64ArrayType = 37
|
||||
};
|
||||
|
||||
enum Binding
|
||||
@@ -442,7 +445,8 @@ typedef TemplateArray<Matrixd,Array::MatrixdArrayType,16,GL_DOUBLE>
|
||||
|
||||
typedef TemplateArray<Quat,Array::QuatArrayType,4,GL_DOUBLE> QuatArray;
|
||||
|
||||
|
||||
typedef TemplateIndexArray<GLuint64,Array::UInt64ArrayType,1,GL_UNSIGNED_INT64_ARB> UInt64Array;
|
||||
typedef TemplateIndexArray<GLint64,Array::Int64ArrayType,1,GL_INT64_ARB> Int64Array;
|
||||
class ArrayVisitor
|
||||
{
|
||||
public:
|
||||
@@ -497,6 +501,9 @@ class ArrayVisitor
|
||||
|
||||
virtual void apply(MatrixfArray&) {}
|
||||
virtual void apply(MatrixdArray&) {}
|
||||
|
||||
virtual void apply(UInt64Array&) {}
|
||||
virtual void apply(Int64Array&) {}
|
||||
};
|
||||
|
||||
class ConstArrayVisitor
|
||||
@@ -553,6 +560,9 @@ class ConstArrayVisitor
|
||||
|
||||
virtual void apply(const MatrixfArray&) {}
|
||||
virtual void apply(const MatrixdArray&) {}
|
||||
|
||||
virtual void apply(const UInt64Array&) {}
|
||||
virtual void apply(const Int64Array&) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -610,6 +620,9 @@ class ValueVisitor
|
||||
virtual void apply(Matrixd&) {}
|
||||
|
||||
virtual void apply(Quat&) {}
|
||||
|
||||
virtual void apply(GLuint64&){}
|
||||
virtual void apply(GLint64&){}
|
||||
};
|
||||
|
||||
class ConstValueVisitor
|
||||
@@ -666,6 +679,9 @@ class ConstValueVisitor
|
||||
virtual void apply(const Matrixd&) {}
|
||||
|
||||
virtual void apply(const Quat&) {}
|
||||
|
||||
virtual void apply(const GLuint64&){}
|
||||
virtual void apply(const GLint64&){}
|
||||
};
|
||||
|
||||
template<typename T, Array::Type ARRAYTYPE, int DataSize, int DataType>
|
||||
|
||||
@@ -556,6 +556,14 @@ typedef char GLchar;
|
||||
#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020
|
||||
#endif
|
||||
|
||||
#define GL_INT64_ARB 0x140E
|
||||
#define GL_UNSIGNED_INT64_ARB 0x140F
|
||||
#define GL_INT64_VEC2_ARB 0x8FE9
|
||||
#define GL_INT64_VEC3_ARB 0x8FEA
|
||||
#define GL_INT64_VEC4_ARB 0x8FEB
|
||||
#define GL_UNSIGNED_INT64_VEC2_ARB 0x8FF5
|
||||
#define GL_UNSIGNED_INT64_VEC3_ARB 0x8FF6
|
||||
#define GL_UNSIGNED_INT64_VEC4_ARB 0x8FF7
|
||||
/* ------------------------------ GL_KHR_debug ----------------------------- */
|
||||
#ifndef GL_KHR_debug
|
||||
#define GL_KHR_debug 1
|
||||
@@ -602,7 +610,6 @@ typedef char GLchar;
|
||||
#define GL_DEBUG_OUTPUT 0x92E0
|
||||
|
||||
#endif /* GL_KHR_debug */
|
||||
|
||||
#ifndef GL_ARB_sync
|
||||
#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111
|
||||
#define GL_OBJECT_TYPE 0x9112
|
||||
@@ -653,6 +660,10 @@ typedef char GLchar;
|
||||
#define GL_ALPHA_TEST 0x0BC0
|
||||
#endif
|
||||
|
||||
#ifndef GLuint64EXT
|
||||
typedef uint64_t GLuint64EXT;
|
||||
#endif
|
||||
|
||||
namespace osg
|
||||
{
|
||||
#ifndef GL_VERSION_3_2
|
||||
|
||||
@@ -319,6 +319,22 @@ class OSG_EXPORT GLExtensions : public osg::Referenced
|
||||
void (GL_APIENTRY * glUniform2uiv)( GLint location, GLsizei count, const GLuint *value );
|
||||
void (GL_APIENTRY * glUniform3uiv)( GLint location, GLsizei count, const GLuint *value );
|
||||
void (GL_APIENTRY * glUniform4uiv)( GLint location, GLsizei count, const GLuint *value );
|
||||
void (GL_APIENTRY * glUniform1i64 )(GLint location, GLint64 x) ;
|
||||
void (GL_APIENTRY * glUniform1i64v )(GLint location, GLsizei count, const GLint64* value) ;
|
||||
void (GL_APIENTRY * glUniform1ui64 )(GLint location, GLuint64 x) ;
|
||||
void (GL_APIENTRY * glUniform1ui64v)(GLint location, GLsizei count, const GLuint64* value) ;
|
||||
void (GL_APIENTRY * glUniform2i64 )(GLint location, GLint64 x, GLint64 y) ;
|
||||
void (GL_APIENTRY * glUniform2i64v )(GLint location, GLsizei count, const GLint64* value) ;
|
||||
void (GL_APIENTRY * glUniform2ui64 )(GLint location, GLuint64 x, GLuint64 y) ;
|
||||
void (GL_APIENTRY * glUniform2ui64v)(GLint location, GLsizei count, const GLuint64* value) ;
|
||||
void (GL_APIENTRY * glUniform3i64 )(GLint location, GLint64 x, GLint64 y, GLint64 z) ;
|
||||
void (GL_APIENTRY * glUniform3i64v )(GLint location, GLsizei count, const GLint64* value) ;
|
||||
void (GL_APIENTRY * glUniform3ui64 )(GLint location, GLuint64 x, GLuint64 y, GLuint64 z) ;
|
||||
void (GL_APIENTRY * glUniform3ui64v)(GLint location, GLsizei count, const GLuint64* value) ;
|
||||
void (GL_APIENTRY * glUniform4i64 )(GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w) ;
|
||||
void (GL_APIENTRY * glUniform4i64v )(GLint location, GLsizei count, const GLint64* value) ;
|
||||
void (GL_APIENTRY * glUniform4ui64 )(GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w) ;
|
||||
void (GL_APIENTRY * glUniform4ui64v)(GLint location, GLsizei count, const GLuint64* value) ;
|
||||
GLuint (GL_APIENTRY * glGetHandleARB) (GLenum pname);
|
||||
void (GL_APIENTRY * glGetUniformIndices)(GLuint program, GLsizei uniformCount, const GLchar* *uniformNames, GLuint *uniformIndices);
|
||||
void (GL_APIENTRY * glGetActiveUniformsiv)(GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params);
|
||||
@@ -348,7 +364,11 @@ class OSG_EXPORT GLExtensions : public osg::Referenced
|
||||
void (GL_APIENTRY * glUniformMatrix4x3dv)( GLint location, GLsizei count, GLboolean transpose, const GLdouble* value );
|
||||
void (GL_APIENTRY * glGetActiveAtomicCounterBufferiv)( GLuint program, GLuint bufferIndex, GLenum pname, GLint* params );
|
||||
void (GL_APIENTRY * glDispatchCompute)( GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ );
|
||||
|
||||
GLuint64EXT (GL_APIENTRY* glGetTextureHandle)(GLint texture);
|
||||
void (GL_APIENTRY* glMakeTextureHandleResident)(GLuint64EXT handle);
|
||||
void (GL_APIENTRY* glMakeTextureHandleNonResident)(GLuint64EXT handle);
|
||||
void (GL_APIENTRY* glUniformHandleui64)(GLint location, GLuint64EXT handle);
|
||||
GLboolean (GL_APIENTRY* glIsTextureHandleResident)(GLuint64EXT handle);
|
||||
|
||||
// Buffer Object extensions
|
||||
bool isBufferObjectSupported;
|
||||
|
||||
@@ -439,6 +439,9 @@ class OSG_EXPORT Uniform : public Object
|
||||
BOOL_VEC3 = GL_BOOL_VEC3,
|
||||
BOOL_VEC4 = GL_BOOL_VEC4,
|
||||
|
||||
INT64 = GL_INT64_ARB,
|
||||
UNSIGNED_INT64 = GL_UNSIGNED_INT64_ARB,
|
||||
|
||||
FLOAT_MAT2 = GL_FLOAT_MAT2,
|
||||
FLOAT_MAT3 = GL_FLOAT_MAT3,
|
||||
FLOAT_MAT4 = GL_FLOAT_MAT4,
|
||||
@@ -603,6 +606,8 @@ class OSG_EXPORT Uniform : public Object
|
||||
explicit Uniform( const char* name, int i );
|
||||
explicit Uniform( const char* name, unsigned int ui );
|
||||
explicit Uniform( const char* name, bool b );
|
||||
explicit Uniform( const char* name, unsigned long long ull);
|
||||
explicit Uniform( const char* name, long long ll );
|
||||
Uniform( const char* name, const osg::Vec2& v2 );
|
||||
Uniform( const char* name, const osg::Vec3& v3 );
|
||||
Uniform( const char* name, const osg::Vec4& v4 );
|
||||
@@ -679,6 +684,8 @@ class OSG_EXPORT Uniform : public Object
|
||||
bool set( int i );
|
||||
bool set( unsigned int ui );
|
||||
bool set( bool b );
|
||||
bool set( unsigned long long ull );
|
||||
bool set( long long ll );
|
||||
bool set( const osg::Vec2& v2 );
|
||||
bool set( const osg::Vec3& v3 );
|
||||
bool set( const osg::Vec4& v4 );
|
||||
@@ -719,6 +726,8 @@ class OSG_EXPORT Uniform : public Object
|
||||
bool get( int& i ) const;
|
||||
bool get( unsigned int& ui ) const;
|
||||
bool get( bool& b ) const;
|
||||
bool get( unsigned long long & ull ) const;
|
||||
bool get( long long& ll ) const;
|
||||
bool get( osg::Vec2& v2 ) const;
|
||||
bool get( osg::Vec3& v3 ) const;
|
||||
bool get( osg::Vec4& v4 ) const;
|
||||
@@ -759,6 +768,8 @@ class OSG_EXPORT Uniform : public Object
|
||||
bool setElement( unsigned int index, int i );
|
||||
bool setElement( unsigned int index, unsigned int ui );
|
||||
bool setElement( unsigned int index, bool b );
|
||||
bool setElement( unsigned int index, unsigned long long ull );
|
||||
bool setElement( unsigned int index, long long ll );
|
||||
bool setElement( unsigned int index, const osg::Vec2& v2 );
|
||||
bool setElement( unsigned int index, const osg::Vec3& v3 );
|
||||
bool setElement( unsigned int index, const osg::Vec4& v4 );
|
||||
@@ -799,6 +810,8 @@ class OSG_EXPORT Uniform : public Object
|
||||
bool getElement( unsigned int index, int& i ) const;
|
||||
bool getElement( unsigned int index, unsigned int& ui ) const;
|
||||
bool getElement( unsigned int index, bool& b ) const;
|
||||
bool getElement( unsigned int index, unsigned long long & ull ) const;
|
||||
bool getElement( unsigned int index, long long& ll ) const;
|
||||
bool getElement( unsigned int index, osg::Vec2& v2 ) const;
|
||||
bool getElement( unsigned int index, osg::Vec3& v3 ) const;
|
||||
bool getElement( unsigned int index, osg::Vec4& v4 ) const;
|
||||
@@ -866,7 +879,8 @@ class OSG_EXPORT Uniform : public Object
|
||||
bool setArray( DoubleArray* array );
|
||||
bool setArray( IntArray* array );
|
||||
bool setArray( UIntArray* array );
|
||||
|
||||
bool setArray( UInt64Array* array );
|
||||
bool setArray( Int64Array* array );
|
||||
/** Get the internal data array for a float osg::Uniform. */
|
||||
FloatArray* getFloatArray() { return _floatArray.get(); }
|
||||
const FloatArray* getFloatArray() const { return _floatArray.get(); }
|
||||
@@ -883,6 +897,14 @@ class OSG_EXPORT Uniform : public Object
|
||||
UIntArray* getUIntArray() { return _uintArray.get(); }
|
||||
const UIntArray* getUIntArray() const { return _uintArray.get(); }
|
||||
|
||||
/** Get the internal data array for an unsigned int osg::Uniform. */
|
||||
UInt64Array* getUInt64Array() { return _uint64Array.get(); }
|
||||
const UInt64Array* getUInt64Array() const { return _uint64Array.get(); }
|
||||
|
||||
/** Get the internal data array for an unsigned int osg::Uniform. */
|
||||
Int64Array* getInt64Array() { return _int64Array.get(); }
|
||||
const Int64Array* getInt64Array() const { return _int64Array.get(); }
|
||||
|
||||
inline void setModifiedCount(unsigned int mc) { _modifiedCount = mc; }
|
||||
inline unsigned int getModifiedCount() const { return _modifiedCount; }
|
||||
|
||||
@@ -922,6 +944,8 @@ class OSG_EXPORT Uniform : public Object
|
||||
ref_ptr<DoubleArray> _doubleArray;
|
||||
ref_ptr<IntArray> _intArray;
|
||||
ref_ptr<UIntArray> _uintArray;
|
||||
ref_ptr<Int64Array> _int64Array;
|
||||
ref_ptr<UInt64Array> _uint64Array;
|
||||
|
||||
ref_ptr<UniformCallback> _updateCallback;
|
||||
ref_ptr<UniformCallback> _eventCallback;
|
||||
|
||||
@@ -33,6 +33,7 @@ const int CHAR_SIZE = 1;
|
||||
const int SHORT_SIZE = 2;
|
||||
const int INT_SIZE = 4;
|
||||
const int LONG_SIZE = 4;
|
||||
const int INT64_SIZE = 8;
|
||||
const int FLOAT_SIZE = 4;
|
||||
const int DOUBLE_SIZE = 8;
|
||||
const int GLENUM_SIZE = 4;
|
||||
@@ -71,6 +72,9 @@ const int ID_VEC2UI_ARRAY = 29;
|
||||
const int ID_VEC3UI_ARRAY = 30;
|
||||
const int ID_VEC4UI_ARRAY = 31;
|
||||
|
||||
const int ID_UINT64_ARRAY = 32;
|
||||
const int ID_INT64_ARRAY = 33;
|
||||
|
||||
const int ID_DRAWARRAYS = 50;
|
||||
const int ID_DRAWARRAY_LENGTH = 51;
|
||||
const int ID_DRAWELEMENTS_UBYTE = 52;
|
||||
|
||||
@@ -102,6 +102,8 @@ public:
|
||||
OutputStream& operator<<( unsigned long l ) { _out->writeULong(l); return *this; }
|
||||
OutputStream& operator<<( float f ) { _out->writeFloat(f); return *this; }
|
||||
OutputStream& operator<<( double d ) { _out->writeDouble(d); return *this; }
|
||||
OutputStream& operator<<( long long ll ) { _out->writeInt64(ll); return *this; }
|
||||
OutputStream& operator<<( unsigned long long ull ) { _out->writeUInt64(ull); return *this; }
|
||||
OutputStream& operator<<( const std::string& s ) { _out->writeString(s); return *this; }
|
||||
OutputStream& operator<<( const char* s ) { _out->writeString(s); return *this; }
|
||||
OutputStream& operator<<( std::ostream& (*fn)(std::ostream&) ) { _out->writeStream(fn); return *this; }
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
virtual void writeUInt( unsigned int i ) = 0;
|
||||
virtual void writeLong( long l ) = 0;
|
||||
virtual void writeULong( unsigned long l ) = 0;
|
||||
virtual void writeInt64( int64_t ll ) = 0;
|
||||
virtual void writeUInt64( uint64_t ull ) = 0;
|
||||
virtual void writeFloat( float f ) = 0;
|
||||
virtual void writeDouble( double d ) = 0;
|
||||
virtual void writeString( const std::string& s ) = 0;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <float.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
@@ -640,6 +641,25 @@ GLExtensions::GLExtensions(unsigned int in_contextID):
|
||||
setGLExtensionFuncPtr(glUniform2uiv, "glUniform2uiv", "glUniform2uivEXT", validContext);
|
||||
setGLExtensionFuncPtr(glUniform3uiv, "glUniform3uiv", "glUniform3uivEXT", validContext);
|
||||
setGLExtensionFuncPtr(glUniform4uiv, "glUniform4uiv", "glUniform4uivEXT", validContext);
|
||||
|
||||
// ARB_gpu_shader_int64
|
||||
setGLExtensionFuncPtr(glUniform1i64, "glUniform1i64", "glUniform1i64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform1ui64, "glUniform1ui64", "glUniform1ui64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform2i64, "glUniform2i64", "glUniform2i64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform2ui64, "glUniform2ui64", "glUniform2ui64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform3i64, "glUniform3i64", "glUniform3i64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform3ui64, "glUniform3ui64", "glUniform3ui64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform4i64, "glUniform4i64", "glUniform4i64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform4ui64, "glUniform4ui64", "glUniform4ui64ARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform1i64v, "glUniform1i64v", "glUniform1i64vARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform1ui64v,"glUniform1ui64v","glUniform1ui64vARB",validContext);
|
||||
setGLExtensionFuncPtr(glUniform2i64v, "glUniform2i64v", "glUniform2i64vARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform2ui64v,"glUniform2ui64v","glUniform2ui64vARB",validContext);
|
||||
setGLExtensionFuncPtr(glUniform3i64v, "glUniform3i64v", "glUniform3i64vARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform3ui64v,"glUniform3ui64v","glUniform3ui64vARB",validContext);
|
||||
setGLExtensionFuncPtr(glUniform4i64v, "glUniform4i64v", "glUniform4i64vARB", validContext);
|
||||
setGLExtensionFuncPtr(glUniform4ui64v,"glUniform4ui64v","glUniform4ui64vARB",validContext);
|
||||
|
||||
// ARB_uniform_buffer_object
|
||||
setGLExtensionFuncPtr(glGetUniformIndices, "glGetUniformIndices", validContext);
|
||||
setGLExtensionFuncPtr(glGetActiveUniformsiv, "glGetActiveUniformsiv", validContext);
|
||||
@@ -910,6 +930,13 @@ GLExtensions::GLExtensions(unsigned int in_contextID):
|
||||
maxLayerCount = 0;
|
||||
if (validContext) glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &maxLayerCount);
|
||||
|
||||
// Bindless textures
|
||||
setGLExtensionFuncPtr(glGetTextureHandle, "glGetTextureHandle", "glGetTextureHandleARB","glGetTextureHandleNV", validContext);
|
||||
setGLExtensionFuncPtr(glMakeTextureHandleResident, "glMakeTextureHandleResident", "glMakeTextureHandleResidentARB","glMakeTextureHandleResidentNV", validContext);
|
||||
setGLExtensionFuncPtr(glMakeTextureHandleNonResident, "glMakeTextureHandleNonResident", "glMakeTextureHandleNonResidentARB", "glMakeTextureHandleNonResidentNV",validContext);
|
||||
setGLExtensionFuncPtr(glUniformHandleui64, "glUniformHandleui64", "glUniformHandleui64ARB","glUniformHandleui64NV", validContext);
|
||||
setGLExtensionFuncPtr(glIsTextureHandleResident, "glIsTextureHandleResident","glIsTextureHandleResidentARB", "glIsTextureHandleResidentNV", validContext);
|
||||
|
||||
// Blending
|
||||
isBlendColorSupported = validContext &&
|
||||
(OSG_GLES2_FEATURES || OSG_GL3_FEATURES ||
|
||||
|
||||
@@ -1229,6 +1229,13 @@ bool State::convertVertexShaderSourceToOsgBuiltIns(std::string& source) const
|
||||
declPos = 0;
|
||||
}
|
||||
|
||||
std::string::size_type extPos = source.rfind( "#extension " );
|
||||
if ( extPos != std::string::npos )
|
||||
{
|
||||
// found the string, now find the next linefeed and set the insertion point after it.
|
||||
declPos = source.find( '\n', extPos );
|
||||
declPos = declPos != std::string::npos ? declPos+1 : source.length();
|
||||
}
|
||||
if (_useModelViewAndProjectionUniforms)
|
||||
{
|
||||
// replace ftransform as it only works with built-ins
|
||||
|
||||
@@ -168,6 +168,8 @@ bool Uniform::setArray( FloatArray* array )
|
||||
_doubleArray = 0;
|
||||
_intArray = 0;
|
||||
_uintArray = 0;
|
||||
_int64Array = 0;
|
||||
_uint64Array = 0;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
@@ -187,6 +189,8 @@ bool Uniform::setArray( DoubleArray* array )
|
||||
_floatArray = 0;
|
||||
_intArray = 0;
|
||||
_uintArray = 0;
|
||||
_int64Array = 0;
|
||||
_uint64Array = 0;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
@@ -206,6 +210,8 @@ bool Uniform::setArray( IntArray* array )
|
||||
_floatArray = 0;
|
||||
_doubleArray = 0;
|
||||
_uintArray = 0;
|
||||
_int64Array = 0;
|
||||
_uint64Array = 0;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
@@ -225,6 +231,49 @@ bool Uniform::setArray( UIntArray* array )
|
||||
_floatArray = 0;
|
||||
_doubleArray = 0;
|
||||
_intArray = 0;
|
||||
_int64Array = 0;
|
||||
_uint64Array = 0;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
bool Uniform::setArray( UInt64Array* array )
|
||||
{
|
||||
if( !array ) return false;
|
||||
|
||||
// incoming array must match configuration of the Uniform
|
||||
if( getInternalArrayType(getType())!=GL_UNSIGNED_INT || getInternalArrayNumElements()!=array->getNumElements() )
|
||||
{
|
||||
OSG_WARN << "Uniform::setArray : incompatible array" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
_uint64Array = array;
|
||||
_floatArray = 0;
|
||||
_doubleArray = 0;
|
||||
_intArray = 0;
|
||||
_uintArray = 0;
|
||||
_int64Array =0;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uniform::setArray( Int64Array* array )
|
||||
{
|
||||
if( !array ) return false;
|
||||
|
||||
// incoming array must match configuration of the Uniform
|
||||
if( getInternalArrayType(getType())!=GL_UNSIGNED_INT || getInternalArrayNumElements()!=array->getNumElements() )
|
||||
{
|
||||
OSG_WARN << "Uniform::setArray : incompatible array" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
_int64Array = array;
|
||||
_floatArray = 0;
|
||||
_doubleArray = 0;
|
||||
_intArray = 0;
|
||||
_uintArray = 0;
|
||||
_uint64Array =0;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
@@ -282,6 +331,22 @@ int Uniform::compareData(const Uniform& rhs) const
|
||||
return memcmp( _uintArray->getDataPointer(), rhs._uintArray->getDataPointer(),
|
||||
_uintArray->getTotalDataSize() );
|
||||
}
|
||||
|
||||
else if( _uint64Array.valid() )
|
||||
{
|
||||
if( ! rhs._uint64Array ) return 1;
|
||||
if( _uint64Array == rhs._uint64Array ) return 0;
|
||||
return memcmp( _uint64Array->getDataPointer(), rhs._uint64Array->getDataPointer(),
|
||||
_uint64Array->getTotalDataSize() );
|
||||
}
|
||||
|
||||
else if( _int64Array.valid() )
|
||||
{
|
||||
if( ! rhs._int64Array ) return 1;
|
||||
if( _int64Array == rhs._int64Array ) return 0;
|
||||
return memcmp( _int64Array->getDataPointer(), rhs._int64Array->getDataPointer(),
|
||||
_int64Array->getTotalDataSize() );
|
||||
}
|
||||
|
||||
return -1; // how got here?
|
||||
}
|
||||
@@ -296,6 +361,8 @@ void Uniform::copyData(const Uniform& rhs)
|
||||
if( _doubleArray.valid() && rhs._doubleArray.valid() ) *_doubleArray = *rhs._doubleArray;
|
||||
if( _intArray.valid() && rhs._intArray.valid() ) *_intArray = *rhs._intArray;
|
||||
if( _uintArray.valid() && rhs._uintArray.valid() ) *_uintArray = *rhs._uintArray;
|
||||
if( _int64Array.valid() && rhs._int64Array.valid() ) *_int64Array = *rhs._int64Array;
|
||||
if( _uint64Array.valid() && rhs._uint64Array.valid() ) *_uint64Array = *rhs._uint64Array;
|
||||
dirty();
|
||||
}
|
||||
|
||||
@@ -361,6 +428,9 @@ const char* Uniform::getTypename( Type t )
|
||||
case BOOL_VEC3: return "bvec3";
|
||||
case BOOL_VEC4: return "bvec4";
|
||||
|
||||
case INT64: return "int64_t";
|
||||
case UNSIGNED_INT64: return "uint64_t";
|
||||
|
||||
case FLOAT_MAT2: return "mat2";
|
||||
case FLOAT_MAT3: return "mat3";
|
||||
case FLOAT_MAT4: return "mat4";
|
||||
@@ -473,6 +543,8 @@ int Uniform::getTypeNumComponents( Type t )
|
||||
case INT:
|
||||
case UNSIGNED_INT:
|
||||
case BOOL:
|
||||
case UNSIGNED_INT64:
|
||||
case INT64:
|
||||
|
||||
case SAMPLER_1D:
|
||||
case SAMPLER_2D:
|
||||
@@ -635,6 +707,9 @@ Uniform::Type Uniform::getTypeId( const std::string& tname )
|
||||
if( tname == "bvec3" ) return BOOL_VEC3;
|
||||
if( tname == "bvec4" ) return BOOL_VEC4;
|
||||
|
||||
if( tname == "uint64_t" ) return UNSIGNED_INT64;
|
||||
if( tname == "int64_t" ) return INT64;
|
||||
|
||||
if( tname == "mat2" || tname == "mat2x2" ) return FLOAT_MAT2;
|
||||
if( tname == "mat3" || tname == "mat3x3" ) return FLOAT_MAT3;
|
||||
if( tname == "mat4" || tname == "mat4x4" ) return FLOAT_MAT4;
|
||||
@@ -832,6 +907,12 @@ Uniform::Type Uniform::getGlApiType( Type t )
|
||||
case BOOL_VEC4:
|
||||
return INT_VEC4;
|
||||
|
||||
case UNSIGNED_INT64:
|
||||
return UNSIGNED_INT64;
|
||||
|
||||
case INT64:
|
||||
return INT64;
|
||||
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
@@ -965,6 +1046,12 @@ GLenum Uniform::getInternalArrayType( Type t )
|
||||
case UNSIGNED_INT_VEC4:
|
||||
return GL_UNSIGNED_INT;
|
||||
|
||||
case UNSIGNED_INT64:
|
||||
return GL_UNSIGNED_INT64_ARB;
|
||||
|
||||
case INT64:
|
||||
return GL_INT64_ARB;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -1298,8 +1385,21 @@ Uniform::Uniform( const char* name, bool b0, bool b1, bool b2, bool b3 ) :
|
||||
allocateDataArray();
|
||||
set( b0, b1, b2, b3 );
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Uniform::Uniform( const char* name, unsigned long long ull) :
|
||||
_type(UNSIGNED_INT64), _numElements(1), _modifiedCount(0)
|
||||
{
|
||||
setName(name);
|
||||
allocateDataArray();
|
||||
set( ull );
|
||||
}
|
||||
Uniform::Uniform( const char* name, long long ll) :
|
||||
_type(INT64), _numElements(1), _modifiedCount(0)
|
||||
{
|
||||
setName(name);
|
||||
allocateDataArray();
|
||||
set( ll );
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Value assignment for single-element (ie: non-array) uniforms.
|
||||
// (For backwards compatibility, if not already configured, set the
|
||||
// Uniform's _numElements=1)
|
||||
@@ -1532,6 +1632,17 @@ bool Uniform::set( bool b0, bool b1, bool b2, bool b3 )
|
||||
return isScalar() ? setElement(0,b0,b1,b2,b3) : false;
|
||||
}
|
||||
|
||||
|
||||
bool Uniform::set( unsigned long long ull )
|
||||
{
|
||||
if( getNumElements() == 0 ) setNumElements(1);
|
||||
return isScalar() ? setElement(0,ull) : false;
|
||||
}
|
||||
bool Uniform::set( long long ll )
|
||||
{
|
||||
if( getNumElements() == 0 ) setNumElements(1);
|
||||
return isScalar() ? setElement(0,ll) : false;
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Value query for single-element (ie: non-array) uniforms.
|
||||
|
||||
@@ -1725,6 +1836,14 @@ bool Uniform::get( bool& b0, bool& b1, bool& b2, bool& b3 ) const
|
||||
return isScalar() ? getElement(0,b0,b1,b2,b3) : false;
|
||||
}
|
||||
|
||||
bool Uniform::get( unsigned long long& ull ) const
|
||||
{
|
||||
return isScalar() ? getElement(0,ull) : false;
|
||||
}
|
||||
bool Uniform::get( long long& ll ) const
|
||||
{
|
||||
return isScalar() ? getElement(0,ll) : false;
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Value assignment for array uniforms.
|
||||
|
||||
@@ -2110,7 +2229,22 @@ bool Uniform::setElement( unsigned int index, bool b0, bool b1, bool b2, bool b3
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uniform::setElement( unsigned int index, unsigned long long ull )
|
||||
{
|
||||
if( index>=getNumElements() || !isCompatibleType(UNSIGNED_INT64) ) return false;
|
||||
unsigned int j = index * getTypeNumComponents(getType());
|
||||
(*_uint64Array)[j] = ull;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
bool Uniform::setElement( unsigned int index, long long ll )
|
||||
{
|
||||
if( index>=getNumElements() || !isCompatibleType(INT64) ) return false;
|
||||
unsigned int j = index * getTypeNumComponents(getType());
|
||||
(*_int64Array)[j] = ll;
|
||||
dirty();
|
||||
return true;
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Value query for array uniforms.
|
||||
|
||||
@@ -2422,6 +2556,22 @@ bool Uniform::getElement( unsigned int index, bool& b ) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uniform::getElement( unsigned int index, unsigned long long& ull ) const
|
||||
{
|
||||
if( index>=getNumElements() || !isCompatibleType(UNSIGNED_INT64) ) return false;
|
||||
unsigned int j = index * getTypeNumComponents(getType());
|
||||
ull = ((*_uint64Array)[j] != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uniform::getElement( unsigned int index, long long& ll ) const
|
||||
{
|
||||
if( index>=getNumElements() || !isCompatibleType(INT64) ) return false;
|
||||
unsigned int j = index * getTypeNumComponents(getType());
|
||||
ll = ((*_int64Array)[j] != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Uniform::getElement( unsigned int index, bool& b0, bool& b1 ) const
|
||||
{
|
||||
if( index>=getNumElements() || !isCompatibleType(BOOL_VEC2) ) return false;
|
||||
@@ -2604,6 +2754,19 @@ void Uniform::apply(const GLExtensions* ext, GLint location) const
|
||||
if( _uintArray.valid() ) ext->glUniform4uiv( location, num, &_uintArray->front() );
|
||||
break;
|
||||
|
||||
case UNSIGNED_INT64:
|
||||
if( _uint64Array.valid() ){
|
||||
if (ext->glUniform1ui64v)
|
||||
ext->glUniform1ui64v( location, num, &_uint64Array->front() );
|
||||
else
|
||||
OSG_WARN << "how got here? " __FILE__ ":" << __LINE__ << std::endl;
|
||||
}
|
||||
break;
|
||||
|
||||
case INT64:
|
||||
if( _int64Array.valid() ) ext->glUniform1i64v( location, num, &_int64Array->front() );
|
||||
break;
|
||||
|
||||
default:
|
||||
OSG_FATAL << "how got here? " __FILE__ ":" << __LINE__ << std::endl;
|
||||
break;
|
||||
|
||||
@@ -58,6 +58,8 @@ public:
|
||||
virtual void writeULong( unsigned long l ) { write(l); }
|
||||
virtual void writeFloat( float f ) { write(f); }
|
||||
virtual void writeDouble( double d ) { write(d); }
|
||||
virtual void writeInt64( long long ll ) { write(ll); }
|
||||
virtual void writeUInt64( unsigned long long ull ) { write(ull); }
|
||||
virtual void writeString( const std::string& s ) { _str.insert(_str.end(), s.begin(), s.end()); }
|
||||
virtual void writeStream( std::ostream& (*)(std::ostream&) ) {}
|
||||
virtual void writeBase( std::ios_base& (*)(std::ios_base&) ) {}
|
||||
|
||||
@@ -497,6 +497,34 @@ void DataOutputStream::writeVec4b(const osg::Vec4b& v){
|
||||
|
||||
if (_verboseOutput) std::cout<<"read/writeVec4b() ["<<v<<"]"<<std::endl;
|
||||
}
|
||||
void DataOutputStream::writeUInt64(unsigned long long ull){
|
||||
_ostream->write((char*)&ull, INT64SIZE);
|
||||
|
||||
if (_verboseOutput) std::cout<<"read/writeUInt64() ["<<ull<<"]"<<std::endl;
|
||||
}
|
||||
void DataOutputStream::writeInt64(long long ll){
|
||||
_ostream->write((char*)&ll, INT64SIZE);
|
||||
|
||||
if (_verboseOutput) std::cout<<"read/writeInt64() ["<<ll<<"]"<<std::endl;
|
||||
}
|
||||
void DataOutputStream::writeUInt64Array(const osg::UInt64Array* a){
|
||||
int size = a->getNumElements();
|
||||
writeUInt64(size);
|
||||
for(int i =0; i<size ;i++){
|
||||
writeInt((*a)[i]);
|
||||
}
|
||||
|
||||
if (_verboseOutput) std::cout<<"read/writeUInt64Array() ["<<size<<"]"<<std::endl;
|
||||
}
|
||||
void DataOutputStream::writeInt64Array(const osg::Int64Array* a){
|
||||
int size = a->getNumElements();
|
||||
writeInt64(size);
|
||||
for(int i =0; i<size ;i++){
|
||||
writeInt((*a)[i]);
|
||||
}
|
||||
|
||||
if (_verboseOutput) std::cout<<"read/writeInt64Array() ["<<size<<"]"<<std::endl;
|
||||
}
|
||||
|
||||
void DataOutputStream::writeQuat(const osg::Quat& q){
|
||||
writeFloat(q.x());
|
||||
@@ -589,10 +617,14 @@ void DataOutputStream::writeArray(const osg::Array* a){
|
||||
writeChar((char)16);
|
||||
writeVec3dArray(static_cast<const osg::Vec3dArray*>(a));
|
||||
break;
|
||||
case osg::Array::Vec4dArrayType:
|
||||
case osg::Array::Vec4dArrayType:
|
||||
writeChar((char)17);
|
||||
writeVec4dArray(static_cast<const osg::Vec4dArray*>(a));
|
||||
break;
|
||||
case osg::Array::UInt64ArrayType:
|
||||
writeChar((char)18);
|
||||
writeUInt64Array(static_cast<const osg::UInt64Array*>(a));
|
||||
break;
|
||||
default: throwException("Unknown array type in DataOutputStream::writeArray()");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,11 @@ public:
|
||||
void writeVec2b(const osg::Vec2b& v);
|
||||
void writeVec3b(const osg::Vec3b& v);
|
||||
void writeVec4b(const osg::Vec4b& v);
|
||||
|
||||
|
||||
void writeUInt64(unsigned long long ull);
|
||||
void writeInt64(long long ll);
|
||||
void writeUInt64Array(const osg::UInt64Array* a);
|
||||
void writeInt64Array(const osg::Int64Array* a);
|
||||
void writePackedFloatArray(const osg::FloatArray* a, float maxError);
|
||||
|
||||
void writeFloatArray(const osg::FloatArray* a);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#define FLOATSIZE 4
|
||||
#define LONGSIZE 4
|
||||
#define DOUBLESIZE 8
|
||||
|
||||
#define INT64SIZE 8
|
||||
//Don't know where else to put this
|
||||
namespace ive{
|
||||
|
||||
|
||||
@@ -49,6 +49,12 @@ public:
|
||||
virtual void writeULong( unsigned long l )
|
||||
{ indentIfRequired(); *_out << l << ' '; }
|
||||
|
||||
virtual void writeInt64( long long ll )
|
||||
{ indentIfRequired(); *_out << ll << ' '; }
|
||||
|
||||
virtual void writeUInt64( unsigned long long ull )
|
||||
{ indentIfRequired(); *_out << ull << ' '; }
|
||||
|
||||
virtual void writeFloat( float f )
|
||||
{ indentIfRequired(); *_out << f << ' '; }
|
||||
|
||||
|
||||
@@ -49,6 +49,18 @@ public:
|
||||
_out->write( (char*)&value, osgDB::LONG_SIZE );
|
||||
}
|
||||
|
||||
virtual void writeInt64( int64_t ll )
|
||||
{_out->write( (char*)&ll, osgDB::INT64_SIZE );}
|
||||
|
||||
virtual void writeUInt64( uint64_t ull )
|
||||
{_out->write( (char*)&ull, osgDB::INT64_SIZE );}
|
||||
|
||||
virtual void writeInt( long long ll )
|
||||
{ _out->write( (char*)&ll, osgDB::INT64_SIZE ); }
|
||||
|
||||
virtual void writeUInt( unsigned long long ull )
|
||||
{ _out->write( (char*)&ull, osgDB::INT64_SIZE ); }
|
||||
|
||||
virtual void writeFloat( float f )
|
||||
{ _out->write( (char*)&f, osgDB::FLOAT_SIZE ); }
|
||||
|
||||
|
||||
@@ -59,6 +59,18 @@ public:
|
||||
virtual void writeULong( unsigned long l )
|
||||
{ _sstream << l; addToCurrentNode( _sstream.str() ); _sstream.str(""); }
|
||||
|
||||
virtual void writeUInt64(uint64_t ull)
|
||||
{_sstream << ull; addToCurrentNode( _sstream.str() ); _sstream.str("");}
|
||||
|
||||
virtual void writeInt64(int64_t ll)
|
||||
{_sstream << ll; addToCurrentNode( _sstream.str() ); _sstream.str("");}
|
||||
|
||||
virtual void writeInt( unsigned long long ull )
|
||||
{ _sstream << ull; addToCurrentNode( _sstream.str() ); _sstream.str(""); }
|
||||
|
||||
virtual void writeUInt( long long ll )
|
||||
{ _sstream << ll; addToCurrentNode( _sstream.str() ); _sstream.str(""); }
|
||||
|
||||
virtual void writeFloat( float f )
|
||||
{ _sstream << f; addToCurrentNode( _sstream.str() ); _sstream.str(""); }
|
||||
|
||||
|
||||
@@ -995,6 +995,21 @@ bool Array_writeLocalData(const Array& array,Output& fw)
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case(Array::UInt64ArrayType):
|
||||
{
|
||||
fw<<array.className()<<" "<<array.getNumElements()<<std::endl;
|
||||
const UInt64Array::ElementDataType* base = static_cast<const UInt64Array::ElementDataType*>(array.getDataPointer());
|
||||
writeArray(fw,&base[0], &base[array.getNumElements()]);
|
||||
return true;
|
||||
}
|
||||
case(Array::Int64ArrayType):
|
||||
{
|
||||
fw<<array.className()<<" "<<array.getNumElements()<<std::endl;
|
||||
const Int64Array::ElementDataType* base = static_cast<const Int64Array::ElementDataType*>(array.getDataPointer());
|
||||
writeArray(fw,&base[0], &base[array.getNumElements()]);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case(Array::ArrayType):
|
||||
default:
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user