openMSX
SDLRasterizer.cc
Go to the documentation of this file.
1 #include "SDLRasterizer.hh"
2 #include "VDP.hh"
3 #include "VDPVRAM.hh"
4 #include "RawFrame.hh"
5 #include "CharacterConverter.hh"
6 #include "BitmapConverter.hh"
7 #include "SpriteConverter.hh"
8 #include "MSXMotherBoard.hh"
9 #include "Display.hh"
10 #include "Renderer.hh"
11 #include "RenderSettings.hh"
12 #include "PostProcessor.hh"
13 #include "FloatSetting.hh"
14 #include "StringSetting.hh"
15 #include "MemoryOps.hh"
16 #include "VisibleSurface.hh"
17 #include "memory.hh"
18 #include "build-info.hh"
19 #include "components.hh"
20 #include <algorithm>
21 #include <cassert>
22 
23 namespace openmsx {
24 
27 static const int TICKS_LEFT_BORDER = 100 + 102;
28 
33 static const int TICKS_VISIBLE_MIDDLE =
34  TICKS_LEFT_BORDER + (VDP::TICKS_PER_LINE - TICKS_LEFT_BORDER - 27) / 2;
35 
36 template <class Pixel>
37 inline int SDLRasterizer<Pixel>::translateX(int absoluteX, bool narrow)
38 {
39  int maxX = narrow ? 640 : 320;
40  if (absoluteX == VDP::TICKS_PER_LINE) return maxX;
41 
42  // Note: The ROUND_MASK forces the ticks to a pixel (2-tick) boundary.
43  // If this is not done, rounding errors will occur.
44  // This is especially tricky because division of a negative number
45  // is rounded towards zero instead of down.
46  const int ROUND_MASK = narrow ? ~1 : ~3;
47  int screenX =
48  ((absoluteX & ROUND_MASK) - (TICKS_VISIBLE_MIDDLE & ROUND_MASK))
49  / (narrow ? 2 : 4)
50  + maxX / 2;
51  return std::max(screenX, 0);
52 }
53 
54 template <class Pixel>
55 inline void SDLRasterizer<Pixel>::renderBitmapLine(Pixel* buf, unsigned vramLine)
56 {
57  if (vdp.getDisplayMode().isPlanar()) {
58  const byte* vramPtr0;
59  const byte* vramPtr1;
60  vram.bitmapCacheWindow.getReadAreaPlanar(
61  vramLine * 256, 256, vramPtr0, vramPtr1);
62  bitmapConverter->convertLinePlanar(buf, vramPtr0, vramPtr1);
63  } else {
64  const byte* vramPtr =
65  vram.bitmapCacheWindow.getReadArea(vramLine * 128, 128);
66  bitmapConverter->convertLine(buf, vramPtr);
67  }
68 }
69 
70 template <class Pixel>
72  VDP& vdp_, Display& display, VisibleSurface& screen_,
73  std::unique_ptr<PostProcessor> postProcessor_)
74  : vdp(vdp_), vram(vdp.getVRAM())
75  , screen(screen_)
76  , postProcessor(std::move(postProcessor_))
77  , workFrame(make_unique<RawFrame>(screen.getSDLFormat(), 640, 240))
78  , renderSettings(display.getRenderSettings())
79  , characterConverter(make_unique<CharacterConverter<Pixel>>(
80  vdp, palFg, palBg))
81  , bitmapConverter(make_unique<BitmapConverter<Pixel>>(
82  palFg, PALETTE256, V9958_COLORS))
83  , spriteConverter(make_unique<SpriteConverter<Pixel>>(
84  vdp.getSpriteChecker()))
85 {
86  // Init the palette.
87  precalcPalette();
88 
89  // Initialize palette (avoid UMR)
90  if (!vdp.isMSX1VDP()) {
91  for (int i = 0; i < 16; ++i) {
92  palFg[i] = palFg[i + 16] = palBg[i] =
93  V9938_COLORS[0][0][0];
94  }
95  }
96 
97  renderSettings.getGamma() .attach(*this);
98  renderSettings.getBrightness() .attach(*this);
99  renderSettings.getContrast() .attach(*this);
100  renderSettings.getColorMatrix().attach(*this);
101 }
102 
103 template <class Pixel>
105 {
106  renderSettings.getColorMatrix().detach(*this);
107  renderSettings.getGamma() .detach(*this);
108  renderSettings.getBrightness() .detach(*this);
109  renderSettings.getContrast() .detach(*this);
110 }
111 
112 template <class Pixel>
114 {
115  return postProcessor.get();
116 }
117 
118 template <class Pixel>
120 {
121  return postProcessor->needRender() &&
122  vdp.getMotherBoard().isActive() &&
123  !vdp.getMotherBoard().isFastForwarding();
124 }
125 
126 template <class Pixel>
128 {
129  // Init renderer state.
130  setDisplayMode(vdp.getDisplayMode());
131  spriteConverter->setTransparency(vdp.getTransparency());
132 
133  resetPalette();
134 }
135 
136 template <class Pixel>
138 {
139  if (!vdp.isMSX1VDP()) {
140  // Reset the palette.
141  for (int i = 0; i < 16; i++) {
142  setPalette(i, vdp.getPalette(i));
143  }
144  }
145 }
146 
147 template<class Pixel>
149 {
150  postProcessor->setSuperimposeVideoFrame(videoSource);
151  precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(),
152  videoSource, vdp.getBackgroundColor());
153 }
154 
155 template <class Pixel>
157 {
158  workFrame = postProcessor->rotateFrames(std::move(workFrame),
159  vdp.isInterlaced()
160  ? (vdp.getEvenOdd() ? FrameSource::FIELD_ODD : FrameSource::FIELD_EVEN)
162  time);
163 
164  // Calculate line to render at top of screen.
165  // Make sure the display area is centered.
166  // 240 - 212 = 28 lines available for top/bottom border; 14 each.
167  // NTSC: display at [32..244),
168  // PAL: display at [59..271).
169  lineRenderTop = vdp.isPalTiming() ? 59 - 14 : 32 - 14;
170 }
171 
172 template <class Pixel>
174 {
175  // Nothing to do.
176 }
177 
178 template <class Pixel>
180 {
181  if (mode.isBitmapMode()) {
182  bitmapConverter->setDisplayMode(mode);
183  } else {
184  characterConverter->setDisplayMode(mode);
185  }
186  precalcColorIndex0(mode, vdp.getTransparency(),
187  vdp.isSuperimposing(), vdp.getBackgroundColor());
188  spriteConverter->setDisplayMode(mode);
189  spriteConverter->setPalette(mode.getByte() == DisplayMode::GRAPHIC7
190  ? palGraphic7Sprites : palBg);
191 }
192 
193 template <class Pixel>
194 void SDLRasterizer<Pixel>::setPalette(int index, int grb)
195 {
196  // Update SDL colors in palette.
197  Pixel newColor = V9938_COLORS[(grb >> 4) & 7][grb >> 8][grb & 7];
198  palFg[index ] = newColor;
199  palFg[index + 16] = newColor;
200  palBg[index ] = newColor;
201  bitmapConverter->palette16Changed();
202 
203  precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(),
204  vdp.isSuperimposing(), vdp.getBackgroundColor());
205 }
206 
207 template <class Pixel>
209 {
210  precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(),
211  vdp.isSuperimposing(), index);
212 }
213 
214 template <class Pixel>
216 {
217  spriteConverter->setTransparency(enabled);
218  precalcColorIndex0(vdp.getDisplayMode(), enabled,
219  vdp.isSuperimposing(), vdp.getBackgroundColor());
220 }
221 
222 template <class Pixel>
224 {
225  if (vdp.isMSX1VDP()) {
226  // Fixed palette.
227  for (int i = 0; i < 16; i++) {
228  const byte* rgb = Renderer::TMS99X8A_PALETTE[i];
229  double dr = rgb[0] / 255.0;
230  double dg = rgb[1] / 255.0;
231  double db = rgb[2] / 255.0;
232  renderSettings.transformRGB(dr, dg, db);
233  palFg[i] = palFg[i + 16] = palBg[i] =
234  screen.mapKeyedRGB<Pixel>(dr, dg, db);
235  }
236  } else {
237  if (vdp.hasYJK()) {
238  // Precalculate palette for V9958 colors.
239  if (renderSettings.isColorMatrixIdentity()) {
240  // Most users use the "normal" monitor type; making this a
241  // special case speeds up palette precalculation a lot.
242  int intensity[32];
243  for (int i = 0; i < 32; i++) {
244  intensity[i] =
245  int(255 * renderSettings.transformComponent(i / 31.0));
246  }
247  for (int rgb = 0; rgb < (1 << 15); rgb++) {
248  V9958_COLORS[rgb] = screen.mapKeyedRGB<Pixel>(
249  intensity[(rgb >> 10) ],
250  intensity[(rgb >> 5) & 31],
251  intensity[ rgb & 31]);
252  }
253  } else {
254  for (int r5 = 0; r5 < 32; r5++) {
255  for (int g5 = 0; g5 < 32; g5++) {
256  for (int b5 = 0; b5 < 32; b5++) {
257  double dr = r5 / 31.0;
258  double dg = g5 / 31.0;
259  double db = b5 / 31.0;
260  renderSettings.transformRGB(dr, dg, db);
261  V9958_COLORS[(r5<<10) + (g5<<5) + b5] =
262  screen.mapKeyedRGB<Pixel>(dr, dg, db);
263  }
264  }
265  }
266  }
267  // Precalculate palette for V9938 colors.
268  // Based on comparing red and green gradients, using palette and
269  // YJK, in SCREEN11 on a real turbo R.
270  for (int r3 = 0; r3 < 8; r3++) {
271  int r5 = (r3 << 2) | (r3 >> 1);
272  for (int g3 = 0; g3 < 8; g3++) {
273  int g5 = (g3 << 2) | (g3 >> 1);
274  for (int b3 = 0; b3 < 8; b3++) {
275  int b5 = (b3 << 2) | (b3 >> 1);
276  V9938_COLORS[r3][g3][b3] =
277  V9958_COLORS[(r5<<10) + (g5<<5) + b5];
278  }
279  }
280  }
281  } else {
282  // Precalculate palette for V9938 colors.
283  if (renderSettings.isColorMatrixIdentity()) {
284  int intensity[8];
285  for (int i = 0; i < 8; i++) {
286  intensity[i] =
287  int(255 * renderSettings.transformComponent(i / 7.0));
288  }
289  for (int r3 = 0; r3 < 8; r3++) {
290  for (int g3 = 0; g3 < 8; g3++) {
291  for (int b3 = 0; b3 < 8; b3++) {
292  V9938_COLORS[r3][g3][b3] =
293  screen.mapKeyedRGB<Pixel>(
294  intensity[r3], intensity[g3], intensity[b3]
295  );
296  }
297  }
298  }
299  } else {
300  for (int r3 = 0; r3 < 8; r3++) {
301  for (int g3 = 0; g3 < 8; g3++) {
302  for (int b3 = 0; b3 < 8; b3++) {
303  double dr = r3 / 7.0;
304  double dg = g3 / 7.0;
305  double db = b3 / 7.0;
306  renderSettings.transformRGB(dr, dg, db);
307  V9938_COLORS[r3][g3][b3] =
308  screen.mapKeyedRGB<Pixel>(dr, dg, db);
309  }
310  }
311  }
312  }
313  }
314  // Precalculate Graphic 7 bitmap palette.
315  for (int i = 0; i < 256; i++) {
316  PALETTE256[i] = V9938_COLORS
317  [(i & 0x1C) >> 2]
318  [(i & 0xE0) >> 5]
319  [(i & 0x03) == 3 ? 7 : (i & 0x03) * 2];
320  }
321  // Precalculate Graphic 7 sprite palette.
322  for (int i = 0; i < 16; i++) {
324  palGraphic7Sprites[i] =
325  V9938_COLORS[(grb >> 4) & 7][grb >> 8][grb & 7];
326  }
327  }
328 }
329 
330 template <class Pixel>
331 void SDLRasterizer<Pixel>::precalcColorIndex0(DisplayMode mode,
332  bool transparency, const RawFrame* superimposing, byte bgcolorIndex)
333 {
334  // Graphic7 mode doesn't use transparency.
335  if (mode.getByte() == DisplayMode::GRAPHIC7) {
336  transparency = false;
337  }
338 
339  int tpIndex = transparency ? bgcolorIndex : 0;
340  if (mode.getBase() != DisplayMode::GRAPHIC5) {
341  Pixel c = (superimposing && (bgcolorIndex == 0))
342  ? screen.getKeyColor<Pixel>()
343  : palBg[tpIndex];
344 
345  if (palFg[0] != c) {
346  palFg[0] = c;
347  bitmapConverter->palette16Changed();
348  }
349  } else {
350  // TODO: superimposing
351  if ((palFg[ 0] != palBg[tpIndex >> 2]) ||
352  (palFg[16] != palBg[tpIndex & 3])) {
353  palFg[ 0] = palBg[tpIndex >> 2];
354  palFg[16] = palBg[tpIndex & 3];
355  bitmapConverter->palette16Changed();
356  }
357  }
358 }
359 
360 template <class Pixel>
362  int fromX, int fromY, int limitX, int limitY)
363 {
364  DisplayMode mode = vdp.getDisplayMode();
365  byte modeBase = mode.getBase();
366  int bgColor = vdp.getBackgroundColor();
367  Pixel border0, border1;
368  if (modeBase == DisplayMode::GRAPHIC5) {
369  // border in SCREEN6 has separate color for even and odd pixels.
370  // TODO odd/even swapped?
371  border0 = palBg[(bgColor & 0x0C) >> 2];
372  border1 = palBg[(bgColor & 0x03) >> 0];
373  } else if (modeBase == DisplayMode::GRAPHIC7) {
374  border0 = border1 = PALETTE256[bgColor];
375  } else {
376  if (!bgColor && vdp.isSuperimposing()) {
377  border0 = border1 = screen.getKeyColor<Pixel>();
378  } else {
379  border0 = border1 = palBg[bgColor];
380  }
381  }
382 
383  int startY = std::max(fromY - lineRenderTop, 0);
384  int endY = std::min(limitY - lineRenderTop, 240);
385  if ((fromX == 0) && (limitX == VDP::TICKS_PER_LINE) &&
386  (border0 == border1)) {
387  // complete lines, non striped
388  for (int y = startY; y < endY; y++) {
389  workFrame->setBlank(y, border0);
390  }
391  } else {
392  unsigned lineWidth = mode.getLineWidth();
393  unsigned x = translateX(fromX, (lineWidth == 512));
394  unsigned num = translateX(limitX, (lineWidth == 512)) - x;
395  unsigned width = (lineWidth == 512) ? 640 : 320;
397  for (int y = startY; y < endY; ++y) {
398  memset(workFrame->getLinePtrDirect<Pixel>(y) + x,
399  num, border0, border1);
400  workFrame->setLineWidth(y, width);
401  }
402  }
403 }
404 
405 template <class Pixel>
407  int /*fromX*/, int fromY,
408  int displayX, int displayY,
409  int displayWidth, int displayHeight)
410 {
411  DisplayMode mode = vdp.getDisplayMode();
412  unsigned lineWidth = mode.getLineWidth();
413  if (lineWidth == 256) {
414  int endX = displayX + displayWidth;
415  displayX /= 2;
416  displayWidth = endX / 2 - displayX;
417  }
418 
419  // Clip to screen area.
420  int screenLimitY = std::min(
421  fromY + displayHeight - lineRenderTop,
422  240);
423  int screenY = fromY - lineRenderTop;
424  if (screenY < 0) {
425  displayY -= screenY;
426  fromY = lineRenderTop;
427  screenY = 0;
428  }
429  displayHeight = screenLimitY - screenY;
430  if (displayHeight <= 0) return;
431 
432  int leftBackground =
433  translateX(vdp.getLeftBackground(), lineWidth == 512);
434  // TODO: Find out why this causes 1-pixel jitter:
435  //dest.x = translateX(fromX);
436  int hScroll =
437  mode.isTextMode()
438  ? 0
439  : 8 * (lineWidth / 256) * (vdp.getHorizontalScrollHigh() & 0x1F);
440 
441  // Page border is display X coordinate where to stop drawing current page.
442  // This is either the multi page split point, or the right edge of the
443  // rectangle to draw, whichever comes first.
444  // Note that it is possible for pageBorder to be to the left of displayX,
445  // in that case only the second page should be drawn.
446  int pageBorder = displayX + displayWidth;
447  int scrollPage1, scrollPage2;
448  if (vdp.isMultiPageScrolling()) {
449  scrollPage1 = vdp.getHorizontalScrollHigh() >> 5;
450  scrollPage2 = scrollPage1 ^ 1;
451  } else {
452  scrollPage1 = 0;
453  scrollPage2 = 0;
454  }
455  // Because SDL blits do not wrap, unlike GL textures, the pageBorder is
456  // also used if multi page is disabled.
457  int pageSplit = lineWidth - hScroll;
458  if (pageSplit < pageBorder) {
459  pageBorder = pageSplit;
460  }
461 
462  if (mode.isBitmapMode()) {
463  // Which bits in the name mask determine the page?
464  int pageMaskOdd = (mode.isPlanar() ? 0x000 : 0x200) |
465  vdp.getEvenOddMask();
466  int pageMaskEven = vdp.isMultiPageScrolling()
467  ? (pageMaskOdd & ~0x100)
468  : pageMaskOdd;
469 
470  for (int y = screenY; y < screenLimitY; y++) {
471  const int vramLine[2] = {
472  (vram.nameTable.getMask() >> 7) & (pageMaskEven | displayY),
473  (vram.nameTable.getMask() >> 7) & (pageMaskOdd | displayY)
474  };
475 
476  Pixel buf[512];
477  int lineInBuf = -1; // buffer data not valid
478  Pixel* dst = workFrame->getLinePtrDirect<Pixel>(y)
479  + leftBackground + displayX;
480  int firstPageWidth = pageBorder - displayX;
481  if (firstPageWidth > 0) {
482  if ((displayX + hScroll) == 0) {
483  renderBitmapLine(dst, vramLine[scrollPage1]);
484  } else {
485  lineInBuf = vramLine[scrollPage1];
486  renderBitmapLine(buf, vramLine[scrollPage1]);
487  const Pixel* src = buf + displayX + hScroll;
488  MemoryOps::stream_memcpy(dst, src, firstPageWidth);
489  }
490  } else {
491  firstPageWidth = 0;
492  }
493  if (firstPageWidth < displayWidth) {
494  if (lineInBuf != vramLine[scrollPage2]) {
495  renderBitmapLine(buf, vramLine[scrollPage2]);
496  }
497  unsigned x = displayX < pageBorder
498  ? 0 : displayX + hScroll - lineWidth;
500  dst + firstPageWidth,
501  buf + x,
502  displayWidth - firstPageWidth);
503  }
504  workFrame->setLineWidth(y, (lineWidth == 512) ? 640 : 320);
505 
506  displayY = (displayY + 1) & 255;
507  }
508  } else {
509  // horizontal scroll (high) is implemented in CharacterConverter
510  for (int y = screenY; y < screenLimitY; y++) {
511  assert(!vdp.isMSX1VDP() || displayY < 192);
512 
513  Pixel* dst = workFrame->getLinePtrDirect<Pixel>(y)
514  + leftBackground + displayX;
515  if (displayX == 0) {
516  characterConverter->convertLine(dst, displayY);
517  } else {
518  Pixel buf[512];
519  characterConverter->convertLine(buf, displayY);
520  const Pixel* src = buf + displayX;
521  MemoryOps::stream_memcpy(dst, src, displayWidth);
522  }
523 
524  workFrame->setLineWidth(y, (lineWidth == 512) ? 640 : 320);
525  displayY = (displayY + 1) & 255;
526  }
527  }
528 }
529 
530 template <class Pixel>
532  int /*fromX*/, int fromY,
533  int displayX, int displayY,
534  int displayWidth, int displayHeight)
535 {
536  // Clip to screen area.
537  // TODO: Code duplicated from drawDisplay.
538  int screenLimitY = std::min(
539  fromY + displayHeight - lineRenderTop,
540  240);
541  int screenY = fromY - lineRenderTop;
542  if (screenY < 0) {
543  displayY -= screenY;
544  fromY = lineRenderTop;
545  screenY = 0;
546  }
547  displayHeight = screenLimitY - screenY;
548  if (displayHeight <= 0) return;
549 
550  // Render sprites.
551  // TODO: Call different SpriteConverter methods depending on narrow/wide
552  // pixels in this display mode?
553  int spriteMode = vdp.getDisplayMode().getSpriteMode();
554  int displayLimitX = displayX + displayWidth;
555  int limitY = fromY + displayHeight;
556  int screenX = translateX(
557  vdp.getLeftSprites(),
558  vdp.getDisplayMode().getLineWidth() == 512);
559  if (spriteMode == 1) {
560  for (int y = fromY; y < limitY; y++, screenY++) {
561  Pixel* pixelPtr = workFrame->getLinePtrDirect<Pixel>(screenY) + screenX;
562  spriteConverter->drawMode1(y, displayX, displayLimitX, pixelPtr);
563  }
564  } else {
565  byte mode = vdp.getDisplayMode().getByte();
566  if (mode == DisplayMode::GRAPHIC5) {
567  for (int y = fromY; y < limitY; y++, screenY++) {
568  Pixel* pixelPtr = workFrame->getLinePtrDirect<Pixel>(screenY) + screenX;
569  spriteConverter->template drawMode2<DisplayMode::GRAPHIC5>(
570  y, displayX, displayLimitX, pixelPtr);
571  }
572  } else if (mode == DisplayMode::GRAPHIC6) {
573  for (int y = fromY; y < limitY; y++, screenY++) {
574  Pixel* pixelPtr = workFrame->getLinePtrDirect<Pixel>(screenY) + screenX;
575  spriteConverter->template drawMode2<DisplayMode::GRAPHIC6>(
576  y, displayX, displayLimitX, pixelPtr);
577  }
578  } else {
579  for (int y = fromY; y < limitY; y++, screenY++) {
580  Pixel* pixelPtr = workFrame->getLinePtrDirect<Pixel>(screenY) + screenX;
581  spriteConverter->template drawMode2<DisplayMode::GRAPHIC4>(
582  y, displayX, displayLimitX, pixelPtr);
583  }
584  }
585  }
586 }
587 
588 template <class Pixel>
590 {
591  return postProcessor->isRecording();
592 }
593 
594 template <class Pixel>
595 void SDLRasterizer<Pixel>::update(const Setting& setting)
596 {
597  if ((&setting == &renderSettings.getGamma()) ||
598  (&setting == &renderSettings.getBrightness()) ||
599  (&setting == &renderSettings.getContrast()) ||
600  (&setting == &renderSettings.getColorMatrix())) {
601  precalcPalette();
602  resetPalette();
603  }
604 }
605 
606 
607 // Force template instantiation.
608 #if HAVE_16BPP
609 template class SDLRasterizer<word>;
610 #endif
611 #if HAVE_32BPP || COMPONENT_GL
612 template class SDLRasterizer<unsigned>;
613 #endif
614 
615 } // namespace openmsx