//////////////////////////////////////////////////////////////////////////////////////// // // 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 #include #include #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(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 ); } } } } } }