openMSX
Display.cc
Go to the documentation of this file.
1 #include "Display.hh"
2 #include "Layer.hh"
3 #include "VideoSystem.hh"
4 #include "VideoLayer.hh"
5 #include "EventDistributor.hh"
6 #include "FinishFrameEvent.hh"
7 #include "FileOperations.hh"
8 #include "FileContext.hh"
9 #include "InputEvents.hh"
10 #include "AlarmEvent.hh"
11 #include "Command.hh"
12 #include "InfoTopic.hh"
13 #include "OSDGUI.hh"
14 #include "CliComm.hh"
15 #include "CommandConsole.hh"
16 #include "Timer.hh"
17 #include "RenderSettings.hh"
18 #include "BooleanSetting.hh"
19 #include "IntegerSetting.hh"
20 #include "EnumSetting.hh"
21 #include "Reactor.hh"
22 #include "MSXMotherBoard.hh"
23 #include "HardwareConfig.hh"
24 #include "XMLElement.hh"
26 #include "CommandException.hh"
27 #include "StringOp.hh"
28 #include "Version.hh"
29 #include "build-info.hh"
30 #include "checked_cast.hh"
31 #include "stl.hh"
32 #include "unreachable.hh"
33 #include "memory.hh"
34 #include <algorithm>
35 #include <cassert>
36 
37 using std::string;
38 using std::vector;
39 
40 namespace openmsx {
41 
42 class ScreenShotCmd : public Command
43 {
44 public:
45  ScreenShotCmd(CommandController& commandController, Display& display);
46  virtual void execute(array_ref<TclObject> tokens, TclObject& result);
47  virtual string help(const vector<string>& tokens) const;
48  virtual void tabCompletion(vector<string>& tokens) const;
49 private:
50  Display& display;
51 };
52 
53 class FpsInfoTopic : public InfoTopic
54 {
55 public:
56  FpsInfoTopic(InfoCommand& openMSXInfoCommand, Display& display);
57  virtual void execute(array_ref<TclObject> tokens,
58  TclObject& result) const;
59  virtual string help(const vector<string>& tokens) const;
60 private:
61  Display& display;
62 };
63 
64 
66  : alarm(make_unique<AlarmEvent>(
67  reactor_.getEventDistributor(), *this,
69  , screenShotCmd(make_unique<ScreenShotCmd>(
70  reactor_.getCommandController(), *this))
71  , fpsInfo(make_unique<FpsInfoTopic>(
72  reactor_.getOpenMSXInfoCommand(), *this))
73  , osdGui(make_unique<OSDGUI>(
74  reactor_.getCommandController(), *this))
75  , reactor(reactor_)
76  , renderSettings(make_unique<RenderSettings>(
77  reactor.getCommandController()))
78  , commandConsole(make_unique<CommandConsole>(
79  reactor.getGlobalCommandController(),
80  reactor.getEventDistributor(), *this))
81  , currentRenderer(RendererFactory::UNINITIALIZED)
82  , switchInProgress(false)
83 {
84  frameDurationSum = 0;
85  for (unsigned i = 0; i < NUM_FRAME_DURATIONS; ++i) {
86  frameDurations.addFront(20);
87  frameDurationSum += 20;
88  }
89  prevTimeStamp = Timer::getTime();
90 
91  EventDistributor& eventDistributor = reactor.getEventDistributor();
93  *this);
95  *this);
97  *this);
99  *this);
100 #if PLATFORM_ANDROID
102  *this);
103 #endif
104  renderSettings->getRenderer().attach(*this);
105  renderSettings->getFullScreen().attach(*this);
106  renderSettings->getScaleFactor().attach(*this);
107  renderFrozen = false;
108 }
109 
111 {
112  renderSettings->getRenderer().detach(*this);
113  renderSettings->getFullScreen().detach(*this);
114  renderSettings->getScaleFactor().detach(*this);
115 
116  EventDistributor& eventDistributor = reactor.getEventDistributor();
117 #if PLATFORM_ANDROID
119  *this);
120 #endif
122  *this);
124  *this);
126  *this);
128  *this);
129 
130  PRT_DEBUG("Reset video system...");
131  resetVideoSystem();
132  PRT_DEBUG("Reset video system... DONE!");
133 
134  assert(listeners.empty());
135 }
136 
138 {
139  assert(!videoSystem);
140  assert(currentRenderer == RendererFactory::UNINITIALIZED);
141  assert(!switchInProgress);
142  currentRenderer = renderSettings->getRenderer().getEnum();
143  switchInProgress = true;
144  doRendererSwitch();
145 }
146 
148 {
149  assert(videoSystem);
150  return *videoSystem;
151 }
152 
153 void Display::resetVideoSystem()
154 {
155  videoSystem.reset();
156  // At this point all layers expect for the Video9000 layer
157  // should be gone.
158  //assert(layers.empty());
159 }
160 
162 {
163  return reactor.getCliComm();
164 }
165 
167 {
168  return *renderSettings;
169 }
170 
172 {
173  return *osdGui;
174 }
175 
177 {
178  return *commandConsole;
179 }
180 
182 {
183  assert(!contains(listeners, &listener));
184  listeners.push_back(&listener);
185 }
186 
188 {
189  listeners.erase(find_unguarded(listeners, &listener));
190 }
191 
193 {
194  for (auto& l : layers) {
195  if (l->getZ() == Layer::Z_MSX_ACTIVE) {
196  return l;
197  }
198  }
199  return nullptr;
200 }
201 
202 Display::Layers::iterator Display::baseLayer()
203 {
204  // Note: It is possible to cache this, but since the number of layers is
205  // low at the moment, it's not really worth it.
206  auto it = end(layers);
207  while (true) {
208  if (it == begin(layers)) {
209  // There should always be at least one opaque layer.
210  // TODO: This is not true for DummyVideoSystem.
211  // Anyway, a missing layer will probably stand out visually,
212  // so do we really have to assert on it?
213  //UNREACHABLE;
214  return it;
215  }
216  --it;
217  if ((*it)->getCoverage() == Layer::COVER_FULL) return it;
218  }
219 }
220 
221 int Display::signalEvent(const std::shared_ptr<const Event>& event)
222 {
223  if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) {
224  auto& ffe = checked_cast<const FinishFrameEvent&>(*event);
225  if (ffe.needRender()) {
226  repaint();
228  std::make_shared<SimpleEvent>(
230  }
231  } else if (event->getType() == OPENMSX_DELAYED_REPAINT_EVENT) {
232  repaint();
233  } else if (event->getType() == OPENMSX_SWITCH_RENDERER_EVENT) {
234  doRendererSwitch();
235  } else if (event->getType() == OPENMSX_MACHINE_LOADED_EVENT) {
236  setWindowTitle();
237  } else if (event->getType() == OPENMSX_EXPOSE_EVENT) {
238  // Don't render too often, and certainly not when the screen
239  // will anyway soon be rendered.
240  repaintDelayed(100 * 1000); // 10fps
241  } else if (PLATFORM_ANDROID && event->getType() == OPENMSX_FOCUS_EVENT) {
242  // On Android, the rendering must be frozen when the app is sent to
243  // the background, because Android takes away all graphics resources
244  // from the app. It simply destroys the entire graphics context.
245  // Though, a repaint() must happen within the focus-lost event
246  // so that the SDL Android port realises that the graphix context
247  // is gone and will re-build it again on the first flush to the
248  // surface after the focus has been regained.
249 
250  // Perform a repaint before updating the renderFrozen flag:
251  // -When loosing the focus, this repaint will flush a last
252  // time the SDL surface, making sure that the Android SDL
253  // port discovers that the graphics context is gone.
254  // -When gaining the focus, this repaint does nothing as
255  // the renderFrozen flag is still false
256  repaint();
257  auto& focusEvent = checked_cast<const FocusEvent&>(*event);
258  ad_printf("Setting renderFrozen to %d", !focusEvent.getGain());
259  renderFrozen = !focusEvent.getGain();
260  }
261  return 0;
262 }
263 
264 void Display::setWindowTitle()
265 {
266  string title = Version::full();
267  if (!Version::RELEASE) {
268  title += " [";
269  title += BUILD_FLAVOUR;
270  title += ']';
271  }
272  if (MSXMotherBoard* motherboard = reactor.getMotherBoard()) {
273  if (const HardwareConfig* machine = motherboard->getMachineConfig()) {
274  const XMLElement& config = machine->getConfig();
275  title += " - " +
276  config.getChild("info").getChildData("manufacturer") + ' ' +
277  config.getChild("info").getChildData("code");
278  }
279  }
280  videoSystem->setWindowTitle(title);
281 }
282 
283 void Display::update(const Setting& setting)
284 {
285  if (&setting == &renderSettings->getRenderer()) {
286  checkRendererSwitch();
287  } else if (&setting == &renderSettings->getFullScreen()) {
288  checkRendererSwitch();
289  } else if (&setting == &renderSettings->getScaleFactor()) {
290  checkRendererSwitch();
291  } else {
292  UNREACHABLE;
293  }
294 }
295 
296 void Display::checkRendererSwitch()
297 {
298  if (switchInProgress) {
299  // This method only queues a request to switch renderer (see
300  // comments below why). If there already is such a request
301  // queued we don't need to do it again.
302  return;
303  }
304  RendererFactory::RendererID newRenderer =
305  renderSettings->getRenderer().getEnum();
306  if ((newRenderer != currentRenderer) ||
307  !getVideoSystem().checkSettings()) {
308  currentRenderer = newRenderer;
309  // don't do the actualing swithing in the Tcl callback
310  // it seems creating and destroying Settings (= Tcl vars)
311  // causes problems???
312  switchInProgress = true;
314  std::make_shared<SimpleEvent>(
316  }
317 }
318 
319 void Display::doRendererSwitch()
320 {
321  assert(switchInProgress);
322 
323  bool success = false;
324  while (!success) {
325  try {
326  doRendererSwitch2();
327  success = true;
328  } catch (MSXException& e) {
329  string errorMsg = "Couldn't activate renderer " +
330  renderSettings->getRenderer().getString() +
331  ": " + e.getMessage();
332  // now try some things that might work against this:
333  if (renderSettings->getRenderer().getEnum() != RendererFactory::SDL) {
334  errorMsg += "\nTrying to switch to SDL renderer instead...";
335  renderSettings->getRenderer().setEnum(RendererFactory::SDL);
336  currentRenderer = RendererFactory::SDL;
337  } else {
338  unsigned curval = renderSettings->getScaleFactor().getInt();
339  if (curval == 1) throw MSXException(e.getMessage() + " (and I have no other ideas to try...)"); // give up and die... :(
340  errorMsg += "\nTrying to decrease scale_factor setting from " + StringOp::toString(curval) + " to " + StringOp::toString(curval - 1) + "...";
341  renderSettings->getScaleFactor().setInt(curval - 1);
342  }
343  getCliComm().printWarning(errorMsg);
344  }
345  }
346 
347  switchInProgress = false;
348 }
349 
350 void Display::doRendererSwitch2()
351 {
352  for (auto& l : listeners) {
353  l->preVideoSystemChange();
354  }
355 
356  resetVideoSystem();
357  videoSystem = RendererFactory::createVideoSystem(reactor);
358  setWindowTitle();
359 
360  for (auto& l : listeners) {
361  l->postVideoSystemChange();
362  }
363 }
364 
366 {
367  if (switchInProgress) {
368  // The checkRendererSwitch() method will queue a
369  // SWITCH_RENDERER_EVENT, but before that event is handled
370  // we shouldn't do any repaints (with inconsistent setting
371  // values and render objects). This can happen when this
372  // method gets called because of a DELAYED_REPAINT_EVENT
373  // (DELAYED_REPAINT_EVENT was already queued before
374  // SWITCH_RENDERER_EVENT is queued).
375  return;
376  }
377 
378  alarm->cancel(); // cancel delayed repaint
379 
380  if (!renderFrozen) {
381  assert(videoSystem);
382  if (OutputSurface* surface = videoSystem->getOutputSurface()) {
383  repaint(*surface);
384  videoSystem->flush();
385  }
386  }
387 
388  // update fps statistics
389  auto now = Timer::getTime();
390  auto duration = now - prevTimeStamp;
391  prevTimeStamp = now;
392  frameDurationSum += duration - frameDurations.removeBack();
393  frameDurations.addFront(duration);
394 }
395 
397 {
398  for (auto it = baseLayer(); it != end(layers); ++it) {
399  if ((*it)->getCoverage() != Layer::COVER_NONE) {
400  (*it)->paint(surface);
401  }
402  }
403 }
404 
405 void Display::repaintDelayed(uint64_t delta)
406 {
407  if (alarm->pending()) {
408  // already a pending repaint
409  return;
410  }
411  alarm->schedule(unsigned(delta));
412 }
413 
415 {
416  int z = layer.getZ();
417  auto it = find_if(begin(layers), end(layers),
418  [&](Layer* l) { return l->getZ() > z; });
419  layers.insert(it, &layer);
420  layer.setDisplay(*this);
421 }
422 
424 {
425  layers.erase(find_unguarded(layers, &layer));
426 }
427 
428 void Display::updateZ(Layer& layer)
429 {
430  // Remove at old Z-index...
431  removeLayer(layer);
432  // ...and re-insert at new Z-index.
433  addLayer(layer);
434 }
435 
436 
437 // ScreenShotCmd
438 
440  Display& display_)
441  : Command(commandController, "screenshot")
442  , display(display_)
443 {
444 }
445 
447 {
448  bool rawShot = false;
449  bool withOsd = false;
450  bool doubleSize = false;
451  string_ref prefix = "openmsx";
452  vector<TclObject> arguments;
453  for (unsigned i = 1; i < tokens.size(); ++i) {
454  string_ref tok = tokens[i].getString();
455  if (StringOp::startsWith(tok, '-')) {
456  if (tok == "--") {
457  arguments.insert(end(arguments),
458  std::begin(tokens) + i + 1, std::end(tokens));
459  break;
460  }
461  if (tok == "-prefix") {
462  if (++i == tokens.size()) {
463  throw CommandException("Missing argument");
464  }
465  prefix = tokens[i].getString();
466  } else if (tok == "-raw") {
467  rawShot = true;
468  } else if (tok == "-msxonly") {
469  display.getCliComm().printWarning(
470  "The -msxonly option has been deprecated and will "
471  "be removed in a future release. Instead, use the "
472  "-raw option for the same effect.");
473  rawShot = true;
474  } else if (tok == "-doublesize") {
475  doubleSize = true;
476  } else if (tok == "-with-osd") {
477  withOsd = true;
478  } else {
479  throw CommandException("Invalid option: " + tok);
480  }
481  } else {
482  arguments.push_back(tokens[i]);
483  }
484  }
485  if (doubleSize && !rawShot) {
486  throw CommandException("-doublesize option can only be used in "
487  "combination with -raw");
488  }
489  if (rawShot && withOsd) {
490  throw CommandException("-with-osd cannot be used in "
491  "combination with -raw");
492  }
493 
494  string_ref fname;
495  switch (arguments.size()) {
496  case 0:
497  // nothing
498  break;
499  case 1:
500  fname = arguments[0].getString();
501  break;
502  default:
503  throw SyntaxError();
504  }
506  fname, "screenshots", prefix, ".png");
507 
508  if (!rawShot) {
509  // include all layers (OSD stuff, console)
510  try {
511  display.getVideoSystem().takeScreenShot(filename, withOsd);
512  } catch (MSXException& e) {
513  throw CommandException(
514  "Failed to take screenshot: " + e.getMessage());
515  }
516  } else {
517  auto videoLayer = dynamic_cast<VideoLayer*>(
518  display.findActiveLayer());
519  if (!videoLayer) {
520  throw CommandException(
521  "Current renderer doesn't support taking screenshots.");
522  }
523  unsigned height = doubleSize ? 480 : 240;
524  try {
525  videoLayer->takeRawScreenShot(height, filename);
526  } catch (MSXException& e) {
527  throw CommandException(
528  "Failed to take screenshot: " + e.getMessage());
529  }
530  }
531 
532  display.getCliComm().printInfo("Screen saved to " + filename);
533  result.setString(filename);
534 }
535 
536 string ScreenShotCmd::help(const vector<string>& /*tokens*/) const
537 {
538  // Note: -no-sprites option is implemented in Tcl
539  return
540  "screenshot Write screenshot to file \"openmsxNNNN.png\"\n"
541  "screenshot <filename> Write screenshot to indicated file\n"
542  "screenshot -prefix foo Write screenshot to file \"fooNNNN.png\"\n"
543  "screenshot -raw 320x240 raw screenshot (of MSX screen only)\n"
544  "screenshot -raw -doublesize 640x480 raw screenshot (of MSX screen only)\n"
545  "screenshot -with-osd Include OSD elements in the screenshot\n"
546  "screenshot -no-sprites Don't include sprites in the screenshot\n";
547 }
548 
549 void ScreenShotCmd::tabCompletion(vector<string>& tokens) const
550 {
551  static const char* const extra[] = {
552  "-prefix", "-raw", "-doublesize", "-with-osd", "-no-sprites",
553  };
554  completeFileName(tokens, UserFileContext(), extra);
555 }
556 
557 
558 // FpsInfoTopic
559 
561  Display& display_)
562  : InfoTopic(openMSXInfoCommand, "fps")
563  , display(display_)
564 {
565 }
566 
568  TclObject& result) const
569 {
570  double fps = 1000000.0 * Display::NUM_FRAME_DURATIONS / display.frameDurationSum;
571  result.setDouble(fps);
572 }
573 
574 string FpsInfoTopic::help(const vector<string>& /*tokens*/) const
575 {
576  return "Returns the current rendering speed in frames per second.";
577 }
578 
579 } // namespace openmsx
void setDouble(double value)
Definition: TclObject.cc:88
virtual string help(const vector< string > &tokens) const
Print help for this topic.
Definition: Display.cc:574
virtual string help(const vector< string > &tokens) const
Print help for this command.
Definition: Display.cc:536
Contains the main loop of openMSX.
Definition: Reactor.hh:62
Sent when a OPENMSX_FINISH_FRAME_EVENT caused a redraw of the screen.
Definition: Event.hh:36
void createVideoSystem()
Definition: Display.cc:137
string_ref::const_iterator end(const string_ref &x)
Definition: string_ref.hh:135
Layer is not visible, that is completely transparent.
Definition: Layer.hh:39
size_type size() const
Definition: array_ref.hh:61
Represents the output window/screen of openMSX.
Definition: Display.hh:33
Send when a (new) machine configuration is loaded.
Definition: Event.hh:67
ITER find_unguarded(ITER first, ITER last, const VAL &val)
Faster alternative to 'find' when it's guaranteed that the value will be found (if not the behavior i...
Definition: stl.hh:114
Layer * findActiveLayer() const
Definition: Display.cc:192
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
void addLayer(Layer &layer)
Definition: Display.cc:414
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
string toString(long long a)
Definition: StringOp.cc:155
RendererID
Enumeration of Renderers known to openMSX.
void printWarning(string_ref message)
Definition: CliComm.cc:28
Interface for display layers.
Definition: Layer.hh:11
CliComm & getCliComm() const
Definition: Display.cc:161
Display(Reactor &reactor)
Definition: Display.cc:65
Layer fully covers the screen: any underlying layers are invisible.
Definition: Layer.hh:32
bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:95
void distributeEvent(const EventPtr &event)
Schedule the given event for delivery.
virtual void execute(array_ref< TclObject > tokens, TclObject &result)
Execute this command.
Definition: Display.cc:446
EventDistributor & getEventDistributor()
Definition: Reactor.cc:278
This class implements a subset of the proposal for std::string_ref (proposed for the next c++ standar...
Definition: string_ref.hh:18
A frame buffer where pixels can be written to.
unique_ptr< VideoSystem > createVideoSystem(Reactor &reactor)
Create the video system required by the current renderer setting.
#define PLATFORM_ANDROID
Definition: build-info.hh:18
static const bool RELEASE
Definition: Version.hh:12
VideoSystem & getVideoSystem()
Definition: Display.cc:147
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:102
void repaint()
Redraw the display.
Definition: Display.cc:365
Sent when VDP (V99x8 or V9990) reaches the end of a frame.
Definition: Event.hh:30
void removeLayer(Layer &layer)
Definition: Display.cc:423
bool startsWith(string_ref total, string_ref part)
Definition: StringOp.cc:241
This class implements a subset of the proposal for std::array_ref (proposed for the next c++ standard...
Definition: array_ref.hh:19
void detach(VideoSystemChangeListener &listener)
Definition: Display.cc:187
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:402
string parseCommandFileArgument(string_ref argument, string_ref directory, string_ref prefix, string_ref extension)
Helper function for parsing filename arguments in Tcl commands.
void setDisplay(LayerListener &display)
Store pointer to Display.
Definition: Layer.cc:26
const std::string & getMessage() const
Definition: MSXException.hh:14
FpsInfoTopic(InfoCommand &openMSXInfoCommand, Display &display)
Definition: Display.cc:560
Video back-end system.
Definition: VideoSystem.hh:20
virtual void tabCompletion(vector< string > &tokens) const
Attempt tab completion for this command.
Definition: Display.cc:549
void repaintDelayed(uint64_t delta)
Definition: Display.cc:405
virtual void execute(array_ref< TclObject > tokens, TclObject &result) const
Show info on this topic.
Definition: Display.cc:567
Send when (part of) the openMSX window gets exposed, and thus should be repainted.
Definition: Event.hh:76
virtual void takeScreenShot(const std::string &filename, bool withOsd)
Take a screenshot.
Definition: VideoSystem.cc:15
ZIndex getZ() const
Query the Z-index of this layer.
Definition: Layer.hh:50
RenderSettings & getRenderSettings() const
Definition: Display.cc:166
void attach(VideoSystemChangeListener &listener)
Definition: Display.cc:181
void setString(string_ref value)
Definition: TclObject.cc:55
Class containing all settings for renderers.
static std::string full()
Definition: Version.cc:7
void printInfo(string_ref message)
Definition: CliComm.cc:23
CliComm & getCliComm()
Definition: Reactor.cc:293
OSDGUI & getOSDGUI() const
Definition: Display.cc:171
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:24
ScreenShotCmd(CommandController &commandController, Display &display)
Definition: Display.cc:439
CommandConsole & getCommandConsole()
Definition: Display.cc:176
void addFront(const T &element)
string_ref::const_iterator begin(const string_ref &x)
Definition: string_ref.hh:134
Convenience wrapper around the Alarm class.
Definition: AlarmEvent.hh:18
std::unique_ptr< T > make_unique()
Definition: memory.hh:27
#define ad_printf(...)
Definition: openmsx.hh:16
#define PRT_DEBUG(mes)
Definition: openmsx.hh:69
virtual ~Display()
Definition: Display.cc:110
#define UNREACHABLE
Definition: unreachable.hh:56