openMSX
Display.cc
Go to the documentation of this file.
1#include "Display.hh"
2#include "RendererFactory.hh"
3#include "Layer.hh"
4#include "VideoSystem.hh"
5#include "VideoLayer.hh"
6#include "EventDistributor.hh"
7#include "Event.hh"
8#include "FileOperations.hh"
9#include "FileContext.hh"
10#include "CliComm.hh"
11#include "Timer.hh"
12#include "BooleanSetting.hh"
13#include "IntegerSetting.hh"
14#include "EnumSetting.hh"
15#include "Reactor.hh"
16#include "MSXMotherBoard.hh"
17#include "HardwareConfig.hh"
18#include "TclArgParser.hh"
19#include "XMLElement.hh"
21#include "CommandException.hh"
22#include "Version.hh"
23#include "build-info.hh"
24#include "narrow.hh"
25#include "outer.hh"
26#include "ranges.hh"
27#include "stl.hh"
28#include "unreachable.hh"
29#include "xrange.hh"
30#include <array>
31#include <cassert>
32
33using std::string;
34
35namespace openmsx {
36
38 : RTSchedulable(reactor_.getRTScheduler())
39 , screenShotCmd(reactor_.getCommandController())
40 , fpsInfo(reactor_.getOpenMSXInfoCommand())
41 , osdGui(reactor_.getCommandController(), *this)
42 , reactor(reactor_)
43 , renderSettings(reactor.getCommandController())
44 , commandConsole(reactor.getGlobalCommandController(),
45 reactor.getEventDistributor(), *this)
46{
47 frameDurationSum = 0;
48 repeat(NUM_FRAME_DURATIONS, [&] {
49 frameDurations.addFront(20);
50 frameDurationSum += 20;
51 });
52 prevTimeStamp = Timer::getTime();
53
54 EventDistributor& eventDistributor = reactor.getEventDistributor();
56 *this);
58 *this);
60 *this);
62 *this);
63#if PLATFORM_ANDROID
65 *this);
66#endif
67 renderSettings.getRendererSetting().attach(*this);
68 renderSettings.getFullScreenSetting().attach(*this);
69 renderSettings.getScaleFactorSetting().attach(*this);
70}
71
73{
74 renderSettings.getRendererSetting().detach(*this);
75 renderSettings.getFullScreenSetting().detach(*this);
76 renderSettings.getScaleFactorSetting().detach(*this);
77
78 EventDistributor& eventDistributor = reactor.getEventDistributor();
79#if PLATFORM_ANDROID
81 *this);
82#endif
84 *this);
86 *this);
88 *this);
90 *this);
91
92 resetVideoSystem();
93
94 assert(listeners.empty());
95}
96
98{
99 assert(!videoSystem);
100 assert(currentRenderer == RenderSettings::UNINITIALIZED);
101 assert(!switchInProgress);
102 currentRenderer = renderSettings.getRenderer();
103 switchInProgress = true;
104 doRendererSwitch();
105}
106
108{
109 assert(videoSystem);
110 return *videoSystem;
111}
112
114{
115 return videoSystem ? videoSystem->getOutputSurface() : nullptr;
116}
117
118void Display::resetVideoSystem()
119{
120 videoSystem.reset();
121 // At this point all layers except for the Video9000 layer
122 // should be gone.
123 //assert(layers.empty());
124}
125
127{
128 return reactor.getCliComm();
129}
130
132{
133 assert(!contains(listeners, &listener));
134 listeners.push_back(&listener);
135}
136
138{
139 move_pop_back(listeners, rfind_unguarded(listeners, &listener));
140}
141
143{
144 auto it = ranges::find_if(layers, &Layer::isActive);
145 return (it != layers.end()) ? *it : nullptr;
146}
147
148Display::Layers::iterator Display::baseLayer()
149{
150 // Note: It is possible to cache this, but since the number of layers is
151 // low at the moment, it's not really worth it.
152 auto it = end(layers);
153 while (true) {
154 if (it == begin(layers)) {
155 // There should always be at least one opaque layer.
156 // TODO: This is not true for DummyVideoSystem.
157 // Anyway, a missing layer will probably stand out visually,
158 // so do we really have to assert on it?
159 //UNREACHABLE;
160 return it;
161 }
162 --it;
163 if ((*it)->getCoverage() == Layer::COVER_FULL) return it;
164 }
165}
166
167void Display::executeRT()
168{
169 repaint();
170}
171
172int Display::signalEvent(const Event& event)
173{
175 [&](const FinishFrameEvent& e) {
176 if (e.needRender()) {
177 repaint();
179 Event::create<FrameDrawnEvent>());
180 }
181 },
182 [&](const SwitchRendererEvent& /*e*/) {
183 doRendererSwitch(); // might throw
184 },
185 [&](const MachineLoadedEvent& /*e*/) {
186 videoSystem->updateWindowTitle();
187 },
188 [&](const ExposeEvent& /*e*/) {
189 // Don't render too often, and certainly not when the screen
190 // will anyway soon be rendered.
191 repaintDelayed(100 * 1000); // 10fps
192 },
193 [&](const FocusEvent& e) {
194 (void)e;
195 if (PLATFORM_ANDROID) {
196 // On Android, the rendering must be frozen when the app is sent to
197 // the background, because Android takes away all graphics resources
198 // from the app. It simply destroys the entire graphics context.
199 // Though, a repaint() must happen within the focus-lost event
200 // so that the SDL Android port realizes that the graphics context
201 // is gone and will re-build it again on the first flush to the
202 // surface after the focus has been regained.
203
204 // Perform a repaint before updating the renderFrozen flag:
205 // -When loosing the focus, this repaint will flush a last
206 // time the SDL surface, making sure that the Android SDL
207 // port discovers that the graphics context is gone.
208 // -When gaining the focus, this repaint does nothing as
209 // the renderFrozen flag is still false
210 repaint();
211 ad_printf("Setting renderFrozen to %d", !e.getGain());
212 renderFrozen = !e.getGain();
213 }
214 },
215 [](const EventBase&) { /*ignore*/ }
216 }, event);
217 return 0;
218}
219
221{
222 string title = Version::full();
223 if (!Version::RELEASE) {
224 strAppend(title, " [", BUILD_FLAVOUR, ']');
225 }
226 if (MSXMotherBoard* motherboard = reactor.getMotherBoard()) {
227 if (const HardwareConfig* machine = motherboard->getMachineConfig()) {
228 const auto& config = machine->getConfig();
229 strAppend(title, " - ",
230 config.getChild("info").getChildData("manufacturer"), ' ',
231 config.getChild("info").getChildData("code"));
232 }
233 }
234 return title;
235}
236
237void Display::update(const Setting& setting) noexcept
238{
239 if (&setting == &renderSettings.getRendererSetting()) {
240 checkRendererSwitch();
241 } else if (&setting == &renderSettings.getFullScreenSetting()) {
242 checkRendererSwitch();
243 } else if (&setting == &renderSettings.getScaleFactorSetting()) {
244 checkRendererSwitch();
245 } else {
247 }
248}
249
250void Display::checkRendererSwitch()
251{
252 if (switchInProgress) {
253 // This method only queues a request to switch renderer (see
254 // comments below why). If there already is such a request
255 // queued we don't need to do it again.
256 return;
257 }
258 auto newRenderer = renderSettings.getRenderer();
259 if ((newRenderer != currentRenderer) ||
260 !getVideoSystem().checkSettings()) {
261 currentRenderer = newRenderer;
262 // don't do the actual switching in the Tcl callback
263 // it seems creating and destroying Settings (= Tcl vars)
264 // causes problems???
265 switchInProgress = true;
267 Event::create<SwitchRendererEvent>());
268 }
269}
270
271void Display::doRendererSwitch()
272{
273 assert(switchInProgress);
274
275 bool success = false;
276 while (!success) {
277 try {
278 doRendererSwitch2();
279 success = true;
280 } catch (MSXException& e) {
281 auto& rendererSetting = renderSettings.getRendererSetting();
282 string errorMsg = strCat(
283 "Couldn't activate renderer ",
284 rendererSetting.getString(),
285 ": ", e.getMessage());
286 // now try some things that might work against this:
287 if (rendererSetting.getEnum() != RenderSettings::SDL) {
288 errorMsg += "\nTrying to switch to SDL renderer instead...";
289 rendererSetting.setEnum(RenderSettings::SDL);
290 currentRenderer = RenderSettings::SDL;
291 } else {
292 auto& scaleFactorSetting = renderSettings.getScaleFactorSetting();
293 auto curVal = scaleFactorSetting.getInt();
294 if (curVal == 1) {
295 throw FatalError(
296 e.getMessage(),
297 " (and I have no other ideas to try...)"); // give up and die... :(
298 }
299 strAppend(errorMsg, "\nTrying to decrease scale_factor setting from ",
300 curVal, " to ", curVal - 1, "...");
301 scaleFactorSetting.setInt(curVal - 1);
302 }
303 getCliComm().printWarning(errorMsg);
304 }
305 }
306
307 switchInProgress = false;
308}
309
310void Display::doRendererSwitch2()
311{
312 for (auto& l : listeners) {
313 l->preVideoSystemChange();
314 }
315
316 resetVideoSystem();
317 videoSystem = RendererFactory::createVideoSystem(reactor);
318
319 for (auto& l : listeners) {
320 l->postVideoSystemChange();
321 }
322}
323
325{
326 if (switchInProgress) {
327 // The checkRendererSwitch() method will queue a
328 // SWITCH_RENDERER_EVENT, but before that event is handled
329 // we shouldn't do any repaints (with inconsistent setting
330 // values and render objects). This can happen when this
331 // method gets called because of a DELAYED_REPAINT_EVENT
332 // (DELAYED_REPAINT_EVENT was already queued before
333 // SWITCH_RENDERER_EVENT is queued).
334 return;
335 }
336
337 cancelRT(); // cancel delayed repaint
338
339 if (!renderFrozen) {
340 assert(videoSystem);
341 if (OutputSurface* surface = videoSystem->getOutputSurface()) {
342 repaintImpl(*surface);
343 videoSystem->flush();
344 }
345 }
346
347 // update fps statistics
348 auto now = Timer::getTime();
349 auto duration = now - prevTimeStamp;
350 prevTimeStamp = now;
351 frameDurationSum += duration - frameDurations.removeBack();
352 frameDurations.addFront(duration);
353}
354
356{
357 for (auto it = baseLayer(); it != end(layers); ++it) {
358 if ((*it)->getCoverage() != Layer::COVER_NONE) {
359 (*it)->paint(surface);
360 }
361 }
362}
363
365{
366 // Request a repaint from the VideoSystem. This may call repaintImpl()
367 // directly or for example defer to a signal callback on VisibleSurface.
368 videoSystem->repaint();
369}
370
371void Display::repaintDelayed(uint64_t delta)
372{
373 if (isPendingRT()) {
374 // already a pending repaint
375 return;
376 }
377 scheduleRT(unsigned(delta));
378}
379
381{
382 int z = layer.getZ();
383 auto it = ranges::find_if(layers, [&](Layer* l) { return l->getZ() > z; });
384 layers.insert(it, &layer);
385 layer.setDisplay(*this);
386}
387
389{
390 layers.erase(rfind_unguarded(layers, &layer));
391}
392
393void Display::updateZ(Layer& layer) noexcept
394{
395 // Remove at old Z-index...
396 removeLayer(layer);
397 // ...and re-insert at new Z-index.
398 addLayer(layer);
399}
400
401
402// ScreenShotCmd
403
404Display::ScreenShotCmd::ScreenShotCmd(CommandController& commandController_)
405 : Command(commandController_, "screenshot")
406{
407}
408
409void Display::ScreenShotCmd::execute(std::span<const TclObject> tokens, TclObject& result)
410{
411 std::string_view prefix = "openmsx";
412 bool rawShot = false;
413 bool msxOnly = false;
414 bool doubleSize = false;
415 bool withOsd = false;
416 std::array info = {
417 valueArg("-prefix", prefix),
418 flagArg("-raw", rawShot),
419 flagArg("-msxonly", msxOnly),
420 flagArg("-doublesize", doubleSize),
421 flagArg("-with-osd", withOsd)
422 };
423 auto arguments = parseTclArgs(getInterpreter(), tokens.subspan(1), info);
424
425 auto& display = OUTER(Display, screenShotCmd);
426 if (msxOnly) {
427 display.getCliComm().printWarning(
428 "The -msxonly option has been deprecated and will "
429 "be removed in a future release. Instead, use the "
430 "-raw option for the same effect.");
431 rawShot = true;
432 }
433 if (doubleSize && !rawShot) {
434 throw CommandException("-doublesize option can only be used in "
435 "combination with -raw");
436 }
437 if (rawShot && withOsd) {
438 throw CommandException("-with-osd cannot be used in "
439 "combination with -raw");
440 }
441
442 std::string_view fname;
443 switch (arguments.size()) {
444 case 0:
445 // nothing
446 break;
447 case 1:
448 fname = arguments[0].getString();
449 break;
450 default:
451 throw SyntaxError();
452 }
454 fname, "screenshots", prefix, ".png");
455
456 if (!rawShot) {
457 // include all layers (OSD stuff, console)
458 try {
459 display.getVideoSystem().takeScreenShot(filename, withOsd);
460 } catch (MSXException& e) {
461 throw CommandException(
462 "Failed to take screenshot: ", e.getMessage());
463 }
464 } else {
465 auto* videoLayer = dynamic_cast<VideoLayer*>(
466 display.findActiveLayer());
467 if (!videoLayer) {
468 throw CommandException(
469 "Current renderer doesn't support taking screenshots.");
470 }
471 unsigned height = doubleSize ? 480 : 240;
472 try {
473 videoLayer->takeRawScreenShot(height, filename);
474 } catch (MSXException& e) {
475 throw CommandException(
476 "Failed to take screenshot: ", e.getMessage());
477 }
478 }
479
480 display.getCliComm().printInfo("Screen saved to ", filename);
481 result = filename;
482}
483
484string Display::ScreenShotCmd::help(std::span<const TclObject> /*tokens*/) const
485{
486 // Note: -no-sprites option is implemented in Tcl
487 return "screenshot Write screenshot to file \"openmsxNNNN.png\"\n"
488 "screenshot <filename> Write screenshot to indicated file\n"
489 "screenshot -prefix foo Write screenshot to file \"fooNNNN.png\"\n"
490 "screenshot -raw 320x240 raw screenshot (of MSX screen only)\n"
491 "screenshot -raw -doublesize 640x480 raw screenshot (of MSX screen only)\n"
492 "screenshot -with-osd Include OSD elements in the screenshot\n"
493 "screenshot -no-sprites Don't include sprites in the screenshot\n";
494}
495
496void Display::ScreenShotCmd::tabCompletion(std::vector<string>& tokens) const
497{
498 using namespace std::literals;
499 static constexpr std::array extra = {
500 "-prefix"sv, "-raw"sv, "-doublesize"sv, "-with-osd"sv, "-no-sprites"sv,
501 };
502 completeFileName(tokens, userFileContext(), extra);
503}
504
505
506// FpsInfoTopic
507
508Display::FpsInfoTopic::FpsInfoTopic(InfoCommand& openMSXInfoCommand)
509 : InfoTopic(openMSXInfoCommand, "fps")
510{
511}
512
513void Display::FpsInfoTopic::execute(std::span<const TclObject> /*tokens*/,
514 TclObject& result) const
515{
516 auto& display = OUTER(Display, fpsInfo);
517 result = 1000000.0f * Display::NUM_FRAME_DURATIONS / narrow_cast<float>(display.frameDurationSum);
518}
519
520string Display::FpsInfoTopic::help(std::span<const TclObject> /*tokens*/) const
521{
522 return "Returns the current rendering speed in frames per second.";
523}
524
525} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:28
#define PLATFORM_ANDROID
Definition: build-info.hh:17
constexpr T & removeBack()
constexpr void addFront(const T &element)
void printWarning(std::string_view message)
Definition: CliComm.cc:10
std::string getWindowTitle()
Definition: Display.cc:220
void repaint()
Redraw the display.
Definition: Display.cc:364
void repaintImpl()
Definition: Display.cc:324
void detach(VideoSystemChangeListener &listener)
Definition: Display.cc:137
CliComm & getCliComm() const
Definition: Display.cc:126
void removeLayer(Layer &layer)
Definition: Display.cc:388
VideoSystem & getVideoSystem()
Definition: Display.cc:107
Display(Reactor &reactor)
Definition: Display.cc:37
void attach(VideoSystemChangeListener &listener)
Definition: Display.cc:131
void createVideoSystem()
Definition: Display.cc:97
void repaintDelayed(uint64_t delta)
Definition: Display.cc:371
Layer * findActiveLayer() const
Definition: Display.cc:142
OutputSurface * getOutputSurface()
Definition: Display.cc:113
void addLayer(Layer &layer)
Definition: Display.cc:380
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void distributeEvent(Event &&event)
Schedule the given event for delivery.
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
int getInt() const noexcept
Interface for display layers.
Definition: Layer.hh:12
@ COVER_FULL
Layer fully covers the screen: any underlying layers are invisible.
Definition: Layer.hh:32
@ COVER_NONE
Layer is not visible, that is completely transparent.
Definition: Layer.hh:39
void setDisplay(LayerListener &display_)
Store pointer to Display.
Definition: Layer.hh:60
ZIndex getZ() const
Query the Z-index of this layer.
Definition: Layer.hh:50
bool isActive() const
Definition: Layer.hh:51
A frame buffer where pixels can be written to.
void scheduleRT(uint64_t delta)
bool isPendingRT() const
Contains the main loop of openMSX.
Definition: Reactor.hh:68
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:375
CliComm & getCliComm()
Definition: Reactor.cc:313
EventDistributor & getEventDistributor()
Definition: Reactor.hh:82
RendererSetting & getRendererSetting()
The current renderer.
BooleanSetting & getFullScreenSetting()
Full screen [on, off].
IntegerSetting & getScaleFactorSetting()
The current scaling factor.
RendererID getRenderer() const
void detach(Observer< T > &observer)
Definition: Subject.hh:56
void attach(Observer< T > &observer)
Definition: Subject.hh:50
static std::string full()
Definition: Version.cc:8
static const bool RELEASE
Definition: Version.hh:12
Video back-end system.
Definition: VideoSystem.hh:23
constexpr double e
Definition: Math.hh:20
string parseCommandFileArgument(string_view argument, string_view directory, string_view prefix, string_view extension)
Helper function for parsing filename arguments in Tcl commands.
std::unique_ptr< VideoSystem > createVideoSystem(Reactor &reactor)
Create the video system required by the current renderer setting.
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
This file implemented 3 utility functions:
Definition: Autofire.cc:9
auto visit(Visitor &&visitor, const Event &event)
Definition: Event.hh:655
ArgsInfo valueArg(std::string_view name, T &value)
Definition: TclArgParser.hh:85
std::vector< TclObject > parseTclArgs(Interpreter &interp, std::span< const TclObject > inArgs, std::span< const ArgsInfo > table)
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:171
ArgsInfo flagArg(std::string_view name, bool &flag)
Definition: TclArgParser.hh:72
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:173
#define ad_printf(...)
Definition: openmsx.hh:11
#define OUTER(type, member)
Definition: outer.hh:41
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:125
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition: stl.hh:100
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:23
std::string strCat(Ts &&...ts)
Definition: strCat.hh:542
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:620
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition: xrange.hh:148
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)