openMSX
PostProcessor.cc
Go to the documentation of this file.
1#include "PostProcessor.hh"
2
3#include "AviRecorder.hh"
4#include "CliComm.hh"
5#include "CommandException.hh"
6#include "Deflicker.hh"
8#include "Display.hh"
9#include "DoubledFrame.hh"
10#include "Event.hh"
11#include "EventDistributor.hh"
12#include "FloatSetting.hh"
13#include "GLContext.hh"
14#include "GLScaler.hh"
15#include "GLScalerFactory.hh"
16#include "MSXMotherBoard.hh"
17#include "OutputSurface.hh"
18#include "PNG.hh"
19#include "RawFrame.hh"
20#include "Reactor.hh"
21#include "RenderSettings.hh"
22#include "SuperImposedFrame.hh"
23#include "gl_transform.hh"
24
25#include "MemBuffer.hh"
26#include "aligned.hh"
27#include "inplace_buffer.hh"
28#include "narrow.hh"
29#include "random.hh"
30#include "ranges.hh"
31#include "stl.hh"
32#include "xrange.hh"
33
34#include <algorithm>
35#include <cassert>
36#include <cstdint>
37#include <memory>
38#include <numeric>
39
40using namespace gl;
41
42namespace openmsx {
43
45 MSXMotherBoard& motherBoard_, Display& display_,
46 OutputSurface& screen_, const std::string& videoSource,
47 unsigned maxWidth_, unsigned height_, bool canDoInterlace_)
48 : VideoLayer(motherBoard_, videoSource)
49 , Schedulable(motherBoard_.getScheduler())
50 , display(display_)
51 , renderSettings(display_.getRenderSettings())
52 , eventDistributor(motherBoard_.getReactor().getEventDistributor())
53 , screen(screen_)
54 , maxWidth(maxWidth_)
55 , height(height_)
56 , canDoInterlace(canDoInterlace_)
57 , lastRotate(motherBoard_.getCurrentTime())
58{
59 if (canDoInterlace) {
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>();
64 } else {
65 // Laserdisc always produces non-interlaced frames, so we don't
66 // need lastFrames[1..3], deinterlacedFrame and
67 // interlacedFrame. Also it produces a complete frame at a
68 // time, so we don't need lastFrames[0] (and have a separate
69 // work buffer, for partially rendered frames).
70 }
71
72 preCalcNoise(renderSettings.getNoise());
73 initBuffers();
74
75 VertexShader vertexShader ("monitor3D.vert");
76 FragmentShader fragmentShader("monitor3D.frag");
77 monitor3DProg.attach(vertexShader);
78 monitor3DProg.attach(fragmentShader);
79 monitor3DProg.bindAttribLocation(0, "a_position");
80 monitor3DProg.bindAttribLocation(1, "a_normal");
81 monitor3DProg.bindAttribLocation(2, "a_texCoord");
82 monitor3DProg.link();
83 preCalcMonitor3D(renderSettings.getHorizontalStretch());
84
85 pbo.allocate(maxWidth * height * 2); // *2 for interlace TODO only when 'canDoInterlace'
86
87 renderSettings.getNoiseSetting().attach(*this);
88 renderSettings.getHorizontalStretchSetting().attach(*this);
89}
90
92{
93 renderSettings.getHorizontalStretchSetting().detach(*this);
94 renderSettings.getNoiseSetting().detach(*this);
95
96 if (recorder) {
98 "Video recording stopped, because you "
99 "changed machine or changed a video setting "
100 "during recording.");
101 recorder->stop();
102 }
103}
104
105void PostProcessor::initBuffers()
106{
107 // combined positions and texture coordinates
108 static constexpr std::array pos_tex = {
109 vec2(-1, 1), vec2(-1,-1), vec2( 1,-1), vec2( 1, 1), // pos
110 vec2( 0, 1), vec2( 0, 0), vec2( 1, 0), vec2( 1, 1), // tex
111 };
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);
115}
116
118{
119 return display.getCliComm();
120}
121
122unsigned PostProcessor::getLineWidth(
123 FrameSource* frame, unsigned y, unsigned step)
124{
125 return max_value(xrange(step), [&](auto i) { return frame->getLineWidth(y + i); });
126}
127
128void PostProcessor::executeUntil(EmuTime::param /*time*/)
129{
130 // insert fake end of frame event
131 eventDistributor.distributeEvent(FinishFrameEvent(
133}
134
135using WorkBuffer = std::vector<MemBuffer<FrameSource::Pixel, SSE_ALIGNMENT>>;
136static void getScaledFrame(const FrameSource& paintFrame,
137 std::span<const FrameSource::Pixel*> lines,
138 WorkBuffer& workBuffer)
139{
140 auto height = narrow<unsigned>(lines.size());
141 unsigned width = (height == 240) ? 320 : 640;
142 const FrameSource::Pixel* linePtr = nullptr;
143 FrameSource::Pixel* work = nullptr;
144 for (auto i : xrange(height)) {
145 if (linePtr == work) {
146 // If work buffer was used in previous iteration,
147 // then allocate a new one.
148 work = workBuffer.emplace_back(width).data();
149 }
150 if (height == 240) {
151 auto line = paintFrame.getLinePtr320_240(i, std::span<uint32_t, 320>{work, 320});
152 linePtr = line.data();
153 } else {
154 assert (height == 480);
155 auto line = paintFrame.getLinePtr640_480(i, std::span<uint32_t, 640>{work, 640});
156 linePtr = line.data();
157 }
158 lines[i] = linePtr;
159 }
160}
161
162void PostProcessor::takeRawScreenShot(unsigned height2, const std::string& filename)
163{
164 if (!paintFrame) {
165 throw CommandException("TODO");
166 }
167
169 WorkBuffer workBuffer;
170 getScaledFrame(*paintFrame, lines, workBuffer);
171 unsigned width = (height2 == 240) ? 320 : 640;
172 PNG::saveRGBA(width, lines, filename);
173}
174
175void PostProcessor::createRegions()
176{
177 regions.clear();
178
179 const unsigned srcHeight = paintFrame->getHeight();
180 const unsigned dstHeight = screen.getLogicalHeight();
181
182 unsigned g = std::gcd(srcHeight, dstHeight);
183 unsigned srcStep = srcHeight / g;
184 unsigned dstStep = dstHeight / g;
185
186 // TODO: Store all MSX lines in RawFrame and only scale the ones that fit
187 // on the PC screen, as a preparation for resizable output window.
188 unsigned srcStartY = 0;
189 unsigned dstStartY = 0;
190 while (dstStartY < dstHeight) {
191 // Currently this is true because the source frame height
192 // is always >= dstHeight/(dstStep/srcStep).
193 assert(srcStartY < srcHeight);
194
195 // get region with equal lineWidth
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)) {
201 srcEndY += srcStep;
202 dstEndY += dstStep;
203 }
204
205 regions.emplace_back(srcStartY, srcEndY,
206 dstStartY, dstEndY,
207 lineWidth);
208
209 // next region
210 srcStartY = srcEndY;
211 dstStartY = dstEndY;
212 }
213}
214
216{
217 if (renderSettings.getInterleaveBlackFrame()) {
218 interleaveCount ^= 1;
219 if (interleaveCount) {
220 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
221 glClear(GL_COLOR_BUFFER_BIT);
222 return;
223 }
224 }
225
226 auto deform = renderSettings.getDisplayDeform();
227 float horStretch = renderSettings.getHorizontalStretch();
228 int glow = renderSettings.getGlow();
229
230 if ((screen.getViewOffset() != ivec2()) || // any part of the screen not covered by the viewport?
231 (deform == RenderSettings::DisplayDeform::_3D) || !paintFrame) {
232 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
233 glClear(GL_COLOR_BUFFER_BIT);
234 if (!paintFrame) {
235 return;
236 }
237 }
238
239 // New scaler algorithm selected?
240 if (auto algo = renderSettings.getScaleAlgorithm();
241 scaleAlgorithm != algo) {
242 scaleAlgorithm = algo;
243 currScaler = GLScalerFactory::createScaler(renderSettings, maxWidth, height * 2); // *2 for interlace TODO only when canDoInterlace
244
245 // Re-upload frame data, this is both
246 // - Chunks of RawFrame with a specific line width, possibly
247 // with some extra lines above and below each chunk that are
248 // also converted to this line width.
249 // - Extra data that is specific for the scaler (ATM only the
250 // hq and hqlite scalers require this).
251 // Re-uploading the first is not strictly needed. But switching
252 // scalers doesn't happen that often, so it also doesn't hurt
253 // and it keeps the code simpler.
254 uploadFrame();
255 }
256
257 auto size = screen.getLogicalSize();
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, // target
265 0, // level
266 GL_RGB, // internal format
267 size.x, // width
268 size.y, // height
269 0, // border
270 GL_RGB, // format
271 GL_UNSIGNED_BYTE, // type
272 nullptr); // data
273 renderedFrame.fbo = FrameBufferObject(renderedFrame.tex);
274 }
275 renderedFrame.fbo.push();
276
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, // src
284 r.dstStartY, r.dstEndY, size.x, // dst
285 paintFrame->getHeight()); // dst
286 }
287
288 drawNoise();
289 drawGlow(glow);
290
291 renderedFrame.fbo.pop();
292 renderedFrame.tex.bind();
293 auto [x, y] = screen.getViewOffset();
294 auto [w, h] = screen.getViewSize();
295 glViewport(x, y, w, h);
296
298 drawMonitor3D();
299 } else {
300 float x1 = (320.0f - float(horStretch)) * (1.0f / (2.0f * 320.0f));
301 float x2 = 1.0f - x1;
302 std::array tex = {
303 vec2(x1, 1), vec2(x1, 0), vec2(x2, 0), vec2(x2, 1)
304 };
305
306 const auto& glContext = *gl::context;
307 glContext.progTex.activate();
308 glUniform4f(glContext.unifTexColor,
309 1.0f, 1.0f, 1.0f, 1.0f);
310 mat4 I;
311 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.data());
312
313 glBindBuffer(GL_ARRAY_BUFFER, vbo.get());
314 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
315 glEnableVertexAttribArray(0);
316
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);
321
322 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
323
324 glDisableVertexAttribArray(1);
325 glDisableVertexAttribArray(0);
326 glBindBuffer(GL_ARRAY_BUFFER, 0);
327 }
328 storedFrame = true;
329 //gl::checkGLError("PostProcessor::paint");
330}
331
332std::unique_ptr<RawFrame> PostProcessor::rotateFrames(
333 std::unique_ptr<RawFrame> finishedFrame, EmuTime::param time)
334{
335 if (renderSettings.getInterleaveBlackFrame()) {
336 auto delta = time - lastRotate; // time between last two calls
337 auto middle = time + delta / 2; // estimate for middle between now
338 // and next call
339 setSyncPoint(middle);
340 }
341 lastRotate = time;
342
343 // Figure out how many past frames we want to use.
344 int numRequired = 1;
345 bool doDeinterlace = false;
346 bool doInterlace = false;
347 bool doDeflicker = false;
348 auto currType = finishedFrame->getField();
349 if (canDoInterlace) {
351 if (renderSettings.getDeinterlace()) {
352 doDeinterlace = true;
353 numRequired = 2;
354 } else {
355 doInterlace = true;
356 }
357 } else if (renderSettings.getDeflicker()) {
358 doDeflicker = true;
359 numRequired = 4;
360 }
361 }
362
363 // Which frame can be returned (recycled) to caller. Prefer to return
364 // the youngest frame to improve cache locality.
365 int recycleIdx = (lastFramesCount < numRequired)
366 ? lastFramesCount++ // store one more
367 : (numRequired - 1); // youngest that's no longer needed
368 assert(recycleIdx < 4);
369 auto recycleFrame = std::move(lastFrames[recycleIdx]); // might be nullptr
370
371 // Insert new frame in front of lastFrames[], shift older frames
372 std::move_backward(&lastFrames[0], &lastFrames[recycleIdx],
373 &lastFrames[recycleIdx + 1]);
374 lastFrames[0] = std::move(finishedFrame);
375
376 // Are enough frames available?
377 if (lastFramesCount >= numRequired) {
378 // Only the last 'numRequired' are kept up to date.
379 lastFramesCount = numRequired;
380 } else {
381 // Not enough past frames, fall back to 'regular' rendering.
382 // This situation can only occur when:
383 // - The very first frame we render needs to be deinterlaced.
384 // In other case we have at least one valid frame from the
385 // past plus one new frame passed via the 'finishedFrame'
386 // parameter.
387 // - Or when (re)enabling the deflicker setting. Typically only
388 // 1 frame in lastFrames[] is kept up-to-date (and we're
389 // given 1 new frame), so it can take up-to 2 frame after
390 // enabling deflicker before it actually takes effect.
391 doDeinterlace = false;
392 doInterlace = false;
393 doDeflicker = false;
394 }
395
396 // Setup the to-be-painted frame
397 if (doDeinterlace) {
398 if (currType == FrameSource::FieldType::ODD) {
399 deinterlacedFrame->init(lastFrames[1].get(), lastFrames[0].get());
400 } else {
401 deinterlacedFrame->init(lastFrames[0].get(), lastFrames[1].get());
402 }
403 paintFrame = deinterlacedFrame.get();
404 } else if (doInterlace) {
405 interlacedFrame->init(
406 lastFrames[0].get(),
407 (currType == FrameSource::FieldType::ODD) ? 1 : 0);
408 paintFrame = interlacedFrame.get();
409 } else if (doDeflicker) {
410 deflicker->init();
411 paintFrame = deflicker.get();
412 } else {
413 paintFrame = lastFrames[0].get();
414 }
415 if (superImposeVdpFrame) {
416 superImposedFrame->init(paintFrame, superImposeVdpFrame);
417 paintFrame = superImposedFrame.get();
418 }
419
420 // Possibly record this frame
421 if (recorder && needRecord()) {
422 try {
423 recorder->addImage(paintFrame, time);
424 } catch (MSXException& e) {
426 "Recording stopped with error: ",
427 e.getMessage());
428 recorder->stop();
429 assert(!recorder);
430 }
431 }
432
433 // Return recycled frame to the caller
434 std::unique_ptr<RawFrame> reuseFrame = [&] {
435 if (canDoInterlace) {
436 if (!recycleFrame) [[unlikely]] {
437 recycleFrame = std::make_unique<RawFrame>(maxWidth, height);
438 }
439 return std::move(recycleFrame);
440 } else {
441 return std::move(lastFrames[0]);
442 }
443 }();
444
445 uploadFrame();
446 ++frameCounter;
447 noiseX = random_float(0.0f, 1.0f);
448 noiseY = random_float(0.0f, 1.0f);
449 return reuseFrame;
450}
451
452void PostProcessor::update(const Setting& setting) noexcept
453{
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());
461 }
462}
463
464void PostProcessor::uploadFrame()
465{
466 createRegions();
467
468 const unsigned srcHeight = paintFrame->getHeight();
469 for (const auto& r : regions) {
470 // upload data
471 // TODO get before/after data from scaler
472 int before = 1;
473 unsigned after = 1;
474 uploadBlock(narrow<unsigned>(std::max(0, narrow<int>(r.srcStartY) - before)),
475 std::min(srcHeight, r.srcEndY + after),
476 r.lineWidth);
477 }
478
479 if (superImposeVideoFrame) {
480 int w = narrow<GLsizei>(superImposeVideoFrame->getWidth());
481 int h = narrow<GLsizei>(superImposeVideoFrame->getHeight());
482 if (superImposeTex.getWidth() != w ||
483 superImposeTex.getHeight() != h) {
484 superImposeTex.resize(w, h);
485 superImposeTex.setInterpolation(true);
486 }
487 superImposeTex.bind();
488 glTexSubImage2D(
489 GL_TEXTURE_2D, // target
490 0, // level
491 0, // offset x
492 0, // offset y
493 w, // width
494 h, // height
495 GL_RGBA, // format
496 GL_UNSIGNED_BYTE, // type
497 const_cast<RawFrame*>(superImposeVideoFrame)->getLineDirect(0).data()); // data
498 }
499}
500
501void PostProcessor::uploadBlock(
502 unsigned srcStartY, unsigned srcEndY, unsigned lineWidth)
503{
504 // create texture on demand
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)); // *2 for interlace TODO only when canDoInterlace
510 textures.push_back(std::move(textureData));
511 it = end(textures) - 1;
512 }
513 auto& tex = it->tex;
514
515 // bind texture
516 tex.bind();
517
518 // upload data
519 pbo.bind();
520 auto mapped = pbo.mapWrite();
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()) {
526 ranges::copy(line, dest);
527 }
528 }
529 pbo.unmap();
530#if defined(__APPLE__)
531 // The nVidia GL driver for the GeForce 8000/9000 series seems to hang
532 // on texture data replacements that are 1 pixel wide and start on a
533 // line number that is a non-zero multiple of 16.
534 if (lineWidth == 1 && srcStartY != 0 && srcStartY % 16 == 0) {
535 srcStartY--;
536 }
537#endif
538 glTexSubImage2D(
539 GL_TEXTURE_2D, // target
540 0, // level
541 0, // offset x
542 narrow<GLint>(srcStartY), // offset y
543 narrow<GLint>(lineWidth), // width
544 narrow<GLint>(numLines), // height
545 GL_RGBA, // format
546 GL_UNSIGNED_BYTE, // type
547 mapped.data()); // data
548 pbo.unbind();
549
550 // possibly upload scaler specific data
551 if (currScaler) {
552 currScaler->uploadBlock(srcStartY, srcEndY, lineWidth, *paintFrame);
553 }
554}
555
556void PostProcessor::drawGlow(int glow)
557{
558 if ((glow == 0) || !storedFrame) return;
559
560 const auto& glContext = *gl::context;
561 glContext.progTex.activate();
562 glEnable(GL_BLEND);
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));
567 mat4 I;
568 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.data());
569
570 glBindBuffer(GL_ARRAY_BUFFER, vbo.get());
571
572 const vec2* offset = nullptr;
573 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, offset); // pos
574 offset += 4; // see initBuffers()
575 glEnableVertexAttribArray(0);
576
577 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, offset); // tex
578 glEnableVertexAttribArray(1);
579
580 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
581
582 glDisableVertexAttribArray(1);
583 glDisableVertexAttribArray(0);
584 glBindBuffer(GL_ARRAY_BUFFER, 0);
585 glDisable(GL_BLEND);
586}
587
588void PostProcessor::preCalcNoise(float factor)
589{
590 std::array<uint8_t, 256 * 256> buf1;
591 std::array<uint8_t, 256 * 256> buf2;
592 auto& generator = global_urng(); // fast (non-cryptographic) random numbers
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);
599 }
600
601 // GL_LUMINANCE is no longer supported in newer openGL versions
602 auto format = (OPENGL_VERSION >= OPENGL_3_3) ? GL_RED : GL_LUMINANCE;
603 noiseTextureA.bind();
604 glTexImage2D(
605 GL_TEXTURE_2D, // target
606 0, // level
607 format, // internal format
608 256, // width
609 256, // height
610 0, // border
611 format, // format
612 GL_UNSIGNED_BYTE, // type
613 buf1.data()); // data
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);
617#endif
618
619 noiseTextureB.bind();
620 glTexImage2D(
621 GL_TEXTURE_2D, // target
622 0, // level
623 format, // internal format
624 256, // width
625 256, // height
626 0, // border
627 format, // format
628 GL_UNSIGNED_BYTE, // type
629 buf2.data()); // data
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);
633#endif
634}
635
636void PostProcessor::drawNoise() const
637{
638 if (renderSettings.getNoise() == 0.0f) return;
639
640 // Rotate and mirror noise texture in consecutive frames to avoid
641 // seeing 'patterns' in the noise.
642 static constexpr std::array pos = {
643 std::array{vec2{-1, -1}, vec2{ 1, -1}, vec2{ 1, 1}, vec2{-1, 1}},
644 std::array{vec2{-1, 1}, vec2{ 1, 1}, vec2{ 1, -1}, vec2{-1, -1}},
645 std::array{vec2{-1, 1}, vec2{-1, -1}, vec2{ 1, -1}, vec2{ 1, 1}},
646 std::array{vec2{ 1, 1}, vec2{ 1, -1}, vec2{-1, -1}, vec2{-1, 1}},
647 std::array{vec2{ 1, 1}, vec2{-1, 1}, vec2{-1, -1}, vec2{ 1, -1}},
648 std::array{vec2{ 1, -1}, vec2{-1, -1}, vec2{-1, 1}, vec2{ 1, 1}},
649 std::array{vec2{ 1, -1}, vec2{ 1, 1}, vec2{-1, 1}, vec2{-1, -1}},
650 std::array{vec2{-1, -1}, vec2{-1, 1}, vec2{ 1, 1}, vec2{ 1, -1}},
651 };
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 ),
658 };
659
660 const auto& glContext = *gl::context;
661 glContext.progTex.activate();
662
663 glEnable(GL_BLEND);
664 glBlendFunc(GL_ONE, GL_ONE);
665 glUniform4f(glContext.unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f);
666 mat4 I;
667 glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.data());
668
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);
674
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);
680
681 glDisableVertexAttribArray(1);
682 glDisableVertexAttribArray(0);
683 glBlendEquation(GL_FUNC_ADD); // restore default
684 glDisable(GL_BLEND);
685}
686
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;
695
696void PostProcessor::preCalcMonitor3D(float width)
697{
698 // precalculate vertex-positions, -normals and -texture-coordinates
699 std::array<std::array<Vertex, GRID_SIZE1>, GRID_SIZE1> vertices;
700
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));
704
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;
710
711 v.position = vec3(x, y, (x * x + y * y) * (1.0f / -12.0f));
712 v.normal = normalize(vec3(x * (1.0f / 6.0f), y * (1.0f / 6.0f), 1.0f)) * 1.2f;
713 v.tex = vec2((float(sx) / GRID_SIZE) * s + b,
714 float(sy) / GRID_SIZE);
715 }
716 }
717
718 // calculate indices
719 std::array<uint16_t, NUM_INDICES> indices;
720
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);
726 }
727 // skip 2, filled in later
728 ind += 2;
729 }
730 assert((ind - indices.data()) == NUM_INDICES + 2);
731 ind = indices.data();
732 repeat(GRID_SIZE - 1, [&] {
733 ind += 2 * GRID_SIZE1;
734 // repeat prev and next index to restart strip
735 ind[0] = ind[-1];
736 ind[1] = ind[ 2];
737 ind += 2;
738 });
739
740 // upload calculated values to buffers
741 glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.get());
742 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices.data(),
743 GL_STATIC_DRAW);
744 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.get());
745 glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices.data(),
746 GL_STATIC_DRAW);
747 glBindBuffer(GL_ARRAY_BUFFER, 0);
748 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
749
750 // calculate transformation matrices
751 mat4 proj = frustum(-1, 1, -1, 1, 1, 10);
752 mat4 tran = translate(vec3(0.0f, 0.4f, -2.0f));
753 mat4 rotX = rotateX(radians(-10.0f));
754 mat4 scal = scale(vec3(2.2f, 2.2f, 2.2f));
755
756 mat3 normal(rotX);
757 mat4 mvp = proj * tran * rotX * scal;
758
759 // set uniforms
760 monitor3DProg.activate();
761 glUniform1i(monitor3DProg.getUniformLocation("u_tex"), 0);
762 glUniformMatrix4fv(monitor3DProg.getUniformLocation("u_mvpMatrix"),
763 1, GL_FALSE, mvp.data());
764 glUniformMatrix3fv(monitor3DProg.getUniformLocation("u_normalMatrix"),
765 1, GL_FALSE, normal.data());
766}
767
768void PostProcessor::drawMonitor3D() const
769{
770 monitor3DProg.activate();
771
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),
776 base);
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);
784
785 glDrawElements(GL_TRIANGLE_STRIP, NUM_INDICES, GL_UNSIGNED_SHORT, nullptr);
786 glDisableVertexAttribArray(2);
787 glDisableVertexAttribArray(1);
788 glDisableVertexAttribArray(0);
789
790 glBindBuffer(GL_ARRAY_BUFFER, 0);
791 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
792}
793
794} // namespace openmsx
#define OPENGL_3_3
Definition GLUtil.hh:24
#define OPENGL_VERSION
Definition GLUtil.hh:25
BaseSetting * setting
int g
GLuint get() const
Definition GLUtil.hh:444
GLsizei getHeight() const
Definition GLUtil.hh:118
GLsizei getWidth() const
Definition GLUtil.hh:117
void resize(GLsizei width, GLsizei height)
Definition GLUtil.cc:85
Wrapper around an OpenGL fragment shader: a program executed on the GPU that computes the colors of p...
Definition GLUtil.hh:348
void allocate(GLuint size)
Allocate the maximum required size for this buffer.
Definition GLUtil.hh:239
void unmap() const
Unmaps the contents of this buffer.
Definition GLUtil.hh:282
void unbind() const
Unbind this buffer.
Definition GLUtil.hh:263
void bind() const
Bind this PixelBuffer.
Definition GLUtil.hh:255
std::span< T > mapWrite()
Maps the contents of this buffer into memory.
Definition GLUtil.hh:271
void activate() const
Makes this program the active shader program.
Definition GLUtil.cc:282
void attach(const Shader &shader)
Adds a given shader to this program.
Definition GLUtil.cc:237
void link()
Links all attached shaders together into one program.
Definition GLUtil.cc:249
void bindAttribLocation(unsigned index, const char *name)
Bind the given name for a vertex shader attribute to the given location.
Definition GLUtil.cc:269
GLint getUniformLocation(const char *name) const
Gets a reference to a uniform variable declared in the shader source.
Definition GLUtil.cc:274
void bind() const
Makes this texture the active GL texture.
Definition GLUtil.hh:88
void setInterpolation(bool interpolation)
Enable/disable bilinear interpolation for this texture.
Definition GLUtil.cc:61
Wrapper around an OpenGL vertex shader: a program executed on the GPU that computes per-vertex stuff.
Definition GLUtil.hh:333
constexpr const T * data() const
Definition gl_mat.hh:100
void addImage(const FrameSource *frame, EmuTime::param time)
void printWarning(std::string_view message)
Definition CliComm.cc:12
Represents the output window/screen of openMSX.
Definition Display.hh:31
CliComm & getCliComm() const
Definition Display.cc:110
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
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.
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)
Definition Subject.hh:60
void attach(Observer< T > &observer)
Definition Subject.hh:54
int getVideoSource() const
Returns the ID for this VideoLayer.
Definition VideoLayer.cc:40
bool needRecord() const
Definition VideoLayer.cc:92
int getVideoSourceSetting() const
Definition VideoLayer.cc:44
void update(const Setting &setting) noexcept override
Definition VideoLayer.cc:49
Definition gl_mat.hh:23
vecN< 3, float > vec3
Definition gl_vec.hh:383
mat4 rotateX(float angle)
vecN< 2, float > vec2
Definition gl_vec.hh:382
std::optional< Context > context
Definition GLContext.cc:10
constexpr T radians(T d)
Definition gl_vec.hh:410
constexpr mat4 frustum(float left, float right, float bottom, float top, float nearVal, float farVal)
vecN< N, T > normalize(const vecN< N, T > &x)
Definition gl_vec.hh:512
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)
Definition PNG.cc:322
This file implemented 3 utility functions:
Definition Autofire.cc:11
std::vector< MemBuffer< FrameSource::Pixel, SSE_ALIGNMENT > > WorkBuffer
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
constexpr auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:252
auto & global_urng()
Return reference to a (shared) global random number generator.
Definition random.hh:8
float random_float(float from, float upto)
Return a random float in the range [from, upto) (note: half-open interval).
Definition random.hh:50
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...
Definition stl.hh:75
auto max_value(InputIterator first, InputIterator last, Proj proj={})
Definition stl.hh:227
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147
constexpr auto xrange(T e)
Definition xrange.hh:132
constexpr auto end(const zstring_view &x)