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 "SuperImposedFrame.hh"
7 #include "PNG.hh"
8 #include "RenderSettings.hh"
9 #include "BooleanSetting.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 "MemoryOps.hh"
19 #include "vla.hh"
20 #include "memory.hh"
21 #include "build-info.hh"
22 #include <algorithm>
23 #include <cassert>
24 #include <cstdint>
25 
26 namespace openmsx {
27 
29  Display& display_, OutputSurface& screen_, const std::string& videoSource,
30  unsigned maxWidth, unsigned height, bool canDoInterlace_)
31  : VideoLayer(motherBoard, videoSource)
32  , Schedulable(motherBoard.getScheduler())
33  , renderSettings(display_.getRenderSettings())
34  , screen(screen_)
35  , paintFrame(nullptr)
36  , recorder(nullptr)
37  , superImposeVideoFrame(nullptr)
38  , superImposeVdpFrame(nullptr)
39  , interleaveCount(0)
40  , display(display_)
41  , canDoInterlace(canDoInterlace_)
42  , lastRotate(motherBoard.getCurrentTime())
43  , eventDistributor(motherBoard.getReactor().getEventDistributor())
44 {
45  if (canDoInterlace) {
46  currFrame = make_unique<RawFrame>(
47  screen.getSDLFormat(), maxWidth, height);
48  prevFrame = make_unique<RawFrame>(
49  screen.getSDLFormat(), maxWidth, height);
50  deinterlacedFrame = make_unique<DeinterlacedFrame>(
52  interlacedFrame = make_unique<DoubledFrame>(
56  } else {
57  // Laserdisc always produces non-interlaced frames, so we don't
58  // need prevFrame, deinterlacedFrame and interlacedFrame. Also
59  // it produces a complete frame at a time, so we don't need
60  // currFrame (and have a separate work buffer, for partially
61  // rendered frames).
62  }
63 }
64 
66 {
67  if (recorder) {
69  "Videorecording stopped, because you "
70  "changed machine or changed a video setting "
71  "during recording.");
72  recorder->stop();
73  }
74 }
75 
77 {
78  return display.getCliComm();
79 }
80 
82  FrameSource* frame, unsigned y, unsigned step)
83 {
84  unsigned result = frame->getLineWidth(y);
85  for (unsigned i = 1; i < step; ++i) {
86  result = std::max(result, frame->getLineWidth(y + i));
87  }
88  return result;
89 }
90 
91 std::unique_ptr<RawFrame> PostProcessor::rotateFrames(
92  std::unique_ptr<RawFrame> finishedFrame, FrameSource::FieldType field,
93  EmuTime::param time)
94 {
96  auto delta = time - lastRotate; // time between last two calls
97  auto middle = time + delta / 2; // estimate for middle between now
98  // and next call
99  setSyncPoint(middle);
100  }
101  lastRotate = time;
102 
103  std::unique_ptr<RawFrame> reuseFrame;
104  if (canDoInterlace) {
105  reuseFrame = std::move(prevFrame);
106  prevFrame = std::move(currFrame);
107  currFrame = std::move(finishedFrame);
108  reuseFrame->init(field);
109  } else {
110  currFrame = std::move(finishedFrame);
111  assert(field == FrameSource::FIELD_NONINTERLACED);
112  assert(currFrame->getField() == FrameSource::FIELD_NONINTERLACED);
113  }
114 
115  // TODO: When frames are being skipped or if (de)interlace was just
116  // turned on, it's not guaranteed that prevFrame is a
117  // different field from currFrame.
118  // Or in the case of frame skip, it might be the right field,
119  // but from several frames ago.
120  FrameSource::FieldType currType = currFrame->getField();
121  if (currType != FrameSource::FIELD_NONINTERLACED) {
123  // deinterlaced
124  if (currType == FrameSource::FIELD_ODD) {
125  deinterlacedFrame->init(prevFrame.get(), currFrame.get());
126  } else {
127  deinterlacedFrame->init(currFrame.get(), prevFrame.get());
128  }
130  } else {
131  // interlaced
132  interlacedFrame->init(currFrame.get(),
133  (currType == FrameSource::FIELD_ODD) ? 1 : 0);
134  paintFrame = interlacedFrame.get();
135  }
136  } else {
137  // non interlaced
138  paintFrame = currFrame.get();
139  }
140 
141  if (superImposeVdpFrame) {
144  }
145 
146  if (recorder && needRecord()) {
147  try {
148  recorder->addImage(paintFrame, time);
149  } catch (MSXException& e) {
151  "Recording stopped with error: " +
152  e.getMessage());
153  recorder->stop();
154  assert(!recorder);
155  }
156  }
157 
158  if (canDoInterlace) {
159  return reuseFrame;
160  } else {
161  return std::move(currFrame);
162  }
163 }
164 
165 void PostProcessor::executeUntil(EmuTime::param /*time*/, int /*userData*/)
166 {
167  // insert fake end of frame event
168  eventDistributor.distributeEvent(
169  std::make_shared<FinishFrameEvent>(
171 }
172 
174 {
175  superImposeVideoFrame = videoSource;
176 }
177 
179 {
180  superImposeVdpFrame = vdpSource;
181 }
182 
183 void PostProcessor::getScaledFrame(unsigned height, const void** lines,
184  std::vector<void*>& workBuffer)
185 {
186  unsigned width = (height == 240) ? 320 : 640;
187  unsigned pitch = width * ((getBpp() == 32) ? 4 : 2);
188  const void* line = nullptr;
189  void* work = nullptr;
190  for (unsigned i = 0; i < height; ++i) {
191  if (line == work) {
192  // If work buffer was used in previous iteration,
193  // then allocate a new one.
194  work = MemoryOps::mallocAligned(16, pitch);
195  workBuffer.push_back(work);
196  }
197 #if HAVE_32BPP
198  if (getBpp() == 32) {
199  // 32bpp
200  auto* work2 = static_cast<uint32_t*>(work);
201  if (height == 240) {
202  line = paintFrame->getLinePtr320_240(i, work2);
203  } else {
204  assert (height == 480);
205  line = paintFrame->getLinePtr640_480(i, work2);
206  }
207  } else
208 #endif
209  {
210 #if HAVE_16BPP
211  // 15bpp or 16bpp
212  auto* work2 = static_cast<uint16_t*>(work);
213  if (height == 240) {
214  line = paintFrame->getLinePtr320_240(i, work2);
215  } else {
216  assert (height == 480);
217  line = paintFrame->getLinePtr640_480(i, work2);
218  }
219 #endif
220  }
221  lines[i] = line;
222  }
223 }
224 
225 void PostProcessor::takeRawScreenShot(unsigned height, const std::string& filename)
226 {
227  if (!paintFrame) {
228  throw CommandException("TODO");
229  }
230 
231  VLA(const void*, lines, height);
232  std::vector<void*> workBuffer;
233  getScaledFrame(height, lines, workBuffer);
234 
235  unsigned width = (height == 240) ? 320 : 640;
236  PNG::save(width, height, lines, paintFrame->getSDLPixelFormat(), filename);
237 
238  for (void* p : workBuffer) {
240  }
241 }
242 
244 {
245  recorder = recorder_;
246 }
247 
249 {
250  return recorder != nullptr;
251 }
252 
253 unsigned PostProcessor::getBpp() const
254 {
255  return screen.getSDLFormat().BitsPerPixel;
256 }
257 
258 } // namespace openmsx
bool isRecording() const
Is recording active.
std::unique_ptr< DoubledFrame > interlacedFrame
Each line of currFrame 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:48
void setRecorder(AviRecorder *recorder)
Start/stop recording.
Represents the output window/screen of openMSX.
Definition: Display.hh:33
std::unique_ptr< RawFrame > currFrame
The last finished frame, ready to be displayed.
static unsigned getLineWidth(FrameSource *frame, unsigned y, unsigned step)
Returns the maximum width for lines [y..y+step).
std::unique_ptr< RawFrame > prevFrame
The frame before currFrame, ready to be displayed.
void setSuperimposeVideoFrame(const RawFrame *videoSource)
Set the Video frame on which to superimpose the 'normal' output of this PostProcessor.
Interlacing is on and this is an odd frame.
Definition: FrameSource.hh:29
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:31
void freeAligned(void *)
Definition: MemoryOps.cc:315
CliComm & getCliComm() const
Definition: Display.cc:160
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.
const SDL_PixelFormat & getSDLPixelFormat() const
Definition: FrameSource.hh:195
int getVideoSource() const
Returns the ID for this videolayer.
Definition: VideoLayer.cc:45
std::unique_ptr< DeinterlacedFrame > deinterlacedFrame
Combined currFrame and prevFrame.
Interface for getting lines from a video frame.
Definition: FrameSource.hh:15
void setSuperimposeVdpFrame(const FrameSource *vdpSource)
Set the VDP frame on which to superimpose the 'normal' output of this PostProcessor.
const FrameSource * superImposeVdpFrame
void save(SDL_Surface *surface, const std::string &filename)
Definition: PNG.cc:364
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
A video frame as output by the VDP scanline conversion unit, before any postprocessing filters are ap...
Definition: RawFrame.hh:13
unsigned getBpp() const
Get the number of bits per pixel for the pixels in these frames.
virtual std::unique_ptr< RawFrame > rotateFrames(std::unique_ptr< RawFrame > finishedFrame, FrameSource::FieldType field, EmuTime::param time)
Sets up the "abcdFrame" variables for a new frame.
BooleanSetting & getDeinterlace() const
Deinterlacing [on, off].
void * mallocAligned(size_t alignment, size_t size)
Definition: MemoryOps.cc:285
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 addImage(FrameSource *frame, EmuTime::param time)
Definition: AviRecorder.cc:175
OutputSurface & screen
The surface which is visible to the user.
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.
const RawFrame * superImposeVideoFrame
Video frame on which to superimpose the (VDP) output.
BooleanSetting & getInterleaveBlackFrame() const
Is black frame interleaving enabled?
bool needRecord() const
Definition: VideoLayer.cc:102
virtual void takeRawScreenShot(unsigned height, const std::string &filename)
Create a raw (=non-postprocessed) screenshot.
PostProcessor(MSXMotherBoard &motherBoard, Display &display, OutputSurface &screen, const std::string &videoSource, unsigned maxWidth, unsigned height, bool canDoInterlace)
FieldType
What role does this frame play in interlacing?
Definition: FrameSource.hh:20
#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)