Skip to content
This repository has been archived by the owner on Sep 11, 2023. It is now read-only.

NTSC filter improvements #148

Draft
wants to merge 36 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
88ac9a1
Add NTSC-CRT library
Gumball2415 Jan 10, 2023
23fb398
Add modifications for NES NTSC composite
Gumball2415 Jan 10, 2023
0c2a651
Update NTSC-CRT library
Gumball2415 Jan 11, 2023
c45d727
Begin to connect filter library
Gumball2415 Jan 11, 2023
b6a2419
Implement emulator bindings
Gumball2415 Jan 12, 2023
ae06dc5
Fix NTSC emulator settings
Gumball2415 Jan 13, 2023
4ace702
Fix hue flickering
Gumball2415 Jan 13, 2023
4a4ce1e
Fix issues
Gumball2415 Jan 13, 2023
c028173
Increase colorburst integration rate
Gumball2415 Jan 13, 2023
664cc79
Directly set carrier phase for faster convergence
Gumball2415 Jan 13, 2023
fd1d4de
Implement border color and gray pulse
Gumball2415 Jan 14, 2023
0f19ef1
Precalculate composite voltage values
Gumball2415 Jan 14, 2023
1aaad19
Properly name resource label
Gumball2415 Jan 14, 2023
27e1308
Optimize NTSC signal encoding
Gumball2415 Jan 14, 2023
614834e
Fix resource labels
Gumball2415 Jan 15, 2023
1b9024f
Set artifact phase as member object
Gumball2415 Jan 15, 2023
d57b5a3
Add helper thread for blargg NTSC
Gumball2415 Jan 15, 2023
0ff807f
Optimize NTSC-CRT library
Gumball2415 Jan 15, 2023
fd4e046
Optimize filter
Gumball2415 Jan 16, 2023
51bfe82
Restore Mapper 431
Gumball2415 Jan 16, 2023
bf6f4fb
Implement library updates
Gumball2415 Jan 18, 2023
ccb4922
Add binding for frame blending
Gumball2415 Jan 18, 2023
d3bcf36
Sync with library implementation
Gumball2415 Jan 18, 2023
c0fb97e
Sync with NTSC-CRT v2.0.0
Gumball2415 Jan 18, 2023
868da7b
Fix linker issue
Gumball2415 Jan 18, 2023
394ee8f
Normalize IRE level output
Gumball2415 Jan 18, 2023
0f590c6
Remove unnecessary code
Gumball2415 Jan 18, 2023
8738a09
Adjust default contrast value to avoid clipping
Gumball2415 Jan 26, 2023
0cccf67
Increase sync window
Gumball2415 Jan 26, 2023
7610d05
Fix broken emphasis colors on Bisqwit NTSC
Gumball2415 Jan 26, 2023
11979ce
Refactor dot crawl phase
Gumball2415 Mar 28, 2023
ddac244
Force 2-phase dot crawl when overclocking is used
Gumball2415 Mar 28, 2023
bcb1569
Sync with NTSC-CRT v2.2.0
Gumball2415 Apr 2, 2023
41adb36
Update sync window implementation
Gumball2415 Apr 3, 2023
d5a535b
Calibrate decoding matrix to match Bisqwit's...
Gumball2415 Apr 3, 2023
cd834fb
Optimize emphasis waveform generation
Gumball2415 Apr 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Core/BaseVideoFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,17 @@ bool BaseVideoFilter::IsOddFrame()
return _isOddFrame;
}

void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber)
uint32_t BaseVideoFilter::GetVideoPhase()
{
return _videoPhase;
}

void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhase)
{
_frameLock.Acquire();
_overscan = _console->GetSettings()->GetOverscanDimensions();
_isOddFrame = frameNumber & 0x01;
_videoPhase = videoPhase;
UpdateBufferSize();
OnBeforeApplyFilter();
ApplyFilter(ppuOutputBuffer);
Expand Down
4 changes: 3 additions & 1 deletion Core/BaseVideoFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class BaseVideoFilter
SimpleLock _frameLock;
OverscanDimensions _overscan;
bool _isOddFrame;
uint32_t _videoPhase = 0;

void UpdateBufferSize();

Expand All @@ -30,7 +31,8 @@ class BaseVideoFilter

uint32_t* GetOutputBuffer();
bool IsOddFrame();
void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber);
uint32_t GetVideoPhase();
void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, uint32_t videoPhase);
void TakeScreenshot(string romName, VideoFilterType filterType);
void TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream = nullptr, bool rawScreenshot = false);

Expand Down
105 changes: 62 additions & 43 deletions Core/BisqwitNtscFilter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,29 @@ BisqwitNtscFilter::BisqwitNtscFilter(shared_ptr<Console> console, int resDivider
//Precalculate the low and high signal chosen for each 64 base colors
//with their respective attenuated values
for (int h = 0; h <= 1; h++) {
for(int i = 0; i <= 0x3F; i++) {
int r = (i & 0x0F) >= 0x0E ? 0x1D : i;
for (int i = 0; i <= 0x3F; i++) {

int m = signalLumaLow[h][r / 0x10];
int q = signalLumaHigh[h][r / 0x10];
if((r & 0x0F) == 13) {
int m = signalLumaLow[h][i / 0x10];
int q = signalLumaHigh[h][i / 0x10];

if ((i & 0x0F) == 0x0D) {
q = m;
} else if((r & 0x0F) == 0) {
}
else if ((i & 0x0F) == 0) {
m = q;
}
else if ((i & 0x0F) >= 0x0E) {
// colors $xE and $xF are not affected by emphasis
// https://forums.nesdev.org/viewtopic.php?p=160669#p160669
m = signalLumaLow[0][1];
q = signalLumaLow[0][1];
}

_signalLow[h][i] = m;
_signalHigh[h][i] = q;
}
}

_extraThread = std::thread([=]() {
//Worker thread to improve decode speed
while(!_stopThread) {
Expand All @@ -50,7 +59,7 @@ BisqwitNtscFilter::BisqwitNtscFilter(shared_ptr<Console> console, int resDivider
} else {
outputBuffer += GetOverscan().GetScreenWidth() * 64 / _resDivider / _resDivider * (120 - GetOverscan().Top);
}
DecodeFrame(120, 239 - GetOverscan().Bottom, _ppuOutputBuffer, outputBuffer, ((_console->GetModel() == NesModel::NTSC ? _console->GetStartingPhase() : 0) * 4) + 327360);
DecodeFrame(120, 239 - GetOverscan().Bottom, outputBuffer, (GetVideoPhase() * 4) + 327360);

_workDone = true;
}
Expand All @@ -70,7 +79,7 @@ void BisqwitNtscFilter::ApplyFilter(uint16_t *ppuOutputBuffer)

_workDone = false;
_waitWork.Signal();
DecodeFrame(GetOverscan().Top, 120, ppuOutputBuffer, GetOutputBuffer(), ((_console->GetModel() == NesModel::NTSC ? _console->GetStartingPhase() : 0) * 4) + GetOverscan().Top * 341 * 8);
DecodeFrame(GetOverscan().Top, 120, GetOutputBuffer(), (GetVideoPhase() * 4) + GetOverscan().Top * 341 * 8);
while(!_workDone) {}
}

Expand All @@ -96,8 +105,14 @@ void BisqwitNtscFilter::OnBeforeApplyFilter()
int saturation = (int)((pictureSettings.Saturation + 1.0) * (pictureSettings.Saturation + 1.0) * 144044);
bool colorimetryCorrection = _console->GetSettings()->GetNtscFilterSettings().ColorimetryCorrection;

// [saturation at 0] * 100 / [I or Q width at 0]
double SatFactor = 144044 * 100 / 12;
_brightness = static_cast<int>(pictureSettings.Brightness * 750);

// for some reason the original coefficients
// of bisqwit's decoding matrix has been reduced by at least 10^-6
// the aim here is to more closely match Bisqwit's palette generator
// at hue 0, saturation 1.0, contrast 1.0, brightness 1.0, gamma 1.8
// https://bisqwit.iki.fi/utils/nespalette.php
double SatFactor = 1000000;

for(int i = 0; i < 27; i++) {
_sinetable[i] = (int8_t)(8 * std::sin(i * 2 * pi / 12 + pictureSettings.Hue * pi));
Expand All @@ -109,14 +124,26 @@ void BisqwitNtscFilter::OnBeforeApplyFilter()

_y = contrast / _yWidth;

_ir = colorimetryCorrection ? (int)(contrast * 1.994681e-6 * saturation / _iWidth) : (int)(contrast * (0.95599 / SatFactor) * saturation / _iWidth);
_qr = colorimetryCorrection ? (int)(contrast * 9.915742e-7 * saturation / _qWidth) : (int)(contrast * (0.62082 / SatFactor) * saturation / _qWidth);

_ig = colorimetryCorrection ? (int)(contrast * 9.151351e-8 * saturation / _iWidth) : (int)(contrast * (-0.27201 / SatFactor) * saturation / _iWidth);
_qg = colorimetryCorrection ? (int)(contrast * -6.334805e-7 * saturation / _qWidth) : (int)(contrast * (-0.64720 / SatFactor) * saturation / _qWidth);

_ib = colorimetryCorrection ? (int)(contrast * -1.012984e-6 * saturation / _iWidth) : (int)(contrast * (-1.10674 / SatFactor) * saturation / _iWidth);
_qb = colorimetryCorrection ? (int)(contrast * 1.667217e-6 * saturation / _qWidth) : (int)(contrast * (1.70423 / SatFactor) * saturation / _qWidth);
_ir = colorimetryCorrection ?
(int)(contrast * 1.994681e-6 * saturation / _iWidth) :
(int)(contrast * (0.95599 / SatFactor) * saturation / _iWidth);
_qr = colorimetryCorrection ?
(int)(contrast * 9.915742e-7 * saturation / _qWidth) :
(int)(contrast * (0.62082 / SatFactor) * saturation / _qWidth);

_ig = colorimetryCorrection ?
(int)(contrast * 9.151351e-8 * saturation / _iWidth) :
(int)(contrast * (-0.27201 / SatFactor) * saturation / _iWidth);
_qg = colorimetryCorrection ?
(int)(contrast * -6.334805e-7 * saturation / _qWidth) :
(int)(contrast * (-0.64720 / SatFactor) * saturation / _qWidth);

_ib = colorimetryCorrection ?
(int)(contrast * -1.012984e-6 * saturation / _iWidth) :
(int)(contrast * (-1.10674 / SatFactor) * saturation / _iWidth);
_qb = colorimetryCorrection ?
(int)(contrast * 1.667217e-6 * saturation / _qWidth) :
(int)(contrast * (1.70423 / SatFactor) * saturation / _qWidth);
}

void BisqwitNtscFilter::RecursiveBlend(int iterationCount, uint64_t *output, uint64_t *currentLine, uint64_t *nextLine, int pixelsPerCycle, bool verticalBlend)
Expand Down Expand Up @@ -167,37 +194,30 @@ void BisqwitNtscFilter::GenerateNtscSignal(int8_t *ntscSignal, int &phase, int r
{
for(int x = -_paddingSize; x < 256 + _paddingSize; x++) {
// pixel_color = Pixel color (9-bit) given as input. Bitmask format: "eeellcccc".
uint16_t pixel_color = _ppuOutputBuffer[(rowNumber << 8) | (x < 0 ? 0 : (x >= 256 ? 255 : x))];
uint16_t pixel_color = _ppuOutputBuffer[(rowNumber << 8) | x];

int8_t emphasis = pixel_color >> 6;
int8_t color = pixel_color & 0x3F;

auto phase_shift_up = [=](uint16_t value, uint16_t amt) {
amt = amt % 12;
uint16_t uint12_value = value & 0xFFF;
uint32_t result = (((uint12_value << 12) | uint12_value) & 0xFFFFFFFF);
return uint16_t((result >> (amt % 12)) & 0xFFFF);
};
int8_t hue = color & 0x0F;

uint16_t emphasis_wave = 0;
if (emphasis & 0b001) // tint R; color phase C
emphasis_wave |= 0b000000111111;
if (emphasis & 0b010) // tint G; color phase 4
emphasis_wave |= 0b001111110000;
if (emphasis & 0b100) // tint B; color phase 8
emphasis_wave |= 0b111100000011;
if (emphasis)
emphasis_wave = phase_shift_up(emphasis_wave, (color & 0x0F));

uint16_t phaseBitmask = _bitmaskLut[std::abs(phase - (color & 0x0F)) % 12];
if(emphasis) {
if(emphasis & 0b001) // tint R; aligned to color phase C
emphasis_wave |= 0b000000111111;
if(emphasis & 0b010) // tint G; aligned to color phase 4
emphasis_wave |= 0b001111110000;
if(emphasis & 0b100) // tint B; aligned to color phase 8
emphasis_wave |= 0b111100000011;
// phase shift 12-bit waveform relative to pixel hue
emphasis_wave = ((emphasis_wave >> (hue % 12)) | (emphasis_wave << (12 - (hue % 12)))) & 0xFFFF;
}

uint16_t phaseBitmask = _bitmaskLut[std::abs(phase - hue) % 12];
bool attenuate = 0;

int8_t voltage;
for(int j = 0; j < _signalsPerPixel; j++) {
// colors $xE and $xF are not affected by emphasis
// https://forums.nesdev.org/viewtopic.php?p=160669#p160669
if ((color & 0x0F) <= 0x0D)
attenuate = (phaseBitmask & emphasis_wave);
attenuate = (phaseBitmask & emphasis_wave);

voltage = _signalHigh[attenuate][color];

Expand All @@ -218,7 +238,7 @@ void BisqwitNtscFilter::GenerateNtscSignal(int8_t *ntscSignal, int &phase, int r
phase += (341 - 256 - _paddingSize * 2) * _signalsPerPixel;
}

void BisqwitNtscFilter::DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase)
void BisqwitNtscFilter::DecodeFrame(int startRow, int endRow, uint32_t* outputBuffer, int startPhase)
{
int pixelsPerCycle = 8 / _resDivider;
int phase = startPhase;
Expand Down Expand Up @@ -296,8 +316,7 @@ void BisqwitNtscFilter::NtscDecodeLine(int width, const int8_t* signal, uint32_t
auto Cos = [=](int pos) -> char { return _sinetable[(pos + 36) % 12 + phase0]; };
auto Sin = [=](int pos) -> char { return _sinetable[(pos + 36) % 12 + 3 + phase0]; };

int brightness = (int)(_console->GetSettings()->GetPictureSettings().Brightness * 750);
int ysum = brightness, isum = 0, qsum = 0;
int ysum = _brightness, isum = 0, qsum = 0;
int offset = _resDivider + 4;
int leftOverscan = (GetOverscan().Left + _paddingSize) * 8 + offset;
int rightOverscan = width - (GetOverscan().Right + _paddingSize) * 8 + offset;
Expand Down
4 changes: 3 additions & 1 deletion Core/BisqwitNtscFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class BisqwitNtscFilter : public BaseVideoFilter

int _resDivider = 1;
uint16_t *_ppuOutputBuffer = nullptr;

int _brightness = 0;

/* Ywidth, Iwidth and Qwidth are the filter widths for Y,I,Q respectively.
* All widths at 12 produce the best signal quality.
Expand All @@ -46,7 +48,7 @@ class BisqwitNtscFilter : public BaseVideoFilter
void NtscDecodeLine(int width, const int8_t* signal, uint32_t* target, int phase0);

void GenerateNtscSignal(int8_t *ntscSignal, int &phase, int rowNumber);
void DecodeFrame(int startRow, int endRow, uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, int startPhase);
void DecodeFrame(int startRow, int endRow, uint32_t* outputBuffer, int startPhase);
void OnBeforeApplyFilter();

public:
Expand Down
9 changes: 7 additions & 2 deletions Core/Console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,9 +568,14 @@ uint32_t Console::GetFrameCount()
return _ppu ? _ppu->GetFrameCount() : 0;
}

uint8_t Console::GetStartingPhase()
uint8_t Console::GetVideoPhase()
{
return _ppu ? _ppu->GetStartingPhase() : 0;
return _ppu ? _ppu->GetVideoPhase() : 0;
}

bool Console::GetDotSkipped()
{
return _ppu ? _ppu->GetDotSkipped() : 0;
}

NesModel Console::GetModel()
Expand Down
3 changes: 2 additions & 1 deletion Core/Console.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ class Console : public std::enable_shared_from_this<Console>
RomInfo GetRomInfo();
uint32_t GetFrameCount();
// https://forums.nesdev.org/viewtopic.php?p=30625#p30625
uint8_t GetStartingPhase();
uint8_t GetVideoPhase();
bool GetDotSkipped();
NesModel GetModel();

uint32_t GetLagCounter();
Expand Down
2 changes: 2 additions & 0 deletions Core/Core.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@
<ClInclude Include="Kaiser7031.h" />
<ClInclude Include="KeyManager.h" />
<ClInclude Include="Lh51.h" />
<ClInclude Include="LMP88959NtscFilter.h" />
<ClInclude Include="Mapper116.h" />
<ClInclude Include="McAcc.h" />
<ClInclude Include="MMC3_198.h" />
Expand Down Expand Up @@ -1122,6 +1123,7 @@
<ClCompile Include="HdPpu.cpp" />
<ClCompile Include="HistoryViewer.cpp" />
<ClCompile Include="KeyManager.cpp" />
<ClCompile Include="LMP88959NtscFilter.cpp" />
<ClCompile Include="LuaApi.cpp" />
<ClCompile Include="LuaCallHelper.cpp" />
<ClCompile Include="LuaScriptingContext.cpp" />
Expand Down
4 changes: 4 additions & 0 deletions Core/Core.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,7 @@
<ClInclude Include="fmopn_2608rom.h">
<Filter>Nes\Mappers\EPSG</Filter>
</ClInclude>
<ClInclude Include="LMP88959NtscFilter.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
Expand Down Expand Up @@ -2164,5 +2165,8 @@
<ClCompile Include="emu2149.cpp">
<Filter>Nes\Mappers\EPSG</Filter>
</ClCompile>
<ClCompile Include="LMP88959NtscFilter.cpp">
<Filter>Misc</Filter>
</ClCompile>
</ItemGroup>
</Project>
10 changes: 9 additions & 1 deletion Core/EmulationSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ enum class VideoFilterType
Prescale8x = 23,
Prescale10x = 24,
Raw = 25,
LMP88959Ntsc = 26,
HdPack = 999
};

Expand Down Expand Up @@ -244,6 +245,9 @@ struct NtscFilterSettings
double DecodeMatrixQG = -0.647f;
double DecodeMatrixIB = -1.105f;
double DecodeMatrixQB = 1.702f;

double Noise = 0;
bool FrameBlend = true;
};

enum class RamPowerOnState
Expand Down Expand Up @@ -1252,7 +1256,9 @@ class EmulationSettings
bool verticalBlend,
bool keepVerticalResolution,
bool colorimetryCorrection,
bool useExternalPalette)
bool useExternalPalette,
double Noise,
bool FrameBlend)
{
_ntscFilterSettings.Artifacts = artifacts;
_ntscFilterSettings.Bleed = bleed;
Expand All @@ -1279,6 +1285,8 @@ class EmulationSettings
_ntscFilterSettings.VerticalBlend = verticalBlend;
_ntscFilterSettings.KeepVerticalResolution = keepVerticalResolution;
_ntscFilterSettings.ColorimetryCorrection = colorimetryCorrection;
_ntscFilterSettings.Noise = Noise;
_ntscFilterSettings.FrameBlend = FrameBlend;
}

NtscFilterSettings GetNtscFilterSettings()
Expand Down
Loading