quickerNES/source/playbackInstance.hpp

275 lines
10 KiB
C++
Raw Normal View History

2024-01-10 18:48:57 +00:00
#pragma once
2024-07-28 14:52:05 +00:00
#include "nesInstance.hpp"
2024-01-10 18:48:57 +00:00
#include <SDL.h>
#include <SDL_image.h>
2024-03-06 15:21:04 +00:00
#include <extern/hqn/hqn.h>
#include <extern/hqn/hqn_gui_controller.h>
#include <jaffarCommon/deserializers/contiguous.hpp>
#include <jaffarCommon/hash.hpp>
2024-07-28 14:52:05 +00:00
#include <jaffarCommon/serializers/contiguous.hpp>
#include <string>
#include <unistd.h>
2024-01-10 18:48:57 +00:00
#define _INVERSE_FRAME_RATE 16667
2024-01-11 18:44:54 +00:00
struct stepData_t
{
2024-07-28 15:07:38 +00:00
std::string inputString;
jaffar::input_t decodedInput;
2024-01-20 10:21:34 +00:00
uint8_t *stateData;
2024-03-06 15:21:04 +00:00
jaffarCommon::hash::hash_t hash;
2024-01-11 18:44:54 +00:00
};
2024-01-10 18:48:57 +00:00
class PlaybackInstance
{
static const uint16_t image_width = 256;
static const uint16_t image_height = 240;
2024-01-20 10:21:34 +00:00
public:
2024-07-28 15:07:38 +00:00
void addStep(const std::string &inputString, const jaffar::input_t decodedInput)
2024-01-11 18:44:54 +00:00
{
stepData_t step;
2024-07-28 15:07:38 +00:00
step.inputString = inputString;
step.decodedInput = decodedInput;
2024-02-09 19:43:42 +00:00
step.stateData = (uint8_t *)malloc(_emu->getFullStateSize());
jaffarCommon::serializer::Contiguous serializer(step.stateData);
_emu->serializeState(serializer);
2024-03-06 15:21:04 +00:00
step.hash = jaffarCommon::hash::calculateMetroHash(_emu->getLowMem(), _emu->getLowMemSize());
2024-01-11 18:44:54 +00:00
// Adding the step into the sequence
_stepSequence.push_back(step);
}
2024-01-10 18:48:57 +00:00
2024-01-11 18:44:54 +00:00
// Initializes the playback module instance
2024-03-22 11:50:46 +00:00
PlaybackInstance(NESInstance *emu, const std::string &overlayPath = "") : _emu(emu)
2024-01-11 18:44:54 +00:00
{
2024-01-20 10:21:34 +00:00
// Loading overlay, if provided
if (overlayPath != "")
{
// Using overlay
_useOverlay = true;
// Loading overlay images
std::string imagePath;
imagePath = _overlayPath + std::string("/base.png");
_overlayBaseSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayBaseSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_a.png");
_overlayButtonASurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonASurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_b.png");
_overlayButtonBSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonBSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_select.png");
_overlayButtonSelectSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonSelectSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_start.png");
_overlayButtonStartSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonStartSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_left.png");
_overlayButtonLeftSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonLeftSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_right.png");
_overlayButtonRightSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonRightSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_up.png");
_overlayButtonUpSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonUpSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
imagePath = _overlayPath + std::string("/button_down.png");
_overlayButtonDownSurface = IMG_Load(imagePath.c_str());
2024-03-06 15:21:04 +00:00
if (_overlayButtonDownSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
2024-01-20 10:21:34 +00:00
}
2024-03-22 11:50:46 +00:00
}
void initialize(const std::vector<std::string> &sequence)
{
2024-07-28 15:07:38 +00:00
// Getting input decoder
auto inputParser = _emu->getInputParser();
2024-03-22 11:50:46 +00:00
// Building sequence information
for (const auto &input : sequence)
{
2024-07-28 15:07:38 +00:00
// Getting decoded input
const auto decodedInput = inputParser->parseInputString(input);
2024-03-22 11:50:46 +00:00
// Adding new step
2024-07-28 15:07:38 +00:00
addStep(input, decodedInput);
2024-03-22 11:50:46 +00:00
// Advance state based on the input received
2024-07-28 15:07:38 +00:00
_emu->advanceState(decodedInput);
2024-03-22 11:50:46 +00:00
}
// Adding last step with no input
2024-07-28 15:07:38 +00:00
addStep("<End Of Sequence>", jaffar::input_t());
2024-03-22 11:50:46 +00:00
}
2024-07-28 14:52:05 +00:00
void enableRendering(SDL_Window *window)
2024-03-22 11:50:46 +00:00
{
// Allocating video buffer
_video_buffer = (uint8_t *)malloc(image_width * image_height);
// Setting video buffer
2024-07-28 14:52:05 +00:00
((emulator_t *)_emu->getInternalEmulatorPointer())->set_pixels(_video_buffer, image_width + 8);
2024-01-20 10:21:34 +00:00
2024-03-22 11:50:46 +00:00
// Loading Emulator instance HQN
_hqnState.setEmulatorPointer(_emu->getInternalEmulatorPointer());
static uint8_t video_buffer[image_width * image_height];
_hqnState.m_emu->set_pixels(video_buffer, image_width + 8);
2024-01-20 10:21:34 +00:00
2024-03-22 11:50:46 +00:00
// Enabling emulation rendering
_emu->enableRendering();
2024-01-20 10:21:34 +00:00
// Creating HQN GUI
2024-03-22 11:50:46 +00:00
_hqnGUI = hqn::GUIController::create(_hqnState, window);
2024-01-20 10:21:34 +00:00
_hqnGUI->setScale(1);
2024-01-11 18:44:54 +00:00
}
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
// Function to render frame
void renderFrame(const size_t stepId)
2024-01-10 18:48:57 +00:00
{
2024-01-20 10:21:34 +00:00
// Checking the required step id does not exceed contents of the sequence
2024-03-06 15:21:04 +00:00
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
2024-01-20 10:21:34 +00:00
// Getting step information
const auto &step = _stepSequence[stepId];
// Pointer to overlay images (NULL if unused)
SDL_Surface *overlayButtonASurface = NULL;
SDL_Surface *overlayButtonBSurface = NULL;
SDL_Surface *overlayButtonSelectSurface = NULL;
SDL_Surface *overlayButtonStartSurface = NULL;
SDL_Surface *overlayButtonLeftSurface = NULL;
SDL_Surface *overlayButtonRightSurface = NULL;
SDL_Surface *overlayButtonUpSurface = NULL;
SDL_Surface *overlayButtonDownSurface = NULL;
// Load correct overlay images, if using overlay
if (_useOverlay == true)
{
2024-07-28 15:07:38 +00:00
if (step.inputString.find("A") != std::string::npos) overlayButtonASurface = _overlayButtonASurface;
if (step.inputString.find("B") != std::string::npos) overlayButtonBSurface = _overlayButtonBSurface;
if (step.inputString.find("S") != std::string::npos) overlayButtonSelectSurface = _overlayButtonSelectSurface;
if (step.inputString.find("T") != std::string::npos) overlayButtonStartSurface = _overlayButtonStartSurface;
if (step.inputString.find("L") != std::string::npos) overlayButtonLeftSurface = _overlayButtonLeftSurface;
if (step.inputString.find("R") != std::string::npos) overlayButtonRightSurface = _overlayButtonRightSurface;
if (step.inputString.find("U") != std::string::npos) overlayButtonUpSurface = _overlayButtonUpSurface;
if (step.inputString.find("D") != std::string::npos) overlayButtonDownSurface = _overlayButtonDownSurface;
2024-01-20 10:21:34 +00:00
}
// Since we do not store the blit information (too much memory), we need to load the previous frame and re-run the input
// If its the first step, then simply reset
if (stepId == 0) _emu->doHardReset();
// Else we load the previous frame
if (stepId > 0)
{
const auto stateData = getStateData(stepId - 1);
2024-02-09 19:43:42 +00:00
jaffarCommon::deserializer::Contiguous deserializer(stateData);
_emu->deserializeState(deserializer);
2024-07-28 15:07:38 +00:00
_emu->advanceState(getDecodedInput(stepId - 1));
2024-01-20 10:21:34 +00:00
}
// Updating image
int32_t curBlit[BLIT_SIZE];
saveBlit(_emu->getInternalEmulatorPointer(), curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
_hqnGUI->update_blit(curBlit, _overlayBaseSurface, overlayButtonASurface, overlayButtonBSurface, overlayButtonSelectSurface, overlayButtonStartSurface, overlayButtonLeftSurface, overlayButtonRightSurface, overlayButtonUpSurface, overlayButtonDownSurface);
}
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
size_t getSequenceLength() const
{
return _stepSequence.size();
}
2024-01-10 18:48:57 +00:00
2024-07-28 15:07:38 +00:00
const std::string getInputString(const size_t stepId) const
2024-01-20 10:21:34 +00:00
{
// Checking the required step id does not exceed contents of the sequence
2024-03-06 15:21:04 +00:00
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
// Getting step information
const auto &step = _stepSequence[stepId];
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
// Returning step input
2024-07-28 15:07:38 +00:00
return step.inputString;
2024-01-20 10:21:34 +00:00
}
2024-01-10 18:48:57 +00:00
2024-07-28 15:07:38 +00:00
const jaffar::input_t getDecodedInput(const size_t stepId) const
2024-01-20 10:21:34 +00:00
{
// Checking the required step id does not exceed contents of the sequence
2024-03-06 15:21:04 +00:00
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
// Getting step information
const auto &step = _stepSequence[stepId];
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
// Returning step input
2024-07-28 15:07:38 +00:00
return step.decodedInput;
2024-01-20 10:21:34 +00:00
}
2024-01-10 18:48:57 +00:00
2024-07-28 15:07:38 +00:00
const uint8_t *getStateData(const size_t stepId) const
2024-01-20 10:21:34 +00:00
{
// Checking the required step id does not exceed contents of the sequence
2024-03-06 15:21:04 +00:00
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
// Getting step information
const auto &step = _stepSequence[stepId];
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
// Returning step input
2024-07-28 15:07:38 +00:00
return step.stateData;
2024-01-10 18:48:57 +00:00
}
2024-07-28 15:07:38 +00:00
const jaffarCommon::hash::hash_t getStateHash(const size_t stepId) const
2024-01-10 18:48:57 +00:00
{
2024-01-20 10:21:34 +00:00
// Checking the required step id does not exceed contents of the sequence
2024-03-06 15:21:04 +00:00
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
2024-01-20 10:21:34 +00:00
// Getting step information
const auto &step = _stepSequence[stepId];
2024-01-20 10:21:34 +00:00
// Returning step input
2024-07-28 15:07:38 +00:00
return step.hash;
}
2024-01-20 10:21:34 +00:00
2024-07-28 15:07:38 +00:00
2024-01-20 10:21:34 +00:00
private:
// Internal sequence information
std::vector<stepData_t> _stepSequence;
// Storage for the HQN state
hqn::HQNState _hqnState;
// Storage for the HQN GUI controller
hqn::GUIController *_hqnGUI;
// Pointer to the contained emulator instance
NESInstance *const _emu;
2024-01-20 10:21:34 +00:00
// Flag to store whether to use the button overlay
bool _useOverlay = false;
2024-01-28 18:57:24 +00:00
// Video buffer
uint8_t *_video_buffer;
2024-01-20 10:21:34 +00:00
// Overlay info
std::string _overlayPath;
SDL_Surface *_overlayBaseSurface = NULL;
SDL_Surface *_overlayButtonASurface;
SDL_Surface *_overlayButtonBSurface;
SDL_Surface *_overlayButtonSelectSurface;
SDL_Surface *_overlayButtonStartSurface;
SDL_Surface *_overlayButtonLeftSurface;
SDL_Surface *_overlayButtonRightSurface;
SDL_Surface *_overlayButtonUpSurface;
SDL_Surface *_overlayButtonDownSurface;
2024-01-10 18:48:57 +00:00
};