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 "narrow.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
29namespace 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 , maxWidth(maxWidth_)
39 , height(height_)
40 , display(display_)
41 , canDoInterlace(canDoInterlace_)
42 , lastRotate(motherBoard_.getCurrentTime())
43 , eventDistributor(motherBoard_.getReactor().getEventDistributor())
44{
45 if (canDoInterlace) {
46 deinterlacedFrame = std::make_unique<DeinterlacedFrame>(
48 interlacedFrame = std::make_unique<DoubledFrame>(
54 } else {
55 // Laserdisc always produces non-interlaced frames, so we don't
56 // need lastFrames[1..3], deinterlacedFrame and
57 // interlacedFrame. Also it produces a complete frame at a
58 // time, so we don't need lastFrames[0] (and have a separate
59 // work buffer, for partially rendered frames).
60 }
61}
62
64{
65 if (recorder) {
67 "Video recording stopped, because you "
68 "changed machine or changed a video setting "
69 "during recording.");
70 recorder->stop();
71 }
72}
73
75{
76 return display.getCliComm();
77}
78
80 FrameSource* frame, unsigned y, unsigned step)
81{
82 return max_value(xrange(step), [&](auto i) { return frame->getLineWidth(y + i); });
83}
84
85std::unique_ptr<RawFrame> PostProcessor::rotateFrames(
86 std::unique_ptr<RawFrame> finishedFrame, EmuTime::param time)
87{
89 auto delta = time - lastRotate; // time between last two calls
90 auto middle = time + delta / 2; // estimate for middle between now
91 // and next call
92 setSyncPoint(middle);
93 }
94 lastRotate = time;
95
96 // Figure out how many past frames we want to use.
97 int numRequired = 1;
98 bool doDeinterlace = false;
99 bool doInterlace = false;
100 bool doDeflicker = false;
101 auto currType = finishedFrame->getField();
102 if (canDoInterlace) {
103 if (currType != FrameSource::FIELD_NONINTERLACED) {
105 doDeinterlace = true;
106 numRequired = 2;
107 } else {
108 doInterlace = true;
109 }
110 } else if (renderSettings.getDeflicker()) {
111 doDeflicker = true;
112 numRequired = 4;
113 }
114 }
115
116 // Which frame can be returned (recycled) to caller. Prefer to return
117 // the youngest frame to improve cache locality.
118 int recycleIdx = (lastFramesCount < numRequired)
119 ? lastFramesCount++ // store one more
120 : (numRequired - 1); // youngest that's no longer needed
121 assert(recycleIdx < 4);
122 auto recycleFrame = std::move(lastFrames[recycleIdx]); // might be nullptr
123
124 // Insert new frame in front of lastFrames[], shift older frames
125 std::move_backward(&lastFrames[0], &lastFrames[recycleIdx],
126 &lastFrames[recycleIdx + 1]);
127 lastFrames[0] = std::move(finishedFrame);
128
129 // Are enough frames available?
130 if (lastFramesCount >= numRequired) {
131 // Only the last 'numRequired' are kept up to date.
132 lastFramesCount = numRequired;
133 } else {
134 // Not enough past frames, fall back to 'regular' rendering.
135 // This situation can only occur when:
136 // - The very first frame we render needs to be deinterlaced.
137 // In other case we have at least one valid frame from the
138 // past plus one new frame passed via the 'finishedFrame'
139 // parameter.
140 // - Or when (re)enabling the deflicker setting. Typically only
141 // 1 frame in lastFrames[] is kept up-to-date (and we're
142 // given 1 new frame), so it can take up-to 2 frame after
143 // enabling deflicker before it actually takes effect.
144 doDeinterlace = false;
145 doInterlace = false;
146 doDeflicker = false;
147 }
148
149 // Setup the to-be-painted frame
150 if (doDeinterlace) {
151 if (currType == FrameSource::FIELD_ODD) {
153 } else {
155 }
157 } else if (doInterlace) {
158 interlacedFrame->init(
159 lastFrames[0].get(),
160 (currType == FrameSource::FIELD_ODD) ? 1 : 0);
162 } else if (doDeflicker) {
163 deflicker->init();
164 paintFrame = deflicker.get();
165 } else {
166 paintFrame = lastFrames[0].get();
167 }
171 }
172
173 // Possibly record this frame
174 if (recorder && needRecord()) {
175 try {
177 } catch (MSXException& e) {
179 "Recording stopped with error: ",
180 e.getMessage());
181 recorder->stop();
182 assert(!recorder);
183 }
184 }
185
186 // Return recycled frame to the caller
187 if (canDoInterlace) {
188 if (!recycleFrame) [[unlikely]] {
189 recycleFrame = std::make_unique<RawFrame>(
191 }
192 return recycleFrame;
193 } else {
194 return std::move(lastFrames[0]);
195 }
196}
197
198void PostProcessor::executeUntil(EmuTime::param /*time*/)
199{
200 // insert fake end of frame event
201 eventDistributor.distributeEvent(
202 Event::create<FinishFrameEvent>(
204}
205
206using WorkBuffer = std::vector<MemBuffer<char, SSE_ALIGNMENT>>;
207static void getScaledFrame(FrameSource& paintFrame, unsigned bpp,
208 std::span<const void*> lines,
209 WorkBuffer& workBuffer)
210{
211 auto height = narrow<unsigned>(lines.size());
212 unsigned width = (height == 240) ? 320 : 640;
213 unsigned pitch = width * ((bpp == 32) ? 4 : 2);
214 const void* linePtr = nullptr;
215 void* work = nullptr;
216 for (auto i : xrange(height)) {
217 if (linePtr == work) {
218 // If work buffer was used in previous iteration,
219 // then allocate a new one.
220 work = workBuffer.emplace_back(pitch).data();
221 }
222#if HAVE_32BPP
223 if (bpp == 32) {
224 // 32bpp
225 auto* work2 = static_cast<uint32_t*>(work);
226 if (height == 240) {
227 auto line = paintFrame.getLinePtr320_240(i, std::span<uint32_t, 320>{work2, 320});
228 linePtr = line.data();
229 } else {
230 assert (height == 480);
231 auto line = paintFrame.getLinePtr640_480(i, std::span<uint32_t, 640>{work2, 640});
232 linePtr = line.data();
233 }
234 } else
235#endif
236 {
237#if HAVE_16BPP
238 // 15bpp or 16bpp
239 auto* work2 = static_cast<uint16_t*>(work);
240 if (height == 240) {
241 auto line = paintFrame.getLinePtr320_240(i, std::span<uint16_t, 320>{work2, 320});
242 linePtr = line.data();
243 } else {
244 assert (height == 480);
245 auto line = paintFrame.getLinePtr640_480(i, std::span<uint16_t, 640>{work2, 640});
246 linePtr = line.data();
247 }
248#endif
249 }
250 lines[i] = linePtr;
251 }
252}
253
254void PostProcessor::takeRawScreenShot(unsigned height2, const std::string& filename)
255{
256 if (!paintFrame) {
257 throw CommandException("TODO");
258 }
259
260 VLA(const void*, lines, height2);
261 WorkBuffer workBuffer;
262 getScaledFrame(*paintFrame, getBpp(), lines, workBuffer);
263 unsigned width = (height2 == 240) ? 320 : 640;
264 PNG::save(width, lines, paintFrame->getPixelFormat(), filename);
265}
266
267unsigned PostProcessor::getBpp() const
268{
269 return screen.getPixelFormat().getBpp();
270}
271
272} // namespace openmsx
void addImage(FrameSource *frame, EmuTime::param time)
Definition: AviRecorder.cc:176
void printWarning(std::string_view message)
Definition: CliComm.cc:10
static std::unique_ptr< Deflicker > create(const PixelFormat &format, std::span< std::unique_ptr< RawFrame >, 4 > lastFrames)
Definition: Deflicker.cc:33
Represents the output window/screen of openMSX.
Definition: Display.hh:33
CliComm & getCliComm() const
Definition: Display.cc:126
void distributeEvent(Event &&event)
Schedule the given event for delivery.
Interface for getting lines from a video frame.
Definition: FrameSource.hh:20
std::span< const Pixel, 640 > getLinePtr640_480(unsigned line, std::span< Pixel, 640 > buf) const
Get a pointer to a given line in this frame, the frame is scaled to 640x480 pixels.
Definition: FrameSource.cc:42
virtual unsigned getLineWidth(unsigned line) const =0
Gets the number of display pixels on the given line.
std::span< const Pixel, 320 > getLinePtr320_240(unsigned line, std::span< Pixel, 320 > buf) const
Get a pointer to a given line in this frame, the frame is scaled to 320x240 pixels.
Definition: FrameSource.cc:23
@ FIELD_NONINTERLACED
Interlacing is off for this frame.
Definition: FrameSource.hh:27
@ FIELD_ODD
Interlacing is on and this is an odd frame.
Definition: FrameSource.hh:33
const PixelFormat & getPixelFormat() const
Definition: FrameSource.hh:151
A frame buffer where pixels can be written to.
const PixelFormat & getPixelFormat() const
unsigned getBpp() const
Definition: PixelFormat.hh:24
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-post-processed) screenshot.
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::array< std::unique_ptr< RawFrame >, 4 > lastFrames
The last 4 fully rendered (unscaled) MSX frames.
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:39
bool needRecord() const
Definition: VideoLayer.cc:91
int getVideoSourceSetting() const
Definition: VideoLayer.cc:43
constexpr double e
Definition: Math.hh:21
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:727
auto max_value(InputIterator first, InputIterator last, Proj proj={})
Definition: stl.hh:215
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:12
constexpr auto xrange(T e)
Definition: xrange.hh:132