File: lightmanager.hpp

package info (click to toggle)
openmw 0.49.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,992 kB
  • sloc: cpp: 372,479; xml: 2,149; sh: 1,403; python: 797; makefile: 26
file content (385 lines) | stat: -rw-r--r-- 14,542 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
#ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H
#define OPENMW_COMPONENTS_SCENEUTIL_LIGHTMANAGER_H

#include <array>
#include <memory>
#include <set>
#include <unordered_map>

#include <osg/Group>
#include <osg/Light>
#include <osg/NodeVisitor>
#include <osg/observer_ptr>

#include <components/sceneutil/nodecallback.hpp>

#include "lightingmethod.hpp"

namespace SceneUtil
{
    class LightBuffer;
    struct StateSetGenerator;

    class PPLightBuffer
    {
    public:
        inline static constexpr auto sMaxPPLights = 40;
        inline static constexpr auto sMaxPPLightsArraySize = sMaxPPLights * 3;

        PPLightBuffer()
        {
            for (size_t i = 0; i < 2; ++i)
            {
                mIndex[i] = 0;
                mUniformBuffers[i]
                    = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "omw_PointLights", sMaxPPLightsArraySize);
                mUniformCount[i] = new osg::Uniform("omw_PointLightsCount", static_cast<int>(0));
            }
        }

        void applyUniforms(size_t frame, osg::StateSet* stateset)
        {
            size_t frameId = frame % 2;

            if (!stateset->getUniform("omw_PointLights"))
                stateset->addUniform(mUniformBuffers[frameId]);
            if (!stateset->getUniform("omw_PointLightsCount"))
                stateset->addUniform(mUniformCount[frameId]);

            mUniformBuffers[frameId]->dirty();
            mUniformCount[frameId]->dirty();
        }

        void clear(size_t frame) { mIndex[frame % 2] = 0; }

        void setLight(size_t frame, const osg::Light* light, float radius)
        {
            size_t frameId = frame % 2;
            size_t i = mIndex[frameId];

            if (i >= (sMaxPPLights - 1))
                return;

            i *= 3;

            mUniformBuffers[frameId]->setElement(i + 0, light->getPosition());
            mUniformBuffers[frameId]->setElement(i + 1, light->getDiffuse());
            mUniformBuffers[frameId]->setElement(i + 2,
                osg::Vec4f(light->getConstantAttenuation(), light->getLinearAttenuation(),
                    light->getQuadraticAttenuation(), radius));

            mIndex[frameId]++;
        }

        void updateCount(size_t frame)
        {
            size_t frameId = frame % 2;
            mUniformCount[frameId]->set(static_cast<int>(mIndex[frameId]));
        }

    private:
        std::array<size_t, 2> mIndex;
        std::array<osg::ref_ptr<osg::Uniform>, 2> mUniformBuffers;
        std::array<osg::ref_ptr<osg::Uniform>, 2> mUniformCount;
    };

    /// LightSource managed by a LightManager.
    /// @par Typically used for point lights. Spot lights are not supported yet. Directional lights affect the whole
    /// scene
    ///     so do not need to be managed by a LightManager - so for directional lights use a plain osg::LightSource
    ///     instead.
    /// @note LightSources must be decorated by a LightManager node in order to have an effect. Typical use would
    ///     be one LightManager as the root of the scene graph.
    /// @note One needs to attach LightListCallback's to the scene to have objects receive lighting from LightSources.
    ///     See the documentation of LightListCallback for more information.
    /// @note The position of the contained osg::Light is automatically updated based on the LightSource's world
    /// position.
    class LightSource : public osg::Node
    {
        // double buffered osg::Light's, since one of them may be in use by the draw thread at any given time
        std::array<osg::ref_ptr<osg::Light>, 2> mLight;

        // LightSource will affect objects within this radius
        float mRadius;

        int mId;

        float mActorFade;

        unsigned int mLastAppliedFrame;

        bool mEmpty = false;

    public:
        META_Node(SceneUtil, LightSource)

        LightSource();

        LightSource(const LightSource& copy, const osg::CopyOp& copyop);

        float getRadius() const { return mRadius; }

        /// The LightSource will affect objects within this radius.
        void setRadius(float radius) { mRadius = radius; }

        void setActorFade(float alpha) { mActorFade = alpha; }

        float getActorFade() const { return mActorFade; }

        void setEmpty(bool empty) { mEmpty = empty; }

        bool getEmpty() const { return mEmpty; }

        /// Get the osg::Light safe for modification in the given frame.
        /// @par May be used externally to animate the light's color/attenuation properties,
        /// and is used internally to synchronize the light's position with the position of the LightSource.
        osg::Light* getLight(size_t frame) { return mLight[frame % 2]; }

        /// @warning It is recommended not to replace an existing osg::Light, because there might still be
        /// references to it in the light StateSet cache that are associated with this LightSource's ID.
        /// These references will stay valid due to ref_ptr but will point to the old object.
        /// @warning Do not modify the \a light after you've called this function.
        void setLight(osg::Light* light)
        {
            mLight[0] = light;
            mLight[1] = new osg::Light(*light);
        }

        /// Get the unique ID for this light source.
        int getId() const { return mId; }

        void setLastAppliedFrame(unsigned int lastAppliedFrame) { mLastAppliedFrame = lastAppliedFrame; }

        unsigned int getLastAppliedFrame() const { return mLastAppliedFrame; }
    };

    class UBOManager : public osg::StateAttribute
    {
    public:
        UBOManager(int lightCount = 1);
        UBOManager(const UBOManager& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);

        void releaseGLObjects(osg::State* state) const override;

        int compare(const StateAttribute& sa) const override;

        META_StateAttribute(SceneUtil, UBOManager, osg::StateAttribute::LIGHT)

        void apply(osg::State& state) const override;

        auto& getLightBuffer(size_t frameNum) { return mLightBuffers[frameNum % 2]; }

    private:
        std::string generateDummyShader(int maxLightsInScene);
        void initSharedLayout(osg::GLExtensions* ext, int handle, unsigned int frame) const;

        osg::ref_ptr<osg::Program> mDummyProgram;
        mutable bool mInitLayout;
        mutable std::array<osg::ref_ptr<LightBuffer>, 2> mLightBuffers;
        mutable std::array<bool, 2> mDirty;
        osg::ref_ptr<LightBuffer> mTemplate;
    };

    struct LightSettings
    {
        LightingMethod mLightingMethod = LightingMethod::FFP;
        int mMaxLights = 0;
        float mMaximumLightDistance = 0;
        float mLightFadeStart = 0;
        float mLightBoundsMultiplier = 0;
    };

    /// @brief Decorator node implementing the rendering of any number of LightSources that can be anywhere in the
    /// subgraph.
    class LightManager : public osg::Group
    {
    public:
        static LightingMethod getLightingMethodFromString(const std::string& value);
        /// Returns string as used in settings file, or the empty string if the method is undefined
        static std::string getLightingMethodString(LightingMethod method);

        struct LightSourceTransform
        {
            LightSource* mLightSource;
            osg::Matrixf mWorldMatrix;
        };

        struct LightSourceViewBound
        {
            LightSource* mLightSource;
            osg::BoundingSphere mViewBound;
        };

        using LightList = std::vector<const LightSourceViewBound*>;
        using SupportedMethods = std::array<bool, 3>;

        META_Node(SceneUtil, LightManager)

        explicit LightManager(const LightSettings& settings = LightSettings{});

        LightManager(const LightManager& copy, const osg::CopyOp& copyop);

        /// @param mask This mask is compared with the current Camera's cull mask to determine if lighting is desired.
        /// By default, it's ~0u i.e. always on.
        /// If you have some views that do not require lighting, then set the Camera's cull mask to not include
        /// the lightingMask for a much faster cull and rendering.
        void setLightingMask(size_t mask);
        size_t getLightingMask() const;

        /// Set the first light index that should be used by this manager, typically the number of directional lights in
        /// the scene.
        void setStartLight(int start);
        int getStartLight() const;

        /// Internal use only, called automatically by the LightManager's UpdateCallback
        void update(size_t frameNum);

        /// Internal use only, called automatically by the LightSource's UpdateCallback
        void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum);

        const std::vector<LightSourceViewBound>& getLightsInViewSpace(
            osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum);

        osg::ref_ptr<osg::StateSet> getLightListStateSet(
            const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix);

        void setSunlight(osg::ref_ptr<osg::Light> sun);
        osg::ref_ptr<osg::Light> getSunlight();

        bool usingFFP() const;

        LightingMethod getLightingMethod() const;

        int getMaxLights() const;

        int getMaxLightsInScene() const;

        auto& getDummies() { return mDummies; }

        auto& getLightIndexMap(size_t frameNum) { return mLightIndexMaps[frameNum % 2]; }

        auto& getUBOManager() { return mUBOManager; }

        osg::Matrixf getSunlightBuffer(size_t frameNum) const { return mSunlightBuffers[frameNum % 2]; }
        void setSunlightBuffer(const osg::Matrixf& buffer, size_t frameNum) { mSunlightBuffers[frameNum % 2] = buffer; }

        SupportedMethods getSupportedLightingMethods() { return mSupported; }

        std::map<std::string, std::string> getLightDefines() const;

        void processChangedSettings(float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart);

        /// Not thread safe, it is the responsibility of the caller to stop/start threading on the viewer
        void updateMaxLights(int maxLights);

        osg::ref_ptr<osg::Uniform> generateLightBufferUniform(const osg::Matrixf& sun);

        // Whether to collect main scene camera points lights into a buffer to be later sent to postprocessing shaders
        void setCollectPPLights(bool enabled);

        std::shared_ptr<PPLightBuffer> getPPLightsBuffer() { return mPPLightBuffer; }

    private:
        void initFFP(int targetLights);
        void initPerObjectUniform(int targetLights);
        void initSingleUBO(int targetLights);

        void updateSettings(float lightBoundsMultiplier, float maximumLightDistance, float lightFadeStart);

        void setLightingMethod(LightingMethod method);
        void setMaxLights(int value);

        void updateGPUPointLight(
            int index, LightSource* lightSource, size_t frameNum, const osg::RefMatrix* viewMatrix);

        std::vector<LightSourceTransform> mLights;

        using LightSourceViewBoundCollection = std::vector<LightSourceViewBound>;
        std::map<osg::observer_ptr<osg::Camera>, LightSourceViewBoundCollection> mLightsInViewSpace;

        using LightIdList = std::vector<int>;
        struct HashLightIdList
        {
            size_t operator()(const LightIdList&) const;
        };
        using LightStateSetMap = std::unordered_map<LightIdList, osg::ref_ptr<osg::StateSet>, HashLightIdList>;
        LightStateSetMap mStateSetCache[2];

        std::vector<osg::ref_ptr<osg::StateAttribute>> mDummies;

        int mStartLight;

        size_t mLightingMask;

        osg::ref_ptr<osg::Light> mSun;

        osg::Matrixf mSunlightBuffers[2];

        // < Light ID , Buffer Index >
        using LightIndexMap = std::unordered_map<int, int>;
        LightIndexMap mLightIndexMaps[2];

        std::unique_ptr<StateSetGenerator> mStateSetGenerator;

        osg::ref_ptr<UBOManager> mUBOManager;

        LightingMethod mLightingMethod;

        float mPointLightRadiusMultiplier;
        float mPointLightFadeEnd;
        float mPointLightFadeStart;

        int mMaxLights;

        SupportedMethods mSupported;

        std::shared_ptr<PPLightBuffer> mPPLightBuffer;
    };

    /// To receive lighting, objects must be decorated by a LightListCallback. Light list callbacks must be added via
    /// node->addCullCallback(new LightListCallback). Once a light list callback is added to a node, that node and all
    /// its child nodes can receive lighting.
    /// @par The placement of these LightListCallbacks affects the granularity of light lists. Having too fine grained
    /// light lists can result in degraded performance. Too coarse grained light lists can result in lights no longer
    /// rendering when the size of a light list exceeds the OpenGL limit on the number of concurrent lights (8). A good
    /// starting point is to attach a LightListCallback to each game object's base node.
    /// @note Not thread safe for CullThreadPerCamera threading mode.
    /// @note Due to lack of OSG support, the callback does not work on Drawables.
    class LightListCallback : public SceneUtil::NodeCallback<LightListCallback, osg::Node*, osgUtil::CullVisitor*>
    {
    public:
        LightListCallback()
            : mLightManager(nullptr)
            , mLastFrameNumber(0)
        {
        }
        LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop)
            : osg::Object(copy, copyop)
            , SceneUtil::NodeCallback<LightListCallback, osg::Node*, osgUtil::CullVisitor*>(copy, copyop)
            , mLightManager(copy.mLightManager)
            , mLastFrameNumber(0)
            , mIgnoredLightSources(copy.mIgnoredLightSources)
        {
        }

        META_Object(SceneUtil, LightListCallback)

        void operator()(osg::Node* node, osgUtil::CullVisitor* nv);

        bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv);

        std::set<SceneUtil::LightSource*>& getIgnoredLightSources() { return mIgnoredLightSources; }

    private:
        LightManager* mLightManager;
        size_t mLastFrameNumber;
        LightManager::LightList mLightList;
        std::set<SceneUtil::LightSource*> mIgnoredLightSources;
    };

    void configureStateSetSunOverride(LightManager* lightManager, const osg::Light* light, osg::StateSet* stateset,
        int mode = osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);

}

#endif