quickerNES/source/playbackInstance.hpp

267 lines
9.9 KiB
C++
Raw Normal View History

2024-01-10 18:48:57 +00:00
#pragma once
2024-02-08 18:19:39 +00:00
#include <string>
#include <unistd.h>
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/serializers/contiguous.hpp>
#include <jaffarCommon/deserializers/contiguous.hpp>
#include <jaffarCommon/hash.hpp>
2024-02-08 18:19:39 +00:00
#include "nesInstance.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;
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:
void addStep(const std::string &input)
2024-01-11 18:44:54 +00:00
{
stepData_t step;
step.input = input;
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)
{
// Building sequence information
for (const auto &input : sequence)
{
// Adding new step
addStep(input);
// Advance state based on the input received
_emu->advanceState(input);
}
// Adding last step with no input
addStep("<End Of Sequence>");
}
void enableRendering(SDL_Window* window)
{
// Allocating video buffer
_video_buffer = (uint8_t *)malloc(image_width * image_height);
// Setting video buffer
((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)
{
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;
}
// 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-01-20 10:21:34 +00:00
_emu->advanceState(getStateInput(stepId - 1));
}
// 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-01-20 10:21:34 +00:00
const std::string getInput(const size_t stepId) const
{
// 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
return step.input;
}
2024-01-10 18:48:57 +00:00
2024-01-20 10:21:34 +00:00
const uint8_t *getStateData(const size_t stepId) const
{
// 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
return step.stateData;
}
2024-01-10 18:48:57 +00:00
2024-03-06 15:21:04 +00:00
const jaffarCommon::hash::hash_t getStateHash(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
return step.hash;
2024-01-10 18:48:57 +00:00
}
2024-01-20 10:21:34 +00:00
const std::string getStateInput(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
return step.input;
}
2024-01-20 10:21:34 +00:00
private:
2024-01-28 18:57:24 +00:00
2024-01-20 10:21:34 +00:00
// 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
};