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