//info : osgSSBO example,testing ShaderStorageBufferObjects ,Markus Hein,  2014, osg-3.2.1
//required hardware and driver must support GL >=  GL 4.3 or GL ES 3.1 (GL ES not tested, would be nice if someone will test it on a small device)

//testing osg support for Shader Storage Buffer Objects

//version: "first take" from last night session..



#include <osg/StateAttributeCallback>
#include <osg/Texture2D>
#include <osg/Geometry>
#include <osg/Geode>
#include <osgDB/ReadFile>
#include <osgGA/StateSetManipulator>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>

#include <osg/Node>
#include <osg/PositionAttitudeTransform>
#include <osg/Geometry>
#include <osg/Notify>
#include <osg/MatrixTransform>
#include <osg/Texture2D>
#include <osg/TextureRectangle>
#include <osg/Stencil>
#include <osg/Depth>
#include <osg/Billboard>
#include <osg/Material>
#include <osg/AnimationPath>

#include <osgGA/TrackballManipulator>
#include <osgGA/FlightManipulator>
#include <osgGA/DriveManipulator>

#include <osgUtil/SmoothingVisitor>

#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Renderer>


#include <osg/Array>
#include <osg/BoundingSphere>
#include <osg/BufferIndexBinding>
#include <osg/BufferObject>
#include <osg/Group>
#include <osg/Math>
#include <osg/MatrixTransform>
#include <osg/Program>
#include <osg/Shader>
#include <osg/Drawable>
#include <osg/CopyOp>
#include <osg/State>
#include <osg/Matrix>
#include <osg/ShapeDrawable>
#include <osg/GL>
#include <osg/StateSet>
#include <osg/Texture2D>
#include <osg/BlendFunc>
#include <osg/TexEnv>
#include <osg/Material>
#include <osg/PointSprite>
#include <osg/Program>
#include <osg/Notify>
#include <osg/Point>
#include <osg/io_utils>
#include <osg/VertexProgram>

#include <osgText/Font>
#include <osgText/Text>


#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgDB/FileNameUtils>
#include <osgUtil/Optimizer>
#include <iostream>
#include <typeinfo>


using namespace osg;


//todo .. #define COMPUTATION_IN_SEPARATE_THREAD

#define WORK_GROUP_SIZE 16


#define PRERENDER_ANTIALIASINGMULTISAMPLES 16
#define PRERENDER_HIGH_QUALITY_ANTIALIASING
#define PRERENDER_WIDTH 1920
#define PRERENDER_HEIGHT 1080


#define SUB_PLACEMENT_OFFSET_HORIZONTAL  0.5
#define SUB_PLACEMENT_OFFSET_VERTICAL  0.5

enum BufferOffset
{
    POSITION_NOW_OFFSET,
    POSITION_OLD_OFFSET,
    POSITION_INIT_OFFSET,

    VELOCITY_NOW_OFFSET,
    VELOCITY_OLD_OFFSET,
    VELOCITY_INIT_OFFSET,

    ACCELERATION_OFFSET,
    PROPERTIES_OFFSET,

    OFFSET_END
};


const int __numDataValuesPerChannel = OFFSET_END;
const int __numChannels = 4;

//512x512x4x7 = 7.340.032 floats in SSBO on GPU
const int NUM_ELEMENTS_X = 512;
const int NUM_ELEMENTS_Y = 512;

float random(float min, float max) { return min + (max - min)*(float)rand() / (float)RAND_MAX; }


enum Channel
{
    RED_CHANNEL,
    GREEN_CHANNEL,
    BLUE_CHANNEL,
    ALPHA_CHANNEL,
    RGB_CHANNEL,
    RGBA_CHANNEL
};



class ShaderStorageBufferCallback : public osg::StateAttributeCallback
{
public:
    void operator() (osg::StateAttribute* attr, osg::NodeVisitor* nv)
    {
        //if you need to process the data in your app-code , better leaving it on GPU and processing there, uploading per frame will make it slow
#if 0
        osg::ShaderStorageBufferBinding* ssbb = static_cast<osg::ShaderStorageBufferBinding*>(attr);
        osg::ShaderStorageBufferObject* ssbo
            = static_cast<osg::ShaderStorageBufferObject*>(ssbb->getBufferObject());

        osg::FloatArray* array = static_cast<osg::FloatArray*>(ssbo->getBufferData(0));

        float someValue = array->at(0);
        //std::cout << "someValue now: " << someValue << std::endl;
        //data transfer performance test
        //    array->dirty();
#endif
    }
};


//do not forget to set OSG_FILE_PATH to default OSG-Data and make sure the new shaders are copied there under"shaders"
class ComputeNode : public osg::PositionAttitudeTransform
{

public:

    osg::ref_ptr<osg::Program>                        _computeProgram;
    osg::ref_ptr<osg::Shader>                        _computeShader;        //compute and write position data in SSBO

    osg::ref_ptr<osg::Shader>                        _vertexShader;        //reading position data from SSBO (OBS!: make sure glMemoryBuffer() is syncing this)
    osg::ref_ptr<osg::Shader>                        _geometryShader;    //building a quad looking to the camera
    osg::ref_ptr<osg::Shader>                        _fragmentShader;    //use false-colors etc. for making your data visible

    osg::ref_ptr<osg::Node>                            _helperNode;        // coordinate system node

    ref_ptr<osg::ShaderStorageBufferObject>            _ssbo;
    ref_ptr<osg::ShaderStorageBufferBinding>        _ssbb;

    GLfloat*                                        _data;        // some data we upload to GPU, initialised with random values
    osg::ref_ptr<FloatArray>                        _dataArray; //

    osg::ref_ptr<osg::Group>                        _computationResultsRenderGroup;
    osg::ref_ptr<osg::Program>                        _computationResultsRenderProgram;
    osg::ref_ptr<osg::StateSet>                        _computationResultsRenderStateSet;


    std::string                                        _computeShaderSourcePath;
    std::string                                        _vertexShaderSourcePath;
    std::string                                        _geometryShaderSourcePath;
    std::string                                        _fragmentShaderSourcePath;


    void                addHelperGeometry();
    void                addDataMonitor(osg::Vec3 placement, osg::Vec3 relativePlacement, float scale, Channel channel, BufferOffset shaderBufferOffset, std::string labelcaption, float minDataRange, float maxDataRange);
    void                addComputationResultsRenderTree();
    void                initComputingSetup();

    ComputeNode()
    {
        const char* envOsgFilePath = getenv("OSG_FILE_PATH");
        std::stringstream computeshaderpath; computeshaderpath << envOsgFilePath << "/shaders/osgssboComputeShader.cs";
        _computeShaderSourcePath = computeshaderpath.str();
        std::stringstream vertexshaderpath; vertexshaderpath << envOsgFilePath << "/shaders/osgssboVertexShader.vs";
        _vertexShaderSourcePath = vertexshaderpath.str();
        std::stringstream geometryshaderpath; geometryshaderpath << envOsgFilePath << "/shaders/osgssboGeometryShader.gs";
        _geometryShaderSourcePath = geometryshaderpath.str();
        std::stringstream fragmentshaderpath; fragmentshaderpath << envOsgFilePath << "/shaders/osgssboFragmentShader.fs";
        _fragmentShaderSourcePath = fragmentshaderpath.str();
    }

};


class ComputeNodeUpdateCallback : public osg::NodeCallback
{
public:

    ComputeNode* _computeNode;
    osg::Timer_t _prevShaderUpdateTime;
    osg::Timer _timer;

    ComputeNodeUpdateCallback(){}

    ComputeNodeUpdateCallback(ComputeNode* computeNode)
    {
        _computeNode = computeNode;
        _prevShaderUpdateTime = 0;
    }

    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
    {
        osg::Timer_t currTime = _timer.tick();

        if (_timer.delta_s(_prevShaderUpdateTime, currTime) > 1.0) //one  second interval for shader-changed-do-reload check
        {
            osg::ref_ptr<osg::Shader> reloadedshader;
            std::string runningSource;
            std::string reloadedstring;

            if (_computeNode->_computeShader.valid())
            {
                runningSource = _computeNode->_computeShader->getShaderSource();
                reloadedshader = osg::Shader::readShaderFile(osg::Shader::COMPUTE, _computeNode->_computeShaderSourcePath);

                reloadedstring = reloadedshader->getShaderSource();
                if (!osgDB::equalCaseInsensitive(runningSource.c_str(), reloadedstring.c_str()))
                {
                    _computeNode->_computeProgram->removeShader(_computeNode->_computeShader.get());
                    _computeNode->_computeShader = reloadedshader.get();
                    _computeNode->_computeProgram->addShader(_computeNode->_computeShader.get());
                }
            }

            if (_computeNode->_vertexShader.valid())
            {

                runningSource = _computeNode->_vertexShader->getShaderSource();
                reloadedshader = osg::Shader::readShaderFile(osg::Shader::VERTEX, _computeNode->_vertexShaderSourcePath);

                reloadedstring = reloadedshader->getShaderSource();
                if (!osgDB::equalCaseInsensitive(runningSource.c_str(), reloadedstring.c_str()))
                {
                    _computeNode->_computationResultsRenderProgram->removeShader(_computeNode->_vertexShader.get());
                    _computeNode->_vertexShader = reloadedshader.get();
                    _computeNode->_computationResultsRenderProgram->addShader(_computeNode->_vertexShader.get());
                }
            }



            if (_computeNode->_geometryShader.valid())
            {
                runningSource = _computeNode->_geometryShader->getShaderSource();
                reloadedshader = osg::Shader::readShaderFile(osg::Shader::GEOMETRY, _computeNode->_geometryShaderSourcePath);

                reloadedstring = reloadedshader->getShaderSource();
                if (!osgDB::equalCaseInsensitive(runningSource.c_str(), reloadedstring.c_str()))
                {
                    _computeNode->_computationResultsRenderProgram->removeShader(_computeNode->_geometryShader.get());
                    _computeNode->_geometryShader = reloadedshader.get();
                    _computeNode->_computationResultsRenderProgram->addShader(_computeNode->_geometryShader.get());
                }
            }

            if (_computeNode->_fragmentShader.valid())
            {
                runningSource = _computeNode->_fragmentShader->getShaderSource();
                reloadedshader = osg::Shader::readShaderFile(osg::Shader::FRAGMENT, _computeNode->_fragmentShaderSourcePath);

                reloadedstring = reloadedshader->getShaderSource();
                if (!osgDB::equalCaseInsensitive(runningSource.c_str(), reloadedstring.c_str()))
                {
                    _computeNode->_computationResultsRenderProgram->removeShader(_computeNode->_fragmentShader.get());
                    _computeNode->_fragmentShader = reloadedshader.get();
                    _computeNode->_computationResultsRenderProgram->addShader(_computeNode->_fragmentShader.get());
                }
            }


            _prevShaderUpdateTime = _timer.tick();
        }

        traverse(node, nv);

    }
};

//set  OSG_FILE_PATH for loading axes.osgt
void ComputeNode::addHelperGeometry()
{
    _helperNode = osgDB::readNodeFile("axes.osgt");

    if (_helperNode.valid())
    {
        addChild(_helperNode.get());
    }

    //osg::PositionAttitudeTransform* pat = new osg::PositionAttitudeTransform;
    //pat->setPosition(osg::Vec3(0.5, 0, 0.5));
    //osg::Geode *sphereGeode = new osg::Geode;
    //float radius = 0.5f;
    //osg::TessellationHints* hints = new osg::TessellationHints;
    //hints->setDetailRatio(0.9f);
    //osg::ShapeDrawable* sphere = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0.0f, 0.0f, 0.0f), radius), hints);
    //sphereGeode->addDrawable(sphere);
    //sphere->setColor(osg::Vec4(0, 1, 0, 0.1));
    //osg::StateSet* stateset = sphereGeode->getOrCreateStateSet();
    //osg::BlendFunc *blend = new osg::BlendFunc;
    //blend->setFunction(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
    //stateset->setAttributeAndModes(blend, osg::StateAttribute::ON);
    //pat->addChild(sphereGeode);
    //addChild(pat);
}






void ComputeNode::addDataMonitor(osg::Vec3 placement, osg::Vec3 relativePlacement, float scale, Channel colorchannel, BufferOffset shaderStorageBufferOffset, std::string labelCaption, float minDataRange, float maxDataRange)
{
    osg::PositionAttitudeTransform* pat = new osg::PositionAttitudeTransform;
    pat->setPosition(relativePlacement);
    addChild(pat);
    osg::Geometry* geom;

    if (NUM_ELEMENTS_X >= NUM_ELEMENTS_Y)
    {
        float ratio = (float)((float)NUM_ELEMENTS_Y / (float)NUM_ELEMENTS_X);
        geom = osg::createTexturedQuadGeometry(placement, osg::Vec3(1.0f*scale, 0.0f, 0.0f), osg::Vec3(0.0f, 0.0f, ratio*1.0f*scale));
    }
    else
    {
        float ratio = (float)((float)NUM_ELEMENTS_X / (float)NUM_ELEMENTS_Y);
        geom = osg::createTexturedQuadGeometry(placement, osg::Vec3(ratio*1.0f*scale, 0.0f, 0.0f), osg::Vec3(0.0f, 0.0f, 1.0f*scale));

    }

    geom->setVertexAttribArray(1, geom->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX);

    osg::ref_ptr<osg::Geode> quad = new osg::Geode;
    quad->addDrawable(geom);
    quad->setStateSet(getOrCreateStateSet());
    pat->addChild(quad.get());

    static const char* vertexShaderSrcChannelMonitor = {

        "#version 430  \n"

        "uniform int numRows;\n"
        "uniform int numCols;\n"
        "uniform float osg_FrameTime;\n"
        "uniform mat4 osg_ProjectionMatrix;\n"
        "uniform mat4 osg_ModelViewMatrix;\n"
        "out vec2 texCoordFromVertexShader;\n"
        "struct particle{ float    x; float y; float z; float w;};"
        "layout (location = 0) in vec3 vertexpos;\n"
        "attribute vec2 tex_coords;\n"
        "void main() {\n"
        "texCoordFromVertexShader.xy = tex_coords.xy;  gl_Position = ( osg_ProjectionMatrix * osg_ModelViewMatrix * vec4(vertexpos.x,vertexpos.y,vertexpos.z,1) ); \n"
        "}\n"
    };




    std::stringstream fragmentshaderstringstreamChannelMonitor;
    fragmentshaderstringstreamChannelMonitor << "#version 430\n";
    fragmentshaderstringstreamChannelMonitor << "uniform int numRows;\n";
    fragmentshaderstringstreamChannelMonitor << "uniform int numCols;\n";
    fragmentshaderstringstreamChannelMonitor << "uniform float dataRangeMin;\n";
    fragmentshaderstringstreamChannelMonitor << "uniform float dataRangeMax;\n";
    fragmentshaderstringstreamChannelMonitor << "in vec2 texCoordFromVertexShader;\n";
    fragmentshaderstringstreamChannelMonitor << "struct particle{ float    x; float y; float z; float w;};";
    fragmentshaderstringstreamChannelMonitor << "layout(std140, binding=0) coherent buffer particles{particle p[];}; ";
    fragmentshaderstringstreamChannelMonitor << "\n";
    fragmentshaderstringstreamChannelMonitor << "void main(void)\n";
    fragmentshaderstringstreamChannelMonitor << "{\n";
    fragmentshaderstringstreamChannelMonitor << "ivec2 storePos = ivec2(numRows*texCoordFromVertexShader.x, numCols*texCoordFromVertexShader.y);  particle particleData = p[" << shaderStorageBufferOffset * NUM_ELEMENTS_X*NUM_ELEMENTS_Y << " + (storePos.x*numRows + storePos.y)]; ";

    //fragmentshaderstringstreamChannelMonitor << " memoryBarrierBuffer(); \n";
    fragmentshaderstringstreamChannelMonitor << " float dataRangeMultiplier = 1.0 / abs(dataRangeMax - dataRangeMin); \n";

    switch (colorchannel)
    {
    case RED_CHANNEL:
    {
        fragmentshaderstringstreamChannelMonitor << "   vec4 color; color.x =  0.5+dataRangeMultiplier*particleData.x; color.y =0.0; color.z = 0.0; color.w = 1.0; gl_FragColor = color;\n";

        break;
    }
    case GREEN_CHANNEL:
    {
        fragmentshaderstringstreamChannelMonitor << "   vec4 color; color.x = 0.0; color.y = 0.5+dataRangeMultiplier*particleData.y; color.z = 0.0; color.w = 1.0; gl_FragColor = color;\n";
        break;
    }
    case BLUE_CHANNEL:
    {
        fragmentshaderstringstreamChannelMonitor << "   vec4 color; color.x = 0.0; color.y = 0.0; color.z = 0.5+dataRangeMultiplier*particleData.z;  color.w = 0.0 ; gl_FragColor = color;\n";
        break;
    }
    case ALPHA_CHANNEL:
    {
        fragmentshaderstringstreamChannelMonitor << "   vec4 color; color.x = 0.5+dataRangeMultiplier*particleData.w; color.y = 0.5+dataRangeMultiplier*particleData.w; color.z = 0.5+dataRangeMultiplier*particleData.w; color.w = 0.5+0.5*particleData.w; gl_FragColor = color;\n";
        break;
    }

    case RGB_CHANNEL:
    {
        fragmentshaderstringstreamChannelMonitor << "   vec4 color; color.x = 0.5+dataRangeMultiplier*particleData.x; color.y = 0.5+dataRangeMultiplier*particleData.y; color.z = 0.5+dataRangeMultiplier*particleData.z; color.w = 1.0; gl_FragColor = color;\n";
        break;
    }

    case RGBA_CHANNEL:
    {
        fragmentshaderstringstreamChannelMonitor << "   vec4 color; color.x = 0.5+dataRangeMultiplier*particleData.x; color.y = 0.5+dataRangeMultiplier*particleData.y; color.z = 0.5+dataRangeMultiplier*particleData.z; color.w = 0.5+0.5*particleData.w; gl_FragColor = color;\n";
        break;
    }

    }

    fragmentshaderstringstreamChannelMonitor << "}\n";




    osg::Program * program = new osg::Program;
    program->addShader(new osg::Shader(osg::Shader::VERTEX, vertexShaderSrcChannelMonitor));
    program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragmentshaderstringstreamChannelMonitor.str().c_str()));
    program->addBindAttribLocation("tex_coords", 1);

    osg::StateSet* ss = geom->getOrCreateStateSet();
    ss->setAttributeAndModes(program, osg::StateAttribute::ON);
    ss->addUniform(new osg::Uniform("numRows", (int)NUM_ELEMENTS_X));
    ss->addUniform(new osg::Uniform("numCols", (int)NUM_ELEMENTS_Y));

    ss->addUniform(new osg::Uniform("dataRangeMin", (float)minDataRange));
    ss->addUniform(new osg::Uniform("dataRangeMax", (float)maxDataRange));


    ss->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

    //add a label
    osgText::Text* text = new osgText::Text;
    osgText::Font* font = osgText::readFontFile("fonts/arial.ttf");
    text->setFont(font);
    text->setColor(osg::Vec4(1, 1, 1, 1));
    text->setCharacterSize(0.1*scale);
    text->setPosition(placement + osg::Vec3(0.05, 0.05, 0));
    pat->setName(labelCaption);
    text->setText(pat->getName());
    text->setBackdropType(osgText::Text::OUTLINE);
    text->setBackdropImplementation(osgText::Text::POLYGON_OFFSET);
    text->setBackdropOffset(0.05f);
    text->setBackdropColor(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f));

    quad->addDrawable(text);

    pat->addChild(quad.get());

}

//compute texture image , taken from osgspotlight
osg::Image* createSpotLightImage(const osg::Vec4& centerColour, const osg::Vec4& backgroudColour, unsigned int size, float power)
{
    osg::Image* image = new osg::Image;
    image->allocateImage(size, size, 1,
        GL_RGBA, GL_UNSIGNED_BYTE);


    float mid = (float(size) - 1)*0.5f;
    float div = 2.0f / float(size);
    for (unsigned int r = 0; r < size; ++r)
    {
        unsigned char* ptr = image->data(0, r, 0);
        for (unsigned int c = 0; c < size; ++c)
        {
            float dx = (float(c) - mid)*div;
            float dy = (float(r) - mid)*div;
            float r = powf(1.0f - sqrtf(dx*dx + dy*dy), power);
            if (r < 0.0f) r = 0.0f;
            osg::Vec4 color = centerColour*r + backgroudColour*(1.0f - r);
            *ptr++ = (unsigned char)((color[0])*255.0f);
            *ptr++ = (unsigned char)((color[1])*255.0f);
            *ptr++ = (unsigned char)((color[2])*255.0f);
            *ptr++ = (unsigned char)((color[3])*255.0f);
        }
    }
    return image;
}


void ComputeNode::addComputationResultsRenderTree()
{

    _computationResultsRenderProgram = new osg::Program;

    _vertexShader = osg::Shader::readShaderFile(osg::Shader::VERTEX, _vertexShaderSourcePath);
    _computationResultsRenderProgram->addShader(_vertexShader.get());

    _geometryShader = osg::Shader::readShaderFile(osg::Shader::GEOMETRY, _geometryShaderSourcePath);
    _computationResultsRenderProgram->addShader(_geometryShader.get());

    _fragmentShader = osg::Shader::readShaderFile(osg::Shader::FRAGMENT, _fragmentShaderSourcePath);
    _computationResultsRenderProgram->addShader(_fragmentShader.get());


    _computationResultsRenderProgram->addBindAttribLocation("tex_coords", 1);

    _computationResultsRenderGroup = new osg::Group;
    _computationResultsRenderGroup->setDataVariance(osg::Object::DYNAMIC);
    _computationResultsRenderStateSet = _computationResultsRenderGroup->getOrCreateStateSet();
    _computationResultsRenderStateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);

    osg::PointSprite *sprite = new osg::PointSprite;
    int texture_unit = 0;
    _computationResultsRenderStateSet->setTextureAttributeAndModes(texture_unit, sprite, osg::StateAttribute::ON);
    _computationResultsRenderStateSet->setAttributeAndModes(_computationResultsRenderProgram.get(), osg::StateAttribute::ON);
    _computationResultsRenderStateSet->addUniform(new osg::Uniform("particleTexture", texture_unit));
    _computationResultsRenderStateSet->addUniform(new osg::Uniform("numRows", (int)NUM_ELEMENTS_X));
    _computationResultsRenderStateSet->addUniform(new osg::Uniform("numCols", (int)NUM_ELEMENTS_Y));


    _computationResultsRenderStateSet->setMode(GL_POINT_SMOOTH, osg::StateAttribute::ON);
    _computationResultsRenderStateSet->setMode(GL_VERTEX_PROGRAM_POINT_SIZE_ARB, osg::StateAttribute::ON);
    _computationResultsRenderStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON);
    _computationResultsRenderStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

    osg::Texture2D *tex = new osg::Texture2D();

    osg::Image* particleImage = createSpotLightImage(osg::Vec4(1, 0, 0, 1), osg::Vec4(0.5, 0, 0, 0.0), 32, 0.7);
    if (particleImage)
    {
        tex->setImage(particleImage);
    }
    _computationResultsRenderStateSet->setTextureAttributeAndModes(texture_unit, tex, osg::StateAttribute::ON);


    osg::BlendFunc *blend = new osg::BlendFunc;
    if (false) //emissive particles
    {
        blend->setFunction(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE);
    }
    else
    {
        blend->setFunction(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
    }

    _computationResultsRenderStateSet->setAttributeAndModes(blend, osg::StateAttribute::ON);


    osg::Depth* depth = new osg::Depth;
    depth->setRange(0.0f, 0.0f);
    depth->setFunction(osg::Depth::ALWAYS);
    depth->setWriteMask(false);
    depth->setFunction(osg::Depth::ALWAYS);

    _computationResultsRenderStateSet->setAttributeAndModes(depth, osg::StateAttribute::OFF);


    osg::Geode* particleGeode = new osg::Geode;
    unsigned int numVertices = NUM_ELEMENTS_X*NUM_ELEMENTS_Y;

    osg::Geometry* particleGeometry = new osg::Geometry;
    particleGeometry->setUseDisplayList(false);
    particleGeometry->setUseVertexBufferObjects(true);

    osg::Vec3Array* vertexarray = new osg::Vec3Array;
    osg::Vec2Array* tcoords = new osg::Vec2Array;

    osg::Vec2 bottom_texcoord(0.0f, 0.0f);

    osg::Vec2 dx_texcoord(1.0f / (float)(NUM_ELEMENTS_X), 0.0f);
    osg::Vec2 dy_texcoord(0.0f, 1.0f / (float)(NUM_ELEMENTS_Y));



    for (int i = 0; i < NUM_ELEMENTS_X; i++)
    {
        osg::Vec2 texcoord = bottom_texcoord + dy_texcoord*(float)i;

        for (int j = 0; j < NUM_ELEMENTS_Y; j++)
        {
            vertexarray->push_back(osg::Vec3(texcoord.x(), texcoord.y(), 0.0));
            tcoords->push_back(osg::Vec2(texcoord.x(), texcoord.y()));
            texcoord += dx_texcoord;
        }
    }

    particleGeometry->setVertexArray(vertexarray);
    particleGeometry->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, numVertices));
    particleGeometry->setTexCoordArray(0, tcoords);
    //this glMemoryBarrier thing... not sure if we could better do instanced drawing?  all the data is in Shader Storage Buffer..
    particleGeometry->setVertexAttribArray(1, particleGeometry->getTexCoordArray(0), osg::Array::BIND_PER_VERTEX);

    _computationResultsRenderGroup->addChild(particleGeode);
    particleGeode->addDrawable(particleGeometry);

    addChild(_computationResultsRenderGroup.get());

}


void ComputeNode::initComputingSetup()
{

    _computeProgram = new osg::Program;
    _computeProgram->setComputeGroups((NUM_ELEMENTS_X / WORK_GROUP_SIZE) <= 1 ? 1 : (NUM_ELEMENTS_X / WORK_GROUP_SIZE), (NUM_ELEMENTS_Y / WORK_GROUP_SIZE) <= 1 ? 1 : (NUM_ELEMENTS_Y / WORK_GROUP_SIZE), 1);
    _computeShader = osg::Shader::readShaderFile(osg::Shader::COMPUTE, _computeShaderSourcePath);
    _computeProgram->addShader(_computeShader.get());

    setDataVariance(osg::Object::DYNAMIC);
    osg::StateSet* statesetComputation = getOrCreateStateSet();
    statesetComputation->setAttributeAndModes(_computeProgram.get());
    statesetComputation->addUniform(new osg::Uniform("numCols", (int)NUM_ELEMENTS_X));
    statesetComputation->addUniform(new osg::Uniform("numRows", (int)NUM_ELEMENTS_Y));
    statesetComputation->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

    //blocksize
    int  numParticles = NUM_ELEMENTS_X * NUM_ELEMENTS_Y;
    const unsigned blockSize = numParticles * __numChannels * __numDataValuesPerChannel* sizeof(GLfloat);

    //init all the particle data array
    int idx = 0;
    _data = new GLfloat[NUM_ELEMENTS_X  * NUM_ELEMENTS_Y  * __numChannels * __numDataValuesPerChannel];
    _dataArray = new FloatArray;

    //init the data array  somehow, this way all is stored in one BufferObject. maybe better using multiple buffers instead? not sure what is faster and better for threading
    for (int d = 0; d < __numDataValuesPerChannel; ++d)
    {
        for (int i = 0; i < NUM_ELEMENTS_X; ++i)
        {

            for (int j = 0; j < NUM_ELEMENTS_Y; ++j)
            {

                for (int k = 0; k < __numChannels; ++k)
                {
                    switch (k)
                    {

                    case (RED_CHANNEL) :
                    {
                        if ((d == POSITION_NOW_OFFSET) || (d == POSITION_OLD_OFFSET) || (d == POSITION_INIT_OFFSET))//position
                        {
                            *_data = random(0.25, 0.75);
                        }
                        if ((d == VELOCITY_NOW_OFFSET) || (d == VELOCITY_OLD_OFFSET) || (d == VELOCITY_INIT_OFFSET))//velocity
                        {
                            *_data = random(-2.4, 2.4);
                        }
                        if (d == ACCELERATION_OFFSET) //acceleration
                        {
                            *_data = random(-3.0, 3.0);
                        }

                        if (d == PROPERTIES_OFFSET) //property particle mass (compute shader is computing sphere mass from radius instead)
                        {
                            *_data = random(0.2, 15.0);
                        }

                        break;
                    }

                    case (GREEN_CHANNEL) :
                    {
                        if ((d == POSITION_NOW_OFFSET) || (d == POSITION_OLD_OFFSET) || (d == POSITION_INIT_OFFSET))//position
                        {
                            *_data = random(0.25, 0.75);
                        }
                        if ((d == VELOCITY_NOW_OFFSET) || (d == VELOCITY_OLD_OFFSET) || (d == VELOCITY_INIT_OFFSET))//velocity
                        {
                            *_data = random(-2.4, 2.4);
                        }

                        if (d == ACCELERATION_OFFSET)//acceleration
                        {
                            *_data = random(-3.0, 3.0);
                        }
                        if (d == PROPERTIES_OFFSET) //property particle radius
                        {
                            *_data = random(0.07, 0.219);
                        }

                        break;
                    }

                    case (BLUE_CHANNEL) :
                    {
                        if ((d == POSITION_NOW_OFFSET) || (d == POSITION_OLD_OFFSET) || (d == POSITION_INIT_OFFSET))//position
                        {
                            *_data = random(0.25, 0.75);
                        }
                        if ((d == VELOCITY_NOW_OFFSET) || (d == VELOCITY_OLD_OFFSET) || (d == VELOCITY_INIT_OFFSET))//velocity
                        {
                            *_data = random(-2.4, 2.4);
                        }

                        if (d == ACCELERATION_OFFSET)//acceleration
                        {
                            *_data = random(-3.0, 3.0);
                        }


                        if (d == PROPERTIES_OFFSET)  //place for some other property
                        {
                            *_data = random(0.0, 0.0);
                        }

                        break;
                    }

                    case (ALPHA_CHANNEL) :
                    {
                        if ((d == POSITION_NOW_OFFSET) || (d == POSITION_OLD_OFFSET) || (d == POSITION_INIT_OFFSET))//position
                        {
                            *_data = random(1.0, 1.0);
                        }
                        if ((d == VELOCITY_NOW_OFFSET) || (d == VELOCITY_OLD_OFFSET) || (d == VELOCITY_INIT_OFFSET))//velocity
                        {
                            *_data = random(-2.4, 2.4);
                        }

                        if (d == ACCELERATION_OFFSET) //acceleration
                        {
                            //*_data = random(1.0, 1.0);
                            *_data = random(0.0, 0.0);
                        }

                        if (d == PROPERTIES_OFFSET) //place for some other property
                        {
                            *_data = random(0.3, 0.3);
                        }

                        break;
                    }



                    }
                    _dataArray->push_back(*_data);
                    _data++;
                    idx++;
                }
            }
        }
    }

    _ssbo = new osg::ShaderStorageBufferObject;
    _dataArray->setBufferObject(_ssbo.get());


    _ssbb = new osg::ShaderStorageBufferBinding(0, _ssbo.get(), 0, blockSize);
    statesetComputation->setAttributeAndModes(_ssbb.get(), osg::StateAttribute::ON);


    //option, do something useful with data or test the transfer speed
    //_ssbb->setUpdateCallback(new ShaderStorageBufferCallback);

    //adding a quad , visualizing data in buffer
    addDataMonitor(osg::Vec3(0, -1, 0), osg::Vec3(SUB_PLACEMENT_OFFSET_HORIZONTAL * 0, -SUB_PLACEMENT_OFFSET_VERTICAL * -2.0, SUB_PLACEMENT_OFFSET_HORIZONTAL * 0), 1.0, RGB_CHANNEL, POSITION_NOW_OFFSET, "X,Y,Z - PositionNow", -1.0, 1.0);

    //the coord from default dataset
    addHelperGeometry();


    addComputationResultsRenderTree();

}


//taken from osgdistorsion example for getting it nice on screen with antialiasing
osg::Node* createPrerenderSubgraph(osg::Node* subgraph, const osg::Vec4& clearColour)
{
    osg::Group* prerenderNode = new osg::Group;

    unsigned int tex_width = PRERENDER_WIDTH;
    unsigned int tex_height = PRERENDER_HEIGHT;

    osg::Texture2D* texture = new osg::Texture2D;
    texture->setTextureSize(tex_width, tex_height);
    texture->setInternalFormat(GL_RGBA);
    texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
    texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);

    {
        osg::Camera* prerenderCamera = new osg::Camera;
        prerenderCamera->setClearColor(clearColour);
        prerenderCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        prerenderCamera->setReferenceFrame(osg::Transform::RELATIVE_RF);
        prerenderCamera->setProjectionMatrix(osg::Matrixd::identity());
        prerenderCamera->setViewMatrix(osg::Matrixd::identity());
        prerenderCamera->setViewport(0, 0, tex_width, tex_height);
        prerenderCamera->setRenderOrder(osg::Camera::PRE_RENDER);
        prerenderCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
        prerenderCamera->attach(osg::Camera::COLOR_BUFFER0, texture, 0, 0, false, PRERENDER_ANTIALIASINGMULTISAMPLES, PRERENDER_ANTIALIASINGMULTISAMPLES);
        prerenderCamera->addChild(subgraph);
        prerenderNode->addChild(prerenderCamera);

    }

    {
        osg::Geometry* polyGeom = new osg::Geometry();

        polyGeom->setSupportsDisplayList(false);

        osg::Vec3 origin(0.0f, 0.0f, 0.0f);
        osg::Vec3 xAxis(1.0f, 0.0f, 0.0f);
        osg::Vec3 yAxis(0.0f, 1.0f, 0.0f);

        float height = 1024.0f;
        float width = 1280.0f;
        int noSteps = 3;

        osg::Vec3Array* vertices = new osg::Vec3Array;
        osg::Vec2Array* texcoords = new osg::Vec2Array;
        osg::Vec4Array* colors = new osg::Vec4Array;

        osg::Vec3 bottom = origin;
        osg::Vec3 dx = xAxis*(width / ((float)(noSteps - 1)));
        osg::Vec3 dy = yAxis*(height / ((float)(noSteps - 1)));

        osg::Vec2 bottom_texcoord(0.0f, 0.0f);
        osg::Vec2 dx_texcoord(1.0f / (float)(noSteps - 1), 0.0f);
        osg::Vec2 dy_texcoord(0.0f, 1.0f / (float)(noSteps - 1));

        int i, j;
        for (i = 0; i < noSteps; ++i)
        {
            osg::Vec3 cursor = bottom + dy*(float)i;
            osg::Vec2 texcoord = bottom_texcoord + dy_texcoord*(float)i;
            for (j = 0; j < noSteps; ++j)
            {
                vertices->push_back(cursor);
                texcoords->push_back(osg::Vec2((sin(texcoord.x()*osg::PI - osg::PI*0.5) + 1.0f)*0.5f, (sin(texcoord.y()*osg::PI - osg::PI*0.5) + 1.0f)*0.5f));
                colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
                cursor += dx;
                texcoord += dx_texcoord;
            }
        }

        polyGeom->setVertexArray(vertices);
        polyGeom->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
        polyGeom->setTexCoordArray(0, texcoords);

        for (i = 0; i < noSteps - 1; ++i)
        {
            osg::DrawElementsUShort* elements = new osg::DrawElementsUShort(osg::PrimitiveSet::QUAD_STRIP);
            for (j = 0; j < noSteps; ++j)
            {
                elements->push_back(j + (i + 1)*noSteps);
                elements->push_back(j + (i)*noSteps);
            }
            polyGeom->addPrimitiveSet(elements);
        }

        osg::StateSet* stateset = polyGeom->getOrCreateStateSet();
        stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
        stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

        osg::Geode* geode = new osg::Geode();
        geode->addDrawable(polyGeom);

        osg::Camera* nestedRenderCamera = new osg::Camera;
        nestedRenderCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
        nestedRenderCamera->setViewMatrix(osg::Matrix::identity());
        nestedRenderCamera->setProjectionMatrixAsOrtho2D(0, 1280, 0, 1024);
        nestedRenderCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
        nestedRenderCamera->addChild(geode);

        prerenderNode->addChild(nestedRenderCamera);
    }

    return prerenderNode;
}




int main(int argc, char** argv)
{
    osg::ArgumentParser arguments(&argc, argv);

    osgViewer::Viewer viewer;

    osg::ref_ptr<osg::Group> scene = new osg::Group;


    viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));
    viewer.addEventHandler(new osgViewer::StatsHandler);
    viewer.addEventHandler(new osgViewer::WindowSizeHandler);
    viewer.addEventHandler(new osgViewer::ThreadingHandler);
    viewer.getCamera()->setProjectionMatrixAsPerspective(60.0f, 1.33333, 0.01, 100.0);
    viewer.setCameraManipulator(new osgGA::TrackballManipulator());

    viewer.setUpViewInWindow(11, 11, 800 + 11, 600 + 11);
    //viewer.setUpViewOnSingleScreen(0); // !!

    viewer.getCamera()->setClearColor(osg::Vec4(0.3, 0.3, 0.3, 1.0));
    viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);// we can play with threading models later

    osg::ref_ptr<ComputeNode> computeNode = new ComputeNode();
    computeNode->setPosition(osg::Vec3(0, 0, 0));
    computeNode->setUpdateCallback(new ComputeNodeUpdateCallback(computeNode.get())); // on-the-fly reloading the shaders if shader source on disk is changed
    computeNode->initComputingSetup();



    scene->addChild(computeNode.get());
    scene->addChild(computeNode->_computationResultsRenderGroup.get());


#ifdef PRERENDER_HIGH_QUALITY_ANTIALIASING
    viewer.setSceneData(createPrerenderSubgraph(scene.get(), osg::Vec4(0.3, 0.4, 0.6, 1)));
#else
    viewer.setSceneData(scene.get());
#endif

    viewer.realize();

    viewer.getCamera()->getGraphicsContext()->getState()->setUseModelViewAndProjectionUniforms(true);

    viewer.run();

    return 1;
}
