mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
838 lines
25 KiB
C++
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 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|