From fa7c32f51caac2be6c27c78209b562332e97e36f Mon Sep 17 00:00:00 2001 From: Persune Date: Mon, 23 Oct 2023 01:01:03 +0800 Subject: [PATCH] Correct dot crawl phase offset calculation - Thanks lidnariq! - The new formula is based on the color generator clock cycle count per frame. --- Core/NES/BaseNesPpu.h | 1 + Core/NES/BisqwitNtscFilter.cpp | 4 ++-- Core/NES/NesNtscFilter.cpp | 2 +- Core/NES/NesPpu.cpp | 19 ++++++++++++++----- Core/Shared/RenderedFrame.h | 6 +++--- Core/Shared/Video/BaseVideoFilter.cpp | 8 ++++---- Core/Shared/Video/BaseVideoFilter.h | 6 +++--- Core/Shared/Video/VideoDecoder.cpp | 2 +- 8 files changed, 29 insertions(+), 19 deletions(-) diff --git a/Core/NES/BaseNesPpu.h b/Core/NES/BaseNesPpu.h index dbce3a41b..e87fbac07 100644 --- a/Core/NES/BaseNesPpu.h +++ b/Core/NES/BaseNesPpu.h @@ -16,6 +16,7 @@ class BaseNesPpu : public INesMemoryHandler, public ISerializable { protected: uint64_t _masterClock = 0; + uint64_t _masterClockFrameStart = 0; uint32_t _cycle = 0; int16_t _scanline = 0; bool _emulatorBgEnabled = false; diff --git a/Core/NES/BisqwitNtscFilter.cpp b/Core/NES/BisqwitNtscFilter.cpp index 666d24419..53f054fff 100644 --- a/Core/NES/BisqwitNtscFilter.cpp +++ b/Core/NES/BisqwitNtscFilter.cpp @@ -65,7 +65,7 @@ BisqwitNtscFilter::BisqwitNtscFilter(Emulator* emu) : BaseVideoFilter(emu) //Adjust outputbuffer to start at the middle of the picture outputBuffer += frameInfo.Width * (frameInfo.Height / 2); - DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, (GetVideoPhase() * 4) + 327360); + DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, GetVideoPhaseOffset() + 120*341*_signalsPerPixel); _workDone = true; } @@ -89,7 +89,7 @@ void BisqwitNtscFilter::ApplyFilter(uint16_t *ppuOutputBuffer) _workDone = false; _waitWork.Signal(); - DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), (GetVideoPhase() * 4) + GetOverscan().Top*341*8); + DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), GetVideoPhaseOffset() + GetOverscan().Top * 341 * _signalsPerPixel); while(!_workDone) {} } diff --git a/Core/NES/NesNtscFilter.cpp b/Core/NES/NesNtscFilter.cpp index d6729c24d..f38ed4fe9 100644 --- a/Core/NES/NesNtscFilter.cpp +++ b/Core/NES/NesNtscFilter.cpp @@ -74,7 +74,7 @@ void NesNtscFilter::ApplyFilter(uint16_t *ppuOutputBuffer) NesDefaultVideoFilter::ApplyPalBorder(ppuOutputBuffer); } - nes_ntsc_blit(&_ntscData, ppuOutputBuffer, _baseFrameInfo.Width, GetVideoPhase(), _baseFrameInfo.Width, _baseFrameInfo.Height, _ntscBuffer, NES_NTSC_OUT_WIDTH(_baseFrameInfo.Width) * 4); + nes_ntsc_blit(&_ntscData, ppuOutputBuffer, _baseFrameInfo.Width, (GetVideoPhaseOffset() / 4), _baseFrameInfo.Width, _baseFrameInfo.Height, _ntscBuffer, NES_NTSC_OUT_WIDTH(_baseFrameInfo.Width) * 4); for(uint32_t i = 0; i < frameInfo.Height; i+=2) { memcpy(GetOutputBuffer()+i*frameInfo.Width, _ntscBuffer + yOffset + xOffset + (i/2)*baseWidth, frameInfo.Width * sizeof(uint32_t)); diff --git a/Core/NES/NesPpu.cpp b/Core/NES/NesPpu.cpp index 976adc6c1..454dd2dd6 100644 --- a/Core/NES/NesPpu.cpp +++ b/Core/NES/NesPpu.cpp @@ -33,6 +33,7 @@ template NesPpu::NesPpu(NesConsole* console) _emu = console->GetEmulator(); _mapper = console->GetMapper(); _masterClock = 0; + _masterClockFrameStart = 0; _masterClockDivider = 4; _settings = _emu->GetSettings(); @@ -78,6 +79,7 @@ template NesPpu::NesPpu(NesConsole* console) template void NesPpu::Reset(bool softReset) { _masterClock = 0; + _masterClockFrameStart = 0; //Reset OAM decay timestamps regardless of the reset PPU option memset(_oamDecayCycles, 0, sizeof(_oamDecayCycles)); @@ -868,6 +870,10 @@ template void NesPpu::ProcessScanlineImpl() } if(_scanline >= 0) { + if(_scanline == 0 && _cycle == 1) { + // get the master clock at the first dot + _masterClockFrameStart = _masterClock; + } ((T*)this)->DrawPixel(); ShiftTileRegisters(); @@ -1104,15 +1110,18 @@ template void NesPpu::SendFrame() _emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer); } - //Get phase at the start of the current frame (341*241 cycles ago) - uint32_t videoPhase = ((_masterClock / _masterClockDivider) - 82181) % 3; + //Get chroma phase offset at the start of the current frame + //In units of color generator clocks + //https://www.nesdev.org/wiki/NTSC_video#Color_Artifacts + uint32_t videoPhaseOffset = (_masterClockFrameStart % 6) * 2; + NesConfig& cfg = _console->GetNesConfig(); if(_region != ConsoleRegion::Ntsc || cfg.PpuExtraScanlinesAfterNmi != 0 || cfg.PpuExtraScanlinesBeforeNmi != 0) { //Force 2-phase pattern for PAL or when overclocking is used - videoPhase = _frameCount & 0x01; + videoPhaseOffset = (_frameCount & 0x01) * 4; } - RenderedFrame frame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, 1.0, _frameCount, _console->GetControlManager()->GetPortStates(), videoPhase); + RenderedFrame frame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, 1.0, _frameCount, _console->GetControlManager()->GetPortStates(), videoPhaseOffset); frame.Data = frameData; //HD packs if(_console->GetVsMainConsole() || _console->GetVsSubConsole()) { @@ -1462,7 +1471,7 @@ template void NesPpu::Serialize(Serializer& s) SV(_mask.IntensifyBlue); SV(_paletteRamMask); SV(_intensifyColorBits); SV(_statusFlags.SpriteOverflow); SV(_statusFlags.Sprite0Hit); SV(_statusFlags.VerticalBlank); SV(_scanline); SV(_cycle); SV(_frameCount); SV(_memoryReadBuffer); SV(_region); - SV(_ppuBusAddress); SV(_masterClock); + SV(_ppuBusAddress); SV(_masterClock); SV(_masterClockFrameStart); if(s.GetFormat() != SerializeFormat::Map) { //Hide these entries from the Lua API diff --git a/Core/Shared/RenderedFrame.h b/Core/Shared/RenderedFrame.h index d095046c6..4c8574705 100644 --- a/Core/Shared/RenderedFrame.h +++ b/Core/Shared/RenderedFrame.h @@ -11,7 +11,7 @@ struct RenderedFrame uint32_t Height = 240; double Scale = 1.0; uint32_t FrameNumber = 0; - uint32_t VideoPhase = 0; + uint32_t VideoPhaseOffset = 0; vector InputData; RenderedFrame() @@ -27,14 +27,14 @@ struct RenderedFrame InputData({}) {} - RenderedFrame(void* buffer, uint32_t width, uint32_t height, double scale, uint32_t frameNumber, vector inputData, uint32_t videoPhase = 0) : + RenderedFrame(void* buffer, uint32_t width, uint32_t height, double scale, uint32_t frameNumber, vector inputData, uint32_t videoPhaseOffset = 0) : FrameBuffer(buffer), Data(nullptr), Width(width), Height(height), Scale(scale), FrameNumber(frameNumber), - VideoPhase(videoPhase), + VideoPhaseOffset(videoPhaseOffset), InputData(inputData) {} }; diff --git a/Core/Shared/Video/BaseVideoFilter.cpp b/Core/Shared/Video/BaseVideoFilter.cpp index c8f046109..dd4eb301e 100644 --- a/Core/Shared/Video/BaseVideoFilter.cpp +++ b/Core/Shared/Video/BaseVideoFilter.cpp @@ -70,9 +70,9 @@ bool BaseVideoFilter::IsOddFrame() return _isOddFrame; } -uint32_t BaseVideoFilter::GetVideoPhase() +uint32_t BaseVideoFilter::GetVideoPhaseOffset() { - return _videoPhase; + return _videoPhaseOffset; } uint32_t BaseVideoFilter::GetBufferSize() @@ -80,12 +80,12 @@ uint32_t BaseVideoFilter::GetBufferSize() return _bufferSize * sizeof(uint32_t); } -FrameInfo BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhase, void* frameData, bool enableOverscan) +FrameInfo BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhaseOffset, void* frameData, bool enableOverscan) { auto lock = _frameLock.AcquireSafe(); _overscan = enableOverscan ? _emu->GetSettings()->GetOverscan() : OverscanDimensions{}; _isOddFrame = frameNumber % 2; - _videoPhase = videoPhase; + _videoPhaseOffset = videoPhaseOffset; _frameData = frameData; _ppuOutputBuffer = ppuOutputBuffer; OnBeforeApplyFilter(); diff --git a/Core/Shared/Video/BaseVideoFilter.h b/Core/Shared/Video/BaseVideoFilter.h index 40ee9194e..64aae0c56 100644 --- a/Core/Shared/Video/BaseVideoFilter.h +++ b/Core/Shared/Video/BaseVideoFilter.h @@ -14,7 +14,7 @@ class BaseVideoFilter SimpleLock _frameLock; OverscanDimensions _overscan = {}; bool _isOddFrame = false; - uint32_t _videoPhase = 0; + uint32_t _videoPhaseOffset = 0; void UpdateBufferSize(); @@ -33,7 +33,7 @@ class BaseVideoFilter virtual void ApplyFilter(uint16_t *ppuOutputBuffer) = 0; virtual void OnBeforeApplyFilter(); bool IsOddFrame(); - uint32_t GetVideoPhase(); + uint32_t GetVideoPhaseOffset(); uint32_t GetBufferSize(); template bool NtscFilterOptionsChanged(T& ntscSetup); @@ -44,7 +44,7 @@ class BaseVideoFilter virtual ~BaseVideoFilter(); uint32_t* GetOutputBuffer(); - FrameInfo SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhase, void* frameData, bool enableOverscan = true); + FrameInfo SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhaseOffset, void* frameData, bool enableOverscan = true); void TakeScreenshot(string romName, VideoFilterType filterType); void TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream = nullptr); diff --git a/Core/Shared/Video/VideoDecoder.cpp b/Core/Shared/Video/VideoDecoder.cpp index 4172827c4..61d562567 100644 --- a/Core/Shared/Video/VideoDecoder.cpp +++ b/Core/Shared/Video/VideoDecoder.cpp @@ -105,7 +105,7 @@ void VideoDecoder::DecodeFrame(bool forRewind) } _videoFilter->SetBaseFrameInfo(_baseFrameSize); - FrameInfo frameSize = _videoFilter->SendFrame((uint16_t*)_frame.FrameBuffer, _frame.FrameNumber, _frame.VideoPhase, _frame.Data); + FrameInfo frameSize = _videoFilter->SendFrame((uint16_t*)_frame.FrameBuffer, _frame.FrameNumber, _frame.VideoPhaseOffset, _frame.Data); uint32_t* outputBuffer = _videoFilter->GetOutputBuffer();