GB: Added options to emulate LCD blending and GBC LCD colors

This commit is contained in:
Sour 2020-06-20 23:20:03 -04:00
parent 084bb70402
commit ef930104e6
9 changed files with 124 additions and 26 deletions

View file

@ -434,8 +434,10 @@ bool Console::LoadRom(VirtualFile romFile, VirtualFile patchFile, bool stopRom,
if(_cart->GetCoprocessor() == nullptr && _cart->GetGameboy()) { if(_cart->GetCoprocessor() == nullptr && _cart->GetGameboy()) {
_cart->GetGameboy()->PowerOn(); _cart->GetGameboy()->PowerOn();
_consoleType = _cart->GetGameboy()->IsCgb() ? ConsoleType::GameboyColor : ConsoleType::Gameboy;
_settings->SetFlag(EmulationFlags::GameboyMode); _settings->SetFlag(EmulationFlags::GameboyMode);
} else { } else {
_consoleType = ConsoleType::Snes;
_settings->ClearFlag(EmulationFlags::GameboyMode); _settings->ClearFlag(EmulationFlags::GameboyMode);
} }
@ -506,6 +508,11 @@ ConsoleRegion Console::GetRegion()
return _region; return _region;
} }
ConsoleType Console::GetConsoleType()
{
return _consoleType;
}
void Console::UpdateRegion() void Console::UpdateRegion()
{ {
switch(_settings->GetEmulationConfig().Region) { switch(_settings->GetEmulationConfig().Region) {

View file

@ -37,6 +37,7 @@ enum class MemoryOperationType;
enum class SnesMemoryType; enum class SnesMemoryType;
enum class EventType; enum class EventType;
enum class ConsoleRegion; enum class ConsoleRegion;
enum class ConsoleType;
class Console : public std::enable_shared_from_this<Console> class Console : public std::enable_shared_from_this<Console>
{ {
@ -82,6 +83,7 @@ private:
atomic<bool> _threadPaused; atomic<bool> _threadPaused;
ConsoleRegion _region; ConsoleRegion _region;
ConsoleType _consoleType;
uint32_t _masterClockRate; uint32_t _masterClockRate;
atomic<bool> _isRunAheadFrame; atomic<bool> _isRunAheadFrame;
@ -130,6 +132,7 @@ public:
uint64_t GetMasterClock(); uint64_t GetMasterClock();
uint32_t GetMasterClockRate(); uint32_t GetMasterClockRate();
ConsoleRegion GetRegion(); ConsoleRegion GetRegion();
ConsoleType GetConsoleType();
ConsoleLock AcquireLock(); ConsoleLock AcquireLock();
void Lock(); void Lock();

View file

@ -11,6 +11,8 @@ const static double PI = 3.14159265358979323846;
DefaultVideoFilter::DefaultVideoFilter(shared_ptr<Console> console) : BaseVideoFilter(console) DefaultVideoFilter::DefaultVideoFilter(shared_ptr<Console> console) : BaseVideoFilter(console)
{ {
InitLookupTable(); InitLookupTable();
_prevFrame = new uint16_t[256 * 240];
memset(_prevFrame, 0, 256 * 240 * sizeof(uint16_t));
} }
void DefaultVideoFilter::InitConversionMatrix(double hueShift, double saturationShift) void DefaultVideoFilter::InitConversionMatrix(double hueShift, double saturationShift)
@ -41,10 +43,26 @@ void DefaultVideoFilter::InitLookupTable()
double y, i, q; double y, i, q;
for(int rgb555 = 0; rgb555 < 0x8000; rgb555++) { for(int rgb555 = 0; rgb555 < 0x8000; rgb555++) {
uint8_t r = rgb555 & 0x1F;
uint8_t g = (rgb555 >> 5) & 0x1F;
uint8_t b = (rgb555 >> 10) & 0x1F;
if(_gbcAdjustColors) {
uint8_t r2 = std::min(240, (r * 26 + g * 4 + b * 2) >> 2);
uint8_t g2 = std::min(240, (g * 24 + b * 8) >> 2);
uint8_t b2 = std::min(240, (r * 6 + g * 4 + b * 22) >> 2);
r = r2;
g = g2;
b = b2;
} else {
r = To8Bit(r);
g = To8Bit(g);
b = To8Bit(b);
}
if(config.Hue != 0 || config.Saturation != 0 || config.Brightness != 0 || config.Contrast != 0) { if(config.Hue != 0 || config.Saturation != 0 || config.Brightness != 0 || config.Contrast != 0) {
double redChannel = To8Bit(rgb555 & 0x1F) / 255.0; double redChannel = r / 255.0;
double greenChannel = To8Bit((rgb555 >> 5) & 0x1F) / 255.0; double greenChannel = g / 255.0;
double blueChannel = To8Bit(rgb555 >> 10) / 255.0; double blueChannel = b / 255.0;
//Apply brightness, contrast, hue & saturation //Apply brightness, contrast, hue & saturation
RgbToYiq(redChannel, greenChannel, blueChannel, y, i, q); RgbToYiq(redChannel, greenChannel, blueChannel, y, i, q);
@ -57,9 +75,6 @@ void DefaultVideoFilter::InitLookupTable()
int b = std::min(255, (int)(blueChannel * 255)); int b = std::min(255, (int)(blueChannel * 255));
_calculatedPalette[rgb555] = 0xFF000000 | (r << 16) | (g << 8) | b; _calculatedPalette[rgb555] = 0xFF000000 | (r << 16) | (g << 8) | b;
} else { } else {
uint8_t r = To8Bit(rgb555 & 0x1F);
uint8_t g = To8Bit((rgb555 >> 5) & 0x1F);
uint8_t b = To8Bit((rgb555 >> 10) & 0x1F);
_calculatedPalette[rgb555] = 0xFF000000 | (r << 16) | (g << 8) | b; _calculatedPalette[rgb555] = 0xFF000000 | (r << 16) | (g << 8) | b;
} }
} }
@ -70,9 +85,15 @@ void DefaultVideoFilter::InitLookupTable()
void DefaultVideoFilter::OnBeforeApplyFilter() void DefaultVideoFilter::OnBeforeApplyFilter()
{ {
VideoConfig config = _console->GetSettings()->GetVideoConfig(); VideoConfig config = _console->GetSettings()->GetVideoConfig();
if(_videoConfig.Hue != config.Hue || _videoConfig.Saturation != config.Saturation || _videoConfig.Contrast != config.Contrast || _videoConfig.Brightness != config.Brightness) { EmulationConfig emulationConfig = _console->GetSettings()->GetEmulationConfig();
ConsoleType consoleType = _console->GetConsoleType();
bool adjustColors = emulationConfig.GbcAdjustColors && consoleType == ConsoleType::GameboyColor;
if(_videoConfig.Hue != config.Hue || _videoConfig.Saturation != config.Saturation || _videoConfig.Contrast != config.Contrast || _videoConfig.Brightness != config.Brightness || _gbcAdjustColors != adjustColors) {
_gbcAdjustColors = adjustColors;
InitLookupTable(); InitLookupTable();
} }
_gbBlendFrames = emulationConfig.GbBlendFrames && (consoleType == ConsoleType::Gameboy || consoleType == ConsoleType::GameboyColor);
_videoConfig = config; _videoConfig = config;
} }
@ -106,12 +127,12 @@ void DefaultVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer)
for(uint32_t i = 0; i < frameInfo.Height; i++) { for(uint32_t i = 0; i < frameInfo.Height; i++) {
if(i & 0x01) { if(i & 0x01) {
for(uint32_t j = 0; j < frameInfo.Width; j++) { for(uint32_t j = 0; j < frameInfo.Width; j++) {
*out = ApplyScanlineEffect(_calculatedPalette[ppuOutputBuffer[i * width + j + yOffset + xOffset]], scanlineIntensity); *out = ApplyScanlineEffect(GetPixel(ppuOutputBuffer, i * width + j + yOffset + xOffset), scanlineIntensity);
out++; out++;
} }
} else { } else {
for(uint32_t j = 0; j < frameInfo.Width; j++) { for(uint32_t j = 0; j < frameInfo.Width; j++) {
*out = _calculatedPalette[ppuOutputBuffer[i * width + j + yOffset + xOffset]]; *out = GetPixel(ppuOutputBuffer, i * width + j + yOffset + xOffset);
out++; out++;
} }
} }
@ -119,7 +140,7 @@ void DefaultVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer)
} else { } else {
for(uint32_t i = 0; i < frameInfo.Height; i++) { for(uint32_t i = 0; i < frameInfo.Height; i++) {
for(uint32_t j = 0; j < frameInfo.Width; j++) { for(uint32_t j = 0; j < frameInfo.Width; j++) {
out[i*frameInfo.Width+j] = _calculatedPalette[ppuOutputBuffer[i * width + j + yOffset + xOffset]]; out[i*frameInfo.Width+j] = GetPixel(ppuOutputBuffer, i * width + j + yOffset + xOffset);
} }
} }
} }
@ -136,6 +157,19 @@ void DefaultVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer)
} }
} }
} }
if(_gbBlendFrames) {
std::copy(ppuOutputBuffer, ppuOutputBuffer + 256 * 240, _prevFrame);
}
}
uint32_t DefaultVideoFilter::GetPixel(uint16_t* ppuFrame, uint32_t offset)
{
if(_gbBlendFrames) {
return BlendPixels(_calculatedPalette[_prevFrame[offset]], _calculatedPalette[ppuFrame[offset]]);
} else {
return _calculatedPalette[ppuFrame[offset]];
}
} }
uint32_t DefaultVideoFilter::BlendPixels(uint32_t a, uint32_t b) uint32_t DefaultVideoFilter::BlendPixels(uint32_t a, uint32_t b)

View file

@ -7,9 +7,13 @@
class DefaultVideoFilter : public BaseVideoFilter class DefaultVideoFilter : public BaseVideoFilter
{ {
private: private:
uint32_t _calculatedPalette[0x8000]; uint32_t _calculatedPalette[0x8000] = {};
double _yiqToRgbMatrix[6]; double _yiqToRgbMatrix[6] = {};
VideoConfig _videoConfig; VideoConfig _videoConfig = {};
uint16_t* _prevFrame = nullptr;
bool _gbBlendFrames = false;
bool _gbcAdjustColors = false;
void InitConversionMatrix(double hueShift, double saturationShift); void InitConversionMatrix(double hueShift, double saturationShift);
void InitLookupTable(); void InitLookupTable();
@ -18,6 +22,7 @@ private:
void YiqToRgb(double y, double i, double q, double &r, double &g, double &b); void YiqToRgb(double y, double i, double q, double &r, double &g, double &b);
__forceinline static uint8_t To8Bit(uint8_t color); __forceinline static uint8_t To8Bit(uint8_t color);
__forceinline static uint32_t BlendPixels(uint32_t a, uint32_t b); __forceinline static uint32_t BlendPixels(uint32_t a, uint32_t b);
__forceinline uint32_t GetPixel(uint16_t* ppuFrame, uint32_t offset);
protected: protected:
void OnBeforeApplyFilter(); void OnBeforeApplyFilter();

View file

@ -270,6 +270,13 @@ enum class ConsoleRegion
Pal = 2 Pal = 2
}; };
enum class ConsoleType
{
Snes = 0,
Gameboy = 1,
GameboyColor = 2
};
enum class GameboyModel enum class GameboyModel
{ {
Auto = 0, Auto = 0,
@ -301,6 +308,8 @@ struct EmulationConfig
GameboyModel GbModel = GameboyModel::Auto; GameboyModel GbModel = GameboyModel::Auto;
bool UseSgb2 = true; bool UseSgb2 = true;
bool GbBlendFrames = true;
bool GbcAdjustColors = true;
}; };
struct PreferencesConfig struct PreferencesConfig

View file

@ -31,6 +31,8 @@ namespace Mesen.GUI.Config
public GameboyModel GbModel = GameboyModel.Auto; public GameboyModel GbModel = GameboyModel.Auto;
[MarshalAs(UnmanagedType.I1)] public bool UseSgb2 = true; [MarshalAs(UnmanagedType.I1)] public bool UseSgb2 = true;
[MarshalAs(UnmanagedType.I1)] public bool GbBlendFrames = true;
[MarshalAs(UnmanagedType.I1)] public bool GbcAdjustColors = true;
public void ApplyConfig() public void ApplyConfig()
{ {

View file

@ -293,6 +293,12 @@
<Control ID="chkShowLagCounter">Show Lag Counter</Control> <Control ID="chkShowLagCounter">Show Lag Counter</Control>
<Control ID="btnResetLagCounter">Reset Counter</Control> <Control ID="btnResetLagCounter">Reset Counter</Control>
<Control ID="tpgGameboy">Game Boy</Control>
<Control ID="lblGameboy">Game Boy Model</Control>
<Control ID="chkUseSgb2">Use Super Game Boy 2 timings and behavior</Control>
<Control ID="chkGbBlendFrames">Enable LCD frame blending</Control>
<Control ID="chkGbcAdjustColors">Enable GBC LCD color emulation</Control>
<Control ID="btnOK">OK</Control> <Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Cancel</Control> <Control ID="btnCancel">Cancel</Control>
</Form> </Form>

View file

@ -53,6 +53,9 @@
this.tableLayoutPanel7 = new System.Windows.Forms.TableLayoutPanel(); this.tableLayoutPanel7 = new System.Windows.Forms.TableLayoutPanel();
this.lblGameboy = new System.Windows.Forms.Label(); this.lblGameboy = new System.Windows.Forms.Label();
this.cboGameboyModel = new System.Windows.Forms.ComboBox(); this.cboGameboyModel = new System.Windows.Forms.ComboBox();
this.chkUseSgb2 = new System.Windows.Forms.CheckBox();
this.chkGbBlendFrames = new System.Windows.Forms.CheckBox();
this.chkGbcAdjustColors = new System.Windows.Forms.CheckBox();
this.tpgBsx = new System.Windows.Forms.TabPage(); this.tpgBsx = new System.Windows.Forms.TabPage();
this.grpBsxDateTime = new System.Windows.Forms.GroupBox(); this.grpBsxDateTime = new System.Windows.Forms.GroupBox();
this.tableLayoutPanel6 = new System.Windows.Forms.TableLayoutPanel(); this.tableLayoutPanel6 = new System.Windows.Forms.TableLayoutPanel();
@ -79,7 +82,6 @@
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.nudGsuClockSpeed = new Mesen.GUI.Controls.MesenNumericUpDown(); this.nudGsuClockSpeed = new Mesen.GUI.Controls.MesenNumericUpDown();
this.lblGsuClockSpeed = new System.Windows.Forms.Label(); this.lblGsuClockSpeed = new System.Windows.Forms.Label();
this.chkUseSgb2 = new System.Windows.Forms.CheckBox();
this.tabMain.SuspendLayout(); this.tabMain.SuspendLayout();
this.tpgGeneral.SuspendLayout(); this.tpgGeneral.SuspendLayout();
this.tableLayoutPanel4.SuspendLayout(); this.tableLayoutPanel4.SuspendLayout();
@ -452,10 +454,14 @@
this.tableLayoutPanel7.Controls.Add(this.lblGameboy, 0, 0); this.tableLayoutPanel7.Controls.Add(this.lblGameboy, 0, 0);
this.tableLayoutPanel7.Controls.Add(this.cboGameboyModel, 1, 0); this.tableLayoutPanel7.Controls.Add(this.cboGameboyModel, 1, 0);
this.tableLayoutPanel7.Controls.Add(this.chkUseSgb2, 0, 1); this.tableLayoutPanel7.Controls.Add(this.chkUseSgb2, 0, 1);
this.tableLayoutPanel7.Controls.Add(this.chkGbBlendFrames, 0, 2);
this.tableLayoutPanel7.Controls.Add(this.chkGbcAdjustColors, 0, 3);
this.tableLayoutPanel7.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel7.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel7.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel7.Location = new System.Drawing.Point(3, 3);
this.tableLayoutPanel7.Name = "tableLayoutPanel7"; this.tableLayoutPanel7.Name = "tableLayoutPanel7";
this.tableLayoutPanel7.RowCount = 3; this.tableLayoutPanel7.RowCount = 5;
this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
@ -480,6 +486,39 @@
this.cboGameboyModel.Size = new System.Drawing.Size(119, 21); this.cboGameboyModel.Size = new System.Drawing.Size(119, 21);
this.cboGameboyModel.TabIndex = 1; this.cboGameboyModel.TabIndex = 1;
// //
// chkUseSgb2
//
this.chkUseSgb2.AutoSize = true;
this.tableLayoutPanel7.SetColumnSpan(this.chkUseSgb2, 2);
this.chkUseSgb2.Location = new System.Drawing.Point(3, 30);
this.chkUseSgb2.Name = "chkUseSgb2";
this.chkUseSgb2.Size = new System.Drawing.Size(237, 17);
this.chkUseSgb2.TabIndex = 2;
this.chkUseSgb2.Text = "Use Super Game Boy 2 timings and behavior";
this.chkUseSgb2.UseVisualStyleBackColor = true;
//
// chkGbBlendFrames
//
this.chkGbBlendFrames.AutoSize = true;
this.tableLayoutPanel7.SetColumnSpan(this.chkGbBlendFrames, 2);
this.chkGbBlendFrames.Location = new System.Drawing.Point(3, 53);
this.chkGbBlendFrames.Name = "chkGbBlendFrames";
this.chkGbBlendFrames.Size = new System.Drawing.Size(155, 17);
this.chkGbBlendFrames.TabIndex = 3;
this.chkGbBlendFrames.Text = "Enable LCD frame blending";
this.chkGbBlendFrames.UseVisualStyleBackColor = true;
//
// chkGbcAdjustColors
//
this.chkGbcAdjustColors.AutoSize = true;
this.tableLayoutPanel7.SetColumnSpan(this.chkGbcAdjustColors, 2);
this.chkGbcAdjustColors.Location = new System.Drawing.Point(3, 76);
this.chkGbcAdjustColors.Name = "chkGbcAdjustColors";
this.chkGbcAdjustColors.Size = new System.Drawing.Size(182, 17);
this.chkGbcAdjustColors.TabIndex = 4;
this.chkGbcAdjustColors.Text = "Enable GBC LCD color emulation";
this.chkGbcAdjustColors.UseVisualStyleBackColor = true;
//
// tpgBsx // tpgBsx
// //
this.tpgBsx.Controls.Add(this.grpBsxDateTime); this.tpgBsx.Controls.Add(this.grpBsxDateTime);
@ -869,17 +908,6 @@
this.lblGsuClockSpeed.TabIndex = 0; this.lblGsuClockSpeed.TabIndex = 0;
this.lblGsuClockSpeed.Text = "Super FX clock speed (%):"; this.lblGsuClockSpeed.Text = "Super FX clock speed (%):";
// //
// chkUseSgb2
//
this.chkUseSgb2.AutoSize = true;
this.tableLayoutPanel7.SetColumnSpan(this.chkUseSgb2, 2);
this.chkUseSgb2.Location = new System.Drawing.Point(3, 30);
this.chkUseSgb2.Name = "chkUseSgb2";
this.chkUseSgb2.Size = new System.Drawing.Size(237, 17);
this.chkUseSgb2.TabIndex = 2;
this.chkUseSgb2.Text = "Use Super Game Boy 2 timings and behavior";
this.chkUseSgb2.UseVisualStyleBackColor = true;
//
// frmEmulationConfig // frmEmulationConfig
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@ -985,5 +1013,7 @@
private System.Windows.Forms.Label lblGameboy; private System.Windows.Forms.Label lblGameboy;
private System.Windows.Forms.ComboBox cboGameboyModel; private System.Windows.Forms.ComboBox cboGameboyModel;
private System.Windows.Forms.CheckBox chkUseSgb2; private System.Windows.Forms.CheckBox chkUseSgb2;
private System.Windows.Forms.CheckBox chkGbBlendFrames;
private System.Windows.Forms.CheckBox chkGbcAdjustColors;
} }
} }

View file

@ -40,6 +40,8 @@ namespace Mesen.GUI.Forms.Config
AddBinding(nameof(EmulationConfig.GbModel), cboGameboyModel); AddBinding(nameof(EmulationConfig.GbModel), cboGameboyModel);
AddBinding(nameof(EmulationConfig.UseSgb2), chkUseSgb2); AddBinding(nameof(EmulationConfig.UseSgb2), chkUseSgb2);
AddBinding(nameof(EmulationConfig.GbBlendFrames), chkGbBlendFrames);
AddBinding(nameof(EmulationConfig.GbcAdjustColors), chkGbcAdjustColors);
long customDate = ConfigManager.Config.Emulation.BsxCustomDate; long customDate = ConfigManager.Config.Emulation.BsxCustomDate;
if(customDate >= 0) { if(customDate >= 0) {