Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/GraphicsManager.cc @ 9301

Last change on this file since 9301 was 8861, checked in by landauf, 13 years ago

added some additional output for loading steps that might take some time on slow systems

  • Property svn:eol-style set to native
File size: 21.2 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 <cstdlib>
33#include <fstream>
34#include <sstream>
35#include <boost/filesystem.hpp>
36#include <boost/shared_array.hpp>
37
38#include <OgreFrameListener.h>
39#include <OgreRoot.h>
40#include <OgreLogManager.h>
41#include <OgreRenderWindow.h>
42#include <OgreRenderSystem.h>
43#include <OgreResourceGroupManager.h>
44#include <OgreTextureManager.h>
45#include <OgreViewport.h>
46#include <OgreWindowEventUtilities.h>
47
48#include "SpecialConfig.h"
49#include "util/Clock.h"
50#include "util/Convert.h"
51#include "util/Exception.h"
52#include "util/StringUtils.h"
53#include "util/SubString.h"
54#include "ConfigValueIncludes.h"
55#include "CoreIncludes.h"
56#include "Core.h"
57#include "Game.h"
58#include "GameMode.h"
59#include "GUIManager.h"
60#include "Loader.h"
61#include "PathConfig.h"
62#include "ViewportEventListener.h"
63#include "WindowEventListener.h"
64#include "XMLFile.h"
65#include "command/ConsoleCommand.h"
66#include "input/InputManager.h"
67
68namespace orxonox
69{
70    static const std::string __CC_GraphicsManager_group = "GraphicsManager";
71    static const std::string __CC_setScreenResolution_name = "setScreenResolution";
72    static const std::string __CC_setFSAA_name = "setFSAA";
73    static const std::string __CC_setVSync_name = "setVSync";
74    DeclareConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name, &prototype::string__uint_uint_bool);
75    DeclareConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name, &prototype::string__string);
76    DeclareConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name, &prototype::string__bool);
77
78    static const std::string __CC_printScreen_name = "printScreen";
79    DeclareConsoleCommand(__CC_printScreen_name, &prototype::void__void);
80
81    class OgreWindowEventListener : public Ogre::WindowEventListener
82    {
83    public:
84        void windowResized     (Ogre::RenderWindow* rw)
85            { orxonox::WindowEventListener::resizeWindow(rw->getWidth(), rw->getHeight()); }
86        void windowFocusChange (Ogre::RenderWindow* rw)
87            { orxonox::WindowEventListener::changeWindowFocus(rw->isActive()); }
88        void windowClosed      (Ogre::RenderWindow* rw)
89            { orxonox::Game::getInstance().stop(); }
90        void windowMoved       (Ogre::RenderWindow* rw)
91            { orxonox::WindowEventListener::moveWindow(); }
92    };
93
94    GraphicsManager* GraphicsManager::singletonPtr_s = 0;
95
96    GraphicsManager::GraphicsManager(bool bLoadRenderer)
97        : ogreWindowEventListener_(new OgreWindowEventListener())
98        , renderWindow_(0)
99        , viewport_(0)
100        , lastFrameStartTime_(0.0f)
101        , lastFrameEndTime_(0.0f)
102        , destructionHelper_(this)
103    {
104        RegisterObject(GraphicsManager);
105
106        orxout(internal_status) << "initializing GraphicsManager..." << endl;
107        this->setConfigValues();
108
109        // Ogre setup procedure (creating Ogre::Root)
110        this->loadOgreRoot();
111
112        // At first, add the root paths of the data directories as resource locations
113        Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getDataPathString(), "FileSystem");
114        // Load resources
115        resources_.reset(new XMLFile("DefaultResources.oxr"));
116        resources_->setLuaSupport(false);
117        Loader::open(resources_.get(), ClassTreeMask(), false);
118
119        // Only for runs in the build directory (not installed)
120        if (PathConfig::buildDirectoryRun())
121            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getExternalDataPathString(), "FileSystem");
122
123        extResources_.reset(new XMLFile("resources.oxr"));
124        extResources_->setLuaSupport(false);
125        Loader::open(extResources_.get(), ClassTreeMask(), false);
126
127        if (bLoadRenderer)
128        {
129            // Reads the ogre config and creates the render window
130            this->upgradeToGraphics();
131        }
132
133        orxout(internal_status) << "finished initializing GraphicsManager" << endl;
134    }
135
136    void GraphicsManager::destroy()
137    {
138        orxout(internal_status) << "destroying GraphicsManager..." << endl;
139
140        Loader::unload(debugOverlay_.get());
141
142        Ogre::WindowEventUtilities::removeWindowEventListener(renderWindow_, ogreWindowEventListener_);
143        ModifyConsoleCommand(__CC_printScreen_name).resetFunction();
144        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name).resetFunction();
145        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name).resetFunction();
146        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name).resetFunction();
147
148        // Undeclare the resources
149        Loader::unload(resources_.get());
150        Loader::unload(extResources_.get());
151
152        safeObjectDelete(&ogreRoot_);
153        safeObjectDelete(&ogreLogger_);
154        safeObjectDelete(&ogreWindowEventListener_);
155
156        orxout(internal_status) << "finished destroying GraphicsManager" << endl;
157    }
158
159    void GraphicsManager::setConfigValues()
160    {
161        SetConfigValue(ogreConfigFile_,  "ogre.cfg")
162            .description("Location of the Ogre config file");
163        SetConfigValue(ogrePlugins_, specialConfig::ogrePlugins)
164            .description("Comma separated list of all plugins to load.");
165        SetConfigValue(ogreLogFile_,     "ogre.log")
166            .description("Logfile for messages from Ogre. Use \"\" to suppress log file creation.");
167    }
168
169    /**
170    @brief
171        Loads the renderer and creates the render window if not yet done so.
172    @remarks
173        This operation is irreversible without recreating the GraphicsManager!
174        So if it throws you HAVE to recreate the GraphicsManager!!!
175        It therefore offers almost no exception safety.
176    */
177    void GraphicsManager::upgradeToGraphics()
178    {
179        if (renderWindow_ != NULL)
180            return;
181
182        orxout(internal_info) << "GraphicsManager upgrade to graphics" << endl;
183
184        // load all the required plugins for Ogre
185        orxout(user_info) << "Loading Ogre plugins..." << endl;
186        this->loadOgrePlugins();
187
188        orxout(user_info) << "Creating render window..." << endl;
189        this->loadRenderer();
190
191        // Initialise all resources (do this AFTER the renderer has been loaded!)
192        // Note: You can only do this once! Ogre will check whether a resource group has
193        // already been initialised. If you need to load resources later, you will have to
194        // choose another resource group.
195        orxout(user_info) << "Initializing all resource groups..." << endl;
196        Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
197
198        orxout(internal_info) << "GraphicsManager finished upgrade to graphics" << endl;
199    }
200
201    /**
202    @brief
203        Creates the Ogre Root object and sets up the ogre log.
204    */
205    void GraphicsManager::loadOgreRoot()
206    {
207        orxout(internal_info) << "Setting up Ogre..." << endl;
208
209        if (ogreConfigFile_.empty())
210        {
211            orxout(internal_warning) << "Ogre config file set to \"\". Defaulting to config.cfg" << endl;
212            ModifyConfigValue(ogreConfigFile_, tset, "config.cfg");
213        }
214        if (ogreLogFile_.empty())
215        {
216            orxout(internal_warning) << "Ogre log file set to \"\". Defaulting to ogre.log" << endl;
217            ModifyConfigValue(ogreLogFile_, tset, "ogre.log");
218        }
219
220        boost::filesystem::path ogreConfigFilepath(PathConfig::getConfigPath() / this->ogreConfigFile_);
221        boost::filesystem::path ogreLogFilepath(PathConfig::getLogPath() / this->ogreLogFile_);
222
223        // create a new logManager
224        // Ogre::Root will detect that we've already created a Log
225        ogreLogger_ = new Ogre::LogManager();
226        orxout(internal_info) << "Ogre LogManager created" << endl;
227
228        // create our own log that we can listen to
229        Ogre::Log *myLog;
230        myLog = ogreLogger_->createLog(ogreLogFilepath.string(), true, false, false);
231        orxout(internal_info) << "Ogre Log created" << endl;
232
233        myLog->setLogDetail(Ogre::LL_BOREME);
234        myLog->addListener(this);
235
236        orxout(internal_info) << "Creating Ogre Root..." << endl;
237
238        // check for config file existence because Ogre displays (caught) exceptions if not
239        if (!boost::filesystem::exists(ogreConfigFilepath))
240        {
241            // create a zero sized file
242            std::ofstream creator;
243            creator.open(ogreConfigFilepath.string().c_str());
244            creator.close();
245        }
246
247        // Leave plugins file empty. We're going to do that part manually later
248        ogreRoot_ = new Ogre::Root("", ogreConfigFilepath.string(), ogreLogFilepath.string());
249
250        orxout(internal_info) << "Ogre set up done." << endl;
251    }
252
253    void GraphicsManager::loadOgrePlugins()
254    {
255        orxout(internal_info) << "loading ogre plugins" << endl;
256
257        // Plugin path can have many different locations...
258        std::string pluginPath = specialConfig::ogrePluginsDirectory;
259#ifdef DEPENDENCY_PACKAGE_ENABLE
260        if (!PathConfig::buildDirectoryRun())
261        {
262#  if defined(ORXONOX_PLATFORM_WINDOWS)
263            pluginPath = PathConfig::getExecutablePathString();
264#  elif defined(ORXONOX_PLATFORM_APPLE)
265            // TODO: Where are the plugins being installed to?
266            pluginPath = PathConfig::getExecutablePathString();
267#  endif
268        }
269#endif
270
271#ifdef ORXONOX_PLATFORM_WINDOWS
272        // Add OGRE plugin path to the environment. That way one plugin could
273        // also depend on another without problems on Windows
274        const char* currentPATH = getenv("PATH");
275        std::string newPATH = pluginPath;
276        if (currentPATH != NULL)
277            newPATH = std::string(currentPATH) + ';' + newPATH;
278        putenv(const_cast<char*>(("PATH=" + newPATH).c_str()));
279#endif
280
281        // Do some SubString magic to get the comma separated list of plugins
282        SubString plugins(ogrePlugins_, ",", " ", false, '\\', false, '"', false, '{', '}', false, '\0');
283        for (unsigned int i = 0; i < plugins.size(); ++i)
284            ogreRoot_->loadPlugin(pluginPath + '/' + plugins[i]);
285    }
286
287    void GraphicsManager::loadRenderer()
288    {
289        orxout(internal_info) << "GraphicsManager: Configuring Renderer" << endl;
290
291        bool updatedConfig = Core::getInstance().getOgreConfigTimestamp() > Core::getInstance().getLastLevelTimestamp();
292        if (updatedConfig)
293            orxout(user_info)<< "Ogre config file has changed, but no level was started since then. Displaying config dialogue again to verify the changes." << endl;
294
295        if (!ogreRoot_->restoreConfig() || updatedConfig)
296        {
297            if (!ogreRoot_->showConfigDialog())
298                ThrowException(InitialisationFailed, "OGRE graphics configuration dialogue canceled.");
299            else
300                Core::getInstance().updateOgreConfigTimestamp();
301        }
302
303        orxout(internal_info) << "Creating render window" << endl;
304
305        this->renderWindow_ = ogreRoot_->initialise(true, "Orxonox");
306        // Propagate the size of the new winodw
307        this->ogreWindowEventListener_->windowResized(renderWindow_);
308
309        Ogre::WindowEventUtilities::addWindowEventListener(this->renderWindow_, ogreWindowEventListener_);
310
311        // create a full screen default viewport
312        // Note: This may throw when adding a viewport with an existing z-order!
313        //       But in our case we only have one viewport for now anyway, therefore
314        //       no ScopeGuards or anything to handle exceptions.
315        this->viewport_ = this->renderWindow_->addViewport(0, 0);
316
317        Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(Ogre::MIP_UNLIMITED);
318
319        // add console commands
320        ModifyConsoleCommand(__CC_printScreen_name).setFunction(&GraphicsManager::printScreen, this);
321        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name).setFunction(&GraphicsManager::setScreenResolution, this);
322        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name).setFunction(&GraphicsManager::setFSAA, this);
323        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name).setFunction(&GraphicsManager::setVSync, this);
324    }
325
326    void GraphicsManager::loadDebugOverlay()
327    {
328        // Load debug overlay to show info about fps and tick time
329        orxout(internal_info) << "Loading Debug Overlay..." << endl;
330        debugOverlay_.reset(new XMLFile("debug.oxo"));
331        Loader::open(debugOverlay_.get(), ClassTreeMask(), false);
332    }
333
334    /**
335    @note
336        A note about the Ogre::FrameListener: Even though we don't use them,
337        they still get called.
338    */
339    void GraphicsManager::postUpdate(const Clock& time)
340    {
341        // Time before rendering
342        uint64_t timeBeforeTick = time.getRealMicroseconds();
343
344        // Ogre's time keeping object
345        Ogre::FrameEvent evt;
346
347        // Translate to Ogre float times before the update
348        float temp = lastFrameStartTime_;
349        lastFrameStartTime_ = (float)timeBeforeTick * 0.000001f;
350        evt.timeSinceLastFrame = lastFrameStartTime_ - temp;
351        evt.timeSinceLastEvent = lastFrameStartTime_ - lastFrameEndTime_;
352
353        // Ogre requires the time too
354        ogreRoot_->_fireFrameStarted(evt);
355
356        // Pump messages in all registered RenderWindows
357        // This calls the WindowEventListener objects.
358        Ogre::WindowEventUtilities::messagePump();
359        // Make sure the window stays active even when not focused
360        // (probably only necessary on windows)
361        this->renderWindow_->setActive(true);
362
363        // Render frame
364        ogreRoot_->_updateAllRenderTargets();
365
366        uint64_t timeAfterTick = time.getRealMicroseconds();
367        // Subtract the time used for rendering from the tick time counter
368        Game::getInstance().subtractTickTime((int32_t)(timeAfterTick - timeBeforeTick));
369
370        // Translate to Ogre float times after the update
371        temp = lastFrameEndTime_;
372        lastFrameEndTime_ = (float)timeBeforeTick * 0.000001f;
373        evt.timeSinceLastFrame = lastFrameEndTime_ - temp;
374        evt.timeSinceLastEvent = lastFrameEndTime_ - lastFrameStartTime_;
375
376        // Ogre also needs the time after the frame finished
377        ogreRoot_->_fireFrameEnded(evt);
378    }
379
380    void GraphicsManager::setCamera(Ogre::Camera* camera)
381    {
382        Ogre::Camera* oldCamera = this->viewport_->getCamera();
383
384        this->viewport_->setCamera(camera);
385        GUIManager::getInstance().setCamera(camera);
386
387        for (ObjectList<ViewportEventListener>::iterator it = ObjectList<ViewportEventListener>::begin(); it != ObjectList<ViewportEventListener>::end(); ++it)
388            it->cameraChanged(this->viewport_, oldCamera);
389    }
390
391    /**
392    @brief
393        Method called by the LogListener interface from Ogre.
394        We use it to capture Ogre log messages and handle it ourselves.
395    @param message
396        The message to be logged
397    @param lml
398        The message level the log is using
399    @param maskDebug
400        If we are printing to the console or not
401    @param logName
402        The name of this log (so you can have several listeners
403        for different logs, and identify them)
404    */
405    void GraphicsManager::messageLogged(const std::string& message,
406        Ogre::LogMessageLevel lml, bool maskDebug, const std::string& logName)
407    {
408        OutputLevel orxonoxLevel;
409        std::string introduction;
410        // Do not show caught OGRE exceptions in front
411        if (message.find("EXCEPTION") != std::string::npos)
412        {
413            orxonoxLevel = level::internal_error;
414            introduction = "Ogre, caught exception: ";
415        }
416        else
417        {
418            switch (lml)
419            {
420            case Ogre::LML_TRIVIAL:
421                orxonoxLevel = level::verbose_more;
422                break;
423            case Ogre::LML_NORMAL:
424                orxonoxLevel = level::verbose;
425                break;
426            case Ogre::LML_CRITICAL:
427                orxonoxLevel = level::internal_warning;
428                break;
429            default:
430                orxonoxLevel = level::debug_output;
431            }
432            introduction = "Ogre: ";
433        }
434
435        orxout(orxonoxLevel, context::ogre) << introduction << message << endl;
436    }
437
438    size_t GraphicsManager::getRenderWindowHandle()
439    {
440        size_t windowHnd = 0;
441        renderWindow_->getCustomAttribute("WINDOW", &windowHnd);
442        return windowHnd;
443    }
444
445    bool GraphicsManager::isFullScreen() const
446    {
447        return this->renderWindow_->isFullScreen();
448    }
449
450    unsigned int GraphicsManager::getWindowWidth() const
451    {
452        return this->renderWindow_->getWidth();
453    }
454
455    unsigned int GraphicsManager::getWindowHeight() const
456    {
457        return this->renderWindow_->getHeight();
458    }
459
460    bool GraphicsManager::hasVSyncEnabled() const
461    {
462        Ogre::ConfigOptionMap& options = ogreRoot_->getRenderSystem()->getConfigOptions();
463        Ogre::ConfigOptionMap::iterator it = options.find("VSync");
464        if (it != options.end())
465            return (it->second.currentValue == "Yes");
466        else
467            return false;
468    }
469
470    std::string GraphicsManager::getFSAAMode() const
471    {
472        Ogre::ConfigOptionMap& options = ogreRoot_->getRenderSystem()->getConfigOptions();
473        Ogre::ConfigOptionMap::iterator it = options.find("FSAA");
474        if (it != options.end())
475            return it->second.currentValue;
476        else
477            return "";
478    }
479
480    std::string GraphicsManager::setScreenResolution(unsigned int width, unsigned int height, bool fullscreen)
481    {
482        // workaround to detect if the colour depth should be written to the config file
483        bool bWriteColourDepth = false;
484        Ogre::ConfigOptionMap& options = ogreRoot_->getRenderSystem()->getConfigOptions();
485        Ogre::ConfigOptionMap::iterator it = options.find("Video Mode");
486        if (it != options.end())
487            bWriteColourDepth = (it->second.currentValue.find('@') != std::string::npos);
488
489        if (bWriteColourDepth)
490        {
491            this->ogreRoot_->getRenderSystem()->setConfigOption("Video Mode", multi_cast<std::string>(width)
492                                                                    + " x " + multi_cast<std::string>(height)
493                                                                    + " @ " + multi_cast<std::string>(this->getRenderWindow()->getColourDepth()) + "-bit colour");
494        }
495        else
496        {
497            this->ogreRoot_->getRenderSystem()->setConfigOption("Video Mode", multi_cast<std::string>(width)
498                                                                    + " x " + multi_cast<std::string>(height));
499        }
500
501        this->ogreRoot_->getRenderSystem()->setConfigOption("Full Screen", fullscreen ? "Yes" : "No");
502
503        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
504
505        if (validate == "")
506        {
507            GraphicsManager::getInstance().getRenderWindow()->setFullscreen(fullscreen, width, height);
508            this->ogreRoot_->saveConfig();
509            Core::getInstance().updateOgreConfigTimestamp();
510            // Also reload the input devices
511            InputManager::getInstance().reload();
512        }
513
514        return validate;
515    }
516
517    std::string GraphicsManager::setFSAA(const std::string& mode)
518    {
519        this->ogreRoot_->getRenderSystem()->setConfigOption("FSAA", mode);
520
521        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
522
523        if (validate == "")
524        {
525            //this->ogreRoot_->getRenderSystem()->reinitialise(); // can't use this that easily, because it recreates the render window, invalidating renderWindow_
526            this->ogreRoot_->saveConfig();
527            Core::getInstance().updateOgreConfigTimestamp();
528        }
529
530        return validate;
531    }
532
533    std::string GraphicsManager::setVSync(bool vsync)
534    {
535        this->ogreRoot_->getRenderSystem()->setConfigOption("VSync", vsync ? "Yes" : "No");
536
537        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
538
539        if (validate == "")
540        {
541            //this->ogreRoot_->getRenderSystem()->reinitialise(); // can't use this that easily, because it recreates the render window, invalidating renderWindow_
542            this->ogreRoot_->saveConfig();
543            Core::getInstance().updateOgreConfigTimestamp();
544        }
545
546        return validate;
547    }
548
549    void GraphicsManager::printScreen()
550    {
551        assert(this->renderWindow_);
552        this->renderWindow_->writeContentsToTimestampedFile(PathConfig::getLogPathString() + "screenShot_", ".png");
553    }
554}
Note: See TracBrowser for help on using the repository browser.