openMSX
PostProcessor.cc
Go to the documentation of this file.
1#include "PostProcessor.hh"
2#include "Display.hh"
3#include "OutputSurface.hh"
5#include "DoubledFrame.hh"
6#include "Deflicker.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 "vla.hh"
21#include "xrange.hh"
22#include "build-info.hh"
23#include <algorithm>
24#include <cassert>
25#include <cstdint>
26#include <memory>
27
28namespace 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 = std::make_unique<DeinterlacedFrame>(
53 interlacedFrame = std::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 (auto i : xrange(1u, step)) {
89 result = std::max(result, frame->getLineWidth(y + i));
90 }
91 return result;
92}
93
94std::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()) {
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) {
162 } else {
164 }
166 } else if (doInterlace) {
167 interlacedFrame->init(
168 lastFrames[0].get(),
169 (currType == FrameSource::FIELD_ODD) ? 1 : 0);
171 } else if (doDeflicker) {
172 deflicker->init();
173 paintFrame = deflicker.get();
174 } else {
175 paintFrame = lastFrames[0].get();
176 }
180 }
181
182 // Possibly record this frame
183 if (recorder && needRecord()) {
184 try {
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 (!recycleFrame) [[unlikely]] {
198 recycleFrame = std::make_unique<RawFrame>(
200 }
201 return recycleFrame;
202 } else {
203 return std::move(lastFrames[0]);
204 }
205}
206
207void PostProcessor::executeUntil(EmuTime::param /*time*/)
208{
209 // insert fake end of frame event
210 eventDistributor.distributeEvent(
211 Event::create<FinishFrameEvent>(
213}
214
215using WorkBuffer = std::vector<MemBuffer<char, SSE_ALIGNMENT>>;
216static void getScaledFrame(FrameSource& paintFrame, unsigned bpp,
217 unsigned height, const void** lines,
218 WorkBuffer& workBuffer)
219{
220 unsigned width = (height == 240) ? 320 : 640;
221 unsigned pitch = width * ((bpp == 32) ? 4 : 2);
222 const void* line = nullptr;
223 void* work = nullptr;
224 for (auto i : xrange(height)) {
225 if (line == work) {
226 // If work buffer was used in previous iteration,
227 // then allocate a new one.
228 work = workBuffer.emplace_back(pitch).data();
229 }
230#if HAVE_32BPP
231 if (bpp == 32) {
232 // 32bpp
233 auto* work2 = static_cast<uint32_t*>(work);
234 if (height == 240) {
235 line = paintFrame.getLinePtr320_240(i, work2);
236 } else {
237 assert (height == 480);
238 line = paintFrame.getLinePtr640_480(i, work2);
239 }
240 } else
241#endif
242 {
243#if HAVE_16BPP
244 // 15bpp or 16bpp
245 auto* work2 = static_cast<uint16_t*>(work);
246 if (height == 240) {
247 line = paintFrame.getLinePtr320_240(i, work2);
248 } else {
249 assert (height == 480);
250 line = paintFrame.getLinePtr640_480(i, work2);
251 }
252#endif
253 }
254 lines[i] = line;
255 }
256}
257
258void PostProcessor::takeRawScreenShot(unsigned height2, const std::string& filename)
259{
260 if (!paintFrame) {
261 throw CommandException("TODO");
262 }
263
264 VLA(const void*, lines, height2);
265 WorkBuffer workBuffer;
266 getScaledFrame(*paintFrame, getBpp(), height2, lines, workBuffer);
267 unsigned width = (height2 == 240) ? 320 : 640;
268 PNG::save(width, height2, lines, paintFrame->getPixelFormat(), filename);
269}
270
271unsigned PostProcessor::getBpp() const
272{
273 return screen.getPixelFormat().getBpp();
274}
275
276} // namespace openmsx
void addImage(FrameSource *frame, EmuTime::param time)
Definition: AviRecorder.cc:172
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:33
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:17
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:20
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:37
@ FIELD_NONINTERLACED
Interlacing is off for this frame.
Definition: FrameSource.hh:24
@ FIELD_ODD
Interlacing is on and this is an odd frame.
Definition: FrameSource.hh:30
const PixelFormat & getPixelFormat() const
Definition: FrameSource.hh:192
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
constexpr double e
Definition: Math.hh:18
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:283
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:724
constexpr const char *const filename
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
constexpr auto xrange(T e)
Definition: xrange.hh:133