openMSX
PostProcessor.cc
Go to the documentation of this file.
1 #include "PostProcessor.hh"
2 #include "Display.hh"
3 #include "OutputSurface.hh"
4 #include "DeinterlacedFrame.hh"
5 #include "DoubledFrame.hh"
6 #include "Deflicker.hh"
7 #include "SuperImposedFrame.hh"
8 #include "PNG.hh"
9 #include "RenderSettings.hh"
10 #include "RawFrame.hh"
11 #include "AviRecorder.hh"
12 #include "CliComm.hh"
13 #include "MSXMotherBoard.hh"
14 #include "Reactor.hh"
15 #include "EventDistributor.hh"
16 #include "Event.hh"
17 #include "CommandException.hh"
18 #include "MemBuffer.hh"
19 #include "aligned.hh"
20 #include "likely.hh"
21 #include "vla.hh"
22 #include "xrange.hh"
23 #include "build-info.hh"
24 #include <algorithm>
25 #include <cassert>
26 #include <cstdint>
27 #include <memory>
28 
29 namespace openmsx {
30 
32  Display& display_, OutputSurface& screen_, const std::string& videoSource,
33  unsigned maxWidth_, unsigned height_, bool canDoInterlace_)
34  : VideoLayer(motherBoard_, videoSource)
35  , Schedulable(motherBoard_.getScheduler())
36  , renderSettings(display_.getRenderSettings())
37  , screen(screen_)
38  , paintFrame(nullptr)
39  , recorder(nullptr)
40  , superImposeVideoFrame(nullptr)
41  , superImposeVdpFrame(nullptr)
42  , interleaveCount(0)
43  , lastFramesCount(0)
44  , maxWidth(maxWidth_)
45  , height(height_)
46  , display(display_)
47  , canDoInterlace(canDoInterlace_)
48  , lastRotate(motherBoard_.getCurrentTime())
49  , eventDistributor(motherBoard_.getReactor().getEventDistributor())
50 {
51  if (canDoInterlace) {
52  deinterlacedFrame = std::make_unique<DeinterlacedFrame>(
54  interlacedFrame = std::make_unique<DoubledFrame>(
60  } else {
61  // Laserdisc always produces non-interlaced frames, so we don't
62  // need lastFrames[1..3], deinterlacedFrame and
63  // interlacedFrame. Also it produces a complete frame at a
64  // time, so we don't need lastFrames[0] (and have a separate
65  // work buffer, for partially rendered frames).
66  }
67 }
68 
70 {
71  if (recorder) {
73  "Videorecording stopped, because you "
74  "changed machine or changed a video setting "
75  "during recording.");
76  recorder->stop();
77  }
78 }
79 
81 {
82  return display.getCliComm();
83 }
84 
86  FrameSource* frame, unsigned y, unsigned step)
87 {
88  unsigned result = frame->getLineWidth(y);
89  for (auto i : xrange(1u, step)) {
90  result = std::max(result, frame->getLineWidth(y + i));
91  }
92  return result;
93 }
94 
95 std::unique_ptr<RawFrame> PostProcessor::rotateFrames(
96  std::unique_ptr<RawFrame> finishedFrame, EmuTime::param time)
97 {
99  auto delta = time - lastRotate; // time between last two calls
100  auto middle = time + delta / 2; // estimate for middle between now
101  // and next call
102  setSyncPoint(middle);
103  }
104  lastRotate = time;
105 
106  // Figure out how many past frames we want to use.
107  int numRequired = 1;
108  bool doDeinterlace = false;
109  bool doInterlace = false;
110  bool doDeflicker = false;
111  auto currType = finishedFrame->getField();
112  if (canDoInterlace) {
113  if (currType != FrameSource::FIELD_NONINTERLACED) {
115  doDeinterlace = true;
116  numRequired = 2;
117  } else {
118  doInterlace = true;
119  }
120  } else if (renderSettings.getDeflicker()) {
121  doDeflicker = true;
122  numRequired = 4;
123  }
124  }
125 
126  // Which frame can be returned (recycled) to caller. Prefer to return
127  // the youngest frame to improve cache locality.
128  int recycleIdx = (lastFramesCount < numRequired)
129  ? lastFramesCount++ // store one more
130  : (numRequired - 1); // youngest that's no longer needed
131  assert(recycleIdx < 4);
132  auto recycleFrame = std::move(lastFrames[recycleIdx]); // might be nullptr
133 
134  // Insert new frame in front of lastFrames[], shift older frames
135  std::move_backward(lastFrames, lastFrames + recycleIdx,
136  lastFrames + recycleIdx + 1);
137  lastFrames[0] = std::move(finishedFrame);
138 
139  // Are enough frames available?
140  if (lastFramesCount >= numRequired) {
141  // Only the last 'numRequired' are kept up to date.
142  lastFramesCount = numRequired;
143  } else {
144  // Not enough past frames, fall back to 'regular' rendering.
145  // This situation can only occur when:
146  // - The very first frame we render needs to be deinterlaced.
147  // In other case we have at least one valid frame from the
148  // past plus one new frame passed via the 'finishedFrame'
149  // parameter.
150  // - Or when (re)enabling the deflicker setting. Typically only
151  // 1 frame in lastFrames[] is kept up-to-date (and we're
152  // given 1 new frame), so it can take up-to 2 frame after
153  // enabling deflicker before it actually takes effect.
154  doDeinterlace = false;
155  doInterlace = false;
156  doDeflicker = false;
157  }
158 
159  // Setup the to-be-painted frame
160  if (doDeinterlace) {
161  if (currType == FrameSource::FIELD_ODD) {
162  deinterlacedFrame->init(lastFrames[1].get(), lastFrames[0].get());
163  } else {
164  deinterlacedFrame->init(lastFrames[0].get(), lastFrames[1].get());
165  }
167  } else if (doInterlace) {
168  interlacedFrame->init(
169  lastFrames[0].get(),
170  (currType == FrameSource::FIELD_ODD) ? 1 : 0);
171  paintFrame = interlacedFrame.get();
172  } else if (doDeflicker) {
173  deflicker->init();
174  paintFrame = deflicker.get();
175  } else {
176  paintFrame = lastFrames[0].get();
177  }
178  if (superImposeVdpFrame) {
181  }
182 
183  // Possibly record this frame
184  if (recorder && needRecord()) {
185  try {
186  recorder->addImage(paintFrame, time);
187  } catch (MSXException& e) {
189  "Recording stopped with error: ",
190  e.getMessage());
191  recorder->stop();
192  assert(!recorder);
193  }
194  }
195 
196  // Return recycled frame to the caller
197  if (canDoInterlace) {
198  if (unlikely(!recycleFrame)) {
199  recycleFrame = std::make_unique<RawFrame>(
201  }
202  return recycleFrame;
203  } else {
204  return std::move(lastFrames[0]);
205  }
206 }
207 
208 void PostProcessor::executeUntil(EmuTime::param /*time*/)
209 {
210  // insert fake end of frame event
211  eventDistributor.distributeEvent(
212  Event::create<FinishFrameEvent>(
214 }
215 
216 using WorkBuffer = std::vector<MemBuffer<char, SSE_ALIGNMENT>>;
217 static void getScaledFrame(FrameSource& paintFrame, unsigned bpp,
218  unsigned height, const void** lines,
219  WorkBuffer& workBuffer)
220 {
221  unsigned width = (height == 240) ? 320 : 640;
222  unsigned pitch = width * ((bpp == 32) ? 4 : 2);
223  const void* line = nullptr;
224  void* work = nullptr;
225  for (auto i : xrange(height)) {
226  if (line == work) {
227  // If work buffer was used in previous iteration,
228  // then allocate a new one.
229  work = workBuffer.emplace_back(pitch).data();
230  }
231 #if HAVE_32BPP
232  if (bpp == 32) {
233  // 32bpp
234  auto* work2 = static_cast<uint32_t*>(work);
235  if (height == 240) {
236  line = paintFrame.getLinePtr320_240(i, work2);
237  } else {
238  assert (height == 480);
239  line = paintFrame.getLinePtr640_480(i, work2);
240  }
241  } else
242 #endif
243  {
244 #if HAVE_16BPP
245  // 15bpp or 16bpp
246  auto* work2 = static_cast<uint16_t*>(work);
247  if (height == 240) {
248  line = paintFrame.getLinePtr320_240(i, work2);
249  } else {
250  assert (height == 480);
251  line = paintFrame.getLinePtr640_480(i, work2);
252  }
253 #endif
254  }
255  lines[i] = line;
256  }
257 }
258 
259 void PostProcessor::takeRawScreenShot(unsigned height2, const std::string& filename)
260 {
261  if (!paintFrame) {
262  throw CommandException("TODO");
263  }
264 
265  VLA(const void*, lines, height2);
266  WorkBuffer workBuffer;
267  getScaledFrame(*paintFrame, getBpp(), height2, lines, workBuffer);
268  unsigned width = (height2 == 240) ? 320 : 640;
269  PNG::save(width, height2, lines, paintFrame->getPixelFormat(), filename);
270 }
271 
272 unsigned PostProcessor::getBpp() const
273 {
274  return screen.getPixelFormat().getBpp();
275 }
276 
277 } // namespace openmsx
void addImage(FrameSource *frame, EmuTime::param time)
Definition: AviRecorder.cc:171
void printWarning(std::string_view message)
Definition: CliComm.cc:10
static std::unique_ptr< Deflicker > create(const PixelFormat &format, std::unique_ptr< RawFrame > *lastFrames)
Definition: Deflicker.cc:32
Represents the output window/screen of openMSX.
Definition: Display.hh:33
CliComm & getCliComm() const
Definition: Display.cc:127
void distributeEvent(Event &&event)
Schedule the given event for delivery.
Interface for getting lines from a video frame.
Definition: FrameSource.hh:16
virtual unsigned getLineWidth(unsigned line) const =0
Gets the number of display pixels on the given line.
const Pixel * getLinePtr320_240(unsigned line, Pixel *buf) const
Get a pointer to a given line in this frame, the frame is scaled to 320x240 pixels.
Definition: FrameSource.cc:21
const PixelFormat & getPixelFormat() const
Definition: FrameSource.hh:191
const Pixel * getLinePtr640_480(unsigned line, Pixel *buf) const
Get a pointer to a given line in this frame, the frame is scaled to 640x480 pixels.
Definition: FrameSource.cc:38
@ FIELD_NONINTERLACED
Interlacing is off for this frame.
Definition: FrameSource.hh:23
@ FIELD_ODD
Interlacing is on and this is an odd frame.
Definition: FrameSource.hh:29
const std::string & getMessage() const &
Definition: MSXException.hh:23
A frame buffer where pixels can be written to.
const PixelFormat & getPixelFormat() const
unsigned getBpp() const
Definition: PixelFormat.hh:22
virtual std::unique_ptr< RawFrame > rotateFrames(std::unique_ptr< RawFrame > finishedFrame, EmuTime::param time)
Sets up the "abcdFrame" variables for a new frame.
PostProcessor(MSXMotherBoard &motherBoard, Display &display, OutputSurface &screen, const std::string &videoSource, unsigned maxWidth, unsigned height, bool canDoInterlace)
void takeRawScreenShot(unsigned height, const std::string &filename) override
Create a raw (=non-postprocessed) screenshot.
std::unique_ptr< RawFrame > lastFrames[4]
The last 4 fully rendered (unscaled) MSX frames.
FrameSource * paintFrame
Represents a frame as it should be displayed.
std::unique_ptr< SuperImposedFrame > superImposedFrame
Result of superimposing 2 frames.
unsigned getBpp() const
Get the number of bits per pixel for the pixels in these frames.
static unsigned getLineWidth(FrameSource *frame, unsigned y, unsigned step)
Returns the maximum width for lines [y..y+step).
std::unique_ptr< DeinterlacedFrame > deinterlacedFrame
Combined the last two frames in a deinterlaced frame.
const FrameSource * superImposeVdpFrame
std::unique_ptr< DoubledFrame > interlacedFrame
Each line of the last frame twice, to get double vertical resolution.
RenderSettings & renderSettings
Render settings.
std::unique_ptr< Deflicker > deflicker
Combine the last 4 frames into one 'flicker-free' frame.
AviRecorder * recorder
Video recorder, nullptr when not recording.
OutputSurface & screen
The surface which is visible to the user.
bool getDeflicker() const
Deflicker [on, off].
bool getDeinterlace() const
Deinterlacing [on, off].
bool getInterleaveBlackFrame() const
Is black frame interleaving enabled?
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:34
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
static std::unique_ptr< SuperImposedFrame > create(const PixelFormat &format)
int getVideoSource() const
Returns the ID for this videolayer.
Definition: VideoLayer.cc:41
bool needRecord() const
Definition: VideoLayer.cc:93
int getVideoSourceSetting() const
Definition: VideoLayer.cc:45
constexpr auto step
Definition: eeprom.cc:9
#define unlikely(x)
Definition: likely.hh:15
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
This file implemented 3 utility functions:
Definition: Autofire.cc:9
std::vector< MemBuffer< char, SSE_ALIGNMENT > > WorkBuffer
const T & get(const Event &event)
Definition: Event.hh:725
constexpr const char *const filename
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
constexpr auto xrange(T e)
Definition: xrange.hh:155