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