OXYGENHUB
◆ main openmsx / src / video / PostProcessor.cc
25.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "PostProcessor.hh"
2
3
#include "AviRecorder.hh"
4
#include "CliComm.hh"
5
#include "CommandException.hh"
6
#include "Deflicker.hh"
7
#include "DeinterlacedFrame.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
40
using namespace gl;
41
42
namespace openmsx {
43
44
PostProcessor::PostProcessor(
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
91
PostProcessor::~PostProcessor()
92
{
93
	renderSettings.getHorizontalStretchSetting().detach(*this);
94
	renderSettings.getNoiseSetting().detach(*this);
95
96
	if (recorder) {
97
		getCliComm().printWarning(
98
			"Video recording stopped, because you "
99
			"changed machine or changed a video setting "
100
			"during recording.");
101
		recorder->stop();
102
	}
103
}
104
105
void 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
117
CliComm& PostProcessor::getCliComm()
118
{
119
	return display.getCliComm();
120
}
121
122
unsigned 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
128
void PostProcessor::executeUntil(EmuTime /*time*/)
129
{
130
	// insert fake end of frame event
131
	eventDistributor.distributeEvent(FinishFrameEvent(
132
		getVideoSource(), getVideoSourceSetting(), false));
133
}
134
135
using WorkBuffer = std::vector<MemBuffer<FrameSource::Pixel, SSE_ALIGNMENT>>;
136
static 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
162
void PostProcessor::takeRawScreenShot(std::optional<unsigned> desiredHeight, const std::string& filename)
163
{
164
	if (!paintFrame) {
165
		throw CommandException("TODO");
166
	}
167
168
	const unsigned targetHeight = desiredHeight ? *desiredHeight : [this] {
169
		auto maxLineWidth = getLineWidth(paintFrame, 0, paintFrame->getHeight());
170
		if (maxLineWidth == 320) return 240;
171
		// in all other cases (there could be other than 640), we go for 640x480
172
		return 480;
173
	}();
174
175
	inplace_buffer<const FrameSource::Pixel*, 480> lines(uninitialized_tag{}, targetHeight);
176
	WorkBuffer workBuffer;
177
	getScaledFrame(*paintFrame, lines, workBuffer);
178
	unsigned width = (targetHeight == 240) ? 320 : 640;
179
	PNG::saveRGBA(width, lines, filename);
180
}
181
182
void PostProcessor::createRegions()
183
{
184
	regions.clear();
185
186
	const unsigned srcHeight = paintFrame->getHeight();
187
	const unsigned dstHeight = screen.getLogicalHeight();
188
	regionsDstHeight = dstHeight;
189
190
	unsigned g = std::gcd(srcHeight, dstHeight);
191
	unsigned srcStep = srcHeight / g;
192
	unsigned dstStep = dstHeight / g;
193
194
	// TODO: Store all MSX lines in RawFrame and only scale the ones that fit
195
	//       on the PC screen, as a preparation for resizable output window.
196
	unsigned srcStartY = 0;
197
	unsigned dstStartY = 0;
198
	while (dstStartY < dstHeight) {
199
		// Currently this is true because the source frame height
200
		// is always >= dstHeight/(dstStep/srcStep).
201
		assert(srcStartY < srcHeight);
202
203
		// get region with equal lineWidth
204
		unsigned lineWidth = getLineWidth(paintFrame, srcStartY, srcStep);
205
		unsigned srcEndY = srcStartY + srcStep;
206
		unsigned dstEndY = dstStartY + dstStep;
207
		while ((srcEndY < srcHeight) && (dstEndY < dstHeight) &&
208
		       (getLineWidth(paintFrame, srcEndY, srcStep) == lineWidth)) {
209
			srcEndY += srcStep;
210
			dstEndY += dstStep;
211
		}
212
213
		regions.emplace_back(srcStartY, srcEndY,
214
		                     dstStartY, dstEndY,
215
		                     lineWidth);
216
217
		// next region
218
		srcStartY = srcEndY;
219
		dstStartY = dstEndY;
220
	}
221
}
222
223
void PostProcessor::paint(OutputSurface& /*output*/)
224
{
225
	if (renderSettings.getInterleaveBlackFrame()) {
226
		interleaveCount ^= 1;
227
		if (interleaveCount) {
228
			glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
229
			glClear(GL_COLOR_BUFFER_BIT);
230
			return;
231
		}
232
	}
233
234
	auto deform = renderSettings.getDisplayDeform();
235
	float horStretch = renderSettings.getHorizontalStretch();
236
	int glow = renderSettings.getGlow();
237
238
	if ((screen.getViewOffset() != ivec2()) || // any part of the screen not covered by the viewport?
239
	    (deform == RenderSettings::DisplayDeform::_3D) || !paintFrame) {
240
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
241
		glClear(GL_COLOR_BUFFER_BIT);
242
		if (!paintFrame) {
243
			return;
244
		}
245
	}
246
247
	auto size = screen.getLogicalSize();
248
	bool needReUpload = size.y != int(regionsDstHeight);
249
250
	// New scaler algorithm selected?
251
	if (auto algo = renderSettings.getScaleAlgorithm();
252
	    scaleAlgorithm != algo) {
253
		scaleAlgorithm = algo;
254
		currScaler = GLScalerFactory::createScaler(renderSettings, maxWidth, height * 2); // *2 for interlace   TODO only when canDoInterlace
255
		needReUpload = true;
256
	}
257
258
	if (needReUpload) {
259
		// Re-upload frame data, this is both
260
		//  - Chunks of RawFrame with a specific line width, possibly
261
		//    with some extra lines above and below each chunk that are
262
		//    also converted to this line width.
263
		//  - Extra data that is specific for the scaler (ATM only the
264
		//    hq and hqlite scalers require this).
265
		// Re-uploading the first is not strictly needed. But switching
266
		// scalers doesn't happen that often, so it also doesn't hurt
267
		// and it keeps the code simpler.
268
		uploadFrame();
269
	}
270
271
	glViewport(0, 0, size.x, size.y);
272
	glBindTexture(GL_TEXTURE_2D, 0);
273
	auto& renderedFrame = renderedFrames[frameCounter & 1];
274
	if (renderedFrame.size != size) {
275
		renderedFrame.tex.bind();
276
		renderedFrame.tex.setInterpolation(true);
277
		glTexImage2D(GL_TEXTURE_2D,     // target
278
			     0,                 // level
279
			     GL_RGB,            // internal format
280
			     size.x,            // width
281
			     size.y,            // height
282
			     0,                 // border
283
			     GL_RGB,            // format
284
			     GL_UNSIGNED_BYTE,  // type
285
			     nullptr);          // data
286
		renderedFrame.fbo = FrameBufferObject(renderedFrame.tex);
287
	}
288
	renderedFrame.fbo.push();
289
290
	for (const auto& r : regions) {
291
		auto it = find_unguarded(textures, r.lineWidth, &TextureData::width);
292
		auto* superImpose = superImposeVideoFrame
293
		                  ? &superImposeTex : nullptr;
294
		currScaler->scaleImage(
295
			it->tex, superImpose,
296
			r.srcStartY, r.srcEndY, r.lineWidth, // src
297
			r.dstStartY, r.dstEndY, size.x,   // dst
298
			paintFrame->getHeight()); // dst
299
	}
300
301
	drawNoise();
302
	drawGlow(glow);
303
304
	renderedFrame.fbo.pop();
305
	renderedFrame.tex.bind();
306
307
	if (renderSettings.getFullStretch()) {
308
		auto [w, h] = screen.getPhysicalSize();
309
		glViewport(0, 0, w, h);
310
	} else {
311
		auto [x, y] = screen.getViewOffset();
312
		auto [w, h] = screen.getViewSize();
313
		glViewport(x, y, w, h);
314
	}
315
316
	if (deform == RenderSettings::DisplayDeform::_3D) {
317
		drawMonitor3D();
318
	} else {
319
		float x1 = (320.0f - horStretch) * (1.0f / (2.0f * 320.0f));
320
		float x2 = 1.0f - x1;
321
		std::array tex = {
322
			vec2(x1, 1), vec2(x1, 0), vec2(x2, 0), vec2(x2, 1)
323
		};
324
325
		const auto& glContext = *gl::context;
326
		glContext.progTex.activate();
327
		glUniform4f(glContext.unifTexColor,
328
				1.0f, 1.0f, 1.0f, 1.0f);
329
		mat4 I;
330
		glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.data());
331
332
		glBindBuffer(GL_ARRAY_BUFFER, vbo.get());
333
		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
334
		glEnableVertexAttribArray(0);
335
336
		glBindBuffer(GL_ARRAY_BUFFER, stretchVBO.get());
337
		glBufferData(GL_ARRAY_BUFFER, sizeof(tex), tex.data(), GL_STREAM_DRAW);
338
		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
339
		glEnableVertexAttribArray(1);
340
341
		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
342
343
		glDisableVertexAttribArray(1);
344
		glDisableVertexAttribArray(0);
345
		glBindBuffer(GL_ARRAY_BUFFER, 0);
346
	}
347
	storedFrame = true;
348
	//gl::checkGLError("PostProcessor::paint");
349
}
350
351
std::unique_ptr<RawFrame> PostProcessor::rotateFrames(
352
	std::unique_ptr<RawFrame> finishedFrame, EmuTime time)
353
{
354
	if (renderSettings.getInterleaveBlackFrame()) {
355
		auto delta = time - lastRotate; // time between last two calls
356
		auto middle = time + delta / 2; // estimate for middle between now
357
		                                // and next call
358
		setSyncPoint(middle);
359
	}
360
	lastRotate = time;
361
362
	// Figure out how many past frames we want to use.
363
	int numRequired = 1;
364
	bool doDeinterlace = false;
365
	bool doInterlace   = false;
366
	bool doDeflicker   = false;
367
	auto currType = finishedFrame->getField();
368
	if (canDoInterlace) {
369
		if (currType != FrameSource::FieldType::NONINTERLACED) {
370
			if (renderSettings.getDeinterlace()) {
371
				doDeinterlace = true;
372
				numRequired = 2;
373
			} else {
374
				doInterlace = true;
375
			}
376
		} else if (renderSettings.getDeflicker()) {
377
			doDeflicker = true;
378
			numRequired = 4;
379
		}
380
	}
381
382
	// Which frame can be returned (recycled) to caller. Prefer to return
383
	// the youngest frame to improve cache locality.
384
	int recycleIdx = (lastFramesCount < numRequired)
385
		? lastFramesCount++  // store one more
386
		: (numRequired - 1); // youngest that's no longer needed
387
	assert(recycleIdx < 4);
388
	auto recycleFrame = std::move(lastFrames[recycleIdx]); // might be nullptr
389
390
	// Insert new frame in front of lastFrames[], shift older frames
391
	std::move_backward(&lastFrames[0], &lastFrames[recycleIdx],
392
	                   &lastFrames[recycleIdx + 1]);
393
	lastFrames[0] = std::move(finishedFrame);
394
395
	// Are enough frames available?
396
	if (lastFramesCount >= numRequired) {
397
		// Only the last 'numRequired' are kept up to date.
398
		lastFramesCount = numRequired;
399
	} else {
400
		// Not enough past frames, fall back to 'regular' rendering.
401
		// This situation can only occur when:
402
		// - The very first frame we render needs to be deinterlaced.
403
		//   In other case we have at least one valid frame from the
404
		//   past plus one new frame passed via the 'finishedFrame'
405
		//   parameter.
406
		// - Or when (re)enabling the deflicker setting. Typically only
407
		//   1 frame in lastFrames[] is kept up-to-date (and we're
408
		//   given 1 new frame), so it can take up-to 2 frame after
409
		//   enabling deflicker before it actually takes effect.
410
		doDeinterlace = false;
411
		doInterlace   = false;
412
		doDeflicker   = false;
413
	}
414
415
	// Setup the to-be-painted frame
416
	if (doDeinterlace) {
417
		if (currType == FrameSource::FieldType::ODD) {
418
			deinterlacedFrame->init(lastFrames[1].get(), lastFrames[0].get());
419
		} else {
420
			deinterlacedFrame->init(lastFrames[0].get(), lastFrames[1].get());
421
		}
422
		paintFrame = deinterlacedFrame.get();
423
	} else if (doInterlace) {
424
		interlacedFrame->init(
425
			lastFrames[0].get(),
426
			(currType == FrameSource::FieldType::ODD) ? 1 : 0);
427
		paintFrame = interlacedFrame.get();
428
	} else if (doDeflicker) {
429
		deflicker->init();
430
		paintFrame = deflicker.get();
431
	} else {
432
		paintFrame = lastFrames[0].get();
433
	}
434
	if (superImposeVdpFrame) {
435
		superImposedFrame->init(paintFrame, superImposeVdpFrame);
436
		paintFrame = superImposedFrame.get();
437
	}
438
439
	// Possibly record this frame
440
	if (recorder && needRecord()) {
441
		try {
442
			recorder->addImage(paintFrame, time);
443
		} catch (MSXException& e) {
444
			getCliComm().printWarning(
445
				"Recording stopped with error: ",
446
				e.getMessage());
447
			recorder->stop();
448
			assert(!recorder);
449
		}
450
	}
451
452
	// Return recycled frame to the caller
453
	std::unique_ptr<RawFrame> reuseFrame = [&] {
454
		if (canDoInterlace) {
455
			if (!recycleFrame) [[unlikely]] {
456
				recycleFrame = std::make_unique<RawFrame>(maxWidth, height);
457
			}
458
			return std::move(recycleFrame);
459
		} else {
460
			return std::move(lastFrames[0]);
461
		}
462
	}();
463
464
	uploadFrame();
465
	++frameCounter;
466
	noiseX = random_float(0.0f, 1.0f);
467
	noiseY = random_float(0.0f, 1.0f);
468
	return reuseFrame;
469
}
470
471
void PostProcessor::update(const Setting& setting) noexcept
472
{
473
	VideoLayer::update(setting);
474
	const auto& noiseSetting = renderSettings.getNoiseSetting();
475
	const auto& horizontalStretch = renderSettings.getHorizontalStretchSetting();
476
	if (&setting == &noiseSetting) {
477
		preCalcNoise(noiseSetting.getFloat());
478
	} else if (&setting == &horizontalStretch) {
479
		preCalcMonitor3D(horizontalStretch.getFloat());
480
	}
481
}
482
483
void PostProcessor::uploadFrame()
484
{
485
	createRegions();
486
487
	const unsigned srcHeight = paintFrame->getHeight();
488
	for (const auto& r : regions) {
489
		// upload data
490
		// TODO get before/after data from scaler
491
		int before = 1;
492
		unsigned after  = 1;
493
		uploadBlock(narrow<unsigned>(std::max(0, narrow<int>(r.srcStartY) - before)),
494
		            std::min(srcHeight, r.srcEndY + after),
495
		            r.lineWidth);
496
	}
497
498
	if (superImposeVideoFrame) {
499
		int w = narrow<GLsizei>(superImposeVideoFrame->getWidth());
500
		int h = narrow<GLsizei>(superImposeVideoFrame->getHeight());
501
		if (superImposeTex.getWidth()  != w ||
502
		    superImposeTex.getHeight() != h) {
503
			superImposeTex.resize(w, h);
504
			superImposeTex.setInterpolation(true);
505
		}
506
		superImposeTex.bind();
507
		glTexSubImage2D(
508
			GL_TEXTURE_2D,     // target
509
			0,                 // level
510
			0,                 // offset x
511
			0,                 // offset y
512
			w,                 // width
513
			h,                 // height
514
			GL_RGBA,           // format
515
			GL_UNSIGNED_BYTE,  // type
516
			superImposeVideoFrame->getLineDirect(0).data()); // data
517
	}
518
}
519
520
void PostProcessor::uploadBlock(
521
	unsigned srcStartY, unsigned srcEndY, unsigned lineWidth)
522
{
523
	// create texture on demand
524
	auto it = std::ranges::find(textures, lineWidth, &TextureData::width);
525
	if (it == end(textures)) {
526
		TextureData textureData;
527
		textureData.tex.resize(narrow<GLsizei>(lineWidth),
528
		                       narrow<GLsizei>(height * 2)); // *2 for interlace   TODO only when canDoInterlace
529
		textures.push_back(std::move(textureData));
530
		it = end(textures) - 1;
531
	}
532
	auto& tex = it->tex;
533
534
	// bind texture
535
	tex.bind();
536
537
	// upload data
538
	pbo.bind();
539
	auto mapped = pbo.mapWrite();
540
	auto numLines = srcEndY - srcStartY;
541
	for (auto yy : xrange(numLines)) {
542
		auto dest = mapped.subspan(yy * size_t(lineWidth), lineWidth);
543
		auto line = paintFrame->getLine(narrow<int>(yy + srcStartY), dest);
544
		if (line.data() != dest.data()) {
545
			copy_to_range(line, dest);
546
		}
547
	}
548
	pbo.unmap();
549
#ifdef __APPLE__
550
	// The nVidia GL driver for the GeForce 8000/9000 series seems to hang
551
	// on texture data replacements that are 1 pixel wide and start on a
552
	// line number that is a non-zero multiple of 16.
553
	if (lineWidth == 1 && srcStartY != 0 && srcStartY % 16 == 0) {
554
		srcStartY--;
555
	}
556
#endif
557
	glTexSubImage2D(
558
		GL_TEXTURE_2D,            // target
559
		0,                        // level
560
		0,                        // offset x
561
		narrow<GLint>(srcStartY), // offset y
562
		narrow<GLint>(lineWidth), // width
563
		narrow<GLint>(numLines),  // height
564
		GL_RGBA,                  // format
565
		GL_UNSIGNED_BYTE,         // type
566
		mapped.data());           // data
567
	pbo.unbind();
568
569
	// possibly upload scaler specific data
570
	if (currScaler) {
571
		currScaler->uploadBlock(srcStartY, srcEndY, lineWidth, *paintFrame);
572
	}
573
}
574
575
void PostProcessor::drawGlow(int glow)
576
{
577
	if ((glow == 0) || !storedFrame) return;
578
579
	const auto& glContext = *gl::context;
580
	glContext.progTex.activate();
581
	glEnable(GL_BLEND);
582
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
583
	renderedFrames[(frameCounter & 1) ^ 1].tex.bind();
584
	glUniform4f(glContext.unifTexColor,
585
	            1.0f, 1.0f, 1.0f, narrow<float>(glow) * (31.0f / 3200.0f));
586
	mat4 I;
587
	glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.data());
588
589
	glBindBuffer(GL_ARRAY_BUFFER, vbo.get());
590
591
	const vec2* offset = nullptr;
592
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, offset); // pos
593
	offset += 4; // see initBuffers()
594
	glEnableVertexAttribArray(0);
595
596
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, offset); // tex
597
	glEnableVertexAttribArray(1);
598
599
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
600
601
	glDisableVertexAttribArray(1);
602
	glDisableVertexAttribArray(0);
603
	glBindBuffer(GL_ARRAY_BUFFER, 0);
604
	glDisable(GL_BLEND);
605
}
606
607
void PostProcessor::preCalcNoise(float factor)
608
{
609
	std::array<uint8_t, 256 * 256> buf1;
610
	std::array<uint8_t, 256 * 256> buf2;
611
	auto& generator = global_urng(); // fast (non-cryptographic) random numbers
612
	std::normal_distribution<float> distribution(0.0f, 1.0f);
613
	for (auto i : xrange(256 * 256)) {
614
		float r = distribution(generator);
615
		int s = std::clamp(int(roundf(r * factor)), -255, 255);
616
		buf1[i] = narrow<uint8_t>((s > 0) ?  s : 0);
617
		buf2[i] = narrow<uint8_t>((s < 0) ? -s : 0);
618
	}
619
620
	// GL_LUMINANCE is no longer supported in newer openGL versions
621
	auto format = (OPENGL_VERSION >= OPENGL_3_3) ? GL_RED : GL_LUMINANCE;
622
	noiseTextureA.bind();
623
	glTexImage2D(
624
		GL_TEXTURE_2D,    // target
625
		0,                // level
626
		format,           // internal format
627
		256,              // width
628
		256,              // height
629
		0,                // border
630
		format,           // format
631
		GL_UNSIGNED_BYTE, // type
632
		buf1.data());     // data
633
#if OPENGL_VERSION >= OPENGL_3_3
634
	GLint swizzleMask1[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
635
	glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask1);
636
#endif
637
638
	noiseTextureB.bind();
639
	glTexImage2D(
640
		GL_TEXTURE_2D,    // target
641
		0,                // level
642
		format,           // internal format
643
		256,              // width
644
		256,              // height
645
		0,                // border
646
		format,           // format
647
		GL_UNSIGNED_BYTE, // type
648
		buf2.data());     // data
649
#if OPENGL_VERSION >= OPENGL_3_3
650
	GLint swizzleMask2[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
651
	glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask2);
652
#endif
653
}
654
655
void PostProcessor::drawNoise() const
656
{
657
	if (renderSettings.getNoise() == 0.0f) return;
658
659
	// Rotate and mirror noise texture in consecutive frames to avoid
660
	// seeing 'patterns' in the noise.
661
	static constexpr std::array pos = {
662
		std::array{vec2{-1, -1}, vec2{ 1, -1}, vec2{ 1,  1}, vec2{-1,  1}},
663
		std::array{vec2{-1,  1}, vec2{ 1,  1}, vec2{ 1, -1}, vec2{-1, -1}},
664
		std::array{vec2{-1,  1}, vec2{-1, -1}, vec2{ 1, -1}, vec2{ 1,  1}},
665
		std::array{vec2{ 1,  1}, vec2{ 1, -1}, vec2{-1, -1}, vec2{-1,  1}},
666
		std::array{vec2{ 1,  1}, vec2{-1,  1}, vec2{-1, -1}, vec2{ 1, -1}},
667
		std::array{vec2{ 1, -1}, vec2{-1, -1}, vec2{-1,  1}, vec2{ 1,  1}},
668
		std::array{vec2{ 1, -1}, vec2{ 1,  1}, vec2{-1,  1}, vec2{-1, -1}},
669
		std::array{vec2{-1, -1}, vec2{-1,  1}, vec2{ 1,  1}, vec2{ 1, -1}},
670
	};
671
	vec2 noise(noiseX, noiseY);
672
	const std::array tex = {
673
		noise + vec2(0.0f, 1.875f),
674
		noise + vec2(2.0f, 1.875f),
675
		noise + vec2(2.0f, 0.0f  ),
676
		noise + vec2(0.0f, 0.0f  ),
677
	};
678
679
	const auto& glContext = *gl::context;
680
	glContext.progTex.activate();
681
682
	glEnable(GL_BLEND);
683
	glBlendFunc(GL_ONE, GL_ONE);
684
	glUniform4f(glContext.unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f);
685
	mat4 I;
686
	glUniformMatrix4fv(glContext.unifTexMvp, 1, GL_FALSE, I.data());
687
688
	unsigned seq = frameCounter & 7;
689
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos[seq].data());
690
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex.data());
691
	glEnableVertexAttribArray(0);
692
	glEnableVertexAttribArray(1);
693
694
	noiseTextureA.bind();
695
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
696
	glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
697
	noiseTextureB.bind();
698
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
699
700
	glDisableVertexAttribArray(1);
701
	glDisableVertexAttribArray(0);
702
	glBlendEquation(GL_FUNC_ADD); // restore default
703
	glDisable(GL_BLEND);
704
}
705
706
static constexpr int GRID_SIZE = 16;
707
static constexpr int GRID_SIZE1 = GRID_SIZE + 1;
708
static constexpr int NUM_INDICES = (GRID_SIZE1 * 2 + 2) * GRID_SIZE - 2;
709
struct Vertex {
710
	vec3 position;
711
	vec3 normal;
712
	vec2 tex;
713
};
714
715
void PostProcessor::preCalcMonitor3D(float width)
716
{
717
	// precalculate vertex-positions, -normals and -texture-coordinates
718
	std::array<std::array<Vertex, GRID_SIZE1>, GRID_SIZE1> vertices;
719
720
	constexpr float GRID_SIZE2 = float(GRID_SIZE) * 0.5f;
721
	float s = width * (1.0f / 320.0f);
722
	float b = (320.0f - width) * (1.0f / (2.0f * 320.0f));
723
724
	for (auto sx : xrange(GRID_SIZE1)) {
725
		for (auto sy : xrange(GRID_SIZE1)) {
726
			Vertex& v = vertices[sx][sy];
727
			float x = (narrow<float>(sx) - GRID_SIZE2) / GRID_SIZE2;
728
			float y = (narrow<float>(sy) - GRID_SIZE2) / GRID_SIZE2;
729
730
			v.position = vec3(x, y, (x * x + y * y) * (1.0f / -12.0f));
731
			v.normal = normalize(vec3(x * (1.0f / 6.0f), y * (1.0f / 6.0f), 1.0f)) * 1.2f;
732
			v.tex = vec2((float(sx) / GRID_SIZE) * s + b,
733
			              float(sy) / GRID_SIZE);
734
		}
735
	}
736
737
	// calculate indices
738
	std::array<uint16_t, NUM_INDICES> indices;
739
740
	uint16_t* ind = indices.data();
741
	for (auto y : xrange(GRID_SIZE)) {
742
		for (auto x : xrange(GRID_SIZE1)) {
743
			*ind++ = narrow<uint16_t>((y + 0) * GRID_SIZE1 + x);
744
			*ind++ = narrow<uint16_t>((y + 1) * GRID_SIZE1 + x);
745
		}
746
		// skip 2, filled in later
747
		ind += 2;
748
	}
749
	assert((ind - indices.data()) == NUM_INDICES + 2);
750
	ind = indices.data();
751
	repeat(GRID_SIZE - 1, [&] {
752
		ind += 2 * GRID_SIZE1;
753
		// repeat prev and next index to restart strip
754
		ind[0] = ind[-1];
755
		ind[1] = ind[ 2];
756
		ind += 2;
757
	});
758
759
	// upload calculated values to buffers
760
	glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.get());
761
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices.data(),
762
	             GL_STATIC_DRAW);
763
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.get());
764
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices.data(),
765
	             GL_STATIC_DRAW);
766
	glBindBuffer(GL_ARRAY_BUFFER, 0);
767
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
768
769
	// calculate transformation matrices
770
	mat4 proj = frustum(-1, 1, -1, 1, 1, 10);
771
	mat4 tran = translate(vec3(0.0f, 0.4f, -2.0f));
772
	mat4 rotX = rotateX(radians(-10.0f));
773
	mat4 scal = scale(vec3(2.2f, 2.2f, 2.2f));
774
775
	mat3 normal(rotX);
776
	mat4 mvp = proj * tran * rotX * scal;
777
778
	// set uniforms
779
	monitor3DProg.activate();
780
	glUniform1i(monitor3DProg.getUniformLocation("u_tex"), 0);
781
	glUniformMatrix4fv(monitor3DProg.getUniformLocation("u_mvpMatrix"),
782
		1, GL_FALSE, mvp.data());
783
	glUniformMatrix3fv(monitor3DProg.getUniformLocation("u_normalMatrix"),
784
		1, GL_FALSE, normal.data());
785
}
786
787
void PostProcessor::drawMonitor3D() const
788
{
789
	monitor3DProg.activate();
790
791
	char* base = nullptr;
792
	glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.get());
793
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.get());
794
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
795
	                      base);
796
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
797
	                      base + sizeof(vec3));
798
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
799
	                      base + sizeof(vec3) + sizeof(vec3));
800
	glEnableVertexAttribArray(0);
801
	glEnableVertexAttribArray(1);
802
	glEnableVertexAttribArray(2);
803
804
	glDrawElements(GL_TRIANGLE_STRIP, NUM_INDICES, GL_UNSIGNED_SHORT, nullptr);
805
	glDisableVertexAttribArray(2);
806
	glDisableVertexAttribArray(1);
807
	glDisableVertexAttribArray(0);
808
809
	glBindBuffer(GL_ARRAY_BUFFER, 0);
810
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
811
}
812
813
} // namespace openmsx