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