Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

added console command to change screen resolution and fullscreen mode at runtime
added console commands to change FSAA and VSync (require restart)

  • Property svn:eol-style set to native
File size: 21.1 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    {
109        RegisterObject(GraphicsManager);
110
111        this->setConfigValues();
112
113        // Ogre setup procedure (creating Ogre::Root)
114        this->loadOgreRoot();
115
116        // At first, add the root paths of the data directories as resource locations
117        Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getDataPathString(), "FileSystem");
118        // Load resources
119        resources_.reset(new XMLFile("DefaultResources.oxr"));
120        resources_->setLuaSupport(false);
121        Loader::open(resources_.get());
122
123        // Only for development runs
124        if (PathConfig::isDevelopmentRun())
125        {
126            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getExternalDataPathString(), "FileSystem");
127            extResources_.reset(new XMLFile("resources.oxr"));
128            extResources_->setLuaSupport(false);
129            Loader::open(extResources_.get());
130        }
131
132        if (bLoadRenderer)
133        {
134            // Reads the ogre config and creates the render window
135            this->upgradeToGraphics();
136        }
137    }
138
139    /**
140    @brief
141        Destruction is done by the member scoped_ptrs.
142    */
143    GraphicsManager::~GraphicsManager()
144    {
145        Loader::unload(debugOverlay_.get());
146
147        Ogre::WindowEventUtilities::removeWindowEventListener(renderWindow_, ogreWindowEventListener_.get());
148        ModifyConsoleCommand(__CC_printScreen_name).resetFunction();
149        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name).resetFunction();
150        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name).resetFunction();
151        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name).resetFunction();
152
153        // Undeclare the resources
154        Loader::unload(resources_.get());
155        if (PathConfig::isDevelopmentRun())
156            Loader::unload(extResources_.get());
157    }
158
159    void GraphicsManager::setConfigValues()
160    {
161        SetConfigValue(ogreConfigFile_,  "ogre.cfg")
162            .description("Location of the Ogre config file");
163        SetConfigValue(ogrePluginsDirectory_, specialConfig::ogrePluginsDirectory)
164            .description("Folder where the Ogre plugins are located.");
165        SetConfigValue(ogrePlugins_, specialConfig::ogrePlugins)
166            .description("Comma separated list of all plugins to load.");
167        SetConfigValue(ogreLogFile_,     "ogre.log")
168            .description("Logfile for messages from Ogre. Use \"\" to suppress log file creation.");
169        SetConfigValue(ogreLogLevelTrivial_ , 5)
170            .description("Corresponding orxonox debug level for ogre Trivial");
171        SetConfigValue(ogreLogLevelNormal_  , 4)
172            .description("Corresponding orxonox debug level for ogre Normal");
173        SetConfigValue(ogreLogLevelCritical_, 2)
174            .description("Corresponding orxonox debug level for ogre Critical");
175    }
176
177    /**
178    @brief
179        Loads the renderer and creates the render window if not yet done so.
180    @remarks
181        This operation is irreversible without recreating the GraphicsManager!
182        So if it throws you HAVE to recreate the GraphicsManager!!!
183        It therefore offers almost no exception safety.
184    */
185    void GraphicsManager::upgradeToGraphics()
186    {
187        if (renderWindow_ != NULL)
188            return;
189
190        // load all the required plugins for Ogre
191        this->loadOgrePlugins();
192
193        this->loadRenderer();
194
195#if OGRE_VERSION < 0x010600
196        // WORKAROUND: There is an incompatibility for particle scripts when trying
197        // to support both Ogre 1.4 and 1.6. The hacky solution is to create
198        // scripts for the 1.6 version and then remove the inserted "particle_system"
199        // keyword. But we need to supply these new scripts as well, which is why
200        // there is an extra Ogre::Archive dealing with it in the memory.
201        using namespace Ogre;
202        ArchiveManager::getSingleton().addArchiveFactory(memoryArchiveFactory_.get());
203        const StringVector& groups = ResourceGroupManager::getSingleton().getResourceGroups();
204        // Travers all groups
205        for (StringVector::const_iterator itGroup = groups.begin(); itGroup != groups.end(); ++itGroup)
206        {
207            FileInfoListPtr files = ResourceGroupManager::getSingleton().findResourceFileInfo(*itGroup, "*.particle");
208            for (FileInfoList::const_iterator itFile = files->begin(); itFile != files->end(); ++itFile)
209            {
210                // open file
211                Ogre::DataStreamPtr input = ResourceGroupManager::getSingleton().openResource(itFile->filename, *itGroup, false);
212                std::stringstream output;
213                // Parse file and replace "particle_system" with nothing
214                while (!input->eof())
215                {
216                    std::string line = input->getLine();
217                    size_t pos = line.find("particle_system");
218                    if (pos != std::string::npos)
219                    {
220                        // 15 is the length of "particle_system"
221                        line.replace(pos, 15, "");
222                    }
223                    output << line << std::endl;
224                }
225                // Add file to the memory archive
226                shared_array<char> data(new char[output.str().size()]);
227                // Debug optimisations
228                const std::string& outputStr = output.str();
229                char* rawData = data.get();
230                for (unsigned i = 0; i < outputStr.size(); ++i)
231                    rawData[i] = outputStr[i];
232                MemoryArchive::addFile("particle_scripts_ogre_1.4_" + *itGroup, itFile->filename, data, output.str().size());
233            }
234            if (!files->empty())
235            {
236                // Declare the files, but using a new group
237                ResourceGroupManager::getSingleton().addResourceLocation("particle_scripts_ogre_1.4_" + *itGroup,
238                    "Memory", "particle_scripts_ogre_1.4_" + *itGroup);
239            }
240        }
241#endif
242
243        // Initialise all resources (do this AFTER the renderer has been loaded!)
244        // Note: You can only do this once! Ogre will check whether a resource group has
245        // already been initialised. If you need to load resources later, you will have to
246        // choose another resource group.
247        Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
248    }
249
250    /**
251    @brief
252        Creates the Ogre Root object and sets up the ogre log.
253    */
254    void GraphicsManager::loadOgreRoot()
255    {
256        COUT(3) << "Setting up Ogre..." << std::endl;
257
258        if (ogreConfigFile_.empty())
259        {
260            COUT(2) << "Warning: Ogre config file set to \"\". Defaulting to config.cfg" << std::endl;
261            ModifyConfigValue(ogreConfigFile_, tset, "config.cfg");
262        }
263        if (ogreLogFile_.empty())
264        {
265            COUT(2) << "Warning: Ogre log file set to \"\". Defaulting to ogre.log" << std::endl;
266            ModifyConfigValue(ogreLogFile_, tset, "ogre.log");
267        }
268
269        boost::filesystem::path ogreConfigFilepath(PathConfig::getConfigPath() / this->ogreConfigFile_);
270        boost::filesystem::path ogreLogFilepath(PathConfig::getLogPath() / this->ogreLogFile_);
271
272        // create a new logManager
273        // Ogre::Root will detect that we've already created a Log
274        ogreLogger_.reset(new Ogre::LogManager());
275        COUT(4) << "Ogre LogManager created" << std::endl;
276
277        // create our own log that we can listen to
278        Ogre::Log *myLog;
279        myLog = ogreLogger_->createLog(ogreLogFilepath.string(), true, false, false);
280        COUT(4) << "Ogre Log created" << std::endl;
281
282        myLog->setLogDetail(Ogre::LL_BOREME);
283        myLog->addListener(this);
284
285        COUT(4) << "Creating Ogre Root..." << std::endl;
286
287        // check for config file existence because Ogre displays (caught) exceptions if not
288        if (!boost::filesystem::exists(ogreConfigFilepath))
289        {
290            // create a zero sized file
291            std::ofstream creator;
292            creator.open(ogreConfigFilepath.string().c_str());
293            creator.close();
294        }
295
296        // Leave plugins file empty. We're going to do that part manually later
297        ogreRoot_.reset(new Ogre::Root("", ogreConfigFilepath.string(), ogreLogFilepath.string()));
298
299        COUT(3) << "Ogre set up done." << std::endl;
300    }
301
302    void GraphicsManager::loadOgrePlugins()
303    {
304        // just to make sure the next statement doesn't segfault
305        if (ogrePluginsDirectory_.empty())
306            ogrePluginsDirectory_ = '.';
307
308        boost::filesystem::path folder(ogrePluginsDirectory_);
309        // Do some SubString magic to get the comma separated list of plugins
310        SubString plugins(ogrePlugins_, ",", " ", false, '\\', false, '"', false, '{', '}', false, '\0');
311        // Use backslash paths on Windows! file_string() already does that though.
312        for (unsigned int i = 0; i < plugins.size(); ++i)
313            ogreRoot_->loadPlugin((folder / plugins[i]).file_string());
314    }
315
316    void GraphicsManager::loadRenderer()
317    {
318        CCOUT(4) << "Configuring Renderer" << std::endl;
319
320        if (!ogreRoot_->restoreConfig() || Core::getInstance().getOgreConfigTimestamp() > Core::getInstance().getLastLevelTimestamp())
321        {
322            if (!ogreRoot_->showConfigDialog())
323                ThrowException(InitialisationFailed, "OGRE graphics configuration dialogue canceled.");
324            else
325                Core::getInstance().updateOgreConfigTimestamp();
326        }
327
328        CCOUT(4) << "Creating render window" << std::endl;
329
330        this->renderWindow_ = ogreRoot_->initialise(true, "Orxonox");
331        // Propagate the size of the new winodw
332        this->ogreWindowEventListener_->windowResized(renderWindow_);
333
334        Ogre::WindowEventUtilities::addWindowEventListener(this->renderWindow_, ogreWindowEventListener_.get());
335
336        // create a full screen default viewport
337        // Note: This may throw when adding a viewport with an existing z-order!
338        //       But in our case we only have one viewport for now anyway, therefore
339        //       no ScopeGuards or anything to handle exceptions.
340        this->viewport_ = this->renderWindow_->addViewport(0, 0);
341
342        Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(Ogre::MIP_UNLIMITED);
343
344        // add console commands
345        ModifyConsoleCommand(__CC_printScreen_name).setFunction(&GraphicsManager::printScreen, this);
346        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setScreenResolution_name).setFunction(&GraphicsManager::setScreenResolution, this);
347        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setFSAA_name).setFunction(&GraphicsManager::setFSAA, this);
348        ModifyConsoleCommand(__CC_GraphicsManager_group, __CC_setVSync_name).setFunction(&GraphicsManager::setVSync, this);
349    }
350
351    void GraphicsManager::loadDebugOverlay()
352    {
353        // Load debug overlay to show info about fps and tick time
354        COUT(4) << "Loading Debug Overlay..." << std::endl;
355        debugOverlay_.reset(new XMLFile("debug.oxo"));
356        Loader::open(debugOverlay_.get());
357    }
358
359    /**
360    @note
361        A note about the Ogre::FrameListener: Even though we don't use them,
362        they still get called. However, the delta times are not correct (except
363        for timeSinceLastFrame, which is the most important). A little research
364        as shown that there is probably only one FrameListener that doesn't even
365        need the time. So we shouldn't run into problems.
366    */
367    void GraphicsManager::postUpdate(const Clock& time)
368    {
369        Ogre::FrameEvent evt;
370        evt.timeSinceLastFrame = time.getDeltaTime();
371        evt.timeSinceLastEvent = time.getDeltaTime(); // note: same time, but shouldn't matter anyway
372
373        // don't forget to call _fireFrameStarted to OGRE to make sure
374        // everything goes smoothly
375        ogreRoot_->_fireFrameStarted(evt);
376
377        // Pump messages in all registered RenderWindows
378        // This calls the WindowEventListener objects.
379        Ogre::WindowEventUtilities::messagePump();
380        // make sure the window stays active even when not focused
381        // (probably only necessary on windows)
382        this->renderWindow_->setActive(true);
383
384        // Time before rendering
385        uint64_t timeBeforeTick = time.getRealMicroseconds();
386
387        // Render frame
388        ogreRoot_->_updateAllRenderTargets();
389
390        uint64_t timeAfterTick = time.getRealMicroseconds();
391        // Subtract the time used for rendering from the tick time counter
392        Game::getInstance().subtractTickTime((int32_t)(timeAfterTick - timeBeforeTick));
393
394        // again, just to be sure OGRE works fine
395        ogreRoot_->_fireFrameEnded(evt); // note: uses the same time as _fireFrameStarted
396    }
397
398    void GraphicsManager::setCamera(Ogre::Camera* camera)
399    {
400        Ogre::Camera* oldCamera = this->viewport_->getCamera();
401
402        this->viewport_->setCamera(camera);
403        GUIManager::getInstance().setCamera(camera);
404
405        for (ObjectList<ViewportEventListener>::iterator it = ObjectList<ViewportEventListener>::begin(); it != ObjectList<ViewportEventListener>::end(); ++it)
406            it->cameraChanged(this->viewport_, oldCamera);
407    }
408
409    /**
410    @brief
411        Method called by the LogListener interface from Ogre.
412        We use it to capture Ogre log messages and handle it ourselves.
413    @param message
414        The message to be logged
415    @param lml
416        The message level the log is using
417    @param maskDebug
418        If we are printing to the console or not
419    @param logName
420        The name of this log (so you can have several listeners
421        for different logs, and identify them)
422    */
423    void GraphicsManager::messageLogged(const std::string& message,
424        Ogre::LogMessageLevel lml, bool maskDebug, const std::string& logName)
425    {
426        int orxonoxLevel;
427        std::string introduction;
428        // Do not show caught OGRE exceptions in front
429        if (message.find("EXCEPTION") != std::string::npos)
430        {
431            orxonoxLevel = OutputLevel::Debug;
432            introduction = "Ogre, caught exception: ";
433        }
434        else
435        {
436            switch (lml)
437            {
438            case Ogre::LML_TRIVIAL:
439                orxonoxLevel = this->ogreLogLevelTrivial_;
440                break;
441            case Ogre::LML_NORMAL:
442                orxonoxLevel = this->ogreLogLevelNormal_;
443                break;
444            case Ogre::LML_CRITICAL:
445                orxonoxLevel = this->ogreLogLevelCritical_;
446                break;
447            default:
448                orxonoxLevel = 0;
449            }
450            introduction = "Ogre: ";
451        }
452        OutputHandler::getOutStream(orxonoxLevel)
453            << introduction << message << std::endl;
454    }
455
456    size_t GraphicsManager::getRenderWindowHandle()
457    {
458        size_t windowHnd = 0;
459        renderWindow_->getCustomAttribute("WINDOW", &windowHnd);
460        return windowHnd;
461    }
462
463    bool GraphicsManager::isFullScreen() const
464    {
465        Ogre::ConfigOptionMap& options = ogreRoot_->getRenderSystem()->getConfigOptions();
466        if (options.find("Full Screen") != options.end())
467        {
468            if (options["Full Screen"].currentValue == "Yes")
469                return true;
470            else
471                return false;
472        }
473        else
474        {
475            COUT(0) << "Could not find 'Full Screen' render system option. Fix This!!!" << std::endl;
476            return false;
477        }
478    }
479
480    std::string GraphicsManager::setScreenResolution(unsigned int width, unsigned int height, bool fullscreen)
481    {
482        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");
483        this->ogreRoot_->getRenderSystem()->setConfigOption("Full Screen", fullscreen ? "Yes" : "No");
484
485        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
486
487        if (validate == "")
488        {
489            GraphicsManager::getInstance().getRenderWindow()->setFullscreen(fullscreen, width, height);
490            this->ogreRoot_->saveConfig();
491            Core::getInstance().updateOgreConfigTimestamp();
492        }
493
494        return validate;
495    }
496
497    std::string GraphicsManager::setFSAA(const std::string& mode)
498    {
499        this->ogreRoot_->getRenderSystem()->setConfigOption("FSAA", mode);
500
501        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
502
503        if (validate == "")
504        {
505            //this->ogreRoot_->getRenderSystem()->reinitialise(); // can't use this that easily, because it recreates the render window, invalidating renderWindow_
506            this->ogreRoot_->saveConfig();
507            Core::getInstance().updateOgreConfigTimestamp();
508        }
509
510        return validate;
511    }
512
513    std::string GraphicsManager::setVSync(bool vsync)
514    {
515        this->ogreRoot_->getRenderSystem()->setConfigOption("VSync", vsync ? "Yes" : "No");
516
517        std::string validate = this->ogreRoot_->getRenderSystem()->validateConfigOptions();
518
519        if (validate == "")
520        {
521            //this->ogreRoot_->getRenderSystem()->reinitialise(); // can't use this that easily, because it recreates the render window, invalidating renderWindow_
522            this->ogreRoot_->saveConfig();
523            Core::getInstance().updateOgreConfigTimestamp();
524        }
525
526        return validate;
527    }
528
529    void GraphicsManager::printScreen()
530    {
531        assert(this->renderWindow_);
532        this->renderWindow_->writeContentsToTimestampedFile(PathConfig::getLogPathString() + "screenShot_", ".png");
533    }
534}
Note: See TracBrowser for help on using the repository browser.