47 unsigned maxWidth_,
unsigned height_,
bool canDoInterlace_)
51 , renderSettings(display_.getRenderSettings())
52 , eventDistributor(motherBoard_.getReactor().getEventDistributor())
56 , canDoInterlace(canDoInterlace_)
57 , lastRotate(motherBoard_.getCurrentTime())
60 deinterlacedFrame = std::make_unique<DeinterlacedFrame>();
61 interlacedFrame = std::make_unique<DoubledFrame>();
62 deflicker = std::make_unique<Deflicker>(lastFrames);
63 superImposedFrame = std::make_unique<SuperImposedFrame>();
72 preCalcNoise(renderSettings.
getNoise());
77 monitor3DProg.
attach(vertexShader);
78 monitor3DProg.
attach(fragmentShader);
98 "Video recording stopped, because you "
99 "changed machine or changed a video setting "
100 "during recording.");
105void PostProcessor::initBuffers()
108 static constexpr std::array pos_tex = {
112 glBindBuffer(GL_ARRAY_BUFFER, vbo.
get());
113 glBufferData(GL_ARRAY_BUFFER,
sizeof(pos_tex), pos_tex.data(), GL_STATIC_DRAW);
114 glBindBuffer(GL_ARRAY_BUFFER, 0);
122unsigned PostProcessor::getLineWidth(
128void PostProcessor::executeUntil(EmuTime::param )
135using WorkBuffer = std::vector<MemBuffer<FrameSource::Pixel, SSE_ALIGNMENT>>;
136static void getScaledFrame(
const FrameSource& paintFrame,
137 std::span<const FrameSource::Pixel*> lines,
140 auto height = narrow<unsigned>(lines.size());
141 unsigned width = (height == 240) ? 320 : 640;
144 for (
auto i :
xrange(height)) {
145 if (linePtr == work) {
148 work = workBuffer.emplace_back(width).data();
151 auto line = paintFrame.
getLinePtr320_240(i, std::span<uint32_t, 320>{work, 320});
152 linePtr = line.data();
154 assert (height == 480);
155 auto line = paintFrame.
getLinePtr640_480(i, std::span<uint32_t, 640>{work, 640});
156 linePtr = line.data();
170 getScaledFrame(*paintFrame, lines, workBuffer);
171 unsigned width = (height2 == 240) ? 320 : 640;
175void PostProcessor::createRegions()
179 const unsigned srcHeight = paintFrame->
getHeight();
182 unsigned g = std::gcd(srcHeight, dstHeight);
183 unsigned srcStep = srcHeight /
g;
184 unsigned dstStep = dstHeight /
g;
188 unsigned srcStartY = 0;
189 unsigned dstStartY = 0;
190 while (dstStartY < dstHeight) {
193 assert(srcStartY < srcHeight);
196 unsigned lineWidth = getLineWidth(paintFrame, srcStartY, srcStep);
197 unsigned srcEndY = srcStartY + srcStep;
198 unsigned dstEndY = dstStartY + dstStep;
199 while ((srcEndY < srcHeight) && (dstEndY < dstHeight) &&
200 (getLineWidth(paintFrame, srcEndY, srcStep) == lineWidth)) {
205 regions.emplace_back(srcStartY, srcEndY,
218 interleaveCount ^= 1;
219 if (interleaveCount) {
220 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
221 glClear(GL_COLOR_BUFFER_BIT);
228 int glow = renderSettings.
getGlow();
232 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
233 glClear(GL_COLOR_BUFFER_BIT);
241 scaleAlgorithm != algo) {
242 scaleAlgorithm = algo;
258 glViewport(0, 0, size.x, size.y);
259 glBindTexture(GL_TEXTURE_2D, 0);
260 auto& renderedFrame = renderedFrames[frameCounter & 1];
261 if (renderedFrame.size != size) {
262 renderedFrame.tex.bind();
263 renderedFrame.tex.setInterpolation(
true);
264 glTexImage2D(GL_TEXTURE_2D,
275 renderedFrame.fbo.push();
277 for (
const auto& r : regions) {
278 auto it =
find_unguarded(textures, r.lineWidth, &TextureData::width);
279 auto* superImpose = superImposeVideoFrame
280 ? &superImposeTex :
nullptr;
281 currScaler->scaleImage(
282 it->tex, superImpose,
283 r.srcStartY, r.srcEndY, r.lineWidth,
284 r.dstStartY, r.dstEndY, size.x,
291 renderedFrame.fbo.pop();
292 renderedFrame.tex.bind();
295 glViewport(x, y, w, h);
300 float x1 = (320.0f - float(horStretch)) * (1.0f / (2.0f * 320.0f));
301 float x2 = 1.0f - x1;
307 glContext.progTex.activate();
308 glUniform4f(glContext.unifTexColor,
309 1.0f, 1.0f, 1.0f, 1.0f);
311 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.
data());
313 glBindBuffer(GL_ARRAY_BUFFER, vbo.
get());
314 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0,
nullptr);
315 glEnableVertexAttribArray(0);
317 glBindBuffer(GL_ARRAY_BUFFER, stretchVBO.
get());
318 glBufferData(GL_ARRAY_BUFFER,
sizeof(tex), tex.data(), GL_STREAM_DRAW);
319 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0,
nullptr);
320 glEnableVertexAttribArray(1);
322 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
324 glDisableVertexAttribArray(1);
325 glDisableVertexAttribArray(0);
326 glBindBuffer(GL_ARRAY_BUFFER, 0);
333 std::unique_ptr<RawFrame> finishedFrame, EmuTime::param time)
336 auto delta = time - lastRotate;
337 auto middle = time + delta / 2;
345 bool doDeinterlace =
false;
346 bool doInterlace =
false;
347 bool doDeflicker =
false;
348 auto currType = finishedFrame->getField();
349 if (canDoInterlace) {
352 doDeinterlace =
true;
365 int recycleIdx = (lastFramesCount < numRequired)
368 assert(recycleIdx < 4);
369 auto recycleFrame = std::move(lastFrames[recycleIdx]);
372 std::move_backward(&lastFrames[0], &lastFrames[recycleIdx],
373 &lastFrames[recycleIdx + 1]);
374 lastFrames[0] = std::move(finishedFrame);
377 if (lastFramesCount >= numRequired) {
379 lastFramesCount = numRequired;
391 doDeinterlace =
false;
399 deinterlacedFrame->init(lastFrames[1].get(), lastFrames[0].get());
401 deinterlacedFrame->init(lastFrames[0].get(), lastFrames[1].get());
403 paintFrame = deinterlacedFrame.get();
404 }
else if (doInterlace) {
405 interlacedFrame->init(
408 paintFrame = interlacedFrame.get();
409 }
else if (doDeflicker) {
411 paintFrame = deflicker.get();
413 paintFrame = lastFrames[0].get();
415 if (superImposeVdpFrame) {
416 superImposedFrame->init(paintFrame, superImposeVdpFrame);
417 paintFrame = superImposedFrame.get();
423 recorder->
addImage(paintFrame, time);
426 "Recording stopped with error: ",
434 std::unique_ptr<RawFrame> reuseFrame = [&] {
435 if (canDoInterlace) {
436 if (!recycleFrame) [[unlikely]] {
437 recycleFrame = std::make_unique<RawFrame>(maxWidth, height);
439 return std::move(recycleFrame);
441 return std::move(lastFrames[0]);
455 const auto& noiseSetting = renderSettings.getNoiseSetting();
456 const auto& horizontalStretch = renderSettings.getHorizontalStretchSetting();
457 if (&
setting == &noiseSetting) {
458 preCalcNoise(noiseSetting.getFloat());
459 }
else if (&
setting == &horizontalStretch) {
460 preCalcMonitor3D(horizontalStretch.getFloat());
464void PostProcessor::uploadFrame()
468 const unsigned srcHeight = paintFrame->
getHeight();
469 for (
const auto& r : regions) {
474 uploadBlock(narrow<unsigned>(std::max(0, narrow<int>(r.srcStartY) - before)),
475 std::min(srcHeight, r.srcEndY + after),
479 if (superImposeVideoFrame) {
480 int w = narrow<GLsizei>(superImposeVideoFrame->
getWidth());
481 int h = narrow<GLsizei>(superImposeVideoFrame->
getHeight());
482 if (superImposeTex.
getWidth() != w ||
484 superImposeTex.
resize(w, h);
487 superImposeTex.
bind();
497 const_cast<RawFrame*
>(superImposeVideoFrame)->getLineDirect(0).data());
501void PostProcessor::uploadBlock(
502 unsigned srcStartY,
unsigned srcEndY,
unsigned lineWidth)
505 auto it =
ranges::find(textures, lineWidth, &TextureData::width);
506 if (it ==
end(textures)) {
507 TextureData textureData;
508 textureData.tex.resize(narrow<GLsizei>(lineWidth),
509 narrow<GLsizei>(height * 2));
510 textures.push_back(std::move(textureData));
511 it =
end(textures) - 1;
521 auto numLines = srcEndY - srcStartY;
522 for (
auto yy :
xrange(numLines)) {
523 auto dest = mapped.subspan(yy *
size_t(lineWidth), lineWidth);
524 auto line = paintFrame->
getLine(narrow<int>(yy + srcStartY), dest);
525 if (line.data() != dest.data()) {
530#if defined(__APPLE__)
534 if (lineWidth == 1 && srcStartY != 0 && srcStartY % 16 == 0) {
542 narrow<GLint>(srcStartY),
543 narrow<GLint>(lineWidth),
544 narrow<GLint>(numLines),
552 currScaler->uploadBlock(srcStartY, srcEndY, lineWidth, *paintFrame);
556void PostProcessor::drawGlow(
int glow)
558 if ((glow == 0) || !storedFrame)
return;
561 glContext.progTex.activate();
563 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
564 renderedFrames[(frameCounter & 1) ^ 1].tex.bind();
565 glUniform4f(glContext.unifTexColor,
566 1.0f, 1.0f, 1.0f, narrow<float>(glow) * (31.0f / 3200.0f));
568 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.
data());
570 glBindBuffer(GL_ARRAY_BUFFER, vbo.
get());
572 const vec2* offset =
nullptr;
573 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, offset);
575 glEnableVertexAttribArray(0);
577 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, offset);
578 glEnableVertexAttribArray(1);
580 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
582 glDisableVertexAttribArray(1);
583 glDisableVertexAttribArray(0);
584 glBindBuffer(GL_ARRAY_BUFFER, 0);
588void PostProcessor::preCalcNoise(
float factor)
590 std::array<uint8_t, 256 * 256> buf1;
591 std::array<uint8_t, 256 * 256> buf2;
593 std::normal_distribution<float> distribution(0.0f, 1.0f);
594 for (
auto i :
xrange(256 * 256)) {
595 float r = distribution(generator);
596 int s = std::clamp(
int(roundf(r * factor)), -255, 255);
597 buf1[i] = narrow<uint8_t>((s > 0) ? s : 0);
598 buf2[i] = narrow<uint8_t>((s < 0) ? -s : 0);
603 noiseTextureA.
bind();
614#if OPENGL_VERSION >= OPENGL_3_3
615 GLint swizzleMask1[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
616 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask1);
619 noiseTextureB.
bind();
630#if OPENGL_VERSION >= OPENGL_3_3
631 GLint swizzleMask2[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
632 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask2);
636void PostProcessor::drawNoise()
const
638 if (renderSettings.
getNoise() == 0.0f)
return;
642 static constexpr std::array pos = {
652 vec2 noise(noiseX, noiseY);
653 const std::array tex = {
654 noise +
vec2(0.0f, 1.875f),
655 noise +
vec2(2.0f, 1.875f),
656 noise +
vec2(2.0f, 0.0f ),
657 noise +
vec2(0.0f, 0.0f ),
661 glContext.progTex.activate();
664 glBlendFunc(GL_ONE, GL_ONE);
665 glUniform4f(glContext.unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f);
667 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.
data());
669 unsigned seq = frameCounter & 7;
670 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos[seq].data());
671 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex.data());
672 glEnableVertexAttribArray(0);
673 glEnableVertexAttribArray(1);
675 noiseTextureA.
bind();
676 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
677 glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
678 noiseTextureB.
bind();
679 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
681 glDisableVertexAttribArray(1);
682 glDisableVertexAttribArray(0);
683 glBlendEquation(GL_FUNC_ADD);
687static constexpr int GRID_SIZE = 16;
688static constexpr int GRID_SIZE1 = GRID_SIZE + 1;
689static constexpr int NUM_INDICES = (GRID_SIZE1 * 2 + 2) * GRID_SIZE - 2;
696void PostProcessor::preCalcMonitor3D(
float width)
699 std::array<std::array<Vertex, GRID_SIZE1>, GRID_SIZE1> vertices;
701 constexpr float GRID_SIZE2 = float(GRID_SIZE) * 0.5f;
702 float s = width * (1.0f / 320.0f);
703 float b = (320.0f - width) * (1.0f / (2.0f * 320.0f));
705 for (
auto sx :
xrange(GRID_SIZE1)) {
706 for (
auto sy :
xrange(GRID_SIZE1)) {
707 Vertex& v = vertices[sx][sy];
708 float x = (narrow<float>(sx) - GRID_SIZE2) / GRID_SIZE2;
709 float y = (narrow<float>(sy) - GRID_SIZE2) / GRID_SIZE2;
711 v.
position =
vec3(x, y, (x * x + y * y) * (1.0f / -12.0f));
713 v.
tex =
vec2((
float(sx) / GRID_SIZE) * s + b,
714 float(sy) / GRID_SIZE);
719 std::array<uint16_t, NUM_INDICES> indices;
721 uint16_t* ind = indices.data();
722 for (
auto y :
xrange(GRID_SIZE)) {
723 for (
auto x :
xrange(GRID_SIZE1)) {
724 *ind++ = narrow<uint16_t>((y + 0) * GRID_SIZE1 + x);
725 *ind++ = narrow<uint16_t>((y + 1) * GRID_SIZE1 + x);
730 assert((ind - indices.data()) == NUM_INDICES + 2);
731 ind = indices.data();
732 repeat(GRID_SIZE - 1, [&] {
733 ind += 2 * GRID_SIZE1;
741 glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.
get());
742 glBufferData(GL_ARRAY_BUFFER,
sizeof(vertices), vertices.data(),
744 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.
get());
745 glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(indices), indices.data(),
747 glBindBuffer(GL_ARRAY_BUFFER, 0);
748 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
757 mat4 mvp = proj * tran * rotX * scal;
763 1, GL_FALSE, mvp.
data());
765 1, GL_FALSE, normal.data());
768void PostProcessor::drawMonitor3D()
const
772 char* base =
nullptr;
773 glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.
get());
774 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.
get());
775 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
777 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
778 base +
sizeof(
vec3));
779 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
780 base +
sizeof(
vec3) +
sizeof(
vec3));
781 glEnableVertexAttribArray(0);
782 glEnableVertexAttribArray(1);
783 glEnableVertexAttribArray(2);
785 glDrawElements(GL_TRIANGLE_STRIP, NUM_INDICES, GL_UNSIGNED_SHORT,
nullptr);
786 glDisableVertexAttribArray(2);
787 glDisableVertexAttribArray(1);
788 glDisableVertexAttribArray(0);
790 glBindBuffer(GL_ARRAY_BUFFER, 0);
791 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
GLsizei getHeight() const
void resize(GLsizei width, GLsizei height)
Wrapper around an OpenGL fragment shader: a program executed on the GPU that computes the colors of p...
void allocate(GLuint size)
Allocate the maximum required size for this buffer.
void unmap() const
Unmaps the contents of this buffer.
void unbind() const
Unbind this buffer.
void bind() const
Bind this PixelBuffer.
std::span< T > mapWrite()
Maps the contents of this buffer into memory.
void activate() const
Makes this program the active shader program.
void attach(const Shader &shader)
Adds a given shader to this program.
void link()
Links all attached shaders together into one program.
void bindAttribLocation(unsigned index, const char *name)
Bind the given name for a vertex shader attribute to the given location.
GLint getUniformLocation(const char *name) const
Gets a reference to a uniform variable declared in the shader source.
void bind() const
Makes this texture the active GL texture.
void setInterpolation(bool interpolation)
Enable/disable bilinear interpolation for this texture.
Wrapper around an OpenGL vertex shader: a program executed on the GPU that computes per-vertex stuff.
constexpr const T * data() const
void addImage(const FrameSource *frame, EmuTime::param time)
void printWarning(std::string_view message)
Represents the output window/screen of openMSX.
CliComm & getCliComm() const
void distributeEvent(Event &&event)
Schedule the given event for delivery.
Interface for getting lines from a video frame.
std::span< const Pixel > getLine(int line, std::span< Pixel > buf) const
Gets a pointer to the pixels of the given line number.
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.
unsigned getWidth() const
Get the width of (all) lines in this frame.
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.
unsigned getHeight() const
Gets the number of lines in this frame.
@ ODD
Interlacing is on and this is an odd frame.
@ NONINTERLACED
Interlacing is off for this frame.
A frame buffer where pixels can be written to.
gl::ivec2 getLogicalSize() const
gl::ivec2 getViewOffset() const
gl::ivec2 getViewSize() const
int getLogicalHeight() const
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.
void paint(OutputSurface &output) override
Paint this layer.
~PostProcessor() override
bool getDeflicker() const
bool getDeinterlace() const
bool getInterleaveBlackFrame() const
Is black frame interleaving enabled?
FloatSetting & getNoiseSetting()
The amount of noise to add to the frame.
float getHorizontalStretch() const
DisplayDeform getDisplayDeform() const
FloatSetting & getHorizontalStretchSetting()
Amount of horizontal stretch.
ScaleAlgorithm getScaleAlgorithm() const
Every class that wants to get scheduled at some point must inherit from this class.
void setSyncPoint(EmuTime::param timestamp)
void detach(Observer< T > &observer)
void attach(Observer< T > &observer)
int getVideoSource() const
Returns the ID for this VideoLayer.
int getVideoSourceSetting() const
void update(const Setting &setting) noexcept override
mat4 rotateX(float angle)
std::optional< Context > context
constexpr mat4 frustum(float left, float right, float bottom, float top, float nearVal, float farVal)
vecN< N, T > normalize(const vecN< N, T > &x)
constexpr mat4 translate(const vec3 &xyz)
constexpr mat4 scale(const vec3 &xyz)
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
std::unique_ptr< GLScaler > createScaler(RenderSettings &renderSettings, unsigned maxWidth, unsigned maxHeight)
Instantiates a Scaler.
void saveRGBA(size_t width, std::span< const uint32_t * > rowPointers, const std::string &filename)
This file implemented 3 utility functions:
std::vector< MemBuffer< FrameSource::Pixel, SSE_ALIGNMENT > > WorkBuffer
auto find(InputRange &&range, const T &value)
constexpr auto copy(InputRange &&range, OutputIter out)
auto & global_urng()
Return reference to a (shared) global random number generator.
float random_float(float from, float upto)
Return a random float in the range [from, upto) (note: half-open interval).
ITER find_unguarded(ITER first, ITER last, const VAL &val, Proj proj={})
Faster alternative to 'find' when it's guaranteed that the value will be found (if not the behavior i...
auto max_value(InputIterator first, InputIterator last, Proj proj={})
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
constexpr auto xrange(T e)
constexpr auto end(const zstring_view &x)