source: GTP/trunk/App/Demos/Vis/FriendlyCulling/src/DeferredRenderer.cpp @ 3148

Revision 3148, 30.5 KB checked in by mattausch, 16 years ago (diff)

done a little cleanup

RevLine 
[2896]1#include "DeferredRenderer.h"
[2859]2#include "FrameBufferObject.h"
3#include "RenderState.h"
4#include "SampleGenerator.h"
[2860]5#include "Vector3.h"
6#include "Camera.h"
[2884]7#include "shaderenv.h"
[2886]8#include "Halton.h"
[2895]9#include "ShadowMapping.h"
[2952]10#include "Light.h"
[3057]11#include "ShaderManager.h"
[2858]12
[3003]13#include <IL/il.h>
14#include <assert.h>
[2859]15
[3003]16
[3021]17#ifdef _CRT_SET
18        #define _CRTDBG_MAP_ALLOC
19        #include <stdlib.h>
20        #include <crtdbg.h>
21
22        // redefine new operator
23        #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
24        #define new DEBUG_NEW
25#endif
26
27
[2858]28using namespace std;
29
30
[3003]31static void startil()
32{
33        ilInit();
34        assert(ilGetError() == IL_NO_ERROR);
35}
36
37
38static void stopil()
39{
40        ilShutDown();
41        assert(ilGetError() == IL_NO_ERROR);
42}
43
[2858]44namespace CHCDemoEngine
45{
46
[3026]47static ShaderProgram *sCgSsaoProgram = NULL;
48static ShaderProgram *sCgGiProgram = NULL;
[2873]49
[3026]50static ShaderProgram *sCgDeferredProgram = NULL;
51static ShaderProgram *sCgAntiAliasingProgram = NULL;
52static ShaderProgram *sCgDeferredShadowProgram = NULL;
[2859]53
[3026]54static ShaderProgram *sCgCombineSsaoProgram = NULL;
55static ShaderProgram *sCgCombineIllumProgram = NULL;
56static ShaderProgram *sCgLogLumProgram = NULL;
57static ShaderProgram *sCgToneProgram = NULL;
58static ShaderProgram *sCgDownSampleProgram = NULL;
[3137]59static ShaderProgram *sCgScaleDepthProgram = NULL;
[2869]60
[2992]61
[3129]62static GLuint noiseTex2D = 0;
63static GLuint noiseTex1D = 0;
[2865]64
[3128]65
[2859]66// ssao random spherical samples
[2903]67static Sample2 samples2[NUM_SAMPLES];
[2966]68// number of pcf tabs
[3103]69static Sample2 pcfSamples[NUM_PCF_TABS];
[2966]70
[3026]71
[3103]72static float ssaoFilterOffsets[NUM_SSAO_FILTERSAMPLES * 2];
73static float ssaoFilterWeights[NUM_SSAO_FILTERSAMPLES];
74
75
[3118]76int DeferredRenderer::colorBufferIdx = 0;
[2859]77
[2992]78
[3085]79/** Helper method that computes the view vectors in the corners of the current view frustum.
80*/
81static void ComputeViewVectors(PerspectiveCamera *cam, Vector3 &bl, Vector3 &br, Vector3 &tl, Vector3 &tr)
82{
83        Vector3 ftl, ftr, fbl, fbr, ntl, ntr, nbl, nbr;
84        cam->ComputePoints(ftl, ftr, fbl, fbr, ntl, ntr, nbl, nbr);
85
86        bl = Normalize(nbl - fbl);
87        br = Normalize(nbr - fbr);
88        tl = Normalize(ntl - ftl);
89        tr = Normalize(ntr - ftr);
90}
91
92
[3025]93static float GaussianDistribution(float x, float y, float rho)
94{
[3110]95        float g = 1.0f / sqrtf(2.0f * M_PI * rho * rho);
[3133]96    g *= expf( -(x * x + y * y) / (2.0f * rho * rho));
[3025]97
98    return g;
99}
100
101
[2859]102static void PrintGLerror(char *msg)
103{
104        GLenum errCode;
105        const GLubyte *errStr;
106       
107        if ((errCode = glGetError()) != GL_NO_ERROR)
108        {
109                errStr = gluErrorString(errCode);
110                fprintf(stderr,"OpenGL ERROR: %s: %s\n", errStr, msg);
111        }
112}
113
114
[3134]115static void ComputeSampleOffsets(float *sampleOffsets,
116                                                                 int imageW, int imageH,
117                                                                 float width,
118                                                                 int samples)
119{
120        const float xoffs = width / (float)imageW;
121        const float yoffs = width / (float)imageH;
[3017]122       
[3134]123        const int numSamples = (int)sqrt((float)samples);
124        const int startSamples = -numSamples / 2;
125        const int endSamples = numSamples + startSamples - 1;
126        //cout << startSamples << " " << endSamples << endl;
[2976]127
[3134]128        int idx = 0;
[3017]129
[3134]130        for (int x = startSamples; x <= endSamples; ++ x)
[3017]131        {
[3134]132                for (int y = startSamples; y <= endSamples; ++ y)
[3017]133                {
[3134]134                        sampleOffsets[idx + 0] = (float)x * xoffs;
135                        sampleOffsets[idx + 1] = (float)y * yoffs;
[3017]136                        idx += 2;
137                }
138        }
139}
140
[3026]141
[3132]142void DeferredRenderer::FlipFbos(FrameBufferObject *fbo)
143{
144        fbo->Bind();
145        colorBufferIdx = 3 - colorBufferIdx;
146        glDrawBuffers(1, mrt + colorBufferIdx);
147}
148
149
[3026]150void DeferredRenderer::DrawQuad(ShaderProgram *p)
151{
[3132]152        if (p) p->Bind();
[3026]153
[3089]154        // interpolate the view vector
[3085]155        Vector3 bl = mCornersView[0];
156        Vector3 br = mCornersView[1];
157        Vector3 tl = mCornersView[2];
158        Vector3 tr = mCornersView[3];
159
[3026]160        // note: slightly larger texture could hide ambient occlusion error on border but costs resolution
161        glBegin(GL_QUADS);
162
[3093]163        glTexCoord2f(0, 0); glMultiTexCoord3fARB(GL_TEXTURE1_ARB, bl.x, bl.y, bl.z); glVertex2f( .0f,  .0f);
164        glTexCoord2f(1, 0); glMultiTexCoord3fARB(GL_TEXTURE1_ARB, br.x, br.y, br.z); glVertex2f(1.0f,  .0f);
165        glTexCoord2f(1, 1); glMultiTexCoord3fARB(GL_TEXTURE1_ARB, tr.x, tr.y, tr.z); glVertex2f(1.0f, 1.0f);
166        glTexCoord2f(0, 1); glMultiTexCoord3fARB(GL_TEXTURE1_ARB, tl.x, tl.y, tl.z); glVertex2f( .0f, 1.0f);
[3026]167
168        glEnd();
169}
170
171
[2859]172/** Generate poisson disc distributed sample points on the unit disc
173*/
[2887]174static void GenerateSamples(int sampling)
[2859]175{
[2887]176        switch (sampling)
177        {
[2930]178        case DeferredRenderer::SAMPLING_POISSON:
[2887]179                {
[2930]180                        PoissonDiscSampleGenerator2 poisson(NUM_SAMPLES, 1.0f);
[2900]181                        poisson.Generate((float *)samples2);
[2887]182                }
183                break;
[2930]184        case DeferredRenderer::SAMPLING_QUADRATIC:
[2887]185                {
[2930]186                        QuadraticDiscSampleGenerator2 g(NUM_SAMPLES, 1.0f);
187                        g.Generate((float *)samples2);
[2887]188                }
189                break;
[2930]190        default: // SAMPLING_DEFAULT
[3026]191                {
192                        RandomSampleGenerator2 g(NUM_SAMPLES, 1.0f);
193                        g.Generate((float *)samples2);
194                }
[2903]195        }
[2859]196}
197
198
[2879]199static void CreateNoiseTex2D(int w, int h)
200{
201        //GLubyte *randomNormals = new GLubyte[mWidth * mHeight * 3];
202        float *randomNormals = new float[w * h * 3];
203
[2900]204        static HaltonSequence halton;
205        float r[2];
206
[2879]207        for (int i = 0; i < w * h * 3; i += 3)
208        {
[2903]209                // create random samples on a circle
210                r[0] = RandomValue(0, 1);
211                //halton.GetNext(1, r);
212
213                const float theta = 2.0f * acos(sqrt(1.0f - r[0]));
214               
215                randomNormals[i + 0] = cos(theta);
216                randomNormals[i + 1] = sin(theta);
217                randomNormals[i + 2] = 0;
[2879]218        }
219
220        glEnable(GL_TEXTURE_2D);
[3129]221        glGenTextures(1, &noiseTex2D);
222        glBindTexture(GL_TEXTURE_2D, noiseTex2D);
[2879]223               
224        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
225        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
226        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
227        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
228
229        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, w, h, 0, GL_RGB, GL_FLOAT, randomNormals);
230
231        glBindTexture(GL_TEXTURE_2D, 0);
232        glDisable(GL_TEXTURE_2D);
233
234        delete [] randomNormals;
235
236        cout << "created noise texture" << endl;
237
238        PrintGLerror("noisetexture");
239}
240
241
[3129]242static void CreateNoiseTex1D(int w)
243{
244        float *randomValues = new float[w * 3];
245
246        static HaltonSequence halton;
247       
248        randomValues[0] = randomValues[1] = randomValues[2] = 0;
249
250        for (int i = 3; i < w * 3; i += 3)
251        {
252                // create random samples on a circle
253                randomValues[i + 0] = 20.0f * RandomValue(0, 1) / 512.0f;
254                randomValues[i + 1] = 20.0f * RandomValue(0, 1) / 384.0f;
255                randomValues[i + 2] = 0;
256        }
257
258        glEnable(GL_TEXTURE_2D);
259        glGenTextures(1, &noiseTex1D);
260        glBindTexture(GL_TEXTURE_2D, noiseTex1D);
261               
262        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
263        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
264        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
265        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
266
267        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, w, 1, 0, GL_RGB, GL_FLOAT, randomValues);
268
269        glBindTexture(GL_TEXTURE_2D, 0);
270        glDisable(GL_TEXTURE_2D);
271
272        delete [] randomValues;
273
274        cout << "created noise texture 1D" << endl;
275
276        PrintGLerror("noisetexture 1D");
277}
278
279
280
[3068]281DeferredRenderer::DeferredRenderer(int w, int h, PerspectiveCamera *cam):
[2860]282mWidth(w), mHeight(h),
283mCamera(cam),
[2875]284mUseTemporalCoherence(true),
[2895]285mRegenerateSamples(true),
[2930]286mSamplingMethod(SAMPLING_POISSON),
[2895]287mShadingMethod(DEFAULT),
[3135]288mIllumFboIndex(0)
[2861]289{
290        ///////////
291        //-- the flip-flop fbos
[2859]292
[3142]293        const int dsw = w / 2; const int dsh = h / 2;
294        //const int dsw = w; const int dsh = h;
[3103]295
296        mIllumFbo = new FrameBufferObject(dsw, dsh, FrameBufferObject::DEPTH_NONE);
[3094]297        //mIllumFbo = new FrameBufferObject(w, h, FrameBufferObject::DEPTH_NONE);
298
[3019]299        mFBOs.push_back(mIllumFbo);
[2891]300
[3019]301        for (int i = 0; i < 4; ++ i)
302        {
[3088]303                mIllumFbo->AddColorBuffer(ColorBufferObject::RGBA_FLOAT_32, ColorBufferObject::WRAP_CLAMP_TO_EDGE, ColorBufferObject::FILTER_LINEAR);
[3117]304                FrameBufferObject::InitBuffer(mIllumFbo, i);
[3019]305        }
[3002]306
[3117]307
308        ///////////////
309        //-- the downsampled ssao + color bleeding textures: as gi is inherently low frequency, we can use these to improve performance
310
[3103]311        mDownSampleFbo = new FrameBufferObject(dsw, dsh, FrameBufferObject::DEPTH_NONE);
[3094]312        //mDownSampleFbo = new FrameBufferObject(w, h, FrameBufferObject::DEPTH_NONE);
[3002]313        mDownSampleFbo->AddColorBuffer(ColorBufferObject::RGBA_FLOAT_32, ColorBufferObject::WRAP_CLAMP_TO_EDGE, ColorBufferObject::FILTER_LINEAR);
[3087]314        // downsample buffer for the normal texture
[3017]315        mDownSampleFbo->AddColorBuffer(ColorBufferObject::RGB_FLOAT_16, ColorBufferObject::WRAP_CLAMP_TO_EDGE, ColorBufferObject::FILTER_LINEAR);
[3117]316        // downsample buffer for the offset texture
317        mDownSampleFbo->AddColorBuffer(ColorBufferObject::RGB_FLOAT_32, ColorBufferObject::WRAP_CLAMP_TO_EDGE, ColorBufferObject::FILTER_LINEAR);
[3019]318
[3117]319        for (int i = 0; i < 3; ++ i)
320        {
321                FrameBufferObject::InitBuffer(mDownSampleFbo, i);
322        }
323
[3019]324        mFBOs.push_back(mDownSampleFbo);
[3038]325
[3084]326        // create noise texture for ssao
[3132]327        CreateNoiseTex2D(mIllumFbo->GetWidth(), mIllumFbo->GetHeight());
328        //CreateNoiseTex2D(mIllumFbo->GetWidth() / 4, mIllumFbo->GetWidth() / 4);
[3129]329        CreateNoiseTex1D(mWidth / 8);
[3084]330
[3085]331        mProjViewMatrix = IdentityMatrix();
332        mOldProjViewMatrix = IdentityMatrix();
333
334        for (int i = 0; i < 4; ++ i)
335        {
336                mCornersView[i] = mOldCornersView[i] = Vector3::UNIT_X();
337        }
338
339        mEyePos = mOldEyePos = Vector3::ZERO();
340
[3038]341        InitCg();
[2861]342}
343
344
[2896]345DeferredRenderer::~DeferredRenderer()
[2861]346{
[3019]347        CLEAR_CONTAINER(mFBOs);
[3129]348        glDeleteTextures(1, &noiseTex2D);
349        glDeleteTextures(1, &noiseTex1D);
[2861]350}
351
352
[2896]353void DeferredRenderer::SetUseTemporalCoherence(bool temporal)
[2875]354{
355        mUseTemporalCoherence = temporal;
356}
357
358
[3021]359
[3038]360void DeferredRenderer::InitCg()
[3026]361{       
[3057]362        ShaderManager *sm = ShaderManager::GetSingleton();
[2873]363
[3057]364        sCgDeferredProgram = sm->CreateFragmentProgram("deferred", "main", "deferredFrag");
365        sCgDeferredShadowProgram = sm->CreateFragmentProgram("deferred", "main_shadow", "deferredFragShader");
366        sCgSsaoProgram = sm->CreateFragmentProgram("ssao", "main", "ssaoFrag");
367        sCgGiProgram = sm->CreateFragmentProgram("globillum", "main", "giFrag");
368        sCgCombineIllumProgram = sm->CreateFragmentProgram("globillum", "combine", "combineGi");
[3120]369        sCgCombineSsaoProgram = sm->CreateFragmentProgram("combineSsao", "combine", "combineSsao");
[3057]370        sCgAntiAliasingProgram = sm->CreateFragmentProgram("antialiasing", "main", "antiAliasing");
371        sCgToneProgram = sm->CreateFragmentProgram("tonemap", "ToneMap", "toneMap");
[3137]372        sCgDownSampleProgram = sm->CreateFragmentProgram("deferred", "Output", "Output");
373        sCgScaleDepthProgram = sm->CreateFragmentProgram("deferred", "ScaleDepth", "ScaleDepth");
[3057]374        sCgLogLumProgram = sm->CreateFragmentProgram("tonemap", "CalcAvgLogLum", "avgLogLum");
[3038]375
[3034]376
[3104]377        ///////////////////
378        //-- initialize program parameters
[3034]379
[3104]380        string ssaoParams[] =
381                {"colors", "normals", "oldTex", "noiseTex", "temporalCoherence",
[3111]382                 "samples", "bl", "br", "tl", "tr",
383                 "modelViewProj", "oldModelViewProj", "oldEyePos", "oldbl", "oldbr",
[3129]384                 "oldtl", "oldtr", "attribsTex", "noiseTex1D"};
385        sCgSsaoProgram->AddParameters(ssaoParams, 0, 19);
[3085]386       
[3104]387        string giParams[] =
388                {"colors", "normals", "noiseTex", "oldSsaoTex", "oldIllumTex",
[3111]389                 "temporalCoherence", "samples", "bl", "br", "tl",
390                 "tr", "oldModelViewProj", "modelViewProj"};
[3104]391        sCgGiProgram->AddParameters(giParams, 0, 13);
[3034]392
[3104]393        string toneParams[] = {"colors", "imageKey", "whiteLum", "middleGrey"};
394        sCgToneProgram->AddParameters(toneParams, 0, 4);
[3034]395
396
[3104]397        ////////////////
398
399        string deferredShadowParams[] =
400                {"colors", "normals", "shadowMap", "noiseTex", "shadowMatrix",
401                 "sampleWidth", "lightDir", "eyePos", "samples", "weights"};
[3035]402       
[3104]403        sCgDeferredShadowProgram->AddParameters(deferredShadowParams, 0, 10);
404       
405        ////////////////
[3034]406
[3104]407        string combineIllumParams[] = {"colorsTex", "ssaoTex", "illumTex"};
408        sCgCombineIllumProgram->AddParameters(combineIllumParams, 0, 3);
[3035]409
[3104]410        ////////////////
[3035]411
[3134]412        string combineSsaoParams[] = {"colorsTex", "normalsTex", "ssaoTex", "filterOffs", "filterWeights", "bl", "br", "tl", "tr"};
413        sCgCombineSsaoProgram->AddParameters(combineSsaoParams, 0, 9);
[3035]414
[3104]415        //////////////
[3036]416
[3104]417        string deferredParams[] = {"colors", "normals", "lightDir"};
418        sCgDeferredProgram->AddParameters(deferredParams, 0, 3);
419
420        ///////////////////
421
[3136]422        string aaParams[] = {"colors", "normals", "offsets"};
423        sCgAntiAliasingProgram->AddParameters(aaParams, 0, 3);
[3104]424
425        /////////////////////
426
[3137]427        string downSampleParams[] = {"colors"};
428        sCgDownSampleProgram->AddParameters(downSampleParams, 0, 1);
[3104]429
[3137]430        /////////////////////
431
432        string scaleDepthParams[] = {"colors"};
433        sCgScaleDepthProgram->AddParameters(scaleDepthParams, 0, 1);
434
[3104]435        ////////////
436
437        sCgLogLumProgram->AddParameter("colors", 0);
[3128]438
439        ////////////////
440
[3104]441
[3143]442        const float filterWidth = 100.0f;
443        //const float filterWidth = 1000.0f;
[3134]444
445#if 1
[3103]446        PoissonDiscSampleGenerator2 poisson(NUM_SSAO_FILTERSAMPLES, 1.0f);
447        poisson.Generate((float *)ssaoFilterOffsets);
[3036]448
[3105]449        const float xoffs = (float)filterWidth / mWidth;
450        const float yoffs = (float)filterWidth / mHeight;
[3103]451
452        for (int i = 0; i < NUM_SSAO_FILTERSAMPLES; ++ i)
453        {
454                float x = ssaoFilterOffsets[2 * i + 0];
455                float y = ssaoFilterOffsets[2 * i + 1];
456
[3140]457                ssaoFilterWeights[i] = GaussianDistribution(x, y, 1.0f);
458                //ssaoFilterWeights[i] = 1.0f;
[3120]459
[3103]460                ssaoFilterOffsets[2 * i + 0] *= xoffs;
461                ssaoFilterOffsets[2 * i + 1] *= yoffs;
462        }
[3133]463#else
[3134]464        //ComputeSampleOffsets(ssaoFilterOffsets, mWidth, mHeight, sqrt(NUM_SSAO_FILTERSAMPLES), NUM_SSAO_FILTERSAMPLES);
465        ComputeSampleOffsets(ssaoFilterOffsets, mWidth, mHeight, filterWidth, NUM_SSAO_FILTERSAMPLES);
466        //cout<<"ssao filter size: " << NUM_SSAO_FILTERSAMPLES << endl;
[3133]467        for (int i = 0; i < NUM_SSAO_FILTERSAMPLES; ++ i)
468                ssaoFilterWeights[i] = 1.0f;
[3103]469
[3133]470#endif
471
[3144]472        /////////
[3133]473        //-- pcf tabs for shadowing
474
[3026]475        float filterWeights[NUM_PCF_TABS];
[3103]476        PoissonDiscSampleGenerator2 poisson2(NUM_PCF_TABS, 1.0f);
477        poisson2.Generate((float *)pcfSamples);
[2990]478
[3026]479        for (int i = 0; i < NUM_PCF_TABS; ++ i)
[2880]480        {
[3026]481                filterWeights[i] = GaussianDistribution(pcfSamples[i].x, pcfSamples[i].y, 1.0f);
[2880]482        }
483
[3035]484        sCgDeferredShadowProgram->SetArray2f(8, (float *)pcfSamples, NUM_PCF_TABS);
485        sCgDeferredShadowProgram->SetArray1f(9, (float *)filterWeights, NUM_PCF_TABS);
[2880]486
[2859]487        PrintGLerror("init");
488}
489
490
[2896]491void DeferredRenderer::Render(FrameBufferObject *fbo,
[2901]492                                                          float tempCohFactor,
[2952]493                                                          DirectionalLight *light,
[2991]494                                                          bool useToneMapping,
495                                                          ShadowMap *shadowMap
496                                                          )
[2859]497{
[3085]498        InitFrame();
499
[2944]500        if (shadowMap)
[2952]501                FirstPassShadow(fbo, light, shadowMap);
[2895]502        else
[2952]503                FirstPass(fbo, light);
[2944]504
[3103]505        if (mShadingMethod != 0)
[2880]506        {
[3133]507                // downsample fbo buffers
[3137]508                // colors
509                DownSample(fbo, colorBufferIdx, mDownSampleFbo, 0, sCgScaleDepthProgram);
510                DownSample(fbo, 1, mDownSampleFbo, 1, sCgDownSampleProgram); // normals
511                DownSample(fbo, 2, mDownSampleFbo, 2, sCgDownSampleProgram); // offsets
[3103]512        }
[3006]513
[3133]514        // antialiasing of the color buffer
[3135]515        //AntiAliasing(fbo, light);
[3132]516
[3103]517        switch (mShadingMethod)
518        {
519        case SSAO:
[3085]520                ComputeSsao(fbo, tempCohFactor);
[2944]521                CombineSsao(fbo);
522                break;
523        case GI:
[3085]524                ComputeGlobIllum(fbo, tempCohFactor);
[2944]525                CombineIllum(fbo);
526                break;
527        default: // DEFAULT
528                // do nothing: standard deferred shading
529                break;
530        }
[2884]531
[2991]532        if (useToneMapping)
533        {
534                float imageKey, whiteLum, middleGrey;
535
536                ComputeToneParameters(fbo, light, imageKey, whiteLum, middleGrey);
[3007]537                ToneMap(fbo, imageKey, whiteLum, middleGrey);
[2991]538        }
539
[3142]540        // as multisampling is difficult / costly with deferred shading,
541        // at least do some antialiasing
[3135]542        AntiAliasing(fbo, light);
543
[3142]544        // just output the latest buffer
545        //Output(fbo);
[2867]546
547        glEnable(GL_LIGHTING);
548        glDisable(GL_TEXTURE_2D);
549
550        glMatrixMode(GL_PROJECTION);
551        glPopMatrix();
552
553        glMatrixMode(GL_MODELVIEW);
554        glPopMatrix();
555
[3089]556        // viewport
[2867]557        glPopAttrib();
558
[3007]559        FrameBufferObject::Release();
[3057]560        ShaderManager::GetSingleton()->DisableFragmentProfile();
[2859]561}
562
563
[2896]564void DeferredRenderer::ComputeSsao(FrameBufferObject *fbo,
[3087]565                                                                   float tempCohFactor)
[2859]566{
[3117]567        GLuint colorsTex, normalsTex, attribsTex;
[3015]568
[3133]569        if (0)
[3117]570        {
571                colorsTex = fbo->GetColorBuffer(colorBufferIdx)->GetTexture();
572                normalsTex = fbo->GetColorBuffer(1)->GetTexture();
573                attribsTex = fbo->GetColorBuffer(2)->GetTexture();
574        }
575        else
576        {
577                colorsTex = mDownSampleFbo->GetColorBuffer(0)->GetTexture();
578                normalsTex = mDownSampleFbo->GetColorBuffer(1)->GetTexture();
579                attribsTex = mDownSampleFbo->GetColorBuffer(2)->GetTexture();
[3132]580                //attribsTex = fbo->GetColorBuffer(2)->GetTexture();
[3117]581        }
582
[3087]583        // flip flop between illumination buffers
[3019]584        GLuint oldTex = mIllumFbo->GetColorBuffer(2 - mIllumFboIndex)->GetTexture();
[2993]585
[3006]586        glPushAttrib(GL_VIEWPORT_BIT);
[3019]587        glViewport(0, 0, mIllumFbo->GetWidth(), mIllumFbo->GetHeight());
[3006]588
[2861]589        // read the second buffer, write to the first buffer
[3019]590        mIllumFbo->Bind();
591        glDrawBuffers(1, mrt + mIllumFboIndex);
[2868]592
[3095]593        int i = 0;
[3034]594
[3095]595        sCgSsaoProgram->SetTexture(i ++, colorsTex);
596        sCgSsaoProgram->SetTexture(i ++, normalsTex);
597        sCgSsaoProgram->SetTexture(i ++, oldTex);
[3129]598        sCgSsaoProgram->SetTexture(i ++, noiseTex2D);
[3095]599
600        sCgSsaoProgram->SetValue1f(i ++, (mUseTemporalCoherence && !mRegenerateSamples) ? tempCohFactor : 0);
[3035]601       
[3129]602        if (mUseTemporalCoherence || mRegenerateSamples)
603        //if (mRegenerateSamples)
[2875]604        {
[2895]605                mRegenerateSamples = false;
[2859]606
[2875]607                // q: should we generate new samples or only rotate the old ones?
608                // in the first case, the sample patterns look nicer, but the kernel
609                // needs longer to converge
[3129]610                GenerateSamples(mSamplingMethod);
[3095]611                sCgSsaoProgram->SetArray2f(i, (float *)samples2, NUM_SAMPLES);
[2875]612        }
[3085]613       
[3095]614        ++ i;
[2859]615
[3095]616        for (int j = 0; j < 4; ++ j, ++ i)
617                sCgSsaoProgram->SetValue3f(i, mCornersView[j].x, mCornersView[j].y, mCornersView[j].z);
[2987]618
[3095]619        sCgSsaoProgram->SetMatrix(i ++, mProjViewMatrix);
620        sCgSsaoProgram->SetMatrix(i ++, mOldProjViewMatrix);
[3062]621
[3105]622        Vector3 de;
623        de.x = mOldEyePos.x - mEyePos.x;
624        de.y = mOldEyePos.y - mEyePos.y;
625        de.z = mOldEyePos.z - mEyePos.z;
[3035]626
[3095]627        sCgSsaoProgram->SetValue3f(i ++, de.x, de.y, de.z);
[3093]628
[3095]629        for (int j = 0; j < 4; ++ j, ++ i)
630                sCgSsaoProgram->SetValue3f(i, mOldCornersView[j].x, mOldCornersView[j].y, mOldCornersView[j].z);
[3085]631
[3110]632        sCgSsaoProgram->SetTexture(i ++, attribsTex);
633
[3129]634        sCgSsaoProgram->SetTexture(i ++, noiseTex1D);
635
[3026]636        DrawQuad(sCgSsaoProgram);
[2987]637
[3006]638        glPopAttrib();
[2859]639
640        PrintGLerror("ssao first pass");
641}
642
643
[2865]644static void SetVertex(float x, float y, float x_offs, float y_offs)
645{
646        glMultiTexCoord2fARB(GL_TEXTURE0_ARB, x, y); // center
647        glMultiTexCoord2fARB(GL_TEXTURE1_ARB, x - x_offs, y + y_offs); // left top
648        glMultiTexCoord2fARB(GL_TEXTURE2_ARB, x + x_offs, y - y_offs); // right bottom
649        glMultiTexCoord2fARB(GL_TEXTURE3_ARB, x + x_offs, y + y_offs); // right top
650        glMultiTexCoord2fARB(GL_TEXTURE4_ARB, x - x_offs, y - y_offs); // left bottom
651
652        glMultiTexCoord4fARB(GL_TEXTURE5_ARB, x - x_offs, y, x + x_offs, y); // left right
653        glMultiTexCoord4fARB(GL_TEXTURE6_ARB, x, y + y_offs, x, y - y_offs); // top bottom
654
[3089]655        //glVertex3f(x - 0.5f, y - 0.5f, -0.5f);
656        glVertex2f(x, y);
[2865]657}
658
659
[2970]660void DeferredRenderer::AntiAliasing(FrameBufferObject *fbo, DirectionalLight *light)
[2865]661{
[2968]662        ColorBufferObject *colorBuffer = fbo->GetColorBuffer(colorBufferIdx);
663        GLuint colorsTex = colorBuffer->GetTexture();
[3136]664        GLuint normalsTex = fbo->GetColorBuffer(1)->GetTexture();
[3007]665
[3142]666        FrameBufferObject::Release();
[3132]667        // read the second buffer, write to the first buffer
[3142]668        //FlipFbos(fbo);
[3132]669
[3136]670        // the neighbouring texels
671        float xOffs = 1.0f / fbo->GetWidth();
672        float yOffs = 1.0f / fbo->GetHeight();
673
[3034]674        sCgAntiAliasingProgram->SetTexture(0, colorsTex);
675        sCgAntiAliasingProgram->SetTexture(1, normalsTex);
[2865]676
[3136]677        float offsets[16];
678        int i = 0;
[2868]679
[3136]680        offsets[i] = -xOffs; offsets[i + 1] =  yOffs; i += 2; // left top
681        offsets[i] =  xOffs; offsets[i + 1] = -yOffs; i += 2; // right bottom
682        offsets[i] =  xOffs; offsets[i + 1] =  yOffs; i += 2; // right top
683        offsets[i] = -xOffs; offsets[i + 1] = -yOffs; i += 2; // left bottom
684        offsets[i] = -xOffs; offsets[i + 1] =    .0f; i += 2; // left
685        offsets[i] =  xOffs; offsets[i + 1] =    .0f; i += 2; // right
686        offsets[i] =    .0f; offsets[i + 1] =  yOffs; i += 2; // top
687        offsets[i] =    .0f; offsets[i + 1] = -yOffs; i += 2; // bottom
[2865]688
[3136]689        sCgAntiAliasingProgram->SetArray2f(2, offsets, 8);
[2865]690
[3136]691        DrawQuad(sCgAntiAliasingProgram);
[2865]692
[2867]693        PrintGLerror("antialiasing");
[2865]694}
695
696
[2952]697void DeferredRenderer::FirstPass(FrameBufferObject *fbo, DirectionalLight *light)
[2868]698{
[2973]699        GLuint colorsTex = fbo->GetColorBuffer(colorBufferIdx)->GetTexture();
[3009]700        GLuint normalsTex = fbo->GetColorBuffer(1)->GetTexture();
[2868]701
[3132]702        FlipFbos(fbo);
[2868]703
[3026]704        const Vector3 lightDir = -light->GetDirection();
[2868]705
[3034]706        sCgDeferredProgram->SetTexture(0, colorsTex);
707        sCgDeferredProgram->SetTexture(1, normalsTex);
708        sCgDeferredProgram->SetValue3f(2, lightDir.x, lightDir.y, lightDir.z);
[2868]709       
[3026]710        DrawQuad(sCgDeferredProgram);
[2952]711
[2868]712        PrintGLerror("deferred shading");
713}
714
[2869]715
[2896]716void DeferredRenderer::ComputeGlobIllum(FrameBufferObject *fbo,
[3085]717                                                                                float tempCohFactor)
[2869]718{
[3016]719#if 0
720        GLuint colorsTex = fbo->GetColorBuffer(colorBufferIdx)->GetTexture();
721        GLuint normalsTex = fbo->GetColorBuffer(1)->GetTexture();
722#else
[3003]723        GLuint colorsTex = mDownSampleFbo->GetColorBuffer(0)->GetTexture();
[3009]724        GLuint normalsTex = mDownSampleFbo->GetColorBuffer(1)->GetTexture();
[3016]725#endif
[2869]726
[3006]727        glPushAttrib(GL_VIEWPORT_BIT);
[3019]728        glViewport(0, 0, mIllumFbo->GetWidth(), mIllumFbo->GetHeight());
[2869]729
[2873]730        // read the second buffer, write to the first buffer
[3019]731        mIllumFbo->Bind();
[2873]732
[3019]733        glDrawBuffers(2, mrt + mIllumFboIndex);
[2873]734
[3019]735        GLuint oldSsaoTex = mIllumFbo->GetColorBuffer(2 - mIllumFboIndex)->GetTexture();
736        GLuint oldIllumTex = mIllumFbo->GetColorBuffer(2 - mIllumFboIndex + 1)->GetTexture();
[2869]737
[3034]738        sCgGiProgram->SetTexture(0, colorsTex);
739        sCgGiProgram->SetTexture(1, normalsTex);
[3129]740        sCgGiProgram->SetTexture(2, noiseTex2D);
[3034]741        sCgGiProgram->SetTexture(3, oldSsaoTex);
742        sCgGiProgram->SetTexture(4, oldIllumTex);
[2869]743
[3094]744        sCgGiProgram->SetValue1f(5,
[3026]745                (mUseTemporalCoherence && !mRegenerateSamples) ? tempCohFactor : 0);
[2869]746
[2895]747        if (mUseTemporalCoherence || mRegenerateSamples)
[2875]748        {
[2895]749                mRegenerateSamples = false;
[2873]750
[2875]751                // q: should we generate new samples or only rotate the old ones?
752                // in the first case, the sample patterns look nicer, but the kernel
753                // needs longer to converge
[2895]754                GenerateSamples(mSamplingMethod);
[2903]755
[3094]756                sCgGiProgram->SetArray2f(6, (float *)samples2, NUM_SAMPLES);
[2875]757        }
758
[3085]759        Vector3 bl = mCornersView[0];
760        Vector3 br = mCornersView[1];
761        Vector3 tl = mCornersView[2];
762        Vector3 tr = mCornersView[3];
[2895]763
[3094]764        sCgGiProgram->SetValue3f(7, bl.x, bl.y, bl.z);
765        sCgGiProgram->SetValue3f(8, br.x, br.y, br.z);
766        sCgGiProgram->SetValue3f(9, tl.x, tl.y, tl.z);
767        sCgGiProgram->SetValue3f(10, tr.x, tr.y, tr.z);
[2873]768
[3094]769        sCgGiProgram->SetMatrix(11, mOldProjViewMatrix);
770        sCgGiProgram->SetMatrix(12, mProjViewMatrix);
[3035]771
772
[3026]773        DrawQuad(sCgGiProgram);
[2990]774
[3006]775        glPopAttrib();
776
[2873]777        PrintGLerror("globillum first pass");
[2869]778}
779
780
[2896]781void DeferredRenderer::CombineIllum(FrameBufferObject *fbo)
[2880]782{
[2973]783        GLuint colorsTex = fbo->GetColorBuffer(colorBufferIdx)->GetTexture();
[2884]784
[3019]785        GLuint ssaoTex = mIllumFbo->GetColorBuffer(mIllumFboIndex)->GetTexture();
786        GLuint illumTex = mIllumFbo->GetColorBuffer(mIllumFboIndex + 1)->GetTexture();
[2880]787
[3132]788        FlipFbos(fbo);
[2891]789
[3036]790        sCgCombineIllumProgram->SetTexture(0, colorsTex);
791        sCgCombineIllumProgram->SetTexture(1, ssaoTex);
792        sCgCombineIllumProgram->SetTexture(2, illumTex);
[2880]793       
[3026]794        DrawQuad(sCgCombineIllumProgram);
[2880]795
796        PrintGLerror("combine");
797}
798
799
[2896]800void DeferredRenderer::CombineSsao(FrameBufferObject *fbo)
[2880]801{
[2973]802        GLuint colorsTex = fbo->GetColorBuffer(colorBufferIdx)->GetTexture();
[3103]803        GLuint normalsTex = fbo->GetColorBuffer(1)->GetTexture();
[3144]804        GLuint ssaoTex = mIllumFbo->GetColorBuffer(mIllumFboIndex)->GetTexture();
[3132]805       
806        FlipFbos(fbo);
[2880]807
[3104]808        int i = 0;
[3148]809
[3104]810        sCgCombineSsaoProgram->SetTexture(i ++, colorsTex);
[3120]811        sCgCombineSsaoProgram->SetTexture(i ++, normalsTex);
[3104]812        sCgCombineSsaoProgram->SetTexture(i ++, ssaoTex);
[3017]813
[3104]814        sCgCombineSsaoProgram->SetArray2f(i ++, (float *)ssaoFilterOffsets, NUM_SSAO_FILTERSAMPLES);
815        sCgCombineSsaoProgram->SetArray1f(i ++, (float *)ssaoFilterWeights, NUM_SSAO_FILTERSAMPLES);
[2880]816       
[3134]817        Vector3 bl = mCornersView[0];
818        Vector3 br = mCornersView[1];
819        Vector3 tl = mCornersView[2];
820        Vector3 tr = mCornersView[3];
821
822
[3026]823        DrawQuad(sCgCombineSsaoProgram);
[2974]824       
[2880]825        PrintGLerror("combine ssao");
826}
827
828
[2952]829void DeferredRenderer::FirstPassShadow(FrameBufferObject *fbo,
830                                                                           DirectionalLight *light,
831                                                                           ShadowMap *shadowMap)
[2895]832{
[2973]833        GLuint colorsTex = fbo->GetColorBuffer(colorBufferIdx)->GetTexture();
[3009]834        GLuint normalsTex = fbo->GetColorBuffer(1)->GetTexture();
[2973]835
[2928]836        GLuint shadowTex = shadowMap->GetDepthTexture();
[2895]837
838        Matrix4x4 shadowMatrix;
839        shadowMap->GetTextureMatrix(shadowMatrix);
840
841
[3132]842        FlipFbos(fbo);
843
[3035]844        sCgDeferredShadowProgram->SetTexture(0, colorsTex);
845        sCgDeferredShadowProgram->SetTexture(1, normalsTex);
846        sCgDeferredShadowProgram->SetTexture(2, shadowTex);
[3129]847        sCgDeferredShadowProgram->SetTexture(3, noiseTex2D);
[3035]848        sCgDeferredShadowProgram->SetMatrix(4, shadowMatrix);
849        sCgDeferredShadowProgram->SetValue1f(5, 2.0f / shadowMap->GetSize());
[3027]850
[3026]851        const Vector3 lightDir = -light->GetDirection();
[3035]852        sCgDeferredShadowProgram->SetValue3f(6, lightDir.x, lightDir.y, lightDir.z);
[3085]853        sCgDeferredShadowProgram->SetValue3f(7, mEyePos.x, mEyePos.y, mEyePos.z);
[2952]854
[3026]855        DrawQuad(sCgDeferredShadowProgram);
[2895]856
857        PrintGLerror("deferred shading + shadows");
858}
859
860
[2896]861void DeferredRenderer::SetSamplingMethod(SAMPLING_METHOD s)
[2895]862{
863        if (s != mSamplingMethod)
864        {
865                mSamplingMethod = s;
866                mRegenerateSamples = true;
867        }
868}
869
[2897]870
871void DeferredRenderer::SetShadingMethod(SHADING_METHOD s)
872{
873        if (s != mShadingMethod)
874        {
875                mShadingMethod = s;
876                mRegenerateSamples = true;
877        }
878}
879
[2965]880
[2973]881void DeferredRenderer::ComputeToneParameters(FrameBufferObject *fbo,
882                                                                                         DirectionalLight *light,
883                                                                                         float &imageKey,
884                                                                                         float &whiteLum,
885                                                                                         float &middleGrey)
[2972]886{
[2975]887        // hack: estimate value where sky burns out
[3010]888        whiteLum = log(WHITE_LUMINANCE);
[2973]889       
[2975]890        ////////////////////
891        //-- linear interpolate brightness key depending on the current sun position
[2973]892
893        const float minKey = 0.09f;
[3020]894        const float maxKey = 0.36f;
[2973]895
896        const float lightIntensity = DotProd(-light->GetDirection(), Vector3::UNIT_Z());
897        middleGrey = lightIntensity * maxKey + (1.0f - lightIntensity) * minKey;
[2991]898
[2993]899
[2991]900        //////////
901        //-- compute avg loglum
902
903        ColorBufferObject *colorBuffer = fbo->GetColorBuffer(colorBufferIdx);
[2992]904        GLuint colorsTex = colorBuffer->GetTexture();
[2991]905
[3132]906        FlipFbos(fbo);
[2991]907       
[3035]908        sCgLogLumProgram->SetTexture(0, colorsTex);
[3026]909        DrawQuad(sCgLogLumProgram);
[2991]910       
911        PrintGLerror("ToneMapParams");
912
913
914        ///////////////////
915        //-- compute avg loglum in scene using mipmapping
916
[3008]917        glBindTexture(GL_TEXTURE_2D, fbo->GetColorBuffer(colorBufferIdx)->GetTexture());
918        glGenerateMipmapEXT(GL_TEXTURE_2D);
[2972]919}
920
921
[3003]922static void ExportData(float *data, int w, int h)
923{
924        startil();
925
926        cout << "w: " << w << " h: " << h << endl;
927        ILstring filename = ILstring("downsample2.jpg");
928        ilRegisterType(IL_FLOAT);
929
930        const int depth = 1;
931        const int bpp = 4;
932
933        if (!ilTexImage(w, h, depth, bpp, IL_RGBA, IL_FLOAT, data))
934        {
935                cerr << "IL error " << ilGetError() << endl;
936                stopil();
937                return;
938        }
939
940        if (!ilSaveImage(filename))
941        {
942                cerr << "TGA write error " << ilGetError() << endl;
943        }
944
945        stopil();
946}
947
948
[3103]949void DeferredRenderer::DownSample(FrameBufferObject *fbo,
950                                                                  int bufferIdx,
[3006]951                                                                  FrameBufferObject *downSampleFbo,
[3137]952                                                                  int downSampleBufferIdx,
953                                                                  ShaderProgram *program)
[2972]954{
[3137]955        ColorBufferObject *buffer = fbo->GetColorBuffer(bufferIdx);
956        GLuint tex = buffer->GetTexture();
[3006]957
958        glPushAttrib(GL_VIEWPORT_BIT);
959        glViewport(0, 0, downSampleFbo->GetWidth(), downSampleFbo->GetHeight());
[2972]960       
[3137]961        downSampleFbo->Bind();
[2994]962
[3137]963        program->SetTexture(0, tex);
[3006]964        glDrawBuffers(1, mrt + downSampleBufferIdx);
[2994]965
[3137]966        DrawQuad(program);
[3005]967       
[3006]968        glPopAttrib();
[3005]969        PrintGLerror("downsample");
[2972]970}
971
972
[2973]973void DeferredRenderer::ToneMap(FrameBufferObject *fbo,
974                                                           float imageKey,
975                                                           float whiteLum,
976                                                           float middleGrey)
[2972]977{
978        ColorBufferObject *colorBuffer = fbo->GetColorBuffer(colorBufferIdx);
[2994]979        GLuint colorsTex = colorBuffer->GetTexture();
[3132]980        //FrameBufferObject::Release();
[2972]981
[3132]982        FlipFbos(fbo);
[3007]983
[3035]984        sCgToneProgram->SetTexture(0, colorsTex);
985        sCgToneProgram->SetValue1f(1, imageKey);
986        sCgToneProgram->SetValue1f(2, whiteLum);
987        sCgToneProgram->SetValue1f(3, middleGrey);
[3007]988
[3026]989        DrawQuad(sCgToneProgram);
[2972]990
[2973]991        PrintGLerror("ToneMap");
[2972]992}
993
994
[3132]995void DeferredRenderer::Output(FrameBufferObject *fbo)
996{
997        glPushAttrib(GL_VIEWPORT_BIT);
998        glViewport(0, 0, fbo->GetWidth(), fbo->GetHeight());
999       
1000        ColorBufferObject *colorBuffer = fbo->GetColorBuffer(colorBufferIdx);
1001        GLuint colorsTex = colorBuffer->GetTexture();
1002
1003        sCgDownSampleProgram->SetTexture(0, colorsTex);
1004
1005        FrameBufferObject::Release();
1006        DrawQuad(sCgDownSampleProgram);
1007
[3134]1008        PrintGLerror("output");
[3132]1009}
1010
1011
[3081]1012/*
1013void DeferredRenderer::BackProject(FrameBufferObject *fbo)
1014{
1015        // back project new frame into old one and check
1016        // if pixel still valid. store this property with ssao texture or even
1017        // betteer with color / depth texture. This way
1018        // we can sample this property together with the color / depth
1019        // values and we do not require additional texture lookups
1020        fbo->Bind();
1021
1022        GLuint colorsTex = fbo->GetColorBuffer(colorBufferIdx)->GetTexture();
1023        //GLuint ssaoTex = mIllumFbo->GetColorBuffer(mIllumFboIndex)->GetTexture();
1024
1025        // overwrite old color texture
1026        //colorBufferIdx = 3 - colorBufferIdx;
1027        //glDrawBuffers(1, mrt + colorBufferIdx);
1028        //glDrawBuffers(1, mrt + colorBufferIdx);
1029
1030        sCgCombineSsaoProgram->SetTexture(0, colorsTex);
1031        //sCgCombineSsaoProgram->SetTexture(1, ssaoTex);
1032
1033        DrawQuad(sCgBackProjectProgram);
1034       
1035        PrintGLerror("combine ssao");
1036}
1037*/
1038
1039
[3085]1040void DeferredRenderer::InitFrame()
1041{
[3095]1042        for (int i = 0; i < 4; ++ i)
1043                mOldCornersView[i] = mCornersView[i];
1044
[3085]1045        mOldProjViewMatrix = mProjViewMatrix;
[3095]1046        mOldEyePos = mEyePos;
[3106]1047        mEyePos = mCamera->GetPosition();
[3081]1048
[3104]1049        // hack: temporarily change far to improve precision
1050        const float oldFar = mCamera->GetFar();
[3106]1051        const float oldNear = mCamera->GetNear();
[3132]1052       
[3104]1053
[3106]1054        Matrix4x4 matViewing, matProjection;
[3104]1055
[3106]1056
1057        ///////////////////
1058
1059
[3093]1060        mCamera->GetViewOrientationMatrix(matViewing);
[3085]1061        mCamera->GetProjectionMatrix(matProjection);
1062
1063        mProjViewMatrix = matViewing * matProjection;
[3095]1064        ComputeViewVectors(mCamera, mCornersView[0], mCornersView[1], mCornersView[2], mCornersView[3]);
[3106]1065       
[3085]1066
[3095]1067        // switch roles of old and new fbo
1068        // the algorihm uses two input fbos, where the one
1069        // contais the color buffer from the last frame,
1070        // the other one will be written
[3085]1071
[3095]1072        mIllumFboIndex = 2 - mIllumFboIndex;
1073       
1074        // enable fragment shading
1075        ShaderManager::GetSingleton()->EnableFragmentProfile();
1076
1077        glDisable(GL_ALPHA_TEST);
1078        glDisable(GL_TEXTURE_2D);
1079        glDisable(GL_LIGHTING);
1080        glDisable(GL_BLEND);
1081        glDisable(GL_DEPTH_TEST);
1082
1083        glPolygonMode(GL_FRONT, GL_FILL);
1084
1085        glMatrixMode(GL_PROJECTION);
1086        glPushMatrix();
1087        glLoadIdentity();
1088
1089        gluOrtho2D(0, 1, 0, 1);
1090
1091
1092        glMatrixMode(GL_MODELVIEW);
1093        glPushMatrix();
1094        glLoadIdentity();
1095
1096       
1097        glPushAttrib(GL_VIEWPORT_BIT);
1098        glViewport(0, 0, mWidth, mHeight);
[3128]1099
1100        // revert to old far and near plane
[3104]1101        mCamera->SetFar(oldFar);
[3106]1102        mCamera->SetNear(oldNear);
[3085]1103}
1104
1105
[2858]1106} // namespace
Note: See TracBrowser for help on using the repository browser.