nestopia/source/core/NstVideoRenderer.cpp
2022-11-02 21:32:16 -06:00

838 lines
25 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Nestopia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
////////////////////////////////////////////////////////////////////////////////////////
#include <cstring>
#include <cmath>
#include <new>
#include "NstCore.hpp"
#include "NstAssert.hpp"
#include "NstFpuPrecision.hpp"
#include "api/NstApiVideo.hpp"
#include "NstVideoRenderer.hpp"
#include "NstVideoFilterNone.hpp"
#ifndef NO_NTSC
#include "NstVideoFilterNtsc.hpp"
#endif
#ifndef NST_NO_SCALEX
#include "NstVideoFilterScaleX.hpp"
#endif
#ifndef NST_NO_HQ2X
#include "NstVideoFilterHqX.hpp"
#endif
#ifndef NST_NO_2XSAI
#include "NstVideoFilter2xSaI.hpp"
#endif
#ifndef NST_NO_XBR
#include "NstVideoFilterxBR.hpp"
#endif
namespace Nes
{
namespace Core
{
namespace Video
{
const byte Renderer::Palette::pc10Palette[64][3] =
{
{0x6D,0x6D,0x6D}, {0x00,0x24,0x92}, {0x00,0x00,0xDB}, {0x6D,0x49,0xDB},
{0x92,0x00,0x6D}, {0xB6,0x00,0x6D}, {0xB6,0x24,0x00}, {0x92,0x49,0x00},
{0x6D,0x49,0x00}, {0x24,0x49,0x00}, {0x00,0x6D,0x24}, {0x00,0x92,0x00},
{0x00,0x49,0x49}, {0x00,0x00,0x00}, {0x00,0x00,0x00}, {0x00,0x00,0x00},
{0xB6,0xB6,0xB6}, {0x00,0x6D,0xDB}, {0x00,0x49,0xFF}, {0x92,0x00,0xFF},
{0xB6,0x00,0xFF}, {0xFF,0x00,0x92}, {0xFF,0x00,0x00}, {0xDB,0x6D,0x00},
{0x92,0x6D,0x00}, {0x24,0x92,0x00}, {0x00,0x92,0x00}, {0x00,0xB6,0x6D},
{0x00,0x92,0x92}, {0x24,0x24,0x24}, {0x00,0x00,0x00}, {0x00,0x00,0x00},
{0xFF,0xFF,0xFF}, {0x6D,0xB6,0xFF}, {0x92,0x92,0xFF}, {0xDB,0x6D,0xFF},
{0xFF,0x00,0xFF}, {0xFF,0x6D,0xFF}, {0xFF,0x92,0x00}, {0xFF,0xB6,0x00},
{0xDB,0xDB,0x00}, {0x6D,0xDB,0x00}, {0x00,0xFF,0x00}, {0x49,0xFF,0xDB},
{0x00,0xFF,0xFF}, {0x49,0x49,0x49}, {0x00,0x00,0x00}, {0x00,0x00,0x00},
{0xFF,0xFF,0xFF}, {0xB6,0xDB,0xFF}, {0xDB,0xB6,0xFF}, {0xFF,0xB6,0xFF},
{0xFF,0x92,0xFF}, {0xFF,0xB6,0xB6}, {0xFF,0xDB,0x92}, {0xFF,0xFF,0x49},
{0xFF,0xFF,0x6D}, {0xB6,0xFF,0x49}, {0x92,0xFF,0x6D}, {0x49,0xFF,0xDB},
{0x92,0xDB,0xFF}, {0x92,0x92,0x92}, {0x00,0x00,0x00}, {0x00,0x00,0x00}
};
const byte Renderer::Palette::vsPalette[4][64][3] =
{
{
{0xFF,0xB6,0xB6}, {0xDB,0x6D,0xFF}, {0xFF,0x00,0x00}, {0x92,0x92,0xFF},
{0x00,0x92,0x92}, {0x24,0x49,0x00}, {0x49,0x49,0x49}, {0xFF,0x00,0x92},
{0xFF,0xFF,0xFF}, {0x6D,0x6D,0x6D}, {0xFF,0xB6,0x00}, {0xB6,0x00,0x6D},
{0x92,0x00,0x6D}, {0xDB,0xDB,0x00}, {0x6D,0x49,0x00}, {0xFF,0xFF,0xFF},
{0x6D,0xB6,0xFF}, {0xDB,0xB6,0x6D}, {0x6D,0x24,0x00}, {0x6D,0xDB,0x00},
{0x92,0xDB,0xFF}, {0xDB,0xB6,0xFF}, {0xFF,0xDB,0x92}, {0x00,0x49,0xFF},
{0xFF,0xDB,0x00}, {0x49,0xFF,0xDB}, {0x00,0x00,0x00}, {0x49,0x00,0x00},
{0xDB,0xDB,0xDB}, {0x92,0x92,0x92}, {0xFF,0x00,0xFF}, {0x00,0x24,0x92},
{0x00,0x00,0x6D}, {0xB6,0xDB,0xFF}, {0xFF,0xB6,0xFF}, {0x00,0xFF,0x00},
{0x00,0xFF,0xFF}, {0x00,0x49,0x49}, {0x00,0xB6,0x6D}, {0xB6,0x00,0xFF},
{0x00,0x00,0x00}, {0x92,0x49,0x00}, {0xFF,0x92,0xFF}, {0xB6,0x24,0x00},
{0x92,0x00,0xFF}, {0x00,0x00,0xDB}, {0xFF,0x92,0x00}, {0x00,0x00,0x00},
{0x00,0x00,0x00}, {0x24,0x92,0x00}, {0xB6,0xB6,0xB6}, {0x00,0x6D,0x24},
{0xB6,0xFF,0x49}, {0x6D,0x49,0xDB}, {0xFF,0xFF,0x00}, {0xDB,0x6D,0x00},
{0x00,0x49,0x00}, {0x00,0x6D,0xDB}, {0x00,0x92,0x00}, {0x24,0x24,0x24},
{0xFF,0xFF,0x6D}, {0xFF,0x6D,0xFF}, {0x92,0x6D,0x00}, {0x92,0xFF,0x6D}
},
{
{0x00,0x00,0x00}, {0xFF,0xB6,0x00}, {0x92,0x6D,0x00}, {0xB6,0xFF,0x49},
{0x92,0xFF,0x6D}, {0xFF,0x6D,0xFF}, {0x00,0x92,0x92}, {0xB6,0xDB,0xFF},
{0xFF,0x00,0x00}, {0x92,0x00,0xFF}, {0xFF,0xFF,0x6D}, {0xFF,0x92,0xFF},
{0xFF,0xFF,0xFF}, {0xDB,0x6D,0xFF}, {0x92,0xDB,0xFF}, {0x00,0x92,0x00},
{0x00,0x49,0x00}, {0x6D,0xB6,0xFF}, {0xB6,0x24,0x00}, {0xDB,0xDB,0xDB},
{0x00,0xB6,0x6D}, {0x6D,0xDB,0x00}, {0x49,0x00,0x00}, {0x92,0x92,0xFF},
{0x49,0x49,0x49}, {0xFF,0x00,0xFF}, {0x00,0x00,0x6D}, {0x49,0xFF,0xDB},
{0xDB,0xB6,0xFF}, {0x6D,0x49,0x00}, {0x00,0x00,0x00}, {0x6D,0x49,0xDB},
{0x92,0x00,0x6D}, {0xFF,0xDB,0x92}, {0xFF,0x92,0x00}, {0xFF,0xB6,0xFF},
{0x00,0x6D,0xDB}, {0x6D,0x24,0x00}, {0xB6,0xB6,0xB6}, {0x00,0x00,0xDB},
{0xB6,0x00,0xFF}, {0xFF,0xDB,0x00}, {0x6D,0x6D,0x6D}, {0x24,0x49,0x00},
{0x00,0x49,0xFF}, {0x00,0x00,0x00}, {0xDB,0xDB,0x00}, {0xFF,0xFF,0xFF},
{0xDB,0xB6,0x6D}, {0x24,0x24,0x24}, {0x00,0xFF,0x00}, {0xDB,0x6D,0x00},
{0x00,0x49,0x49}, {0x00,0x24,0x92}, {0xFF,0x00,0x92}, {0x24,0x92,0x00},
{0x00,0x00,0x00}, {0x00,0xFF,0xFF}, {0x92,0x49,0x00}, {0xFF,0xFF,0x00},
{0xFF,0xB6,0xB6}, {0xB6,0x00,0x6D}, {0x00,0x6D,0x24}, {0x92,0x92,0x92}
},
{
{0xB6,0x00,0xFF}, {0xFF,0x6D,0xFF}, {0x92,0xFF,0x6D}, {0xB6,0xB6,0xB6},
{0x00,0x92,0x00}, {0xFF,0xFF,0xFF}, {0xB6,0xDB,0xFF}, {0x24,0x49,0x00},
{0x00,0x24,0x92}, {0x00,0x00,0x00}, {0xFF,0xDB,0x92}, {0x6D,0x49,0x00},
{0xFF,0x00,0x92}, {0xDB,0xDB,0xDB}, {0xDB,0xB6,0x6D}, {0x92,0xDB,0xFF},
{0x92,0x92,0xFF}, {0x00,0x92,0x92}, {0xB6,0x00,0x6D}, {0x00,0x49,0xFF},
{0x24,0x92,0x00}, {0x92,0x6D,0x00}, {0xDB,0x6D,0x00}, {0x00,0xB6,0x6D},
{0x6D,0x6D,0x6D}, {0x6D,0x49,0xDB}, {0x00,0x00,0x00}, {0x00,0x00,0xDB},
{0xFF,0x00,0x00}, {0xB6,0x24,0x00}, {0xFF,0x92,0xFF}, {0xFF,0xB6,0xB6},
{0xDB,0x6D,0xFF}, {0x00,0x49,0x00}, {0x00,0x00,0x6D}, {0xFF,0xFF,0x00},
{0x24,0x24,0x24}, {0xFF,0xB6,0x00}, {0xFF,0x92,0x00}, {0xFF,0xFF,0xFF},
{0x6D,0xDB,0x00}, {0x92,0x00,0x6D}, {0x6D,0xB6,0xFF}, {0xFF,0x00,0xFF},
{0x00,0x6D,0xDB}, {0x92,0x92,0x92}, {0x00,0x00,0x00}, {0x6D,0x24,0x00},
{0x00,0xFF,0xFF}, {0x49,0x00,0x00}, {0xB6,0xFF,0x49}, {0xFF,0xB6,0xFF},
{0x92,0x49,0x00}, {0x00,0xFF,0x00}, {0xDB,0xDB,0x00}, {0x49,0x49,0x49},
{0x00,0x6D,0x24}, {0x00,0x00,0x00}, {0xDB,0xB6,0xFF}, {0xFF,0xFF,0x6D},
{0x92,0x00,0xFF}, {0x49,0xFF,0xDB}, {0xFF,0xDB,0x00}, {0x00,0x49,0x49}
},
{
{0x92,0x6D,0x00}, {0x6D,0x49,0xDB}, {0x00,0x92,0x92}, {0xDB,0xDB,0x00},
{0x00,0x00,0x00}, {0xFF,0xB6,0xB6}, {0x00,0x24,0x92}, {0xDB,0x6D,0x00},
{0xB6,0xB6,0xB6}, {0x6D,0x24,0x00}, {0x00,0xFF,0x00}, {0x00,0x00,0x6D},
{0xFF,0xDB,0x92}, {0xFF,0xFF,0x00}, {0x00,0x92,0x00}, {0xB6,0xFF,0x49},
{0xFF,0x6D,0xFF}, {0x49,0x00,0x00}, {0x00,0x49,0xFF}, {0xFF,0x92,0xFF},
{0x00,0x00,0x00}, {0x49,0x49,0x49}, {0xB6,0x24,0x00}, {0xFF,0x92,0x00},
{0xDB,0xB6,0x6D}, {0x00,0xB6,0x6D}, {0x92,0x92,0xFF}, {0x24,0x92,0x00},
{0x92,0x00,0x6D}, {0x00,0x00,0x00}, {0x92,0xFF,0x6D}, {0x6D,0xB6,0xFF},
{0xB6,0x00,0x6D}, {0x00,0x6D,0x24}, {0x92,0x49,0x00}, {0x00,0x00,0xDB},
{0x92,0x00,0xFF}, {0xB6,0x00,0xFF}, {0x6D,0x6D,0x6D}, {0xFF,0x00,0x92},
{0x00,0x49,0x49}, {0xDB,0xDB,0xDB}, {0x00,0x6D,0xDB}, {0x00,0x49,0x00},
{0x24,0x24,0x24}, {0xFF,0xFF,0x6D}, {0x92,0x92,0x92}, {0xFF,0x00,0xFF},
{0xFF,0xB6,0xFF}, {0xFF,0xFF,0xFF}, {0x6D,0x49,0x00}, {0xFF,0x00,0x00},
{0xFF,0xDB,0x00}, {0x49,0xFF,0xDB}, {0xFF,0xFF,0xFF}, {0x92,0xDB,0xFF},
{0x00,0x00,0x00}, {0xFF,0xB6,0x00}, {0xDB,0x6D,0xFF}, {0xB6,0xDB,0xFF},
{0x6D,0xDB,0x00}, {0xDB,0xB6,0xFF}, {0x00,0xFF,0xFF}, {0x24,0x49,0x00}
}
};
const double Renderer::Palette::Constants::pi = 3.141592653589793;
const double Renderer::Palette::Constants::deg = 0.017453292519943296;
const double Renderer::Palette::Constants::levels[2][4] =
{
{-0.12, 0.00, 0.31, 0.72 },
{ 0.40, 0.68, 1.00, 1.00 }
};
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
inline Renderer::Palette::Custom::Custom()
: emphasis(NULL) {}
inline Renderer::Palette::Custom::~Custom()
{
delete [] emphasis;
}
bool Renderer::Palette::Custom::EnableEmphasis(bool enable)
{
if (!enable)
{
delete [] emphasis;
emphasis = NULL;
}
else if (!emphasis)
{
emphasis = new (std::nothrow) byte [7][64][3];
}
return bool(emphasis) == enable;
}
Renderer::Palette::Palette()
: type(PALETTE_YUV), custom(NULL)
{
}
Renderer::Palette::~Palette()
{
delete custom;
}
Result Renderer::Palette::SetDecoder(const Decoder& d)
{
if (decoder == d)
return RESULT_NOP;
for (uint i=0; i < 3; ++i)
{
if (d.axes[i].angle >= 360 || d.axes[i].gain > 2.0)
return RESULT_ERR_INVALID_PARAM;
}
decoder = d;
return RESULT_OK;
}
void Renderer::Palette::Store(const double (&src)[3],byte (&dst)[3])
{
for (uint i=0; i < 3; ++i)
dst[i] = Clamp<0,255>( src[i] * 255 + 0.5 );
}
Result Renderer::Palette::LoadCustom(const byte (*colors)[3],const bool emphasis)
{
if (!colors)
return RESULT_ERR_INVALID_PARAM;
if ((custom == NULL && NULL == (custom = new (std::nothrow) Custom)) || !custom->EnableEmphasis( emphasis ))
return RESULT_ERR_OUT_OF_MEMORY;
std::memcpy( custom->palette, colors, 64*3 );
if (emphasis)
std::memcpy( custom->emphasis, colors + 64, 7*64*3 );
return RESULT_OK;
}
uint Renderer::Palette::SaveCustom(byte (*colors)[3],const bool emphasis) const
{
if (!colors)
return 0;
std::memcpy( colors, custom ? custom->palette : pc10Palette, 64*3 );
if (!emphasis || !custom || !custom->emphasis)
return 64;
std::memcpy( colors + 64, custom->emphasis, 7*64*3 );
return 7*64;
}
bool Renderer::Palette::ResetCustom()
{
if (custom)
{
custom->EnableEmphasis( false );
std::memcpy( custom->palette, pc10Palette, 64*3 );
return true;
}
return false;
}
Result Renderer::Palette::SetType(PaletteType t)
{
if (t == type)
return RESULT_NOP;
if (t == PALETTE_CUSTOM && !custom)
{
if (NULL == (custom = new (std::nothrow) Custom))
return RESULT_ERR_OUT_OF_MEMORY;
ResetCustom();
}
type = t;
return RESULT_OK;
}
void Renderer::Palette::GenerateEmphasis(uint tint,double s,double& y,double& i,double& q)
{
if (tint == 7)
{
y = y * (0.79399 * 1.13) - (0.0782838 * 1.13);
}
else
{
s = s * (0.5 - 0.79399 * 0.5) + 0.0782838 * 0.5;
y -= s * 0.5;
if (tint >= 3 && tint != 4)
{
s *= 0.6;
y -= s;
}
static const byte tints[8] =
{
0, 6, 10, 8, 2, 4, 0, 0
};
const double a = Constants::pi / 12 * (tints[tint] * 2 - 7);
i += std::sin( a ) * s;
q += std::cos( a ) * s;
}
}
void Renderer::Palette::Build(const int bi,const int si,const int ci,const int hue)
{
NST_ASSERT( type != PALETTE_YUV );
const double brightness = bi / 200.0;
const double saturation = (si + 100) / 100.0;
const double contrast = (ci + 100) / 100.0;
const double matrix[6] =
{
std::sin( (90 - 33 - hue) * Constants::deg ) * (0.570 * 2),
std::cos( (90 - 33 - hue) * Constants::deg ) * (0.570 * 2),
std::sin( (236 - 33 - hue) * Constants::deg ) * (0.351 * 2),
std::cos( (236 - 33 - hue) * Constants::deg ) * (0.351 * 2),
std::sin( (0 - 33 - hue) * Constants::deg ) * (1.015 * 2),
std::cos( (0 - 33 - hue) * Constants::deg ) * (1.015 * 2)
};
const byte (*from)[3] =
(
type == PALETTE_CUSTOM ? custom->palette :
type == PALETTE_VS1 ? vsPalette[0] :
type == PALETTE_VS2 ? vsPalette[1] :
type == PALETTE_VS3 ? vsPalette[2] :
type == PALETTE_VS4 ? vsPalette[3] :
pc10Palette
);
NST_ASSERT( from );
for (uint i=0; i < 8; ++i)
{
if (i && type == PALETTE_CUSTOM && custom->emphasis)
from = custom->emphasis[i-1];
for (uint j=0; j < 64; ++j)
{
double rgb[3] =
{
from[j][0] / 255.0,
from[j][1] / 255.0,
from[j][2] / 255.0
};
if (i && type != PALETTE_CUSTOM)
{
switch (i)
{
case 1: rgb[0] = 1; break;
case 2: rgb[1] = 1; break;
case 3: rgb[0] = 1; rgb[1] = 1; break;
case 4: rgb[2] = 1; break;
case 5: rgb[0] = 1; rgb[2] = 1; break;
case 6: rgb[1] = 1; rgb[2] = 1; break;
case 7: rgb[0] = 1; rgb[1] = 1; rgb[2] = 1; break;
}
}
double yiq[3] =
{
0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2],
0.596 * rgb[0] - 0.275 * rgb[1] - 0.321 * rgb[2],
0.212 * rgb[0] - 0.523 * rgb[1] + 0.311 * rgb[2]
};
if (i && type == PALETTE_CUSTOM && !custom->emphasis && (j & 0xF) <= 0xD)
GenerateEmphasis( i, Constants::levels[(j & 0xF) != 0xD][j >> 4 & 0x3], yiq[0], yiq[1], yiq[2] );
yiq[0] = yiq[0] * contrast + brightness;
yiq[1] *= saturation;
yiq[2] *= saturation;
for (uint k=0; k < 3; ++k)
rgb[k] = yiq[0] + matrix[k*2+0] * yiq[1] + matrix[k*2+1] * yiq[2];
Store( rgb, palette[(i * 64) + j] );
}
}
}
void Renderer::Palette::Generate(const int b,const int s,const int c,int hue)
{
NST_ASSERT( type == PALETTE_YUV );
const double brightness = b / 200.0;
const double saturation = (s + 100) / 100.0;
const double contrast = (c + 100) / 100.0;
hue += 33;
const double matrix[6] =
{
std::sin( (int(decoder.axes[0].angle) - hue) * Constants::deg ) * decoder.axes[0].gain * 2,
std::cos( (int(decoder.axes[0].angle) - hue) * Constants::deg ) * decoder.axes[0].gain * 2,
std::sin( (int(decoder.axes[1].angle) - hue) * Constants::deg ) * decoder.axes[1].gain * 2,
std::cos( (int(decoder.axes[1].angle) - hue) * Constants::deg ) * decoder.axes[1].gain * 2,
std::sin( (int(decoder.axes[2].angle) - hue) * Constants::deg ) * decoder.axes[2].gain * 2,
std::cos( (int(decoder.axes[2].angle) - hue) * Constants::deg ) * decoder.axes[2].gain * 2
};
for (uint n=0; n < PALETTE; ++n)
{
double level[2] =
{
Constants::levels[0][n >> 4 & 3],
Constants::levels[1][n >> 4 & 3]
};
const int color = n & 0x0F;
if (color == 0x00)
{
level[0] = level[1];
}
else if (color == 0x0D)
{
level[1] = level[0];
}
else if (color > 0x0D)
{
level[0] = 0;
level[1] = 0;
}
double y = (level[1] + level[0]) * 0.5;
double s = (level[1] - level[0]) * 0.5;
double h = Constants::pi / 6 * (color - 3);
double i = std::sin( h ) * s;
double q = std::cos( h ) * s;
const uint tint = n >> 6 & 7;
if (tint && color <= 0x0D)
GenerateEmphasis( tint, level[1], y, i, q );
if (decoder.boostYellow)
{
const double yellowness = i - q;
if (yellowness > DBL_EPSILON)
{
i = i + yellowness * ((n >> 4 & 3) / 4.0);
q = q - yellowness * ((n >> 4 & 3) / 4.0);
}
}
i *= saturation;
q *= saturation;
y = y * contrast + brightness;
const double rgb[3] =
{
y + matrix[0] * i + matrix[1] * q,
y + matrix[2] * i + matrix[3] * q,
y + matrix[4] * i + matrix[5] * q
};
Store( rgb, palette[n] );
}
}
void Renderer::Palette::Update(int brightness,int saturation,int contrast,int hue)
{
FpuPrecision precision;
(*this.*(type == PALETTE_YUV ? &Palette::Generate : &Palette::Build))( brightness, saturation, contrast, hue );
}
inline const Renderer::PaletteEntries& Renderer::Palette::Get() const
{
return palette;
}
Renderer::Filter::Format::Format(const RenderState& state)
: bpp(state.bits.count)
{
for (uint i=0; i < 3; ++i)
{
ulong mask = (i == 0 ? state.bits.mask.r : i == 1 ? state.bits.mask.g : state.bits.mask.b);
shifts[i] = 0;
if (mask)
{
while (!(mask & 0x1))
{
mask >>= 1;
shifts[i]++;
}
}
masks[i] = mask;
}
}
Renderer::Filter::Filter(const RenderState& state)
: format(state) {}
void Renderer::Filter::Transform(const byte (&src)[PALETTE][3],Input::Palette& dst) const
{
for (uint i=0; i < PALETTE; ++i)
{
dst[i] =
(
((src[i][0] * format.masks[0] + 0x7F) / 0xFF) << format.shifts[0] |
((src[i][1] * format.masks[1] + 0x7F) / 0xFF) << format.shifts[1] |
((src[i][2] * format.masks[2] + 0x7F) / 0xFF) << format.shifts[2]
);
}
}
Renderer::State::State()
:
width (0),
height (0),
filter (RenderState::FILTER_NONE),
update (UPDATE_PALETTE),
fieldMerging (0),
brightness (0),
saturation (0),
hue (0),
contrast (0),
sharpness (0),
resolution (0),
bleed (0),
artifacts (0),
fringing (0),
blendPixels (1),
xbr_corner_rounding(0)
{
mask.r = 0;
mask.g = 0;
mask.b = 0;
}
Renderer::Renderer()
: filter(NULL) {}
Renderer::~Renderer()
{
delete filter;
}
Result Renderer::SetState(const RenderState& renderState)
{
if (filter)
{
if
(
state.filter == renderState.filter &&
state.width == renderState.width &&
state.height == renderState.height &&
filter->format.bpp == renderState.bits.count &&
state.mask.r == renderState.bits.mask.r &&
state.mask.g == renderState.bits.mask.g &&
state.mask.b == renderState.bits.mask.b
)
return RESULT_NOP;
delete filter;
filter = NULL;
}
try
{
switch (renderState.filter)
{
case RenderState::FILTER_NONE:
if (FilterNone::Check( renderState ))
filter = new FilterNone( renderState );
break;
#ifndef NST_NO_SCALEX
case RenderState::FILTER_SCALE2X:
case RenderState::FILTER_SCALE3X:
if (FilterScaleX::Check( renderState ))
filter = new FilterScaleX( renderState );
break;
#endif
#ifndef NST_NO_HQ2X
case RenderState::FILTER_HQ2X:
case RenderState::FILTER_HQ3X:
case RenderState::FILTER_HQ4X:
if (FilterHqX::Check( renderState ))
filter = new FilterHqX( renderState );
break;
#endif
#ifndef NST_NO_2XSAI
case RenderState::FILTER_2XSAI:
if (Filter2xSaI::Check( renderState ))
filter = new Filter2xSaI( renderState );
break;
#endif
#ifndef NO_NTSC
case RenderState::FILTER_NTSC:
if (FilterNtsc::Check( renderState ))
{
filter = new FilterNtsc
(
renderState,
GetPalette(),
state.sharpness,
state.resolution,
state.bleed,
state.artifacts,
state.fringing,
state.fieldMerging
);
}
break;
#endif
#ifndef NST_NO_XBR
case RenderState::FILTER_2XBR:
case RenderState::FILTER_3XBR:
case RenderState::FILTER_4XBR:
if (FilterxBR::Check( renderState ))
filter = new FilterxBR( renderState, state.blendPixels, state.xbr_corner_rounding );
break;
#endif
}
}
catch (const std::bad_alloc&)
{
delete filter;
filter = NULL;
return RESULT_ERR_OUT_OF_MEMORY;
}
if (filter)
{
state.filter = renderState.filter;
state.width = renderState.width;
state.height = renderState.height;
state.mask = renderState.bits.mask;
if (state.filter == RenderState::FILTER_NTSC)
state.update = 0;
else
state.update |= uint(State::UPDATE_FILTER);
return RESULT_OK;
}
else
{
return RESULT_ERR_UNSUPPORTED;
}
}
Result Renderer::GetState(RenderState& output) const
{
if (filter)
{
output.filter = static_cast<RenderState::Filter>(state.filter);
output.width = state.width;
output.height = state.height;
output.bits.count = filter->format.bpp;
output.bits.mask = state.mask;
return RESULT_OK;
}
return RESULT_ERR_NOT_READY;
}
void Renderer::EnableFieldMerging(bool fieldMerging)
{
const bool old = state.fieldMerging;
state.fieldMerging &= uint(State::FIELD_MERGING_FORCED);
if (fieldMerging)
state.fieldMerging |= uint(State::FIELD_MERGING_USER);
if (bool(state.fieldMerging) != old)
state.update |= uint(State::UPDATE_NTSC);
}
void Renderer::EnableForcedFieldMerging(bool fieldMerging)
{
const bool old = state.fieldMerging;
state.fieldMerging &= uint(State::FIELD_MERGING_USER);
if (fieldMerging)
state.fieldMerging |= uint(State::FIELD_MERGING_FORCED);
if (bool(state.fieldMerging) != old)
state.update |= uint(State::UPDATE_NTSC);
}
Result Renderer::SetHue(int hue)
{
if (hue < -45 || hue > 45)
return RESULT_ERR_INVALID_PARAM;
if (state.hue == hue)
return RESULT_NOP;
state.hue = hue;
state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
return RESULT_OK;
}
Result Renderer::SetLevel(schar& type,int value,uint update)
{
if (value < -100 || value > 100)
return RESULT_ERR_INVALID_PARAM;
if (type == value)
return RESULT_NOP;
type = value;
state.update |= update;
return RESULT_OK;
}
Result Renderer::SetDecoder(const Decoder& decoder)
{
const Result result = palette.SetDecoder( decoder );
if (NES_SUCCEEDED(result) && result != RESULT_NOP && palette.GetType() == PALETTE_YUV)
state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
return result;
}
Result Renderer::SetPaletteType(PaletteType type)
{
const Result result = palette.SetType( type );
if (NES_SUCCEEDED(result) && result != RESULT_NOP)
state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
return result;
}
Result Renderer::LoadCustomPalette(const byte (*colors)[3],const bool emphasis)
{
const Result result = palette.LoadCustom( colors, emphasis );
if (NES_SUCCEEDED(result) && result != RESULT_NOP && palette.GetType() == PALETTE_CUSTOM)
state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
return result;
}
void Renderer::ResetCustomPalette()
{
if (palette.ResetCustom() && palette.GetType() == PALETTE_CUSTOM)
state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
}
const Renderer::PaletteEntries& Renderer::GetPalette()
{
if (state.update & uint(State::UPDATE_PALETTE))
{
state.update &= ~uint(State::UPDATE_PALETTE);
palette.Update( state.brightness, state.saturation, state.contrast, state.hue );
}
return palette.Get();
}
void Renderer::UpdateFilter(Input& input)
{
NST_VERIFY( state.update );
if (state.filter == RenderState::FILTER_NTSC || state.update == 1)
{
RenderState renderState;
GetState( renderState );
delete filter;
filter = NULL;
SetState( renderState );
}
else if (state.update & uint(State::UPDATE_FILTER))
{
filter->Transform( GetPalette(), input.palette );
}
state.update = 0;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void Renderer::Blit(Output& output,Input& input,uint burstPhase)
{
if (filter)
{
if (state.update)
UpdateFilter( input );
if (Output::lockCallback( output ))
{
NST_VERIFY( std::labs(output.pitch) >= dword(state.width) << (filter->format.bpp / 16) );
filter->bgColor = bgColor;
if (std::labs(output.pitch) >= dword(state.width) << (filter->format.bpp / 16))
filter->Blit( input, output, burstPhase );
Output::unlockCallback( output );
}
}
}
}
}
}