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