44 unsigned maxWidth_,
unsigned height_,
bool canDoInterlace_)
48 , renderSettings(display_.getRenderSettings())
49 , eventDistributor(motherBoard_.getReactor().getEventDistributor())
53 , canDoInterlace(canDoInterlace_)
54 , lastRotate(motherBoard_.getCurrentTime())
57 deinterlacedFrame = std::make_unique<DeinterlacedFrame>();
58 interlacedFrame = std::make_unique<DoubledFrame>();
59 deflicker = std::make_unique<Deflicker>(lastFrames);
60 superImposedFrame = std::make_unique<SuperImposedFrame>();
69 preCalcNoise(renderSettings.
getNoise());
74 monitor3DProg.
attach(vertexShader);
75 monitor3DProg.
attach(fragmentShader);
93 "Video recording stopped, because you "
94 "changed machine or changed a video setting "
100void PostProcessor::initBuffers()
103 static constexpr std::array pos_tex = {
107 glBindBuffer(GL_ARRAY_BUFFER, vbo.
get());
108 glBufferData(GL_ARRAY_BUFFER,
sizeof(pos_tex), pos_tex.data(), GL_STATIC_DRAW);
109 glBindBuffer(GL_ARRAY_BUFFER, 0);
117unsigned PostProcessor::getLineWidth(
123void PostProcessor::executeUntil(EmuTime::param )
130using WorkBuffer = std::vector<MemBuffer<char, SSE_ALIGNMENT>>;
132 std::span<const void*> lines,
135 auto height = narrow<unsigned>(lines.size());
136 unsigned width = (height == 240) ? 320 : 640;
137 unsigned pitch = width * 4;
138 const void* linePtr =
nullptr;
139 void* work =
nullptr;
140 for (
auto i :
xrange(height)) {
141 if (linePtr == work) {
144 work = workBuffer.emplace_back(pitch).data();
146 auto* work2 =
static_cast<uint32_t*
>(work);
148 auto line = paintFrame.
getLinePtr320_240(i, std::span<uint32_t, 320>{work2, 320});
149 linePtr = line.data();
151 assert (height == 480);
152 auto line = paintFrame.
getLinePtr640_480(i, std::span<uint32_t, 640>{work2, 640});
153 linePtr = line.data();
165 VLA(
const void*, lines, height2);
167 getScaledFrame(*paintFrame, lines, workBuffer);
168 unsigned width = (height2 == 240) ? 320 : 640;
172void PostProcessor::createRegions()
176 const unsigned srcHeight = paintFrame->
getHeight();
179 unsigned g = std::gcd(srcHeight, dstHeight);
180 unsigned srcStep = srcHeight /
g;
181 unsigned dstStep = dstHeight /
g;
185 unsigned srcStartY = 0;
186 unsigned dstStartY = 0;
187 while (dstStartY < dstHeight) {
190 assert(srcStartY < srcHeight);
193 unsigned lineWidth = getLineWidth(paintFrame, srcStartY, srcStep);
194 unsigned srcEndY = srcStartY + srcStep;
195 unsigned dstEndY = dstStartY + dstStep;
196 while ((srcEndY < srcHeight) && (dstEndY < dstHeight) &&
197 (getLineWidth(paintFrame, srcEndY, srcStep) == lineWidth)) {
202 regions.emplace_back(srcStartY, srcEndY,
215 interleaveCount ^= 1;
216 if (interleaveCount) {
217 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
218 glClear(GL_COLOR_BUFFER_BIT);
225 int glow = renderSettings.
getGlow();
229 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
230 glClear(GL_COLOR_BUFFER_BIT);
238 if (scaleAlgorithm != algo) {
239 scaleAlgorithm = algo;
256 glBindTexture(GL_TEXTURE_2D, 0);
257 auto& renderedFrame = renderedFrames[frameCounter & 1];
258 if (renderedFrame.size !=
size) {
259 renderedFrame.tex.bind();
260 renderedFrame.tex.setInterpolation(
true);
261 glTexImage2D(GL_TEXTURE_2D,
272 renderedFrame.fbo.push();
274 for (
auto& r : regions) {
277 auto it =
find_unguarded(textures, r.lineWidth, &TextureData::width);
278 auto* superImpose = superImposeVideoFrame
279 ? &superImposeTex :
nullptr;
280 currScaler->scaleImage(
281 it->tex, superImpose,
282 r.srcStartY, r.srcEndY, r.lineWidth,
283 r.dstStartY, r.dstEndY,
size[0],
290 renderedFrame.fbo.pop();
291 renderedFrame.tex.bind();
294 glViewport(x, y, w, h);
299 float x1 = (320.0f - float(horStretch)) * (1.0f / (2.0f * 320.0f));
300 float x2 = 1.0f - x1;
306 glContext.progTex.activate();
307 glUniform4f(glContext.unifTexColor,
308 1.0f, 1.0f, 1.0f, 1.0f);
310 glUniformMatrix4fv(glContext.unifTexMvp,
311 1, GL_FALSE, &I[0][0]);
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 auto& noiseSetting = renderSettings.getNoiseSetting();
456 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 (
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;
509 textureData.tex.resize(narrow<GLsizei>(lineWidth),
510 narrow<GLsizei>(height * 2));
511 textureData.pbo.setImage(lineWidth, height * 2);
512 textures.push_back(std::move(textureData));
513 it =
end(textures) - 1;
523 uint32_t* mapped = pbo.mapWrite();
524 for (
auto y :
xrange(srcStartY, srcEndY)) {
525 auto* dest = mapped + y * size_t(lineWidth);
526 auto line = paintFrame->
getLine(narrow<int>(y), std::span{dest, lineWidth});
527 if (line.data() != dest) {
532#if defined(__APPLE__)
536 if (lineWidth == 1 && srcStartY != 0 && srcStartY % 16 == 0) {
544 narrow<GLint>(srcStartY),
545 narrow<GLint>(lineWidth),
546 narrow<GLint>(srcEndY - srcStartY),
549 pbo.getOffset(0, srcStartY));
554 currScaler->uploadBlock(srcStartY, srcEndY, lineWidth, *paintFrame);
558void PostProcessor::drawGlow(
int glow)
560 if ((glow == 0) || !storedFrame)
return;
563 glContext.progTex.activate();
565 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
566 renderedFrames[(frameCounter & 1) ^ 1].tex.bind();
567 glUniform4f(glContext.unifTexColor,
568 1.0f, 1.0f, 1.0f, narrow<float>(glow) * (31.0f / 3200.0f));
570 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, &I[0][0]);
572 glBindBuffer(GL_ARRAY_BUFFER, vbo.
get());
574 const vec2* offset =
nullptr;
575 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, offset);
577 glEnableVertexAttribArray(0);
579 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, offset);
580 glEnableVertexAttribArray(1);
582 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
584 glDisableVertexAttribArray(1);
585 glDisableVertexAttribArray(0);
586 glBindBuffer(GL_ARRAY_BUFFER, 0);
590void PostProcessor::preCalcNoise(
float factor)
592 std::array<uint8_t, 256 * 256> buf1;
593 std::array<uint8_t, 256 * 256> buf2;
595 std::normal_distribution<float> distribution(0.0f, 1.0f);
596 for (
auto i :
xrange(256 * 256)) {
597 float r = distribution(generator);
598 int s =
std::clamp(
int(roundf(r * factor)), -255, 255);
599 buf1[i] = narrow<uint8_t>((s > 0) ? s : 0);
600 buf2[i] = narrow<uint8_t>((s < 0) ? -s : 0);
605 noiseTextureA.
bind();
616#if OPENGL_VERSION >= OPENGL_3_3
617 GLint swizzleMask1[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
618 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask1);
621 noiseTextureB.
bind();
632#if OPENGL_VERSION >= OPENGL_3_3
633 GLint swizzleMask2[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
634 glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask2);
638void PostProcessor::drawNoise()
640 if (renderSettings.
getNoise() == 0.0f)
return;
644 static constexpr std::array pos = {
654 vec2 noise(noiseX, noiseY);
655 const std::array tex = {
656 noise +
vec2(0.0f, 1.875f),
657 noise +
vec2(2.0f, 1.875f),
658 noise +
vec2(2.0f, 0.0f ),
659 noise +
vec2(0.0f, 0.0f ),
663 glContext.progTex.activate();
666 glBlendFunc(GL_ONE, GL_ONE);
667 glUniform4f(glContext.unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f);
669 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, &I[0][0]);
671 unsigned seq = frameCounter & 7;
672 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos[seq].data());
673 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex.data());
674 glEnableVertexAttribArray(0);
675 glEnableVertexAttribArray(1);
677 noiseTextureA.
bind();
678 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
679 glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
680 noiseTextureB.
bind();
681 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
683 glDisableVertexAttribArray(1);
684 glDisableVertexAttribArray(0);
685 glBlendEquation(GL_FUNC_ADD);
689static constexpr int GRID_SIZE = 16;
690static constexpr int GRID_SIZE1 = GRID_SIZE + 1;
691static constexpr int NUM_INDICES = (GRID_SIZE1 * 2 + 2) * GRID_SIZE - 2;
698void PostProcessor::preCalcMonitor3D(
float width)
701 std::array<std::array<Vertex, GRID_SIZE1>, GRID_SIZE1> vertices;
703 constexpr float GRID_SIZE2 = float(GRID_SIZE) * 0.5f;
704 float s = width * (1.0f / 320.0f);
705 float b = (320.0f - width) * (1.0f / (2.0f * 320.0f));
707 for (
auto sx :
xrange(GRID_SIZE1)) {
708 for (
auto sy :
xrange(GRID_SIZE1)) {
709 Vertex& v = vertices[sx][sy];
710 float x = (narrow<float>(sx) - GRID_SIZE2) / GRID_SIZE2;
711 float y = (narrow<float>(sy) - GRID_SIZE2) / GRID_SIZE2;
713 v.
position =
vec3(x, y, (x * x + y * y) * (1.0f / -12.0f));
715 v.
tex =
vec2((
float(sx) / GRID_SIZE) * s + b,
716 float(sy) / GRID_SIZE);
721 std::array<uint16_t, NUM_INDICES> indices;
723 uint16_t* ind = indices.data();
724 for (
auto y :
xrange(GRID_SIZE)) {
725 for (
auto x :
xrange(GRID_SIZE1)) {
726 *ind++ = narrow<uint16_t>((y + 0) * GRID_SIZE1 + x);
727 *ind++ = narrow<uint16_t>((y + 1) * GRID_SIZE1 + x);
732 assert((ind - indices.data()) == NUM_INDICES + 2);
733 ind = indices.data();
734 repeat(GRID_SIZE - 1, [&] {
735 ind += 2 * GRID_SIZE1;
743 glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.
get());
744 glBufferData(GL_ARRAY_BUFFER,
sizeof(vertices), vertices.data(),
746 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.
get());
747 glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(indices), indices.data(),
749 glBindBuffer(GL_ARRAY_BUFFER, 0);
750 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
759 mat4 mvp = proj * tran * rotX * scal;
765 1, GL_FALSE, &mvp[0][0]);
767 1, GL_FALSE, &normal[0][0]);
770void PostProcessor::drawMonitor3D()
774 char* base =
nullptr;
775 glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.
get());
776 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.
get());
777 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
779 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
780 base +
sizeof(
vec3));
781 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
782 base +
sizeof(
vec3) +
sizeof(
vec3));
783 glEnableVertexAttribArray(0);
784 glEnableVertexAttribArray(1);
785 glEnableVertexAttribArray(2);
787 glDrawElements(GL_TRIANGLE_STRIP, NUM_INDICES, GL_UNSIGNED_SHORT,
nullptr);
788 glDisableVertexAttribArray(2);
789 glDisableVertexAttribArray(1);
790 glDisableVertexAttribArray(0);
792 glBindBuffer(GL_ARRAY_BUFFER, 0);
793 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 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 setInterpolation(bool interpolation)
Enable/disable bilinear interpolation for this texture.
void bind()
Makes this texture the active GL texture.
Wrapper around an OpenGL vertex shader: a program executed on the GPU that computes per-vertex stuff.
void addImage(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.
@ FIELD_NONINTERLACED
Interlacing is off for this frame.
@ FIELD_ODD
Interlacing is on and this is an odd 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)
matMxN< 3, 3, float > mat3
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
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 vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
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)
Instantiates a Scaler.
void saveRGBA(size_t width, std::span< const void * > rowPointers, const std::string &filename)
This file implemented 3 utility functions:
std::vector< MemBuffer< char, SSE_ALIGNMENT > > WorkBuffer
auto copy(InputRange &&range, OutputIter out)
auto find(InputRange &&range, const T &value)
size_t size(std::string_view utf8)
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={})
#define VLA(TYPE, NAME, LENGTH)
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)