12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250 |
- #ifndef VDP_HH
- #define VDP_HH
- #include "MSXDevice.hh"
- #include "Schedulable.hh"
- #include "VideoSystemChangeListener.hh"
- #include "SimpleDebuggable.hh"
- #include "TclCallback.hh"
- #include "InfoTopic.hh"
- #include "IRQHelper.hh"
- #include "Clock.hh"
- #include "DisplayMode.hh"
- #include "Observer.hh"
- #include "openmsx.hh"
- #include "outer.hh"
- #include <memory>
- #include <array>
- namespace openmsx {
- class PostProcessor;
- class Renderer;
- class VDPCmdEngine;
- class VDPVRAM;
- class MSXCPU;
- class SpriteChecker;
- class Display;
- class RawFrame;
- class Setting;
- template<typename> class EnumSetting;
- namespace VDPAccessSlots {
- enum Delta : int;
- class Calculator;
- }
- /** Unified implementation of MSX Video Display Processors (VDPs).
- * MSX1 VDP is Texas Instruments TMS9918A or TMS9928A.
- * MSX2 VDP is Yamaha V9938.
- * MSX2+ and turbo R VDP is Yamaha V9958.
- *
- * Current implementation is written by Maarten ter Huurne,
- * with some contributions from other openMSX developers.
- * The first implementation was based on Sean Young's code from MESS,
- * which was in turn based on code by Mike Balfour.
- * Integration of MESS code into openMSX was done by David Heremans.
- *
- * All undocumented features as described in the following file
- * should be emulated:
- * http://www.msxnet.org/tech/tms9918a.txt
- *
- * This class is the VDP core, it uses a separate Renderer to
- * convert the VRAM contents into pixels on the host machine.
- * Communicates with the Renderer through a pull variant of the
- * Observer pattern: a VDP object fires events when its state changes,
- * the Renderer can retrieve the new state if necessary by calling
- * methods on the VDP object.
- *
- * A note about timing: the start of a frame or line is defined as
- * the starting time of the corresponding sync (vsync, hsync).
- */
- class VDP final : public MSXDevice, private VideoSystemChangeListener
- , private Observer<Setting>
- {
- public:
- /** Number of VDP clock ticks per second.
- */
- static constexpr int TICKS_PER_SECOND = 3579545 * 6; // 21.5MHz;
- using VDPClock = Clock<TICKS_PER_SECOND>;
- /** Number of VDP clock ticks per line.
- */
- static constexpr int TICKS_PER_LINE = 1368;
- explicit VDP(const DeviceConfig& config);
- ~VDP() override;
- void powerUp(EmuTime::param time) override;
- void reset(EmuTime::param time) override;
- byte readIO(word port, EmuTime::param time) override;
- byte peekIO(word port, EmuTime::param time) const override;
- void writeIO(word port, byte value, EmuTime::param time) override;
- /** Used by Video9000 to be able to couple the VDP and V9990 output.
- * Can return nullptr in case of renderer=none. This value can change
- * over the lifetime of the VDP object (on renderer switch).
- */
- PostProcessor* getPostProcessor() const;
- /** Is this an MSX1 VDP?
- * @return True if this is an MSX1 VDP
- * False otherwise.
- */
- inline bool isMSX1VDP() const {
- return (version & VM_MSX1) != 0;
- }
- /** Is this a VDP only capable of PAL?
- * @return True iff this is a PAL only VDP
- */
- inline bool isVDPwithPALonly() const {
- return (version & VM_PAL) != 0;
- }
- /** Is this a VDP that lacks mirroring?
- * @return True iff this VDP lacks the screen 2 mirrored mode
- */
- inline bool vdpLacksMirroring() const {
- return (version & VM_NO_MIRRORING) != 0;
- }
- /** Is this a VDP that has pattern/colortable mirroring?
- * @return True iff this VDP has pattern/colortable mirroring
- */
- inline bool vdpHasPatColMirroring() const {
- return (version & VM_PALCOL_MIRRORING) != 0;
- }
- /** Does this VDP have VRAM remapping when switching from 4k to 8/16k mode?
- * @return True iff this is a VDP with VRAM remapping
- */
- inline bool isVDPwithVRAMremapping() const {
- return (version & VM_VRAM_REMAPPING) != 0;
- }
- /** Does this VDP support YJK display?
- * @return True for V9958, false otherwise.
- */
- inline bool hasYJK() const {
- return (version & VM_YJK) != 0;
- }
- /** Get the (fixed) palette for this MSX1 VDP.
- * Don't use this if it's not an MSX1 VDP!
- * @return an array of 16 RGB triplets
- */
- std::array<std::array<uint8_t, 3>, 16> getMSX1Palette() const;
- /** Get the display mode the VDP is in.
- * @return The current display mode.
- */
- inline DisplayMode getDisplayMode() const {
- return displayMode;
- }
- /** Get the VRAM object for this VDP.
- */
- inline VDPVRAM& getVRAM() {
- return *vram;
- }
- /** Are we currently superimposing?
- * In case of superimpose, returns a pointer to the to-be-superimposed
- * frame. Returns nullptr if superimpose is not active.
- */
- inline const RawFrame* isSuperimposing() const {
- // Note that bit 0 of r#0 has no effect on an V9938 or higher,
- // but this bit is masked out. Also note that on an MSX1, if
- // bit 0 of r#0 is enabled and there is no external video
- // source, then we lose sync.
- // Also note that because this property is fixed per frame we
- // cannot (re)calculate it from register values.
- return superimposing;
- }
- /** Get the sprite checker for this VDP.
- */
- inline SpriteChecker& getSpriteChecker() {
- return *spriteChecker;
- }
- /** Gets the current transparency setting.
- * @return True iff color 0 is transparent.
- */
- inline bool getTransparency() const {
- return (controlRegs[8] & 0x20) == 0;
- }
- /** Can a sprite which has color=0 collide with some other sprite?
- */
- bool canSpriteColor0Collide() const {
- // On MSX1 (so far only tested a TMS9129(?)) sprites with
- // color=0 can always collide with other sprites. Though on
- // V99x8 (only tested V9958) collisions only occur when color 0
- // is not transparent. For more details see:
- // https://github.com/openMSX/openMSX/issues/1198
- return isMSX1VDP() || !getTransparency();
- }
- /** Gets the current foreground color.
- * @return Color index [0..15].
- */
- inline int getForegroundColor() const {
- return controlRegs[7] >> 4;
- }
- /** Gets the current background color.
- * @return Color index.
- * In Graphic5 mode, the range is [0..15];
- * bits 3-2 contains the color for even pixels,
- * bits 1-0 contains the color for odd pixels.
- * In Graphic7 mode with YJK off, the range is [0..255].
- * In other modes, the range is [0..15].
- */
- inline int getBackgroundColor() const {
- byte reg7 = controlRegs[7];
- if (displayMode.getByte() == DisplayMode::GRAPHIC7) {
- return reg7;
- } else {
- return reg7 & 0x0F;
- }
- }
- /** Gets the current blinking color for blinking text.
- * @return Color index [0..15].
- */
- inline int getBlinkForegroundColor() const {
- return controlRegs[12] >> 4;
- }
- /** Gets the current blinking color for blinking text.
- * @return Color index [0..15].
- */
- inline int getBlinkBackgroundColor() const {
- return controlRegs[12] & 0x0F;
- }
- /** Gets the current blink state.
- * @return True iff alternate colors / page should be displayed.
- */
- inline bool getBlinkState() const {
- return blinkState;
- }
- /** Gets a palette entry.
- * @param index The index [0..15] in the palette.
- * @return Color value in the format of the palette registers:
- * bit 10..8 is green, bit 6..4 is red and bit 2..0 is blue.
- */
- inline word getPalette(int index) const {
- return palette[index];
- }
- /** Is the display enabled?
- * Both the regular border and forced blanking by clearing
- * the display enable bit are considered disabled display.
- * @return true iff enabled.
- */
- inline bool isDisplayEnabled() const {
- return isDisplayArea && displayEnabled;
- }
- /** Are sprites enabled?
- * @return True iff blanking is off, the current mode supports
- * sprites and sprites are not disabled.
- */
- inline bool spritesEnabled() const {
- return displayEnabled &&
- (displayMode.getSpriteMode(isMSX1VDP()) != 0) &&
- ((controlRegs[8] & 0x02) == 0x00);
- }
- /** Same as spritesEnabled(), but may only be called in sprite
- * mode 1 or 2. Is a tiny bit faster.
- */
- inline bool spritesEnabledFast() const {
- assert(displayMode.getSpriteMode(isMSX1VDP()) != 0);
- return displayEnabled && ((controlRegs[8] & 0x02) == 0x00);
- }
- /** Still faster variant (just looks at the sprite-enabled-bit).
- * But only valid in sprite mode 1/2 with screen enabled.
- */
- inline bool spritesEnabledRegister() const {
- return (controlRegs[8] & 0x02) == 0x00;
- }
- /** Gets the current vertical scroll (line displayed at Y=0).
- * @return Vertical scroll register value.
- */
- inline byte getVerticalScroll() const {
- return controlRegs[23];
- }
- /** Gets the current horizontal scroll lower bits.
- * Rather than actually scrolling the screen, this setting shifts the
- * screen 0..7 bytes to the right.
- * @return Horizontal scroll low register value.
- */
- inline byte getHorizontalScrollLow() const {
- return controlRegs[27];
- }
- /** Gets the current horizontal scroll higher bits.
- * This value determines how many 8-pixel steps the background is
- * rotated to the left.
- * @return Horizontal scroll high register value.
- */
- inline byte getHorizontalScrollHigh() const {
- return controlRegs[26];
- }
- /** Gets the current border mask setting.
- * Border mask extends the left border by 8 pixels if enabled.
- * This is a V9958 feature, on older VDPs it always returns false.
- * @return true iff enabled.
- */
- inline bool isBorderMasked() const {
- return (controlRegs[25] & 0x02) != 0;
- }
- /** Is multi page scrolling enabled?
- * It is considered enabled if both the multi page scrolling flag is
- * enabled and the current page is odd. Scrolling wraps into the
- * lower even page.
- * @return true iff enabled.
- */
- inline bool isMultiPageScrolling() const {
- return (controlRegs[25] & 0x01) && (controlRegs[2] & 0x20);
- }
- /** Get the absolute line number of display line zero.
- * Usually this is equal to the height of the top border,
- * but not so during overscan.
- */
- inline int getLineZero() const {
- return displayStart / TICKS_PER_LINE;
- }
- /** Is PAL timing active?
- * This setting is fixed at start of frame.
- * @return True if PAL timing, false if NTSC timing.
- */
- inline bool isPalTiming() const {
- return palTiming;
- }
- /** Get interlace status.
- * Interlace means the odd fields are displayed half a line lower
- * than the even fields. Together with even/odd page alternation
- * this can be used to create an interlaced display.
- * This setting is fixed at start of frame.
- * @return True iff interlace is enabled.
- */
- inline bool isInterlaced() const {
- return interlaced;
- }
- /** Get 'fast-blink' status.
- * Normally blinking timing (alternating between pages) is based on
- * frames. Though the V99x8 has an undocumented feature which changes
- * this timing to lines. Sometimes this is called the "Cadari" feature
- * after Luciano Cadari who discovered it.
- *
- * See ticket#1091: "Support for undocumented V99x8 register 1 bit 2"
- * https://github.com/openMSX/openMSX/issues/1091
- * for testcases and links to more information.
- */
- inline bool isFastBlinkEnabled() const {
- return (controlRegs[1] & 4) != 0;
- }
- /** Get even/odd page alternation status.
- * Interlace means the odd fields are displayed half a line lower
- * than the even fields. Together with even/odd page alternation
- * this can be used to create an interlaced display.
- * This setting is NOT fixed at start of frame.
- * TODO: Find out how real VDP does it.
- * If it fixes it at start of frame, so should we.
- * If it handles it dynamically (my guess), then an update method
- * should be added on the Renderer interface.
- * @return True iff even/odd page alternation is enabled.
- */
- inline bool isEvenOddEnabled() const {
- if (isFastBlinkEnabled()) return false;
- return (controlRegs[9] & 4) != 0;
- }
- /** Is the even or odd field being displayed?
- * @return True iff this field should be displayed half a line lower.
- */
- inline bool getEvenOdd() const {
- return (statusReg2 & 2) != 0;
- }
- /** Expresses the state of even/odd page interchange in a mask
- * on the line number. If even/odd interchange is active, for some
- * frames lines 256..511 (page 1) are replaced by 0..255 (page 0)
- * and 768..1023 (page 3, if applicable) by 512..767 (page 2).
- * Together with the interlace setting this can be used to create
- * an interlaced display.
- * Even/odd interchange can also happen because of the 'blink'
- * feature in bitmap modes.
- * @pre !isFastBlinkEnabled()
- * @return Line number mask that expressed even/odd state.
- */
- inline int getEvenOddMask() const {
- // TODO: Verify which page is displayed on even fields.
- assert(!isFastBlinkEnabled());
- return (((~controlRegs[9] & 4) << 6) | ((statusReg2 & 2) << 7)) &
- (!blinkState << 8);
- }
- /** Similar to the above getEvenOddMask() method, but can also be
- * called when 'isFastBlinkEnabled() == true'. In the latter case
- * the timinig is based on lines instead of frames. This means the
- * result is no longer fixed per frame, and thus this method takes
- * an additional line parameter.
- */
- inline int getEvenOddMask(int line) const {
- if (isFastBlinkEnabled()) {
- // EO and IL not considered in this mode
- auto p = calculateLineBlinkState(line);
- return (!p.first) << 8;
- } else {
- return getEvenOddMask();
- }
- }
- /** Calculates what 'blinkState' and 'blinkCount' would be at a specific line.
- * (The actual 'blinkState' and 'blinkCount' variables represent the values
- * for line 0 and remain fixed for the duration of the frame.
- */
- inline std::pair<bool, int> calculateLineBlinkState(unsigned line) const {
- assert(isFastBlinkEnabled());
- if (blinkCount == 0) { // not changing
- return {blinkState, blinkCount};
- }
- unsigned evenLen = ((controlRegs[13] >> 4) & 0x0F) * 10;
- unsigned oddLen = ((controlRegs[13] >> 0) & 0x0F) * 10;
- unsigned totalLen = evenLen + oddLen;
- assert(totalLen != 0); // because this implies 'blinkCount == 0'
- line %= totalLen; // reduce double flips
- bool resultState = blinkState; // initial guess, adjusted later
- if (blinkState) {
- // We start in the 'even' period -> check first for
- // even/odd transition, next for odd/even
- } else {
- // We start in the 'odd' period -> do the opposite
- std::swap(evenLen, oddLen);
- }
- int newCount = blinkCount - line;
- if (newCount <= 0) {
- // switch even->odd (or odd->even)
- resultState = !resultState;
- newCount += oddLen;
- if (newCount <= 0) {
- // switch odd->even (or even->odd)
- resultState = !resultState;
- newCount += evenLen;
- assert(newCount > 0);
- }
- }
- return {resultState, newCount};
- }
- /** Gets the number of VDP clock ticks (21MHz) elapsed between
- * a given time and the start of this frame.
- */
- inline int getTicksThisFrame(EmuTime::param time) const {
- return frameStartTime.getTicksTill_fast(time);
- }
- inline EmuTime::param getFrameStartTime() const {
- return frameStartTime.getTime();
- }
- /** Gets the sprite size in pixels (8/16).
- */
- inline int getSpriteSize() const {
- return ((controlRegs[1] & 2) << 2) + 8;
- }
- /** Are sprites magnified?
- */
- inline bool isSpriteMag() const {
- return controlRegs[1] & 1;
- }
- /** Are commands possible in non Graphic modes? (V9958 only)
- * @return True iff CMD bit set.
- */
- inline bool getCmdBit() const {
- return (controlRegs[25] & 0x40) != 0;
- }
- /** Gets the number of lines per frame.
- */
- inline int getLinesPerFrame() const {
- return palTiming ? 313 : 262;
- }
- /** Gets the number of VDP clockticks (21MHz) per frame.
- */
- inline int getTicksPerFrame() const {
- return getLinesPerFrame() * TICKS_PER_LINE;
- }
- /** Is the given timestamp inside the current frame?
- * Mainly useful for debugging, because relevant timestamps should
- * always be inside the current frame.
- * The end time of a frame is still considered inside,
- * to support the case when the given timestamp is exclusive itself:
- * a typically "limit" variable.
- * @param time Timestamp to check.
- * @return True iff the timestamp is inside the current frame.
- */
- inline bool isInsideFrame(EmuTime::param time) const {
- return time >= frameStartTime.getTime() &&
- getTicksThisFrame(time) <= getTicksPerFrame();
- }
- /** This is a combination of the (horizontal) set adjust register and
- * the YJK-mode bit.
- */
- inline int getHorizontalAdjust() const {
- return horizontalAdjust;
- }
- /** Gets the number of VDP clockticks between start of line and the start
- * of the sprite plane.
- * The location of the sprite plane is not influenced by horizontal scroll
- * or border mask.
- * TODO: Leave out the text mode case, since there are no sprites
- * in text mode?
- */
- inline int getLeftSprites() const {
- return 100 + 102 + 56
- + (horizontalAdjust - 7) * 4
- + (displayMode.isTextMode() ? 36 : 0);
- }
- /** Gets the number of VDP clockticks between start of line and the end
- * of the left border.
- * Does not include extra pixels of horizontal scroll low, since those
- * are not actually border pixels (sprites appear in front of them).
- */
- inline int getLeftBorder() const {
- return getLeftSprites() + (isBorderMasked() ? 8 * 4 : 0);
- }
- /** Gets the number of VDP clockticks between start of line and the start
- * of the right border.
- */
- inline int getRightBorder() const {
- return getLeftSprites()
- + (displayMode.isTextMode() ? 960 : 1024);
- }
- /** Gets the number of VDP clockticks between start of line and the time
- * when the background pixel with X coordinate 0 would be drawn.
- * This includes extra pixels of horizontal scroll low,
- * but disregards border mask.
- */
- inline int getLeftBackground() const {
- return getLeftSprites() + getHorizontalScrollLow() * 4;
- }
- /** Should only be used by SpriteChecker. Returns the current value
- * of status register 0 (both the F-flag and the sprite related bits).
- */
- byte getStatusReg0() const { return statusReg0; }
- /** Should only be used by SpriteChecker. Change the sprite related
- * bits of status register 0 (leaves the F-flag unchanged).
- * Bit 6 (5S) is set when more than 4 (sprite mode 1) or 8 (sprite
- * mode 2) sprites occur on the same line.
- * Bit 5 (C) is set when sprites collide.
- * Bit 4..0 (5th sprite number) contains the number of the first
- * sprite to exceed the limit per line.
- */
- void setSpriteStatus(byte value)
- {
- statusReg0 = (statusReg0 & 0x80) | (value & 0x7F);
- }
- /** Returns current VR mode.
- * false -> VR=0, true -> VR=1
- */
- bool getVRMode() const {
- return (controlRegs[8] & 8) != 0;
- }
- /** Enable superimposing
- */
- void setExternalVideoSource(const RawFrame* externalSource) {
- externalVideo = externalSource;
- }
- /** Value of the cmdTiming setting, true means commands have infinite speed.
- */
- bool getBrokenCmdTiming() const {
- return brokenCmdTiming;
- }
- /** Get the earliest access slot that is at least 'delta' cycles in
- * the future. */
- EmuTime getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const;
- /** Same as getAccessSlot(), but it can be _much_ faster for repeated
- * calls, e.g. in the implementation of VDP commands. However it does
- * have some limitations:
- * - The returned calculator only remains valid for as long as
- * the VDP access timing remains the same (display/sprite enable).
- * - The calculator needs to see _all_ time changes.
- * (So this means that in every VDPCmd::execute() method you need
- * to construct a new calculator).
- */
- VDPAccessSlots::Calculator getAccessSlotCalculator(
- EmuTime::param time, EmuTime::param limit) const;
- /** Only used when there are commandExecuting-probe listeners.
- *
- * Call to announce a (lower-bound) estimate for when the VDP command
- * will finish executing. In response the VDP will schedule a
- * synchronization point to sync with VDPCmdEngine emulation.
- *
- * Normally it's not required to pro-actively sync with the end of a
- * VDP command. Instead these sync happen reactively on VDP status
- * reads (e.g. polling the CE bit) or on VRAM reads (rendering or CPU
- * VRAM read). This is in contrast with the V9990 where we DO need an
- * active sync because the V9990 can generate an IRQ on command end.
- *
- * Though when the VDP.commandExecuting probe is in use we do want a
- * reasonably timing accurate reaction of that probe. So (only) then we
- * do add the extra syncs (thus with extra emulation overhead when you
- * use that probe).
- */
- void scheduleCmdSync(EmuTime t) {
- auto now = getCurrentTime();
- if (t <= now) {
- // The largest amount of VDP cycles between 'progress'
- // in command emulation:
- // - worst case the LMMM takes 120+64 cycles to fully process one pixel
- // - the largest gap between access slots is 70 cycles
- // - but if we're unlucky the CPU steals that slot
- int LARGEST_STALL = 184 + 2 * 70;
- t = now + VDPClock::duration(LARGEST_STALL);
- }
- syncCmdDone.setSyncPoint(t);
- }
- template<typename Archive>
- void serialize(Archive& ar, unsigned version);
- private:
- void initTables();
- // VdpVersion bitmasks
- static constexpr unsigned VM_MSX1 = 1; // set-> MSX1, unset-> MSX2 or MSX2+
- static constexpr unsigned VM_PAL = 2; // set-> fixed PAL, unset-> fixed NTSC or switchable
- static constexpr unsigned VM_NO_MIRRORING = 4; // set-> no (screen2) mirroring
- static constexpr unsigned VM_PALCOL_MIRRORING = 8; // set-> pattern/color-table mirroring
- static constexpr unsigned VM_VRAM_REMAPPING = 16; // set-> 4k,8/16k VRAM remapping
- static constexpr unsigned VM_TOSHIBA_PALETTE = 32; // set-> has Toshiba palette
- static constexpr unsigned VM_YJK = 64; // set-> has YJK (MSX2+)
- static constexpr unsigned VM_YM2220_PALETTE = 128; // set-> has YM2220 palette
- /** VDP version: the VDP model being emulated. */
- enum VdpVersion {
- /** MSX1 VDP, NTSC version.
- * TMS9918A has NTSC encoding built in,
- * while TMS9928A has color difference output;
- * in emulation there is no difference. */
- TMS99X8A = VM_MSX1 | VM_PALCOL_MIRRORING | VM_VRAM_REMAPPING,
- /** MSX1 VDP, PAL version. */
- TMS9929A = VM_MSX1 | VM_PALCOL_MIRRORING | VM_VRAM_REMAPPING | VM_PAL,
- /** newer variant PAL. */
- TMS9129 = VM_MSX1 | VM_PAL,
- /** newer variant NTSC. */
- TMS91X8 = VM_MSX1,
- /** Toshiba clone (hardwired as PAL). */
- T6950PAL = VM_MSX1 | VM_TOSHIBA_PALETTE | VM_NO_MIRRORING | VM_PAL,
- /** Toshiba clone (hardwired as NTSC). */
- T6950NTSC = VM_MSX1 | VM_TOSHIBA_PALETTE | VM_NO_MIRRORING,
- /** VDP in Toshiba T7937A engine (hardwired as PAL). */
- T7937APAL = VM_MSX1 | VM_TOSHIBA_PALETTE | VM_PAL,
- /** VDP in Toshiba T7937A engine (hardwired as NTSC). */
- T7937ANTSC = VM_MSX1 | VM_TOSHIBA_PALETTE,
- /** Yamaha clone (hardwired as PAL). */
- YM2220PAL = VM_MSX1 | VM_YM2220_PALETTE | VM_PALCOL_MIRRORING | VM_PAL,
- /** Yamaha clone (hardwired as NTSC). */
- YM2220NTSC = VM_MSX1 | VM_YM2220_PALETTE | VM_PALCOL_MIRRORING,
- /** MSX2 VDP. */
- V9938 = 0,
- /** MSX2+ and turbo R VDP. */
- V9958 = VM_YJK,
- };
- struct SyncBase : public Schedulable {
- explicit SyncBase(VDP& vdp_) : Schedulable(vdp_.getScheduler()) {}
- using Schedulable::removeSyncPoint;
- using Schedulable::setSyncPoint;
- using Schedulable::pendingSyncPoint;
- protected:
- ~SyncBase() = default;
- };
- struct SyncVSync final : public SyncBase {
- explicit SyncVSync(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncVSync);
- vdp.execVSync(time);
- }
- } syncVSync;
- struct SyncDisplayStart final : public SyncBase {
- explicit SyncDisplayStart(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncDisplayStart);
- vdp.execDisplayStart(time);
- }
- } syncDisplayStart;
- struct SyncVScan final : public SyncBase {
- explicit SyncVScan(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncVScan);
- vdp.execVScan(time);
- }
- } syncVScan;
- struct SyncHScan final : public SyncBase {
- explicit SyncHScan(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param /*time*/) override {
- auto& vdp = OUTER(VDP, syncHScan);
- vdp.execHScan();
- }
- } syncHScan;
- struct SyncHorAdjust final : public SyncBase {
- explicit SyncHorAdjust(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncHorAdjust);
- vdp.execHorAdjust(time);
- }
- } syncHorAdjust;
- struct SyncSetMode final : public SyncBase {
- explicit SyncSetMode(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncSetMode);
- vdp.execSetMode(time);
- }
- } syncSetMode;
- struct SyncSetBlank final : public SyncBase {
- explicit SyncSetBlank(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncSetBlank);
- vdp.execSetBlank(time);
- }
- } syncSetBlank;
- struct SyncCpuVramAccess final : public SyncBase {
- explicit SyncCpuVramAccess(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncCpuVramAccess);
- vdp.execCpuVramAccess(time);
- }
- } syncCpuVramAccess;
- struct SyncCmdDone final : public SyncBase {
- explicit SyncCmdDone(VDP& vdp) : SyncBase(vdp) {}
- void executeUntil(EmuTime::param time) override {
- auto& vdp = OUTER(VDP, syncCmdDone);
- vdp.execSyncCmdDone(time);
- }
- } syncCmdDone;
- void execVSync(EmuTime::param time);
- void execDisplayStart(EmuTime::param time);
- void execVScan(EmuTime::param time);
- void execHScan();
- void execHorAdjust(EmuTime::param time);
- void execSetMode(EmuTime::param time);
- void execSetBlank(EmuTime::param time);
- void execCpuVramAccess(EmuTime::param time);
- void execSyncCmdDone(EmuTime::param time);
- /** Gets the number of display lines per screen.
- * @return 192 or 212.
- */
- inline int getNumberOfLines() const {
- return controlRegs[9] & 0x80 ? 212 : 192;
- }
- /** Returns the amount of vertical set-adjust 0..15.
- * Neutral set-adjust (that is 'set adjust(0,0)') returns the value '7'.
- */
- int getVerticalAdjust() const {
- return (controlRegs[18] >> 4) ^ 0x07;
- }
- /** Gets the value of the horizontal retrace status bit.
- * Note that HR flipping continues at all times, not just during
- * vertical display range.
- * @param ticksThisFrame The screen position (in VDP ticks)
- * to return HR for.
- * @return True iff the VDP scanning is inside the left/right
- * border or left/right erase or horizontal sync.
- * False iff the VDP scanning is in the display range.
- */
- inline bool getHR(int ticksThisFrame) const {
- // Note: These constants are located inside this function because
- // GCC 4.0.x won't link if they are in the class scope.
- /** Length of horizontal blank (HR=1) in text mode, measured in VDP
- * ticks.
- */
- static constexpr int HBLANK_LEN_TXT = 404;
- /** Length of horizontal blank (HR=1) in graphics mode, measured in VDP
- * ticks.
- */
- static constexpr int HBLANK_LEN_GFX = 312;
- return
- ( ticksThisFrame + TICKS_PER_LINE - getRightBorder()
- ) % TICKS_PER_LINE
- < (displayMode.isTextMode() ? HBLANK_LEN_TXT : HBLANK_LEN_GFX);
- }
- // VideoSystemChangeListener interface:
- void preVideoSystemChange() override;
- void postVideoSystemChange() override;
- /** Called both on init and on reset.
- * Puts VDP into reset state.
- * Does not call any renderer methods.
- */
- void resetInit();
- /** Companion to resetInit: in resetInit the registers are reset,
- * in this method the new base masks are distributed to the VDP
- * subsystems.
- */
- void resetMasks(EmuTime::param time);
- /** Start a new frame.
- * @param time The moment in emulated time the frame starts.
- */
- void frameStart(EmuTime::param time);
- /** Schedules a DISPLAY_START sync point.
- * Also removes a pending DISPLAY_START sync, if any.
- * Since HSCAN and VSCAN are relative to display start,
- * their schedule methods are called by this method.
- * @param time The moment in emulated time this call takes place.
- * Note: time is not the DISPLAY_START sync time!
- */
- void scheduleDisplayStart(EmuTime::param time);
- /** Schedules a VSCAN sync point.
- * Also removes a pending VSCAN sync, if any.
- * @param time The moment in emulated time this call takes place.
- * Note: time is not the VSCAN sync time!
- */
- void scheduleVScan(EmuTime::param time);
- /** Schedules a HSCAN sync point.
- * Also removes a pending HSCAN sync, if any.
- * @param time The moment in emulated time this call takes place.
- * Note: time is not the HSCAN sync time!
- */
- void scheduleHScan(EmuTime::param time);
- /** Byte is written to VRAM by the CPU.
- */
- void vramWrite(byte value, EmuTime::param time);
- /** Byte is read from VRAM by the CPU.
- */
- byte vramRead(EmuTime::param time);
- /** Helper methods for CPU-VRAM access. */
- void scheduleCpuVramAccess(bool isRead, byte write, EmuTime::param time);
- void executeCpuVramAccess(EmuTime::param time);
- /** Read the contents of a status register
- */
- byte peekStatusReg(byte reg, EmuTime::param time) const;
- byte readStatusReg(byte reg, EmuTime::param time);
- /** VDP control register has changed, work out the consequences.
- */
- void changeRegister(byte reg, byte val, EmuTime::param time);
- /** Schedule a sync point at the start of the next line.
- */
- void syncAtNextLine(SyncBase& type, EmuTime::param time);
- /** Create a new renderer.
- */
- void createRenderer();
- /** Name base mask has changed.
- * Inform the renderer and the VRAM.
- */
- void updateNameBase(EmuTime::param time);
- /** Color base mask has changed.
- * Inform the renderer and the VRAM.
- */
- void updateColorBase(EmuTime::param time);
- /** Pattern base mask has changed.
- * Inform the renderer and the VRAM.
- */
- void updatePatternBase(EmuTime::param time);
- /** Sprite attribute base mask has changed.
- * Inform the SpriteChecker and the VRAM.
- */
- void updateSpriteAttributeBase(EmuTime::param time);
- /** Sprite pattern base mask has changed.
- * Inform the SpriteChecker and the VRAM.
- */
- void updateSpritePatternBase(EmuTime::param time);
- /** Display mode has changed.
- * Update displayMode's value and inform the Renderer.
- */
- void updateDisplayMode(DisplayMode newMode, bool cmdBit, EmuTime::param time);
- /** Sets a palette entry.
- * @param index The index [0..15] in the palette.
- * @param grb value in the format of the palette registers:
- * bit 10..8 is green, bit 6..4 is red and bit 2..0 is blue.
- * @param time Moment in time palette change occurs.
- */
- void setPalette(int index, word grb, EmuTime::param time);
- // Observer<Setting>
- void update(const Setting& setting) override;
- private:
- Display& display;
- EnumSetting<bool>& cmdTiming;
- EnumSetting<bool>& tooFastAccess;
- struct RegDebug final : SimpleDebuggable {
- explicit RegDebug(VDP& vdp);
- byte read(unsigned address) override;
- void write(unsigned address, byte value, EmuTime::param time) override;
- } vdpRegDebug;
- struct StatusRegDebug final : SimpleDebuggable {
- explicit StatusRegDebug(VDP& vdp);
- byte read(unsigned address, EmuTime::param time) override;
- } vdpStatusRegDebug;
- struct PaletteDebug final : SimpleDebuggable {
- explicit PaletteDebug(VDP& vdp);
- byte read(unsigned address) override;
- void write(unsigned address, byte value, EmuTime::param time) override;
- } vdpPaletteDebug;
- struct VRAMPointerDebug final : SimpleDebuggable {
- explicit VRAMPointerDebug(VDP& vdp);
- byte read(unsigned address) override;
- void write(unsigned address, byte value, EmuTime::param time) override;
- } vramPointerDebug;
- class Info : public InfoTopic {
- public:
- void execute(span<const TclObject> tokens,
- TclObject& result) const override;
- std::string help(const std::vector<std::string>& tokens) const override;
- virtual int calc(const EmuTime& time) const = 0;
- protected:
- Info(VDP& vdp_, const std::string& name, std::string helpText_);
- ~Info() = default;
- VDP& vdp;
- const std::string helpText;
- };
- struct FrameCountInfo final : Info {
- explicit FrameCountInfo(VDP& vdp);
- int calc(const EmuTime& time) const override;
- } frameCountInfo;
- struct CycleInFrameInfo final : Info {
- explicit CycleInFrameInfo(VDP& vdp);
- int calc(const EmuTime& time) const override;
- } cycleInFrameInfo;
- struct LineInFrameInfo final : Info {
- explicit LineInFrameInfo(VDP& vdp);
- int calc(const EmuTime& time) const override;
- } lineInFrameInfo;
- struct CycleInLineInfo final : Info {
- explicit CycleInLineInfo(VDP& vdp);
- int calc(const EmuTime& time) const override;
- } cycleInLineInfo;
- struct MsxYPosInfo final : Info {
- explicit MsxYPosInfo(VDP& vdp);
- int calc(const EmuTime& time) const override;
- } msxYPosInfo;
- struct MsxX256PosInfo final : Info {
- explicit MsxX256PosInfo(VDP& vdp);
- int calc(const EmuTime& time) const override;
- } msxX256PosInfo;
- struct MsxX512PosInfo final : Info {
- explicit MsxX512PosInfo(VDP& vdp);
- int calc(const EmuTime& time) const override;
- } msxX512PosInfo;
- /** Renderer that converts this VDP's state into an image.
- */
- std::unique_ptr<Renderer> renderer;
- /** Command engine: the part of the V9938/58 that executes commands.
- */
- std::unique_ptr<VDPCmdEngine> cmdEngine;
- /** Sprite checker: calculates sprite patterns and collisions.
- */
- std::unique_ptr<SpriteChecker> spriteChecker;
- /** VRAM management object.
- */
- std::unique_ptr<VDPVRAM> vram;
- /** Is there an external video source which we must superimpose
- * upon?
- */
- const RawFrame* externalVideo;
- /** Are we currently superimposing?
- * This is a combination of the 'externalVideo' member (see above) and
- * the superimpose-enable bit in VDP register R#0. This property only
- * changes at most once per frame (at the beginning of the frame).
- */
- const RawFrame* superimposing;
- /** The emulation time when this frame was started (vsync).
- */
- VDPClock frameStartTime;
- /** Manages vertical scanning interrupt request.
- */
- OptionalIRQHelper irqVertical;
- /** Manages horizontal scanning interrupt request.
- */
- OptionalIRQHelper irqHorizontal;
- /** Time of last set DISPLAY_START sync point.
- */
- EmuTime displayStartSyncTime;
- /** Time of last set VSCAN sync point.
- */
- EmuTime vScanSyncTime;
- /** Time of last set HSCAN sync point.
- */
- EmuTime hScanSyncTime;
- TclCallback tooFastCallback;
- /** VDP version.
- */
- VdpVersion version;
- /** Saturation of Pr component of TMS9XXXA output circuitry.
- * The output of the VDP and the circuitry between the output and the
- * output connector influences this value. Percentage in range [0:100]
- */
- int saturationPr;
- /** Saturation of Pb component of TMS9XXXA output circuitry.
- * The output of the VDP and the circuitry between the output and the
- * output connector influences this value. Percentage in range [0:100]
- */
- int saturationPb;
- /** The number of already fully rendered frames.
- * Not used for actual emulation, only for 'frame_count' info topic.
- * This value cannot be calculated from EmuTime because framerate is
- * not constant (PAL/NTSC).
- */
- int frameCount;
- /** VDP ticks between start of frame and start of display.
- */
- int displayStart;
- /** VDP ticks between start of frame and the moment horizontal
- * scan match occurs.
- */
- int horizontalScanOffset;
- /** Horizontal display adjust.
- * This value is update at the start of a line.
- */
- int horizontalAdjust;
- /** Control registers.
- */
- byte controlRegs[32];
- /** Mask on the control register index:
- * makes MSX2 registers inaccessible on MSX1,
- * instead the MSX1 registers are mirrored.
- */
- int controlRegMask;
- /** Mask on the values of control registers.
- * This saves a lot of masking when using the register values,
- * because it is guaranteed non-existant bits are always zero.
- * It also disables access to VDP features on a VDP model
- * which does not support those features.
- */
- byte controlValueMasks[32];
- /** Blinking count: number of frames until next state.
- * If the ON or OFF period is 0, blinkCount is fixed to 0.
- */
- int blinkCount;
- /** VRAM read/write access pointer.
- * Contains the lower 14 bits of the current VRAM access address.
- */
- int vramPointer;
- /** V9938 palette.
- */
- word palette[16];
- /** Is the current scan position inside the display area?
- */
- bool isDisplayArea;
- /** Is PAL timing active? False means NTSC timing.
- * This value is updated at the start of every frame,
- * to avoid problems with mid-screen PAL/NTSC switching.
- * @see isPalTiming.
- */
- bool palTiming;
- /** Is interlace active?
- * @see isInterlaced.
- */
- bool interlaced;
- /** Status register 0.
- * Both the F flag (bit 7) and the sprite related bits (bits 6-0)
- * are stored here.
- */
- byte statusReg0;
- /** Status register 1.
- * Bit 7 and 6 are always zero because light pen is not implemented.
- * Bit 0 is always zero; its calculation depends on IE1.
- * So all that remains is the version number.
- */
- byte statusReg1;
- /** Status register 2.
- * Bit 7, 4 and 0 of this field are always zero,
- * their value can be retrieved from the command engine.
- */
- byte statusReg2;
- /** Blinking state: should alternate color / page be displayed?
- */
- bool blinkState;
- /** First byte written through port #99, #9A or #9B.
- */
- byte dataLatch;
- /** Does the data latch have register data (port #99) stored?
- */
- bool registerDataStored;
- /** Does the data latch have palette data (port #9A) stored?
- */
- bool paletteDataStored;
- /** VRAM is read as soon as VRAM pointer changes.
- * TODO: Is this actually what happens?
- * On TMS9928A the VRAM interface is the only access method.
- * But on V9938/58 there are other ways to access VRAM;
- * I wonder if they are consistent with this implementation.
- * This also holds the soon-to-be-written data for CPU-VRAM writes.
- */
- byte cpuVramData;
- /** CPU-VRAM requests are not executed immediately (though soon). This
- * variable indicates whether the pending request is read or write.
- */
- bool cpuVramReqIsRead;
- bool pendingCpuAccess; // always equal to pendingSyncPoint(CPU_VRAM_ACCESS)
- /** Does CPU interface access main VRAM (false) or extended VRAM (true)?
- * This is determined by MXC (R#45, bit 6).
- */
- bool cpuExtendedVram;
- /** Current dispay mode. Note that this is not always the same as the
- * display mode that can be obtained by combining the different mode
- * bits because a mode change only takes place at the start of the
- * next line.
- */
- DisplayMode displayMode;
- /** Is display enabled. Note that this is not always the same as bit 6
- * of R#1 because the display enable status change only takes place at
- * the start of the next line.
- */
- bool displayEnabled;
- /** Has a warning been printed.
- * This is set when a warning about setting the dotclock direction
- * is printed. */
- bool warningPrinted;
- /** Cached version of cmdTiming/tooFastAccess setting. */
- bool brokenCmdTiming;
- bool allowTooFastAccess;
- /** Cached CPU reference */
- MSXCPU& cpu;
- const byte fixedVDPIOdelayCycles;
- };
- SERIALIZE_CLASS_VERSION(VDP, 8);
- } // namespace openmsx
- #endif
|