Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/usability/src/libraries/core/GraphicsManager.cc @ 7995

Last change on this file since 7995 was 7995, checked in by rgrieder, 13 years ago

Changed call to frame rendering in GraphicsManager so that Ogre always receives exactly the delta times it expects.
Just to be sure.

  • Property svn:eol-style set to native
File size: 21.4 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Reto Grieder
24 *      Benjamin Knecht <beni_at_orxonox.net>, (C) 2007
25 *   Co-authors:
26 *      Felix Schulthess
27 *
28 */
29
30#include "GraphicsManager.h"
31
32#include <fstream>
33#include <sstream>
34#include <boost/filesystem.hpp>
35#include <boost/shared_array.hpp>
36
37#include <OgreArchiveFactory.h>
38#include <OgreArchiveManager.h>
39#include <OgreFrameListener.h>
40#include <OgreRoot.h>
41#include <OgreLogManager.h>
42#include <OgreRenderWindow.h>
43#include <OgreRenderSystem.h>
44#include <OgreResourceGroupManager.h>
45#include <OgreTextureManager.h>
46#include <OgreViewport.h>
47#include <OgreWindowEventUtilities.h>
48
49#include "SpecialConfig.h"
50#include "util/Clock.h"
51#include "util/Convert.h"
52#include "util/Exception.h"
53#include "util/StringUtils.h"
54#include "util/SubString.h"
55#include "ConfigValueIncludes.h"
56#include "CoreIncludes.h"
57#include "Core.h"
58#include "Game.h"
59#include "GameMode.h"
60#include "GUIManager.h"
61#include "Loader.h"
62#include "MemoryArchive.h"
63#include "PathConfig.h"
64#include "ViewportEventListener.h"
65#include "WindowEventListener.h"
66#include "XMLFile.h"
67#include "command/ConsoleCommand.h"
68
69namespace orxonox
70{
71    static const std::string __CC_GraphicsManager_group = "GraphicsManager";
72    static const std::string __CC_setScreenResolution_name = "setScreenResolution";
73    static const std::string __CC_setFSAA_name = "setFSAA";
74    static const std::string __CC_setVSync_name = "setVSync";
75    DeclareConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name, &prototype::string__uint_uint_bool);
76    DeclareConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name, &prototype::string__string);
77    DeclareConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name, &prototype::string__bool);
78
79    static const std::string __CC_printScreen_name = "printScreen";
80    DeclareConsoleCommand(__CC_printScreen_name, &prototype::void__void);
81
82    class OgreWindowEventListener : public Ogre::WindowEventListener
83    {
84    public:
85        void windowResized     (Ogre::RenderWindow* rw)
86            { orxonox::WindowEventListener::resizeWindow(rw->getWidth(), rw->getHeight()); }
87        void windowFocusChange (Ogre::RenderWindow* rw)
88            { orxonox::WindowEventListener::changeWindowFocus(rw->isActive()); }
89        void windowClosed      (Ogre::RenderWindow* rw)
90            { orxonox::Game::getInstance().stop(); }
91        void windowMoved       (Ogre::RenderWindow* rw)
92            { orxonox::WindowEventListener::moveWindow(); }
93    };
94
95    GraphicsManager* GraphicsManager::singletonPtr_s = 0;
96
97    /**
98    @brief
99        Non-initialising constructor.
100    */
101    GraphicsManager::GraphicsManager(bool bLoadRenderer)
102        : ogreWindowEventListener_(new OgreWindowEventListener())
103#if OGRE_VERSION < 0x010600
104        , memoryArchiveFactory_(new MemoryArchiveFactory())
105#endif
106        , renderWindow_(0)
107        , viewport_(0)
108        , lastFrameStartTime_(0.0f)
109        , lastFrameEndTime_(0.0f)
110    {
111        RegisterObject(GraphicsManager);
112
113        this->setConfigValues();
114
115        // Ogre setup procedure (creating Ogre::Root)
116        this->loadOgreRoot();
117
118        // At first, add the root paths of the data directories as resource locations
119        Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getDataPathString(), "FileSystem");
120        // Load resources
121        resources_.reset(new XMLFile("DefaultResources.oxr"));
122        resources_->setLuaSupport(false);
123        Loader::open(resources_.get());
124
125        // Only for development runs
126        if (PathConfig::isDevelopmentRun())
127        {
128            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getExternalDataPathString(), "FileSystem");
129            extResources_.reset(new XMLFile("resources.oxr"));
130            extResources_->setLuaSupport(false);
131            Loader::open(extResources_.get());
132        }
133
134        if (bLoadRenderer)
135        {
136            // Reads the ogre config and creates the render window
137            this->upgradeToGraphics();
138        }
139    }
140
141    /**
142    @brief
143        Destruction is done by the member scoped_ptrs.
144    */
145    GraphicsManager::~GraphicsManager()
146    {
147        Loader::unload(debugOverlay_.get());
148
149        Ogre::WindowEventUtilities::removeWindowEventListener(renderWindow_, ogreWindowEventListener_.get());
150        ModifyConsoleCommand(__CC_printScreen_name).resetFunction();
151        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name).resetFunction();
152        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name).resetFunction();
153        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name).resetFunction();
154
155        // Undeclare the resources
156        Loader::unload(resources_.get());
157        if (PathConfig::isDevelopmentRun())
158            Loader::unload(extResources_.get());
159    }
160
161    void GraphicsManager::setConfigValues()
162    {
163        SetConfigValue(ogreConfigFile_,  "ogre.cfg")
164            .description("Location of the Ogre config file");
165        SetConfigValue(ogrePluginsDirectory_, specialConfig::ogrePluginsDirectory)
166            .description("Folder where the Ogre plugins are located.");
167        SetConfigValue(ogrePlugins_, specialConfig::ogrePlugins)
168            .description("Comma separated list of all plugins to load.");
169        SetConfigValue(ogreLogFile_,     "ogre.log")
170            .description("Logfile for messages from Ogre. Use \"\" to suppress log file creation.");
171        SetConfigValue(ogreLogLevelTrivial_ , 5)
172            .description("Corresponding orxonox debug level for ogre Trivial");
173        SetConfigValue(ogreLogLevelNormal_  , 4)
174            .description("Corresponding orxonox debug level for ogre Normal");
175        SetConfigValue(ogreLogLevelCritical_, 2)
176            .description("Corresponding orxonox debug level for ogre Critical");
177    }
178
179    /**
180    @brief
181        Loads the renderer and creates the render window if not yet done so.
182    @remarks
183        This operation is irreversible without recreating the GraphicsManager!
184        So if it throws you HAVE to recreate the GraphicsManager!!!
185        It therefore offers almost no exception safety.
186    */
187    void GraphicsManager::upgradeToGraphics()
188    {
189        if (renderWindow_ != NULL)
190            return;
191
192        // load all the required plugins for Ogre
193        this->loadOgrePlugins();
194
195        this->loadRenderer();
196
197#if OGRE_VERSION < 0x010600
198        // WORKAROUND: There is an incompatibility for particle scripts when trying
199        // to support both Ogre 1.4 and 1.6. The hacky solution is to create
200        // scripts for the 1.6 version and then remove the inserted "particle_system"
201        // keyword. But we need to supply these new scripts as well, which is why
202        // there is an extra Ogre::Archive dealing with it in the memory.
203        using namespace Ogre;
204        ArchiveManager::getSingleton().addArchiveFactory(memoryArchiveFactory_.get());
205        const StringVector& groups = ResourceGroupManager::getSingleton().getResourceGroups();
206        // Travers all groups
207        for (StringVector::const_iterator itGroup = groups.begin(); itGroup != groups.end(); ++itGroup)
208        {
209            FileInfoListPtr files = ResourceGroupManager::getSingleton().findResourceFileInfo(*itGroup, "*.particle");
210            for (FileInfoList::const_iterator itFile = files->begin(); itFile != files->end(); ++itFile)
211            {
212                // open file
213                Ogre::DataStreamPtr input = ResourceGroupManager::getSingleton().openResource(itFile->filename, *itGroup, false);
214                std::stringstream output;
215                // Parse file and replace "particle_system" with nothing
216                while (!input->eof())
217                {
218                    std::string line = input->getLine();
219                    size_t pos = line.find("particle_system");
220                    if (pos != std::string::npos)
221                    {
222                        // 15 is the length of "particle_system"
223                        line.replace(pos, 15, "");
224                    }
225                    output << line << std::endl;
226                }
227                // Add file to the memory archive
228                shared_array<char> data(new char[output.str().size()]);
229                // Debug optimisations
230                const std::string& outputStr = output.str();
231                char* rawData = data.get();
232                for (unsigned i = 0; i < outputStr.size(); ++i)
233                    rawData[i] = outputStr[i];
234                MemoryArchive::addFile("particle_scripts_ogre_1.4_" + *itGroup, itFile->filename, data, output.str().size());
235            }
236            if (!files->empty())
237            {
238                // Declare the files, but using a new group
239                ResourceGroupManager::getSingleton().addResourceLocation("particle_scripts_ogre_1.4_" + *itGroup,
240                    "Memory", "particle_scripts_ogre_1.4_" + *itGroup);
241            }
242        }
243#endif
244
245        // Initialise all resources (do this AFTER the renderer has been loaded!)
246        // Note: You can only do this once! Ogre will check whether a resource group has
247        // already been initialised. If you need to load resources later, you will have to
248        // choose another resource group.
249        Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
250    }
251
252    /**
253    @brief
254        Creates the Ogre Root object and sets up the ogre log.
255    */
256    void GraphicsManager::loadOgreRoot()
257    {
258        COUT(3) << "Setting up Ogre..." << std::endl;
259
260        if (ogreConfigFile_.empty())
261        {
262            COUT(2) << "Warning: Ogre config file set to \"\". Defaulting to config.cfg" << std::endl;
263            ModifyConfigValue(ogreConfigFile_, tset, "config.cfg");
264        }
265        if (ogreLogFile_.empty())
266        {
267            COUT(2) << "Warning: Ogre log file set to \"\". Defaulting to ogre.log" << std::endl;
268            ModifyConfigValue(ogreLogFile_, tset, "ogre.log");
269        }
270
271        boost::filesystem::path ogreConfigFilepath(PathConfig::getConfigPath() / this->ogreConfigFile_);
272        boost::filesystem::path ogreLogFilepath(PathConfig::getLogPath() / this->ogreLogFile_);
273
274        // create a new logManager
275        // Ogre::Root will detect that we've already created a Log
276        ogreLogger_.reset(new Ogre::LogManager());
277        COUT(4) << "Ogre LogManager created" << std::endl;
278
279        // create our own log that we can listen to
280        Ogre::Log *myLog;
281        myLog = ogreLogger_->createLog(ogreLogFilepath.string(), true, false, false);
282        COUT(4) << "Ogre Log created" << std::endl;
283
284        myLog->setLogDetail(Ogre::LL_BOREME);
285        myLog->addListener(this);
286
287        COUT(4) << "Creating Ogre Root..." << std::endl;
288
289        // check for config file existence because Ogre displays (caught) exceptions if not
290        if (!boost::filesystem::exists(ogreConfigFilepath))
291        {
292            // create a zero sized file
293            std::ofstream creator;
294            creator.open(ogreConfigFilepath.string().c_str());
295            creator.close();
296        }
297
298        // Leave plugins file empty. We're going to do that part manually later
299        ogreRoot_.reset(new Ogre::Root("", ogreConfigFilepath.string(), ogreLogFilepath.string()));
300
301        COUT(3) << "Ogre set up done." << std::endl;
302    }
303
304    void GraphicsManager::loadOgrePlugins()
305    {
306        // just to make sure the next statement doesn't segfault
307        if (ogrePluginsDirectory_.empty())
308            ogrePluginsDirectory_ = '.';
309
310        boost::filesystem::path folder(ogrePluginsDirectory_);
311        // Do some SubString magic to get the comma separated list of plugins
312        SubString plugins(ogrePlugins_, ",", " ", false, '\\', false, '"', false, '{', '}', false, '\0');
313        // Use backslash paths on Windows! file_string() already does that though.
314        for (unsigned int i = 0; i < plugins.size(); ++i)
315            ogreRoot_->loadPlugin((folder / plugins[i]).file_string());
316    }
317
318    void GraphicsManager::loadRenderer()
319    {
320        CCOUT(4) << "Configuring Renderer" << std::endl;
321
322        bool updatedConfig = Core::getInstance().getOgreConfigTimestamp() > Core::getInstance().getLastLevelTimestamp();
323        if (updatedConfig)
324            COUT(2) << "Ogre config file has changed, but no level was started since then. Displaying config dialogue again to verify the changes." << std::endl;
325
326        if (!ogreRoot_->restoreConfig() || updatedConfig)
327        {
328            if (!ogreRoot_->showConfigDialog())
329                ThrowException(InitialisationFailed, "OGRE graphics configuration dialogue canceled.");
330            else
331                Core::getInstance().updateOgreConfigTimestamp();
332        }
333
334        CCOUT(4) << "Creating render window" << std::endl;
335
336        this->renderWindow_ = ogreRoot_->initialise(true, "Orxonox");
337        // Propagate the size of the new winodw
338        this->ogreWindowEventListener_->windowResized(renderWindow_);
339
340        Ogre::WindowEventUtilities::addWindowEventListener(this->renderWindow_, ogreWindowEventListener_.get());
341
342        // create a full screen default viewport
343        // Note: This may throw when adding a viewport with an existing z-order!
344        //       But in our case we only have one viewport for now anyway, therefore
345        //       no ScopeGuards or anything to handle exceptions.
346        this->viewport_ = this->renderWindow_->addViewport(0, 0);
347
348        Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(Ogre::MIP_UNLIMITED);
349
350        // add console commands
351        ModifyConsoleCommand(__CC_printScreen_name).setFunction(&GraphicsManager::printScreen, this);
352        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name).setFunction(&GraphicsManager::setScreenResolution, this);
353        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name).setFunction(&GraphicsManager::setFSAA, this);
354        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name).setFunction(&GraphicsManager::setVSync, this);
355    }
356
357    void GraphicsManager::loadDebugOverlay()
358    {
359        // Load debug overlay to show info about fps and tick time
360        COUT(4) << "Loading Debug Overlay..." << std::endl;
361        debugOverlay_.reset(new XMLFile("debug.oxo"));
362        Loader::open(debugOverlay_.get());
363    }
364
365    /**
366    @note
367        A note about the Ogre::FrameListener: Even though we don't use them,
368        they still get called.
369    */
370    void GraphicsManager::postUpdate(const Clock& time)
371    {
372        // Time before rendering
373        uint64_t timeBeforeTick = time.getRealMicroseconds();
374
375        // Ogre's time keeping object
376        Ogre::FrameEvent evt;
377
378        // Translate to Ogre float times before the update
379        float temp = lastFrameStartTime_;
380        lastFrameStartTime_ = (float)(timeBeforeTick / 1000000);
381        evt.timeSinceLastFrame = lastFrameStartTime_ - temp;
382        evt.timeSinceLastEvent = lastFrameStartTime_ - lastFrameEndTime_;
383
384        // Ogre requires the time too
385        ogreRoot_->_fireFrameStarted(evt);
386
387        // Pump messages in all registered RenderWindows
388        // This calls the WindowEventListener objects.
389        Ogre::WindowEventUtilities::messagePump();
390        // Make sure the window stays active even when not focused
391        // (probably only necessary on windows)
392        this->renderWindow_->setActive(true);
393
394        // Render frame
395        ogreRoot_->_updateAllRenderTargets();
396
397        uint64_t timeAfterTick = time.getRealMicroseconds();
398        // Subtract the time used for rendering from the tick time counter
399        Game::getInstance().subtractTickTime((int32_t)(timeAfterTick - timeBeforeTick));
400
401        // Translate to Ogre float times after the update
402        temp = lastFrameEndTime_;
403        lastFrameEndTime_ = (float)(timeAfterTick / 1000000);
404        evt.timeSinceLastFrame = lastFrameEndTime_ - temp;
405        evt.timeSinceLastEvent = lastFrameEndTime_ - lastFrameStartTime_;
406
407        // Ogre also needs the time after the frame finished
408        ogreRoot_->_fireFrameEnded(evt);
409    }
410
411    void GraphicsManager::setCamera(Ogre::Camera* camera)
412    {
413        Ogre::Camera* oldCamera = this->viewport_->getCamera();
414
415        this->viewport_->setCamera(camera);
416        GUIManager::getInstance().setCamera(camera);
417
418        for (ObjectList<ViewportEventListener>::iterator it = ObjectList<ViewportEventListener>::begin(); it != ObjectList<ViewportEventListener>::end(); ++it)
419            it->cameraChanged(this->viewport_, oldCamera);
420    }
421
422    /**
423    @brief
424        Method called by the LogListener interface from Ogre.
425        We use it to capture Ogre log messages and handle it ourselves.
426    @param message
427        The message to be logged
428    @param lml
429        The message level the log is using
430    @param maskDebug
431        If we are printing to the console or not
432    @param logName
433        The name of this log (so you can have several listeners
434        for different logs, and identify them)
435    */
436    void GraphicsManager::messageLogged(const std::string& message,
437        Ogre::LogMessageLevel lml, bool maskDebug, const std::string& logName)
438    {
439        int orxonoxLevel;
440        std::string introduction;
441        // Do not show caught OGRE exceptions in front
442        if (message.find("EXCEPTION") != std::string::npos)
443        {
444            orxonoxLevel = OutputLevel::Debug;
445            introduction = "Ogre, caught exception: ";
446        }
447        else
448        {
449            switch (lml)
450            {
451            case Ogre::LML_TRIVIAL:
452                orxonoxLevel = this->ogreLogLevelTrivial_;
453                break;
454            case Ogre::LML_NORMAL:
455                orxonoxLevel = this->ogreLogLevelNormal_;
456                break;
457            case Ogre::LML_CRITICAL:
458                orxonoxLevel = this->ogreLogLevelCritical_;
459                break;
460            default:
461                orxonoxLevel = 0;
462            }
463            introduction = "Ogre: ";
464        }
465        OutputHandler::getOutStream(orxonoxLevel)
466            << introduction << message << std::endl;
467    }
468
469    size_t GraphicsManager::getRenderWindowHandle()
470    {
471        size_t windowHnd = 0;
472        renderWindow_->getCustomAttribute("WINDOW", &windowHnd);
473        return windowHnd;
474    }
475
476    bool GraphicsManager::isFullScreen() const
477    {
478        return this->renderWindow_->isFullScreen();
479    }
480
481    bool GraphicsManager::hasVSyncEnabled() const
482    {
483        Ogre::ConfigOptionMap& options = ogreRoot_->getRenderSystem()->getConfigOptions();
484        if (options.find("VSync") != options.end())
485            return (options["VSync"].currentValue == "Yes");
486        else
487            return false;
488    }
489
490    std::string GraphicsManager::setScreenResolution(unsigned int width, unsigned int height, bool fullscreen)
491    {
492        this->ogreRoot_->getRenderSystem()->setConfigOption("Video Mode", multi_cast<std::string>(width) + " x " + multi_cast<std::string>(height) + " @ " + multi_cast<std::string>(this->getRenderWindow()->getColourDepth()) + "-bit colour");
493        this->ogreRoot_->getRenderSystem()->setConfigOption("Full Screen", fullscreen ? "Yes" : "No");
494
495        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
496
497        if (validate == "")
498        {
499            GraphicsManager::getInstance().getRenderWindow()->setFullscreen(fullscreen, width, height);
500            this->ogreRoot_->saveConfig();
501            Core::getInstance().updateOgreConfigTimestamp();
502        }
503
504        return validate;
505    }
506
507    std::string GraphicsManager::setFSAA(const std::string& mode)
508    {
509        this->ogreRoot_->getRenderSystem()->setConfigOption("FSAA", mode);
510
511        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
512
513        if (validate == "")
514        {
515            //this->ogreRoot_->getRenderSystem()->reinitialise(); // can't use this that easily, because it recreates the render window, invalidating renderWindow_
516            this->ogreRoot_->saveConfig();
517            Core::getInstance().updateOgreConfigTimestamp();
518        }
519
520        return validate;
521    }
522
523    std::string GraphicsManager::setVSync(bool vsync)
524    {
525        this->ogreRoot_->getRenderSystem()->setConfigOption("VSync", vsync ? "Yes" : "No");
526
527        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
528
529        if (validate == "")
530        {
531            //this->ogreRoot_->getRenderSystem()->reinitialise(); // can't use this that easily, because it recreates the render window, invalidating renderWindow_
532            this->ogreRoot_->saveConfig();
533            Core::getInstance().updateOgreConfigTimestamp();
534        }
535
536        return validate;
537    }
538
539    void GraphicsManager::printScreen()
540    {
541        assert(this->renderWindow_);
542        this->renderWindow_->writeContentsToTimestampedFile(PathConfig::getLogPathString() + "screenShot_", ".png");
543    }
544}
Note: See TracBrowser for help on using the repository browser.