Writing apps directly to RenderSystem API

Discussion area about developing or extending OGRE, adding plugins for it or building applications on it. No newbie questions please, use the Help forum for that.
Post Reply
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Writing apps directly to RenderSystem API

Post by sparkprime »

Why would anyone want to do this? This is not really relevant to this post but I'm sure some people will not be satisfied until they have an answer, and those people can (according to their tastes) consider some weighted sum of the following reasons:
  • Curiosity
  • Better understanding of Ogre architecture
  • Implementing new rendering pipelines on top of RenderSystem
  • Test beds for new render systems
  • Performance tests of existing render systems

I tried to write a hello world app to this API because I could not find one anywhere. I've tried to strip down Ogre as much as possible (i.e. use only the fundamentals), e.g. there is no use of scripts, meshes, scene management, plugins, etc. I'm using a slightly out of date (and forked) ogre but I don't think that should make a major difference at this point. Here is the code. Can anyone comment on it?


Things I noticed:

Rendering in a loop to an inactive render target causes a serious memory leak that will instantly kill your computer. This was fixed by guarding the code with a test to isActive().

One cannot omit Root. This is due to static methods on Root being called from the render system layer and expecting the root singleton to be initialised. Also, it is the only class allowed to set isPrimary() on the first window. This seems like a design error.


Questions:

Should viewport be considered part of the render system layer, or can one render without it? My gut is that it is not necessary, but I got an exception if I tried to render without setting a current viewport.


Future directions:

I'd like to omit Root entirely from these experiments.

I'd like to be able to write down the list of classes that provide the fundamental ogre rendering capability (on which the higher level functionality is based).

I'd like to render something more sophisiticated, with e.g. textures, shaders, instancing, etc. A more complete use of the API.

I'd like to work out what subset of the rendersystem API corresponds to a shader-only pipeline, and consider a superclass of RenderSystem that does not have these methods.

Code: Select all

#define _POSIX_C_SOURCE 199309
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <cassert>

#include <sstream>

#include <OgreRoot.h>
#include <OgreRenderSystem.h>
#include <OgreRenderWindow.h>
#include <OgreVector3.h>
#include <OgreQuaternion.h>
#include "OgreGLRenderSystem.h"
#include "OgreWindowEventUtilities.h"
#ifdef WIN32
#  include "OgreD3D9RenderSystem.h"
#endif

Ogre::RenderWindow *ogre_win;
Ogre::Viewport *ogre_vp;
Ogre::Root *ogre_root;

bool clicked_close = false;

struct WindowEventListener : Ogre::WindowEventListener {

    void windowClosed (Ogre::RenderWindow *rw)
    {
        (void)rw;
        clicked_close = true;
    }

} wel;


void mysleep(long micros)
{
        if (micros<=0) return;
        struct timespec t = {0,micros*1000};
        int r = nanosleep(&t, NULL);
        if (r) {
                perror("sleep");
        }
}

int main (void)
{

    try {

        Ogre::LogManager *lmgr = OGRE_NEW Ogre::LogManager();
        Ogre::Log *ogre_log = OGRE_NEW Ogre::Log("",false,true);
        lmgr->setDefaultLog(ogre_log);
        lmgr->setLogDetail(Ogre::LL_NORMAL);
    
        #ifdef WIN32
        bool use_d3d9 = getenv("OGRE_DIRECT_GL")==NULL;
        #else 
        bool use_d3d9 = false;
        #endif

        Ogre::RenderSystem *rs;
        if (use_d3d9) {
            #ifdef WIN32
            rs = OGRE_NEW Ogre::D3D9RenderSystem();
            rs->setConfigOption("Allow NVPerfHUD","Yes");
            rs->setConfigOption("Floating-point mode","Consistent");
            rs->setConfigOption("Video Mode","800 x 600 @ 32-bit colour");
            #endif
        } else {
            rs = OGRE_NEW Ogre::GLRenderSystem();
            rs->setConfigOption("RTT Preferred Mode","FBO");
            rs->setConfigOption("Video Mode","800 x 600");
        }
        rs->setConfigOption("Full Screen","No");
        rs->setConfigOption("VSync","Yes");

        ogre_root = OGRE_NEW Ogre::Root("","","");
        ogre_root->setRenderSystem(rs);
        ogre_win = ogre_root->initialise(true, "Ogre Direct Window");
        ogre_win->setDeactivateOnFocusChange(false);
        Ogre::WindowEventUtilities::addWindowEventListener(ogre_win, &wel);

        ogre_vp = ogre_win->addViewport(NULL, 1, 0,0,1,1);
        ogre_vp->setBackgroundColour(Ogre::ColourValue(0.0f, 0.0f, 0.5f));

        Ogre::VertexData vdata;
        unsigned triangles = 1;
        unsigned vdecl_size = 0;
        vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
        vdecl_size += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
                
        Ogre::HardwareVertexBufferSharedPtr vbuf =
            Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
                vdecl_size, 3*triangles, 
                Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
                    
        vdata.vertexBufferBinding->setBinding(0, vbuf);
        vdata.vertexStart = 0;
        vdata.vertexCount = 3*triangles;

                
        float data[] = { 0.25f, 0.25f, 0.25f,
                         0.75f, 0.25f, 0.25f,
                         0.75f, 0.75f, 0.25f };
        vbuf->writeData(0, 3*triangles*vdecl_size, &data[0]);


        while (!clicked_close) {
            Ogre::WindowEventUtilities::messagePump();

            rs->_setViewport(ogre_vp);

            // necessary for memory leak
            if (ogre_win->isActive()) {

                rs->_beginFrame();

                rs->clearFrameBuffer(Ogre::FBT_COLOUR | Ogre::FBT_DEPTH | Ogre::FBT_STENCIL, Ogre::ColourValue(0, 0, 0.5));

                rs->setLightingEnabled(true);
                rs->setAmbientLight(0.25, 0.25, 0.25);

                rs->_setWorldMatrix(Ogre::Matrix4::IDENTITY);
                rs->_setViewMatrix(Ogre::Matrix4::IDENTITY);
                rs->_setProjectionMatrix(Ogre::Matrix4::IDENTITY);
                rs->_setCullingMode(Ogre::CULL_CLOCKWISE);

                //rs->_disableTextureUnitsFrom(0);

                rs->_setSurfaceParams(Ogre::ColourValue::White, Ogre::ColourValue::Red, Ogre::ColourValue::Green, Ogre::ColourValue::ZERO, 10.0f);

                // render something
                Ogre::RenderOperation op;
                op.useIndexes = false;
                op.vertexData = &vdata;
                op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
                rs->_render(op);
                rs->_endFrame();

                rs->_swapAllRenderTargetBuffers(false);
            } else {
                mysleep(10000);
            }
        }


    } catch (Ogre::Exception &e) {
        std::cerr << e.getFullDescription() << std::endl;
    }

    return EXIT_SUCCESS;
}

// vim: shiftwidth=4:tabstop=4:expandtab
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

This is only tested in linux, it would need to be ported to windows (it uses nanosleep). I can provide code if anyone wants to do that.

I did include some code for d3d9 but this is untested as I haven't tried setting those defines.

I'm only interested in using ogre to abstract d3d9 (probably in future d3d11), and gl at this point.
User avatar
Klaim
Old One
Posts: 2565
Joined: Sun Sep 11, 2005 1:04 am
Location: Paris, France
x 56
Contact:

Re: Writing apps directly to RenderSystem API

Post by Klaim »

I applaud at this experiment, it is quite interesting (to me at least).

I was always thinking that rendersystems were fully isolated, but I never checked that...
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

Here is an updated version. It renders a quad using an index buffer (i.e. 4 vertexes), and uses a GLSL shader. Won't work on D3D.

Code: Select all

#define _POSIX_C_SOURCE 199309
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <cassert>

#include <sstream>

#include <OgreRoot.h>
#include "OgreWindowEventUtilities.h"

#include <OgreVector3.h>
#include <OgreQuaternion.h>

#include <OgreRenderSystem.h>
#include <OgreGLRenderSystem.h>
#ifdef WIN32
#  include <OgreD3D9RenderSystem.h>
#endif
#include <OgreRenderWindow.h>
#include <OgreHighLevelGpuProgramManager.h>
#include <OgreGpuProgramParams.h>
#include <OgreGpuProgram.h>

Ogre::RenderWindow *ogre_win;
Ogre::Viewport *ogre_vp;
Ogre::Root *ogre_root;

bool clicked_close = false;

struct WindowEventListener : Ogre::WindowEventListener {

    void windowClosed (Ogre::RenderWindow *rw)
    {
        (void)rw;
        clicked_close = true;
    }

} wel;


void mysleep(long micros)
{
        if (micros<=0) return;
        struct timespec t = {0,micros*1000};
        int r = nanosleep(&t, NULL);
        if (r) {
                perror("sleep");
        }
}

void prog_check (const Ogre::HighLevelGpuProgramPtr &gp)
{
        if (!gp->isLoaded()) {
            std::cout << "Not loaded: \""+gp->getName()+"\"\n" << std::endl;
        }

        if (!gp->isSupported()) {
            std::cout << "Not supported: \""+gp->getName()+"\"\n" << std::endl;
        }

        if (gp->hasCompileError()) {
            std::cout << "Compile error in \""+gp->getName()+"\"\n" << std::endl;
        }

        //std::cout << "Source code of \""+gp->getName()+"\"\n" << gp->getSource() << std::endl;
        //gp->_getBindingDelegate()->load();
        //std::cout << "Target code of \""+gp->_getBindingDelegate()->getName()+"\"\n" << gp->_getBindingDelegate()->getSource() << std::endl;
}

int main (void)
{

    try {

        Ogre::LogManager *lmgr = OGRE_NEW Ogre::LogManager();
        Ogre::Log *ogre_log = OGRE_NEW Ogre::Log("",true,true);
        lmgr->setDefaultLog(ogre_log);
        lmgr->setLogDetail(Ogre::LL_NORMAL);
    
        #ifdef WIN32
        bool use_d3d9 = getenv("OGRE_DIRECT_GL")==NULL;
        #else 
        bool use_d3d9 = false;
        #endif

        Ogre::RenderSystem *rs;
        if (use_d3d9) {
            #ifdef WIN32
            rs = OGRE_NEW Ogre::D3D9RenderSystem();
            rs->setConfigOption("Allow NVPerfHUD","Yes");
            rs->setConfigOption("Floating-point mode","Consistent");
            rs->setConfigOption("Video Mode","800 x 600 @ 32-bit colour");
            #endif
        } else {
            rs = OGRE_NEW Ogre::GLRenderSystem();
            rs->setConfigOption("RTT Preferred Mode","FBO");
            rs->setConfigOption("Video Mode","800 x 600");
        }
        rs->setConfigOption("Full Screen","No");
        rs->setConfigOption("VSync","Yes");

        ogre_root = OGRE_NEW Ogre::Root("","","");
        ogre_root->setRenderSystem(rs);
        ogre_win = ogre_root->initialise(true, "Ogre Direct Window");
        ogre_win->setDeactivateOnFocusChange(false);
        Ogre::WindowEventUtilities::addWindowEventListener(ogre_win, &wel);

        ogre_vp = ogre_win->addViewport(NULL, 1, 0,0,1,1);
        ogre_vp->setBackgroundColour(Ogre::ColourValue(0.0f, 0.0f, 0.5f));

        unsigned vertexes = 4;
        unsigned triangles = 2;

        Ogre::VertexData vdata;
        unsigned vdecl_size = 0;
        vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
        vdecl_size += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
                
        Ogre::HardwareVertexBufferSharedPtr vbuf =
            Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
                vdecl_size, vertexes, 
                Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
                    
        vdata.vertexBufferBinding->setBinding(0, vbuf);
        vdata.vertexStart = 0;
        vdata.vertexCount = vertexes;
        // 2 3   y
        // 0 1   ->x
        float vdata_raw[] = { 0.25f, 0.25f, 0.25f,
                              0.75f, 0.25f, 0.25f,
                              0.25f, 0.75f, 0.25f,
                              0.75f, 0.75f, 0.25f
                            };
        vbuf->writeData(0, vertexes*vdecl_size, &vdata_raw[0]);

        Ogre::IndexData idata;
        Ogre::HardwareIndexBufferSharedPtr ibuf =
            Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
                Ogre::HardwareIndexBuffer::IT_16BIT, 3*triangles, 
                Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
                    
        idata.indexBuffer = ibuf;
        idata.indexStart = 0;
        idata.indexCount = 3*triangles;
        uint16_t idata_raw[] = { 0, 1, 2,   1, 3, 2 };
        ibuf->writeData(0, 3*triangles*2, &idata_raw[0]);

        const char *vertex_program_code = 
            "void main()\n"
            "{\n"
            "    gl_Position = gl_Vertex;\n"
            "}\n";

        const char *fragment_program_code =
            "void main()\n"
            "{\n"
            "    gl_FragColor = vec4(1,0.5,0,1);\n"
            "}\n";

        Ogre::HighLevelGpuProgramPtr vp = Ogre::HighLevelGpuProgramManager::getSingleton()
            .createProgram("MyVertexProgram", "General", "glsl", Ogre::GPT_VERTEX_PROGRAM);

        Ogre::HighLevelGpuProgramPtr fp = Ogre::HighLevelGpuProgramManager::getSingleton()
            .createProgram("MyFragmentProgram", "General", "glsl", Ogre::GPT_FRAGMENT_PROGRAM);

        vp->setSource(vertex_program_code);
        fp->setSource(fragment_program_code);

        vp->load();
        fp->load();

        Ogre::GpuProgramParametersSharedPtr vertex_params = vp->createParameters();
        Ogre::GpuProgramParametersSharedPtr fragment_params = fp->createParameters();

        prog_check(vp);
        prog_check(fp);

        while (!clicked_close) {
            Ogre::WindowEventUtilities::messagePump();

            rs->_setViewport(ogre_vp);

            // necessary for memory leak
            if (ogre_win->isActive()) {

                rs->_beginFrame();

                rs->clearFrameBuffer(Ogre::FBT_COLOUR | Ogre::FBT_DEPTH | Ogre::FBT_STENCIL, Ogre::ColourValue(0, 0, 0.5));

                rs->_setWorldMatrix(Ogre::Matrix4::IDENTITY);
                rs->_setViewMatrix(Ogre::Matrix4::IDENTITY);
                rs->_setProjectionMatrix(Ogre::Matrix4::IDENTITY);
                rs->_setCullingMode(Ogre::CULL_CLOCKWISE);

                rs->bindGpuProgram(vp->_getBindingDelegate());
                rs->bindGpuProgramParameters(Ogre::GPT_VERTEX_PROGRAM, vertex_params, Ogre::GPV_ALL);
                rs->bindGpuProgram(fp->_getBindingDelegate());
                rs->bindGpuProgramParameters(Ogre::GPT_FRAGMENT_PROGRAM, fragment_params, Ogre::GPV_ALL);

                //rs->_disableTextureUnitsFrom(0);

                // render something
                Ogre::RenderOperation op;
                op.useIndexes = true;
                op.vertexData = &vdata;
                op.indexData = &idata;
                op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
                rs->_render(op);

                rs->_endFrame();

                rs->_swapAllRenderTargetBuffers(true);
            } else {
                mysleep(10000);
            }
        }


    } catch (Ogre::Exception &e) {
        std::cerr << e.getFullDescription() << std::endl;
    }

    return EXIT_SUCCESS;
}

// vim: shiftwidth=4:tabstop=4:expandtab
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

I was confused for a long time because I could not use Ogre::GPUProgramManager to make a glsl program. It silently gave me a null program that didn't do anything. I have to use Ogre::HighLevelGPUProgramManager and a different API, but only for glsl. I actually consider glsl to be a low level shader language at this point because it is not portable to d3d. I intend to manually compile cg shaders to either glsl or sm3 and then use either Ogre::GPUProgramManager or Ogre::HighLevelGPUManager if the render system is d3d9 or gl respectively. This seems to be pretty bad design :(
bstone
OGRE Expert User
OGRE Expert User
Posts: 1920
Joined: Sun Feb 19, 2012 9:24 pm
Location: Russia
x 201

Re: Writing apps directly to RenderSystem API

Post by bstone »

sparkprime wrote:This seems to be pretty bad design :(
Someone has to learn to blame his pretty bad hands first before blaming the design. Ogre::GpuProgramManager is a base (even abstract by intent) class that lays the foundation for specific implementations that derive from it. The latter are provided by rendering systems. You can't use the base abstract class by itself and expect anything better than a null gpu program. It's not a pretty bad design. It's the lack of common sense and minimal knowledge in the area of OOP design at the same time.

On the original topic. I don't think it would be wise to expect that the rendering systems in Ogre are totally decoupled from the core (i.e. Root). They have been developed for Ogre after all. What is the purpose of trading off performance and simplicity by making them completely detached? I think there's none.
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

GpuProgramManager is the intended interface for building gpu programs. You do not get a null program if you use this interface except in GL if you use an unrecognised syntax (in this case, GLSL). The subclasses are rendersystem specific and using them directly would be unportable.

The problem is that GLSL is treated as a high level language unlike the long list of asm languages and has its own GpuProgramManager with an incompatible interface. Obviously the implementation would have to be different for glsl shaders but it would seem to make sense to expose the same interface. Otherwise I have to branch for different shading languages which is the problem the interface is trying to solve in the first place. This probably made sense at some point, but since the asms are also 'high level' (in that they are not the target language) and on ATI, at least on linux, glsl is the only option, it seems silly to maintain this distinction. There is also the irritation that Cg cannot be used with non-nvidia GL at this time, which is related to this point.

I don't see any evidence that detaching Ogre::Root would hurt performance. However it is used in many places (mainly for frivolous reasons, like getting the render system) and there would have to be some reasonable work to remove it (even for just GL and D3D9). That time would probably better be spent improving Ogre in other ways. But it is still nice to acknowledge where improvements can be made.
User avatar
Kojack
OGRE Moderator
OGRE Moderator
Posts: 7157
Joined: Sun Jan 25, 2004 7:35 am
Location: Brisbane, Australia
x 534

Re: Writing apps directly to RenderSystem API

Post by Kojack »

There is also the irritation that Cg cannot be used with non-nvidia GL at this time, which is related to this point.
I assume you mean in linux, because cg on ati gl works fine for me in windows.
bstone
OGRE Expert User
OGRE Expert User
Posts: 1920
Joined: Sun Feb 19, 2012 9:24 pm
Location: Russia
x 201

Re: Writing apps directly to RenderSystem API

Post by bstone »

sparkprime wrote:The problem is that GLSL is treated as a high level language unlike the long list of asm languages and has its own GpuProgramManager with an incompatible interface."
No, you got it all wrong. GLSL is a high level language and as such it is not supported by Ogre::GLGpuProgramManager (which derives from Ogre::GpuProgramManager) because this class is for creating low level (assembler) programs. HIgh level GLSL programs are supported by the GL render system via an implementation of Ogre::HighLevelGpuProgram (see Ogre::GLSLProgram) which is registered with Ogre::HighLevelGpuProgramManager using the respective factory (see Ogre::GLSLProgramFactory).

As for removing Ogre::Root I think there's a tendency of voting for that without giving any amount of thought in the process (similar to the anti-singleton rage). Dragging loosely typed structures and/or pointers to bloated facades throughout the code most of the time inevitably infected with multi-step initialization has never done any good to making code simple and easy to maintain.
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

Kojack wrote:
There is also the irritation that Cg cannot be used with non-nvidia GL at this time, which is related to this point.
I assume you mean in linux, because cg on ati gl works fine for me in windows.
i don't think i ever tried on windows as all of my users just use D3D9. How does it work? What target do you use?

edit: i should say that I cannot use arbvp1,arbfp1 because of the register limits etc
Last edited by sparkprime on Sun Jun 17, 2012 11:28 am, edited 1 time in total.
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

bstone wrote:
sparkprime wrote:The problem is that GLSL is treated as a high level language unlike the long list of asm languages and has its own GpuProgramManager with an incompatible interface."
No, you got it all wrong. GLSL is a high level language and as such it is not supported by Ogre::GLGpuProgramManager (which derives from Ogre::GpuProgramManager) because this class is for creating low level (assembler) programs. HIgh level GLSL programs are supported by the GL render system via an implementation of Ogre::HighLevelGpuProgram (see Ogre::GLSLProgram) which is registered with Ogre::HighLevelGpuProgramManager using the respective factory (see Ogre::GLSLProgramFactory).
I know how it works, I had to debug through it all to find out what was going on, and besides, I've been hacking Ogre on/off for 5 years. The obvious problem with the current implementation is the lack of any error message. The problem with the design is that at the end of the day, shaders are shaders. I don't see any benefit in having two interfaces to do the same thing. I understand the history, but these days the distinction between asm and glsl is not very relevant, whereas the distinction between cg and glsl+asms (i.e., portability between rendersystems) is very real.
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

Here is an update. It renders 4096 spinning cubes using a procedurally generated texture and glsl shaders. Still no support for d3d. I next want to try using hardware instancing at the RS level.

edit: Added simple directional lighting to shader in order to demonstrate normals, also added more comments and did some cleanup

Code: Select all

#define _POSIX_C_SOURCE 199309
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <cassert>

#include <sstream>

#ifdef WIN32
#include <windows.h>
#endif

#include <OgreRoot.h>
#include "OgreWindowEventUtilities.h"

#include <OgreVector3.h>
#include <OgreQuaternion.h>

#include <OgreRenderSystem.h>
#include <OgreGLRenderSystem.h>
#ifdef WIN32
#  include <OgreD3D9RenderSystem.h>
#endif
#include <OgreRenderWindow.h>
#include <OgreHighLevelGpuProgramManager.h>
#include <OgreGpuProgramParams.h>
#include <OgreGpuProgram.h>
#include <OgreHardwarePixelBuffer.h>

bool clicked_close = false;

struct WindowEventListener : Ogre::WindowEventListener {

    void windowClosed (Ogre::RenderWindow *rw)
    {
        (void)rw;
        clicked_close = true;
    }

} wel;


void mysleep(long micros)
{
        if (micros<=0) return;
        #ifdef WIN32
        long millis = micros/1000;
        Sleep(millis);
        #else
        struct timespec t = {0,micros*1000};
        int r = nanosleep(&t, NULL);
        if (r) {
                perror("sleep");
        }
        #endif
}

int main (void)
{

    try {

        // Specify how much logging we want and where we want it to go
        Ogre::LogManager *lmgr = OGRE_NEW Ogre::LogManager();
        Ogre::Log *ogre_log = OGRE_NEW Ogre::Log("",true,true);
        lmgr->setDefaultLog(ogre_log);
        lmgr->setLogDetail(Ogre::LL_NORMAL);
    
        // Default to d3d9 on windows unles env var used
        #ifdef WIN32
        bool use_d3d9 = getenv("OGRE_DIRECT_GL")==NULL;
        #else 
        bool use_d3d9 = false;
        #endif

        Ogre::RenderSystem *rs;
        if (use_d3d9) {
            #ifdef WIN32
            rs = OGRE_NEW Ogre::D3D9RenderSystem();
            rs->setConfigOption("Allow NVPerfHUD","Yes");
            rs->setConfigOption("Floating-point mode","Consistent");
            rs->setConfigOption("Video Mode","800 x 600 @ 32-bit colour");
            #endif
        } else {
            rs = OGRE_NEW Ogre::GLRenderSystem();
            rs->setConfigOption("RTT Preferred Mode","FBO");
            rs->setConfigOption("Video Mode","800 x 600");
        }
        rs->setConfigOption("Full Screen","No");
        rs->setConfigOption("VSync","Yes");

        // Necessary to use Root because it is the only class allowed to _setPrimary on a window
        Ogre::Root *ogre_root = OGRE_NEW Ogre::Root("","","");
        ogre_root->setRenderSystem(rs);
        Ogre::RenderWindow *ogre_win = ogre_root->initialise(true, "Ogre Direct Window");
        ogre_win->setDeactivateOnFocusChange(false);
        Ogre::WindowEventUtilities::addWindowEventListener(ogre_win, &wel);

        // Viewports are mandatory when rendering, but use them here in a minimal fashion
        Ogre::Viewport *ogre_vp = ogre_win->addViewport(NULL, 1, 0,0,1,1);

        // each cube face has 4 vertexes, 2 triangles
        // we do not share vertexes between adjacent faces because we want sharp normals / colour transitions
        const unsigned vertexes = 6 * 4;
        const unsigned triangles = 6 * 2;

        // Prepare vertex buffer
        Ogre::VertexData vdata;
        unsigned vdecl_size = 0;
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT3, Ogre::VES_POSITION).getSize();
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0).getSize();
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT3, Ogre::VES_NORMAL).getSize();
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE).getSize();
        struct VDataRaw { float p[3], uv[2]; Ogre::Vector3 n; uint32_t col; }; // careful with padding here, all prims are 4 bytes long
        Ogre::HardwareVertexBufferSharedPtr vbuf =
            Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
                vdecl_size, vertexes, Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
        vdata.vertexBufferBinding->setBinding(0, vbuf);
        vdata.vertexStart = 0;
        vdata.vertexCount = vertexes;
        // VET_COLOUR requires conversion by rendersystem to appropriate format
        uint32_t col_up;   rs->convertColourValue(Ogre::ColourValue(1.0f, 1.0f, 1.0f), &col_up); // white
        uint32_t col_down; rs->convertColourValue(Ogre::ColourValue(1.0f, 1.0f, 0.5f), &col_down); // yellow
        Ogre::Vector3    norm_up(0,0, 1);
        Ogre::Vector3  norm_down(0,0,-1);
        uint32_t col_east; rs->convertColourValue(Ogre::ColourValue(1.0f, 0.5f, 0.5f), &col_east); // red
        uint32_t col_west; rs->convertColourValue(Ogre::ColourValue(1.0f, 0.75f, 0.5f), &col_west); // orange
        Ogre::Vector3  norm_east( 1,0,0);
        Ogre::Vector3  norm_west(-1,0,0);
        uint32_t col_north; rs->convertColourValue(Ogre::ColourValue(0.5f, 0.5f, 1.0f), &col_north); // blue
        uint32_t col_south; rs->convertColourValue(Ogre::ColourValue(0.5f, 1.0f, 0.5f), &col_south); // green
        Ogre::Vector3 norm_north(0, 1,0);
        Ogre::Vector3 norm_south(0,-1,0);
        VDataRaw vdata_raw[] = {
            { { -1, -1,  1 },  { 0, 0 },  norm_up,  col_up }, 
            { {  1, -1,  1 },  { 1, 0 },  norm_up,  col_up },
            { { -1,  1,  1 },  { 0, 1 },  norm_up,  col_up },
            { {  1,  1,  1 },  { 1, 1 },  norm_up,  col_up },
            { { -1,  1, -1 },  { 0, 0 },  norm_down,  col_down }, 
            { {  1,  1, -1 },  { 1, 0 },  norm_down,  col_down },
            { { -1, -1, -1 },  { 0, 1 },  norm_down,  col_down },
            { {  1, -1, -1 },  { 1, 1 },  norm_down,  col_down },

            { { -1, -1, -1 },  { 0, 0 },  norm_west,  col_west }, 
            { { -1, -1,  1 },  { 1, 0 },  norm_west,  col_west },
            { { -1,  1, -1 },  { 0, 1 },  norm_west,  col_west },
            { { -1,  1,  1 },  { 1, 1 },  norm_west,  col_west },
            { {  1,  1, -1 },  { 0, 0 },  norm_east,  col_east }, 
            { {  1,  1,  1 },  { 1, 0 },  norm_east,  col_east },
            { {  1, -1, -1 },  { 0, 1 },  norm_east,  col_east },
            { {  1, -1,  1 },  { 1, 1 },  norm_east,  col_east },

            { { -1,  1, -1 },  { 0, 0 },  norm_north,  col_north }, 
            { { -1,  1,  1 },  { 1, 0 },  norm_north,  col_north },
            { {  1,  1, -1 },  { 0, 1 },  norm_north,  col_north },
            { {  1,  1,  1 },  { 1, 1 },  norm_north,  col_north },
            { {  1, -1, -1 },  { 0, 0 },  norm_south,  col_south }, 
            { {  1, -1,  1 },  { 1, 0 },  norm_south,  col_south },
            { { -1, -1, -1 },  { 0, 1 },  norm_south,  col_south },
            { { -1, -1,  1 },  { 1, 1 },  norm_south,  col_south },
        };
        vbuf->writeData(vdata.vertexStart, vdata.vertexCount*vdecl_size, &vdata_raw[0]);

        // Prepare index buffer
        Ogre::IndexData idata;
        idata.indexBuffer = Ogre::HardwareBufferManager::getSingleton()
            .createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, 3*triangles, Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
        idata.indexStart = 0;
        idata.indexCount = 3*triangles;
        uint16_t idata_raw[3*triangles];
        for (int i=0 ; i<6 ; ++i) { // 6 cube faces
            idata_raw[i*6 + 0] = 4*i + 0;
            idata_raw[i*6 + 1] = 4*i + 1;
            idata_raw[i*6 + 2] = 4*i + 2;
            idata_raw[i*6 + 3] = 4*i + 1;
            idata_raw[i*6 + 4] = 4*i + 3;
            idata_raw[i*6 + 5] = 4*i + 2;
        };
        idata.indexBuffer->writeData(idata.indexStart, idata.indexCount*sizeof(uint16_t), &idata_raw[0]);

        // Prepare texture (8 bit mono)
        uint8_t *raw_tex = new uint8_t[512*512];
        for (unsigned y=0 ; y<512 ; ++y) {
            for (unsigned x=0 ; x<512 ; ++x) {
                float x_ = (float(x)-256)/256;
                float y_ = (float(y)-256)/256;
                // compute a solid colour circle that fades to black beyond its boundary
                float intensity = (2-sqrtf(x_*x_*2 + y_*y_*2));
                intensity = intensity > 1 ? 1 : intensity;
                intensity = intensity < 0.5 ? 0.5 : intensity;
                raw_tex[y*512+x] = intensity * 255;
            }
        }
        Ogre::DataStreamPtr raw_tex_ptr = Ogre::DataStreamPtr(new Ogre::MemoryDataStream(raw_tex,512*512*1));
        // Load raw byte array into an Image
        Ogre::Image img;
        img.loadRawData(raw_tex_ptr, 512, 512, 1, Ogre::PF_L8, 1, 0);
        // Create texture based on img
        Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().loadImage("MyTexture", "General", img);


        // Initialise vertex program
        Ogre::HighLevelGpuProgramPtr vp = Ogre::HighLevelGpuProgramManager::getSingleton()
            .createProgram("MyVertexProgram", "General", "glsl", Ogre::GPT_VERTEX_PROGRAM);
        const char *vertex_program_code = 
            "#version 130\n"
            "uniform mat4x4 world_view_proj;\n"
            "uniform mat4x4 world;\n"
            "in vec3 vertex;\n"  // these 'in' names are used by ogre to determine the semantic -- do not change them
            "in vec2 uv0;\n"
            "in vec3 normal;\n"
            "in vec4 colour;\n"
            "out vec4 interp_colour;\n"
            "out vec2 interp_uv;\n"
            "out vec3 interp_normal;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = world_view_proj * vec4(vertex, 1);\n"
            "    interp_colour = colour;\n"
            "    interp_uv = uv0;\n"
            "    interp_normal = (world * vec4(normal,0)).xyz;\n"
            "}\n";
        vp->setSource(vertex_program_code);
        vp->load();

        // Initialise fragment program
        Ogre::HighLevelGpuProgramPtr fp = Ogre::HighLevelGpuProgramManager::getSingleton()
            .createProgram("MyFragmentProgram", "General", "glsl", Ogre::GPT_FRAGMENT_PROGRAM);
        const char *fragment_program_code =
            "#version 130\n"
            "uniform sampler2D tex;\n"
            "uniform vec3 light_dir;\n"
            "in vec4 interp_colour;\n"
            "in vec2 interp_uv;\n"
            "in vec3 interp_normal;\n"
            "out vec3 fb_colour;\n"
            "void main()\n"
            "{\n"
            "    vec3 colour = interp_colour.rgb * texture2D(tex, interp_uv).rgb;\n"
            "    float illumination = 0.5 + 0.5*dot(light_dir, normalize(interp_normal));\n"
            "    fb_colour = colour * illumination;\n"
            "}\n";
        fp->setSource(fragment_program_code);
        fp->load();

        // Set up program parameters
        Ogre::GpuProgramParametersSharedPtr vertex_params = vp->createParameters();
        Ogre::GpuProgramParametersSharedPtr fragment_params = fp->createParameters();
        fragment_params->setNamedConstant("light_dir", Ogre::Vector3(-1.0f, 1.0f, 10.0f).normalisedCopy());

        // Initialise projection matrix
        Ogre::Matrix4 proj;
        rs->_makeProjectionMatrix (Ogre::Degree(45), 1.0f, 0.2f, 800.0f, proj, true);

        // Initialise object data (scene)
        const int num_cubes = 4096;
        const int num_cols = 64;

        Ogre::Vector3 positions[num_cubes];
        Ogre::Quaternion orientations[num_cubes];
        Ogre::Quaternion rot_velocities[num_cubes];

        for (int i=0 ; i<num_cubes ; ++i) {
            int row = num_cols/2 - i / num_cols;
            int col = i % num_cols - (num_cols/2);
            positions[i] = Ogre::Vector3(col*3.0, row*3.0, -250);
            orientations[i] = Ogre::Quaternion(Ogre::Degree(360.0f * rand()/float(RAND_MAX)), Ogre::Vector3(0,0,1));
            // random rotational velocity -- 1 degree about random axis
            Ogre::Vector3 axis;
            do {
                // a uniformly distributed random vector to somewhere in the cube [-1, 1]
                axis = Ogre::Vector3(rand() / float(RAND_MAX),
                                     rand() / float(RAND_MAX),
                                     rand() / float(RAND_MAX)) * 2 - 1;
            } while (axis.squaredLength() > 1 || axis.squaredLength() < 0.0001);
            axis.normalise();
            // axis is now a random vector uniformly distributed on the surface of the unit sphere
            rot_velocities[i] = Ogre::Quaternion(Ogre::Degree(4), axis);
        }

        Ogre::Timer timer;

        // Rendering loop
        while (!clicked_close) {

            // handle window resizes etc
            Ogre::WindowEventUtilities::messagePump();

            // Tell rs where to render (necessary since resize may have changed things since last frame)
            rs->_setViewport(ogre_vp);
        
            // animate scene
            for (int i=0 ; i<num_cubes ; ++i) {
                orientations[i] = rot_velocities[i] * orientations[i];
            }

            // necessary for memory leak
            if (ogre_win->isActive()) {

                rs->_beginFrame();

                rs->clearFrameBuffer(Ogre::FBT_COLOUR | Ogre::FBT_DEPTH | Ogre::FBT_STENCIL, Ogre::ColourValue(0, 0, 0.5));

                // material settings for cubes
                rs->_setCullingMode(Ogre::CULL_CLOCKWISE);
                rs->_setDepthBufferParams(true, true, Ogre::CMPF_LESS_EQUAL);

                rs->_setTexture(0, true, tex);
                Ogre::TextureUnitState::UVWAddressingMode addr_mode = {
                    Ogre::TextureUnitState::TAM_WRAP,
                    Ogre::TextureUnitState::TAM_WRAP,
                    Ogre::TextureUnitState::TAM_WRAP
                };
                rs->_setTextureAddressingMode(0, addr_mode);
                rs->_setTextureLayerAnisotropy(0, 16);
                rs->_setTextureUnitFiltering(0, Ogre::FO_ANISOTROPIC, Ogre::FO_ANISOTROPIC, Ogre::FO_LINEAR);

                rs->bindGpuProgram(vp->_getBindingDelegate());
                rs->bindGpuProgram(fp->_getBindingDelegate());

                rs->bindGpuProgramParameters(Ogre::GPT_FRAGMENT_PROGRAM, fragment_params, Ogre::GPV_ALL);

                // mesh settings for cubes
                Ogre::RenderOperation op;
                op.useIndexes = true;
                op.vertexData = &vdata;
                op.indexData = &idata;
                op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;

                // render each cube with slightly different shader uniforms for each
                for (int i=0 ; i<num_cubes ; ++i) {
                    Ogre::Matrix4 world;
                    world.makeTransform(positions[i], Ogre::Vector3(1,1,1), orientations[i]); // unit scale
                    vertex_params->setNamedConstant("world_view_proj", proj * world); // no view proj -- camera fixed
                    vertex_params->setNamedConstant("world", world);
                    rs->bindGpuProgramParameters(Ogre::GPT_VERTEX_PROGRAM, vertex_params, Ogre::GPV_ALL);
                    rs->_render(op);
                }

                rs->_endFrame();

                // display rendered frame, after waiting for vsync
                rs->_swapAllRenderTargetBuffers(true);

                std::cout << "FPS: " << 1/ (timer.getMicroseconds() / 1E6) << std::endl;
                timer.reset();

            } else {

                mysleep(10000);

            }
        }


    } catch (Ogre::Exception &e) {
        std::cerr << e.getFullDescription() << std::endl;
    }

    return EXIT_SUCCESS;
}

// vim: shiftwidth=4:tabstop=4:expandtab
Last edited by sparkprime on Sat Jun 23, 2012 2:18 pm, edited 1 time in total.
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

Here is a screenshot Image
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

One anomaly that kept me busy for a couple of days was the following:

Code: Select all

rs->bindGpuProgram(vp->_getBindingDelegate());
rs->bindGpuProgramParameters(Ogre::GPT_VERTEX_PROGRAM, vertex_params, Ogre::GPV_ALL);
rs->bindGpuProgram(fp->_getBindingDelegate());
rs->bindGpuProgramParameters(Ogre::GPT_FRAGMENT_PROGRAM, fragment_params, Ogre::GPV_ALL);
This does not work because on GLSL a new link shader is created when you bind the fragment program, which does not have the vertex_params originally set. I worked around it by changing it to this:

Code: Select all

rs->bindGpuProgram(vp->_getBindingDelegate());
rs->bindGpuProgram(fp->_getBindingDelegate());
rs->bindGpuProgramParameters(Ogre::GPT_VERTEX_PROGRAM, vertex_params, Ogre::GPV_ALL);
rs->bindGpuProgramParameters(Ogre::GPT_FRAGMENT_PROGRAM, fragment_params, Ogre::GPV_ALL);
This seems to be fragile and I couldn't find any reference to it in the documentation. Any comment?
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

This is the version that uses hardware instancing -- it's very fast on my 3-year old laptop, approx 500fps with vsync turned off. edit: about 30 fps with the 4096 explicit batches, and the batches are about as minimal state changes as you can get as it was just changing a couple of uniforms and reissuing the draw

Still no D3D9 test yet.

Code: Select all

#define _POSIX_C_SOURCE 199309
#include <cstdlib>
#include <cstdio>
#include <ctime>
#include <cassert>

#include <sstream>

#ifdef WIN32
#include <windows.h>
#endif

#include <OgreRoot.h>
#include "OgreWindowEventUtilities.h"

#include <OgreVector3.h>
#include <OgreQuaternion.h>

#include <OgreRenderSystem.h>
#include <OgreGLRenderSystem.h>
#ifdef WIN32
#  include <OgreD3D9RenderSystem.h>
#endif
#include <OgreRenderWindow.h>
#include <OgreHighLevelGpuProgramManager.h>
#include <OgreGpuProgramParams.h>
#include <OgreGpuProgram.h>
#include <OgreHardwarePixelBuffer.h>

bool clicked_close = false;

struct WindowEventListener : Ogre::WindowEventListener {

    void windowClosed (Ogre::RenderWindow *rw)
    {
        (void)rw;
        clicked_close = true;
    }

} wel;


void mysleep(long micros)
{
        if (micros<=0) return;
        #ifdef WIN32
        long millis = micros/1000;
        Sleep(millis);
        #else
        struct timespec t = {0,micros*1000};
        int r = nanosleep(&t, NULL);
        if (r) {
                perror("sleep");
        }
        #endif
}

int main (void)
{

    try {

        // Specify how much logging we want and where we want it to go
        Ogre::LogManager *lmgr = OGRE_NEW Ogre::LogManager();
        Ogre::Log *ogre_log = OGRE_NEW Ogre::Log("",true,true);
        lmgr->setDefaultLog(ogre_log);
        lmgr->setLogDetail(Ogre::LL_NORMAL);
    
        // Default to d3d9 on windows unles env var used
        #ifdef WIN32
        bool use_d3d9 = getenv("OGRE_DIRECT_GL")==NULL;
        #else 
        bool use_d3d9 = false;
        #endif

        Ogre::RenderSystem *rs;
        if (use_d3d9) {
            #ifdef WIN32
            rs = OGRE_NEW Ogre::D3D9RenderSystem();
            rs->setConfigOption("Allow NVPerfHUD","Yes");
            rs->setConfigOption("Floating-point mode","Consistent");
            rs->setConfigOption("Video Mode","800 x 600 @ 32-bit colour");
            #endif
        } else {
            rs = OGRE_NEW Ogre::GLRenderSystem();
            rs->setConfigOption("RTT Preferred Mode","FBO");
            rs->setConfigOption("Video Mode","800 x 600");
        }
        rs->setConfigOption("Full Screen","No");

        // Necessary to use Root because it is the only class allowed to _setPrimary on a window
        Ogre::Root *ogre_root = OGRE_NEW Ogre::Root("","","");
        ogre_root->setRenderSystem(rs);
        Ogre::RenderWindow *ogre_win = ogre_root->initialise(true, "Ogre Direct Window");
        ogre_win->setDeactivateOnFocusChange(false);
        ogre_win->setVSyncEnabled(true);
        Ogre::WindowEventUtilities::addWindowEventListener(ogre_win, &wel);

        // Viewports are mandatory when rendering, but use them here in a minimal fashion
        Ogre::Viewport *ogre_vp = ogre_win->addViewport(NULL, 1, 0,0,1,1);

        // each cube face has 4 vertexes, 2 triangles
        // we do not share vertexes between adjacent faces because we want sharp normals / colour transitions
        const unsigned vertexes = 6 * 4;
        const unsigned triangles = 6 * 2;

        const unsigned num_cubes = 4096;
        const int num_cols = 64; // cubes per row

        // Prepare vertex buffer
        Ogre::VertexData vdata;
        vdata.vertexStart = 0;
        vdata.vertexCount = vertexes;
        // Non-instanced data
        unsigned vdecl_size = 0;
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT3, Ogre::VES_POSITION).getSize();
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0).getSize();
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_FLOAT3, Ogre::VES_NORMAL).getSize();
        vdecl_size += vdata.vertexDeclaration->addElement(0, vdecl_size, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE).getSize();
        struct VDataRaw { float p[3], uv[2]; Ogre::Vector3 n; uint32_t col; }; // careful with padding here, all prims are 4 bytes long
        Ogre::HardwareVertexBufferSharedPtr vbuf =
            Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
                vdecl_size, vertexes, Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
        vdata.vertexBufferBinding->setBinding(0, vbuf);
        // VET_COLOUR requires conversion by rendersystem to appropriate format
        uint32_t col_up;   rs->convertColourValue(Ogre::ColourValue(1.0f, 1.0f, 1.0f), &col_up); // white
        uint32_t col_down; rs->convertColourValue(Ogre::ColourValue(1.0f, 1.0f, 0.5f), &col_down); // yellow
        Ogre::Vector3    norm_up(0,0, 1);
        Ogre::Vector3  norm_down(0,0,-1);
        uint32_t col_east; rs->convertColourValue(Ogre::ColourValue(1.0f, 0.5f, 0.5f), &col_east); // red
        uint32_t col_west; rs->convertColourValue(Ogre::ColourValue(1.0f, 0.75f, 0.5f), &col_west); // orange
        Ogre::Vector3  norm_east( 1,0,0);
        Ogre::Vector3  norm_west(-1,0,0);
        uint32_t col_north; rs->convertColourValue(Ogre::ColourValue(0.5f, 0.5f, 1.0f), &col_north); // blue
        uint32_t col_south; rs->convertColourValue(Ogre::ColourValue(0.5f, 1.0f, 0.5f), &col_south); // green
        Ogre::Vector3 norm_north(0, 1,0);
        Ogre::Vector3 norm_south(0,-1,0);
        VDataRaw vdata_raw[] = {
            { { -1, -1,  1 },  { 0, 0 },  norm_up,  col_up }, 
            { {  1, -1,  1 },  { 1, 0 },  norm_up,  col_up },
            { { -1,  1,  1 },  { 0, 1 },  norm_up,  col_up },
            { {  1,  1,  1 },  { 1, 1 },  norm_up,  col_up },
            { { -1,  1, -1 },  { 0, 0 },  norm_down,  col_down }, 
            { {  1,  1, -1 },  { 1, 0 },  norm_down,  col_down },
            { { -1, -1, -1 },  { 0, 1 },  norm_down,  col_down },
            { {  1, -1, -1 },  { 1, 1 },  norm_down,  col_down },

            { { -1, -1, -1 },  { 0, 0 },  norm_west,  col_west }, 
            { { -1, -1,  1 },  { 1, 0 },  norm_west,  col_west },
            { { -1,  1, -1 },  { 0, 1 },  norm_west,  col_west },
            { { -1,  1,  1 },  { 1, 1 },  norm_west,  col_west },
            { {  1,  1, -1 },  { 0, 0 },  norm_east,  col_east }, 
            { {  1,  1,  1 },  { 1, 0 },  norm_east,  col_east },
            { {  1, -1, -1 },  { 0, 1 },  norm_east,  col_east },
            { {  1, -1,  1 },  { 1, 1 },  norm_east,  col_east },

            { { -1,  1, -1 },  { 0, 0 },  norm_north,  col_north }, 
            { { -1,  1,  1 },  { 1, 0 },  norm_north,  col_north },
            { {  1,  1, -1 },  { 0, 1 },  norm_north,  col_north },
            { {  1,  1,  1 },  { 1, 1 },  norm_north,  col_north },
            { {  1, -1, -1 },  { 0, 0 },  norm_south,  col_south }, 
            { {  1, -1,  1 },  { 1, 0 },  norm_south,  col_south },
            { { -1, -1, -1 },  { 0, 1 },  norm_south,  col_south },
            { { -1, -1,  1 },  { 1, 1 },  norm_south,  col_south },
        };
        vbuf->writeData(vdata.vertexStart, vdata.vertexCount*vdecl_size, &vdata_raw[0]);
        // Instanced data
        // could reduce bus bandwidth by 25% by keeping pos in a separate buffer, thus updating only rotation each frame, but not doing that
        unsigned vdecl_inst_size = 0;
        vdecl_inst_size += vdata.vertexDeclaration->addElement(1, vdecl_inst_size, Ogre::VET_FLOAT3, Ogre::VES_TEXTURE_COORDINATES, 1).getSize();
        vdecl_inst_size += vdata.vertexDeclaration->addElement(1, vdecl_inst_size, Ogre::VET_FLOAT3, Ogre::VES_TEXTURE_COORDINATES, 2).getSize();
        vdecl_inst_size += vdata.vertexDeclaration->addElement(1, vdecl_inst_size, Ogre::VET_FLOAT3, Ogre::VES_TEXTURE_COORDINATES, 3).getSize();
        vdecl_inst_size += vdata.vertexDeclaration->addElement(1, vdecl_inst_size, Ogre::VET_FLOAT3, Ogre::VES_TEXTURE_COORDINATES, 4).getSize();
        struct VDataInstRaw { Ogre::Matrix3 rot; Ogre::Vector3 pos; }; // careful with padding here, all prims are 4 bytes long
        Ogre::HardwareVertexBufferSharedPtr vbuf_inst =
            Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
                vdecl_inst_size, num_cubes, Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
        vbuf_inst->setIsInstanceData(true);
        vbuf_inst->setInstanceDataStepRate(1);
        vdata.vertexBufferBinding->setBinding(1, vbuf_inst);
        VDataInstRaw vdata_inst_raw[num_cubes]; // leave 0 for now
        for (unsigned i=0 ; i<num_cubes ; ++i) {
            int row = num_cols/2 - i / num_cols;
            int col = i % num_cols - (num_cols/2);
            vdata_inst_raw[i].pos = Ogre::Vector3(col*3.0, row*3.0, -250);
            Ogre::Quaternion(Ogre::Degree(360.0f * rand()/float(RAND_MAX)), Ogre::Vector3(0,0,1)).ToRotationMatrix(vdata_inst_raw[i].rot);
        }
        vbuf_inst->writeData(0, num_cubes*vdecl_inst_size, &vdata_inst_raw[0]);

        // Prepare index buffer
        Ogre::IndexData idata;
        idata.indexBuffer = Ogre::HardwareBufferManager::getSingleton()
            .createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, 3*triangles, Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
        idata.indexStart = 0;
        idata.indexCount = 3*triangles;
        uint16_t idata_raw[3*triangles];
        for (int i=0 ; i<6 ; ++i) { // 6 cube faces
            idata_raw[i*6 + 0] = 4*i + 0;
            idata_raw[i*6 + 1] = 4*i + 1;
            idata_raw[i*6 + 2] = 4*i + 2;
            idata_raw[i*6 + 3] = 4*i + 1;
            idata_raw[i*6 + 4] = 4*i + 3;
            idata_raw[i*6 + 5] = 4*i + 2;
        };
        idata.indexBuffer->writeData(idata.indexStart, idata.indexCount*sizeof(uint16_t), &idata_raw[0]);

        // Prepare texture (8 bit mono)
        uint8_t *raw_tex = new uint8_t[512*512];
        for (unsigned y=0 ; y<512 ; ++y) {
            for (unsigned x=0 ; x<512 ; ++x) {
                float x_ = (float(x)-256)/256;
                float y_ = (float(y)-256)/256;
                // compute a solid colour circle that fades to black beyond its boundary
                float intensity = (2-sqrtf(x_*x_*2 + y_*y_*2));
                intensity = intensity > 1 ? 1 : intensity;
                intensity = intensity < 0.5 ? 0.5 : intensity;
                raw_tex[y*512+x] = intensity * 255;
            }
        }
        Ogre::DataStreamPtr raw_tex_ptr = Ogre::DataStreamPtr(new Ogre::MemoryDataStream(raw_tex,512*512*1));
        // Load raw byte array into an Image
        Ogre::Image img;
        img.loadRawData(raw_tex_ptr, 512, 512, 1, Ogre::PF_L8, 1, 0);
        // Create texture based on img
        Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().loadImage("MyTexture", "General", img);


        // Initialise vertex program
        Ogre::HighLevelGpuProgramPtr vp = Ogre::HighLevelGpuProgramManager::getSingleton()
            .createProgram("MyVertexProgram", "General", "glsl", Ogre::GPT_VERTEX_PROGRAM);
        const char *vertex_program_code = 
            "#version 130\n"
            "uniform mat4x4 proj;\n"
            "in vec3 vertex;\n"  // these 'in' names are used by ogre to determine the semantic -- do not change them
            "in vec2 uv0;\n"
            "in vec3 uv1;\n"
            "in vec3 uv2;\n"
            "in vec3 uv3;\n"
            "in vec3 uv4;\n"
            "in vec3 normal;\n"
            "in vec4 colour;\n"
            "out vec4 interp_colour;\n"
            "out vec2 interp_uv;\n"
            "out vec3 interp_normal;\n"
            "void main()\n"
            "{\n"
            "    mat3x3 instance_rot = mat3x3(uv1, uv2, uv3);\n"
            "    vec3 instance_pos = uv4;\n"
            "    gl_Position = proj * vec4(instance_rot * vertex + instance_pos, 1);\n"
            "    interp_colour = colour;\n"
            "    interp_uv = uv0;\n"
            "    interp_normal = instance_rot * normal;\n"
            "}\n";
        vp->setSource(vertex_program_code);
        vp->load();

        // Initialise fragment program
        Ogre::HighLevelGpuProgramPtr fp = Ogre::HighLevelGpuProgramManager::getSingleton()
            .createProgram("MyFragmentProgram", "General", "glsl", Ogre::GPT_FRAGMENT_PROGRAM);
        const char *fragment_program_code =
            "#version 130\n"
            "uniform sampler2D tex;\n"
            "uniform vec3 light_dir;\n"
            "in vec4 interp_colour;\n"
            "in vec2 interp_uv;\n"
            "in vec3 interp_normal;\n"
            "out vec3 fb_colour;\n"
            "void main()\n"
            "{\n"
            "    vec3 colour = interp_colour.rgb * texture2D(tex, interp_uv).rgb;\n"
            "    float illumination = 0.5 + 0.5*dot(light_dir, normalize(interp_normal));\n"
            "    fb_colour = colour * illumination;\n"
            "}\n";
        fp->setSource(fragment_program_code);
        fp->load();

        // Set up program parameters
        Ogre::GpuProgramParametersSharedPtr vertex_params = vp->createParameters();
        Ogre::GpuProgramParametersSharedPtr fragment_params = fp->createParameters();
        Ogre::Matrix4 proj;
        rs->_makeProjectionMatrix (Ogre::Degree(45), 1.0f, 0.2f, 800.0f, proj, true);
        vertex_params->setNamedConstant("proj", proj); // no view matrix -- camera fixed
        fragment_params->setNamedConstant("light_dir", Ogre::Vector3(-1.0f, 1.0f, 10.0f).normalisedCopy());

        // Initialise cpu data (for animating scene)
        Ogre::Matrix3 rot_velocities[num_cubes];
        for (unsigned i=0 ; i<num_cubes ; ++i) {
            // random rotational velocity -- 1 degree about random axis
            Ogre::Vector3 axis;
            do {
                // a uniformly distributed random vector to somewhere in the cube [-1, 1]
                axis = Ogre::Vector3(rand() / float(RAND_MAX),
                                     rand() / float(RAND_MAX),
                                     rand() / float(RAND_MAX)) * 2 - 1;
            } while (axis.squaredLength() > 1 || axis.squaredLength() < 0.0001);
            axis.normalise();
            // axis is now a random vector uniformly distributed on the surface of the unit sphere
            Ogre::Quaternion(Ogre::Degree(4), axis).ToRotationMatrix(rot_velocities[i]);
        }

        Ogre::Timer timer;

        // Rendering loop
        while (!clicked_close) {

            // handle window resizes etc
            Ogre::WindowEventUtilities::messagePump();

            // Tell rs where to render (necessary since resize may have changed things since last frame)
            rs->_setViewport(ogre_vp);
        
            // animate scene
            for (unsigned i=0 ; i<num_cubes ; ++i) {
                vdata_inst_raw[i].rot = rot_velocities[i] * vdata_inst_raw[i].rot;
            }
            vbuf_inst->writeData(0, num_cubes*vdecl_inst_size, &vdata_inst_raw[0]);

            // necessary for memory leak
            if (ogre_win->isActive()) {

                rs->_beginFrame();

                rs->clearFrameBuffer(Ogre::FBT_COLOUR | Ogre::FBT_DEPTH | Ogre::FBT_STENCIL, Ogre::ColourValue(0, 0, 0.5));

                // material settings for cubes
                rs->_setCullingMode(Ogre::CULL_CLOCKWISE);
                rs->_setDepthBufferParams(true, true, Ogre::CMPF_LESS_EQUAL);

                rs->_setTexture(0, true, tex);
                Ogre::TextureUnitState::UVWAddressingMode addr_mode = {
                    Ogre::TextureUnitState::TAM_WRAP,
                    Ogre::TextureUnitState::TAM_WRAP,
                    Ogre::TextureUnitState::TAM_WRAP
                };
                rs->_setTextureAddressingMode(0, addr_mode);
                rs->_setTextureLayerAnisotropy(0, 16);
                rs->_setTextureUnitFiltering(0, Ogre::FO_ANISOTROPIC, Ogre::FO_ANISOTROPIC, Ogre::FO_LINEAR);

                rs->bindGpuProgram(vp->_getBindingDelegate());
                rs->bindGpuProgram(fp->_getBindingDelegate());

                rs->bindGpuProgramParameters(Ogre::GPT_FRAGMENT_PROGRAM, fragment_params, Ogre::GPV_ALL);
                rs->bindGpuProgramParameters(Ogre::GPT_VERTEX_PROGRAM, vertex_params, Ogre::GPV_ALL);

                // render the instances
                Ogre::RenderOperation op;
                op.useIndexes = true;
                op.vertexData = &vdata;
                op.indexData = &idata;
                op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
                op.numberOfInstances = 4096;
                rs->_render(op);

                rs->_endFrame();

                // display rendered frame, vsync param ignored here, at least in gl
                rs->_swapAllRenderTargetBuffers(true);

                std::cout << "FPS: " << 1/ (timer.getMicroseconds() / 1E6) << std::endl;
                timer.reset();

            } else {

                mysleep(10000);

            }
        }


    } catch (Ogre::Exception &e) {
        std::cerr << e.getFullDescription() << std::endl;
    }

    return EXIT_SUCCESS;
}

// vim: shiftwidth=4:tabstop=4:expandtab
User avatar
sparkprime
Ogre Magi
Posts: 1137
Joined: Mon May 07, 2007 3:43 am
Location: Ossining, New York
x 13
Contact:

Re: Writing apps directly to RenderSystem API

Post by sparkprime »

I think this belongs on a wiki page. Anyone want to tell me what to call the page, i.e. give me a link to the blank page?

I don't think it has a role within the samples framework because

a) it is not eye candy, it is an example of framework application on which you base a more sophisticated program
b) it probably doesn't play nice with the samples framework (and i have no interest in making it play nice)
c) noone cares
User avatar
Klaim
Old One
Posts: 2565
Joined: Sun Sep 11, 2005 1:04 am
Location: Paris, France
x 56
Contact:

Re: Writing apps directly to RenderSystem API

Post by Klaim »

sparkprime wrote: c) noone cares

I don't think so. If there was a tutorial showing how to use ogre to do very basic rendering without organisation, it would help the beginners in 3D graphics understand how 3D works (in the same way that simple OGL or DX tutorial explain that). That way it might be more interesting for them to learn that first before diving in the higher level structures of Ogre. However, that's interesting only for learning how 3D renders in Ogre, not for people who don't care about graphic rendering and just want their model on screen.
User avatar
madmarx
OGRE Expert User
OGRE Expert User
Posts: 1671
Joined: Mon Jan 21, 2008 10:26 pm
x 50

Re: Writing apps directly to RenderSystem API

Post by madmarx »

I think it's very interesting for people who wants to do some advanced stuff.
You could wiki it, also.

Personnally, I used a lot of direct RenderSystem call, but in conjunction with ogre. (for example , to create my own 'compositor'-like structure). nullsquared made several posts (in 2007/2008?) to help others on this matter, which helped me at that time.
Tutorials + Ogre searchable API + more for Ogre1.7 : http://sourceforge.net/projects/so3dtools/
Corresponding thread : http://www.ogre3d.org/forums/viewtopic. ... 93&start=0
Post Reply