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