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