openMSX
GLPostProcessor.cc
Go to the documentation of this file.
1 #include "GLPostProcessor.hh"
2 #include "GLScaler.hh"
3 #include "GLScalerFactory.hh"
4 #include "IntegerSetting.hh"
5 #include "FloatSetting.hh"
6 #include "EnumSetting.hh"
7 #include "OutputSurface.hh"
8 #include "RawFrame.hh"
9 #include "Math.hh"
10 #include "MemoryOps.hh"
11 #include "InitException.hh"
12 #include "memory.hh"
13 #include <algorithm>
14 #include <cassert>
15 
16 namespace openmsx {
17 
18 GLPostProcessor::TextureData::TextureData()
19 {
20 }
21 
22 GLPostProcessor::TextureData::TextureData(TextureData&& rhs)
23  : tex(std::move(rhs.tex))
24  , pbo(std::move(rhs.pbo))
25 {
26 }
27 
28 
30  MSXMotherBoard& motherBoard, Display& display,
31  OutputSurface& screen, const std::string& videoSource,
32  unsigned maxWidth, unsigned height_, bool canDoInterlace)
33  : PostProcessor(motherBoard, display, screen,
34  videoSource, maxWidth, height_, canDoInterlace)
35  , noiseTextureA(256, 256)
36  , noiseTextureB(256, 256)
37  , height(height_)
38 {
39  if (!GLEW_VERSION_2_0) {
40  throw InitException(
41  "Your video card (or less likely video card driver) "
42  "doesn't support OpenGL 2.0. It's required for the "
43  "SDLGL-PP renderer.");
44  }
45  if (!glewIsSupported("GL_EXT_framebuffer_object")) {
46  throw InitException(
47  "The OpenGL framebuffer object is not supported by "
48  "this glew library. Please upgrade your glew library.\n"
49  "It's also possible (but less likely) your video card "
50  "or video card driver doesn't support framebuffer "
51  "objects.");
52  }
53 
54  scaleAlgorithm = static_cast<RenderSettings::ScaleAlgorithm>(-1); // not a valid scaler
55 
56  frameCounter = 0;
57  noiseX = 0.0;
58  noiseY = 0.0;
59  preCalcNoise(renderSettings.getNoise().getValue());
60 
61  storedFrame = false;
62  for (int i = 0; i < 2; ++i) {
63  colorTex[i].bind();
64  colorTex[i].setWrapMode(false);
65  colorTex[i].enableInterpolation();
66  glTexImage2D(GL_TEXTURE_2D, // target
67  0, // level
68  GL_RGB8, // internal format
69  screen.getWidth(), // width
70  screen.getHeight(),// height
71  0, // border
72  GL_RGB, // format
73  GL_UNSIGNED_BYTE, // type
74  nullptr); // data
75  fbo[i] = FrameBufferObject(colorTex[i]);
76  }
77 
78  monitor3DList = glGenLists(1);
79  preCalc3DDisplayList(renderSettings.getHorizontalStretch().getValue());
80 
83 }
84 
86 {
89 
90  glDeleteLists(monitor3DList, 1);
91 }
92 
93 void GLPostProcessor::createRegions()
94 {
95  regions.clear();
96 
97  const unsigned srcHeight = paintFrame->getHeight();
98  const unsigned dstHeight = screen.getHeight();
99 
100  unsigned g = Math::gcd(srcHeight, dstHeight);
101  unsigned srcStep = srcHeight / g;
102  unsigned dstStep = dstHeight / g;
103 
104  // TODO: Store all MSX lines in RawFrame and only scale the ones that fit
105  // on the PC screen, as a preparation for resizable output window.
106  unsigned srcStartY = 0;
107  unsigned dstStartY = 0;
108  while (dstStartY < dstHeight) {
109  // Currently this is true because the source frame height
110  // is always >= dstHeight/(dstStep/srcStep).
111  assert(srcStartY < srcHeight);
112 
113  // get region with equal lineWidth
114  unsigned lineWidth = getLineWidth(paintFrame, srcStartY, srcStep);
115  unsigned srcEndY = srcStartY + srcStep;
116  unsigned dstEndY = dstStartY + dstStep;
117  while ((srcEndY < srcHeight) && (dstEndY < dstHeight) &&
118  (getLineWidth(paintFrame, srcEndY, srcStep) == lineWidth)) {
119  srcEndY += srcStep;
120  dstEndY += dstStep;
121  }
122 
123  regions.push_back(Region(srcStartY, srcEndY,
124  dstStartY, dstEndY,
125  lineWidth));
126 
127  // next region
128  srcStartY = srcEndY;
129  dstStartY = dstEndY;
130  }
131 }
132 
133 
135 {
138  double horStretch = renderSettings.getHorizontalStretch().getValue();
139  int glow = renderSettings.getGlow().getValue();
140  bool renderToTexture = (deform != RenderSettings::DEFORM_NORMAL) ||
141  (horStretch != 320.0) ||
142  (glow != 0);
143 
144  if ((deform == RenderSettings::DEFORM_3D) || !paintFrame) {
145  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
146  glClear(GL_COLOR_BUFFER_BIT);
147  if (!paintFrame) {
148  return;
149  }
150  }
151 
152  // New scaler algorithm selected?
155  if (scaleAlgorithm != algo) {
156  scaleAlgorithm = algo;
158 
159  // Re-upload frame data, this is both
160  // - Chunks of RawFrame with a specific linewidth, possibly
161  // with some extra lines above and below each chunk that are
162  // also converted to this linewidth.
163  // - Extra data that is specific for the scaler (ATM only the
164  // hq and hqlite scalers require this).
165  // Re-uploading the first is not strictly needed. But switching
166  // scalers doesn't happen that often, so it also doesn't hurt
167  // and it keeps the code simpler.
168  uploadFrame();
169  }
170 
171  if (renderToTexture) {
172  glViewport(0, 0, screen.getWidth(), screen.getHeight());
173  glBindTexture(GL_TEXTURE_2D, 0);
174  fbo[frameCounter & 1].push();
175  }
176 
177  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
178 
179  for (auto& r : regions) {
180  //fprintf(stderr, "post processing lines %d-%d: %d\n",
181  // r.srcStartY, r.srcEndY, r.lineWidth);
182  assert(textures.find(r.lineWidth) != textures.end());
183  auto superImpose = superImposeVideoFrame
184  ? &superImposeTex : nullptr;
185  currScaler->scaleImage(
186  textures[r.lineWidth].tex, superImpose,
187  r.srcStartY, r.srcEndY, r.lineWidth, // src
188  r.dstStartY, r.dstEndY, screen.getWidth(), // dst
189  paintFrame->getHeight()); // dst
190  //GLUtil::checkGLError("GLPostProcessor::paint");
191  }
192 
194 
195  drawNoise();
196  drawGlow(glow);
197 
198  if (renderToTexture) {
199  fbo[frameCounter & 1].pop();
200  colorTex[frameCounter & 1].bind();
201  glViewport(screen.getX(), screen.getY(),
203 
204  glEnable(GL_TEXTURE_2D);
205  if (deform == RenderSettings::DEFORM_3D) {
206  glCallList(monitor3DList);
207  } else {
208  glBegin(GL_QUADS);
209  int w = screen.getWidth();
210  int h = screen.getHeight();
211  GLfloat x1 = (320.0f - GLfloat(horStretch)) / (2.0f * 320.0f);
212  GLfloat x2 = 1.0f - x1;
213  glTexCoord2f(x1, 0.0f); glVertex2i(0, h);
214  glTexCoord2f(x1, 1.0f); glVertex2i(0, 0);
215  glTexCoord2f(x2, 1.0f); glVertex2i(w, 0);
216  glTexCoord2f(x2, 0.0f); glVertex2i(w, h);
217  glEnd();
218  }
219  glDisable(GL_TEXTURE_2D);
220  storedFrame = true;
221  } else {
222  storedFrame = false;
223  }
224 }
225 
226 std::unique_ptr<RawFrame> GLPostProcessor::rotateFrames(
227  std::unique_ptr<RawFrame> finishedFrame, FrameSource::FieldType field,
228  EmuTime::param time)
229 {
230  std::unique_ptr<RawFrame> reuseFrame =
231  PostProcessor::rotateFrames(std::move(finishedFrame), field, time);
232  uploadFrame();
233  ++frameCounter;
234  noiseX = double(rand()) / RAND_MAX;
235  noiseY = double(rand()) / RAND_MAX;
236  return reuseFrame;
237 }
238 
239 void GLPostProcessor::update(const Setting& setting)
240 {
241  VideoLayer::update(setting);
242  FloatSetting& noiseSetting = renderSettings.getNoise();
243  FloatSetting& horizontalStretch = renderSettings.getHorizontalStretch();
244  if (&setting == &noiseSetting) {
245  preCalcNoise(noiseSetting.getValue());
246  } else if (&setting == &horizontalStretch) {
247  preCalc3DDisplayList(horizontalStretch.getValue());
248  }
249 }
250 
251 void GLPostProcessor::uploadFrame()
252 {
253  createRegions();
254 
255  const unsigned srcHeight = paintFrame->getHeight();
256  for (auto& r : regions) {
257  // upload data
258  // TODO get before/after data from scaler
259  unsigned before = 1;
260  unsigned after = 1;
261  uploadBlock(std::max<int>(0, r.srcStartY - before),
262  std::min<int>(srcHeight, r.srcEndY + after),
263  r.lineWidth);
264  }
265 
266  if (superImposeVideoFrame) {
267  int width = superImposeVideoFrame->getWidth();
268  int height = superImposeVideoFrame->getHeight();
269  if (superImposeTex.getWidth() != width ||
270  superImposeTex.getHeight() != height) {
271  superImposeTex.resize(width, height);
272  superImposeTex.enableInterpolation();
273  }
274  superImposeTex.bind();
275  glTexSubImage2D(
276  GL_TEXTURE_2D, // target
277  0, // level
278  0, // offset x
279  0, // offset y
280  width, // width
281  height, // height
282  GL_BGRA, // format
283  GL_UNSIGNED_BYTE, // type
284  const_cast<RawFrame*>(superImposeVideoFrame)->getLinePtrDirect<unsigned>(0)); // data
285  }
286 }
287 
288 void GLPostProcessor::uploadBlock(
289  unsigned srcStartY, unsigned srcEndY, unsigned lineWidth)
290 {
291  // create texture/pbo if needed
292  auto it = textures.find(lineWidth);
293  if (it == textures.end()) {
294  TextureData textureData;
295 
296  textureData.tex = ColorTexture(
297  lineWidth, height * 2); // *2 for interlace
298  textureData.tex.setWrapMode(false);
299 
300  if (textureData.pbo.openGLSupported()) {
301  textureData.pbo.setImage(lineWidth, height * 2);
302  }
303 
304  it = textures.insert(std::make_pair(
305  lineWidth, std::move(textureData))).first;
306  }
307  auto& tex = it->second.tex;
308  auto& pbo = it->second.pbo;
309 
310  // bind texture
311  tex.bind();
312 
313  // upload data
314  unsigned* mapped;
315  if (pbo.openGLSupported()) {
316  pbo.bind();
317  mapped = pbo.mapWrite();
318  } else {
319  mapped = nullptr;
320  }
321  if (mapped) {
322  for (unsigned y = srcStartY; y < srcEndY; ++y) {
323  const unsigned* data =
324  paintFrame->getLinePtr<unsigned>(y, lineWidth);
326  mapped + y * lineWidth, data, lineWidth);
327  paintFrame->freeLineBuffers(); // ASAP to keep cache warm
328  }
329  pbo.unmap();
330 #if defined(__APPLE__)
331  // The nVidia GL driver for the GeForce 8000/9000 series seems to hang
332  // on texture data replacements that are 1 pixel wide and start on a
333  // line number that is a non-zero multiple of 16.
334  if (lineWidth == 1 && srcStartY != 0 && srcStartY % 16 == 0) {
335  srcStartY--;
336  }
337 #endif
338  glTexSubImage2D(
339  GL_TEXTURE_2D, // target
340  0, // level
341  0, // offset x
342  srcStartY, // offset y
343  lineWidth, // width
344  srcEndY - srcStartY, // height
345  GL_BGRA, // format
346  GL_UNSIGNED_BYTE, // type
347  pbo.getOffset(0, srcStartY)); // data
348  }
349  if (pbo.openGLSupported()) {
350  pbo.unbind();
351  }
352  if (!mapped) {
353  glPixelStorei(GL_UNPACK_ROW_LENGTH, paintFrame->getRowLength());
354  unsigned y = srcStartY;
355  unsigned remainingLines = srcEndY - srcStartY;
356  while (remainingLines) {
357  unsigned lines;
358  const unsigned* data = paintFrame->getMultiLinePtr<unsigned>(
359  y, remainingLines, lines, lineWidth);
360  glTexSubImage2D(
361  GL_TEXTURE_2D, // target
362  0, // level
363  0, // offset x
364  y, // offset y
365  lineWidth, // width
366  lines, // height
367  GL_BGRA, // format
368  GL_UNSIGNED_BYTE, // type
369  data); // data
370  paintFrame->freeLineBuffers(); // ASAP to keep cache warm
371 
372  y += lines;
373  remainingLines -= lines;
374  }
375  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); // restore default
376  }
377 
378  // possibly upload scaler specific data
379  if (currScaler.get()) {
380  currScaler->uploadBlock(srcStartY, srcEndY, lineWidth, *paintFrame);
381  }
382 }
383 
384 void GLPostProcessor::drawGlow(int glow)
385 {
386  if ((glow == 0) || !storedFrame) return;
387 
388  colorTex[(frameCounter & 1) ^ 1].bind();
389  glEnable(GL_TEXTURE_2D);
390  glEnable(GL_BLEND);
391  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
392  glBegin(GL_QUADS);
393  GLfloat alpha = glow * 31 / 3200.0f;
394  glColor4f(0.0f, 0.0f, 0.0f, alpha);
395  int w = screen.getWidth();
396  int h = screen.getHeight();
397  glTexCoord2i(0, 0); glVertex2i(0, h);
398  glTexCoord2i(0, 1); glVertex2i(0, 0);
399  glTexCoord2i(1, 1); glVertex2i(w, 0);
400  glTexCoord2i(1, 0); glVertex2i(w, h);
401  glEnd();
402  glDisable(GL_BLEND);
403  glDisable(GL_TEXTURE_2D);
404 }
405 
406 void GLPostProcessor::preCalcNoise(double factor)
407 {
408  GLbyte buf1[256 * 256];
409  GLbyte buf2[256 * 256];
410  for (int i = 0; i < 256 * 256; i += 2) {
411  double r1, r2;
412  Math::gaussian2(r1, r2);
413  int s1 = Math::clip<-255, 255>(r1, factor);
414  buf1[i + 0] = (s1 > 0) ? s1 : 0;
415  buf2[i + 0] = (s1 < 0) ? -s1 : 0;
416  int s2 = Math::clip<-255, 255>(r2, factor);
417  buf1[i + 1] = (s2 > 0) ? s2 : 0;
418  buf2[i + 1] = (s2 < 0) ? -s2 : 0;
419  }
420  noiseTextureA.updateImage(0, 0, 256, 256, buf1);
421  noiseTextureB.updateImage(0, 0, 256, 256, buf2);
422 }
423 
424 void GLPostProcessor::drawNoise()
425 {
426  if (renderSettings.getNoise().getValue() == 0) return;
427 
428  // Rotate and mirror noise texture in consecutive frames to avoid
429  // seeing 'patterns' in the noise.
430  static const int coord[8][4][2] = {
431  { { 0, 0 }, { 320, 0 }, { 320, 240 }, { 0, 240 } },
432  { { 0, 240 }, { 320, 240 }, { 320, 0 }, { 0, 0 } },
433  { { 0, 240 }, { 0, 0 }, { 320, 0 }, { 320, 240 } },
434  { { 320, 240 }, { 320, 0 }, { 0, 0 }, { 0, 240 } },
435  { { 320, 240 }, { 0, 240 }, { 0, 0 }, { 320, 0 } },
436  { { 320, 0 }, { 0, 0 }, { 0, 240 }, { 320, 240 } },
437  { { 320, 0 }, { 320, 240 }, { 0, 240 }, { 0, 0 } },
438  { { 0, 0 }, { 0, 240 }, { 320, 240 }, { 320, 0 } }
439  };
440  int zoom = renderSettings.getScaleFactor().getValue();
441 
442  unsigned seq = frameCounter & 7;
443  glPushAttrib(GL_ALL_ATTRIB_BITS);
444  glEnable(GL_BLEND);
445  glBlendFunc(GL_ONE, GL_ONE);
446  glEnable(GL_TEXTURE_2D);
447  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
448  noiseTextureA.bind();
449  glBegin(GL_QUADS);
450  glTexCoord2f(0.0f + GLfloat(noiseX), 1.875f + GLfloat(noiseY));
451  glVertex2i(coord[seq][0][0] * zoom, coord[seq][0][1] * zoom);
452  glTexCoord2f(2.5f + GLfloat(noiseX), 1.875f + GLfloat(noiseY));
453  glVertex2i(coord[seq][1][0] * zoom, coord[seq][1][1] * zoom);
454  glTexCoord2f(2.5f + GLfloat(noiseX), 0.000f + GLfloat(noiseY));
455  glVertex2i(coord[seq][2][0] * zoom, coord[seq][2][1] * zoom);
456  glTexCoord2f(0.0f + GLfloat(noiseX), 0.000f + GLfloat(noiseY));
457  glVertex2i(coord[seq][3][0] * zoom, coord[seq][3][1] * zoom);
458  glEnd();
459  // Note: If glBlendEquation is not present, the second noise texture will
460  // be added instead of subtracted, which means there will be no noise
461  // on white pixels. A pity, but it's better than no noise at all.
462  if (glBlendEquation) glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
463  noiseTextureB.bind();
464  glBegin(GL_QUADS);
465  glTexCoord2f(0.0f + GLfloat(noiseX), 1.875f + GLfloat(noiseY));
466  glVertex2i(coord[seq][0][0] * zoom, coord[seq][0][1] * zoom);
467  glTexCoord2f(2.5f + GLfloat(noiseX), 1.875f + GLfloat(noiseY));
468  glVertex2i(coord[seq][1][0] * zoom, coord[seq][1][1] * zoom);
469  glTexCoord2f(2.5f + GLfloat(noiseX), 0.000f + GLfloat(noiseY));
470  glVertex2i(coord[seq][2][0] * zoom, coord[seq][2][1] * zoom);
471  glTexCoord2f(0.0f + GLfloat(noiseX), 0.000f + GLfloat(noiseY));
472  glVertex2i(coord[seq][3][0] * zoom, coord[seq][3][1] * zoom);
473  glEnd();
474  glPopAttrib();
475  if (glBlendEquation) glBlendEquation(GL_FUNC_ADD);
476 }
477 
478 void GLPostProcessor::preCalc3DDisplayList(double width)
479 {
480  // generate display list for 3d deform
481  static const int GRID_SIZE = 16;
482  struct Point {
483  GLfloat vx, vy, vz;
484  GLfloat nx, ny, nz;
485  GLfloat tx, ty;
486  } points[GRID_SIZE + 1][GRID_SIZE + 1];
487  const int GRID_SIZE2 = GRID_SIZE / 2;
488  GLfloat s = GLfloat(width) / 320.0f;
489  GLfloat b = (320.0f - GLfloat(width)) / (2.0f * 320.0f);
490 
491  for (int sx = 0; sx <= GRID_SIZE; ++sx) {
492  for (int sy = 0; sy <= GRID_SIZE; ++sy) {
493  Point& p = points[sx][sy];
494  GLfloat x = GLfloat(sx - GRID_SIZE2) / GRID_SIZE2;
495  GLfloat y = GLfloat(sy - GRID_SIZE2) / GRID_SIZE2;
496 
497  p.vx = x;
498  p.vy = y;
499  p.vz = (x * x + y * y) / -12.0f;
500 
501  p.nx = x / 6.0f;
502  p.ny = y / 6.0f;
503  p.nz = 1.0f; // note: not normalized
504 
505  p.tx = (GLfloat(sx) / GRID_SIZE) * s + b;
506  p.ty = GLfloat(sy) / GRID_SIZE;
507  }
508  }
509 
510  GLfloat LightDiffuse[]= { 1.2f, 1.2f, 1.2f, 1.2f };
511  glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);
512  glEnable(GL_LIGHT0);
513  glEnable(GL_NORMALIZE);
514 
515  glNewList(monitor3DList, GL_COMPILE);
516  glEnable(GL_LIGHTING);
517  glMatrixMode(GL_PROJECTION);
518  glPushMatrix();
519  glLoadIdentity();
520  glFrustum(-1, 1, -1, 1, 1, 10);
521  glMatrixMode(GL_MODELVIEW);
522  glPushMatrix();
523  glLoadIdentity();
524  glTranslatef(0.0f, 0.4f, -2.0f);
525  glRotatef(-10.0f, 1.0f, 0.0f, 0.0f);
526  glScalef(2.2f, 2.2f, 2.2f);
527  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
528  for (int y = 0; y < GRID_SIZE; ++y) {
529  glBegin(GL_TRIANGLE_STRIP);
530  for (int x = 0; x < (GRID_SIZE + 1); ++x) {
531  Point& p1 = points[x][y + 0];
532  Point& p2 = points[x][y + 1];
533  glTexCoord2f(p1.tx, p1.ty);
534  glNormal3f (p1.nx, p1.ny, p1.nz);
535  glVertex3f (p1.vx, p1.vy, p1.vz);
536  glTexCoord2f(p2.tx, p2.ty);
537  glNormal3f (p2.nx, p2.ny, p2.nz);
538  glVertex3f (p2.vx, p2.vy, p2.vz);
539  }
540  glEnd();
541  }
542  glMatrixMode(GL_PROJECTION);
543  glPopMatrix();
544  glMatrixMode(GL_MODELVIEW);
545  glPopMatrix();
546  glDisable(GL_LIGHTING);
547  glEndList();
548 }
549 
550 } // namespace openmsx