1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2006, 2007 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27 
28 #ifndef SRC_SYSTEMS_BASE_GRAPHICS_SYSTEM_H_
29 #define SRC_SYSTEMS_BASE_GRAPHICS_SYSTEM_H_
30 
31 #include <boost/filesystem/path.hpp>
32 #include <boost/serialization/access.hpp>
33 #include <boost/serialization/split_member.hpp>
34 #include <boost/serialization/version.hpp>
35 
36 #include <iosfwd>
37 #include <map>
38 #include <memory>
39 #include <queue>
40 #include <set>
41 #include <string>
42 #include <utility>
43 #include <vector>
44 
45 #include "systems/base/cgm_table.h"
46 #include "systems/base/event_listener.h"
47 #include "systems/base/rect.h"
48 #include "systems/base/tone_curve.h"
49 
50 #include "utilities/lazy_array.h"
51 #include "lru_cache.hpp"
52 
53 class ColourFilter;
54 class Gameexe;
55 class GraphicsObject;
56 class GraphicsObjectData;
57 class GraphicsStackFrame;
58 class HIKRenderer;
59 class HIKScript;
60 class MouseCursor;
61 class Renderable;
62 class RGBAColour;
63 class RLMachine;
64 class Size;
65 class Surface;
66 class System;
67 struct ObjectSettings;
68 
69 template <typename T>
70 class LazyArray;
71 
72 // Variables and configuration data that are global across all save
73 // game files in a game.
74 struct GraphicsSystemGlobals {
75   GraphicsSystemGlobals();
76   explicit GraphicsSystemGlobals(Gameexe& gameexe);
77 
78   // ShowObject flags
79   int show_object_1, show_object_2;
80 
81   int show_weather;
82 
83   // Whether we should skip animations (such as those made by Effect)
84   int skip_animations;
85 
86   // screen mode. 1 is windowed (default), 0 is full-screen.
87   int screen_mode;
88 
89   // CG Table
90   CGMTable cg_table;
91 
92   // tone curve table
93   ToneCurve tone_curves;
94 
95   // boost::serialization support
96   template <class Archive>
serializeGraphicsSystemGlobals97   void serialize(Archive& ar, const unsigned int version) {
98     ar& show_object_1& show_object_2& show_weather;
99 
100     if (version > 0)
101       ar& cg_table;
102 
103     if (version > 1)
104       ar& screen_mode;
105   }
106 };
107 
108 BOOST_CLASS_VERSION(GraphicsSystemGlobals, 2)
109 
110 // When marking the screen as dirty, we need to know what kind of
111 // operation was done
112 enum GraphicsUpdateType {
113   GUT_DRAW_DC0,
114   GUT_DRAW_HIK,
115   GUT_DISPLAY_OBJ,
116   GUT_TEXTSYS,
117   GUT_MOUSE_MOTION
118 };
119 
120 // Which type of mutually exclusive background should we display?
121 enum GraphicsBackgroundType { BACKGROUND_DC0, BACKGROUND_HIK };
122 
123 // Abstract interface to a graphics system. Specialize this class for
124 // each system you plan on running RLVM on. For now, there's only one
125 // derived class; SDLGraphicsSystem.
126 //
127 // Two device contexts must be allocated during initialization; DC 0,
128 // which should refer to a surface that is (usually) blitted onto the
129 // screen immediatly after it is written to, and DC 1, which is simply
130 // guarenteed to be allocated, and is guarenteed to not be smaller
131 // then the screen. (Many {rec,grp} functions will load data onto DC1
132 // and then copy it onto DC0 with some sort of fancy transition
133 // effect.)
134 class GraphicsSystem : public EventListener {
135  public:
136   // The current display context drawing mode. The Reallive system
137   // will update the screen after certain events in user code
138   // regarding DCs.
139   //
140   // Note that these are not the only times when the screen will be
141   // updated. Most functions that deal with text windows will trigger
142   // screen updates. (Object manipulation functions *don't*.) Having
143   // this fine level of control is why DCs are often used for smooth
144   // animation...
145   enum DCScreenUpdateMode {
146     // The screen will be redrawn after every load or blit to DC 0.
147     SCREENUPDATEMODE_AUTOMATIC,
148 
149     // We currently don't understand how this differs from automatic
150     // mode. We declare it anyway for compatibility and the hope that
151     // someday we will.
152     SCREENUPDATEMODE_SEMIAUTOMATIC,
153 
154     // The screen is updated after refresh() is called
155     SCREENUPDATEMODE_MANUAL
156   };
157 
158   GraphicsSystem(System& system, Gameexe& gameexe);
159   virtual ~GraphicsSystem();
160 
is_responsible_for_update()161   bool is_responsible_for_update() const { return is_responsible_for_update_; }
set_is_responsible_for_update(bool in)162   void set_is_responsible_for_update(bool in) {
163     is_responsible_for_update_ = in;
164   }
165 
166   // Sets the default name for certain classes of commands. (Certain graphics
167   // commands set either of these and most graphics commands can pass in '?',
168   // which is substituted with the default name.)
default_grp_name()169   const std::string& default_grp_name() const { return default_grp_name_; }
set_default_grp_name(const std::string & name)170   void set_default_grp_name(const std::string& name) {
171     default_grp_name_ = name;
172   }
default_bgr_name()173   const std::string& default_bgr_name() const { return default_bgr_name_; }
set_default_bgr_name(const std::string & name)174   void set_default_bgr_name(const std::string& name) {
175     default_bgr_name_ = name;
176   }
177 
178   // Define who is responsible for screen updates.
screen_update_mode()179   DCScreenUpdateMode screen_update_mode() const { return screen_update_mode_; }
180   virtual void SetScreenUpdateMode(DCScreenUpdateMode u);
181 
set_graphics_background(GraphicsBackgroundType t)182   void set_graphics_background(GraphicsBackgroundType t) {
183     background_type_ = t;
184   }
185 
system()186   System& system() { return system_; }
187 
188   // Screen Shaking
189 
190   // Reads #SHAKE.spec and loads the offsets into the screen shaking queue.
191   void QueueShakeSpec(int spec);
192 
193   // Returns the current screen origin. This is used for simple #SHAKE.* based
194   // screen shaking. While the screen is not shaking, this returns (0,0).
195   Point GetScreenOrigin();
196 
197   // Whether we are currently shaking.
198   bool IsShaking() const;
199 
200   // How long the current frame in the shaking should last. 10ms if there are
201   // no frames.
202   int CurrentShakingFrameTime() const;
203 
204   // Mouse Cursor Management
205 
206   // Whether we are using a custom cursor. Verifies that there was a
207   // \#MOUSE_CURSOR entry in the Gameexe.ini file, and that the currently
208   // selected cursor exists.
209   int ShouldUseCustomCursor();
210 
211   // Sets the cursor to the incoming cursor index.
212   virtual void SetCursor(int cursor);
213 
214   // Returns the current index.
cursor()215   int cursor() const { return cursor_; }
216 
217   // Whether we display a cursor at all.
set_show_cursor_from_bytecode(const int in)218   void set_show_cursor_from_bytecode(const int in) {
219     show_cursor_from_bytecode_ = in;
220   }
221 
222   // Graphics stack implementation
223   //
224   // The RealLive virtual machine keeps track of recent graphics commands so
225   // that when the game is restored, these graphics commands can be replayed to
226   // recreate the screen state.
227 
228   // Adds |command|, the serialized form of a bytecode used by calling the
229   // BytecodeElement::data().
230   void AddGraphicsStackCommand(const std::string& command);
231 
232   // Returns the number of entries in the stack.
233   int StackSize() const;
234 
235   // Clears the graphics stack.
236   void ClearStack();
237 
238   // Removes (up to) |num_items| from the stack. (Stops when the stack is
239   // empty).
240   void StackPop(int num_items);
241 
242   // Replays the graphics stack. This is called after we've reloaded
243   // a saved game and deals with both old style and the new stack system.
244   void ReplayGraphicsStack(RLMachine& machine);
245 
246   // Sets the current hik script. GraphicsSystem takes ownership, freeing the
247   // current HIKScript if applicable. |script| can be NULL.
hik_renderer()248   HIKRenderer* hik_renderer() const { return hik_renderer_.get(); }
249   void SetHikRenderer(HIKRenderer* script);
250 
251   // -----------------------------------------------------------------------
252 
253   // Individual LongOperations can also hook into the end of the rendering
254   // pipeline by injecting Renderables. There should only really be one
255   // Renderable on screen at a time, but the interface allows for multiple
256   // ones.
257   void AddRenderable(Renderable* renderable);
258   void RemoveRenderable(Renderable* renderable);
259 
260   // Subtitle management
261 
262   // Sets the current value of the subtitle, as set with title(). This
263   // is virtual so that UTF8 or other charset systems can convert for
264   // their own internal copy.
265   virtual void SetWindowSubtitle(const std::string& cp932str,
266                                  int text_encoding);
267 
268   // Returns the current window subtitle, in native encoding.
window_subtitle()269   const std::string& window_subtitle() const { return subtitle_; }
270 
271   // Wether we should display the subtitle.
should_display_subtitle()272   bool should_display_subtitle() const { return display_subtitle_; }
273 
274   // Access to the GrapihcsSystem global variables.
globals()275   GraphicsSystemGlobals& globals() { return globals_; }
276 
277   // The `show object' flags are used to provide a way of enabling or
278   // disabling interface elements from the menu. If an object's
279   // `ObjectOnOff' property is set to 1 or 2, it will be shown or
280   // hidden depending on the corresponding `show object' flag. This is
281   // one of the properties controlled by the \#OBJECT variables in
282   // gameexe.ini.
should_show_object1()283   int should_show_object1() const { return globals_.show_object_1; }
set_should_show_object1(const int in)284   void set_should_show_object1(const int in) { globals_.show_object_1 = in; }
should_show_object2()285   int should_show_object2() const { return globals_.show_object_2; }
set_should_show_object2(const int in)286   void set_should_show_object2(const int in) { globals_.show_object_2 = in; }
should_show_weather()287   int should_show_weather() const { return globals_.show_weather; }
set_should_show_weather(const int in)288   void set_should_show_weather(const int in) { globals_.show_weather = in; }
289 
290   // Sets whether we're in fullscreen mode. SetScreenMode() is virtual so we
291   // can tell SDL to switch the screen mode.
screen_mode()292   int screen_mode() const { return globals_.screen_mode; }
293   virtual void SetScreenMode(const int in);
294   void ToggleFullscreen();
295 
296   // Toggles whether the interface is shown. Called by
297   // PauseLongOperation and related functors.
298   void ToggleInterfaceHidden();
is_interface_hidden()299   bool is_interface_hidden() { return interface_hidden_; }
300 
301   // Whether we should skip animations (such as all the Effect subclasses).
should_skip_animations()302   int should_skip_animations() const { return globals_.skip_animations; }
set_should_skip_animations(const int in)303   void set_should_skip_animations(const int in) {
304     globals_.skip_animations = in;
305   }
306 
307   // Returns the ObjectSettings from the Gameexe for obj_num. The data
308   // from this method should be used by all subclasses of
309   // GraphicsSystem when deciding whether to render an object or not.
310   const ObjectSettings& GetObjectSettings(const int obj_num);
311 
312   // Should be called by any of the drawing functions the screen is
313   // invalidated.
314   //
315   // For more information, please see section 5.10.4 of the RLDev
316   // manual, which deals with the behaviour of screen updates, and the
317   // various modes.
318   virtual void MarkScreenAsDirty(GraphicsUpdateType type);
319 
320   // Forces a refresh of the screen the next time the graphics system
321   // executes.
322   virtual void ForceRefresh();
323 
screen_needs_refresh()324   bool screen_needs_refresh() const { return screen_needs_refresh_; }
325   void OnScreenRefreshed();
326 
327   // We keep a separate state about whether object state has been modified. We
328   // do this so that background object mutation in automatic mode plays nicely
329   // with LongOperations.
mark_object_state_as_dirty()330   void mark_object_state_as_dirty() { object_state_dirty_ = true; }
object_state_dirty()331   bool object_state_dirty() const { return object_state_dirty_; }
332 
333   virtual void BeginFrame() = 0;
334   virtual void EndFrame() = 0;
335   virtual std::shared_ptr<Surface> EndFrameToSurface() = 0;
336 
337   // Performs a full redraw of the screen.
338   void Refresh(std::ostream* tree);
339 
340   // Draws the screen (as if refresh() was called), but draw to the returned
341   // surface instead of the screen.
342   std::shared_ptr<Surface> RenderToSurface();
343 
344   // Called from the game loop; Does everything that's needed to keep
345   // things up.
346   virtual void ExecuteGraphicsSystem(RLMachine& machine);
347 
348   // Returns the size of the window in pixels.
screen_size()349   const Size& screen_size() const { return screen_size_; }
350 
351   // Returns a rectangle with an origin of (0,0) and a size returned by
352   // screen_size().
screen_rect()353   const Rect& screen_rect() const { return screen_rect_; }
354 
355   virtual void AllocateDC(int dc, Size size) = 0;
356   virtual void SetMinimumSizeForDC(int dc, Size size) = 0;
357   virtual void FreeDC(int dc) = 0;
358 
359   // Loads an image, optionally marking that this image has been loaded (if it
360   // is in the game's CGM table).
361   std::shared_ptr<const Surface> GetSurfaceNamedAndMarkViewed(
362       RLMachine& machine,
363       const std::string& short_filename);
364 
365   // Just loads an image. This shouldn't be used for images that are destined
366   // for one of the DCs, since those can be CGs.
367   std::shared_ptr<const Surface> GetSurfaceNamed(
368       const std::string& short_filename);
369 
370   virtual std::shared_ptr<Surface> GetHaikei() = 0;
371 
372   virtual std::shared_ptr<Surface> GetDC(int dc) = 0;
373 
374   virtual std::shared_ptr<Surface> BuildSurface(const Size& size) = 0;
375 
376   virtual ColourFilter* BuildColourFiller() = 0;
377 
378   // Clears and promotes objects.
379   void ClearAndPromoteObjects();
380 
381   // Calls render() on all foreground objects that need to be
382   // rendered.
383   void RenderObjects(std::ostream* tree);
384 
385   // Creates rendering data for a graphics object from a G00, PDT or ANM file.
386   // Does not deal with GAN files. Those are built with a separate function.
387   GraphicsObjectData* BuildObjOfFile(const std::string& filename);
388 
389   // Object getters
390   // layer == 0 for fg, layer == 1 for bg.
391   GraphicsObject& GetObject(int layer, int obj_number);
392   void SetObject(int layer, int obj_number, GraphicsObject& object);
393 
394   // Frees the object data (but not the parameters).
395   void FreeObjectData(int obj_number);
396   void FreeAllObjectData();
397 
398   // Resets/reinitializes all the object parameters without deleting the loaded
399   // graphics object data.
400   void InitializeObjectParams(int obj_number);
401   void InitializeAllObjectParams();
402 
403   // The number of objects in a layer for this game. Defaults to 256 and can be
404   // overridden with #OBJECT_MAX.
405   int GetObjectLayerSize();
406 
407   LazyArray<GraphicsObject>& GetBackgroundObjects();
408   LazyArray<GraphicsObject>& GetForegroundObjects();
409 
410   // Returns true if there's a currently playing animation.
411   bool AnimationsPlaying() const;
412 
413   // Takes a snapshot of the current object state. This snapshot is saved
414   // instead of the current state of the graphics, since RealLive is a savepoint
415   // based system.
416   //
417   // (This operation isn't exceptionally expensive; internally GraphicsObject
418   // has multiple copy-on-write data structs to make this and object promotion a
419   // relativly cheap operation.)
420   void TakeSavepointSnapshot();
421 
422   // Sets DC0 to black and frees up DCs 1 through 16.
423   void ClearAllDCs();
424 
425   // Implementation of MouseMotionListener:
426   virtual void MouseMotion(const Point& new_location) override;
427 
428   // Reset the system. Should clear all state for when a user loads a game.
429   virtual void Reset();
430 
431   // Access to the cgtable for the cg* functions.
cg_table()432   CGMTable& cg_table() { return globals_.cg_table; }
433 
434   // Access to the tone curve effects file
tone_curve()435   ToneCurve& tone_curve() { return globals_.tone_curves; }
436 
437   // Gets the emoji surface, if any.
438   std::shared_ptr<const Surface> GetEmojiSurface();
439 
440   // We have a cache of HIK scripts. This is done so we can load HIKScripts
441   // outside of loops.
442   void PreloadHIKScript(System& system,
443                         int slot,
444                         const std::string& name,
445                         const boost::filesystem::path& file);
446   void ClearPreloadedHIKScript(int slot);
447   void ClearAllPreloadedHIKScripts();
448   std::shared_ptr<HIKScript> GetHIKScript(
449       System& system,
450       const std::string& name,
451       const boost::filesystem::path& file);
452 
453   // We have a cache of preloaded g00 files.
454   void PreloadG00(int slot, const std::string& name);
455   void ClearPreloadedG00(int slot);
456   void ClearAllPreloadedG00();
457   std::shared_ptr<const Surface> GetPreloadedG00(const std::string& name);
458 
459  protected:
460   typedef std::set<Renderable*> FinalRenderers;
461 
renderer_begin()462   FinalRenderers::iterator renderer_begin() { return final_renderers_.begin(); }
renderer_end()463   FinalRenderers::iterator renderer_end() { return final_renderers_.end(); }
464 
cursor_pos()465   const Point& cursor_pos() const { return cursor_pos_; }
466 
467   std::shared_ptr<MouseCursor> GetCurrentCursor();
468 
469   void SetScreenSize(const Size& size);
470 
471   void DrawFrame(std::ostream* tree);
472 
473  private:
474   // Gets a platform appropriate surface loaded.
475   virtual std::shared_ptr<const Surface> LoadSurfaceFromFile(
476       const std::string& short_filename) = 0;
477 
478   // Default grp name (used in grp* and rec* functions where filename
479   // is '???')
480   std::string default_grp_name_;
481 
482   // Default bgr name (used in bgr* functions where filename is
483   // '???')
484   std::string default_bgr_name_;
485 
486   // Current screen update mode
487   DCScreenUpdateMode screen_update_mode_;
488 
489   // Whether we display HIK or DC0.
490   GraphicsBackgroundType background_type_;
491 
492   // Flag set to redraw the screen NOW
493   bool screen_needs_refresh_;
494 
495   // Whether object state has been mutated since the last screen refresh.
496   bool object_state_dirty_;
497 
498   // Whether it is the Graphics system's responsibility to redraw the
499   // screen. Some LongOperations temporarily take this responsibility
500   // to implement pretty fades and wipes
501   bool is_responsible_for_update_;
502 
503   // Whether we should try to append subtitle_ in the window
504   // titlebar
505   bool display_subtitle_;
506 
507   // cp932 encoded subtitle string
508   std::string subtitle_;
509 
510   // Controls whether we render the interface (this can be
511   // temporarily toggled by the user at runtime)
512   bool interface_hidden_;
513 
514   // Mutable global data to be saved in the globals file
515   GraphicsSystemGlobals globals_;
516 
517   // Size of our display window.
518   Size screen_size_;
519 
520   // Rectangle of the screen.
521   Rect screen_rect_;
522 
523   // Queued origin/time pairs. The front of the queue shall be the current
524   // screen offset.
525   std::queue<std::pair<Point, int>> screen_shake_queue_;
526 
527   // The last time |screen_shake_queue_| was modified.
528   unsigned int time_at_last_queue_change_;
529 
530   // Immutable
531   struct GraphicsObjectSettings;
532   // Immutable global data that's constructed from the Gameexe.ini file.
533   std::unique_ptr<GraphicsObjectSettings> graphics_object_settings_;
534 
535   struct GraphicsObjectImpl;
536   std::unique_ptr<GraphicsObjectImpl> graphics_object_impl_;
537 
538   // Whether we should use a custom mouse cursor. Set while parsing the Gameexe
539   // file, and then left unchanged. We only use a custom mouse cursor if
540   // \#MOUSE_CURSOR is set in the Gameexe
541   bool use_custom_mouse_cursor_;
542 
543   // Whether we should render any cursor. Controller by the bytecode.
544   bool show_cursor_from_bytecode_;
545 
546   // Current cursor id. Initially set to \#MOUSE_CURSOR if the key exists.
547   int cursor_;
548 
549   // Location of the cursor's hotspot
550   Point cursor_pos_;
551 
552   // Current mouse cursor
553   std::shared_ptr<MouseCursor> mouse_cursor_;
554 
555   // MouseCursor construction is nontrivial so cache everything we
556   // build:
557   typedef std::map<int, std::shared_ptr<MouseCursor>> MouseCursorCache;
558   MouseCursorCache cursor_cache_;
559 
560   // A set of renderers
561   FinalRenderers final_renderers_;
562 
563   // Our parent system object.
564   System& system_;
565 
566   // Preloaded HIKScripts.
567   typedef std::pair<std::string, std::shared_ptr<HIKScript>> HIKArrayItem;
568   typedef LazyArray<HIKArrayItem> HIKScriptList;
569   HIKScriptList preloaded_hik_scripts_;
570 
571   // Preloaded G00 images.
572   typedef std::pair<std::string, std::shared_ptr<const Surface>> G00ArrayItem;
573   typedef LazyArray<G00ArrayItem> G00ScriptList;
574   G00ScriptList preloaded_g00_;
575 
576   // LRU cache filled with the last fifteen accessed images.
577   //
578   // This cache's contents are assumed to be immutable.
579   LRUCache<std::string, std::shared_ptr<const Surface>> image_cache_;
580 
581   // Possible background script which drives graphics to the screen.
582   std::unique_ptr<HIKRenderer> hik_renderer_;
583 
584   // Tuple used in RenderObjects(). Causes about a half megabyte of allocator
585   // churn per minute if we try to allocate it every time.
586   //
587   // The tuple is order, layer, depth, objid, GraphicsObject. Tuples are easy
588   // to sort.
589   typedef std::vector<std::tuple<int, int, int, int, GraphicsObject*>>
590       ToRenderVec;
591   ToRenderVec to_render_;
592 
593   // boost::serialization support
594   friend class boost::serialization::access;
595 
596   // boost::serialization forward declaration
597   template <class Archive>
598   void save(Archive& ar, const unsigned int file_version) const;
599 
600   // boost::serialization forward declaration
601   template <class Archive>
602   void load(Archive& ar, const unsigned int file_version);
603 
604   BOOST_SERIALIZATION_SPLIT_MEMBER()
605 };
606 
607 BOOST_CLASS_VERSION(GraphicsSystem, 1)
608 
609 #endif  // SRC_SYSTEMS_BASE_GRAPHICS_SYSTEM_H_
610