quickerNES/source/playbackInstance.hpp

251 lines
9.0 KiB
C++
Raw Normal View History

2024-01-10 18:48:57 +00:00
#pragma once
#include <string>
#include <unistd.h>
#include <SDL.h>
#include <SDL_image.h>
#include <utils.hpp>
2024-01-10 19:11:49 +00:00
#include <hqn/hqn.h>
2024-01-10 18:48:57 +00:00
#include <hqn/hqn_gui_controller.h>
2024-01-10 19:11:49 +00:00
#include "emuInstance.hpp"
2024-01-10 18:48:57 +00:00
#define _INVERSE_FRAME_RATE 16667
2024-01-11 18:44:54 +00:00
struct stepData_t
{
std::string input;
uint8_t* stateData;
2024-01-12 20:13:51 +00:00
hash_t hash;
2024-01-11 18:44:54 +00:00
};
2024-01-10 18:48:57 +00:00
class PlaybackInstance
{
2024-01-11 18:44:54 +00:00
public:
2024-01-10 18:48:57 +00:00
2024-01-11 18:44:54 +00:00
void addStep(const std::string& input)
{
stepData_t step;
step.input = input;
2024-01-11 19:23:32 +00:00
step.stateData = (uint8_t*) malloc(_emu->getStateSize());
2024-01-11 18:44:54 +00:00
_emu->serializeState(step.stateData);
2024-01-12 20:13:51 +00:00
step.hash = _emu->getStateHash();
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-01-11 19:23:32 +00:00
PlaybackInstance(EmuInstance* emu, const std::vector<std::string>& sequence, const std::string& overlayPath = "") : _emu(emu)
2024-01-11 18:44:54 +00:00
{
// Loading Emulator instance HQN
_hqnState.m_emu = _emu->getInternalEmulator();
static uint8_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height];
_hqnState.m_emu->set_pixels(video_buffer, Nes_Emu::image_width+8);
2024-01-10 18:48:57 +00:00
2024-01-11 18:44:54 +00:00
// Building sequence information
2024-01-11 19:23:32 +00:00
for (const auto& input : sequence)
2024-01-11 18:44:54 +00:00
{
// Adding new step
addStep(input);
2024-01-10 18:48:57 +00:00
2024-01-11 18:44:54 +00:00
// Advance state based on the input received
_emu->advanceState(input);
}
2024-01-10 18:48:57 +00:00
2024-01-11 18:44:54 +00:00
// Adding last step with no input
addStep("<End Of Sequence>");
2024-01-10 18:48:57 +00:00
2024-01-11 18:44:54 +00:00
// Loading overlay, if provided
2024-01-10 18:48:57 +00:00
if (overlayPath != "")
{
// Using overlay
_useOverlay = true;
// Loading overlay images
std::string imagePath;
imagePath = _overlayPath + std::string("/base.png");
_overlayBaseSurface = IMG_Load(imagePath.c_str());
if (_overlayBaseSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_a.png");
_overlayButtonASurface = IMG_Load(imagePath.c_str());
if (_overlayButtonASurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_b.png");
_overlayButtonBSurface = IMG_Load(imagePath.c_str());
if (_overlayButtonBSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_select.png");
_overlayButtonSelectSurface = IMG_Load(imagePath.c_str());
if (_overlayButtonSelectSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_start.png");
_overlayButtonStartSurface = IMG_Load(imagePath.c_str());
if (_overlayButtonStartSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_left.png");
_overlayButtonLeftSurface = IMG_Load(imagePath.c_str());
if (_overlayButtonLeftSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_right.png");
_overlayButtonRightSurface = IMG_Load(imagePath.c_str());
if (_overlayButtonRightSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_up.png");
_overlayButtonUpSurface = IMG_Load(imagePath.c_str());
if (_overlayButtonUpSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
imagePath = _overlayPath + std::string("/button_down.png");
_overlayButtonDownSurface = IMG_Load(imagePath.c_str());
if (_overlayButtonDownSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
}
// Opening rendering window
SDL_SetMainReady();
// We can only call SDL_InitSubSystem once
if (!SDL_WasInit(SDL_INIT_VIDEO))
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0)
EXIT_WITH_ERROR("Failed to initialize video: %s", SDL_GetError());
// Creating HQN GUI
_hqnGUI = hqn::GUIController::create(_hqnState);
_hqnGUI->setScale(1);
}
// Function to render frame
2024-01-11 18:44:54 +00:00
void renderFrame(const size_t stepId)
2024-01-10 18:48:57 +00:00
{
2024-01-11 18:44:54 +00:00
// Checking the required step id does not exceed contents of the sequence
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
// Getting step information
const auto& step = _stepSequence[stepId];
2024-01-10 18:48:57 +00:00
// 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-01-11 18:44:54 +00:00
if (step.input.find("A") != std::string::npos) overlayButtonASurface = _overlayButtonASurface;
if (step.input.find("B") != std::string::npos) overlayButtonBSurface = _overlayButtonBSurface;
if (step.input.find("S") != std::string::npos) overlayButtonSelectSurface = _overlayButtonSelectSurface;
if (step.input.find("T") != std::string::npos) overlayButtonStartSurface = _overlayButtonStartSurface;
if (step.input.find("L") != std::string::npos) overlayButtonLeftSurface = _overlayButtonLeftSurface;
if (step.input.find("R") != std::string::npos) overlayButtonRightSurface = _overlayButtonRightSurface;
if (step.input.find("U") != std::string::npos) overlayButtonUpSurface = _overlayButtonUpSurface;
if (step.input.find("D") != std::string::npos) overlayButtonDownSurface = _overlayButtonDownSurface;
2024-01-10 18:48:57 +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->getInternalEmulator()->reset();
// Else we load the previous frame
if (stepId > 0)
{
const auto stateData = getStateData(stepId-1);
_emu->deserializeState(stateData);
_emu->advanceState(getStateInput(stepId-1));
}
2024-01-11 18:44:54 +00:00
// Updating image
int32_t curBlit[BLIT_SIZE];
saveBlit(_emu->getInternalEmulator(), 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-11 18:44:54 +00:00
}
size_t getSequenceLength() const
{
return _stepSequence.size();
}
2024-01-10 18:48:57 +00:00
2024-01-11 18:44:54 +00:00
const std::string getInput(const size_t stepId) const
{
// Checking the required step id does not exceed contents of the sequence
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
// Getting step information
const auto& step = _stepSequence[stepId];
// Returning step input
return step.input;
2024-01-10 18:48:57 +00:00
}
2024-01-11 18:44:54 +00:00
const uint8_t* getStateData(const size_t stepId) const
{
// Checking the required step id does not exceed contents of the sequence
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
// Getting step information
const auto& step = _stepSequence[stepId];
// Returning step input
return step.stateData;
}
2024-01-12 20:13:51 +00:00
const hash_t getStateHash(const size_t stepId) const
{
// Checking the required step id does not exceed contents of the sequence
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
// Getting step information
const auto& step = _stepSequence[stepId];
// Returning step input
return step.hash;
}
const std::string getStateInput(const size_t stepId) const
{
// Checking the required step id does not exceed contents of the sequence
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
// Getting step information
const auto& step = _stepSequence[stepId];
// Returning step input
return step.input;
}
2024-01-11 18:44:54 +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
EmuInstance* const _emu;
// Flag to store whether to use the button overlay
bool _useOverlay = false;
// 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
};