mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
NES: Added HD pack builder tool
This commit is contained in:
parent
db2db7ddb1
commit
e96d6f3ca5
34 changed files with 1206 additions and 55 deletions
|
@ -24,6 +24,8 @@
|
|||
<ClInclude Include="Debugger\DisassemblySearch.h" />
|
||||
<ClInclude Include="Debugger\FrozenAddressManager.h" />
|
||||
<ClInclude Include="Debugger\StepBackManager.h" />
|
||||
<ClInclude Include="NES\HdPacks\HdBuilderPpu.h" />
|
||||
<ClInclude Include="NES\HdPacks\HdPackBuilder.h" />
|
||||
<ClInclude Include="NES\Mappers\Audio\emu2413.h" />
|
||||
<ClInclude Include="SNES\DSP\DspInterpolation.h" />
|
||||
<ClInclude Include="SNES\DSP\Dsp.h" />
|
||||
|
@ -702,6 +704,7 @@
|
|||
<ClCompile Include="NES\HdPacks\HdAudioDevice.cpp" />
|
||||
<ClCompile Include="NES\HdPacks\HdNesPack.cpp" />
|
||||
<ClCompile Include="NES\HdPacks\HdNesPpu.cpp" />
|
||||
<ClCompile Include="NES\HdPacks\HdPackBuilder.cpp" />
|
||||
<ClCompile Include="NES\HdPacks\HdPackLoader.cpp" />
|
||||
<ClCompile Include="NES\HdPacks\HdVideoFilter.cpp" />
|
||||
<ClCompile Include="NES\HdPacks\OggMixer.cpp" />
|
||||
|
|
|
@ -2610,6 +2610,12 @@
|
|||
<ClInclude Include="NES\Mappers\Audio\emu2413.h">
|
||||
<Filter>NES\Mappers\Audio</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NES\HdPacks\HdBuilderPpu.h">
|
||||
<Filter>NES\HdPacks</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NES\HdPacks\HdPackBuilder.h">
|
||||
<Filter>NES\HdPacks</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Shared\Video\RotateFilter.cpp">
|
||||
|
@ -2741,6 +2747,9 @@
|
|||
<ClCompile Include="NES\Mappers\Audio\emu2413.cpp">
|
||||
<Filter>NES\Mappers\Audio</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NES\HdPacks\HdPackBuilder.cpp">
|
||||
<Filter>NES\HdPacks</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="PCE">
|
||||
|
|
|
@ -45,11 +45,10 @@ NesDebugger::NesDebugger(Debugger* debugger) : IDebugger(debugger->GetEmulator()
|
|||
|
||||
_console = console;
|
||||
_cpu = console->GetCpu();
|
||||
_ppu = console->GetPpu();
|
||||
_mapper = console->GetMapper();
|
||||
_memoryManager = console->GetMemoryManager();
|
||||
|
||||
_traceLogger.reset(new NesTraceLogger(debugger, this, _ppu));
|
||||
_traceLogger.reset(new NesTraceLogger(debugger, this, console));
|
||||
_disassembler = debugger->GetDisassembler();
|
||||
_memoryAccessCounter = debugger->GetMemoryAccessCounter();
|
||||
_settings = debugger->GetEmulator()->GetSettings();
|
||||
|
@ -271,7 +270,7 @@ void NesDebugger::Step(int32_t stepCount, StepType type)
|
|||
case StepType::CpuCycleStep: step.CpuCycleStepCount = stepCount; break;
|
||||
case StepType::PpuStep: step.PpuStepCount = stepCount; break;
|
||||
case StepType::PpuScanline: step.PpuStepCount = 341 * stepCount; break;
|
||||
case StepType::PpuFrame: step.PpuStepCount = 341 * _ppu->GetScanlineCount() * stepCount; break;
|
||||
case StepType::PpuFrame: step.PpuStepCount = 341 * _console->GetPpu()->GetScanlineCount() * stepCount; break;
|
||||
case StepType::SpecificScanline: step.BreakScanline = stepCount; break;
|
||||
}
|
||||
_step.reset(new StepRequest(step));
|
||||
|
@ -279,7 +278,7 @@ void NesDebugger::Step(int32_t stepCount, StepType type)
|
|||
|
||||
void NesDebugger::DrawPartialFrame()
|
||||
{
|
||||
_ppu->DebugSendFrame();
|
||||
_console->GetPpu()->DebugSendFrame();
|
||||
}
|
||||
|
||||
void NesDebugger::ProcessCallStackUpdates(AddressInfo& destAddr, uint16_t destPc)
|
||||
|
@ -349,11 +348,11 @@ void NesDebugger::ProcessPpuWrite(uint16_t addr, uint8_t value, MemoryType memor
|
|||
void NesDebugger::ProcessPpuCycle()
|
||||
{
|
||||
if(_ppuTools->HasOpenedViewer()) {
|
||||
_ppuTools->UpdateViewers(_ppu->GetCurrentScanline(), _ppu->GetCurrentCycle());
|
||||
_ppuTools->UpdateViewers(_console->GetPpu()->GetCurrentScanline(), _console->GetPpu()->GetCurrentCycle());
|
||||
}
|
||||
|
||||
if(_step->HasRequest) {
|
||||
if(_step->HasScanlineBreakRequest() && _ppu->GetCurrentCycle() == 0 && _ppu->GetCurrentScanline() == _step->BreakScanline) {
|
||||
if(_step->HasScanlineBreakRequest() && _console->GetPpu()->GetCurrentCycle() == 0 && _console->GetPpu()->GetCurrentScanline() == _step->BreakScanline) {
|
||||
_debugger->SleepUntilResume(CpuType::Nes, _step->GetBreakSource());
|
||||
} else if(_step->PpuStepCount > 0) {
|
||||
_step->PpuStepCount--;
|
||||
|
@ -446,12 +445,12 @@ BaseState& NesDebugger::GetState()
|
|||
|
||||
void NesDebugger::GetPpuState(BaseState& state)
|
||||
{
|
||||
_ppu->GetState((NesPpuState&)state);
|
||||
_console->GetPpu()->GetState((NesPpuState&)state);
|
||||
}
|
||||
|
||||
void NesDebugger::SetPpuState(BaseState& state)
|
||||
{
|
||||
_ppu->SetState((NesPpuState&)state);
|
||||
_console->GetPpu()->SetState((NesPpuState&)state);
|
||||
}
|
||||
|
||||
ITraceLogger* NesDebugger::GetTraceLogger()
|
||||
|
|
|
@ -36,7 +36,6 @@ class NesDebugger final : public IDebugger
|
|||
|
||||
NesConsole* _console;
|
||||
NesCpu* _cpu;
|
||||
BaseNesPpu* _ppu;
|
||||
BaseMapper* _mapper;
|
||||
NesMemoryManager* _memoryManager;
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
NesEventManager::NesEventManager(Debugger *debugger, NesConsole* console)
|
||||
{
|
||||
_debugger = debugger;
|
||||
_console = console;
|
||||
_cpu = console->GetCpu();
|
||||
_ppu = console->GetPpu();
|
||||
_mapper = console->GetMapper();
|
||||
_snapshotScanlineOffset = -1;
|
||||
|
||||
NesDefaultVideoFilter::GetFullPalette(_palette, console->GetNesConfig(), _ppu->GetPpuModel());
|
||||
NesDefaultVideoFilter::GetFullPalette(_palette, console->GetNesConfig(), console->GetPpu()->GetPpuModel());
|
||||
|
||||
_ppuBuffer = new uint16_t[NesConstants::ScreenPixelCount];
|
||||
memset(_ppuBuffer, 0, NesConstants::ScreenPixelCount * sizeof(uint16_t));
|
||||
|
@ -33,11 +33,12 @@ NesEventManager::~NesEventManager()
|
|||
|
||||
void NesEventManager::AddEvent(DebugEventType type, MemoryOperationInfo& operation, int32_t breakpointId)
|
||||
{
|
||||
BaseNesPpu* ppu = _console->GetPpu();
|
||||
DebugEventInfo evt = {};
|
||||
evt.Type = type;
|
||||
evt.Operation = operation;
|
||||
evt.Scanline = (int16_t)_ppu->GetCurrentScanline();
|
||||
evt.Cycle = (uint16_t)_ppu->GetCurrentCycle();
|
||||
evt.Scanline = (int16_t)ppu->GetCurrentScanline();
|
||||
evt.Cycle = (uint16_t)ppu->GetCurrentCycle();
|
||||
evt.BreakpointId = breakpointId;
|
||||
evt.ProgramCounter = _debugger->GetProgramCounter(CpuType::Nes, true);
|
||||
evt.DmaChannel = -1;
|
||||
|
@ -46,7 +47,7 @@ void NesEventManager::AddEvent(DebugEventType type, MemoryOperationInfo& operati
|
|||
bool isWrite = operation.Type == MemoryOperationType::Write || operation.Type == MemoryOperationType::DmaWrite || operation.Type == MemoryOperationType::DummyWrite;
|
||||
if(isWrite && (addr & 0xE000) == 0x2000) {
|
||||
NesPpuState state;
|
||||
_ppu->GetState(state);
|
||||
ppu->GetState(state);
|
||||
switch(addr & 0x07) {
|
||||
case 4:
|
||||
//OAM write
|
||||
|
@ -85,7 +86,7 @@ void NesEventManager::AddEvent(DebugEventType type)
|
|||
{
|
||||
MemoryOperationInfo op = {};
|
||||
if(type == DebugEventType::BgColorChange) {
|
||||
op.Address = _ppu->GetCurrentBgColor();
|
||||
op.Address = _console->GetPpu()->GetCurrentBgColor();
|
||||
}
|
||||
AddEvent(type, op, -1);
|
||||
}
|
||||
|
@ -193,18 +194,19 @@ void NesEventManager::ConvertScanlineCycleToRowColumn(int32_t& x, int32_t& y)
|
|||
|
||||
uint32_t NesEventManager::TakeEventSnapshot(bool forAutoRefresh)
|
||||
{
|
||||
BaseNesPpu* ppu = _console->GetPpu();
|
||||
DebugBreakHelper breakHelper(_debugger);
|
||||
auto lock = _lock.AcquireSafe();
|
||||
|
||||
uint16_t cycle = _ppu->GetCurrentCycle();
|
||||
uint16_t scanline = _ppu->GetCurrentScanline() + 1;
|
||||
uint16_t cycle = _console->GetPpu()->GetCurrentCycle();
|
||||
uint16_t scanline = ppu->GetCurrentScanline() + 1;
|
||||
|
||||
if(scanline >= 240 || (scanline == 0 && cycle == 0)) {
|
||||
memcpy(_ppuBuffer, _ppu->GetScreenBuffer(false), NesConstants::ScreenPixelCount * sizeof(uint16_t));
|
||||
memcpy(_ppuBuffer, ppu->GetScreenBuffer(false), NesConstants::ScreenPixelCount * sizeof(uint16_t));
|
||||
} else {
|
||||
uint32_t offset = (NesConstants::ScreenWidth * scanline);
|
||||
memcpy(_ppuBuffer, _ppu->GetScreenBuffer(false), offset * sizeof(uint16_t));
|
||||
memcpy(_ppuBuffer + offset, _ppu->GetScreenBuffer(true) + offset, (NesConstants::ScreenPixelCount - offset) * sizeof(uint16_t));
|
||||
memcpy(_ppuBuffer, ppu->GetScreenBuffer(false), offset * sizeof(uint16_t));
|
||||
memcpy(_ppuBuffer + offset, ppu->GetScreenBuffer(true) + offset, (NesConstants::ScreenPixelCount - offset) * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
_snapshotCurrentFrame = _debugEvents;
|
||||
|
@ -212,7 +214,7 @@ uint32_t NesEventManager::TakeEventSnapshot(bool forAutoRefresh)
|
|||
_snapshotScanline = scanline;
|
||||
_snapshotCycle = cycle;
|
||||
_forAutoRefresh = forAutoRefresh;
|
||||
_scanlineCount = _ppu->GetScanlineCount();
|
||||
_scanlineCount = ppu->GetScanlineCount();
|
||||
return _scanlineCount;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ private:
|
|||
NesEventViewerConfig _config = {};
|
||||
|
||||
NesCpu* _cpu = nullptr;
|
||||
BaseNesPpu* _ppu = nullptr;
|
||||
NesConsole* _console = nullptr;
|
||||
BaseMapper* _mapper = nullptr;
|
||||
Debugger* _debugger = nullptr;
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
#include "pch.h"
|
||||
#include "NES/Debugger/NesTraceLogger.h"
|
||||
#include "NES/NesPpu.h"
|
||||
#include "NES/NesConsole.h"
|
||||
#include "NES/BaseNesPpu.h"
|
||||
#include "NES/NesTypes.h"
|
||||
#include "Debugger/DisassemblyInfo.h"
|
||||
#include "Debugger/Debugger.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
|
||||
NesTraceLogger::NesTraceLogger(Debugger* debugger, IDebugger* cpuDebugger, BaseNesPpu* ppu) : BaseTraceLogger(debugger, cpuDebugger, CpuType::Nes)
|
||||
NesTraceLogger::NesTraceLogger(Debugger* debugger, IDebugger* cpuDebugger, NesConsole* console) : BaseTraceLogger(debugger, cpuDebugger, CpuType::Nes)
|
||||
{
|
||||
_ppu = ppu;
|
||||
_console = console;
|
||||
}
|
||||
|
||||
RowDataType NesTraceLogger::GetFormatTagType(string& tag)
|
||||
|
@ -48,10 +49,11 @@ void NesTraceLogger::GetTraceRow(string &output, NesCpuState &cpuState, TraceLog
|
|||
|
||||
void NesTraceLogger::LogPpuState()
|
||||
{
|
||||
BaseNesPpu* ppu = _console->GetPpu();
|
||||
_ppuState[_currentPos] = {
|
||||
_ppu->GetCurrentCycle(),
|
||||
_ppu->GetCurrentCycle(),
|
||||
_ppu->GetCurrentScanline(),
|
||||
_ppu->GetFrameCount()
|
||||
ppu->GetCurrentCycle(),
|
||||
ppu->GetCurrentCycle(),
|
||||
ppu->GetCurrentScanline(),
|
||||
ppu->GetFrameCount()
|
||||
};
|
||||
}
|
|
@ -5,18 +5,18 @@
|
|||
|
||||
class DisassemblyInfo;
|
||||
class Debugger;
|
||||
class BaseNesPpu;
|
||||
class NesConsole;
|
||||
|
||||
class NesTraceLogger : public BaseTraceLogger<NesTraceLogger, NesCpuState>
|
||||
{
|
||||
private:
|
||||
BaseNesPpu* _ppu = nullptr;
|
||||
NesConsole* _console = nullptr;
|
||||
|
||||
protected:
|
||||
RowDataType GetFormatTagType(string& tag) override;
|
||||
|
||||
public:
|
||||
NesTraceLogger(Debugger* debugger, IDebugger* cpuDebugger, BaseNesPpu* ppu);
|
||||
NesTraceLogger(Debugger* debugger, IDebugger* cpuDebugger, NesConsole* console);
|
||||
|
||||
void GetTraceRow(string& output, NesCpuState& cpuState, TraceLogPpuState& ppuState, DisassemblyInfo& disassemblyInfo);
|
||||
void LogPpuState();
|
||||
|
|
157
Core/NES/HdPacks/HdBuilderPpu.h
Normal file
157
Core/NES/HdPacks/HdBuilderPpu.h
Normal file
|
@ -0,0 +1,157 @@
|
|||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "NES/HdPacks/HdNesPpu.h"
|
||||
#include "NES/HdPacks/HdNesPack.h"
|
||||
#include "NES/HdPacks/HdPackBuilder.h"
|
||||
#include "Shared/Video/VideoDecoder.h"
|
||||
#include "Shared/RewindManager.h"
|
||||
#include "Utilities/Serializer.h"
|
||||
|
||||
class HdBuilderPpu final : public NesPpu<HdBuilderPpu>
|
||||
{
|
||||
private:
|
||||
HdPackBuilder* _hdPackBuilder = nullptr;
|
||||
bool _needChrHash = false;
|
||||
uint32_t _chrRamBankSize = 0;
|
||||
uint32_t _chrRamIndexMask = 0;
|
||||
vector<uint32_t> _bankHashes;
|
||||
|
||||
NesSpriteInfoEx _exSpriteInfo[64] = {};
|
||||
NesTileInfoEx _previousTileEx = {};
|
||||
NesTileInfoEx _currentTileEx = {};
|
||||
NesTileInfoEx _nextTileEx = {};
|
||||
|
||||
public:
|
||||
__forceinline bool RemoveSpriteLimit() { return _console->GetNesConfig().RemoveSpriteLimit; }
|
||||
__forceinline bool UseAdaptiveSpriteLimit() { return _console->GetNesConfig().AdaptiveSpriteLimit; }
|
||||
void* OnBeforeSendFrame() { return nullptr; }
|
||||
|
||||
__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 StoreTileInformation()
|
||||
{
|
||||
_previousTileEx = _currentTileEx;
|
||||
_currentTileEx = _nextTileEx;
|
||||
|
||||
uint8_t tileIndex = ReadVram(GetNameTableAddr());
|
||||
uint16_t tileAddr = (tileIndex << 4) | (_videoRamAddr >> 12) | _control.BackgroundPatternAddr;
|
||||
|
||||
_nextTileEx.OffsetY = _videoRamAddr >> 12;
|
||||
_nextTileEx.TileAddr = tileAddr;
|
||||
_nextTileEx.AbsoluteTileAddr = _mapper->GetPpuAbsoluteAddress(tileAddr).Address;
|
||||
}
|
||||
|
||||
__forceinline void ProcessScanline()
|
||||
{
|
||||
ProcessScanlineImpl();
|
||||
}
|
||||
|
||||
void DrawPixel()
|
||||
{
|
||||
if(IsRenderingEnabled() || ((_videoRamAddr & 0x3F00) != 0x3F00)) {
|
||||
BaseMapper* mapper = _console->GetMapper();
|
||||
bool isChrRam = !mapper->HasChrRom();
|
||||
|
||||
_lastSprite = nullptr;
|
||||
uint32_t color = GetPixelColor();
|
||||
_currentOutputBuffer[(_scanline << 8) + _cycle - 1] = _paletteRam[color & 0x03 ? color : 0];
|
||||
uint32_t backgroundColor = 0;
|
||||
if(_mask.BackgroundEnabled && _cycle > _minimumDrawBgCycle) {
|
||||
backgroundColor = (((_lowBitShift << _xScroll) & 0x8000) >> 15) | (((_highBitShift << _xScroll) & 0x8000) >> 14);
|
||||
}
|
||||
|
||||
if(_needChrHash ) {
|
||||
uint16_t addr = 0;
|
||||
_bankHashes.clear();
|
||||
while(addr < 0x2000) {
|
||||
uint32_t hash = 0;
|
||||
for(uint16_t i = 0; i < _chrRamBankSize; i++) {
|
||||
hash += mapper->DebugReadVram(i + addr);
|
||||
hash = (hash << 1) | (hash >> 31);
|
||||
}
|
||||
_bankHashes.push_back(hash);
|
||||
addr += _chrRamBankSize;
|
||||
}
|
||||
_needChrHash = false;
|
||||
}
|
||||
|
||||
bool hasBgSprite = false;
|
||||
if(_lastSprite && _mask.SpritesEnabled) {
|
||||
uint8_t spriteIndex = (uint8_t)((_lastSprite - _spriteTiles) / sizeof(NesSpriteInfo));
|
||||
NesSpriteInfoEx& spriteInfoEx = _exSpriteInfo[spriteIndex];
|
||||
|
||||
if(backgroundColor == 0) {
|
||||
for(uint8_t i = 0; i < _spriteCount; i++) {
|
||||
if(_spriteTiles[i].BackgroundPriority) {
|
||||
hasBgSprite = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(spriteInfoEx.AbsoluteTileAddr >= 0) {
|
||||
HdPpuTileInfo sprite = {};
|
||||
sprite.TileIndex = (isChrRam ? (spriteInfoEx.TileAddr & _chrRamIndexMask) : spriteInfoEx.AbsoluteTileAddr) / 16;
|
||||
sprite.PaletteColors = ReadPaletteRam(_lastSprite->PaletteOffset + 3) | (ReadPaletteRam(_lastSprite->PaletteOffset + 2) << 8) | (ReadPaletteRam(_lastSprite->PaletteOffset + 1) << 16) | 0xFF000000;
|
||||
sprite.IsChrRamTile = isChrRam;
|
||||
mapper->CopyChrTile(spriteInfoEx.AbsoluteTileAddr & 0xFFFFFFF0, sprite.TileData);
|
||||
|
||||
_hdPackBuilder->ProcessTile(_cycle - 1, _scanline, spriteInfoEx.AbsoluteTileAddr, sprite, mapper, false, _bankHashes[spriteInfoEx.TileAddr / _chrRamBankSize], false);
|
||||
}
|
||||
}
|
||||
|
||||
if(_mask.BackgroundEnabled) {
|
||||
bool usePrev = (_xScroll + ((_cycle - 1) & 0x07) < 8);
|
||||
uint8_t tilePalette = usePrev ? _previousTilePalette : _currentTilePalette;
|
||||
NesTileInfoEx& lastTileEx = usePrev ? _previousTileEx : _currentTileEx;
|
||||
//TileInfo* lastTile = &((_xScroll + ((_cycle - 1) & 0x07) < 8) ? _previousTile : _currentTile);
|
||||
if(lastTileEx.AbsoluteTileAddr >= 0) {
|
||||
HdPpuTileInfo tile = {};
|
||||
tile.TileIndex = (isChrRam ? (lastTileEx.TileAddr & _chrRamIndexMask) : lastTileEx.AbsoluteTileAddr) / 16;
|
||||
tile.PaletteColors = ReadPaletteRam(tilePalette + 3) | (ReadPaletteRam(tilePalette + 2) << 8) | (ReadPaletteRam(tilePalette + 1) << 16) | (ReadPaletteRam(0) << 24);
|
||||
tile.IsChrRamTile = isChrRam;
|
||||
mapper->CopyChrTile(lastTileEx.AbsoluteTileAddr & 0xFFFFFFF0, tile.TileData);
|
||||
|
||||
_hdPackBuilder->ProcessTile(_cycle - 1, _scanline, lastTileEx.AbsoluteTileAddr, tile, mapper, false, _bankHashes[lastTileEx.TileAddr / _chrRamBankSize], hasBgSprite);
|
||||
}
|
||||
}
|
||||
} 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[_videoRamAddr & 0x1F];
|
||||
}
|
||||
}
|
||||
|
||||
void WriteRAM(uint16_t addr, uint8_t value)
|
||||
{
|
||||
if(GetRegisterID(addr) == PpuRegisters::VideoMemoryData) {
|
||||
if(_videoRamAddr < 0x2000) {
|
||||
_needChrHash = true;
|
||||
}
|
||||
}
|
||||
NesPpu::WriteRam(addr, value);
|
||||
}
|
||||
|
||||
void Serialize(Serializer& s)
|
||||
{
|
||||
NesPpu::Serialize(s);
|
||||
if(!s.IsSaving()) {
|
||||
_needChrHash = true;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
HdBuilderPpu(NesConsole* console, HdPackBuilder* hdPackBuilder, uint32_t chrRamBankSize) : NesPpu(console)
|
||||
{
|
||||
_hdPackBuilder = hdPackBuilder;
|
||||
_chrRamBankSize = chrRamBankSize;
|
||||
_chrRamIndexMask = chrRamBankSize - 1;
|
||||
_needChrHash = true;
|
||||
}
|
||||
};
|
|
@ -7,22 +7,23 @@
|
|||
#include "NES/BaseMapper.h"
|
||||
#include "NES/HdPacks/HdData.h"
|
||||
|
||||
struct NesSpriteInfoEx
|
||||
{
|
||||
uint32_t AbsoluteTileAddr;
|
||||
uint16_t TileAddr;
|
||||
bool VerticalMirror;
|
||||
uint8_t OffsetY;
|
||||
};
|
||||
|
||||
struct NesTileInfoEx
|
||||
{
|
||||
uint32_t AbsoluteTileAddr;
|
||||
uint16_t TileAddr;
|
||||
uint8_t OffsetY;
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
450
Core/NES/HdPacks/HdPackBuilder.cpp
Normal file
450
Core/NES/HdPacks/HdPackBuilder.cpp
Normal file
|
@ -0,0 +1,450 @@
|
|||
#include "pch.h"
|
||||
#include <algorithm>
|
||||
#include "NES/HdPacks/HdPackBuilder.h"
|
||||
#include "NES/HdPacks/HdNesPack.h"
|
||||
#include "NES/BaseMapper.h"
|
||||
#include "NES/BaseNesPpu.h"
|
||||
#include "NES/NesConstants.h"
|
||||
#include "NES/HdPacks/HdPackLoader.h"
|
||||
#include "NES/NesDefaultVideoFilter.h"
|
||||
#include "Utilities/xBRZ/xbrz.h"
|
||||
#include "Utilities/HQX/hqx.h"
|
||||
#include "Utilities/Scale2x/scalebit.h"
|
||||
#include "Utilities/KreedSaiEagle/SaiEagle.h"
|
||||
#include "Utilities/FolderUtilities.h"
|
||||
#include "Utilities/PNGHelper.h"
|
||||
#include "Utilities/HexUtilities.h"
|
||||
#include "Utilities/VirtualFile.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
|
||||
HdPackBuilder::HdPackBuilder(Emulator* emu, PpuModel ppuModel, bool isChrRam, HdPackBuilderOptions options)
|
||||
{
|
||||
_emu = emu;
|
||||
_isChrRam = isChrRam;
|
||||
|
||||
_saveFolder = options.SaveFolder;
|
||||
options.SaveFolder = nullptr;
|
||||
|
||||
_options = options;
|
||||
|
||||
NesDefaultVideoFilter::GetFullPalette(_palette, _emu->GetSettings()->GetNesConfig(), ppuModel);
|
||||
|
||||
string existingPackDefinition = FolderUtilities::CombinePath(_saveFolder, "hires.txt");
|
||||
if(ifstream(existingPackDefinition)) {
|
||||
HdPackLoader::LoadHdNesPack(existingPackDefinition, _hdData);
|
||||
for(unique_ptr<HdPackTileInfo> &tile : _hdData.Tiles) {
|
||||
//Mark the tiles in the first PNGs as higher usage (preserves order when adding new tiles to an existing set)
|
||||
AddTile(tile.get(), 0xFFFFFFFF - tile->BitmapIndex);
|
||||
}
|
||||
|
||||
if(_hdData.Scale != _options.Scale) {
|
||||
_options.FilterType = ScaleFilterType::Prescale;
|
||||
}
|
||||
} else {
|
||||
_hdData.Scale = _options.Scale;
|
||||
}
|
||||
|
||||
_romName = FolderUtilities::GetFilename(_emu->GetRomInfo().RomFile.GetFileName(), false);
|
||||
}
|
||||
|
||||
HdPackBuilder::~HdPackBuilder()
|
||||
{
|
||||
SaveHdPack();
|
||||
}
|
||||
|
||||
void HdPackBuilder::AddTile(HdPackTileInfo *tile, uint32_t usageCount)
|
||||
{
|
||||
bool isTileBlank = _options.GroupBlankTiles ? tile->Blank : false;
|
||||
|
||||
int chrBankId = isTileBlank ? 0xFFFFFFFF : tile->ChrBankId;
|
||||
int palette = isTileBlank ? _blankTilePalette : tile->PaletteColors;
|
||||
|
||||
if(_tilesByChrBankByPalette.find(chrBankId) == _tilesByChrBankByPalette.end()) {
|
||||
_tilesByChrBankByPalette[chrBankId] = std::map<uint32_t, vector<HdPackTileInfo*>>();
|
||||
}
|
||||
|
||||
std::map<uint32_t, vector<HdPackTileInfo*>> &paletteMap = _tilesByChrBankByPalette[chrBankId];
|
||||
if(paletteMap.find(palette) == paletteMap.end()) {
|
||||
paletteMap[palette] = vector<HdPackTileInfo*>(256, nullptr);
|
||||
}
|
||||
|
||||
if(isTileBlank) {
|
||||
paletteMap[palette][_blankTileIndex] = tile;
|
||||
_blankTileIndex++;
|
||||
if(_blankTileIndex == _options.ChrRamBankSize / 16) {
|
||||
_blankTileIndex = 0;
|
||||
_blankTilePalette++;
|
||||
}
|
||||
} else {
|
||||
if(tile->TileIndex >= 0) {
|
||||
paletteMap[palette][tile->TileIndex % 256] = tile;
|
||||
} else {
|
||||
//FIXME: This will result in data loss if more than 256 tiles of the same palette exist in the hires.txt file
|
||||
//Currently this way to prevent issues when loading a CHR RAM HD pack into the recorder (because TileIndex is -1 in that case)
|
||||
for(int i = 0; i < 256; i++) {
|
||||
if(paletteMap[palette][i] == nullptr) {
|
||||
paletteMap[palette][i] = tile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_tilesByKey[tile->GetKey(false)] = tile;
|
||||
_tileUsageCount[tile->GetKey(false)] = usageCount;
|
||||
}
|
||||
|
||||
void HdPackBuilder::ProcessTile(uint32_t x, uint32_t y, uint16_t tileAddr, HdPpuTileInfo &tile, BaseMapper *mapper, bool isSprite, uint32_t chrBankHash, bool transparencyRequired)
|
||||
{
|
||||
if(_options.IgnoreOverscan) {
|
||||
OverscanDimensions overscan = _emu->GetSettings()->GetOverscan();
|
||||
if(x < overscan.Left || y < overscan.Top || (NesConstants::ScreenWidth - x - 1) < overscan.Right || (NesConstants::ScreenHeight - y - 1) < overscan.Bottom) {
|
||||
//Ignore tiles inside overscan
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto result = _tileUsageCount.find(tile.GetKey(false));
|
||||
if(result == _tileUsageCount.end()) {
|
||||
//Check to see if a default tile matches
|
||||
result = _tileUsageCount.find(tile.GetKey(true));
|
||||
}
|
||||
|
||||
if(result == _tileUsageCount.end()) {
|
||||
//First time seeing this tile/palette combination, store it
|
||||
HdPackTileInfo* hdTile = new HdPackTileInfo();
|
||||
hdTile->PaletteColors = tile.PaletteColors;
|
||||
hdTile->TileIndex = tile.TileIndex;
|
||||
hdTile->DefaultTile = false;
|
||||
hdTile->IsChrRamTile = _isChrRam;
|
||||
hdTile->Brightness = 255;
|
||||
hdTile->ChrBankId = _isChrRam ? chrBankHash : (tileAddr / 16 / 256);
|
||||
hdTile->TransparencyRequired = transparencyRequired;
|
||||
|
||||
memcpy(hdTile->TileData, tile.TileData, 16);
|
||||
|
||||
_hdData.Tiles.push_back(unique_ptr<HdPackTileInfo>(hdTile));
|
||||
AddTile(hdTile, 1);
|
||||
} else {
|
||||
if(transparencyRequired) {
|
||||
auto existingTile = _tilesByKey.find(tile.GetKey(false));
|
||||
if(existingTile != _tilesByKey.end()) {
|
||||
existingTile->second->TransparencyRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(result->second < 0x7FFFFFFF) {
|
||||
//Increase usage count
|
||||
result->second++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HdPackBuilder::GenerateHdTile(HdPackTileInfo *tile)
|
||||
{
|
||||
uint32_t hdScale = _hdData.Scale;
|
||||
|
||||
vector<uint32_t> originalTile = tile->ToRgb(_palette);
|
||||
vector<uint32_t> hdTile(8 * 8 * hdScale*hdScale, 0);
|
||||
|
||||
switch(_options.FilterType) {
|
||||
case ScaleFilterType::HQX:
|
||||
hqx(hdScale, originalTile.data(), hdTile.data(), 8, 8);
|
||||
break;
|
||||
|
||||
case ScaleFilterType::Prescale:
|
||||
hdTile.clear();
|
||||
for(uint8_t i = 0; i < 8 * hdScale; i++) {
|
||||
for(uint8_t j = 0; j < 8 * hdScale; j++) {
|
||||
hdTile.push_back(originalTile[i/hdScale*8+j/hdScale]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ScaleFilterType::Scale2x:
|
||||
scale(hdScale, hdTile.data(), 8 * sizeof(uint32_t) * hdScale, originalTile.data(), 8 * sizeof(uint32_t), 4, 8, 8);
|
||||
break;
|
||||
|
||||
case ScaleFilterType::_2xSai:
|
||||
twoxsai_generic_xrgb8888(8, 8, originalTile.data(), 8, hdTile.data(), 8 * hdScale);
|
||||
break;
|
||||
|
||||
case ScaleFilterType::Super2xSai:
|
||||
supertwoxsai_generic_xrgb8888(8, 8, originalTile.data(), 8, hdTile.data(), 8 * hdScale);
|
||||
break;
|
||||
|
||||
case ScaleFilterType::SuperEagle:
|
||||
supereagle_generic_xrgb8888(8, 8, originalTile.data(), 8, hdTile.data(), 8 * hdScale);
|
||||
break;
|
||||
|
||||
case ScaleFilterType::xBRZ:
|
||||
xbrz::scale(hdScale, originalTile.data(), hdTile.data(), 8, 8, xbrz::ColorFormat::ARGB);
|
||||
break;
|
||||
}
|
||||
|
||||
tile->HdTileData = hdTile;
|
||||
}
|
||||
|
||||
void HdPackBuilder::DrawTile(HdPackTileInfo *tile, int tileNumber, uint32_t *pngBuffer, int pageNumber, bool containsSpritesOnly)
|
||||
{
|
||||
if(tile->HdTileData.empty()) {
|
||||
GenerateHdTile(tile);
|
||||
tile->UpdateFlags();
|
||||
}
|
||||
|
||||
if(containsSpritesOnly && _options.UseLargeSprites) {
|
||||
int row = tileNumber / 16;
|
||||
int column = tileNumber % 16;
|
||||
|
||||
int newColumn = column / 2 + ((row & 1) ? 8 : 0);
|
||||
int newRow = (row & 0xFE) + ((column & 1) ? 1 : 0);
|
||||
|
||||
tileNumber = newRow * 16 + newColumn;
|
||||
}
|
||||
|
||||
tileNumber += pageNumber * (256 / (0x1000 / _options.ChrRamBankSize));
|
||||
|
||||
int tileDimension = 8 * _hdData.Scale;
|
||||
int x = tileNumber % 16 * tileDimension;
|
||||
int y = tileNumber / 16 * tileDimension;
|
||||
|
||||
tile->X = x;
|
||||
tile->Y = y;
|
||||
|
||||
int pngWidth = 128 * _hdData.Scale;
|
||||
int pngPos = y * pngWidth + x;
|
||||
int tilePos = 0;
|
||||
for(uint8_t i = 0; i < tileDimension; i++) {
|
||||
for(uint8_t j = 0; j < tileDimension; j++) {
|
||||
pngBuffer[pngPos] = tile->HdTileData[tilePos++];
|
||||
pngPos++;
|
||||
}
|
||||
pngPos += pngWidth - tileDimension;
|
||||
}
|
||||
}
|
||||
|
||||
void HdPackBuilder::SaveHdPack()
|
||||
{
|
||||
FolderUtilities::CreateFolder(_saveFolder);
|
||||
|
||||
stringstream pngRows;
|
||||
stringstream tileRows;
|
||||
stringstream ss;
|
||||
int pngIndex = 0;
|
||||
ss << "<ver>" << std::to_string(HdNesPack::CurrentVersion) << std::endl;
|
||||
ss << "<scale>" << _hdData.Scale << std::endl;
|
||||
ss << "<supportedRom>" << _emu->GetRomInfo().RomFile.GetSha1Hash() << std::endl;
|
||||
if(_options.IgnoreOverscan) {
|
||||
OverscanDimensions overscan = _emu->GetSettings()->GetOverscan();
|
||||
ss << "<overscan>" << overscan.Top << "," << overscan.Right << "," << overscan.Bottom << "," << overscan.Left << std::endl;
|
||||
}
|
||||
|
||||
int tileDimension = 8 * _hdData.Scale;
|
||||
int pngDimension = 16 * tileDimension;
|
||||
int pngBufferSize = pngDimension * pngDimension;
|
||||
uint32_t* pngBuffer = new uint32_t[pngBufferSize];
|
||||
|
||||
int maxPageNumber = 0x1000 / _options.ChrRamBankSize;
|
||||
int pageNumber = 0;
|
||||
bool pngEmpty = true;
|
||||
int pngNumber = 0;
|
||||
|
||||
for(int i = 0; i < pngBufferSize; i++) {
|
||||
pngBuffer[i] = 0xFFFF00FF;
|
||||
}
|
||||
|
||||
auto savePng = [&tileRows, &pngRows, &ss, &pngBuffer, &pngDimension, &pngIndex, &pngBufferSize, &pngEmpty, &pngNumber, this](uint32_t chrBankId) {
|
||||
if(!pngEmpty) {
|
||||
string pngName;
|
||||
if(_isChrRam) {
|
||||
pngName = "Chr_" + std::to_string(pngNumber) + ".png";
|
||||
} else {
|
||||
pngName = "Chr_" + HexUtilities::ToHex(chrBankId) + "_" + std::to_string(pngNumber) + ".png";
|
||||
}
|
||||
|
||||
tileRows << std::endl << "#" << pngName << std::endl;
|
||||
tileRows << pngRows.str();
|
||||
pngRows = stringstream();
|
||||
|
||||
ss << "<img>" << pngName << std::endl;
|
||||
PNGHelper::WritePNG(FolderUtilities::CombinePath(_saveFolder, pngName), pngBuffer, pngDimension, pngDimension, 32);
|
||||
pngNumber++;
|
||||
pngIndex++;
|
||||
|
||||
for(int i = 0; i < pngBufferSize; i++) {
|
||||
pngBuffer[i] = 0xFFFF00FF;
|
||||
}
|
||||
pngEmpty = true;
|
||||
}
|
||||
};
|
||||
|
||||
for(std::pair<const uint32_t, std::map<uint32_t, vector<HdPackTileInfo*>>> &kvp : _tilesByChrBankByPalette) {
|
||||
if(_options.SortByUsageFrequency) {
|
||||
for(int i = 0; i < 256; i++) {
|
||||
vector<std::pair<uint32_t, HdPackTileInfo*>> tiles;
|
||||
for(std::pair<const uint32_t, vector<HdPackTileInfo*>> &paletteMap : kvp.second) {
|
||||
if(paletteMap.second[i]) {
|
||||
tiles.push_back({ _tileUsageCount[paletteMap.second[i]->GetKey(false)], paletteMap.second[i] });
|
||||
}
|
||||
}
|
||||
std::sort(tiles.begin(), tiles.end(), [=](std::pair<uint32_t, HdPackTileInfo*> &a, std::pair<uint32_t, HdPackTileInfo*> &b) {
|
||||
return a.first > b.first;
|
||||
});
|
||||
|
||||
size_t j = 0;
|
||||
for(std::pair<const uint32_t, vector<HdPackTileInfo*>> &paletteMap : kvp.second) {
|
||||
if(j < tiles.size()) {
|
||||
paletteMap.second[i] = tiles[j].second;
|
||||
j++;
|
||||
} else {
|
||||
paletteMap.second[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!_isChrRam) {
|
||||
pngNumber = 0;
|
||||
}
|
||||
|
||||
for(std::pair<const uint32_t, vector<HdPackTileInfo*>> &tileKvp : kvp.second) {
|
||||
bool pageEmpty = true;
|
||||
bool spritesOnly = true;
|
||||
for(HdPackTileInfo* tileInfo : tileKvp.second) {
|
||||
if(tileInfo && !tileInfo->IsSpriteTile()) {
|
||||
spritesOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < 256; i++) {
|
||||
HdPackTileInfo* tileInfo = tileKvp.second[i];
|
||||
if(tileInfo) {
|
||||
DrawTile(tileInfo, i, pngBuffer, pageNumber, spritesOnly);
|
||||
|
||||
pngRows << tileInfo->ToString(pngIndex) << std::endl;
|
||||
|
||||
pageEmpty = false;
|
||||
pngEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!pageEmpty) {
|
||||
pageNumber++;
|
||||
|
||||
if(pageNumber == maxPageNumber) {
|
||||
savePng(kvp.first);
|
||||
pageNumber = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
savePng(-1);
|
||||
|
||||
for(unique_ptr<HdPackCondition> &condition : _hdData.Conditions) {
|
||||
if(!condition->IsExcludedFromFile()) {
|
||||
ss << condition->ToString() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
for(HdBackgroundInfo &bgInfo : _hdData.Backgrounds) {
|
||||
ss << bgInfo.ToString() << std::endl;
|
||||
}
|
||||
|
||||
for(auto &bgmInfo : _hdData.BgmFilesById) {
|
||||
ss << "<bgm>" << std::to_string(bgmInfo.first >> 8) << "," << std::to_string(bgmInfo.first & 0xFF) << "," << VirtualFile(bgmInfo.second).GetFileName() << std::endl;
|
||||
}
|
||||
|
||||
for(auto &sfxInfo : _hdData.SfxFilesById) {
|
||||
ss << "<sfx>" << std::to_string(sfxInfo.first >> 8) << "," << std::to_string(sfxInfo.first & 0xFF) << "," << VirtualFile(sfxInfo.second).GetFileName() << std::endl;
|
||||
}
|
||||
|
||||
for(auto &patchInfo : _hdData.PatchesByHash) {
|
||||
ss << "<patch>" << VirtualFile(patchInfo.second).GetFileName() << "," << patchInfo.first << std::endl;
|
||||
}
|
||||
|
||||
if(_hdData.OptionFlags != 0) {
|
||||
ss << "<options>";
|
||||
if(_hdData.OptionFlags & (int)HdPackOptions::NoSpriteLimit) {
|
||||
ss << "disableSpriteLimit,";
|
||||
}
|
||||
if(_hdData.OptionFlags & (int)HdPackOptions::AlternateRegisterRange) {
|
||||
ss << "alternateRegisterRange,";
|
||||
}
|
||||
if(_hdData.OptionFlags & (int)HdPackOptions::DisableCache) {
|
||||
ss << "disableCache,";
|
||||
}
|
||||
}
|
||||
|
||||
ss << tileRows.str();
|
||||
|
||||
ofstream hiresFile(FolderUtilities::CombinePath(_saveFolder, "hires.txt"), ios::out);
|
||||
hiresFile << ss.str();
|
||||
hiresFile.close();
|
||||
|
||||
delete[] pngBuffer;
|
||||
}
|
||||
/*
|
||||
void HdPackBuilder::GetChrBankList(uint32_t *banks)
|
||||
{
|
||||
ConsolePauseHelper helper(_instance->_console.get());
|
||||
for(std::pair<const uint32_t, std::map<uint32_t, vector<HdPackTileInfo*>>> &kvp : _instance->_tilesByChrBankByPalette) {
|
||||
*banks = kvp.first;
|
||||
banks++;
|
||||
}
|
||||
*banks = -1;
|
||||
}
|
||||
|
||||
void HdPackBuilder::GetBankPreview(uint32_t bankNumber, uint32_t pageNumber, uint32_t *rgbBuffer)
|
||||
{
|
||||
ConsolePauseHelper helper(_instance->_console.get());
|
||||
|
||||
for(uint32_t i = 0; i < 128 * 128 * _instance->_hdData.Scale*_instance->_hdData.Scale; i++) {
|
||||
rgbBuffer[i] = 0xFF666666;
|
||||
}
|
||||
|
||||
auto result = _instance->_tilesByChrBankByPalette.find(bankNumber);
|
||||
if(result != _instance->_tilesByChrBankByPalette.end()) {
|
||||
std::map<uint32_t, vector<HdPackTileInfo*>> bankData = result->second;
|
||||
|
||||
if(_instance->_flags & HdPackRecordFlags::SortByUsageFrequency) {
|
||||
for(int i = 0; i < 256; i++) {
|
||||
vector<std::pair<uint32_t, HdPackTileInfo*>> tiles;
|
||||
for(std::pair<const uint32_t, vector<HdPackTileInfo*>> &pageData : bankData) {
|
||||
if(pageData.second[i]) {
|
||||
tiles.push_back({ _instance->_tileUsageCount[pageData.second[i]->GetKey(false)], pageData.second[i] });
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(tiles.begin(), tiles.end(), [=](std::pair<uint32_t, HdPackTileInfo*> &a, std::pair<uint32_t, HdPackTileInfo*> &b) {
|
||||
return a.first > b.first;
|
||||
});
|
||||
|
||||
size_t j = 0;
|
||||
for(std::pair<const uint32_t, vector<HdPackTileInfo*>> &pageData : bankData) {
|
||||
if(j < tiles.size()) {
|
||||
pageData.second[i] = tiles[j].second;
|
||||
j++;
|
||||
} else {
|
||||
pageData.second[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool spritesOnly = true;
|
||||
for(HdPackTileInfo* tileInfo : (*bankData.begin()).second) {
|
||||
if(tileInfo && !tileInfo->IsSpriteTile()) {
|
||||
spritesOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < 256; i++) {
|
||||
HdPackTileInfo* tileInfo = (*bankData.begin()).second[i];
|
||||
if(tileInfo) {
|
||||
_instance->DrawTile(tileInfo, i, (uint32_t*)rgbBuffer, 0, spritesOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
56
Core/NES/HdPacks/HdPackBuilder.h
Normal file
56
Core/NES/HdPacks/HdPackBuilder.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "NES/HdPacks/HdData.h"
|
||||
#include "NES/NesTypes.h"
|
||||
#include "Shared/SettingTypes.h"
|
||||
#include <map>
|
||||
|
||||
class Emulator;
|
||||
class BaseMapper;
|
||||
|
||||
struct HdPackBuilderOptions
|
||||
{
|
||||
char* SaveFolder;
|
||||
ScaleFilterType FilterType;
|
||||
uint32_t Scale;
|
||||
uint32_t ChrRamBankSize;
|
||||
|
||||
bool UseLargeSprites;
|
||||
bool SortByUsageFrequency;
|
||||
bool GroupBlankTiles;
|
||||
bool IgnoreOverscan;
|
||||
};
|
||||
|
||||
class HdPackBuilder
|
||||
{
|
||||
private:
|
||||
Emulator* _emu = nullptr;
|
||||
|
||||
HdPackData _hdData;
|
||||
unordered_map<HdTileKey, uint32_t> _tileUsageCount;
|
||||
unordered_map<HdTileKey, HdPackTileInfo*> _tilesByKey;
|
||||
std::map<uint32_t, std::map<uint32_t, vector<HdPackTileInfo*>>> _tilesByChrBankByPalette;
|
||||
bool _isChrRam = false;
|
||||
string _saveFolder;
|
||||
string _romName;
|
||||
HdPackBuilderOptions _options = {};
|
||||
uint32_t _palette[512] = {};
|
||||
|
||||
//Used to group blank tiles together
|
||||
uint32_t _blankTileIndex = 0;
|
||||
int _blankTilePalette = 0;
|
||||
|
||||
void AddTile(HdPackTileInfo *tile, uint32_t usageCount);
|
||||
void GenerateHdTile(HdPackTileInfo *tile);
|
||||
void DrawTile(HdPackTileInfo *tile, int tileIndex, uint32_t* pngBuffer, int pageNumber, bool containsSpritesOnly);
|
||||
|
||||
public:
|
||||
HdPackBuilder(Emulator* emu, PpuModel ppuModel, bool isChrRam, HdPackBuilderOptions options);
|
||||
~HdPackBuilder();
|
||||
|
||||
void ProcessTile(uint32_t x, uint32_t y, uint16_t tileAddr, HdPpuTileInfo& tile, BaseMapper* mapper, bool isSprite, uint32_t chrBankHash, bool transparencyRequired);
|
||||
void SaveHdPack();
|
||||
|
||||
//static void GetChrBankList(uint32_t *banks);
|
||||
//static void GetBankPreview(uint32_t bankNumber, uint32_t pageNumber, uint32_t *rgbBuffer);
|
||||
};
|
|
@ -11,7 +11,6 @@ class MMC3 : public BaseMapper
|
|||
{
|
||||
private:
|
||||
NesCpu* _cpu = nullptr;
|
||||
BaseNesPpu* _ppu = nullptr;
|
||||
|
||||
uint8_t _currentRegister = 0;
|
||||
|
||||
|
@ -197,7 +196,6 @@ protected:
|
|||
void InitMapper() override
|
||||
{
|
||||
_cpu = _console->GetCpu();
|
||||
_ppu = _console->GetPpu();
|
||||
|
||||
//Force MMC3A irqs for boards that are known to use the A revision.
|
||||
//Some MMC3B boards also have the A behavior, but currently no way to tell them apart.
|
||||
|
@ -305,7 +303,7 @@ protected:
|
|||
public:
|
||||
void NotifyVramAddressChange(uint16_t addr) override
|
||||
{
|
||||
if(_a12Watcher.UpdateVramAddress(addr, _ppu->GetFrameCycle()) == A12StateChange::Rise) {
|
||||
if(_a12Watcher.UpdateVramAddress(addr, _console->GetPpu()->GetFrameCycle()) == A12StateChange::Rise) {
|
||||
uint32_t count = _irqCounter;
|
||||
if(_irqCounter == 0 || _irqReload) {
|
||||
_irqCounter = _irqReloadValue;
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "NES/HdPacks/HdData.h"
|
||||
#include "NES/HdPacks/HdNesPpu.h"
|
||||
#include "NES/HdPacks/HdPackLoader.h"
|
||||
#include "NES/HdPacks/HdPackBuilder.h"
|
||||
#include "NES/HdPacks/HdBuilderPpu.h"
|
||||
#include "NES/HdPacks/HdVideoFilter.h"
|
||||
#include "NES/NesDefaultVideoFilter.h"
|
||||
#include "NES/NesNtscFilter.h"
|
||||
|
@ -22,6 +24,8 @@
|
|||
#include "NES/Mappers/NSF/NsfMapper.h"
|
||||
#include "NES/Mappers/FDS/Fds.h"
|
||||
#include "Shared/Emulator.h"
|
||||
#include "Shared/Audio/SoundMixer.h"
|
||||
#include "Shared/SaveStateManager.h"
|
||||
#include "Shared/CheatManager.h"
|
||||
#include "Shared/Movies/MovieManager.h"
|
||||
#include "Shared/BaseControlManager.h"
|
||||
|
@ -400,7 +404,7 @@ ShortcutState NesConsole::IsShortcutAllowed(EmulatorShortcut shortcut, uint32_t
|
|||
|
||||
BaseVideoFilter* NesConsole::GetVideoFilter()
|
||||
{
|
||||
if(_hdData) {
|
||||
if(_hdData && !_hdPackBuilder) {
|
||||
return new HdVideoFilter(_emu, _hdData.get());
|
||||
} else if(GetRomFormat() == RomFormat::Nsf) {
|
||||
return new NesDefaultVideoFilter(_emu);
|
||||
|
@ -610,4 +614,59 @@ DipSwitchInfo NesConsole::GetDipSwitchInfo()
|
|||
info.DipSwitchCount = _mapper->GetMapperDipSwitchCount();
|
||||
info.DatabaseId = _mapper->GetRomInfo().Hash.PrgCrc32;
|
||||
return info;
|
||||
}
|
||||
|
||||
void NesConsole::ProcessNotification(ConsoleNotificationType type, void* parameter)
|
||||
{
|
||||
if(type == ConsoleNotificationType::ExecuteShortcut) {
|
||||
ExecuteShortcutParams* params = (ExecuteShortcutParams*)parameter;
|
||||
switch(params->Shortcut) {
|
||||
default: break;
|
||||
case EmulatorShortcut::StartRecordHdPack: StartRecordingHdPack(*(HdPackBuilderOptions*)params->ParamPtr); break;
|
||||
case EmulatorShortcut::StopRecordHdPack: StopRecordingHdPack(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NesConsole::StartRecordingHdPack(HdPackBuilderOptions options)
|
||||
{
|
||||
auto lock = _emu->AcquireLock();
|
||||
|
||||
std::stringstream saveState;
|
||||
_emu->Serialize(saveState, false, 0);
|
||||
|
||||
_hdPackBuilder.reset();
|
||||
_hdPackBuilder.reset(new HdPackBuilder(_emu, _ppu->GetPpuModel(), !_mapper->HasChrRom(), options));
|
||||
|
||||
_memoryManager->UnregisterIODevice(_ppu.get());
|
||||
_ppu.reset(new HdBuilderPpu(this, _hdPackBuilder.get(), options.ChrRamBankSize));
|
||||
_memoryManager->RegisterIODevice(_ppu.get());
|
||||
|
||||
_emu->Deserialize(saveState, SaveStateManager::FileFormatVersion, false);
|
||||
_emu->GetSoundMixer()->StopAudio();
|
||||
|
||||
_emu->GetVideoDecoder()->ForceFilterUpdate();
|
||||
}
|
||||
|
||||
void NesConsole::StopRecordingHdPack()
|
||||
{
|
||||
if(_hdPackBuilder) {
|
||||
auto lock = _emu->AcquireLock();
|
||||
|
||||
std::stringstream saveState;
|
||||
_emu->Serialize(saveState, false, 0);
|
||||
|
||||
_memoryManager->UnregisterIODevice(_ppu.get());
|
||||
if(_hdData && (!_hdData->Tiles.empty() || !_hdData->Backgrounds.empty())) {
|
||||
_ppu.reset(new HdNesPpu(this, _hdData.get()));
|
||||
} else {
|
||||
_ppu.reset(new DefaultNesPpu(this));
|
||||
}
|
||||
_memoryManager->RegisterIODevice(_ppu.get());
|
||||
_hdPackBuilder.reset();
|
||||
|
||||
_emu->Deserialize(saveState, SaveStateManager::FileFormatVersion, false);
|
||||
_emu->GetSoundMixer()->StopAudio();
|
||||
_emu->GetVideoDecoder()->ForceFilterUpdate();
|
||||
}
|
||||
}
|
|
@ -17,7 +17,9 @@ class NesSoundMixer;
|
|||
class BaseVideoFilter;
|
||||
class BaseControlManager;
|
||||
class HdAudioDevice;
|
||||
class HdPackBuilder;
|
||||
struct HdPackData;
|
||||
struct HdPackBuilderOptions;
|
||||
|
||||
enum class DebugEventType;
|
||||
enum class EventType;
|
||||
|
@ -43,6 +45,7 @@ private:
|
|||
|
||||
unique_ptr<HdPackData> _hdData;
|
||||
unique_ptr<HdAudioDevice> _hdAudioDevice;
|
||||
unique_ptr<HdPackBuilder> _hdPackBuilder;
|
||||
|
||||
ConsoleRegion _region = ConsoleRegion::Auto;
|
||||
|
||||
|
@ -53,6 +56,9 @@ private:
|
|||
|
||||
void InitializeInputDevices(GameInputType inputType, GameSystem system);
|
||||
|
||||
void StartRecordingHdPack(HdPackBuilderOptions options);
|
||||
void StopRecordingHdPack();
|
||||
|
||||
public:
|
||||
NesConsole(Emulator* emulator);
|
||||
~NesConsole();
|
||||
|
@ -113,4 +119,6 @@ public:
|
|||
void ProcessCheatCode(InternalCheatCode& code, uint32_t addr, uint8_t& value) override;
|
||||
void InitializeRam(void* data, uint32_t length);
|
||||
DipSwitchInfo GetDipSwitchInfo() override;
|
||||
|
||||
void ProcessNotification(ConsoleNotificationType type, void* parameter) override;
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "NES/DefaultNesPpu.h"
|
||||
#include "NES/NsfPpu.h"
|
||||
#include "NES/HdPacks/HdNesPpu.h"
|
||||
#include "NES/HdPacks/HdBuilderPpu.h"
|
||||
|
||||
#include "Debugger/Debugger.h"
|
||||
#include "Shared/EmuSettings.h"
|
||||
|
@ -1473,3 +1474,8 @@ 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);
|
||||
|
||||
template NesPpu<HdBuilderPpu>::NesPpu(NesConsole* console);
|
||||
template uint16_t* NesPpu<HdBuilderPpu>::GetScreenBuffer(bool previousBuffer);
|
||||
template void NesPpu<HdBuilderPpu>::Exec();
|
||||
template uint32_t NesPpu<HdBuilderPpu>::GetPixelBrightness(uint8_t x, uint8_t y);
|
||||
|
|
|
@ -514,6 +514,7 @@ void Emulator::InitConsole(unique_ptr<IConsole>& newConsole, ConsoleMemoryInfo o
|
|||
}
|
||||
|
||||
_console.reset(newConsole);
|
||||
_notificationManager->RegisterNotificationListener(_console.lock());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "Utilities/ISerializable.h"
|
||||
#include "Core/Debugger/DebugTypes.h"
|
||||
#include "Shared/Audio/AudioPlayerTypes.h"
|
||||
#include "Shared/Interfaces/INotificationListener.h"
|
||||
#include "Shared/RomInfo.h"
|
||||
#include "Shared/TimingInfo.h"
|
||||
|
||||
|
@ -42,7 +43,7 @@ enum class ShortcutState
|
|||
Default = 2
|
||||
};
|
||||
|
||||
class IConsole : public ISerializable
|
||||
class IConsole : public ISerializable, public INotificationListener
|
||||
{
|
||||
public:
|
||||
virtual ~IConsole() {}
|
||||
|
@ -97,5 +98,7 @@ public:
|
|||
virtual void GetConsoleState(BaseState& state, ConsoleType consoleType) = 0;
|
||||
|
||||
virtual void ProcessCheatCode(InternalCheatCode& code, uint32_t addr, uint8_t& value) {}
|
||||
|
||||
virtual void ProcessNotification(ConsoleNotificationType type, void* parameter) {}
|
||||
};
|
||||
|
||||
|
|
|
@ -45,4 +45,5 @@ struct ExecuteShortcutParams
|
|||
{
|
||||
EmulatorShortcut Shortcut;
|
||||
uint32_t Param;
|
||||
void* ParamPtr;
|
||||
};
|
|
@ -781,6 +781,8 @@ enum class EmulatorShortcut
|
|||
VsInsertCoin2,
|
||||
VsInsertCoin3,
|
||||
VsInsertCoin4,
|
||||
StartRecordHdPack,
|
||||
StopRecordHdPack,
|
||||
|
||||
ShortcutCount,
|
||||
};
|
||||
|
|
|
@ -71,12 +71,13 @@ void VideoDecoder::UpdateVideoFilter()
|
|||
VideoFilterType newFilter = _emu->GetSettings()->GetVideoConfig().VideoFilter;
|
||||
ConsoleType consoleType = _emu->GetConsoleType();
|
||||
|
||||
if(_videoFilterType != newFilter || _videoFilter == nullptr || _consoleType != consoleType) {
|
||||
if(_videoFilterType != newFilter || _videoFilter == nullptr || _consoleType != consoleType || _forceFilterUpdate) {
|
||||
_videoFilterType = newFilter;
|
||||
_consoleType = consoleType;
|
||||
|
||||
_videoFilter.reset(_emu->GetVideoFilter());
|
||||
_scaleFilter = ScaleFilter::GetScaleFilter(_videoFilterType);
|
||||
_forceFilterUpdate = false;
|
||||
}
|
||||
|
||||
uint32_t screenRotation = _emu->GetSettings()->GetVideoConfig().ScreenRotation;
|
||||
|
|
|
@ -26,6 +26,7 @@ private:
|
|||
atomic<bool> _frameChanged;
|
||||
atomic<bool> _stopFlag;
|
||||
uint32_t _frameCount = 0;
|
||||
bool _forceFilterUpdate = false;
|
||||
|
||||
double _lastAspectRatio = 0.0;
|
||||
|
||||
|
@ -51,6 +52,8 @@ public:
|
|||
void DecodeFrame(bool synchronous = false);
|
||||
void TakeScreenshot();
|
||||
void TakeScreenshot(std::stringstream &stream);
|
||||
|
||||
void ForceFilterUpdate() { _forceFilterUpdate = true; }
|
||||
|
||||
uint32_t GetFrameCount();
|
||||
FrameInfo GetBaseFrameInfo(bool removeOverscan);
|
||||
|
|
|
@ -244,6 +244,8 @@ extern "C" {
|
|||
return { 256, 240 };
|
||||
}
|
||||
|
||||
DllExport uint32_t __stdcall GetGameMemorySize(MemoryType type) { return _emu->GetMemory(type).Size; }
|
||||
|
||||
DllExport void __stdcall ClearCheats() { _emu->GetCheatManager()->ClearCheats(); }
|
||||
DllExport void __stdcall SetCheats(CheatCode codes[], uint32_t length) { _emu->GetCheatManager()->SetCheats(codes, length); }
|
||||
DllExport bool __stdcall GetConvertedCheat(CheatCode input, InternalCheatCode& output) { return _emu->GetCheatManager()->GetConvertedCheat(input, output); }
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace Mesen.Config
|
|||
[Reactive] public RecentItems RecentFiles { get; set; } = new();
|
||||
[Reactive] public VideoRecordConfig VideoRecord { get; set; } = new();
|
||||
[Reactive] public MovieRecordConfig MovieRecord { get; set; } = new();
|
||||
[Reactive] public HdPackBuilderConfig HdPackBuilder { get; set; } = new();
|
||||
[Reactive] public CheatWindowConfig Cheats { get; set; } = new();
|
||||
[Reactive] public NetplayConfig Netplay { get; set; } = new();
|
||||
[Reactive] public HistoryViewerConfig HistoryViewer { get; set; } = new();
|
||||
|
|
57
NewUI/Config/HdPackBuilderConfig.cs
Normal file
57
NewUI/Config/HdPackBuilderConfig.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using Mesen.Interop;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
|
||||
namespace Mesen.Config
|
||||
{
|
||||
public class HdPackBuilderConfig : BaseConfig<HdPackBuilderConfig>
|
||||
{
|
||||
public ScaleFilterType FilterType { get; set; } = ScaleFilterType.Prescale;
|
||||
public UInt32 Scale { get; set; } = 1;
|
||||
public UInt32 ChrRamBankSize { get; set; } = 0x1000;
|
||||
|
||||
public bool UseLargeSprites { get; set; } = false;
|
||||
public bool SortByUsageFrequency { get; set; } = true;
|
||||
public bool GroupBlankTiles { get; set; } = true;
|
||||
public bool IgnoreOverscan { get; set; } = false;
|
||||
|
||||
public HdPackBuilderOptions ToInterop(string saveFolder)
|
||||
{
|
||||
return new HdPackBuilderOptions() {
|
||||
SaveFolder = saveFolder,
|
||||
FilterType = FilterType,
|
||||
Scale = Scale,
|
||||
ChrRamBankSize = ChrRamBankSize,
|
||||
UseLargeSprites = UseLargeSprites,
|
||||
SortByUsageFrequency = SortByUsageFrequency,
|
||||
GroupBlankTiles = GroupBlankTiles,
|
||||
IgnoreOverscan = IgnoreOverscan,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ScaleFilterType
|
||||
{
|
||||
xBRZ = 0,
|
||||
HQX = 1,
|
||||
Scale2x = 2,
|
||||
_2xSai = 3,
|
||||
Super2xSai = 4,
|
||||
SuperEagle = 5,
|
||||
Prescale = 6,
|
||||
}
|
||||
|
||||
public struct HdPackBuilderOptions
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] public string SaveFolder;
|
||||
public ScaleFilterType FilterType;
|
||||
public UInt32 Scale;
|
||||
public UInt32 ChrRamBankSize;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)] public bool UseLargeSprites;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool SortByUsageFrequency;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool GroupBlankTiles;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool IgnoreOverscan;
|
||||
}
|
||||
}
|
|
@ -129,6 +129,8 @@ namespace Mesen.Config.Shortcuts
|
|||
VsInsertCoin2,
|
||||
VsInsertCoin3,
|
||||
VsInsertCoin4,
|
||||
StartRecordHdPack,
|
||||
StopRecordHdPack,
|
||||
|
||||
LastValidValue,
|
||||
[Obsolete] LoadRandomGame,
|
||||
|
|
|
@ -79,6 +79,8 @@ namespace Mesen.Interop
|
|||
|
||||
[DllImport(DllPath)] public static extern double GetAspectRatio();
|
||||
[DllImport(DllPath)] public static extern FrameInfo GetBaseScreenSize();
|
||||
[DllImport(DllPath)] public static extern Int32 GetGameMemorySize(MemoryType type);
|
||||
|
||||
[DllImport(DllPath)] public static extern void SetRendererSize(UInt32 width, UInt32 height);
|
||||
|
||||
[DllImport(DllPath)] public static extern void ExecuteShortcut(ExecuteShortcutParams p);
|
||||
|
@ -272,6 +274,7 @@ namespace Mesen.Interop
|
|||
{
|
||||
public EmulatorShortcut Shortcut;
|
||||
public UInt32 Param;
|
||||
public IntPtr ParamPtr;
|
||||
};
|
||||
|
||||
public enum AudioPlayerAction
|
||||
|
@ -349,4 +352,5 @@ namespace Mesen.Interop
|
|||
[MarshalAs(UnmanagedType.I1)] public bool IsRamCode;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool IsAbsoluteAddress;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -703,7 +703,30 @@
|
|||
<Control ID="btnOK">OK</Control>
|
||||
<Control ID="btnCancel">Cancel</Control>
|
||||
</Form>
|
||||
|
||||
<Form ID="HdPackBuilderWindow">
|
||||
<Control ID="wndTitle">HD Pack Builder</Control>
|
||||
|
||||
<Control ID="grpOptions">Recording Options</Control>
|
||||
<Control ID="lblBankSize">CHR Bank Size:</Control>
|
||||
<Control ID="lblScale">Scale/Filter:</Control>
|
||||
<Control ID="chkSortByUsageFrequency">Sort pages by usage frequency</Control>
|
||||
<Control ID="chkUseLargeSprites">Use 8x16 sprite display mode</Control>
|
||||
<Control ID="chkGroupBlankTiles">Group blank tiles</Control>
|
||||
<Control ID="chkIgnoreOverscan">Ignore tiles at the edges of the screen (overscan)</Control>
|
||||
|
||||
<Message ID="lblScaleHelp">Selects the scale and video filter to use when generating the PNG files
for the HD Pack. Use the "Prescale" filters to generate the tiles
at a larger scale without applying any transformation to the pixels.</Message>
|
||||
<Message ID="lblBankSizeHelp">This option is only available for CHR RAM games. CHR RAM games have no
fixed "banks" - they are dynamically created by the game's code.
This option alters the HD Pack Builder's behavior when grouping the tiles into 
the PNG files - a smaller bank size will usually result in less PNG 
files (but depending on the game's code, larger values may produce 
better results).</Message>
|
||||
<Message ID="lblSortByUsageFrequencyHelp">When this option is enabled, the tiles in PNG files are sorted by the
frequency at which they are shown on the screen while recording (more 
common palettes will be grouped together in the first PNG for a specific bank 
number. If this option is unchecked, the PNGs will be sorted by palette - 
each PNG will only contain up to 4 different colors in this case.</Message>
|
||||
<Message ID="lblGroupBlankTilesHelp">This option groups all the blank tiles sequentially into the same PNG
files - this helps reduce the number of PNG files produced by removing 
almost-empty PNG files containing only blank tiles.</Message>
|
||||
<Message ID="lblUseLargeSpritesHelp">When enabled, this option will alter the display order of CHR banks
that contain only sprites to make the sprites easier to edit in the PNG file.</Message>
|
||||
<Message ID="lblIgnoreOverscanHelp">When enabled, this will make the builder ignore any pixels in the overscan area.
This is useful in games that contain glitches on the outer edge of the screen.
Incorrect palette combinations due to these glitches will be ignored and won't be shown in the PNG files.</Message>
|
||||
|
||||
<Control ID="btnStartRecording">Start Recording</Control>
|
||||
<Control ID="btnStopRecording">Stop Recording</Control>
|
||||
<Control ID="btnOpenFolder">Open Save Folder</Control>
|
||||
|
||||
<Control ID="lblSaveTo">Save to:</Control>
|
||||
</Form>
|
||||
<Form ID="InputBarcodeWindow">
|
||||
<Control ID="wndTitle">Input barcode...</Control>
|
||||
<Control ID="lblBarcode">Barcode:</Control>
|
||||
|
|
|
@ -374,6 +374,9 @@
|
|||
<Compile Update="Windows\GameConfigWindow.axaml.cs">
|
||||
<DependentUpon>GameConfigWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Windows\HdPackBuilderWindow.axaml.cs">
|
||||
<DependentUpon>HdPackBuilderWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Windows\UpdatePromptWindow.axaml.cs">
|
||||
<DependentUpon>UpdatePromptWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
|
|
186
NewUI/ViewModels/HdPackBuilderViewModel.cs
Normal file
186
NewUI/ViewModels/HdPackBuilderViewModel.cs
Normal file
|
@ -0,0 +1,186 @@
|
|||
using Avalonia.Threading;
|
||||
using Mesen.Config;
|
||||
using Mesen.Config.Shortcuts;
|
||||
using Mesen.Interop;
|
||||
using Mesen.Localization;
|
||||
using Mesen.Utilities;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Mesen.ViewModels
|
||||
{
|
||||
public class HdPackBuilderViewModel : DisposableViewModel
|
||||
{
|
||||
[Reactive] public string SaveFolder { get; set; }
|
||||
[Reactive] public bool IsRecording { get; set; }
|
||||
[Reactive] public bool IsBankSizeVisible { get; set; }
|
||||
[Reactive] public bool IsOpenFolderEnabled { get; set; }
|
||||
[Reactive] public HdPackBuilderConfig Config { get; set; }
|
||||
[Reactive] public FilterInfo? SelectedFilter { get; set; }
|
||||
[Reactive] public BankSizeInfo SelectedBankSize { get; set; }
|
||||
|
||||
[Reactive] public FilterInfo[] Filters { get; private set; } = Array.Empty<FilterInfo>();
|
||||
|
||||
public BankSizeInfo[] BankSizes { get; } = {
|
||||
new BankSizeInfo() { Name = "1 KB", BankSize = 0x400 },
|
||||
new BankSizeInfo() { Name = "2 KB", BankSize = 0x800 },
|
||||
new BankSizeInfo() { Name = "4 KB", BankSize = 0x1000 },
|
||||
};
|
||||
|
||||
public HdPackBuilderViewModel()
|
||||
{
|
||||
Config = ConfigManager.Config.HdPackBuilder;
|
||||
SaveFolder = Path.Join(ConfigManager.HdPackFolder, EmuApi.GetRomInfo().GetRomName());
|
||||
|
||||
UpdateFilterDropdown();
|
||||
|
||||
SelectedFilter = Filters.Where(x => x.FilterType == Config.FilterType && x.Scale == Config.Scale).FirstOrDefault() ?? Filters[0];
|
||||
SelectedBankSize = BankSizes.Where(x => x.BankSize == Config.ChrRamBankSize).FirstOrDefault() ?? BankSizes[0];
|
||||
IsBankSizeVisible = EmuApi.GetGameMemorySize(MemoryType.NesChrRam) > 0;
|
||||
|
||||
AddDisposable(this.WhenAnyValue(x => x.SelectedFilter).Subscribe(x => {
|
||||
if(x != null) {
|
||||
Config.FilterType = x.FilterType;
|
||||
Config.Scale = x.Scale;
|
||||
}
|
||||
}));
|
||||
|
||||
AddDisposable(this.WhenAnyValue(x => x.SelectedBankSize).Subscribe(x => {
|
||||
Config.ChrRamBankSize = x.BankSize;
|
||||
}));
|
||||
|
||||
AddDisposable(this.WhenAnyValue(x => x.SaveFolder).Subscribe(x => {
|
||||
IsOpenFolderEnabled = File.Exists(SaveFolder);
|
||||
UpdateFilterDropdown();
|
||||
}));
|
||||
}
|
||||
|
||||
private void UpdateFilterDropdown()
|
||||
{
|
||||
string hdDefFile = Path.Combine(SaveFolder, "hires.txt");
|
||||
FilterInfo? selectedFilter = SelectedFilter;
|
||||
if(File.Exists(hdDefFile)) {
|
||||
string fileContent = File.ReadAllText(hdDefFile);
|
||||
Match match = Regex.Match(fileContent, "<scale>(\\d*)");
|
||||
if(match.Success) {
|
||||
int scale;
|
||||
if(Int32.TryParse(match.Groups[1].ToString(), out scale)) {
|
||||
Filters = _allFilters.Where(x => x.Scale == scale).ToArray();
|
||||
SelectedFilter = Filters.Contains(selectedFilter) ? selectedFilter : Filters[0];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Filters = _allFilters;
|
||||
SelectedFilter = selectedFilter;
|
||||
}
|
||||
|
||||
public void StartRecording()
|
||||
{
|
||||
IsRecording = true;
|
||||
|
||||
Task.Run(() => {
|
||||
HdPackBuilderOptions options = Config.ToInterop(SaveFolder);
|
||||
if(!IsBankSizeVisible) {
|
||||
options.ChrRamBankSize = 0x1000;
|
||||
}
|
||||
|
||||
IntPtr optionsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(options));
|
||||
try {
|
||||
Marshal.StructureToPtr(options, optionsPtr, false);
|
||||
EmuApi.ExecuteShortcut(new ExecuteShortcutParams() {
|
||||
Shortcut = EmulatorShortcut.StartRecordHdPack,
|
||||
ParamPtr = optionsPtr
|
||||
});
|
||||
} finally {
|
||||
Marshal.FreeHGlobal(optionsPtr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void StopRecording()
|
||||
{
|
||||
IsRecording = false;
|
||||
|
||||
Task.Run(() => {
|
||||
EmuApi.ExecuteShortcut(new ExecuteShortcutParams() { Shortcut = EmulatorShortcut.StopRecordHdPack });
|
||||
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
IsOpenFolderEnabled = true;
|
||||
UpdateFilterDropdown();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void OpenFolder()
|
||||
{
|
||||
if(Directory.Exists(SaveFolder)) {
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo() {
|
||||
FileName = SaveFolder + Path.DirectorySeparatorChar,
|
||||
UseShellExecute = true,
|
||||
Verb = "open"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class FilterInfo
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public ScaleFilterType FilterType { get; set; }
|
||||
public UInt32 Scale { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
public class BankSizeInfo
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public UInt32 BankSize { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
static private FilterInfo[] _allFilters = {
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.None) + " (1x)", FilterType = ScaleFilterType.Prescale, Scale = 1 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Prescale2x), FilterType = ScaleFilterType.Prescale, Scale = 2 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Prescale3x), FilterType = ScaleFilterType.Prescale, Scale = 3 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Prescale4x), FilterType = ScaleFilterType.Prescale, Scale = 4 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Prescale6x), FilterType = ScaleFilterType.Prescale, Scale = 6 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Prescale8x), FilterType = ScaleFilterType.Prescale, Scale = 8 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Prescale10x), FilterType = ScaleFilterType.Prescale, Scale = 10 },
|
||||
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.HQ2x), FilterType = ScaleFilterType.HQX, Scale = 2 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.HQ3x), FilterType = ScaleFilterType.HQX, Scale = 3 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.HQ4x), FilterType = ScaleFilterType.HQX, Scale = 4 },
|
||||
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Scale2x), FilterType = ScaleFilterType.Scale2x, Scale = 2 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Scale3x), FilterType = ScaleFilterType.Scale2x, Scale = 3 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Scale4x), FilterType = ScaleFilterType.Scale2x, Scale = 4 },
|
||||
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.Super2xSai), FilterType = ScaleFilterType.Super2xSai, Scale = 2 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.SuperEagle), FilterType = ScaleFilterType.SuperEagle, Scale = 2 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType._2xSai), FilterType = ScaleFilterType._2xSai, Scale = 2 },
|
||||
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.xBRZ2x), FilterType = ScaleFilterType.xBRZ, Scale = 2 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.xBRZ3x), FilterType = ScaleFilterType.xBRZ, Scale = 3 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.xBRZ4x), FilterType = ScaleFilterType.xBRZ, Scale = 4 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.xBRZ5x), FilterType = ScaleFilterType.xBRZ, Scale = 5 },
|
||||
new FilterInfo() { Name = ResourceHelper.GetEnumText(VideoFilterType.xBRZ6x), FilterType = ScaleFilterType.xBRZ, Scale = 6 },
|
||||
};
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ using System.IO.Compression;
|
|||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -600,8 +601,9 @@ namespace Mesen.ViewModels
|
|||
},
|
||||
new MainMenuAction() {
|
||||
ActionType = ActionType.HdPackBuilder,
|
||||
IsEnabled = () => false,
|
||||
OnClick = () => { } //TODO
|
||||
OnClick = () => {
|
||||
ApplicationHelper.GetOrCreateUniqueWindow(wnd, () => new HdPackBuilderWindow());
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
62
NewUI/Windows/HdPackBuilderWindow.axaml
Normal file
62
NewUI/Windows/HdPackBuilderWindow.axaml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<Window
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:m="clr-namespace:Mesen"
|
||||
xmlns:c="using:Mesen.Controls"
|
||||
xmlns:i="using:Mesen.Interop"
|
||||
xmlns:cfg="using:Mesen.Config"
|
||||
xmlns:vm="using:Mesen.ViewModels"
|
||||
xmlns:l="using:Mesen.Localization"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="230"
|
||||
x:Class="Mesen.Windows.HdPackBuilderWindow"
|
||||
Width="450" Height="230"
|
||||
CanResize="False"
|
||||
x:DataType="vm:HdPackBuilderViewModel"
|
||||
Title="{l:Translate wndTitle}"
|
||||
Icon="/Assets/HdPack.png"
|
||||
>
|
||||
<DockPanel Margin="5">
|
||||
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto">
|
||||
<c:ButtonWithIcon Icon="Assets/Folder.png" Command="{Binding OpenFolder}" Text="{l:Translate btnOpenFolder}" IsEnabled="{CompiledBinding IsOpenFolderEnabled}" />
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="2">
|
||||
<c:ButtonWithIcon Icon="Assets/Record.png" Command="{Binding StartRecording}" Text="{l:Translate btnStartRecording}" IsVisible="{CompiledBinding !IsRecording}" />
|
||||
<c:ButtonWithIcon Icon="Assets/MediaStop.png" Command="{Binding StopRecording}" Text="{l:Translate btnStopRecording}" IsVisible="{CompiledBinding IsRecording}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,1*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
|
||||
<TextBlock Text="{l:Translate lblSaveTo}" />
|
||||
<c:PathSelector Grid.Column="1" Path="{CompiledBinding SaveFolder}" Editable="True" />
|
||||
|
||||
<c:OptionSection Grid.Row="1" Grid.ColumnSpan="2" Header="{l:Translate grpOptions}">
|
||||
<Grid ColumnDefinitions="Auto,150,Auto" RowDefinitions="Auto,Auto">
|
||||
<TextBlock VerticalAlignment="Center" Text="{l:Translate lblScale}" />
|
||||
<ComboBox Grid.Column="1" HorizontalAlignment="Stretch" Items="{CompiledBinding Filters}" SelectedItem="{CompiledBinding SelectedFilter}" />
|
||||
<Image Grid.Column="2" Stretch="None" Margin="5 0" Source="/Assets/Help.png" ToolTip.Tip="{l:Translate lblScaleHelp}" ToolTip.Placement="Right" ToolTip.ShowDelay="100" />
|
||||
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center" Text="{l:Translate lblBankSize}" IsVisible="{CompiledBinding IsBankSizeVisible}" />
|
||||
<ComboBox Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" Items="{CompiledBinding BankSizes}" SelectedItem="{CompiledBinding SelectedBankSize}" IsVisible="{CompiledBinding IsBankSizeVisible}" />
|
||||
<Image Grid.Column="2" Grid.Row="1" Stretch="None" Margin="5 0" Source="/Assets/Help.png" ToolTip.Tip="{l:Translate lblBankSizeHelp}" ToolTip.Placement="Right" ToolTip.ShowDelay="100" IsVisible="{CompiledBinding IsBankSizeVisible}" />
|
||||
</Grid>
|
||||
<WrapPanel>
|
||||
<CheckBox Content="{l:Translate chkGroupBlankTiles}" IsChecked="{CompiledBinding Config.GroupBlankTiles}" />
|
||||
<Image Stretch="None" Margin="5 0" Source="/Assets/Help.png" ToolTip.Tip="{l:Translate lblGroupBlankTilesHelp}" ToolTip.Placement="Right" ToolTip.ShowDelay="100" />
|
||||
</WrapPanel>
|
||||
<WrapPanel>
|
||||
<CheckBox Content="{l:Translate chkSortByUsageFrequency}" IsChecked="{CompiledBinding Config.SortByUsageFrequency}" />
|
||||
<Image Stretch="None" Margin="5 0" Source="/Assets/Help.png" ToolTip.Tip="{l:Translate lblSortByUsageFrequencyHelp}" ToolTip.Placement="Right" ToolTip.ShowDelay="100" />
|
||||
</WrapPanel>
|
||||
<WrapPanel>
|
||||
<CheckBox Content="{l:Translate chkUseLargeSprites}" IsChecked="{CompiledBinding Config.UseLargeSprites}" />
|
||||
<Image Stretch="None" Margin="5 0" Source="/Assets/Help.png" ToolTip.Tip="{l:Translate lblUseLargeSpritesHelp}" ToolTip.Placement="Right" ToolTip.ShowDelay="100" />
|
||||
</WrapPanel>
|
||||
<WrapPanel>
|
||||
<CheckBox Content="{l:Translate chkIgnoreOverscan}" IsChecked="{CompiledBinding Config.IgnoreOverscan}" />
|
||||
<Image Stretch="None" Margin="5 0" Source="/Assets/Help.png" ToolTip.Tip="{l:Translate lblIgnoreOverscanHelp}" ToolTip.Placement="Right" ToolTip.ShowDelay="100" />
|
||||
</WrapPanel>
|
||||
</c:OptionSection>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Window>
|
39
NewUI/Windows/HdPackBuilderWindow.axaml.cs
Normal file
39
NewUI/Windows/HdPackBuilderWindow.axaml.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Mesen.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Mesen.Windows
|
||||
{
|
||||
public class HdPackBuilderWindow : Window
|
||||
{
|
||||
private HdPackBuilderViewModel _model;
|
||||
|
||||
public HdPackBuilderWindow()
|
||||
{
|
||||
_model = new HdPackBuilderViewModel();
|
||||
DataContext = _model;
|
||||
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
_model.Dispose();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -277,6 +277,10 @@ namespace Mesen.Windows
|
|||
}, TimeSpan.FromMilliseconds(50));
|
||||
});
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
ApplicationHelper.GetExistingWindow<HdPackBuilderWindow>()?.Close();
|
||||
});
|
||||
break;
|
||||
|
||||
case ConsoleNotificationType.DebuggerResumed:
|
||||
|
@ -321,6 +325,12 @@ namespace Mesen.Windows
|
|||
});
|
||||
tcs.Task.Wait();
|
||||
break;
|
||||
|
||||
case ConsoleNotificationType.BeforeGameLoad:
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
ApplicationHelper.GetExistingWindow<HdPackBuilderWindow>()?.Close();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue