Skip to content

Commit fa7c32f

Browse files
committed
Correct dot crawl phase offset calculation
- Thanks lidnariq! - The new formula is based on the color generator clock cycle count per frame.
1 parent 3b72d78 commit fa7c32f

8 files changed

+29
-19
lines changed

Core/NES/BaseNesPpu.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class BaseNesPpu : public INesMemoryHandler, public ISerializable
1616
{
1717
protected:
1818
uint64_t _masterClock = 0;
19+
uint64_t _masterClockFrameStart = 0;
1920
uint32_t _cycle = 0;
2021
int16_t _scanline = 0;
2122
bool _emulatorBgEnabled = false;

Core/NES/BisqwitNtscFilter.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ BisqwitNtscFilter::BisqwitNtscFilter(Emulator* emu) : BaseVideoFilter(emu)
6565
//Adjust outputbuffer to start at the middle of the picture
6666
outputBuffer += frameInfo.Width * (frameInfo.Height / 2);
6767

68-
DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, (GetVideoPhase() * 4) + 327360);
68+
DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, GetVideoPhaseOffset() + 120*341*_signalsPerPixel);
6969

7070
_workDone = true;
7171
}
@@ -89,7 +89,7 @@ void BisqwitNtscFilter::ApplyFilter(uint16_t *ppuOutputBuffer)
8989

9090
_workDone = false;
9191
_waitWork.Signal();
92-
DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), (GetVideoPhase() * 4) + GetOverscan().Top*341*8);
92+
DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), GetVideoPhaseOffset() + GetOverscan().Top * 341 * _signalsPerPixel);
9393
while(!_workDone) {}
9494
}
9595

Core/NES/NesNtscFilter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ void NesNtscFilter::ApplyFilter(uint16_t *ppuOutputBuffer)
7474
NesDefaultVideoFilter::ApplyPalBorder(ppuOutputBuffer);
7575
}
7676

77-
nes_ntsc_blit(&_ntscData, ppuOutputBuffer, _baseFrameInfo.Width, GetVideoPhase(), _baseFrameInfo.Width, _baseFrameInfo.Height, _ntscBuffer, NES_NTSC_OUT_WIDTH(_baseFrameInfo.Width) * 4);
77+
nes_ntsc_blit(&_ntscData, ppuOutputBuffer, _baseFrameInfo.Width, (GetVideoPhaseOffset() / 4), _baseFrameInfo.Width, _baseFrameInfo.Height, _ntscBuffer, NES_NTSC_OUT_WIDTH(_baseFrameInfo.Width) * 4);
7878

7979
for(uint32_t i = 0; i < frameInfo.Height; i+=2) {
8080
memcpy(GetOutputBuffer()+i*frameInfo.Width, _ntscBuffer + yOffset + xOffset + (i/2)*baseWidth, frameInfo.Width * sizeof(uint32_t));

Core/NES/NesPpu.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ template<class T> NesPpu<T>::NesPpu(NesConsole* console)
3333
_emu = console->GetEmulator();
3434
_mapper = console->GetMapper();
3535
_masterClock = 0;
36+
_masterClockFrameStart = 0;
3637
_masterClockDivider = 4;
3738
_settings = _emu->GetSettings();
3839

@@ -78,6 +79,7 @@ template<class T> NesPpu<T>::NesPpu(NesConsole* console)
7879
template<class T> void NesPpu<T>::Reset(bool softReset)
7980
{
8081
_masterClock = 0;
82+
_masterClockFrameStart = 0;
8183

8284
//Reset OAM decay timestamps regardless of the reset PPU option
8385
memset(_oamDecayCycles, 0, sizeof(_oamDecayCycles));
@@ -868,6 +870,10 @@ template<class T> void NesPpu<T>::ProcessScanlineImpl()
868870
}
869871

870872
if(_scanline >= 0) {
873+
if(_scanline == 0 && _cycle == 1) {
874+
// get the master clock at the first dot
875+
_masterClockFrameStart = _masterClock;
876+
}
871877
((T*)this)->DrawPixel();
872878
ShiftTileRegisters();
873879

@@ -1104,15 +1110,18 @@ template<class T> void NesPpu<T>::SendFrame()
11041110
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer);
11051111
}
11061112

1107-
//Get phase at the start of the current frame (341*241 cycles ago)
1108-
uint32_t videoPhase = ((_masterClock / _masterClockDivider) - 82181) % 3;
1113+
//Get chroma phase offset at the start of the current frame
1114+
//In units of color generator clocks
1115+
//https://www.nesdev.org/wiki/NTSC_video#Color_Artifacts
1116+
uint32_t videoPhaseOffset = (_masterClockFrameStart % 6) * 2;
1117+
11091118
NesConfig& cfg = _console->GetNesConfig();
11101119
if(_region != ConsoleRegion::Ntsc || cfg.PpuExtraScanlinesAfterNmi != 0 || cfg.PpuExtraScanlinesBeforeNmi != 0) {
11111120
//Force 2-phase pattern for PAL or when overclocking is used
1112-
videoPhase = _frameCount & 0x01;
1121+
videoPhaseOffset = (_frameCount & 0x01) * 4;
11131122
}
11141123

1115-
RenderedFrame frame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, 1.0, _frameCount, _console->GetControlManager()->GetPortStates(), videoPhase);
1124+
RenderedFrame frame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, 1.0, _frameCount, _console->GetControlManager()->GetPortStates(), videoPhaseOffset);
11161125
frame.Data = frameData; //HD packs
11171126

11181127
if(_console->GetVsMainConsole() || _console->GetVsSubConsole()) {
@@ -1462,7 +1471,7 @@ template<class T> void NesPpu<T>::Serialize(Serializer& s)
14621471
SV(_mask.IntensifyBlue); SV(_paletteRamMask); SV(_intensifyColorBits); SV(_statusFlags.SpriteOverflow); SV(_statusFlags.Sprite0Hit); SV(_statusFlags.VerticalBlank); SV(_scanline);
14631472
SV(_cycle); SV(_frameCount); SV(_memoryReadBuffer); SV(_region);
14641473

1465-
SV(_ppuBusAddress); SV(_masterClock);
1474+
SV(_ppuBusAddress); SV(_masterClock); SV(_masterClockFrameStart);
14661475

14671476
if(s.GetFormat() != SerializeFormat::Map) {
14681477
//Hide these entries from the Lua API

Core/Shared/RenderedFrame.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct RenderedFrame
1111
uint32_t Height = 240;
1212
double Scale = 1.0;
1313
uint32_t FrameNumber = 0;
14-
uint32_t VideoPhase = 0;
14+
uint32_t VideoPhaseOffset = 0;
1515
vector<ControllerData> InputData;
1616

1717
RenderedFrame()
@@ -27,14 +27,14 @@ struct RenderedFrame
2727
InputData({})
2828
{}
2929

30-
RenderedFrame(void* buffer, uint32_t width, uint32_t height, double scale, uint32_t frameNumber, vector<ControllerData> inputData, uint32_t videoPhase = 0) :
30+
RenderedFrame(void* buffer, uint32_t width, uint32_t height, double scale, uint32_t frameNumber, vector<ControllerData> inputData, uint32_t videoPhaseOffset = 0) :
3131
FrameBuffer(buffer),
3232
Data(nullptr),
3333
Width(width),
3434
Height(height),
3535
Scale(scale),
3636
FrameNumber(frameNumber),
37-
VideoPhase(videoPhase),
37+
VideoPhaseOffset(videoPhaseOffset),
3838
InputData(inputData)
3939
{}
4040
};

Core/Shared/Video/BaseVideoFilter.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,22 @@ bool BaseVideoFilter::IsOddFrame()
7070
return _isOddFrame;
7171
}
7272

73-
uint32_t BaseVideoFilter::GetVideoPhase()
73+
uint32_t BaseVideoFilter::GetVideoPhaseOffset()
7474
{
75-
return _videoPhase;
75+
return _videoPhaseOffset;
7676
}
7777

7878
uint32_t BaseVideoFilter::GetBufferSize()
7979
{
8080
return _bufferSize * sizeof(uint32_t);
8181
}
8282

83-
FrameInfo BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhase, void* frameData, bool enableOverscan)
83+
FrameInfo BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhaseOffset, void* frameData, bool enableOverscan)
8484
{
8585
auto lock = _frameLock.AcquireSafe();
8686
_overscan = enableOverscan ? _emu->GetSettings()->GetOverscan() : OverscanDimensions{};
8787
_isOddFrame = frameNumber % 2;
88-
_videoPhase = videoPhase;
88+
_videoPhaseOffset = videoPhaseOffset;
8989
_frameData = frameData;
9090
_ppuOutputBuffer = ppuOutputBuffer;
9191
OnBeforeApplyFilter();

Core/Shared/Video/BaseVideoFilter.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class BaseVideoFilter
1414
SimpleLock _frameLock;
1515
OverscanDimensions _overscan = {};
1616
bool _isOddFrame = false;
17-
uint32_t _videoPhase = 0;
17+
uint32_t _videoPhaseOffset = 0;
1818

1919
void UpdateBufferSize();
2020

@@ -33,7 +33,7 @@ class BaseVideoFilter
3333
virtual void ApplyFilter(uint16_t *ppuOutputBuffer) = 0;
3434
virtual void OnBeforeApplyFilter();
3535
bool IsOddFrame();
36-
uint32_t GetVideoPhase();
36+
uint32_t GetVideoPhaseOffset();
3737
uint32_t GetBufferSize();
3838

3939
template<typename T> bool NtscFilterOptionsChanged(T& ntscSetup);
@@ -44,7 +44,7 @@ class BaseVideoFilter
4444
virtual ~BaseVideoFilter();
4545

4646
uint32_t* GetOutputBuffer();
47-
FrameInfo SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhase, void* frameData, bool enableOverscan = true);
47+
FrameInfo SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhaseOffset, void* frameData, bool enableOverscan = true);
4848
void TakeScreenshot(string romName, VideoFilterType filterType);
4949
void TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream = nullptr);
5050

Core/Shared/Video/VideoDecoder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ void VideoDecoder::DecodeFrame(bool forRewind)
105105
}
106106

107107
_videoFilter->SetBaseFrameInfo(_baseFrameSize);
108-
FrameInfo frameSize = _videoFilter->SendFrame((uint16_t*)_frame.FrameBuffer, _frame.FrameNumber, _frame.VideoPhase, _frame.Data);
108+
FrameInfo frameSize = _videoFilter->SendFrame((uint16_t*)_frame.FrameBuffer, _frame.FrameNumber, _frame.VideoPhaseOffset, _frame.Data);
109109

110110
uint32_t* outputBuffer = _videoFilter->GetOutputBuffer();
111111

0 commit comments

Comments
 (0)