PixelRenderer.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. /*
  2. TODO:
  3. - Implement blinking (of page mask) in bitmap modes.
  4. */
  5. #include "PixelRenderer.hh"
  6. #include "Rasterizer.hh"
  7. #include "PostProcessor.hh"
  8. #include "Display.hh"
  9. #include "VideoSystem.hh"
  10. #include "RenderSettings.hh"
  11. #include "VideoSourceSetting.hh"
  12. #include "IntegerSetting.hh"
  13. #include "VDP.hh"
  14. #include "VDPVRAM.hh"
  15. #include "SpriteChecker.hh"
  16. #include "EventDistributor.hh"
  17. #include "FinishFrameEvent.hh"
  18. #include "RealTime.hh"
  19. #include "SpeedManager.hh"
  20. #include "ThrottleManager.hh"
  21. #include "GlobalSettings.hh"
  22. #include "MSXMotherBoard.hh"
  23. #include "Reactor.hh"
  24. #include "Timer.hh"
  25. #include "one_of.hh"
  26. #include "unreachable.hh"
  27. #include <algorithm>
  28. #include <cassert>
  29. #include <cmath>
  30. namespace openmsx {
  31. void PixelRenderer::draw(
  32. int startX, int startY, int endX, int endY, DrawType drawType, bool atEnd)
  33. {
  34. if (drawType == DRAW_BORDER) {
  35. rasterizer->drawBorder(startX, startY, endX, endY);
  36. } else {
  37. assert(drawType == DRAW_DISPLAY);
  38. // Calculate display coordinates.
  39. int zero = vdp.getLineZero();
  40. int displayX = (startX - vdp.getLeftSprites()) / 2;
  41. int displayY = startY - zero;
  42. if (!vdp.getDisplayMode().isTextMode()) {
  43. displayY += vdp.getVerticalScroll();
  44. } else {
  45. // this is not what the real VDP does, but it is good
  46. // enough for "Boring scroll" demo part of "Relax"
  47. displayY = (displayY & 7) | (textModeCounter * 8);
  48. if (atEnd && (drawType == DRAW_DISPLAY)) {
  49. int low = std::max(0, (startY - zero)) / 8;
  50. int high = std::max(0, (endY - zero)) / 8;
  51. textModeCounter += (high - low);
  52. }
  53. }
  54. displayY &= 255; // Page wrap.
  55. int displayWidth = (endX - (startX & ~1)) / 2;
  56. int displayHeight = endY - startY;
  57. assert(0 <= displayX);
  58. assert(displayX + displayWidth <= 512);
  59. rasterizer->drawDisplay(
  60. startX, startY,
  61. displayX - vdp.getHorizontalScrollLow() * 2, displayY,
  62. displayWidth, displayHeight
  63. );
  64. if (vdp.spritesEnabled() && !renderSettings.getDisableSprites()) {
  65. rasterizer->drawSprites(
  66. startX, startY,
  67. displayX / 2, displayY,
  68. (displayWidth + 1) / 2, displayHeight);
  69. }
  70. }
  71. }
  72. void PixelRenderer::subdivide(
  73. int startX, int startY, int endX, int endY, int clipL, int clipR,
  74. DrawType drawType )
  75. {
  76. // Partial first line.
  77. if (startX > clipL) {
  78. bool atEnd = (startY != endY) || (endX >= clipR);
  79. if (startX < clipR) {
  80. draw(startX, startY, (atEnd ? clipR : endX),
  81. startY + 1, drawType, atEnd);
  82. }
  83. if (startY == endY) return;
  84. startY++;
  85. }
  86. // Partial last line.
  87. bool drawLast = false;
  88. if (endX >= clipR) {
  89. endY++;
  90. } else if (endX > clipL) {
  91. drawLast = true;
  92. }
  93. // Full middle lines.
  94. if (startY < endY) {
  95. draw(clipL, startY, clipR, endY, drawType, true);
  96. }
  97. // Actually draw last line if necessary.
  98. // The point of keeping top-to-bottom draw order is that it increases
  99. // the locality of memory references, which generally improves cache
  100. // hit rates.
  101. if (drawLast) draw(clipL, endY, endX, endY + 1, drawType, false);
  102. }
  103. PixelRenderer::PixelRenderer(VDP& vdp_, Display& display)
  104. : vdp(vdp_), vram(vdp.getVRAM())
  105. , eventDistributor(vdp.getReactor().getEventDistributor())
  106. , realTime(vdp.getMotherBoard().getRealTime())
  107. , speedManager(
  108. vdp.getReactor().getGlobalSettings().getSpeedManager())
  109. , throttleManager(
  110. vdp.getReactor().getGlobalSettings().getThrottleManager())
  111. , renderSettings(display.getRenderSettings())
  112. , videoSourceSetting(vdp.getMotherBoard().getVideoSource())
  113. , spriteChecker(vdp.getSpriteChecker())
  114. , rasterizer(display.getVideoSystem().createRasterizer(vdp))
  115. {
  116. // In case of loadstate we can't yet query any state from the VDP
  117. // (because that object is not yet fully deserialized). But
  118. // VDP::serialize() will call Renderer::reInit() again when it is
  119. // safe to query.
  120. reInit();
  121. finishFrameDuration = 0;
  122. frameSkipCounter = 999; // force drawing of frame
  123. prevRenderFrame = false;
  124. renderSettings.getMaxFrameSkipSetting().attach(*this);
  125. renderSettings.getMinFrameSkipSetting().attach(*this);
  126. }
  127. PixelRenderer::~PixelRenderer()
  128. {
  129. renderSettings.getMinFrameSkipSetting().detach(*this);
  130. renderSettings.getMaxFrameSkipSetting().detach(*this);
  131. }
  132. PostProcessor* PixelRenderer::getPostProcessor() const
  133. {
  134. return rasterizer->getPostProcessor();
  135. }
  136. void PixelRenderer::reInit()
  137. {
  138. // Don't draw before frameStart() is called.
  139. // This for example can happen after a loadstate or after switching
  140. // renderer in the middle of a frame.
  141. renderFrame = false;
  142. rasterizer->reset();
  143. displayEnabled = vdp.isDisplayEnabled();
  144. }
  145. void PixelRenderer::updateDisplayEnabled(bool enabled, EmuTime::param time)
  146. {
  147. sync(time, true);
  148. displayEnabled = enabled;
  149. }
  150. void PixelRenderer::frameStart(EmuTime::param time)
  151. {
  152. if (!rasterizer->isActive()) {
  153. frameSkipCounter = 999;
  154. renderFrame = false;
  155. prevRenderFrame = false;
  156. paintFrame = false;
  157. return;
  158. }
  159. prevRenderFrame = renderFrame;
  160. if (vdp.isInterlaced() && renderSettings.getDeinterlace()
  161. && vdp.getEvenOdd() && vdp.isEvenOddEnabled()) {
  162. // Deinterlaced odd frame: do same as even frame.
  163. paintFrame = prevRenderFrame;
  164. } else if (throttleManager.isThrottled()) {
  165. // Note: min/maxFrameSkip control the number of skipped frames, but
  166. // for every series of skipped frames there is also one painted
  167. // frame, so our boundary checks are offset by one.
  168. int counter = int(frameSkipCounter);
  169. if (counter < renderSettings.getMinFrameSkip()) {
  170. paintFrame = false;
  171. } else if (counter > renderSettings.getMaxFrameSkip()) {
  172. paintFrame = true;
  173. } else {
  174. paintFrame = realTime.timeLeft(
  175. unsigned(finishFrameDuration), time);
  176. }
  177. frameSkipCounter += 1.0f / float(speedManager.getSpeed());
  178. } else {
  179. // We need to render a frame every now and then,
  180. // to show the user what is happening.
  181. paintFrame = (Timer::getTime() - lastPaintTime) >= 100000; // 10 fps
  182. }
  183. if (paintFrame) {
  184. frameSkipCounter = std::remainder(frameSkipCounter, 1.0f);
  185. } else if (!rasterizer->isRecording()) {
  186. renderFrame = false;
  187. return;
  188. }
  189. renderFrame = true;
  190. rasterizer->frameStart(time);
  191. accuracy = renderSettings.getAccuracy();
  192. nextX = 0;
  193. nextY = 0;
  194. // This is not what the real VDP does, but it is good enough
  195. // for the "Boring scroll" demo part of ANMA's "Relax" demo.
  196. textModeCounter = 0;
  197. }
  198. void PixelRenderer::frameEnd(EmuTime::param time)
  199. {
  200. if (renderFrame) {
  201. // Render changes from this last frame.
  202. sync(time, true);
  203. // Let underlying graphics system finish rendering this frame.
  204. auto time1 = Timer::getTime();
  205. rasterizer->frameEnd();
  206. auto time2 = Timer::getTime();
  207. auto current = time2 - time1;
  208. const float ALPHA = 0.2f;
  209. finishFrameDuration = finishFrameDuration * (1 - ALPHA) +
  210. current * ALPHA;
  211. if (vdp.isInterlaced() && vdp.isEvenOddEnabled()
  212. && renderSettings.getDeinterlace() && !prevRenderFrame) {
  213. // Don't paint in deinterlace mode when previous frame
  214. // was not rendered.
  215. paintFrame = false;
  216. }
  217. if (paintFrame) {
  218. lastPaintTime = time2;
  219. }
  220. }
  221. if (vdp.getMotherBoard().isActive() &&
  222. !vdp.getMotherBoard().isFastForwarding()) {
  223. eventDistributor.distributeEvent(
  224. std::make_shared<FinishFrameEvent>(
  225. rasterizer->getPostProcessor()->getVideoSource(),
  226. videoSourceSetting.getSource(),
  227. !paintFrame));
  228. }
  229. }
  230. void PixelRenderer::updateHorizontalScrollLow(
  231. byte scroll, EmuTime::param time)
  232. {
  233. if (displayEnabled) sync(time);
  234. rasterizer->setHorizontalScrollLow(scroll);
  235. }
  236. void PixelRenderer::updateHorizontalScrollHigh(
  237. byte /*scroll*/, EmuTime::param time)
  238. {
  239. if (displayEnabled) sync(time);
  240. }
  241. void PixelRenderer::updateBorderMask(
  242. bool masked, EmuTime::param time)
  243. {
  244. if (displayEnabled) sync(time);
  245. rasterizer->setBorderMask(masked);
  246. }
  247. void PixelRenderer::updateMultiPage(
  248. bool /*multiPage*/, EmuTime::param time)
  249. {
  250. if (displayEnabled) sync(time);
  251. }
  252. void PixelRenderer::updateTransparency(
  253. bool enabled, EmuTime::param time)
  254. {
  255. if (displayEnabled) sync(time);
  256. rasterizer->setTransparency(enabled);
  257. }
  258. void PixelRenderer::updateSuperimposing(
  259. const RawFrame* videoSource, EmuTime::param time)
  260. {
  261. if (displayEnabled) sync(time);
  262. rasterizer->setSuperimposeVideoFrame(videoSource);
  263. }
  264. void PixelRenderer::updateForegroundColor(
  265. int /*color*/, EmuTime::param time)
  266. {
  267. if (displayEnabled) sync(time);
  268. }
  269. void PixelRenderer::updateBackgroundColor(
  270. int color, EmuTime::param time)
  271. {
  272. sync(time);
  273. rasterizer->setBackgroundColor(color);
  274. }
  275. void PixelRenderer::updateBlinkForegroundColor(
  276. int /*color*/, EmuTime::param time)
  277. {
  278. if (displayEnabled) sync(time);
  279. }
  280. void PixelRenderer::updateBlinkBackgroundColor(
  281. int /*color*/, EmuTime::param time)
  282. {
  283. if (displayEnabled) sync(time);
  284. }
  285. void PixelRenderer::updateBlinkState(
  286. bool /*enabled*/, EmuTime::param /*time*/)
  287. {
  288. // TODO: When the sync call is enabled, the screen flashes on
  289. // every call to this method.
  290. // I don't know why exactly, but it's probably related to
  291. // being called at frame start.
  292. //sync(time);
  293. }
  294. void PixelRenderer::updatePalette(
  295. int index, int grb, EmuTime::param time)
  296. {
  297. if (displayEnabled) {
  298. sync(time);
  299. } else {
  300. // Only sync if border color changed.
  301. DisplayMode mode = vdp.getDisplayMode();
  302. if (mode.getBase() == DisplayMode::GRAPHIC5) {
  303. int bgColor = vdp.getBackgroundColor();
  304. if (index == one_of(bgColor & 3, bgColor >> 2)) {
  305. sync(time);
  306. }
  307. } else if (mode.getByte() != DisplayMode::GRAPHIC7) {
  308. if (index == vdp.getBackgroundColor()) {
  309. sync(time);
  310. }
  311. }
  312. }
  313. rasterizer->setPalette(index, grb);
  314. }
  315. void PixelRenderer::updateVerticalScroll(
  316. int /*scroll*/, EmuTime::param time)
  317. {
  318. if (displayEnabled) sync(time);
  319. }
  320. void PixelRenderer::updateHorizontalAdjust(
  321. int adjust, EmuTime::param time)
  322. {
  323. if (displayEnabled) sync(time);
  324. rasterizer->setHorizontalAdjust(adjust);
  325. }
  326. void PixelRenderer::updateDisplayMode(
  327. DisplayMode mode, EmuTime::param time)
  328. {
  329. // Sync if in display area or if border drawing process changes.
  330. DisplayMode oldMode = vdp.getDisplayMode();
  331. if (displayEnabled
  332. || oldMode.getByte() == DisplayMode::GRAPHIC5
  333. || oldMode.getByte() == DisplayMode::GRAPHIC7
  334. || mode.getByte() == DisplayMode::GRAPHIC5
  335. || mode.getByte() == DisplayMode::GRAPHIC7) {
  336. sync(time, true);
  337. }
  338. rasterizer->setDisplayMode(mode);
  339. }
  340. void PixelRenderer::updateNameBase(
  341. int /*addr*/, EmuTime::param time)
  342. {
  343. if (displayEnabled) sync(time);
  344. }
  345. void PixelRenderer::updatePatternBase(
  346. int /*addr*/, EmuTime::param time)
  347. {
  348. if (displayEnabled) sync(time);
  349. }
  350. void PixelRenderer::updateColorBase(
  351. int /*addr*/, EmuTime::param time)
  352. {
  353. if (displayEnabled) sync(time);
  354. }
  355. void PixelRenderer::updateSpritesEnabled(
  356. bool /*enabled*/, EmuTime::param time
  357. ) {
  358. if (displayEnabled) sync(time);
  359. }
  360. static inline bool overlap(
  361. int displayY0, // start of display region, inclusive
  362. int displayY1, // end of display region, exclusive
  363. int vramLine0, // start of VRAM region, inclusive
  364. int vramLine1 // end of VRAM region, exclusive
  365. // Note: Display region can wrap around: 256 -> 0.
  366. // VRAM region cannot wrap around.
  367. ) {
  368. if (displayY0 <= displayY1) {
  369. if (vramLine1 > displayY0) {
  370. if (vramLine0 <= displayY1) return true;
  371. }
  372. } else {
  373. if (vramLine1 > displayY0) return true;
  374. if (vramLine0 <= displayY1) return true;
  375. }
  376. return false;
  377. }
  378. inline bool PixelRenderer::checkSync(int offset, EmuTime::param time)
  379. {
  380. // TODO: Because range is entire VRAM, offset == address.
  381. // If display is disabled, VRAM changes will not affect the
  382. // renderer output, therefore sync is not necessary.
  383. // TODO: Have bitmapVisibleWindow disabled in this case.
  384. if (!displayEnabled) return false;
  385. //if (frameSkipCounter != 0) return false; // TODO
  386. if (accuracy == RenderSettings::ACC_SCREEN) return false;
  387. // Calculate what display lines are scanned between current
  388. // renderer time and update-to time.
  389. // Note: displayY1 is inclusive.
  390. int deltaY = vdp.getVerticalScroll() - vdp.getLineZero();
  391. int limitY = vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE;
  392. int displayY0 = (nextY + deltaY) & 255;
  393. int displayY1 = (limitY + deltaY) & 255;
  394. switch(vdp.getDisplayMode().getBase()) {
  395. case DisplayMode::GRAPHIC2:
  396. case DisplayMode::GRAPHIC3:
  397. if (vram.colorTable.isInside(offset)) {
  398. int vramQuarter = (offset & 0x1800) >> 11;
  399. int mask = (vram.colorTable.getMask() & 0x1800) >> 11;
  400. for (int i = 0; i < 4; i++) {
  401. if ( (i & mask) == vramQuarter
  402. && overlap(displayY0, displayY1, i * 64, (i + 1) * 64) ) {
  403. /*fprintf(stderr,
  404. "color table: %05X %04X - quarter %d\n",
  405. offset, offset & 0x1FFF, i
  406. );*/
  407. return true;
  408. }
  409. }
  410. }
  411. if (vram.patternTable.isInside(offset)) {
  412. int vramQuarter = (offset & 0x1800) >> 11;
  413. int mask = (vram.patternTable.getMask() & 0x1800) >> 11;
  414. for (int i = 0; i < 4; i++) {
  415. if ( (i & mask) == vramQuarter
  416. && overlap(displayY0, displayY1, i * 64, (i + 1) * 64) ) {
  417. /*fprintf(stderr,
  418. "pattern table: %05X %04X - quarter %d\n",
  419. offset, offset & 0x1FFF, i
  420. );*/
  421. return true;
  422. }
  423. }
  424. }
  425. if (vram.nameTable.isInside(offset)) {
  426. int vramLine = ((offset & 0x3FF) / 32) * 8;
  427. if (overlap(displayY0, displayY1, vramLine, vramLine + 8)) {
  428. /*fprintf(stderr,
  429. "name table: %05X %03X - line %d\n",
  430. offset, offset & 0x3FF, vramLine
  431. );*/
  432. return true;
  433. }
  434. }
  435. return false;
  436. case DisplayMode::GRAPHIC4:
  437. case DisplayMode::GRAPHIC5: {
  438. if (vdp.isFastBlinkEnabled()) {
  439. // TODO could be improved
  440. return true;
  441. }
  442. // Is the address inside the visual page(s)?
  443. // TODO: Also look at which lines are touched inside pages.
  444. int visiblePage = vram.nameTable.getMask()
  445. & (0x10000 | (vdp.getEvenOddMask() << 7));
  446. if (vdp.isMultiPageScrolling()) {
  447. return (offset & 0x18000) == visiblePage
  448. || (offset & 0x18000) == (visiblePage & 0x10000);
  449. } else {
  450. return (offset & 0x18000) == visiblePage;
  451. }
  452. }
  453. case DisplayMode::GRAPHIC6:
  454. case DisplayMode::GRAPHIC7:
  455. return true; // TODO: Implement better detection.
  456. default:
  457. // Range unknown; assume full range.
  458. return vram.nameTable.isInside(offset)
  459. || vram.colorTable.isInside(offset)
  460. || vram.patternTable.isInside(offset);
  461. }
  462. }
  463. void PixelRenderer::updateVRAM(unsigned offset, EmuTime::param time)
  464. {
  465. // Note: No need to sync if display is disabled, because then the
  466. // output does not depend on VRAM (only on background color).
  467. if (renderFrame && displayEnabled && checkSync(offset, time)) {
  468. //fprintf(stderr, "vram sync @ line %d\n",
  469. // vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE);
  470. renderUntil(time);
  471. }
  472. }
  473. void PixelRenderer::updateWindow(bool /*enabled*/, EmuTime::param /*time*/)
  474. {
  475. // The bitmapVisibleWindow has moved to a different area.
  476. // This update is redundant: Renderer will be notified in another way
  477. // as well (updateDisplayEnabled or updateNameBase, for example).
  478. // TODO: Can this be used as the main update method instead?
  479. }
  480. void PixelRenderer::sync(EmuTime::param time, bool force)
  481. {
  482. if (!renderFrame) return;
  483. // Synchronisation is done in two phases:
  484. // 1. update VRAM
  485. // 2. update other subsystems
  486. // Note that as part of step 1, type 2 updates can be triggered.
  487. // Executing step 2 takes care of the subsystem changes that occur
  488. // after the last VRAM update.
  489. // This scheme makes sure type 2 routines such as renderUntil and
  490. // checkUntil are not re-entered, which was causing major pain in
  491. // the past.
  492. // TODO: I wonder if it's possible to enforce this synchronisation
  493. // scheme at a higher level. Probably. But how...
  494. //if ((frameSkipCounter == 0) && TODO
  495. if (accuracy != RenderSettings::ACC_SCREEN || force) {
  496. vram.sync(time);
  497. renderUntil(time);
  498. }
  499. }
  500. void PixelRenderer::renderUntil(EmuTime::param time)
  501. {
  502. // Translate from time to pixel position.
  503. int limitTicks = vdp.getTicksThisFrame(time);
  504. assert(limitTicks <= vdp.getTicksPerFrame());
  505. int limitX, limitY;
  506. switch (accuracy) {
  507. case RenderSettings::ACC_PIXEL: {
  508. limitX = limitTicks % VDP::TICKS_PER_LINE;
  509. limitY = limitTicks / VDP::TICKS_PER_LINE;
  510. break;
  511. }
  512. case RenderSettings::ACC_LINE:
  513. case RenderSettings::ACC_SCREEN: {
  514. // Note: I'm not sure the rounding point is optimal.
  515. // It used to be based on the left margin, but that doesn't work
  516. // because the margin can change which leads to a line being
  517. // rendered even though the time doesn't advance.
  518. limitX = 0;
  519. limitY =
  520. (limitTicks + VDP::TICKS_PER_LINE - 400) / VDP::TICKS_PER_LINE;
  521. break;
  522. }
  523. default:
  524. UNREACHABLE;
  525. limitX = limitY = 0; // avoid warning
  526. }
  527. // Stop here if there is nothing to render.
  528. // This ensures that no pixels are rendered in a series of updates that
  529. // happen at exactly the same time; the VDP subsystem states may be
  530. // inconsistent until all updates are performed.
  531. // Also it is a small performance optimisation.
  532. if (limitX == nextX && limitY == nextY) return;
  533. if (displayEnabled) {
  534. if (vdp.spritesEnabled()) {
  535. // Update sprite checking, so that rasterizer can call getSprites.
  536. spriteChecker.checkUntil(time);
  537. }
  538. // Calculate start and end of borders in ticks since start of line.
  539. // The 0..7 extra horizontal scroll low pixels should be drawn in
  540. // border color. These will be drawn together with the border,
  541. // but sprites above these pixels are clipped at the actual border
  542. // rather than the end of the border colored area.
  543. // TODO: Move these calculations and getDisplayLeft() to VDP.
  544. int borderL = vdp.getLeftBorder();
  545. int displayL =
  546. vdp.isBorderMasked() ? borderL : vdp.getLeftBackground();
  547. int borderR = vdp.getRightBorder();
  548. // It's important that right border is drawn last (after left
  549. // border and display area). See comment in SDLRasterizer::drawBorder().
  550. // Left border.
  551. subdivide(nextX, nextY, limitX, limitY,
  552. 0, displayL, DRAW_BORDER);
  553. // Display area.
  554. subdivide(nextX, nextY, limitX, limitY,
  555. displayL, borderR, DRAW_DISPLAY);
  556. // Right border.
  557. subdivide(nextX, nextY, limitX, limitY,
  558. borderR, VDP::TICKS_PER_LINE, DRAW_BORDER);
  559. } else {
  560. subdivide(nextX, nextY, limitX, limitY,
  561. 0, VDP::TICKS_PER_LINE, DRAW_BORDER);
  562. }
  563. nextX = limitX;
  564. nextY = limitY;
  565. }
  566. void PixelRenderer::update(const Setting& setting)
  567. {
  568. assert(&setting == one_of(&renderSettings.getMinFrameSkipSetting(),
  569. &renderSettings.getMaxFrameSkipSetting()));
  570. (void)setting;
  571. // Force drawing of frame.
  572. frameSkipCounter = 999;
  573. }
  574. } // namespace openmsx