WIP HD packs

This commit is contained in:
Sour 2021-05-09 23:14:30 -04:00
parent 5147f2db5d
commit 3e90df5ef4
41 changed files with 8344 additions and 631 deletions

View file

@ -48,7 +48,15 @@
<ClInclude Include="Gameboy\GbsHeader.h" />
<ClInclude Include="NES\BaseNesPpu.h" />
<ClInclude Include="NES\DefaultNesPpu.h" />
<ClInclude Include="NES\HdPacks\HdAudioDevice.h" />
<ClInclude Include="NES\HdPacks\HdData.h" />
<ClInclude Include="NES\HdPacks\HdNesPack.h" />
<ClInclude Include="NES\HdPacks\HdNesPpu.h" />
<ClInclude Include="NES\HdPacks\HdPackConditions.h" />
<ClInclude Include="NES\HdPacks\HdPackLoader.h" />
<ClInclude Include="NES\HdPacks\HdVideoFilter.h" />
<ClInclude Include="NES\HdPacks\OggMixer.h" />
<ClInclude Include="NES\HdPacks\OggReader.h" />
<ClInclude Include="NES\Input\ArkanoidController.h" />
<ClInclude Include="NES\Input\AsciiTurboFile.h" />
<ClInclude Include="NES\Input\BandaiHyperShot.h" />
@ -362,6 +370,13 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="Gameboy\GbControlManager.cpp" />
<ClCompile Include="NES\HdPacks\HdAudioDevice.cpp" />
<ClCompile Include="NES\HdPacks\HdNesPack.cpp" />
<ClCompile Include="NES\HdPacks\HdNesPpu.cpp" />
<ClCompile Include="NES\HdPacks\HdPackLoader.cpp" />
<ClCompile Include="NES\HdPacks\HdVideoFilter.cpp" />
<ClCompile Include="NES\HdPacks\OggMixer.cpp" />
<ClCompile Include="NES\HdPacks\OggReader.cpp" />
<ClCompile Include="NES\Loaders\FdsLoader.cpp" />
<ClCompile Include="NES\Loaders\iNesLoader.cpp" />
<ClCompile Include="NES\Loaders\NsfLoader.cpp" />

View file

@ -791,6 +791,14 @@
<ClInclude Include="NES\NsfPpu.h" />
<ClInclude Include="NES\HdPacks\HdNesPpu.h" />
<ClInclude Include="NES\BaseNesPpu.h" />
<ClInclude Include="NES\HdPacks\HdAudioDevice.h" />
<ClInclude Include="NES\HdPacks\HdData.h" />
<ClInclude Include="NES\HdPacks\HdNesPack.h" />
<ClInclude Include="NES\HdPacks\HdPackConditions.h" />
<ClInclude Include="NES\HdPacks\HdPackLoader.h" />
<ClInclude Include="NES\HdPacks\HdVideoFilter.h" />
<ClInclude Include="NES\HdPacks\OggMixer.h" />
<ClInclude Include="NES\HdPacks\OggReader.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="SNES\Cpu.cpp">
@ -1217,6 +1225,13 @@
<ClCompile Include="NES\Mappers\NsfMapper.cpp" />
<ClCompile Include="Shared\Video\DrawStringCommand.cpp" />
<ClCompile Include="Shared\Video\SystemHud.cpp" />
<ClCompile Include="NES\HdPacks\HdAudioDevice.cpp" />
<ClCompile Include="NES\HdPacks\HdNesPack.cpp" />
<ClCompile Include="NES\HdPacks\HdPackLoader.cpp" />
<ClCompile Include="NES\HdPacks\HdVideoFilter.cpp" />
<ClCompile Include="NES\HdPacks\OggMixer.cpp" />
<ClCompile Include="NES\HdPacks\OggReader.cpp" />
<ClCompile Include="NES\HdPacks\HdNesPpu.cpp" />
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">

View file

@ -9,13 +9,9 @@ public:
{
}
__forceinline void StoreSpriteAbsoluteAddress()
{
}
__forceinline void StoreTileAbsoluteAddress()
{
}
__forceinline void StoreSpriteInformation(bool verticalMirror, uint16_t tileAddr, uint8_t lineOffset) { }
__forceinline void StoreTileInformation() { }
void* OnBeforeSendFrame() { return nullptr; }
__forceinline void ProcessScanline()
{

View file

@ -0,0 +1,183 @@
#include "stdafx.h"
#include "NES/HdPacks/HdAudioDevice.h"
#include "NES/HdPacks/HdData.h"
#include "NES/HdPacks/OggMixer.h"
#include "NES/NesConsole.h"
#include "Shared/MessageManager.h"
#include "Shared/Emulator.h"
#include "Shared/Audio/SoundMixer.h"
#include "Utilities/Serializer.h"
HdAudioDevice::HdAudioDevice(Emulator* emu, HdPackData* hdData)
{
_emu = emu;
_hdData = hdData;
_album = 0;
_playbackOptions = 0;
_trackError = false;
_sfxVolume = 128;
_bgmVolume = 128;
_oggMixer.reset(new OggMixer());
_oggMixer->SetBgmVolume(_bgmVolume);
_oggMixer->SetSfxVolume(_sfxVolume);
_emu->GetSoundMixer()->RegisterAudioProvider(_oggMixer.get());
}
HdAudioDevice::~HdAudioDevice()
{
_emu->GetSoundMixer()->UnregisterAudioProvider(_oggMixer.get());
}
void HdAudioDevice::Serialize(Serializer& s)
{
int32_t trackOffset = 0;
if(s.IsSaving()) {
trackOffset = _oggMixer->GetBgmOffset();
if(trackOffset < 0) {
_lastBgmTrack = -1;
}
s.Stream(_album, _lastBgmTrack, trackOffset, _sfxVolume, _bgmVolume, _playbackOptions);
} else {
s.Stream(_album, _lastBgmTrack, trackOffset, _sfxVolume, _bgmVolume, _playbackOptions);
if(_lastBgmTrack != -1 && trackOffset > 0) {
PlayBgmTrack(_lastBgmTrack, trackOffset);
}
_oggMixer->SetBgmVolume(_bgmVolume);
_oggMixer->SetSfxVolume(_sfxVolume);
_oggMixer->SetPlaybackOptions(_playbackOptions);
}
}
bool HdAudioDevice::PlayBgmTrack(uint8_t track, uint32_t startOffset)
{
int trackId = _album * 256 + track;
auto result = _hdData->BgmFilesById.find(trackId);
if(result != _hdData->BgmFilesById.end()) {
if(_oggMixer->Play(result->second, false, startOffset)) {
_lastBgmTrack = trackId;
return true;
}
} else {
MessageManager::Log("[HDPack] Invalid album+track combination: " + std::to_string(_album) + ":" + std::to_string(track));
}
return false;
}
bool HdAudioDevice::PlaySfx(uint8_t sfxNumber)
{
auto result = _hdData->SfxFilesById.find(_album * 256 + sfxNumber);
if(result != _hdData->SfxFilesById.end()) {
return !_oggMixer->Play(result->second, true, 0);
} else {
MessageManager::Log("[HDPack] Invalid album+sfx number combination: " + std::to_string(_album) + ":" + std::to_string(sfxNumber));
return false;
}
}
void HdAudioDevice::ProcessControlFlags(uint8_t flags)
{
_oggMixer->SetPausedFlag((flags & 0x01) == 0x01);
if(flags & 0x02) {
_oggMixer->StopBgm();
}
if(flags & 0x04) {
_oggMixer->StopSfx();
}
}
void HdAudioDevice::GetMemoryRanges(MemoryRanges & ranges)
{
bool useAlternateRegisters = (_hdData->OptionFlags & (int)HdPackOptions::AlternateRegisterRange) == (int)HdPackOptions::AlternateRegisterRange;
ranges.SetAllowOverride();
if(useAlternateRegisters) {
for(int i = 0; i < 7; i++) {
ranges.AddHandler(MemoryOperation::Write, 0x3002 + i * 0x10);
}
ranges.AddHandler(MemoryOperation::Read, 0x4018);
ranges.AddHandler(MemoryOperation::Read, 0x4019);
} else {
ranges.AddHandler(MemoryOperation::Any, 0x4100, 0x4106);
}
}
void HdAudioDevice::WriteRam(uint16_t addr, uint8_t value)
{
//$4100/$3002: Playback Options
//$4101/$3012: Playback Control
//$4102/$3022: BGM Volume
//$4103/$3032: SFX Volume
//$4104/$3042: Album Number
//$4105/$3052: Play BGM Track
//$4106/$3062: Play SFX Track
int regNumber = addr > 0x4100 ? (addr & 0xF) : ((addr & 0xF0) >> 4);
switch(regNumber) {
//Playback Options
//Bit 0: Loop BGM
//Bit 1-7: Unused, reserved - must be 0
case 0:
_playbackOptions = value;
_oggMixer->SetPlaybackOptions(_playbackOptions);
break;
//Playback Control
//Bit 0: Toggle Pause/Resume (only affects BGM)
//Bit 1: Stop BGM
//Bit 2: Stop all SFX
//Bit 3-7: Unused, reserved - must be 0
case 1: ProcessControlFlags(value); break;
//BGM Volume: 0 = mute, 255 = max
//Also has an immediate effect on currently playing BGM
case 2:
_bgmVolume = value;
_oggMixer->SetBgmVolume(value);
break;
//SFX Volume: 0 = mute, 255 = max
//Also has an immediate effect on all currently playing SFX
case 3:
_sfxVolume = value;
_oggMixer->SetSfxVolume(value);
break;
//Album number: 0-255 (Allows for up to 64k BGM and SFX tracks)
//No immediate effect - only affects subsequent $4FFE/$4FFF writes
case 4: _album = value; break;
//Play BGM track (0-255 = track number)
//Stop the current BGM and starts a new track
case 5: _trackError = PlayBgmTrack(value, 0); break;
//Play sound effect (0-255 = sfx number)
//Plays a new sound effect (no limit to the number of simultaneous sound effects)
case 6: _trackError = PlaySfx(value); break;
}
}
uint8_t HdAudioDevice::ReadRam(uint16_t addr)
{
//$4100/$4018: Status
//$4101/$4019: Revision
//$4102: 'N' (signature to help detection)
//$4103: 'E'
//$4103: 'A'
switch(addr & 0x7) {
case 0:
//Status
return (
(_oggMixer->IsBgmPlaying() ? 1 : 0) |
(_oggMixer->IsSfxPlaying() ? 2 : 0) |
(_trackError ? 4 : 0)
);
case 1: return 1; //Revision
case 2: return 'N'; //NES
case 3: return 'E'; //Enhanced
case 4: return 'A'; //Audio
}
return 0;
}

View file

@ -0,0 +1,37 @@
#pragma once
#include "stdafx.h"
#include "NES/INesMemoryHandler.h"
#include "Utilities/ISerializable.h"
struct HdPackData;
class Emulator;
class OggMixer;
class HdAudioDevice : public INesMemoryHandler, public ISerializable
{
private:
Emulator* _emu = nullptr;
HdPackData* _hdData = nullptr;
uint8_t _album = 0;
uint8_t _playbackOptions = 0;
bool _trackError = false;
unique_ptr<OggMixer> _oggMixer;
int32_t _lastBgmTrack = 0;
uint8_t _bgmVolume = 0;
uint8_t _sfxVolume = 0;
bool PlayBgmTrack(uint8_t track, uint32_t startOffset);
bool PlaySfx(uint8_t sfxNumber);
void ProcessControlFlags(uint8_t flags);
protected:
void Serialize(Serializer& s) override;
public:
HdAudioDevice(Emulator* emu, HdPackData *hdData);
~HdAudioDevice();
void GetMemoryRanges(MemoryRanges &ranges) override;
void WriteRam(uint16_t addr, uint8_t value) override;
uint8_t ReadRam(uint16_t addr) override;
};

382
Core/NES/HdPacks/HdData.h Normal file
View file

@ -0,0 +1,382 @@
#pragma once
#include "stdafx.h"
#include "NES/NesConstants.h"
#include "Utilities/HexUtilities.h"
struct HdTileKey
{
static constexpr int32_t NoTile = -1;
uint32_t PaletteColors;
uint8_t TileData[16];
int32_t TileIndex;
bool IsChrRamTile = false;
HdTileKey GetKey(bool defaultKey)
{
if(defaultKey) {
HdTileKey copy = *this;
copy.PaletteColors = 0xFFFFFFFF;
return copy;
} else {
return *this;
}
}
uint32_t GetHashCode() const
{
if(IsChrRamTile) {
return CalculateHash((uint8_t*)&PaletteColors, 20);
} else {
uint64_t key = TileIndex | ((uint64_t)PaletteColors << 32);
return CalculateHash((uint8_t*)&key, sizeof(key));
}
}
size_t operator() (const HdTileKey &tile) const {
return tile.GetHashCode();
}
bool operator==(const HdTileKey &other) const
{
if(IsChrRamTile) {
return memcmp((uint8_t*)&PaletteColors, (uint8_t*)&other.PaletteColors, 20) == 0;
} else {
return TileIndex == other.TileIndex && PaletteColors == other.PaletteColors;
}
}
uint32_t CalculateHash(const uint8_t* key, size_t len) const
{
uint32_t result = 0;
for(size_t i = 0; i < len; i += 4) {
uint32_t chunk;
memcpy(&chunk, key, sizeof(uint32_t));
result += chunk;
result = (result << 2) | (result >> 30);
key += 4;
}
return result;
}
bool IsSpriteTile()
{
return (PaletteColors & 0xFF000000) == 0xFF000000;
}
};
namespace std {
template <> struct hash<HdTileKey>
{
size_t operator()(const HdTileKey& x) const
{
return x.GetHashCode();
}
};
}
struct HdPpuTileInfo : public HdTileKey
{
uint8_t OffsetX;
uint8_t OffsetY;
bool HorizontalMirroring;
bool VerticalMirroring;
bool BackgroundPriority;
uint8_t BgColorIndex;
uint8_t SpriteColorIndex;
uint8_t BgColor;
uint8_t SpriteColor;
uint8_t PpuBackgroundColor;
};
struct HdPpuPixelInfo
{
HdPpuTileInfo Tile;
vector<HdPpuTileInfo> Sprite;
int SpriteCount;
uint16_t TmpVideoRamAddr;
uint8_t XScroll;
uint8_t EmphasisBits;
bool Grayscale;
HdPpuPixelInfo()
{
for(int i = 0; i < 4; i++) {
Sprite.push_back(HdPpuTileInfo());
}
}
};
struct HdScreenInfo
{
HdPpuPixelInfo* ScreenTiles;
std::unordered_map<uint32_t, uint8_t> WatchedAddressValues;
uint32_t FrameNumber;
HdScreenInfo(const HdScreenInfo& that) = delete;
HdScreenInfo(bool isChrRamGame)
{
ScreenTiles = new HdPpuPixelInfo[NesConstants::ScreenPixelCount];
for(int i = 0; i < NesConstants::ScreenPixelCount; i++) {
ScreenTiles[i].Tile.BackgroundPriority = false;
ScreenTiles[i].Tile.IsChrRamTile = isChrRamGame;
ScreenTiles[i].Tile.HorizontalMirroring = false;
ScreenTiles[i].Tile.VerticalMirroring = false;
for(int j = 0; j < 4; j++) {
ScreenTiles[i].Sprite[j].IsChrRamTile = isChrRamGame;
}
}
}
~HdScreenInfo()
{
delete[] ScreenTiles;
}
};
struct HdPackCondition
{
string Name;
virtual string GetConditionName() = 0;
virtual bool IsExcludedFromFile() { return Name.size() > 0 && Name[0] == '!'; }
virtual string ToString() = 0;
virtual ~HdPackCondition() { }
void ClearCache()
{
_resultCache = -1;
}
bool CheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile)
{
if(_resultCache == -1) {
bool result = InternalCheckCondition(screenInfo, x, y, tile);
if(Name[0] == '!') {
result = !result;
}
if(_useCache) {
_resultCache = result ? 1 : 0;
}
return result;
} else {
return (bool)_resultCache;
}
}
protected:
int8_t _resultCache = -1;
bool _useCache = false;
virtual bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) = 0;
};
struct HdPackTileInfo : public HdTileKey
{
uint32_t X;
uint32_t Y;
uint32_t BitmapIndex;
int Brightness;
bool DefaultTile;
bool Blank;
bool HasTransparentPixels;
bool TransparencyRequired;
bool IsFullyTransparent;
vector<uint32_t> HdTileData;
uint32_t ChrBankId;
vector<HdPackCondition*> Conditions;
bool ForceDisableCache;
bool MatchesCondition(HdScreenInfo *hdScreenInfo, int x, int y, HdPpuTileInfo* tile)
{
for(HdPackCondition* condition : Conditions) {
if(!condition->CheckCondition(hdScreenInfo, x, y, tile)) {
return false;
}
}
return true;
}
vector<uint32_t> ToRgb(uint32_t* palette)
{
vector<uint32_t> rgbBuffer;
for(uint8_t i = 0; i < 8; i++) {
uint8_t lowByte = TileData[i];
uint8_t highByte = TileData[i + 8];
for(uint8_t j = 0; j < 8; j++) {
uint8_t color = ((lowByte >> (7 - j)) & 0x01) | (((highByte >> (7 - j)) & 0x01) << 1);
uint32_t rgbColor;
if(IsSpriteTile() || TransparencyRequired) {
rgbColor = color == 0 ? 0x00FFFFFF : palette[(PaletteColors >> ((3 - color) * 8)) & 0x3F];
} else {
rgbColor = palette[(PaletteColors >> ((3 - color) * 8)) & 0x3F];
}
rgbBuffer.push_back(rgbColor);
}
}
return rgbBuffer;
}
void UpdateFlags()
{
Blank = true;
HasTransparentPixels = false;
IsFullyTransparent = true;
for(size_t i = 0; i < HdTileData.size(); i++) {
if(HdTileData[i] != HdTileData[0]) {
Blank = false;
}
if((HdTileData[i] & 0xFF000000) != 0xFF000000) {
HasTransparentPixels = true;
}
if(HdTileData[i] & 0xFF000000) {
IsFullyTransparent = false;
}
}
}
string ToString(int pngIndex)
{
stringstream out;
if(Conditions.size() > 0) {
out << "[";
for(size_t i = 0; i < Conditions.size(); i++) {
if(i > 0) {
out << "&";
}
out << Conditions[i]->Name;
}
out << "]";
}
if(IsChrRamTile) {
out << "<tile>" << pngIndex << ",";
for(int i = 0; i < 16; i++) {
out << HexUtilities::ToHex(TileData[i]);
}
out << "," <<
HexUtilities::ToHex(PaletteColors, true) << "," <<
X << "," <<
Y << "," <<
(double)Brightness / 255 << "," <<
(DefaultTile ? "Y" : "N") << "," <<
ChrBankId << "," <<
TileIndex;
} else {
out << "<tile>" <<
pngIndex << "," <<
HexUtilities::ToHex(TileIndex) << "," <<
HexUtilities::ToHex(PaletteColors, true) << "," <<
X << "," <<
Y << "," <<
(double)Brightness / 255 << "," <<
(DefaultTile ? "Y" : "N");
}
return out.str();
}
};
struct HdPackBitmapInfo
{
vector<uint8_t> PixelData;
uint32_t Width;
uint32_t Height;
};
struct HdBackgroundFileData
{
string PngName;
uint32_t Width;
uint32_t Height;
vector<uint32_t> PixelData;
};
struct HdBackgroundInfo
{
HdBackgroundFileData* Data;
int Brightness;
vector<HdPackCondition*> Conditions;
float HorizontalScrollRatio;
float VerticalScrollRatio;
uint8_t Priority;
uint32_t Left;
uint32_t Top;
uint32_t* data()
{
return Data->PixelData.data();
}
string ToString()
{
stringstream out;
if(Conditions.size() > 0) {
out << "[";
for(size_t i = 0; i < Conditions.size(); i++) {
if(i > 0) {
out << "&";
}
out << Conditions[i]->Name;
}
out << "]";
}
out << "<background>";
out << Data->PngName << ",";
out << (Brightness / 255.0);
return out.str();
}
};
struct HdPackData
{
vector<HdBackgroundInfo> Backgrounds;
vector<unique_ptr<HdBackgroundFileData>> BackgroundFileData;
vector<unique_ptr<HdPackTileInfo>> Tiles;
vector<unique_ptr<HdPackCondition>> Conditions;
unordered_set<uint32_t> WatchedMemoryAddresses;
unordered_map<HdTileKey, vector<HdPackTileInfo*>> TileByKey;
unordered_map<string, string> PatchesByHash;
unordered_map<int, string> BgmFilesById;
unordered_map<int, string> SfxFilesById;
vector<uint32_t> Palette;
bool HasOverscanConfig = false;
OverscanDimensions Overscan;
uint32_t Scale = 1;
uint32_t Version = 0;
uint32_t OptionFlags = 0;
HdPackData() { }
~HdPackData() { }
HdPackData(const HdPackData&) = delete;
HdPackData& operator=(const HdPackData&) = delete;
};
enum class HdPackOptions
{
None = 0,
NoSpriteLimit = 1,
AlternateRegisterRange = 2,
DisableCache = 8,
DontRenderOriginalTiles = 16
};

View file

@ -0,0 +1,423 @@
#include "stdafx.h"
#include <algorithm>
#include <unordered_map>
#include "NES/HdPacks/HdNesPack.h"
#include "NES/HdPacks/HdPackLoader.h"
#include "NES/NesConsole.h"
#include "Shared/MessageManager.h"
#include "Shared/EmuSettings.h"
#include "Utilities/FolderUtilities.h"
#include "Utilities/PNGHelper.h"
HdNesPack::HdNesPack(HdPackData* hdData)
{
_hdData = hdData;
}
HdNesPack::~HdNesPack()
{
}
void HdNesPack::BlendColors(uint8_t output[4], uint8_t input[4])
{
uint8_t invertedAlpha = 256 - input[3];
output[0] = input[0] + (uint8_t)((invertedAlpha * output[0]) >> 8);
output[1] = input[1] + (uint8_t)((invertedAlpha * output[1]) >> 8);
output[2] = input[2] + (uint8_t)((invertedAlpha * output[2]) >> 8);
output[3] = 0xFF;
}
uint32_t HdNesPack::AdjustBrightness(uint8_t input[4], int brightness)
{
return (
std::min(255, (brightness * ((int)input[0] + 1)) >> 8) |
(std::min(255, (brightness * ((int)input[1] + 1)) >> 8) << 8) |
(std::min(255, (brightness * ((int)input[2] + 1)) >> 8) << 16) |
(input[3] << 24)
);
}
void HdNesPack::DrawColor(uint32_t color, uint32_t *outputBuffer, uint32_t scale, uint32_t screenWidth)
{
if(scale == 1) {
*outputBuffer = color;
} else {
for(uint32_t i = 0; i < scale; i++) {
std::fill(outputBuffer, outputBuffer + scale, color);
outputBuffer += screenWidth;
}
}
}
void HdNesPack::DrawCustomBackground(HdBackgroundInfo& bgInfo, uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth)
{
int brightness = bgInfo.Brightness;
uint32_t left = bgInfo.Left;
uint32_t top = bgInfo.Top;
uint32_t width = bgInfo.Data->Width;
uint32_t *pngData = bgInfo.data() + ((top + y) * _hdData->Scale * width) + ((left + x) * _hdData->Scale);
uint32_t pixelColor;
for(uint32_t i = 0; i < scale; i++) {
for(uint32_t j = 0; j < scale; j++) {
if(brightness == 255) {
pixelColor = *pngData;
} else {
pixelColor = AdjustBrightness((uint8_t*)pngData, brightness);
}
if(((uint8_t*)pngData)[3] == 0xFF) {
*outputBuffer = pixelColor;
} else if(((uint8_t*)pngData)[3]) {
BlendColors((uint8_t*)outputBuffer, (uint8_t*)(&pixelColor));
}
outputBuffer++;
pngData++;
}
outputBuffer += screenWidth - scale;
pngData += width - scale;
}
}
void HdNesPack::DrawTile(HdPpuTileInfo &tileInfo, HdPackTileInfo &hdPackTileInfo, uint32_t *outputBuffer, uint32_t screenWidth)
{
if(hdPackTileInfo.IsFullyTransparent) {
return;
}
uint32_t scale = GetScale();
uint32_t *bitmapData = hdPackTileInfo.HdTileData.data();
uint32_t tileWidth = 8 * scale;
uint8_t tileOffsetX = tileInfo.HorizontalMirroring ? 7 - tileInfo.OffsetX : tileInfo.OffsetX;
uint32_t bitmapOffset = (tileInfo.OffsetY * scale) * tileWidth + tileOffsetX * scale;
int32_t bitmapSmallInc = 1;
int32_t bitmapLargeInc = tileWidth - scale;
if(tileInfo.HorizontalMirroring) {
bitmapOffset += scale - 1;
bitmapSmallInc = -1;
bitmapLargeInc = tileWidth + scale;
}
if(tileInfo.VerticalMirroring) {
bitmapOffset += tileWidth * (scale - 1);
bitmapLargeInc = (tileInfo.HorizontalMirroring ? (int32_t)scale : -(int32_t)scale) - (int32_t)tileWidth;
}
uint32_t rgbValue;
if(hdPackTileInfo.HasTransparentPixels || hdPackTileInfo.Brightness != 255) {
for(uint32_t y = 0; y < scale; y++) {
for(uint32_t x = 0; x < scale; x++) {
if(hdPackTileInfo.Brightness == 255) {
rgbValue = *(bitmapData + bitmapOffset);
} else {
rgbValue = AdjustBrightness((uint8_t*)(bitmapData + bitmapOffset), hdPackTileInfo.Brightness);
}
if(!hdPackTileInfo.HasTransparentPixels || (bitmapData[bitmapOffset] & 0xFF000000) == 0xFF000000) {
*outputBuffer = rgbValue;
} else {
if(bitmapData[bitmapOffset] & 0xFF000000) {
BlendColors((uint8_t*)outputBuffer, (uint8_t*)&rgbValue);
}
}
outputBuffer++;
bitmapOffset += bitmapSmallInc;
}
bitmapOffset += bitmapLargeInc;
outputBuffer += screenWidth - scale;
}
} else {
for(uint32_t y = 0; y < scale; y++) {
for(uint32_t x = 0; x < scale; x++) {
*outputBuffer = *(bitmapData + bitmapOffset);
outputBuffer++;
bitmapOffset += bitmapSmallInc;
}
bitmapOffset += bitmapLargeInc;
outputBuffer += screenWidth - scale;
}
}
}
uint32_t HdNesPack::GetScale()
{
return _hdData->Scale;
}
void HdNesPack::OnLineStart(HdPpuPixelInfo &lineFirstPixel, uint8_t y)
{
_scrollX = ((lineFirstPixel.TmpVideoRamAddr & 0x1F) << 3) | lineFirstPixel.XScroll | ((lineFirstPixel.TmpVideoRamAddr & 0x400) ? 0x100 : 0);
_useCachedTile = false;
int32_t scrollY = (((lineFirstPixel.TmpVideoRamAddr & 0x3E0) >> 2) | ((lineFirstPixel.TmpVideoRamAddr & 0x7000) >> 12)) + ((lineFirstPixel.TmpVideoRamAddr & 0x800) ? 240 : 0);
for(int layer = 0; layer < 4; layer++) {
for(int i = 0; i < _activeBgCount[layer]; i++) {
HdBgConfig& cfg = _bgConfig[layer * HdNesPack::PriorityLevelsPerLayer + i];
HdBackgroundInfo& bgInfo = _hdData->Backgrounds[cfg.BackgroundIndex];
cfg.BgScrollX = (int32_t)(_scrollX * bgInfo.HorizontalScrollRatio);
cfg.BgScrollY = (int32_t)(scrollY * bgInfo.VerticalScrollRatio);
if(y >= -cfg.BgScrollY && (y + bgInfo.Top + cfg.BgScrollY + 1) * _hdData->Scale <= bgInfo.Data->Height) {
cfg.BgMinX = -cfg.BgScrollX;
cfg.BgMaxX = bgInfo.Data->Width / _hdData->Scale - bgInfo.Left - cfg.BgScrollX - 1;
} else {
cfg.BgMinX = -1;
cfg.BgMaxX = -1;
}
}
}
}
int32_t HdNesPack::GetLayerIndex(uint8_t priority)
{
for(size_t i = 0; i < _hdData->Backgrounds.size(); i++) {
if(_hdData->Backgrounds[i].Priority != priority) {
continue;
}
bool isMatch = true;
for(HdPackCondition* condition : _hdData->Backgrounds[i].Conditions) {
if(!condition->CheckCondition(_hdScreenInfo, 0, 0, nullptr)) {
isMatch = false;
break;
}
}
if(isMatch) {
return (int32_t)i;
}
}
return -1;
}
uint32_t palette[512] = {};
void HdNesPack::OnBeforeApplyFilter()
{
_palette = palette;
//TODO
//_palette = _hdData->Palette.size() == 0x40 ? _hdData->Palette.data() : _settings->GetRgbPalette();
_cacheEnabled = (_hdData->OptionFlags & (int)HdPackOptions::DisableCache) == 0;
//TODO
/*
if(_hdData->OptionFlags & (int)HdPackOptions::NoSpriteLimit) {
_settings->SetFlags(EmulationFlags::RemoveSpriteLimit | EmulationFlags::AdaptiveSpriteLimit);
}*/
for(int layer = 0; layer < 4; layer++) {
uint32_t activeCount = 0;
for(int i = 0; i < HdNesPack::PriorityLevelsPerLayer; i++) {
int32_t index = GetLayerIndex(layer * HdNesPack::PriorityLevelsPerLayer + i);
if(index >= 0) {
_bgConfig[layer*10+activeCount].BackgroundIndex = index;
activeCount++;
}
}
_activeBgCount[layer] = activeCount;
}
for(unique_ptr<HdPackCondition> &condition : _hdData->Conditions) {
condition->ClearCache();
}
}
HdPackTileInfo* HdNesPack::GetCachedMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile)
{
if(((_scrollX + x) & 0x07) == 0) {
_useCachedTile = false;
}
bool disableCache = false;
HdPackTileInfo* hdPackTileInfo;
if(_useCachedTile) {
hdPackTileInfo = _cachedTile;
} else {
hdPackTileInfo = GetMatchingTile(x, y, tile, &disableCache);
if(!disableCache && _cacheEnabled) {
//Use this tile for the next 8 horizontal pixels
//Disable cache if a sprite condition is used, because sprites are not on a 8x8 grid
_cachedTile = hdPackTileInfo;
_useCachedTile = true;
}
}
return hdPackTileInfo;
}
HdPackTileInfo* HdNesPack::GetMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile, bool* disableCache)
{
auto hdTile = _hdData->TileByKey.find(*tile);
if(hdTile == _hdData->TileByKey.end()) {
hdTile = _hdData->TileByKey.find(tile->GetKey(true));
}
if(hdTile != _hdData->TileByKey.end()) {
for(HdPackTileInfo* hdPackTile : hdTile->second) {
if(disableCache != nullptr && hdPackTile->ForceDisableCache) {
*disableCache = true;
}
if(hdPackTile->MatchesCondition(_hdScreenInfo, x, y, tile)) {
return hdPackTile;
}
}
}
return nullptr;
}
bool HdNesPack::DrawBackgroundLayer(uint8_t priority, uint32_t x, uint32_t y, uint32_t* outputBuffer, uint32_t screenWidth)
{
HdBgConfig bgConfig = _bgConfig[(int)priority];
if((int32_t)x >= bgConfig.BgMinX && (int32_t)x <= bgConfig.BgMaxX) {
HdBackgroundInfo& bgInfo = _hdData->Backgrounds[bgConfig.BackgroundIndex];
DrawCustomBackground(bgInfo, outputBuffer, x + bgConfig.BgScrollX, y + bgConfig.BgScrollY, _hdData->Scale, screenWidth);
return true;
}
return false;
}
void HdNesPack::GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uint32_t *outputBuffer, uint32_t screenWidth)
{
HdPackTileInfo *hdPackTileInfo = nullptr;
HdPackTileInfo *hdPackSpriteInfo = nullptr;
bool hasSprite = pixelInfo.SpriteCount > 0;
bool renderOriginalTiles = ((_hdData->OptionFlags & (int)HdPackOptions::DontRenderOriginalTiles) == 0);
if(pixelInfo.Tile.TileIndex != HdPpuTileInfo::NoTile) {
hdPackTileInfo = GetCachedMatchingTile(x, y, &pixelInfo.Tile);
}
int lowestBgSprite = 999;
DrawColor(_palette[pixelInfo.Tile.PpuBackgroundColor], outputBuffer, _hdData->Scale, screenWidth);
bool hasBackground = false;
for(int i = 0; i < _activeBgCount[0]; i++) {
hasBackground |= DrawBackgroundLayer(HdNesPack::BehindBgSpritesPriority+i, x, y, outputBuffer, screenWidth);
}
if(hasSprite) {
for(int k = pixelInfo.SpriteCount - 1; k >= 0; k--) {
if(pixelInfo.Sprite[k].BackgroundPriority) {
if(pixelInfo.Sprite[k].SpriteColorIndex != 0) {
lowestBgSprite = k;
}
hdPackSpriteInfo = GetMatchingTile(x, y, &pixelInfo.Sprite[k]);
if(hdPackSpriteInfo) {
DrawTile(pixelInfo.Sprite[k], *hdPackSpriteInfo, outputBuffer, screenWidth);
} else if(pixelInfo.Sprite[k].SpriteColorIndex != 0) {
DrawColor(_palette[pixelInfo.Sprite[k].SpriteColor], outputBuffer, _hdData->Scale, screenWidth);
}
}
}
}
for(int i = 0; i < _activeBgCount[1]; i++) {
hasBackground |= DrawBackgroundLayer(HdNesPack::BehindBgPriority+i, x, y, outputBuffer, screenWidth);
}
if(hdPackTileInfo) {
DrawTile(pixelInfo.Tile, *hdPackTileInfo, outputBuffer, screenWidth);
} else if(renderOriginalTiles) {
//Draw regular SD background tile
if(!hasBackground || pixelInfo.Tile.BgColorIndex != 0) {
DrawColor(_palette[pixelInfo.Tile.BgColor], outputBuffer, _hdData->Scale, screenWidth);
}
}
for(int i = 0; i < _activeBgCount[2]; i++) {
DrawBackgroundLayer(HdNesPack::BehindFgSpritesPriority+i, x, y, outputBuffer, screenWidth);
}
if(hasSprite) {
for(int k = pixelInfo.SpriteCount - 1; k >= 0; k--) {
if(!pixelInfo.Sprite[k].BackgroundPriority && lowestBgSprite > k) {
hdPackSpriteInfo = GetMatchingTile(x, y, &pixelInfo.Sprite[k]);
if(hdPackSpriteInfo) {
DrawTile(pixelInfo.Sprite[k], *hdPackSpriteInfo, outputBuffer, screenWidth);
} else if(pixelInfo.Sprite[k].SpriteColorIndex != 0) {
DrawColor(_palette[pixelInfo.Sprite[k].SpriteColor], outputBuffer, _hdData->Scale, screenWidth);
}
}
}
}
for(int i = 0; i < _activeBgCount[3]; i++) {
DrawBackgroundLayer(HdNesPack::ForegroundPriority+i, x, y, outputBuffer, screenWidth);
}
}
void HdNesPack::Process(HdScreenInfo *hdScreenInfo, uint32_t* outputBuffer, OverscanDimensions &overscan)
{
_hdScreenInfo = hdScreenInfo;
uint32_t hdScale = GetScale();
uint32_t screenWidth = (NesConstants::ScreenWidth - overscan.Left - overscan.Right) * hdScale;
OnBeforeApplyFilter();
for(uint32_t i = overscan.Top, iMax = 240 - overscan.Bottom; i < iMax; i++) {
OnLineStart(hdScreenInfo->ScreenTiles[i << 8], i);
uint32_t bufferIndex = (i - overscan.Top) * screenWidth * hdScale;
uint32_t lineStartIndex = bufferIndex;
for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) {
GetPixels(j, i, hdScreenInfo->ScreenTiles[i * 256 + j], outputBuffer + bufferIndex, screenWidth);
bufferIndex += hdScale;
}
ProcessGrayscaleAndEmphasis(hdScreenInfo->ScreenTiles[i * 256], outputBuffer + lineStartIndex, screenWidth);
}
}
void HdNesPack::ProcessGrayscaleAndEmphasis(HdPpuPixelInfo &pixelInfo, uint32_t* outputBuffer, uint32_t hdScreenWidth)
{
//Apply grayscale/emphasis bits on a scanline level (less accurate, but shouldn't cause issues and simpler to implement)
uint32_t scale = GetScale();
if(pixelInfo.Grayscale) {
uint32_t* out = outputBuffer;
for(uint32_t y = 0; y < scale; y++) {
for(uint32_t x = 0; x < hdScreenWidth; x++) {
uint32_t &rgbValue = out[x];
uint8_t average = (((rgbValue >> 16) & 0xFF) + ((rgbValue >> 8) & 0xFF) + (rgbValue & 0xFF)) / 3;
rgbValue = (rgbValue & 0xFF000000) | (average << 16) | (average << 8) | average;
}
out += hdScreenWidth;
}
}
if(pixelInfo.EmphasisBits) {
uint8_t emphasisBits = pixelInfo.EmphasisBits;
double red = 1.0, green = 1.0, blue = 1.0;
if(emphasisBits & 0x01) {
//Intensify red
red *= 1.1;
green *= 0.9;
blue *= 0.9;
}
if(emphasisBits & 0x02) {
//Intensify green
green *= 1.1;
red *= 0.9;
blue *= 0.9;
}
if(emphasisBits & 0x04) {
//Intensify blue
blue *= 1.1;
red *= 0.9;
green *= 0.9;
}
uint32_t* out = outputBuffer;
for(uint32_t y = 0; y < scale; y++) {
for(uint32_t x = 0; x < hdScreenWidth; x++) {
uint32_t &rgbValue = out[x];
rgbValue = 0xFF000000 |
(std::min<uint16_t>((uint16_t)(((rgbValue >> 16) & 0xFF) * red), 255) << 16) |
(std::min<uint16_t>((uint16_t)(((rgbValue >> 8) & 0xFF) * green), 255) << 8) |
std::min<uint16_t>((uint16_t)((rgbValue & 0xFF) * blue), 255);
}
out += hdScreenWidth;
}
}
}

View file

@ -0,0 +1,61 @@
#pragma once
#include "stdafx.h"
#include "NES/HdPacks/HdData.h"
class HdNesPack
{
private:
struct HdBgConfig
{
int32_t BackgroundIndex = -1;
int32_t BgScrollX = 0;
int32_t BgScrollY = 0;
int16_t BgMinX = -1;
int16_t BgMaxX = -1;
};
HdPackData* _hdData;
static constexpr uint8_t PriorityLevelsPerLayer = 10;
static constexpr uint8_t BehindBgSpritesPriority = 0 * PriorityLevelsPerLayer;
static constexpr uint8_t BehindBgPriority = 1 * PriorityLevelsPerLayer;
static constexpr uint8_t BehindFgSpritesPriority = 2 * PriorityLevelsPerLayer;
static constexpr uint8_t ForegroundPriority = 3 * PriorityLevelsPerLayer;
uint8_t _activeBgCount[4] = {};
HdBgConfig _bgConfig[40] = {};
HdScreenInfo *_hdScreenInfo = nullptr;
uint32_t* _palette = nullptr;
HdPackTileInfo* _cachedTile = nullptr;
bool _cacheEnabled = false;
bool _useCachedTile = false;
int32_t _scrollX = 0;
__forceinline void BlendColors(uint8_t output[4], uint8_t input[4]);
__forceinline uint32_t AdjustBrightness(uint8_t input[4], int brightness);
__forceinline void DrawColor(uint32_t color, uint32_t* outputBuffer, uint32_t scale, uint32_t screenWidth);
__forceinline void DrawTile(HdPpuTileInfo &tileInfo, HdPackTileInfo &hdPackTileInfo, uint32_t* outputBuffer, uint32_t screenWidth);
__forceinline HdPackTileInfo* GetCachedMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile);
__forceinline HdPackTileInfo* GetMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile, bool* disableCache = nullptr);
__forceinline bool DrawBackgroundLayer(uint8_t priority, uint32_t x, uint32_t y, uint32_t* outputBuffer, uint32_t screenWidth);
__forceinline void DrawCustomBackground(HdBackgroundInfo& bgInfo, uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth);
void OnLineStart(HdPpuPixelInfo &lineFirstPixel, uint8_t y);
int32_t GetLayerIndex(uint8_t priority);
void OnBeforeApplyFilter();
__forceinline void GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uint32_t *outputBuffer, uint32_t screenWidth);
__forceinline void ProcessGrayscaleAndEmphasis(HdPpuPixelInfo &pixelInfo, uint32_t* outputBuffer, uint32_t hdScreenWidth);
public:
static constexpr uint32_t CurrentVersion = 106;
HdNesPack(HdPackData* hdData);
~HdNesPack();
uint32_t GetScale();
void Process(HdScreenInfo *hdScreenInfo, uint32_t *outputBuffer, OverscanDimensions &overscan);
};

View file

@ -0,0 +1,51 @@
#include "stdafx.h"
#include "NES/HdPacks/HdNesPpu.h"
#include "NES/NesConsole.h"
#include "NES/HdPacks/HdPackConditions.h"
#include "NES/NesMemoryManager.h"
#include "NES/BaseMapper.h"
#include "NES/HdPacks/HdData.h"
HdNesPpu::HdNesPpu(NesConsole* console, HdPackData* hdData) : NesPpu(console)
{
_hdData = hdData;
if(_hdData) {
_version = _hdData->Version;
bool isChrRamGame = !console->GetMapper()->HasChrRom();
_screenInfo[0] = new HdScreenInfo(isChrRamGame);
_screenInfo[1] = new HdScreenInfo(isChrRamGame);
_info = _screenInfo[0];
}
}
HdNesPpu::~HdNesPpu()
{
if(_hdData) {
delete _screenInfo[0];
delete _screenInfo[1];
}
}
void* HdNesPpu::OnBeforeSendFrame()
{
HdScreenInfo* info = _info;
info->FrameNumber = _frameCount;
info->WatchedAddressValues.clear();
for(uint32_t address : _hdData->WatchedMemoryAddresses) {
if(address & HdPackBaseMemoryCondition::PpuMemoryMarker) {
if((address & 0x3FFF) >= 0x3F00) {
info->WatchedAddressValues[address] = ReadPaletteRAM(address);
} else {
info->WatchedAddressValues[address] = _console->GetMapper()->DebugReadVram(address & 0x3FFF, true);
}
} else {
info->WatchedAddressValues[address] = _console->GetMemoryManager()->DebugRead(address);
}
}
_info = (_info == _screenInfo[0]) ? _screenInfo[1] : _screenInfo[0];
return info;
}

View file

@ -1,22 +1,62 @@
#pragma once
#include "stdafx.h"
#include "NES/NesPpu.h"
#include "NES/NesConsole.h"
#include "NES/HdPacks/HdPackConditions.h"
#include "NES/NesMemoryManager.h"
#include "NES/BaseMapper.h"
#include "NES/HdPacks/HdData.h"
class HdNesPpu final : public NesPpu<HdNesPpu>
{
struct NesSpriteInfoEx
{
uint32_t AbsoluteTileAddr;
uint16_t TileAddr;
bool VerticalMirror;
uint8_t OffsetY;
};
struct NesTileInfoEx
{
uint32_t AbsoluteTileAddr;
uint8_t OffsetY;
};
HdScreenInfo* _screenInfo[2] = {};
HdScreenInfo* _info = nullptr;
uint32_t _version = 0;
HdPackData* _hdData = nullptr;
NesSpriteInfoEx _exSpriteInfo[64] = {};
NesTileInfoEx _previousTileEx = {};
NesTileInfoEx _currentTileEx = {};
NesTileInfoEx _nextTileEx = {};
public:
HdNesPpu(NesConsole* console) : NesPpu(console)
HdNesPpu(NesConsole* console, HdPackData* hdData);
virtual ~HdNesPpu();
void* OnBeforeSendFrame();
__forceinline void StoreSpriteInformation(bool verticalMirror, uint16_t tileAddr, uint8_t lineOffset)
{
NesSpriteInfoEx& info = _exSpriteInfo[_spriteIndex];
info.TileAddr = tileAddr;
info.AbsoluteTileAddr = _mapper->GetPpuAbsoluteAddress(info.TileAddr).Address;
info.VerticalMirror = verticalMirror;
info.OffsetY = lineOffset;
}
__forceinline void StoreSpriteAbsoluteAddress()
__forceinline void StoreTileInformation()
{
_spriteTiles[_spriteIndex].AbsoluteTileAddr = _mapper->GetAbsoluteAddress(_spriteTiles[_spriteIndex].TileAddr).Address;
}
_previousTileEx = _currentTileEx;
_currentTileEx = _nextTileEx;
__forceinline void StoreTileAbsoluteAddress()
{
_tile.AbsoluteTileAddr = _mapper->GetPpuAbsoluteAddress(_tile.TileAddr).Address;
uint8_t tileIndex = ReadVram(GetNameTableAddr());
uint16_t tileAddr = (tileIndex << 4) | (_videoRamAddr >> 12) | _backgroundPatternAddr;
_nextTileEx.OffsetY = _videoRamAddr >> 12;
_nextTileEx.AbsoluteTileAddr = _mapper->GetPpuAbsoluteAddress(tileAddr).Address;
}
__forceinline void ProcessScanline()
@ -24,15 +64,115 @@ public:
ProcessScanlineImpl();
}
__forceinline void DrawPixel()
void DrawPixel()
{
//This is called 3.7 million times per second - needs to be as fast as possible.
if(IsRenderingEnabled() || ((_state.VideoRamAddr & 0x3F00) != 0x3F00)) {
uint16_t bufferOffset = (_scanline << 8) + _cycle - 1;
uint16_t& pixel = _currentOutputBuffer[bufferOffset];
_lastSprite = nullptr;
if(IsRenderingEnabled() || ((_videoRamAddr & 0x3F00) != 0x3F00)) {
bool isChrRam = !_console->GetMapper()->HasChrRom();
BaseMapper* mapper = _console->GetMapper();
uint32_t color = GetPixelColor();
_currentOutputBuffer[(_scanline << 8) + _cycle - 1] = _paletteRAM[color & 0x03 ? color : 0];
pixel = (_paletteRAM[color & 0x03 ? color : 0] & _paletteRamMask) | _intensifyColorBits;
uint8_t tilePalette = (_xScroll + ((_cycle - 1) & 0x07) < 8) ? _previousTilePalette : _currentTilePalette;
NesTileInfoEx& lastTileEx = (_xScroll + ((_cycle - 1) & 0x07) < 8) ? _previousTileEx : _currentTileEx;
uint32_t backgroundColor = 0;
if(_backgroundEnabled && _cycle > _minimumDrawBgCycle) {
backgroundColor = (((_lowBitShift << _xScroll) & 0x8000) >> 15) | (((_highBitShift << _xScroll) & 0x8000) >> 14);
}
HdPpuPixelInfo& tileInfo = _info->ScreenTiles[bufferOffset];
tileInfo.Grayscale = _paletteRamMask == 0x30;
tileInfo.EmphasisBits = _intensifyColorBits >> 6;
tileInfo.Tile.PpuBackgroundColor = ReadPaletteRAM(0);
tileInfo.Tile.BgColorIndex = backgroundColor;
if(backgroundColor == 0) {
tileInfo.Tile.BgColor = tileInfo.Tile.PpuBackgroundColor;
} else {
tileInfo.Tile.BgColor = ReadPaletteRAM(tilePalette + backgroundColor);
}
tileInfo.XScroll = _xScroll;
tileInfo.TmpVideoRamAddr = _tmpVideoRamAddr;
if(_lastSprite && _spritesEnabled) {
int j = 0;
for(uint8_t i = 0; i < _spriteCount; i++) {
int32_t shift = (int32_t)_cycle - _spriteTiles[i].SpriteX - 1;
NesSpriteInfo& sprite = _spriteTiles[i];
NesSpriteInfoEx& spriteEx = _exSpriteInfo[i];
if(shift >= 0 && shift < 8) {
tileInfo.Sprite[j].TileIndex = spriteEx.AbsoluteTileAddr / 16;
if(isChrRam) {
mapper->CopyChrTile(spriteEx.AbsoluteTileAddr & 0xFFFFFFF0, tileInfo.Sprite[j].TileData);
}
if(_version >= 100) {
tileInfo.Sprite[j].PaletteColors = 0xFF000000 | _paletteRAM[sprite.PaletteOffset + 3] | (_paletteRAM[sprite.PaletteOffset + 2] << 8) | (_paletteRAM[sprite.PaletteOffset + 1] << 16);
} else {
tileInfo.Sprite[j].PaletteColors = _paletteRAM[sprite.PaletteOffset + 3] | (_paletteRAM[sprite.PaletteOffset + 2] << 8) | (_paletteRAM[sprite.PaletteOffset + 1] << 16);
}
if(spriteEx.OffsetY >= 8) {
tileInfo.Sprite[j].OffsetY = spriteEx.OffsetY - 8;
} else {
tileInfo.Sprite[j].OffsetY = spriteEx.OffsetY;
}
tileInfo.Sprite[j].OffsetX = shift;
tileInfo.Sprite[j].HorizontalMirroring = sprite.HorizontalMirror;
tileInfo.Sprite[j].VerticalMirroring = spriteEx.VerticalMirror;
tileInfo.Sprite[j].BackgroundPriority = sprite.BackgroundPriority;
int32_t shift = (int32_t)_cycle - sprite.SpriteX - 1;
if(sprite.HorizontalMirror) {
tileInfo.Sprite[j].SpriteColorIndex = ((sprite.LowByte >> shift) & 0x01) | ((sprite.HighByte >> shift) & 0x01) << 1;
} else {
tileInfo.Sprite[j].SpriteColorIndex = ((sprite.LowByte << shift) & 0x80) >> 7 | ((sprite.HighByte << shift) & 0x80) >> 6;
}
if(tileInfo.Sprite[j].SpriteColorIndex == 0) {
tileInfo.Sprite[j].SpriteColor = ReadPaletteRAM(0);
} else {
tileInfo.Sprite[j].SpriteColor = ReadPaletteRAM(sprite.PaletteOffset + tileInfo.Sprite[j].SpriteColorIndex);
}
tileInfo.Sprite[j].PpuBackgroundColor = tileInfo.Tile.PpuBackgroundColor;
tileInfo.Sprite[j].BgColorIndex = tileInfo.Tile.BgColorIndex;
j++;
if(j >= 4) {
break;
}
}
}
tileInfo.SpriteCount = j;
} else {
tileInfo.SpriteCount = 0;
}
if(_backgroundEnabled && _cycle > _minimumDrawBgCycle) {
tileInfo.Tile.TileIndex = lastTileEx.AbsoluteTileAddr / 16;
if(isChrRam) {
mapper->CopyChrTile(lastTileEx.AbsoluteTileAddr & 0xFFFFFFF0, tileInfo.Tile.TileData);
}
if(_version >= 100) {
tileInfo.Tile.PaletteColors = _paletteRAM[tilePalette + 3] | (_paletteRAM[tilePalette + 2] << 8) | (_paletteRAM[tilePalette + 1] << 16) | (_paletteRAM[0] << 24);
} else {
tileInfo.Tile.PaletteColors = _paletteRAM[tilePalette + 3] | (_paletteRAM[tilePalette + 2] << 8) | (_paletteRAM[tilePalette + 1] << 16);
}
tileInfo.Tile.OffsetY = lastTileEx.OffsetY;
tileInfo.Tile.OffsetX = (_xScroll + ((_cycle - 1) & 0x07)) & 0x07;
} else {
tileInfo.Tile.TileIndex = HdPpuTileInfo::NoTile;
}
} else {
//"If the current VRAM address points in the range $3F00-$3FFF during forced blanking, the color indicated by this palette location will be shown on screen instead of the backdrop color."
_currentOutputBuffer[(_scanline << 8) + _cycle - 1] = _paletteRAM[_state.VideoRamAddr & 0x1F];
pixel = ReadPaletteRAM(_videoRamAddr) | _intensifyColorBits;
_info->ScreenTiles[bufferOffset].Tile.TileIndex = HdPpuTileInfo::NoTile;
_info->ScreenTiles[bufferOffset].SpriteCount = 0;
}
}
};

View file

@ -0,0 +1,301 @@
#pragma once
#include "stdafx.h"
#include "NES/HdPacks/HdData.h"
#include "NES/NesConstants.h"
#include "Utilities/HexUtilities.h"
struct HdPackBaseTileCondition : public HdPackCondition
{
int32_t TileX;
int32_t TileY;
uint32_t PaletteColors;
uint8_t TileData[16];
int32_t TileIndex;
int32_t PixelOffset;
void Initialize(int32_t x, int32_t y, uint32_t palette, int32_t tileIndex, string tileData = "")
{
TileX = x;
TileY = y;
PixelOffset = (y * 256) + x;
PaletteColors = palette;
TileIndex = tileIndex;
if(tileData.size() == 32) {
for(int i = 0; i < 16; i++) {
TileData[i] = HexUtilities::FromHex(tileData.substr(i * 2, 2));
}
TileIndex = -1;
}
}
string ToString() override
{
stringstream out;
out << "<condition>" << Name << "," << GetConditionName() << ",";
out << TileX << ",";
out << TileY << ",";
if(TileIndex >= 0) {
out << HexUtilities::ToHex(TileIndex) << ",";
} else {
for(int i = 0; i < 16; i++) {
out << HexUtilities::ToHex(TileData[i]);
}
}
out << HexUtilities::ToHex(PaletteColors, true);
return out.str();
}
};
enum class HdPackConditionOperator
{
Equal = 0,
NotEqual = 1,
GreaterThan = 2,
LowerThan = 3,
LowerThanOrEqual = 4,
GreaterThanOrEqual = 5,
};
struct HdPackBaseMemoryCondition : public HdPackCondition
{
static constexpr uint32_t PpuMemoryMarker = 0x80000000;
uint32_t OperandA;
HdPackConditionOperator Operator;
uint32_t OperandB;
uint8_t Mask;
void Initialize(uint32_t operandA, HdPackConditionOperator op, uint32_t operandB, uint8_t mask)
{
OperandA = operandA;
Operator = op;
OperandB = operandB;
Mask = mask;
}
bool IsPpuCondition()
{
return (OperandA & HdPackBaseMemoryCondition::PpuMemoryMarker) != 0;
}
string ToString() override
{
stringstream out;
out << "<condition>" << Name << "," << GetConditionName() << ",";
out << HexUtilities::ToHex(OperandA & 0xFFFF) << ",";
switch(Operator) {
case HdPackConditionOperator::Equal: out << "=="; break;
case HdPackConditionOperator::NotEqual: out << "!="; break;
case HdPackConditionOperator::GreaterThan: out << ">"; break;
case HdPackConditionOperator::LowerThan: out << "<"; break;
case HdPackConditionOperator::LowerThanOrEqual: out << "<="; break;
case HdPackConditionOperator::GreaterThanOrEqual: out << ">="; break;
}
out << ",";
out << HexUtilities::ToHex(OperandB);
out << ",";
out << HexUtilities::ToHex(Mask);
return out.str();
}
};
struct HdPackHorizontalMirroringCondition : public HdPackCondition
{
string GetConditionName() override { return "hmirror"; }
string ToString() override { return ""; }
bool IsExcludedFromFile() override { return true; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
return tile && tile->HorizontalMirroring;
}
};
struct HdPackVerticalMirroringCondition : public HdPackCondition
{
string GetConditionName() override { return "vmirror"; }
string ToString() override { return ""; }
bool IsExcludedFromFile() override { return true; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
return tile && tile->VerticalMirroring;
}
};
struct HdPackBgPriorityCondition : public HdPackCondition
{
string GetConditionName() override { return "bgpriority"; }
string ToString() override { return ""; }
bool IsExcludedFromFile() override { return true; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
return tile && tile->BackgroundPriority;
}
};
struct HdPackMemoryCheckCondition : public HdPackBaseMemoryCondition
{
HdPackMemoryCheckCondition() { _useCache = true; }
string GetConditionName() override { return IsPpuCondition() ? "ppuMemoryCheck" : "memoryCheck"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
uint8_t a = (uint8_t)(screenInfo->WatchedAddressValues[OperandA] & Mask);
uint8_t b = (uint8_t)(screenInfo->WatchedAddressValues[OperandB] & Mask);
switch(Operator) {
case HdPackConditionOperator::Equal: return a == b;
case HdPackConditionOperator::NotEqual: return a != b;
case HdPackConditionOperator::GreaterThan: return a > b;
case HdPackConditionOperator::LowerThan: return a < b;
case HdPackConditionOperator::LowerThanOrEqual: return a <= b;
case HdPackConditionOperator::GreaterThanOrEqual: return a >= b;
}
return false;
}
};
struct HdPackMemoryCheckConstantCondition : public HdPackBaseMemoryCondition
{
HdPackMemoryCheckConstantCondition() { _useCache = true; }
string GetConditionName() override { return IsPpuCondition() ? "ppuMemoryCheckConstant" : "memoryCheckConstant"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
uint8_t a = (uint8_t)(screenInfo->WatchedAddressValues[OperandA] & Mask);
uint8_t b = OperandB;
switch(Operator) {
case HdPackConditionOperator::Equal: return a == b;
case HdPackConditionOperator::NotEqual: return a != b;
case HdPackConditionOperator::GreaterThan: return a > b;
case HdPackConditionOperator::LowerThan: return a < b;
case HdPackConditionOperator::LowerThanOrEqual: return a <= b;
case HdPackConditionOperator::GreaterThanOrEqual: return a >= b;
}
return false;
}
};
struct HdPackFrameRangeCondition : public HdPackCondition
{
uint32_t OperandA;
uint32_t OperandB;
HdPackFrameRangeCondition() { _useCache = true; }
string GetConditionName() override { return "frameRange"; }
void Initialize(uint32_t operandA, uint32_t operandB)
{
OperandA = operandA;
OperandB = operandB;
}
string ToString() override
{
stringstream out;
out << "<condition>" << Name << "," << GetConditionName() << ",";
out << OperandA << ",";
out << OperandB;
return out.str();
}
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
return screenInfo->FrameNumber % OperandA >= OperandB;
}
};
struct HdPackTileAtPositionCondition : public HdPackBaseTileCondition
{
HdPackTileAtPositionCondition() { _useCache = true; }
string GetConditionName() override { return "tileAtPosition"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[PixelOffset].Tile;
if(TileIndex >= 0) {
return targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex;
} else {
return memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0;
}
}
};
struct HdPackSpriteAtPositionCondition : public HdPackBaseTileCondition
{
HdPackSpriteAtPositionCondition() { _useCache = true; }
string GetConditionName() override { return "spriteAtPosition"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
for(int i = 0, len = screenInfo->ScreenTiles[PixelOffset].SpriteCount; i < len; i++) {
HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[PixelOffset].Sprite[i];
if(TileIndex >= 0) {
if(targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex) {
return true;
}
} else {
if(memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0) {
return true;
}
}
}
return false;
}
};
struct HdPackTileNearbyCondition : public HdPackBaseTileCondition
{
string GetConditionName() override { return "tileNearby"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
int pixelIndex = PixelOffset + (y * 256) + x;
if(pixelIndex < 0 || pixelIndex > NesConstants::ScreenPixelCount) {
return false;
}
HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[pixelIndex].Tile;
if(TileIndex >= 0) {
return targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex;
} else {
return memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0;
}
}
};
struct HdPackSpriteNearbyCondition : public HdPackBaseTileCondition
{
string GetConditionName() override { return "spriteNearby"; }
bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override
{
int xSign = tile && tile->HorizontalMirroring ? -1 : 1;
int ySign = tile && tile->VerticalMirroring ? -1 : 1;
int pixelIndex = ((y + TileY * ySign) * 256) + x + (TileX * xSign);
if(pixelIndex < 0 || pixelIndex > NesConstants::ScreenPixelCount) {
return false;
}
for(int i = 0, len = screenInfo->ScreenTiles[pixelIndex].SpriteCount; i < len; i++) {
HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[pixelIndex].Sprite[i];
if(TileIndex >= 0) {
if(targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex) {
return true;
}
} else {
if(memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0) {
return true;
}
}
}
return false;
}
};

View file

@ -0,0 +1,745 @@
#include "stdafx.h"
#include <algorithm>
#include <unordered_map>
#include "NES/HdPacks/HdPackLoader.h"
#include "NES/HdPacks/HdPackConditions.h"
#include "NES/HdPacks/HdNesPack.h"
#include "NES/NesConsole.h"
#include "Shared/MessageManager.h"
#include "Utilities/ZipReader.h"
#include "Utilities/FolderUtilities.h"
#include "Utilities/StringUtilities.h"
#include "Utilities/HexUtilities.h"
#include "Utilities/PNGHelper.h"
#define checkConstraint(x, y) if(!(x)) { MessageManager::Log(y); return; }
HdPackLoader::HdPackLoader()
{
}
bool HdPackLoader::InitializeLoader(VirtualFile &romFile, HdPackData *data)
{
_data = data;
string romName = FolderUtilities::GetFilename(romFile.GetFileName(), false);
string hdPackFolder = FolderUtilities::GetHdPackFolder();
string zipName = romName + ".hdn";
string definitionPath = FolderUtilities::CombinePath(romName, "hires.txt");
string legacyPath = FolderUtilities::CombinePath(hdPackFolder, definitionPath);
if(ifstream(legacyPath)) {
_loadFromZip = false;
_hdPackFolder = FolderUtilities::GetFolderName(legacyPath);
return true;
} else {
vector<string> hdnPackages = FolderUtilities::GetFilesInFolder(romFile.GetFolderPath(), { ".hdn" }, false);
vector<string> more = FolderUtilities::GetFilesInFolder(hdPackFolder, { ".hdn", ".zip" }, false);
hdnPackages.insert(hdnPackages.end(), more.begin(), more.end());
string sha1Hash = romFile.GetSha1Hash();
for(string path : hdnPackages) {
_reader.LoadArchive(path);
vector<uint8_t> hdDefinition;
if(_reader.ExtractFile("hires.txt", hdDefinition)) {
if(FolderUtilities::GetFilename(path, false) == romName) {
_loadFromZip = true;
_hdPackFolder = path;
return true;
} else {
for(string line : StringUtilities::Split(string(hdDefinition.data(), hdDefinition.data() + hdDefinition.size()), '\n')) {
std::transform(line.begin(), line.end(), line.begin(), ::tolower);
if(line.find("<supportedrom>") != string::npos && line.find(sha1Hash) != string::npos) {
_loadFromZip = true;
_hdPackFolder = path;
return true;
}
}
}
}
}
}
return false;
}
bool HdPackLoader::LoadHdNesPack(string definitionFile, HdPackData &outData)
{
HdPackLoader loader;
if(ifstream(definitionFile)) {
loader._data = &outData;
loader._loadFromZip = false;
loader._hdPackFolder = FolderUtilities::GetFolderName(definitionFile);
return loader.LoadPack();
}
return false;
}
bool HdPackLoader::LoadHdNesPack(VirtualFile &romFile, HdPackData &outData)
{
HdPackLoader loader;
if(loader.InitializeLoader(romFile, &outData)) {
return loader.LoadPack();
}
return false;
}
bool HdPackLoader::CheckFile(string filename)
{
if(_loadFromZip) {
return _reader.CheckFile(filename);
} else {
ifstream file(FolderUtilities::CombinePath(_hdPackFolder, filename), ios::in | ios::binary);
if(file.good()) {
return true;
}
}
return false;
}
bool HdPackLoader::LoadFile(string filename, vector<uint8_t> &fileData)
{
fileData.clear();
if(_loadFromZip) {
if(_reader.ExtractFile(filename, fileData)) {
return true;
}
} else {
ifstream file(FolderUtilities::CombinePath(_hdPackFolder, filename), ios::in | ios::binary);
if(file.good()) {
file.seekg(0, ios::end);
uint32_t fileSize = (uint32_t)file.tellg();
file.seekg(0, ios::beg);
fileData = vector<uint8_t>(fileSize, 0);
file.read((char*)fileData.data(), fileSize);
return true;
}
}
return false;
}
bool HdPackLoader::LoadPack()
{
string currentLine;
try {
vector<uint8_t> hdDefinition;
if(!LoadFile("hires.txt", hdDefinition)) {
return false;
}
InitializeGlobalConditions();
for(string lineContent : StringUtilities::Split(string(hdDefinition.data(), hdDefinition.data() + hdDefinition.size()), '\n')) {
if(lineContent.empty()) {
continue;
}
if(lineContent[lineContent.size() - 1] == '\r') {
lineContent = lineContent.substr(0, lineContent.size() - 1);
}
currentLine = lineContent;
vector<HdPackCondition*> conditions;
if(lineContent.substr(0, 1) == "[") {
size_t endOfCondition = lineContent.find_first_of(']', 1);
conditions = ParseConditionString(lineContent.substr(1, endOfCondition - 1));
lineContent = lineContent.substr(endOfCondition + 1);
}
vector<string> tokens;
if(lineContent.substr(0, 5) == "<ver>") {
_data->Version = stoi(lineContent.substr(5));
if(_data->Version > HdNesPack::CurrentVersion) {
MessageManager::Log("[HDPack] This HD Pack was built with a more recent version of Mesen - update Mesen to the latest version and try again.");
return false;
}
} else if(lineContent.substr(0, 7) == "<scale>") {
lineContent = lineContent.substr(7);
_data->Scale = std::stoi(lineContent);
} else if(lineContent.substr(0, 10) == "<overscan>") {
tokens = StringUtilities::Split(lineContent.substr(10), ',');
ProcessOverscanTag(tokens);
} else if(lineContent.substr(0, 5) == "<img>") {
lineContent = lineContent.substr(5);
if(!ProcessImgTag(lineContent)) {
return false;
}
} else if(lineContent.substr(0, 7) == "<patch>") {
tokens = StringUtilities::Split(lineContent.substr(7), ',');
ProcessPatchTag(tokens);
} else if(lineContent.substr(0, 12) == "<background>") {
tokens = StringUtilities::Split(lineContent.substr(12), ',');
ProcessBackgroundTag(tokens, conditions);
} else if(lineContent.substr(0, 11) == "<condition>") {
tokens = StringUtilities::Split(lineContent.substr(11), ',');
ProcessConditionTag(tokens, false);
ProcessConditionTag(tokens, true);
} else if(lineContent.substr(0, 6) == "<tile>") {
tokens = StringUtilities::Split(lineContent.substr(6), ',');
ProcessTileTag(tokens, conditions);
} else if(lineContent.substr(0, 9) == "<options>") {
tokens = StringUtilities::Split(lineContent.substr(9), ',');
ProcessOptionTag(tokens);
} else if(lineContent.substr(0, 5) == "<bgm>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessBgmTag(tokens);
} else if(lineContent.substr(0, 5) == "<sfx>") {
tokens = StringUtilities::Split(lineContent.substr(5), ',');
ProcessSfxTag(tokens);
}
}
LoadCustomPalette();
InitializeHdPack();
return true;
} catch(std::exception &ex) {
MessageManager::Log(string("[HDPack] Error loading HDPack: ") + ex.what() + " on line: " + currentLine);
return false;
}
}
bool HdPackLoader::ProcessImgTag(string src)
{
vector<uint8_t> fileData;
vector<uint8_t> pixelData;
LoadFile(src, fileData);
_hdNesBitmaps.push_back({});
HdPackBitmapInfo& bitmapInfo = _hdNesBitmaps.back();
if(PNGHelper::ReadPNG(fileData, bitmapInfo.PixelData, bitmapInfo.Width, bitmapInfo.Height)) {
return true;
} else {
_hdNesBitmaps.pop_back();
MessageManager::Log("[HDPack] Error loading HDPack: PNG file " + src + " could not be read.");
return false;
}
}
void HdPackLoader::PremultiplyAlpha(vector<uint32_t> &pixelData)
{
for(size_t i = 0; i < pixelData.size(); i++) {
if(pixelData[i] < 0xFF000000) {
//If not fully opaque, pre-multiply alpha with R/G/B to avoid having to do this while running
uint8_t* output = (uint8_t*)(pixelData.data() + i);
uint8_t alpha = output[3] + 1;
output[0] = (uint8_t)((alpha * output[0]) >> 8);
output[1] = (uint8_t)((alpha * output[1]) >> 8);
output[2] = (uint8_t)((alpha * output[2]) >> 8);
}
}
}
void HdPackLoader::InitializeGlobalConditions()
{
HdPackCondition* hmirror = new HdPackHorizontalMirroringCondition();
hmirror->Name = "hmirror";
_data->Conditions.push_back(unique_ptr<HdPackCondition>(hmirror));
HdPackCondition* invHmirror = new HdPackHorizontalMirroringCondition();
invHmirror->Name = "!hmirror";
_data->Conditions.push_back(unique_ptr<HdPackCondition>(invHmirror));
HdPackCondition* vmirror = new HdPackVerticalMirroringCondition();
vmirror->Name = "vmirror";
_data->Conditions.push_back(unique_ptr<HdPackCondition>(vmirror));
HdPackCondition* invVmirror = new HdPackVerticalMirroringCondition();
invVmirror->Name = "!vmirror";
_data->Conditions.push_back(unique_ptr<HdPackCondition>(invVmirror));
HdPackCondition* bgpriority = new HdPackBgPriorityCondition();
bgpriority->Name = "bgpriority";
_data->Conditions.push_back(unique_ptr<HdPackCondition>(bgpriority));
HdPackCondition* invBgpriority = new HdPackBgPriorityCondition();
invBgpriority->Name = "!bgpriority";
_data->Conditions.push_back(unique_ptr<HdPackCondition>(invBgpriority));
}
void HdPackLoader::ProcessOverscanTag(vector<string> &tokens)
{
OverscanDimensions overscan;
overscan.Top = std::stoi(tokens[0]);
overscan.Right = std::stoi(tokens[1]);
overscan.Bottom = std::stoi(tokens[2]);
overscan.Left = std::stoi(tokens[3]);
_data->HasOverscanConfig = true;
_data->Overscan = overscan;
}
void HdPackLoader::ProcessPatchTag(vector<string> &tokens)
{
checkConstraint(tokens.size() >= 2, "[HDPack] Patch tag requires more parameters");
checkConstraint(tokens[1].size() == 40, string("[HDPack] Invalid SHA1 hash for patch (" + tokens[0] + "): " + tokens[1]));
vector<uint8_t> fileData;
if(!LoadFile(tokens[0], fileData)) {
MessageManager::Log(string("[HDPack] Patch file not found: " + tokens[1]));
return;
}
std::transform(tokens[1].begin(), tokens[1].end(), tokens[1].begin(), ::toupper);
if(_loadFromZip) {
_data->PatchesByHash[tokens[1]] = VirtualFile(_hdPackFolder, tokens[0]);
} else {
_data->PatchesByHash[tokens[1]] = FolderUtilities::CombinePath(_hdPackFolder, tokens[0]);
}
}
void HdPackLoader::ProcessTileTag(vector<string> &tokens, vector<HdPackCondition*> conditions)
{
HdPackTileInfo *tileInfo = new HdPackTileInfo();
size_t index = 0;
if(_data->Version < 100) {
tileInfo->TileIndex = std::stoi(tokens[index++]);
tileInfo->BitmapIndex = std::stoi(tokens[index++]);
tileInfo->PaletteColors = std::stoi(tokens[index + 2]) | (std::stoi(tokens[index + 1]) << 8) | (std::stoi(tokens[index]) << 16);
index += 3;
} else {
tileInfo->BitmapIndex = std::stoi(tokens[index++]);
string tileData = tokens[index++];
if(tileData.size() >= 32) {
//CHR RAM tile, read the tile data
for(int i = 0; i < 16; i++) {
tileInfo->TileData[i] = HexUtilities::FromHex(tileData.substr(i * 2, 2));
}
tileInfo->IsChrRamTile = true;
tileInfo->TileIndex = -1;
} else {
if(_data->Version <= 102) {
tileInfo->TileIndex = std::stoi(tileData);
} else {
tileInfo->TileIndex = HexUtilities::FromHex(tileData);
}
tileInfo->IsChrRamTile = false;
}
tileInfo->PaletteColors = HexUtilities::FromHex(tokens[index++]);
}
tileInfo->X = std::stoi(tokens[index++]);
tileInfo->Y = std::stoi(tokens[index++]);
tileInfo->Conditions = conditions;
tileInfo->ForceDisableCache = false;
for(HdPackCondition* condition : conditions) {
if(dynamic_cast<HdPackSpriteNearbyCondition*>(condition)) {
tileInfo->ForceDisableCache = true;
break;
} else if(dynamic_cast<HdPackTileNearbyCondition*>(condition)) {
HdPackTileNearbyCondition* tileNearby = dynamic_cast<HdPackTileNearbyCondition*>(condition);
if(tileNearby->TileX % 8 > 0 || tileNearby->TileY % 8 > 0) {
tileInfo->ForceDisableCache = true;
break;
}
}
}
if(_data->Version >= 105) {
tileInfo->Brightness = (int)(std::stof(tokens[index++]) * 255);
} else if(_data->Version > 0) {
tileInfo->Brightness = (uint8_t)(std::stof(tokens[index++]) * 255);
} else {
tileInfo->Brightness = 255;
}
tileInfo->DefaultTile = (tokens[index++] == "Y");
//For CHR ROM tiles, the ID is just the bank number in chr rom (4k banks)
tileInfo->ChrBankId = tileInfo->TileIndex / 256;
if(_data->Version < 100) {
if(tokens.size() >= 24) {
//CHR RAM tile, read the tile data
for(int i = 0; i < 16; i++) {
tileInfo->TileData[i] = std::stoi(tokens[index++]);
}
tileInfo->IsChrRamTile = true;
} else {
tileInfo->IsChrRamTile = false;
}
} else {
if(tileInfo->IsChrRamTile && tokens.size() > index) {
tileInfo->ChrBankId = std::stoul(tokens[index++]);
}
if(tileInfo->IsChrRamTile && tokens.size() > index) {
tileInfo->TileIndex = std::stoi(tokens[index++]);
}
}
checkConstraint(tileInfo->BitmapIndex < _hdNesBitmaps.size(), "[HDPack] Invalid bitmap index: " + std::to_string(tileInfo->BitmapIndex));
HdPackBitmapInfo &bitmapInfo = _hdNesBitmaps[tileInfo->BitmapIndex];
uint32_t bitmapOffset = (tileInfo->Y * bitmapInfo.Width + tileInfo->X) * sizeof(uint32_t);
uint8_t* pngData = bitmapInfo.PixelData.data();
tileInfo->HdTileData.resize(64 * _data->Scale * _data->Scale);
for(uint32_t y = 0; y < 8 * _data->Scale; y++) {
for(uint32_t x = 0; x < 8 * _data->Scale; x++) {
uint8_t r = pngData[bitmapOffset];
uint8_t g = pngData[bitmapOffset + 1];
uint8_t b = pngData[bitmapOffset + 2];
uint8_t a = pngData[bitmapOffset + 3];
if(a < 0xFF) {
//If not fully opaque, pre-multiply alpha with R/G/B to avoid having to do this while running
uint8_t alpha = a + 1;
r = (uint8_t)((alpha * r) >> 8);
g = (uint8_t)((alpha * g) >> 8);
b = (uint8_t)((alpha * b) >> 8);
}
tileInfo->HdTileData[y * 8 * _data->Scale + x] = (a << 24) | (r << 16) | (g << 8) | b;
bitmapOffset += sizeof(uint32_t);
}
bitmapOffset += (bitmapInfo.Width - (8 * _data->Scale)) * sizeof(uint32_t);
}
tileInfo->UpdateFlags();
_data->Tiles.push_back(unique_ptr<HdPackTileInfo>(tileInfo));
}
void HdPackLoader::ProcessOptionTag(vector<string> &tokens)
{
for(string token : tokens) {
if(token == "disableSpriteLimit") {
_data->OptionFlags |= (int)HdPackOptions::NoSpriteLimit;
} else if(token == "alternateRegisterRange") {
_data->OptionFlags |= (int)HdPackOptions::AlternateRegisterRange;
} else if(token == "disableCache") {
_data->OptionFlags |= (int)HdPackOptions::DisableCache;
} else if(token == "disableOriginalTiles") {
_data->OptionFlags |= (int)HdPackOptions::DontRenderOriginalTiles;
} else {
MessageManager::Log("[HDPack] Invalid option: " + token);
}
}
}
void HdPackLoader::ProcessConditionTag(vector<string> &tokens, bool createInvertedCondition)
{
checkConstraint(tokens.size() >= 4, "[HDPack] Condition tag should contain at least 4 parameters");
checkConstraint(tokens[0].size() > 0, "[HDPack] Condition name may not be empty");
checkConstraint(tokens[0].find_last_of('!') == string::npos, "[HDPack] Condition name may not contain '!' characters");
unique_ptr<HdPackCondition> condition;
if(tokens[1] == "tileAtPosition") {
condition.reset(new HdPackTileAtPositionCondition());
} else if(tokens[1] == "tileNearby") {
condition.reset(new HdPackTileNearbyCondition());
} else if(tokens[1] == "spriteAtPosition") {
condition.reset(new HdPackSpriteAtPositionCondition());
} else if(tokens[1] == "spriteNearby") {
condition.reset(new HdPackSpriteNearbyCondition());
} else if(tokens[1] == "memoryCheck" || tokens[1] == "ppuMemoryCheck") {
condition.reset(new HdPackMemoryCheckCondition());
} else if(tokens[1] == "memoryCheckConstant" || tokens[1] == "ppuMemoryCheckConstant") {
condition.reset(new HdPackMemoryCheckConstantCondition());
} else if(tokens[1] == "frameRange") {
condition.reset(new HdPackFrameRangeCondition());
} else {
MessageManager::Log("[HDPack] Invalid condition type: " + tokens[1]);
return;
}
tokens[0].erase(tokens[0].find_last_not_of(" \n\r\t") + 1);
condition->Name = tokens[0];
if(createInvertedCondition) {
condition->Name = "!" + condition->Name;
}
int index = 2;
if(dynamic_cast<HdPackBaseTileCondition*>(condition.get())) {
checkConstraint(tokens.size() >= 6, "[HDPack] Condition tag should contain at least 6 parameters");
int x = std::stoi(tokens[index++]);
int y = std::stoi(tokens[index++]);
string token = tokens[index++];
int32_t tileIndex = -1;
string tileData;
if(token.size() == 32) {
tileData = token;
} else {
if(_data->Version < 104) {
tileIndex = std::stoi(token);
} else {
//Tile indexes are stored as hex starting from version 104+
tileIndex = HexUtilities::FromHex(token);
}
}
uint32_t palette = HexUtilities::FromHex(tokens[index++]);
((HdPackBaseTileCondition*)condition.get())->Initialize(x, y, palette, tileIndex, tileData);
} else if(dynamic_cast<HdPackBaseMemoryCondition*>(condition.get())) {
checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs");
checkConstraint(tokens.size() >= 5, "[HDPack] Condition tag should contain at least 5 parameters");
bool usePpuMemory = tokens[1].substr(0, 3) == "ppu";
uint32_t operandA = HexUtilities::FromHex(tokens[index++]);
if(usePpuMemory) {
checkConstraint(operandA <= 0x3FFF, "[HDPack] Out of range memoryCheck operand");
operandA |= HdPackBaseMemoryCondition::PpuMemoryMarker;
} else {
checkConstraint(operandA <= 0xFFFF, "[HDPack] Out of range memoryCheck operand");
}
HdPackConditionOperator op;
string opString = tokens[index++];
if(opString == "==") {
op = HdPackConditionOperator::Equal;
} else if(opString == "!=") {
op = HdPackConditionOperator::NotEqual;
} else if(opString == ">") {
op = HdPackConditionOperator::GreaterThan;
} else if(opString == "<") {
op = HdPackConditionOperator::LowerThan;
} else if(opString == "<=") {
op = HdPackConditionOperator::LowerThanOrEqual;
} else if(opString == ">=") {
op = HdPackConditionOperator::GreaterThanOrEqual;
} else {
checkConstraint(false, "[HDPack] Invalid operator.");
}
uint32_t operandB = HexUtilities::FromHex(tokens[index++]);
uint32_t mask = 0xFF;
if(tokens.size() > 5 && _data->Version >= 103) {
checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheck mask");
mask = HexUtilities::FromHex(tokens[index++]);
}
if(dynamic_cast<HdPackMemoryCheckCondition*>(condition.get())) {
if(usePpuMemory) {
checkConstraint(operandB <= 0x3FFF, "[HDPack] Out of range memoryCheck operand");
operandB |= HdPackBaseMemoryCondition::PpuMemoryMarker;
} else {
checkConstraint(operandB <= 0xFFFF, "[HDPack] Out of range memoryCheck operand");
}
_data->WatchedMemoryAddresses.emplace(operandB);
} else if(dynamic_cast<HdPackMemoryCheckConstantCondition*>(condition.get())) {
checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheckConstant operand");
}
_data->WatchedMemoryAddresses.emplace(operandA);
((HdPackBaseMemoryCondition*)condition.get())->Initialize(operandA, op, operandB, (uint8_t)mask);
} else if(dynamic_cast<HdPackFrameRangeCondition*>(condition.get())) {
checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs");
checkConstraint(tokens.size() >= 4, "[HDPack] Condition tag should contain at least 4 parameters");
int32_t operandA;
int32_t operandB;
if(_data->Version == 101) {
operandA = HexUtilities::FromHex(tokens[index++]);
operandB = HexUtilities::FromHex(tokens[index++]);
} else {
//Version 102+
operandA = std::stoi(tokens[index++]);
operandB = std::stoi(tokens[index++]);
}
checkConstraint(operandA >= 0 && operandA <= 0xFFFF, "[HDPack] Out of range frameRange operand");
checkConstraint(operandB >= 0 && operandB <= 0xFFFF, "[HDPack] Out of range frameRange operand");
((HdPackFrameRangeCondition*)condition.get())->Initialize(operandA, operandB);
}
HdPackCondition *cond = condition.get();
condition.release();
_data->Conditions.emplace_back(unique_ptr<HdPackCondition>(cond));
_conditionsByName[cond->Name] = cond;
}
void HdPackLoader::ProcessBackgroundTag(vector<string> &tokens, vector<HdPackCondition*> conditions)
{
checkConstraint(tokens.size() >= 2, "[HDPack] Background tag should contain at least 2 parameters");
HdBackgroundFileData* bgFileData = nullptr;
for(unique_ptr<HdBackgroundFileData> &bgData : _data->BackgroundFileData) {
if(bgData->PngName == tokens[0]) {
bgFileData = bgData.get();
}
}
if(!bgFileData) {
vector<uint8_t> pixelData;
uint32_t width, height;
vector<uint8_t> fileContent;
if(LoadFile(tokens[0], fileContent)) {
if(PNGHelper::ReadPNG(fileContent, pixelData, width, height)) {
_data->BackgroundFileData.push_back(unique_ptr<HdBackgroundFileData>(new HdBackgroundFileData()));
bgFileData = _data->BackgroundFileData.back().get();
bgFileData->PixelData.resize(pixelData.size() / 4);
memcpy(bgFileData->PixelData.data(), pixelData.data(), bgFileData->PixelData.size() * sizeof(bgFileData->PixelData[0]));
PremultiplyAlpha(bgFileData->PixelData);
bgFileData->Width = width;
bgFileData->Height = height;
bgFileData->PngName = tokens[0];
}
}
}
HdBackgroundInfo backgroundInfo;
if(bgFileData) {
backgroundInfo.Data = bgFileData;
if (_data->Version >= 105) {
backgroundInfo.Brightness = (int)(std::stof(tokens[1]) * 255);
} else {
backgroundInfo.Brightness = (uint8_t)(std::stof(tokens[1]) * 255);
}
backgroundInfo.HorizontalScrollRatio = 0;
backgroundInfo.VerticalScrollRatio = 0;
backgroundInfo.Priority = 10;
backgroundInfo.Left = 0;
backgroundInfo.Top = 0;
for(HdPackCondition* condition : conditions) {
if(
!dynamic_cast<HdPackTileAtPositionCondition*>(condition) &&
!dynamic_cast<HdPackSpriteAtPositionCondition*>(condition) &&
!dynamic_cast<HdPackMemoryCheckCondition*>(condition) &&
!dynamic_cast<HdPackMemoryCheckConstantCondition*>(condition) &&
!dynamic_cast<HdPackFrameRangeCondition*>(condition)
) {
MessageManager::Log("[HDPack] Invalid condition type for background: " + tokens[0]);
return;
} else {
backgroundInfo.Conditions.push_back(condition);
}
}
if(tokens.size() > 2) {
checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs");
backgroundInfo.HorizontalScrollRatio = std::stof(tokens[2]);
if(tokens.size() > 3) {
backgroundInfo.VerticalScrollRatio = std::stof(tokens[3]);
}
if(tokens.size() > 4) {
checkConstraint(_data->Version >= 102, "[HDPack] This feature requires version 102+ of HD Packs");
if(_data->Version >= 106) {
backgroundInfo.Priority = std::stoi(tokens[4]);
checkConstraint(backgroundInfo.Priority >= 0 && backgroundInfo.Priority < 40, "[HDPack] Invalid background priority value");
} else {
backgroundInfo.Priority = tokens[4] == "Y" ? 0 : 10;
}
}
if(tokens.size() > 6) {
checkConstraint(_data->Version >= 105, "[HDPack] This feature requires version 105+ of HD Packs");
backgroundInfo.Left = std::max(0, std::stoi(tokens[5]));
backgroundInfo.Top = std::max(0, std::stoi(tokens[6]));
}
}
_data->Backgrounds.push_back(backgroundInfo);
} else {
MessageManager::Log("[HDPack] Error while loading background: " + tokens[0]);
}
}
int HdPackLoader::ProcessSoundTrack(string albumString, string trackString, string filename)
{
int album = std::stoi(albumString);
if(album < 0 || album > 255) {
MessageManager::Log("[HDPack] Invalid album value: " + albumString);
return -1;
}
int track = std::stoi(trackString);
if(track < 0 || track > 255) {
MessageManager::Log("[HDPack] Invalid track value: " + trackString);
return -1;
}
if(!CheckFile(filename)) {
MessageManager::Log("[HDPack] OGG file not found: " + filename);
return -1;
}
return album * 256 + track;
}
void HdPackLoader::ProcessBgmTag(vector<string> &tokens)
{
int trackId = ProcessSoundTrack(tokens[0], tokens[1], tokens[2]);
if(trackId >= 0) {
if(_loadFromZip) {
VirtualFile file(_hdPackFolder, tokens[2]);
_data->BgmFilesById[trackId] = file;
} else {
_data->BgmFilesById[trackId] = FolderUtilities::CombinePath(_hdPackFolder, tokens[2]);
}
}
}
void HdPackLoader::ProcessSfxTag(vector<string> &tokens)
{
int trackId = ProcessSoundTrack(tokens[0], tokens[1], tokens[2]);
if(trackId >= 0) {
if(_loadFromZip) {
VirtualFile file(_hdPackFolder, tokens[2]);
_data->SfxFilesById[trackId] = file;
} else {
_data->SfxFilesById[trackId] = FolderUtilities::CombinePath(_hdPackFolder, tokens[2]);
}
}
}
vector<HdPackCondition*> HdPackLoader::ParseConditionString(string conditionString)
{
vector<string> conditionNames = StringUtilities::Split(conditionString, '&');
vector<HdPackCondition*> conditions;
for(string conditionName : conditionNames) {
conditionName.erase(conditionName.find_last_not_of(" \n\r\t") + 1);
auto result = _conditionsByName.find(conditionName);
if(result != _conditionsByName.end()) {
conditions.push_back(result->second);
} else {
MessageManager::Log("[HDPack] Condition not found: " + conditionName);
}
}
return conditions;
}
void HdPackLoader::LoadCustomPalette()
{
vector<uint8_t> fileData;
if(LoadFile("palette.dat", fileData)) {
vector<uint32_t> paletteData;
for(size_t i = 0; i < fileData.size(); i+= 3){
paletteData.push_back(0xFF000000 | (fileData[i] << 16) | (fileData[i+1] << 8) | fileData[i+2]);
}
if(paletteData.size() == 0x40) {
_data->Palette = paletteData;
}
}
}
void HdPackLoader::InitializeHdPack()
{
for(unique_ptr<HdPackTileInfo> &tileInfo : _data->Tiles) {
auto tiles = _data->TileByKey.find(tileInfo->GetKey(false));
if(tiles == _data->TileByKey.end()) {
_data->TileByKey[tileInfo->GetKey(false)] = vector<HdPackTileInfo*>();
}
_data->TileByKey[tileInfo->GetKey(false)].push_back(tileInfo.get());
if(tileInfo->DefaultTile) {
auto tiles = _data->TileByKey.find(tileInfo->GetKey(true));
if(tiles == _data->TileByKey.end()) {
_data->TileByKey[tileInfo->GetKey(true)] = vector<HdPackTileInfo*>();
}
_data->TileByKey[tileInfo->GetKey(true)].push_back(tileInfo.get());
}
}
}

View file

@ -0,0 +1,50 @@
#pragma once
#include "stdafx.h"
#include "NES/HdPacks/HdData.h"
#include "Utilities/ZipReader.h"
#include "Utilities/VirtualFile.h"
class HdPackLoader
{
public:
static bool LoadHdNesPack(string definitionFile, HdPackData &outData);
static bool LoadHdNesPack(VirtualFile &romFile, HdPackData &outData);
private:
HdPackData* _data;
bool _loadFromZip = false;
ZipReader _reader;
string _hdPackDefinitionFile;
string _hdPackFolder;
vector<HdPackBitmapInfo> _hdNesBitmaps;
unordered_map<string, HdPackCondition*> _conditionsByName;
HdPackLoader();
bool InitializeLoader(VirtualFile &romPath, HdPackData *data);
bool LoadFile(string filename, vector<uint8_t> &fileData);
bool CheckFile(string filename);
bool LoadPack();
void InitializeHdPack();
void LoadCustomPalette();
void InitializeGlobalConditions();
//Video
bool ProcessImgTag(string src);
void PremultiplyAlpha(vector<uint32_t>& pixelData);
void ProcessPatchTag(vector<string> &tokens);
void ProcessOverscanTag(vector<string> &tokens);
void ProcessConditionTag(vector<string> &tokens, bool createInvertedCondition);
void ProcessTileTag(vector<string> &tokens, vector<HdPackCondition*> conditions);
void ProcessBackgroundTag(vector<string> &tokens, vector<HdPackCondition*> conditions);
void ProcessOptionTag(vector<string>& tokens);
//Audio
int ProcessSoundTrack(string albumString, string trackString, string filename);
void ProcessBgmTag(vector<string> &tokens);
void ProcessSfxTag(vector<string> &tokens);
vector<HdPackCondition*> ParseConditionString(string conditionString);
};

View file

@ -0,0 +1,38 @@
#include "stdafx.h"
#include "NES/HdPacks/HdNesPack.h"
#include "NES/HdPacks/HdVideoFilter.h"
#include "NES/NesConsole.h"
#include "NES/NesConstants.h"
#include "Shared/Video/BaseVideoFilter.h"
HdVideoFilter::HdVideoFilter(Emulator* emu, HdPackData* hdData) : BaseVideoFilter(emu)
{
_hdData = hdData;
_hdNesPack.reset(new HdNesPack(hdData));
}
FrameInfo HdVideoFilter::GetFrameInfo()
{
OverscanDimensions overscan = GetOverscan();
uint32_t hdScale = _hdNesPack->GetScale();
return {
(NesConstants::ScreenWidth - overscan.Left - overscan.Right) * hdScale,
(NesConstants::ScreenHeight - overscan.Top - overscan.Bottom) * hdScale
};
}
OverscanDimensions HdVideoFilter::GetOverscan()
{
if(_hdData->HasOverscanConfig) {
return _hdData->Overscan;
} else {
return BaseVideoFilter::GetOverscan();
}
}
void HdVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer)
{
OverscanDimensions overscan = GetOverscan();
_hdNesPack->Process((HdScreenInfo*)_frameData, GetOutputBuffer(), overscan);
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "stdafx.h"
#include "Shared/Video/BaseVideoFilter.h"
class HdNesPack;
class Emulator;
struct HdPackData;
class HdVideoFilter : public BaseVideoFilter
{
private:
HdPackData* _hdData;
unique_ptr<HdNesPack> _hdNesPack = nullptr;
public:
HdVideoFilter(Emulator* emu, HdPackData* hdData);
void ApplyFilter(uint16_t *ppuOutputBuffer) override;
FrameInfo GetFrameInfo() override;
OverscanDimensions GetOverscan() override;
};

View file

@ -0,0 +1,121 @@
#include "stdafx.h"
#include <algorithm>
#include "NES/HdPacks/OggReader.h"
#include "NES/HdPacks/OggMixer.h"
enum class OggPlaybackOptions
{
None = 0x00,
Loop = 0x01
};
OggMixer::OggMixer()
{
}
void OggMixer::Reset(uint32_t sampleRate)
{
_bgm.reset();
_sfx.clear();
_sfxVolume = 128;
_bgmVolume = 128;
_options = 0;
_sampleRate = sampleRate;
_paused = false;
}
void OggMixer::SetPlaybackOptions(uint8_t options)
{
_options = options;
bool loop = (options & (int)OggPlaybackOptions::Loop) != 0;
if(_bgm) {
_bgm->SetLoopFlag(loop);
}
}
void OggMixer::SetPausedFlag(bool paused)
{
_paused = paused;
}
void OggMixer::StopBgm()
{
_bgm.reset();
}
void OggMixer::StopSfx()
{
_sfx.clear();
}
void OggMixer::SetBgmVolume(uint8_t volume)
{
_bgmVolume = volume;
}
void OggMixer::SetSfxVolume(uint8_t volume)
{
_sfxVolume = volume;
}
bool OggMixer::IsBgmPlaying()
{
return !_paused && _bgm;
}
bool OggMixer::IsSfxPlaying()
{
return _sfx.size() > 0;
}
void OggMixer::SetSampleRate(int sampleRate)
{
_sampleRate = sampleRate;
if(_bgm) {
_bgm->SetSampleRate(sampleRate);
}
for(shared_ptr<OggReader> &sfx : _sfx) {
sfx->SetSampleRate(sampleRate);
}
}
bool OggMixer::Play(string filename, bool isSfx, uint32_t startOffset)
{
shared_ptr<OggReader> reader(new OggReader());
bool loop = !isSfx && (_options & (int)OggPlaybackOptions::Loop) != 0;
if(reader->Init(filename, loop, _sampleRate, startOffset)) {
if(isSfx) {
_sfx.push_back(reader);
} else {
_bgm = reader;
}
return true;
}
return false;
}
int OggMixer::GetBgmOffset()
{
if(_bgm) {
return _bgm->GetOffset();
} else {
return -1;
}
}
void OggMixer::MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate)
{
if(_bgm && !_paused) {
_bgm->SetSampleRate(sampleRate);
_bgm->ApplySamples(out, sampleCount, _bgmVolume);
if(_bgm->IsPlaybackOver()) {
_bgm.reset();
}
}
for(shared_ptr<OggReader>& sfx : _sfx) {
sfx->SetSampleRate(sampleRate);
sfx->ApplySamples(out, sampleCount, _sfxVolume);
}
_sfx.erase(std::remove_if(_sfx.begin(), _sfx.end(), [](const shared_ptr<OggReader>& o) { return o->IsPlaybackOver(); }), _sfx.end());
}

View file

@ -0,0 +1,37 @@
#pragma once
#include "stdafx.h"
#include "Shared/Interfaces/IAudioProvider.h"
class OggReader;
class OggMixer : public IAudioProvider
{
private:
shared_ptr<OggReader> _bgm;
vector<shared_ptr<OggReader>> _sfx;
uint32_t _sampleRate;
uint8_t _bgmVolume;
uint8_t _sfxVolume;
uint8_t _options;
bool _paused;
public:
OggMixer();
void SetSampleRate(int sampleRate);
void Reset(uint32_t sampleRate);
bool Play(string filename, bool isSfx, uint32_t startOffset);
void SetPlaybackOptions(uint8_t options);
void SetPausedFlag(bool paused);
void StopBgm();
void StopSfx();
void SetBgmVolume(uint8_t volume);
void SetSfxVolume(uint8_t volume);
bool IsBgmPlaying();
bool IsSfxPlaying();
int32_t GetBgmOffset();
void MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate) override;
};

View file

@ -0,0 +1,94 @@
#include "stdafx.h"
#include "NES/HdPacks/OggReader.h"
#include "Utilities/Audio/stb_vorbis.h"
OggReader::OggReader()
{
_done = false;
_oggBuffer = new int16_t[10000];
_outputBuffer = new int16_t[2000];
}
OggReader::~OggReader()
{
delete[] _oggBuffer;
delete[] _outputBuffer;
if(_vorbis) {
stb_vorbis_close(_vorbis);
}
}
bool OggReader::Init(string filename, bool loop, uint32_t sampleRate, uint32_t startOffset)
{
int error;
VirtualFile file = filename;
_fileData = vector<uint8_t>(100000);
if(file.ReadFile(_fileData)) {
_vorbis = stb_vorbis_open_memory(_fileData.data(), (int)_fileData.size(), &error, nullptr);
if(_vorbis) {
_loop = loop;
_oggSampleRate = stb_vorbis_get_info(_vorbis).sample_rate;
if(startOffset > 0) {
stb_vorbis_seek(_vorbis, startOffset);
}
return true;
}
}
return false;
}
bool OggReader::IsPlaybackOver()
{
return _done;
}
void OggReader::SetSampleRate(int sampleRate)
{
if(sampleRate != _sampleRate) {
_sampleRate = sampleRate;
}
}
void OggReader::SetLoopFlag(bool loop)
{
_loop = loop;
}
void OggReader::ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume)
{
int32_t samplesNeeded = (int32_t)sampleCount - _leftoverSampleCount;
uint32_t samplesRead = 0;
if(samplesNeeded > 0) {
uint32_t samplesToLoad = samplesNeeded * _oggSampleRate / _sampleRate + 2;
uint32_t samplesLoaded = (uint32_t)stb_vorbis_get_samples_short_interleaved(_vorbis, 2, _oggBuffer, samplesToLoad * 2);
if(samplesLoaded < samplesToLoad) {
if(_loop) {
stb_vorbis_seek_start(_vorbis);
samplesLoaded += stb_vorbis_get_samples_short_interleaved(_vorbis, 2, _oggBuffer + samplesLoaded * 2, samplesToLoad - samplesLoaded);
} else {
_done = true;
}
}
_resampler.SetSampleRates(_oggSampleRate, _sampleRate);
samplesRead = _resampler.Resample(_oggBuffer, samplesLoaded, _outputBuffer + _leftoverSampleCount * 2);
}
uint32_t samplesToProcess = std::min<uint32_t>((uint32_t)sampleCount * 2, (samplesRead + _leftoverSampleCount) * 2);
for(uint32_t i = 0; i < samplesToProcess; i++) {
buffer[i] += (int16_t)((int32_t)_outputBuffer[i] * volume / 255);
}
//Calculate count of extra samples that couldn't be mixed with the rest of the audio and copy them to the beginning of the buffer
//These will be mixed on the next call to ApplySamples
_leftoverSampleCount = std::max(0, (int32_t)(samplesRead + _leftoverSampleCount) - (int32_t)sampleCount);
for(uint32_t i = 0; i < _leftoverSampleCount * 2; i++) {
_outputBuffer[i] = _outputBuffer[samplesToProcess + i];
}
}
uint32_t OggReader::GetOffset()
{
return stb_vorbis_get_file_offset(_vorbis);
}

View file

@ -0,0 +1,36 @@
#pragma once
#include "stdafx.h"
#include "Utilities/VirtualFile.h"
#include "Utilities/Audio/HermiteResampler.h"
struct stb_vorbis;
class OggReader
{
private:
stb_vorbis* _vorbis = nullptr;
int16_t* _outputBuffer = nullptr;
int16_t* _oggBuffer = nullptr;
HermiteResampler _resampler;
uint32_t _leftoverSampleCount = 0;
bool _loop = false;
bool _done = false;
int _sampleRate = 0;
int _oggSampleRate = 0;
vector<uint8_t> _fileData;
public:
OggReader();
~OggReader();
bool Init(string filename, bool loop, uint32_t sampleRate, uint32_t startOffset = 0);
bool IsPlaybackOver();
void SetSampleRate(int sampleRate);
void SetLoopFlag(bool loop);
void ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume);
uint32_t GetOffset();
};

View file

@ -9,6 +9,11 @@
#include "NES/NesMemoryManager.h"
#include "NES/DefaultNesPpu.h"
#include "NES/NsfPpu.h"
#include "NES/HdPacks/HdAudioDevice.h"
#include "NES/HdPacks/HdData.h"
#include "NES/HdPacks/HdNesPpu.h"
#include "NES/HdPacks/HdPackLoader.h"
#include "NES/HdPacks/HdVideoFilter.h"
#include "NES/NesDefaultVideoFilter.h"
#include "NES/NesNtscFilter.h"
#include "NES/NesConstants.h"
@ -112,6 +117,8 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile)
{
RomData romData;
LoadHdPack(romFile);
LoadRomResult result = LoadRomResult::UnknownType;
unique_ptr<BaseMapper> mapper = MapperFactory::InitializeFromFile(this, romFile, romData, result);
if(mapper) {
@ -168,29 +175,34 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile)
_controlManager->SetPollCounter(pollCounter);
_controlManager->UpdateControlDevices();
_mapper->SetConsole(this);
_mapper->Initialize(romData);
//Re-enable battery saves
/*_batteryManager->SetSaveEnabled(true);
*/
if(_hdData && (!_hdData->Tiles.empty() || !_hdData->Backgrounds.empty())) {
_ppu.reset(new HdPpu(shared_from_this(), _hdData.get()));
} else */
if(dynamic_cast<NsfMapper*>(_mapper.get())) {
_ppu.reset(new HdNesPpu(this, _hdData.get()));
} else if(dynamic_cast<NsfMapper*>(_mapper.get())) {
//Disable most of the PPU for NSFs
_ppu.reset(new NsfPpu(this));
} else {
_ppu.reset(new DefaultNesPpu(this));
}
_mapper->SetConsole(this);
_mapper->Initialize(romData);
_memoryManager->SetMapper(_mapper.get());
_memoryManager->RegisterIODevice(_ppu.get());
_memoryManager->RegisterIODevice(_apu.get());
_memoryManager->RegisterIODevice(_controlManager.get());
_memoryManager->RegisterIODevice(_mapper.get());
if(_hdData && (!_hdData->BgmFilesById.empty() || !_hdData->SfxFilesById.empty())) {
_hdAudioDevice.reset(new HdAudioDevice(_emu, _hdData.get()));
_memoryManager->RegisterIODevice(_hdAudioDevice.get());
} else {
_hdAudioDevice.reset();
}
UpdateRegion();
_mixer->Reset();
@ -204,6 +216,23 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile)
return result;
}
void NesConsole::LoadHdPack(VirtualFile& romFile)
{
_hdData.reset();
if(GetNesConfig().EnableHdPacks) {
_hdData.reset(new HdPackData());
if(!HdPackLoader::LoadHdNesPack(romFile, *_hdData.get())) {
_hdData.reset();
} else {
auto result = _hdData->PatchesByHash.find(romFile.GetSha1Hash());
if(result != _hdData->PatchesByHash.end()) {
VirtualFile patchFile = result->second;
romFile.ApplyPatch(patchFile);
}
}
}
}
void NesConsole::UpdateRegion()
{
ConsoleRegion region = GetNesConfig().Region;
@ -353,11 +382,15 @@ void NesConsole::SaveBattery()
BaseVideoFilter* NesConsole::GetVideoFilter()
{
VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter;
if(filterType == VideoFilterType::NTSC) {
return new NesNtscFilter(_emu);
if(_hdData) {
return new HdVideoFilter(_emu, _hdData.get());
} else {
return new NesDefaultVideoFilter(_emu);
VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter;
if(filterType == VideoFilterType::NTSC) {
return new NesNtscFilter(_emu);
} else {
return new NesDefaultVideoFilter(_emu);
}
}
}

View file

@ -15,6 +15,8 @@ class BaseMapper;
class EmuSettings;
class NesSoundMixer;
class BaseVideoFilter;
class HdAudioDevice;
struct HdPackData;
enum class DebugEventType;
enum class EventType;
@ -36,9 +38,13 @@ private:
shared_ptr<NesControlManager> _controlManager;
unique_ptr<NesSoundMixer> _mixer;
unique_ptr<HdPackData> _hdData;
unique_ptr<HdAudioDevice> _hdAudioDevice;
ConsoleRegion _region = ConsoleRegion::Auto;
void UpdateRegion();
void LoadHdPack(VirtualFile& romFile);
public:
NesConsole(Emulator* emulator);

View file

@ -13,6 +13,7 @@
#include "NES/DefaultNesPpu.h"
#include "NES/NsfPpu.h"
#include "NES/HdPacks/HdNesPpu.h"
#include "Debugger/Debugger.h"
#include "Shared/EmuSettings.h"
@ -716,6 +717,8 @@ template<class T> void NesPpu<T>::LoadTileInfo()
if(IsRenderingEnabled()) {
switch(_cycle & 0x07) {
case 1: {
((T*)this)->StoreTileInformation(); //Used by HD packs
_previousTilePalette = _currentTilePalette;
_currentTilePalette = _tile.PaletteOffset;
@ -724,7 +727,6 @@ template<class T> void NesPpu<T>::LoadTileInfo()
uint8_t tileIndex = ReadVram(GetNameTableAddr());
_tile.TileAddr = (tileIndex << 4) | (_videoRamAddr >> 12) | _backgroundPatternAddr;
//_nextTile.OffsetY = _state.VideoRamAddr >> 12;
break;
}
@ -736,7 +738,6 @@ template<class T> void NesPpu<T>::LoadTileInfo()
case 5:
_tile.LowByte = ReadVram(_tile.TileAddr);
((T*)this)->StoreTileAbsoluteAddress();
break;
case 7:
@ -771,7 +772,6 @@ template<class T> void NesPpu<T>::LoadSprite(uint8_t spriteY, uint8_t tileIndex,
NesSpriteInfo &info = _spriteTiles[_spriteIndex];
info.BackgroundPriority = backgroundPriority;
info.HorizontalMirror = horizontalMirror;
//info.VerticalMirror = verticalMirror;
info.PaletteOffset = ((attributes & 0x03) << 2) | 0x10;
if(extraSprite) {
//Use DebugReadVram for extra sprites to prevent side-effects.
@ -782,10 +782,8 @@ template<class T> void NesPpu<T>::LoadSprite(uint8_t spriteY, uint8_t tileIndex,
info.LowByte = ReadVram(tileAddr);
info.HighByte = ReadVram(tileAddr + 8);
}
//info.TileAddr = tileAddr;
//info.OffsetY = lineOffset;
info.SpriteX = spriteX;
((T*)this)->StoreSpriteAbsoluteAddress();
((T*)this)->StoreSpriteInformation(verticalMirror, tileAddr, lineOffset); //Used by HD packs
if(_scanline >= 0) {
//Sprites read on prerender scanline are not shown on scanline 0
@ -1215,12 +1213,14 @@ template<class T> void NesPpu<T>::SendFrame()
{
UpdateGrayscaleAndIntensifyBits();
void* frameData = ((T*)this)->OnBeforeSendFrame();
if(_console->IsVsMainConsole()) {
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer);
}
#ifdef LIBRETRO
_console->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, true, false);
_console->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, true, false, frameData);
#else
if(_console->GetVsMainConsole() || _console->GetVsSubConsole()) {
SendFrameVsDualSystem();
@ -1229,7 +1229,7 @@ template<class T> void NesPpu<T>::SendFrame()
}
} else {
bool forRewind = _emu->GetRewindManager()->IsRewinding();
_emu->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, forRewind, forRewind);
_emu->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, forRewind, forRewind, frameData);
_emu->ProcessEndOfFrame();
}
@ -1619,3 +1619,8 @@ template NesPpu<NsfPpu>::NesPpu(NesConsole* console);
template uint16_t* NesPpu<NsfPpu>::GetScreenBuffer(bool previousBuffer);
template void NesPpu<NsfPpu>::Exec();
template uint32_t NesPpu<NsfPpu>::GetPixelBrightness(uint8_t x, uint8_t y);
template NesPpu<HdNesPpu>::NesPpu(NesConsole* console);
template uint16_t* NesPpu<HdNesPpu>::GetScreenBuffer(bool previousBuffer);
template void NesPpu<HdNesPpu>::Exec();
template uint32_t NesPpu<HdNesPpu>::GetPixelBrightness(uint8_t x, uint8_t y);

View file

@ -81,7 +81,7 @@ protected:
__forceinline uint8_t GetPixelColor();
void UpdateGrayscaleAndIntensifyBits();
virtual void SendFrame();
void SendFrame();
void SendFrameVsDualSystem();

View file

@ -14,7 +14,6 @@ NesSoundMixer::NesSoundMixer(NesConsole* console)
_clockRate = 0;
_console = console;
_mixer = console->GetEmulator()->GetSoundMixer().get();
//_oggMixer.reset();
_outputBuffer = new int16_t[NesSoundMixer::MaxSamplesPerFrame];
_blipBufLeft = blip_new(NesSoundMixer::MaxSamplesPerFrame);
_blipBufRight = blip_new(NesSoundMixer::MaxSamplesPerFrame);
@ -45,10 +44,6 @@ void NesSoundMixer::Serialize(Serializer& s)
void NesSoundMixer::Reset()
{
//TODO
/*if(_oggMixer) {
_oggMixer->Reset(_settings->GetSampleRate());
}*/
_sampleCount = 0;
_previousOutputLeft = 0;
@ -92,11 +87,7 @@ void NesSoundMixer::PlayAudioBuffer(uint32_t time)
}
//TODO
/*_console->GetMapper()->ApplySamples(_outputBuffer, sampleCount, _settings->GetMasterVolume());
if(_oggMixer) {
_oggMixer->ApplySamples(_outputBuffer, sampleCount, _settings->GetMasterVolume());
}*/
//_console->GetMapper()->ApplySamples(_outputBuffer, sampleCount, _settings->GetMasterVolume());
NesConfig& cfg = _console->GetNesConfig();
if(_console->GetVsSubConsole()) {
@ -157,10 +148,6 @@ void NesSoundMixer::UpdateRates(bool forceUpdate)
blip_set_rates(_blipBufLeft, _clockRate, _sampleRate);
blip_set_rates(_blipBufRight, _clockRate, _sampleRate);
//TODO
/*if(_oggMixer) {
_oggMixer->SetSampleRate(_sampleRate);
}*/
}
NesConfig& cfg = _console->GetNesConfig();
@ -245,14 +232,3 @@ void NesSoundMixer::EndFrame(uint32_t time)
memset(_channelOutput, 0, sizeof(_channelOutput));
}
//TODO
/*
OggMixer* NesSoundMixer::GetOggMixer()
{
if(!_oggMixer) {
_oggMixer.reset(new OggMixer());
_oggMixer->Reset(_settings->GetSampleRate());
}
return _oggMixer.get();
}
*/

View file

@ -27,9 +27,6 @@ private:
EmuSettings* _settings = nullptr;
SoundMixer* _mixer = nullptr;
//TODO
//unique_ptr<OggMixer> _oggMixer;
StereoPanningFilter _stereoPanning;
StereoDelayFilter _stereoDelay;
StereoCombFilter _stereoCombFilter;
@ -71,7 +68,5 @@ public:
void PlayAudioBuffer(uint32_t cycle);
void AddDelta(AudioChannel channel, uint32_t time, int16_t delta);
//OggMixer* GetOggMixer();
void Serialize(Serializer& s) override;
};

View file

@ -25,6 +25,11 @@ public:
{
}
void* OnBeforeSendFrame()
{
return nullptr;
}
void Run(uint64_t runTo) override
{
do {

View file

@ -346,7 +346,7 @@ int32_t SaveStateManager::GetSaveStatePreview(string saveStatePath, uint8_t* png
unique_ptr<BaseVideoFilter> filter(_emu->GetVideoFilter());
filter->SetBaseFrameInfo(baseFrameInfo);
FrameInfo frameInfo = filter->GetFrameInfo();
filter->SendFrame((uint16_t*)frameData.data(), 0);
filter->SendFrame((uint16_t*)frameData.data(), 0, nullptr);
std::stringstream pngStream;
PNGHelper::WritePNG(pngStream, filter->GetOutputBuffer(), frameInfo.Width, frameInfo.Height);

View file

@ -67,11 +67,12 @@ uint32_t BaseVideoFilter::GetBufferSize()
return _bufferSize * sizeof(uint32_t);
}
void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber)
void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, void* frameData)
{
_frameLock.Acquire();
_overscan = _emu->GetSettings()->GetOverscan();
_isOddFrame = frameNumber % 2;
_frameData = frameData;
UpdateBufferSize();
OnBeforeApplyFilter();
ApplyFilter(ppuOutputBuffer);

View file

@ -12,14 +12,15 @@ private:
double _yiqToRgbMatrix[6] = {};
uint32_t _bufferSize = 0;
SimpleLock _frameLock;
OverscanDimensions _overscan;
bool _isOddFrame;
OverscanDimensions _overscan = {};
bool _isOddFrame = false;
void UpdateBufferSize();
protected:
Emulator* _emu;
FrameInfo _baseFrameInfo;
Emulator* _emu = nullptr;
FrameInfo _baseFrameInfo = {};
void* _frameData = nullptr;
void InitConversionMatrix(double hueShift, double saturationShift);
void RgbToYiq(double r, double g, double b, double& y, double& i, double& q);
@ -36,7 +37,7 @@ public:
virtual ~BaseVideoFilter();
uint32_t* GetOutputBuffer();
void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber);
void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, void* frameData);
void TakeScreenshot(string romName, VideoFilterType filterType);
void TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream = nullptr);

View file

@ -24,11 +24,11 @@ void DebugHud::ClearScreen()
_commands.clear();
}
void DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t lineWidth, uint32_t frameNumber)
void DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber)
{
auto lock = _commandLock.AcquireSafe();
for(unique_ptr<DrawCommand> &command : _commands) {
command->Draw(argbBuffer, frameInfo, overscan, lineWidth, frameNumber);
command->Draw(argbBuffer, frameInfo, overscan, frameNumber);
}
_commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const unique_ptr<DrawCommand>& c) { return c->Expired(); }), _commands.end());
}

View file

@ -16,7 +16,7 @@ public:
DebugHud();
~DebugHud();
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t width, uint32_t frameNumber);
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber);
void ClearScreen();
void DrawPixel(int x, int y, int color, int frameCount, int startFrame = -1);

View file

@ -9,7 +9,6 @@ private:
uint32_t* _argbBuffer;
FrameInfo _frameInfo;
OverscanDimensions _overscan;
uint32_t _lineWidth;
int32_t _startFrame;
protected:
@ -20,18 +19,19 @@ protected:
virtual void InternalDraw() = 0;
__forceinline void DrawPixel(uint32_t x, uint32_t y, int color)
{
if(x < _overscan.Left || x - _overscan.Left >= _frameInfo.Width || y < _overscan.Top || y - _overscan.Top >= _frameInfo.Height) {
//Out of bounds, skip drawing
return;
}
uint32_t alpha = (color & 0xFF000000);
if(alpha > 0) {
if(_yScale == 1) {
int32_t offset = ((int32_t)y - _overscan.Top) * _frameInfo.Width + ((int32_t)x - _overscan.Left);
if(offset < 0 || offset >= (int32_t)(_frameInfo.Width * _frameInfo.Height)) {
//Out of bounds, skip drawing
return;
}
if(alpha != 0xFF000000) {
BlendColors((uint8_t*)&_argbBuffer[(y - _overscan.Top)*_lineWidth + (x - _overscan.Left)], (uint8_t*)&color);
BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color);
} else {
_argbBuffer[(y - _overscan.Top)*_lineWidth + (x - _overscan.Left)] = color;
_argbBuffer[offset] = color;
}
} else {
int xPixelCount = _useIntegerScaling ? _yScale : (int)((x + 1)*_xScale) - (int)(x*_xScale);
@ -42,10 +42,16 @@ protected:
for(int i = 0; i < _yScale; i++) {
for(int j = 0; j < xPixelCount; j++) {
int32_t offset = ((int32_t)y - top) * _frameInfo.Width + i * _frameInfo.Width + ((int32_t)x - left) + j;
if(offset < 0 || offset >= (int32_t)(_frameInfo.Width * _frameInfo.Height)) {
//Out of bounds, skip drawing
continue;
}
if(alpha != 0xFF000000) {
BlendColors((uint8_t*)&_argbBuffer[(y - top)*_lineWidth + i*_lineWidth + (x - left)+j], (uint8_t*)&color);
BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color);
} else {
_argbBuffer[(y - top)*_lineWidth + i*_lineWidth + (x - left) +j] = color;
_argbBuffer[offset] = color;
}
}
}
@ -75,7 +81,7 @@ public:
{
}
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t lineWidth, uint32_t frameNumber)
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t frameNumber)
{
if(_startFrame < 0) {
//When no start frame was specified, start on the next drawn frame
@ -86,9 +92,8 @@ public:
_argbBuffer = argbBuffer;
_frameInfo = frameInfo;
_overscan = overscan;
_lineWidth = lineWidth;
_yScale = lineWidth >= 512 ? 2 : 1;
_xScale = lineWidth >= 512 ? 2.0f : 1.0f;
_yScale = _frameInfo.Width >= 512 ? 2 : 1;
_xScale = _frameInfo.Width >= 512 ? 2.0f : 1.0f;
InternalDraw();

View file

@ -89,14 +89,14 @@ void VideoDecoder::DecodeFrame(bool forRewind)
UpdateVideoFilter();
_videoFilter->SetBaseFrameInfo(_baseFrameInfo);
_videoFilter->SendFrame(_ppuOutputBuffer, _frameNumber);
_videoFilter->SendFrame(_ppuOutputBuffer, _frameNumber, _frameData);
uint32_t* outputBuffer = _videoFilter->GetOutputBuffer();
FrameInfo frameInfo = _videoFilter->GetFrameInfo();
_inputHud->DrawControllers(_videoFilter->GetOverscan(), _frameNumber);
_systemHud->Draw(frameInfo, _videoFilter->GetOverscan());
_emu->GetDebugHud()->Draw(outputBuffer, frameInfo, _videoFilter->GetOverscan(), frameInfo.Width, _frameNumber);
_emu->GetDebugHud()->Draw(outputBuffer, frameInfo, _videoFilter->GetOverscan(), _frameNumber);
if(_scaleFilter) {
outputBuffer = _scaleFilter->ApplyFilter(outputBuffer, frameInfo.Width, frameInfo.Height, _emu->GetSettings()->GetVideoConfig().ScanlineIntensity);
@ -139,7 +139,7 @@ uint32_t VideoDecoder::GetFrameCount()
return _frameCount;
}
void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind)
void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind, void* frameData)
{
if(_emu->IsRunAheadFrame()) {
return;
@ -159,6 +159,7 @@ void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16
_baseFrameInfo.Height = height;
_frameNumber = frameNumber;
_ppuOutputBuffer = ppuOutputBuffer;
_frameData = frameData;
if(sync) {
DecodeFrame(forRewind);
} else {
@ -171,7 +172,10 @@ void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16
void VideoDecoder::StartThread()
{
#ifndef LIBRETRO
if(!_decodeThread) {
if(!_decodeThread) {
_videoFilter.reset();
UpdateVideoFilter();
_videoFilter->SetBaseFrameInfo(_baseFrameInfo);
_stopFlag = false;
_frameChanged = false;
_frameCount = 0;

View file

@ -18,6 +18,7 @@ private:
shared_ptr<Emulator> _emu;
uint16_t *_ppuOutputBuffer = nullptr;
void* _frameData = nullptr;
uint32_t _frameNumber = 0;
ConsoleType _consoleType = ConsoleType::Snes;
@ -58,7 +59,7 @@ public:
FrameInfo GetFrameInfo();
ScreenSize GetScreenSize(bool ignoreScale);
void UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind);
void UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind, void* frameData = nullptr);
bool IsRunning();
void StartThread();

View file

@ -13,7 +13,6 @@ VideoRenderer::VideoRenderer(shared_ptr<Emulator> emu)
{
_emu = emu;
_stopFlag = false;
StartThread();
}
VideoRenderer::~VideoRenderer()

View file

@ -3,6 +3,9 @@
#include "PNGHelper.h"
#include "miniz.h"
#define SPNG_USE_MINIZ
#include "spng.h"
bool PNGHelper::WritePNG(std::stringstream &stream, uint32_t* buffer, uint32_t xSize, uint32_t ySize, uint32_t bitsPerPixel)
{
size_t pngSize = 0;
@ -51,10 +54,10 @@ bool PNGHelper::ReadPNG(vector<uint8_t> input, vector<uint8_t> &output, uint32_t
if(DecodePNG(output, width, height, input.data(), input.size()) == 0) {
uint32_t *pngDataPtr = (uint32_t*)output.data();
for(size_t i = 0, len = output.size() / 4; i < len; i++) {
/*for(size_t i = 0, len = output.size() / 4; i < len; i++) {
//ABGR to ARGB
pngDataPtr[i] = (pngDataPtr[i] & 0xFF00FF00) | ((pngDataPtr[i] & 0xFF0000) >> 16) | ((pngDataPtr[i] & 0xFF) << 16);
}
}*/
pngWidth = width;
pngHeight = height;
@ -83,533 +86,42 @@ bool PNGHelper::ReadPNG(string filename, vector<uint8_t> &pngData, uint32_t &png
return false;
}
/*
decodePNG: The picoPNG function, decodes a PNG file buffer in memory, into a raw pixel buffer.
out_image: output parameter, this will contain the raw pixels after decoding.
By default the output is 32-bit RGBA color.
The std::vector is automatically resized to the correct size.
image_width: output_parameter, this will contain the width of the image in pixels.
image_height: output_parameter, this will contain the height of the image in pixels.
in_png: pointer to the buffer of the PNG file in memory. To get it from a file on
disk, load it and store it in a memory buffer yourself first.
in_size: size of the input PNG file in bytes.
convert_to_rgba32: optional parameter, true by default.
Set to true to get the output in RGBA 32-bit (8 bit per channel) color format
no matter what color type the original PNG image had. This gives predictable,
useable data from any random input PNG.
Set to false to do no color conversion at all. The result then has the same data
type as the PNG image, which can range from 1 bit to 64 bits per pixel.
Information about the color type or palette colors are not provided. You need
to know this information yourself to be able to use the data so this only
works for trusted PNG files. Use LodePNG instead of picoPNG if you need this information.
return: 0 if success, not 0 if some error occured.
*/
int PNGHelper::DecodePNG(vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32)
{
// picoPNG version 20101224
// Copyright (c) 2005-2010 Lode Vandevenne
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
// picoPNG is a PNG decoder in one C++ function of around 500 lines. Use picoPNG for
// programs that need only 1 .cpp file. Since it's a single function, it's very limited,
// it can convert a PNG to raw pixel data either converted to 32-bit RGBA color or
// with no color conversion at all. For anything more complex, another tiny library
// is available: LodePNG (lodepng.c(pp)), which is a single source and header file.
// Apologies for the compact code style, it's to make this tiny.
static const unsigned long LENBASE[29] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258};
static const unsigned long LENEXTRA[29] = {0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
static const unsigned long DISTBASE[30] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577};
static const unsigned long DISTEXTRA[30] = {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
static const unsigned long CLCL[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; //code length code lengths
struct Zlib //nested functions for zlib decompression
{
static unsigned long readBitFromStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (bitp & 0x7)) & 1; bitp++; return result;}
static unsigned long readBitsFromStream(size_t& bitp, const unsigned char* bits, size_t nbits)
{
unsigned long result = 0;
for(size_t i = 0; i < nbits; i++) result += (readBitFromStream(bitp, bits)) << i;
return result;
}
struct HuffmanTree
{
int makeFromLengths(const std::vector<unsigned long>& bitlen, unsigned long maxbitlen)
{ //make tree given the lengths
unsigned long numcodes = (unsigned long)(bitlen.size()), treepos = 0, nodefilled = 0;
std::vector<unsigned long> tree1d(numcodes), blcount(maxbitlen + 1, 0), nextcode(maxbitlen + 1, 0);
for(unsigned long bits = 0; bits < numcodes; bits++) blcount[bitlen[bits]]++; //count number of instances of each code length
for(unsigned long bits = 1; bits <= maxbitlen; bits++) nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1;
for(unsigned long n = 0; n < numcodes; n++) if(bitlen[n] != 0) tree1d[n] = nextcode[bitlen[n]]++; //generate all the codes
tree2d.clear(); tree2d.resize(numcodes * 2, 32767); //32767 here means the tree2d isn't filled there yet
for(unsigned long n = 0; n < numcodes; n++) //the codes
for(unsigned long i = 0; i < bitlen[n]; i++) //the bits for this code
{
unsigned long bit = (tree1d[n] >> (bitlen[n] - i - 1)) & 1;
if(treepos > numcodes - 2) return 55;
if(tree2d[2 * treepos + bit] == 32767) //not yet filled in
{
if(i + 1 == bitlen[n]) { tree2d[2 * treepos + bit] = n; treepos = 0; } //last bit
else { tree2d[2 * treepos + bit] = ++nodefilled + numcodes; treepos = nodefilled; } //addresses are encoded as values > numcodes
}
else treepos = tree2d[2 * treepos + bit] - numcodes; //subtract numcodes from address to get address value
}
return 0;
}
int decode(bool& decoded, unsigned long& result, size_t& treepos, unsigned long bit) const
{ //Decodes a symbol from the tree
unsigned long numcodes = (unsigned long)tree2d.size() / 2;
if(treepos >= numcodes) return 11; //error: you appeared outside the codetree
result = tree2d[2 * treepos + bit];
decoded = (result < numcodes);
treepos = decoded ? 0 : result - numcodes;
return 0;
}
std::vector<unsigned long> tree2d; //2D representation of a huffman tree: The one dimension is "0" or "1", the other contains all nodes and leaves of the tree.
};
struct Inflator
{
int error;
void inflate(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, size_t inpos = 0)
{
size_t bp = 0, pos = 0; //bit pointer and byte pointer
error = 0;
unsigned long BFINAL = 0;
while(!BFINAL && !error)
{
if(bp >> 3 >= in.size()) { error = 52; return; } //error, bit pointer will jump past memory
BFINAL = readBitFromStream(bp, &in[inpos]);
unsigned long BTYPE = readBitFromStream(bp, &in[inpos]); BTYPE += 2 * readBitFromStream(bp, &in[inpos]);
if(BTYPE == 3) { error = 20; return; } //error: invalid BTYPE
else if(BTYPE == 0) inflateNoCompression(out, &in[inpos], bp, pos, in.size());
else inflateHuffmanBlock(out, &in[inpos], bp, pos, in.size(), BTYPE);
}
if(!error) out.resize(pos); //Only now we know the true size of out, resize it to that
}
void generateFixedTrees(HuffmanTree& tree, HuffmanTree& treeD) //get the tree of a deflated block with fixed tree
{
std::vector<unsigned long> bitlen(288, 8), bitlenD(32, 5);;
for(size_t i = 144; i <= 255; i++) bitlen[i] = 9;
for(size_t i = 256; i <= 279; i++) bitlen[i] = 7;
tree.makeFromLengths(bitlen, 15);
treeD.makeFromLengths(bitlenD, 15);
}
HuffmanTree codetree, codetreeD, codelengthcodetree; //the code tree for Huffman codes, dist codes, and code length codes
unsigned long huffmanDecodeSymbol(const unsigned char* in, size_t& bp, const HuffmanTree& codetree, size_t inlength)
{ //decode a single symbol from given list of bits with given code tree. return value is the symbol
bool decoded; unsigned long ct;
for(size_t treepos = 0;;)
{
if((bp & 0x07) == 0 && (bp >> 3) > inlength) { error = 10; return 0; } //error: end reached without endcode
error = codetree.decode(decoded, ct, treepos, readBitFromStream(bp, in)); if(error) return 0; //stop, an error happened
if(decoded) return ct;
}
}
void getTreeInflateDynamic(HuffmanTree& tree, HuffmanTree& treeD, const unsigned char* in, size_t& bp, size_t inlength)
{ //get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree
std::vector<unsigned long> bitlen(288, 0), bitlenD(32, 0);
if(bp >> 3 >= inlength - 2) { error = 49; return; } //the bit pointer is or will go past the memory
size_t HLIT = readBitsFromStream(bp, in, 5) + 257; //number of literal/length codes + 257
size_t HDIST = readBitsFromStream(bp, in, 5) + 1; //number of dist codes + 1
size_t HCLEN = readBitsFromStream(bp, in, 4) + 4; //number of code length codes + 4
std::vector<unsigned long> codelengthcode(19); //lengths of tree to decode the lengths of the dynamic tree
for(size_t i = 0; i < 19; i++) codelengthcode[CLCL[i]] = (i < HCLEN) ? readBitsFromStream(bp, in, 3) : 0;
error = codelengthcodetree.makeFromLengths(codelengthcode, 7); if(error) return;
size_t i = 0, replength;
while(i < HLIT + HDIST)
{
unsigned long code = huffmanDecodeSymbol(in, bp, codelengthcodetree, inlength); if(error) return;
if(code <= 15) { if(i < HLIT) bitlen[i++] = code; else bitlenD[i++ - HLIT] = code; } //a length code
else if(code == 16) //repeat previous
{
if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory
replength = 3 + readBitsFromStream(bp, in, 2);
unsigned long value; //set value to the previous code
if((i - 1) < HLIT) value = bitlen[i - 1];
else value = bitlenD[i - HLIT - 1];
for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths
{
if(i >= HLIT + HDIST) { error = 13; return; } //error: i is larger than the amount of codes
if(i < HLIT) bitlen[i++] = value; else bitlenD[i++ - HLIT] = value;
}
}
else if(code == 17) //repeat "0" 3-10 times
{
if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory
replength = 3 + readBitsFromStream(bp, in, 3);
for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths
{
if(i >= HLIT + HDIST) { error = 14; return; } //error: i is larger than the amount of codes
if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0;
}
}
else if(code == 18) //repeat "0" 11-138 times
{
if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory
replength = 11 + readBitsFromStream(bp, in, 7);
for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths
{
if(i >= HLIT + HDIST) { error = 15; return; } //error: i is larger than the amount of codes
if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0;
}
}
else { error = 16; return; } //error: somehow an unexisting code appeared. This can never happen.
}
if(bitlen[256] == 0) { error = 64; return; } //the length of the end code 256 must be larger than 0
error = tree.makeFromLengths(bitlen, 15); if(error) return; //now we've finally got HLIT and HDIST, so generate the code trees, and the function is done
error = treeD.makeFromLengths(bitlenD, 15); if(error) return;
}
void inflateHuffmanBlock(std::vector<unsigned char>& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength, unsigned long btype)
{
if(btype == 1) { generateFixedTrees(codetree, codetreeD); }
else if(btype == 2) { getTreeInflateDynamic(codetree, codetreeD, in, bp, inlength); if(error) return; }
for(;;)
{
unsigned long code = huffmanDecodeSymbol(in, bp, codetree, inlength); if(error) return;
if(code == 256) return; //end code
else if(code <= 255) //literal symbol
{
if(pos >= out.size()) out.resize((pos + 1) * 2); //reserve more room
out[pos++] = (unsigned char)(code);
}
else if(code >= 257 && code <= 285) //length code
{
size_t length = LENBASE[code - 257], numextrabits = LENEXTRA[code - 257];
if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory
length += readBitsFromStream(bp, in, numextrabits);
unsigned long codeD = huffmanDecodeSymbol(in, bp, codetreeD, inlength); if(error) return;
if(codeD > 29) { error = 18; return; } //error: invalid dist code (30-31 are never used)
unsigned long dist = DISTBASE[codeD], numextrabitsD = DISTEXTRA[codeD];
if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory
dist += readBitsFromStream(bp, in, numextrabitsD);
size_t start = pos, back = start - dist; //backwards
if(pos + length >= out.size()) out.resize((pos + length) * 2); //reserve more room
for(size_t i = 0; i < length; i++) { out[pos++] = out[back++]; if(back >= start) back = start - dist; }
}
}
}
void inflateNoCompression(std::vector<unsigned char>& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength)
{
while((bp & 0x7) != 0) bp++; //go to first boundary of byte
size_t p = bp / 8;
if(p >= inlength - 4) { error = 52; return; } //error, bit pointer will jump past memory
unsigned long LEN = in[p] + 256 * in[p + 1], NLEN = in[p + 2] + 256 * in[p + 3]; p += 4;
if(LEN + NLEN != 65535) { error = 21; return; } //error: NLEN is not one's complement of LEN
if(pos + LEN >= out.size()) out.resize(pos + LEN);
if(p + LEN > inlength) { error = 23; return; } //error: reading outside of in buffer
for(unsigned long n = 0; n < LEN; n++) out[pos++] = in[p++]; //read LEN bytes of literal data
bp = p * 8;
}
};
int decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in) //returns error value
{
Inflator inflator;
if(in.size() < 2) { return 53; } //error, size of zlib data too small
if((in[0] * 256 + in[1]) % 31 != 0) { return 24; } //error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way
unsigned long CM = in[0] & 15, CINFO = (in[0] >> 4) & 15, FDICT = (in[1] >> 5) & 1;
if(CM != 8 || CINFO > 7) { return 25; } //error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec
if(FDICT != 0) { return 26; } //error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary."
inflator.inflate(out, in, 2);
return inflator.error; //note: adler32 checksum was skipped and ignored
}
};
struct PNG //nested functions for PNG decoding
{
struct Info
{
unsigned long width, height, colorType, bitDepth, compressionMethod, filterMethod, interlaceMethod, key_r, key_g, key_b;
bool key_defined; //is a transparent color key given?
std::vector<unsigned char> palette;
} info;
int error;
void decode(std::vector<unsigned char>& out, const unsigned char* in, size_t size, bool convert_to_rgba32)
{
error = 0;
if(size == 0 || in == 0) { error = 48; return; } //the given data is empty
readPngHeader(&in[0], size); if(error) return;
size_t pos = 33; //first byte of the first chunk after the header
std::vector<unsigned char> idat; //the data from idat chunks
bool IEND = false;
info.key_defined = false;
while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer
{
if(pos + 8 >= size) { error = 30; return; } //error: size of the in buffer too small to contain next chunk
size_t chunkLength = read32bitInt(&in[pos]); pos += 4;
if(chunkLength > 2147483647) { error = 63; return; }
if(pos + chunkLength >= size) { error = 35; return; } //error: size of the in buffer too small to contain next chunk
if(in[pos + 0] == 'I' && in[pos + 1] == 'D' && in[pos + 2] == 'A' && in[pos + 3] == 'T') //IDAT chunk, containing compressed image data
{
idat.insert(idat.end(), &in[pos + 4], &in[pos + 4 + chunkLength]);
pos += (4 + chunkLength);
}
else if(in[pos + 0] == 'I' && in[pos + 1] == 'E' && in[pos + 2] == 'N' && in[pos + 3] == 'D') { pos += 4; IEND = true; }
else if(in[pos + 0] == 'P' && in[pos + 1] == 'L' && in[pos + 2] == 'T' && in[pos + 3] == 'E') //palette chunk (PLTE)
{
pos += 4; //go after the 4 letters
info.palette.resize(4 * (chunkLength / 3));
if(info.palette.size() > (4 * 256)) { error = 38; return; } //error: palette too big
for(size_t i = 0; i < info.palette.size(); i += 4)
{
for(size_t j = 0; j < 3; j++) info.palette[i + j] = in[pos++]; //RGB
info.palette[i + 3] = 255; //alpha
}
}
else if(in[pos + 0] == 't' && in[pos + 1] == 'R' && in[pos + 2] == 'N' && in[pos + 3] == 'S') //palette transparency chunk (tRNS)
{
pos += 4; //go after the 4 letters
if(info.colorType == 3)
{
if(4 * chunkLength > info.palette.size()) { error = 39; return; } //error: more alpha values given than there are palette entries
for(size_t i = 0; i < chunkLength; i++) info.palette[4 * i + 3] = in[pos++];
}
else if(info.colorType == 0)
{
if(chunkLength != 2) { error = 40; return; } //error: this chunk must be 2 bytes for greyscale image
info.key_defined = 1; info.key_r = info.key_g = info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2;
}
else if(info.colorType == 2)
{
if(chunkLength != 6) { error = 41; return; } //error: this chunk must be 6 bytes for RGB image
info.key_defined = 1;
info.key_r = 256 * in[pos] + in[pos + 1]; pos += 2;
info.key_g = 256 * in[pos] + in[pos + 1]; pos += 2;
info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2;
}
else { error = 42; return; } //error: tRNS chunk not allowed for other color models
}
else //it's not an implemented chunk type, so ignore it: skip over the data
{
if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0)
pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk
}
pos += 4; //step over CRC (which is ignored)
}
unsigned long bpp = getBpp(info);
std::vector<unsigned char> scanlines(((info.width * (info.height * bpp + 7)) / 8) + info.height); //now the out buffer will be filled
Zlib zlib; //decompress with the Zlib decompressor
error = zlib.decompress(scanlines, idat); if(error) return; //stop if the zlib decompressor returned an error
size_t bytewidth = (bpp + 7) / 8, outlength = (info.height * info.width * bpp + 7) / 8;
out.resize(outlength); //time to fill the out buffer
unsigned char* out_ = outlength ? &out[0] : 0; //use a regular pointer to the std::vector for faster code if compiled without optimization
if(info.interlaceMethod == 0) //no interlace, just filter
{
size_t linestart = 0, linelength = (info.width * bpp + 7) / 8; //length in bytes of a scanline, excluding the filtertype byte
if(bpp >= 8) //byte per byte
for(unsigned long y = 0; y < info.height; y++)
{
unsigned long filterType = scanlines[linestart];
const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth];
unFilterScanline(&out_[linestart - y], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return;
linestart += (1 + linelength); //go to start of next scanline
}
else //less than 8 bits per pixel, so fill it up bit per bit
{
std::vector<unsigned char> templine((info.width * bpp + 7) >> 3); //only used if bpp < 8
for(size_t y = 0, obp = 0; y < info.height; y++)
{
unsigned long filterType = scanlines[linestart];
const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth];
unFilterScanline(&templine[0], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return;
for(size_t bp = 0; bp < info.width * bpp;) setBitOfReversedStream(obp, out_, readBitFromReversedStream(bp, &templine[0]));
linestart += (1 + linelength); //go to start of next scanline
}
}
}
else //interlaceMethod is 1 (Adam7)
{
size_t passw[7] = { (info.width + 7) / 8, (info.width + 3) / 8, (info.width + 3) / 4, (info.width + 1) / 4, (info.width + 1) / 2, (info.width + 0) / 2, (info.width + 0) / 1 };
size_t passh[7] = { (info.height + 7) / 8, (info.height + 7) / 8, (info.height + 3) / 8, (info.height + 3) / 4, (info.height + 1) / 4, (info.height + 1) / 2, (info.height + 0) / 2 };
size_t passstart[7] = {0};
size_t pattern[28] = {0,4,0,2,0,1,0,0,0,4,0,2,0,1,8,8,4,4,2,2,1,8,8,8,4,4,2,2}; //values for the adam7 passes
for(int i = 0; i < 6; i++) passstart[i + 1] = passstart[i] + passh[i] * ((passw[i] ? 1 : 0) + (passw[i] * bpp + 7) / 8);
std::vector<unsigned char> scanlineo((info.width * bpp + 7) / 8), scanlinen((info.width * bpp + 7) / 8); //"old" and "new" scanline
for(int i = 0; i < 7; i++)
adam7Pass(&out_[0], &scanlinen[0], &scanlineo[0], &scanlines[passstart[i]], info.width, pattern[i], pattern[i + 7], pattern[i + 14], pattern[i + 21], passw[i], passh[i], bpp);
}
if(convert_to_rgba32 && (info.colorType != 6 || info.bitDepth != 8)) //conversion needed
{
std::vector<unsigned char> data = out;
error = convert(out, &data[0], info, info.width, info.height);
}
}
void readPngHeader(const unsigned char* in, size_t inlength) //read the information from the header and store it in the Info
{
if(inlength < 29) { error = 27; return; } //error: the data length is smaller than the length of the header
if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { error = 28; return; } //no PNG signature
if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') { error = 29; return; } //error: it doesn't start with a IHDR chunk!
info.width = read32bitInt(&in[16]); info.height = read32bitInt(&in[20]);
info.bitDepth = in[24]; info.colorType = in[25];
info.compressionMethod = in[26]; if(in[26] != 0) { error = 32; return; } //error: only compression method 0 is allowed in the specification
info.filterMethod = in[27]; if(in[27] != 0) { error = 33; return; } //error: only filter method 0 is allowed in the specification
info.interlaceMethod = in[28]; if(in[28] > 1) { error = 34; return; } //error: only interlace methods 0 and 1 exist in the specification
error = checkColorValidity(info.colorType, info.bitDepth);
}
void unFilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned long filterType, size_t length)
{
switch(filterType)
{
case 0: for(size_t i = 0; i < length; i++) recon[i] = scanline[i]; break;
case 1:
for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i];
for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth];
break;
case 2:
if(precon) for(size_t i = 0; i < length; i++) recon[i] = scanline[i] + precon[i];
else for(size_t i = 0; i < length; i++) recon[i] = scanline[i];
break;
case 3:
if(precon)
{
for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2;
for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2);
}
else
{
for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i];
for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2;
}
break;
case 4:
if(precon)
{
for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + paethPredictor(0, precon[i], 0);
for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]);
}
else
{
for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i];
for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], 0, 0);
}
break;
default: error = 36; return; //error: unexisting filter type given
}
}
void adam7Pass(unsigned char* out, unsigned char* linen, unsigned char* lineo, const unsigned char* in, unsigned long w, size_t passleft, size_t passtop, size_t spacex, size_t spacey, size_t passw, size_t passh, unsigned long bpp)
{ //filter and reposition the pixels into the output when the image is Adam7 interlaced. This function can only do it after the full image is already decoded. The out buffer must have the correct allocated memory size already.
if(passw == 0) return;
size_t bytewidth = (bpp + 7) / 8, linelength = 1 + ((bpp * passw + 7) / 8);
for(unsigned long y = 0; y < passh; y++)
{
unsigned char filterType = in[y * linelength], *prevline = (y == 0) ? 0 : lineo;
unFilterScanline(linen, &in[y * linelength + 1], prevline, bytewidth, filterType, (w * bpp + 7) / 8); if(error) return;
if(bpp >= 8) for(size_t i = 0; i < passw; i++) for(size_t b = 0; b < bytewidth; b++) //b = current byte of this pixel
out[bytewidth * w * (passtop + spacey * y) + bytewidth * (passleft + spacex * i) + b] = linen[bytewidth * i + b];
else for(size_t i = 0; i < passw; i++)
{
size_t obp = bpp * w * (passtop + spacey * y) + bpp * (passleft + spacex * i), bp = i * bpp;
for(size_t b = 0; b < bpp; b++) setBitOfReversedStream(obp, out, readBitFromReversedStream(bp, &linen[0]));
}
unsigned char* temp = linen; linen = lineo; lineo = temp; //swap the two buffer pointers "line old" and "line new"
}
}
static unsigned long readBitFromReversedStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (7 - (bitp & 0x7))) & 1; bitp++; return result;}
static unsigned long readBitsFromReversedStream(size_t& bitp, const unsigned char* bits, unsigned long nbits)
{
unsigned long result = 0;
for(size_t i = nbits - 1; i < nbits; i--) result += ((readBitFromReversedStream(bitp, bits)) << i);
return result;
}
void setBitOfReversedStream(size_t& bitp, unsigned char* bits, unsigned long bit) { bits[bitp >> 3] |= (bit << (7 - (bitp & 0x7))); bitp++; }
unsigned long read32bitInt(const unsigned char* buffer) { return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; }
int checkColorValidity(unsigned long colorType, unsigned long bd) //return type is a LodePNG error code
{
if((colorType == 2 || colorType == 4 || colorType == 6)) { if(!(bd == 8 || bd == 16)) return 37; else return 0; }
else if(colorType == 0) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; else return 0; }
else if(colorType == 3) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; else return 0; }
else return 31; //unexisting color type
}
unsigned long getBpp(const Info& info)
{
if(info.colorType == 2) return (3 * info.bitDepth);
else if(info.colorType >= 4) return (info.colorType - 2) * info.bitDepth;
else return info.bitDepth;
}
int convert(std::vector<unsigned char>& out, const unsigned char* in, Info& infoIn, unsigned long w, unsigned long h)
{ //converts from any color type to 32-bit. return value = LodePNG error code
size_t numpixels = w * h, bp = 0;
out.resize(numpixels * 4);
unsigned char* out_ = out.empty() ? 0 : &out[0]; //faster if compiled without optimization
if(infoIn.bitDepth == 8 && infoIn.colorType == 0) //greyscale
for(size_t i = 0; i < numpixels; i++)
{
out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[i];
out_[4 * i + 3] = (infoIn.key_defined && in[i] == infoIn.key_r) ? 0 : 255;
}
else if(infoIn.bitDepth == 8 && infoIn.colorType == 2) //RGB color
for(size_t i = 0; i < numpixels; i++)
{
for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[3 * i + c];
out_[4 * i + 3] = (infoIn.key_defined == 1 && in[3 * i + 0] == infoIn.key_r && in[3 * i + 1] == infoIn.key_g && in[3 * i + 2] == infoIn.key_b) ? 0 : 255;
}
else if(infoIn.bitDepth == 8 && infoIn.colorType == 3) //indexed color (palette)
for(size_t i = 0; i < numpixels; i++)
{
if(4U * in[i] >= infoIn.palette.size()) return 46;
for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * in[i] + c]; //get rgb colors from the palette
}
else if(infoIn.bitDepth == 8 && infoIn.colorType == 4) //greyscale with alpha
for(size_t i = 0; i < numpixels; i++)
{
out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i + 0];
out_[4 * i + 3] = in[2 * i + 1];
}
else if(infoIn.bitDepth == 8 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[4 * i + c]; //RGB with alpha
else if(infoIn.bitDepth == 16 && infoIn.colorType == 0) //greyscale
for(size_t i = 0; i < numpixels; i++)
{
out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i];
out_[4 * i + 3] = (infoIn.key_defined && 256U * in[i] + in[i + 1] == infoIn.key_r) ? 0 : 255;
}
else if(infoIn.bitDepth == 16 && infoIn.colorType == 2) //RGB color
for(size_t i = 0; i < numpixels; i++)
{
for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[6 * i + 2 * c];
out_[4 * i + 3] = (infoIn.key_defined && 256U*in[6*i+0]+in[6*i+1] == infoIn.key_r && 256U*in[6*i+2]+in[6*i+3] == infoIn.key_g && 256U*in[6*i+4]+in[6*i+5] == infoIn.key_b) ? 0 : 255;
}
else if(infoIn.bitDepth == 16 && infoIn.colorType == 4) //greyscale with alpha
for(size_t i = 0; i < numpixels; i++)
{
out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[4 * i]; //most significant byte
out_[4 * i + 3] = in[4 * i + 2];
}
else if(infoIn.bitDepth == 16 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[8 * i + 2 * c]; //RGB with alpha
else if(infoIn.bitDepth < 8 && infoIn.colorType == 0) //greyscale
for(size_t i = 0; i < numpixels; i++)
{
unsigned long value = (readBitsFromReversedStream(bp, in, infoIn.bitDepth) * 255) / ((1 << infoIn.bitDepth) - 1); //scale value from 0 to 255
out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = (unsigned char)(value);
out_[4 * i + 3] = (infoIn.key_defined && value && ((1U << infoIn.bitDepth) - 1U) == infoIn.key_r && ((1U << infoIn.bitDepth) - 1U)) ? 0 : 255;
}
else if(infoIn.bitDepth < 8 && infoIn.colorType == 3) //palette
for(size_t i = 0; i < numpixels; i++)
{
unsigned long value = readBitsFromReversedStream(bp, in, infoIn.bitDepth);
if(4 * value >= infoIn.palette.size()) return 47;
for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * value + c]; //get rgb colors from the palette
}
return 0;
}
unsigned char paethPredictor(short a, short b, short c) //Paeth predicter, used by PNG filter type 4
{
short p = a + b - c, pa = p > a ? (p - a) : (a - p), pb = p > b ? (p - b) : (b - p), pc = p > c ? (p - c) : (c - p);
return (unsigned char)((pa <= pb && pa <= pc) ? a : pb <= pc ? b : c);
}
};
PNG decoder = { }; decoder.decode(out_image, in_png, in_size, convert_to_rgba32);
image_width = decoder.info.width; image_height = decoder.info.height;
return decoder.error;
int r = 0;
unique_ptr<spng_ctx> ctx = unique_ptr<spng_ctx>(spng_ctx_new(0));
if(r = spng_set_crc_action(ctx.get(), SPNG_CRC_USE, SPNG_CRC_USE)) {
return r;
}
size_t limit = 1024 * 1024 * 64;
if(r = spng_set_chunk_limits(ctx.get(), limit, limit)) {
return r;
}
if(r = spng_set_png_buffer(ctx.get(), in_png, in_size)) {
return r;
}
struct spng_ihdr ihdr;
if(r = spng_get_ihdr(ctx.get(), &ihdr)) {
return r;
}
image_width = ihdr.width;
image_height = ihdr.height;
int fmt = SPNG_FMT_RGBA8;
size_t out_size = 0;
if(r = spng_decoded_image_size(ctx.get(), fmt, &out_size)) {
return r;
}
out_image.resize(out_size);
if(r = spng_decode_image(ctx.get(), out_image.data(), out_image.size(), fmt, 0)) {
return r;
}
return 0;
}

View file

@ -470,6 +470,7 @@
<ClInclude Include="Scale2x\scalebit.h" />
<ClInclude Include="Serializer.h" />
<ClInclude Include="sha1.h" />
<ClInclude Include="spng.h" />
<ClInclude Include="StringUtilities.h" />
<ClInclude Include="SZReader.h" />
<ClInclude Include="UPnPPortMapper.h" />
@ -592,7 +593,18 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Optimize|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="md5.cpp" />
<ClCompile Include="miniz.cpp" />
<ClCompile Include="miniz.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Profile|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Libretro|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Optimize|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Profile|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Libretro|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Optimize|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="NTSC\nes_ntsc.cpp" />
<ClCompile Include="NTSC\snes_ntsc.cpp" />
<ClCompile Include="Patches\BpsPatcher.cpp" />
@ -641,6 +653,19 @@
<ClCompile Include="sha1.cpp" />
<ClCompile Include="SimpleLock.cpp" />
<ClCompile Include="Socket.cpp" />
<ClCompile Include="spng.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
<CompileAs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Default</CompileAs>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Profile|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Libretro|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Optimize|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Profile|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Libretro|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='PGO Optimize|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>

View file

@ -226,6 +226,9 @@
<ClInclude Include="kissfft.h">
<Filter>Misc</Filter>
</ClInclude>
<ClInclude Include="spng.h">
<Filter>Misc</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="xBRZ\xbrz.cpp">
@ -343,5 +346,6 @@
<ClCompile Include="Patches\UpsPatcher.cpp" />
<ClCompile Include="NTSC\nes_ntsc.cpp" />
<ClCompile Include="NTSC\snes_ntsc.cpp" />
<ClCompile Include="spng.c" />
</ItemGroup>
</Project>

View file

@ -157,7 +157,6 @@
*/
#pragma once
#include "stdafx.h"
#ifndef MINIZ_HEADER_INCLUDED
#define MINIZ_HEADER_INCLUDED

4778
Utilities/spng.c Normal file

File diff suppressed because it is too large Load diff

618
Utilities/spng.h Normal file
View file

@ -0,0 +1,618 @@
/* SPDX-License-Identifier: (BSD-2-Clause AND libpng-2.0) */
#define SPNG_STATIC
#define SPNG_USE_MINIZ
#ifndef SPNG_H
#define SPNG_H
#ifdef __cplusplus
extern "C" {
#endif
#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(SPNG_STATIC)
#if defined(SPNG__BUILD)
#define SPNG_API __declspec(dllexport)
#else
#define SPNG_API __declspec(dllimport)
#endif
#else
#define SPNG_API
#endif
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include "miniz.h"
#define SPNG_VERSION_MAJOR 0
#define SPNG_VERSION_MINOR 6
#define SPNG_VERSION_PATCH 3
enum spng_errno
{
SPNG_IO_ERROR = -2,
SPNG_IO_EOF = -1,
SPNG_OK = 0,
SPNG_EINVAL,
SPNG_EMEM,
SPNG_EOVERFLOW,
SPNG_ESIGNATURE,
SPNG_EWIDTH,
SPNG_EHEIGHT,
SPNG_EUSER_WIDTH,
SPNG_EUSER_HEIGHT,
SPNG_EBIT_DEPTH,
SPNG_ECOLOR_TYPE,
SPNG_ECOMPRESSION_METHOD,
SPNG_EFILTER_METHOD,
SPNG_EINTERLACE_METHOD,
SPNG_EIHDR_SIZE,
SPNG_ENOIHDR,
SPNG_ECHUNK_POS,
SPNG_ECHUNK_SIZE,
SPNG_ECHUNK_CRC,
SPNG_ECHUNK_TYPE,
SPNG_ECHUNK_UNKNOWN_CRITICAL,
SPNG_EDUP_PLTE,
SPNG_EDUP_CHRM,
SPNG_EDUP_GAMA,
SPNG_EDUP_ICCP,
SPNG_EDUP_SBIT,
SPNG_EDUP_SRGB,
SPNG_EDUP_BKGD,
SPNG_EDUP_HIST,
SPNG_EDUP_TRNS,
SPNG_EDUP_PHYS,
SPNG_EDUP_TIME,
SPNG_EDUP_OFFS,
SPNG_EDUP_EXIF,
SPNG_ECHRM,
SPNG_EPLTE_IDX,
SPNG_ETRNS_COLOR_TYPE,
SPNG_ETRNS_NO_PLTE,
SPNG_EGAMA,
SPNG_EICCP_NAME,
SPNG_EICCP_COMPRESSION_METHOD,
SPNG_ESBIT,
SPNG_ESRGB,
SPNG_ETEXT,
SPNG_ETEXT_KEYWORD,
SPNG_EZTXT,
SPNG_EZTXT_COMPRESSION_METHOD,
SPNG_EITXT,
SPNG_EITXT_COMPRESSION_FLAG,
SPNG_EITXT_COMPRESSION_METHOD,
SPNG_EITXT_LANG_TAG,
SPNG_EITXT_TRANSLATED_KEY,
SPNG_EBKGD_NO_PLTE,
SPNG_EBKGD_PLTE_IDX,
SPNG_EHIST_NO_PLTE,
SPNG_EPHYS,
SPNG_ESPLT_NAME,
SPNG_ESPLT_DUP_NAME,
SPNG_ESPLT_DEPTH,
SPNG_ETIME,
SPNG_EOFFS,
SPNG_EEXIF,
SPNG_EIDAT_TOO_SHORT,
SPNG_EIDAT_STREAM,
SPNG_EZLIB,
SPNG_EFILTER,
SPNG_EBUFSIZ,
SPNG_EIO,
SPNG_EOF,
SPNG_EBUF_SET,
SPNG_EBADSTATE,
SPNG_EFMT,
SPNG_EFLAGS,
SPNG_ECHUNKAVAIL,
SPNG_ENCODE_ONLY,
SPNG_EOI,
SPNG_ENOPLTE,
SPNG_ECHUNK_LIMITS,
SPNG_EZLIB_INIT,
SPNG_ECHUNK_STDLEN,
SPNG_EINTERNAL,
};
enum spng_text_type
{
SPNG_TEXT = 1,
SPNG_ZTXT = 2,
SPNG_ITXT = 3
};
enum spng_color_type
{
SPNG_COLOR_TYPE_GRAYSCALE = 0,
SPNG_COLOR_TYPE_TRUECOLOR = 2,
SPNG_COLOR_TYPE_INDEXED = 3,
SPNG_COLOR_TYPE_GRAYSCALE_ALPHA = 4,
SPNG_COLOR_TYPE_TRUECOLOR_ALPHA = 6
};
enum spng_filter
{
SPNG_FILTER_NONE = 0,
SPNG_FILTER_SUB = 1,
SPNG_FILTER_UP = 2,
SPNG_FILTER_AVERAGE = 3,
SPNG_FILTER_PAETH = 4
};
enum spng_interlace_method
{
SPNG_INTERLACE_NONE = 0,
SPNG_INTERLACE_ADAM7 = 1
};
/* Channels are always in byte-order */
enum spng_format
{
SPNG_FMT_RGBA8 = 1,
SPNG_FMT_RGBA16 = 2,
SPNG_FMT_RGB8 = 4,
/* Partially implemented, see documentation */
SPNG_FMT_GA8 = 16,
SPNG_FMT_GA16 = 32,
SPNG_FMT_G8 = 64,
/* No conversion or scaling */
SPNG_FMT_PNG = 256, /* host-endian */
SPNG_FMT_RAW = 512 /* big-endian */
};
enum spng_ctx_flags
{
SPNG_CTX_IGNORE_ADLER32 = 1 /* Ignore checksum in DEFLATE streams */
};
enum spng_decode_flags
{
SPNG_DECODE_USE_TRNS = 1, /* Deprecated */
SPNG_DECODE_USE_GAMA = 2, /* Deprecated */
SPNG_DECODE_USE_SBIT = 8, /* Undocumented */
SPNG_DECODE_TRNS = 1, /* Apply transparency */
SPNG_DECODE_GAMMA = 2, /* Apply gamma correction */
SPNG_DECODE_PROGRESSIVE = 256 /* Initialize for progressive reads */
};
enum spng_crc_action
{
/* Default for critical chunks */
SPNG_CRC_ERROR = 0,
/* Discard chunk, invalid for critical chunks.
Since v0.6.2: default for ancillary chunks */
SPNG_CRC_DISCARD = 1,
/* Ignore and don't calculate checksum.
Since v0.6.2: also ignores checksums in DEFLATE streams */
SPNG_CRC_USE = 2
};
struct spng_ihdr
{
uint32_t width;
uint32_t height;
uint8_t bit_depth;
uint8_t color_type;
uint8_t compression_method;
uint8_t filter_method;
uint8_t interlace_method;
};
struct spng_plte_entry
{
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha; /* Reserved for internal use */
};
struct spng_plte
{
uint32_t n_entries;
struct spng_plte_entry entries[256];
};
struct spng_trns
{
uint16_t gray;
uint16_t red;
uint16_t green;
uint16_t blue;
uint32_t n_type3_entries;
uint8_t type3_alpha[256];
};
struct spng_chrm_int
{
uint32_t white_point_x;
uint32_t white_point_y;
uint32_t red_x;
uint32_t red_y;
uint32_t green_x;
uint32_t green_y;
uint32_t blue_x;
uint32_t blue_y;
};
struct spng_chrm
{
double white_point_x;
double white_point_y;
double red_x;
double red_y;
double green_x;
double green_y;
double blue_x;
double blue_y;
};
struct spng_iccp
{
char profile_name[80];
size_t profile_len;
char *profile;
};
struct spng_sbit
{
uint8_t grayscale_bits;
uint8_t red_bits;
uint8_t green_bits;
uint8_t blue_bits;
uint8_t alpha_bits;
};
struct spng_text
{
char keyword[80];
int type;
size_t length;
char *text;
uint8_t compression_flag; /* iTXt only */
uint8_t compression_method; /* iTXt, ztXt only */
char *language_tag; /* iTXt only */
char *translated_keyword; /* iTXt only */
};
struct spng_bkgd
{
uint16_t gray; /* Only for gray/gray alpha */
uint16_t red;
uint16_t green;
uint16_t blue;
uint16_t plte_index; /* Only for indexed color */
};
struct spng_hist
{
uint16_t frequency[256];
};
struct spng_phys
{
uint32_t ppu_x, ppu_y;
uint8_t unit_specifier;
};
struct spng_splt_entry
{
uint16_t red;
uint16_t green;
uint16_t blue;
uint16_t alpha;
uint16_t frequency;
};
struct spng_splt
{
char name[80];
uint8_t sample_depth;
uint32_t n_entries;
struct spng_splt_entry *entries;
};
struct spng_time
{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
};
struct spng_offs
{
int32_t x, y;
uint8_t unit_specifier;
};
struct spng_exif
{
size_t length;
char *data;
};
struct spng_chunk
{
size_t offset;
uint32_t length;
uint8_t type[4];
uint32_t crc;
};
typedef void* spng_malloc_fn(size_t size);
typedef void* spng_realloc_fn(void* ptr, size_t size);
typedef void* spng_calloc_fn(size_t count, size_t size);
typedef void spng_free_fn(void* ptr);
struct spng_alloc
{
spng_malloc_fn *malloc_fn;
spng_realloc_fn *realloc_fn;
spng_calloc_fn *calloc_fn;
spng_free_fn *free_fn;
};
struct spng_row_info
{
uint32_t scanline_idx;
uint32_t row_num; /* deinterlaced row index */
int pass;
uint8_t filter;
};
typedef struct spng_ctx spng_ctx;
struct spng_subimage
{
uint32_t width;
uint32_t height;
size_t out_width; /* byte width based on output format */
size_t scanline_width;
};
struct spng_plte_entry16
{
uint16_t red;
uint16_t green;
uint16_t blue;
uint16_t alpha;
};
struct spng_chunk_bitfield
{
unsigned ihdr : 1;
unsigned plte : 1;
unsigned chrm : 1;
unsigned iccp : 1;
unsigned gama : 1;
unsigned sbit : 1;
unsigned srgb : 1;
unsigned text : 1;
unsigned bkgd : 1;
unsigned hist : 1;
unsigned trns : 1;
unsigned phys : 1;
unsigned splt : 1;
unsigned time : 1;
unsigned offs : 1;
unsigned exif : 1;
};
struct decode_flags
{
unsigned apply_trns : 1;
unsigned apply_gamma : 1;
unsigned use_sbit : 1;
unsigned indexed : 1;
unsigned do_scaling : 1;
unsigned interlaced : 1;
unsigned same_layout : 1;
unsigned zerocopy : 1;
unsigned unpack : 1;
};
typedef int spng_read_fn(spng_ctx* ctx, void* user, void* dest, size_t length);
typedef void spng__undo(spng_ctx* ctx);
struct spng_ctx
{
size_t data_size;
size_t bytes_read;
unsigned char* stream_buf;
const unsigned char* data;
/* User-defined pointers for streaming */
spng_read_fn* read_fn;
void* read_user_ptr;
/* Used for buffer reads */
const unsigned char* png_buf; /* base pointer for the buffer */
size_t bytes_left;
size_t last_read_size;
/* These are updated by read_header()/read_chunk_bytes() */
struct spng_chunk current_chunk;
uint32_t cur_chunk_bytes_left;
uint32_t cur_actual_crc;
struct spng_alloc alloc;
int flags; /* context flags */
int fmt;
unsigned state : 4;
unsigned streaming : 1;
unsigned encode_only : 1;
unsigned strict : 1;
unsigned discard : 1;
unsigned skip_crc : 1;
unsigned prev_was_idat : 1;
spng__undo* undo;
/* input file contains this chunk */
struct spng_chunk_bitfield file;
/* chunk was stored with spng_set_*() */
struct spng_chunk_bitfield user;
/* chunk was stored by reading or with spng_set_*() */
struct spng_chunk_bitfield stored;
/* used to reset the above in case of an error */
struct spng_chunk_bitfield prev_stored;
struct spng_chunk first_idat, last_idat;
uint32_t max_width, max_height;
size_t max_chunk_size;
size_t chunk_cache_limit;
size_t chunk_cache_usage;
int crc_action_critical;
int crc_action_ancillary;
struct spng_ihdr ihdr;
struct spng_plte plte;
struct spng_chrm_int chrm_int;
struct spng_iccp iccp;
uint32_t gama;
struct spng_sbit sbit;
uint8_t srgb_rendering_intent;
uint32_t n_text;
struct spng_text2* text_list;
struct spng_bkgd bkgd;
struct spng_hist hist;
struct spng_trns trns;
struct spng_phys phys;
uint32_t n_splt;
struct spng_splt* splt_list;
struct spng_time time;
struct spng_offs offs;
struct spng_exif exif;
struct spng_subimage subimage[7];
z_stream zstream;
unsigned char* scanline_buf, * prev_scanline_buf, * row_buf;
unsigned char* scanline, * prev_scanline, * row;
size_t total_out_size;
size_t out_width; /* total_out_size / ihdr.height */
unsigned channels;
unsigned bytes_per_pixel; /* input PNG */
unsigned pixel_size; /* output format */
int widest_pass;
int last_pass; /* last non-empty pass */
uint16_t* gamma_lut; /* points to either _lut8 or _lut16 */
uint16_t* gamma_lut16;
uint16_t gamma_lut8[256];
unsigned char trns_px[8];
struct spng_plte_entry16 decode_plte[256];
struct spng_sbit decode_sb;
struct decode_flags decode_flags;
struct spng_row_info row_info;
};
SPNG_API spng_ctx *spng_ctx_new(int flags);
SPNG_API spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags);
SPNG_API void spng_ctx_free(spng_ctx *ctx);
SPNG_API int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size);
SPNG_API int spng_set_png_stream(spng_ctx *ctx, spng_read_fn *read_fn, void *user);
SPNG_API int spng_set_png_file(spng_ctx *ctx, FILE *file);
SPNG_API int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height);
SPNG_API int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height);
SPNG_API int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_size);
SPNG_API int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_size);
SPNG_API int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary);
SPNG_API int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len);
/* Decode */
SPNG_API int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags);
/* Progressive decode */
SPNG_API int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len);
SPNG_API int spng_decode_row(spng_ctx *ctx, void *out, size_t len);
SPNG_API int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info);
SPNG_API int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr);
SPNG_API int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte);
SPNG_API int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns);
SPNG_API int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm);
SPNG_API int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int);
SPNG_API int spng_get_gama(spng_ctx *ctx, double *gamma);
SPNG_API int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp);
SPNG_API int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit);
SPNG_API int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent);
SPNG_API int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text);
SPNG_API int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd);
SPNG_API int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist);
SPNG_API int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys);
SPNG_API int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt);
SPNG_API int spng_get_time(spng_ctx *ctx, struct spng_time *time);
/* Official extensions */
SPNG_API int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs);
SPNG_API int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif);
SPNG_API int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr);
SPNG_API int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte);
SPNG_API int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns);
SPNG_API int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm);
SPNG_API int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int);
SPNG_API int spng_set_gama(spng_ctx *ctx, double gamma);
SPNG_API int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp);
SPNG_API int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit);
SPNG_API int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent);
SPNG_API int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text);
SPNG_API int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd);
SPNG_API int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist);
SPNG_API int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys);
SPNG_API int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt);
SPNG_API int spng_set_time(spng_ctx *ctx, struct spng_time *time);
/* Official extensions */
SPNG_API int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs);
SPNG_API int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif);
SPNG_API const char *spng_strerror(int err);
SPNG_API const char *spng_version_string(void);
#ifdef __cplusplus
}
#endif
#endif /* SPNG_H */