Files

495 lines
12 KiB
C++

#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <fstream>
#include <iostream>
using namespace std;
#include "sdl2basehost.h"
#include "../../../source/hostVmShared.h"
#include "../../../source/nibblehelpers.h"
#include "../../../source/logger.h"
#include "../../../source/filehelpers.h"
// sdl
#include <SDL2/SDL.h>
#define SAMPLERATE 22050
#define SAMPLESPERBUF (SAMPLERATE / 30)
#define NUM_BUFFERS 2
int _windowWidth = 128;
int _windowHeight = 128;
int _screenWidth = 128;
int _screenHeight = 128;
int _maxNoStretchWidth = 128;
int _maxNoStretchHeight = 128;
const int PicoScreenWidth = 128;
const int PicoScreenHeight = 128;
uint32_t _windowFlags;
uint32_t _rendererFlags;
uint32_t _pixelFormat;
uint32_t last_time;
uint32_t now_time;
uint32_t frame_time;
uint32_t targetFrameTimeMs;
uint8_t currKDown;
uint8_t currKHeld;
bool stretchKeyPressed = false;
Audio* _audio;
SDL_Window* window;
SDL_Renderer *renderer;
SDL_Texture *texture = NULL;
SDL_AudioSpec want, have;
SDL_AudioDeviceID dev;
void *pixels;
uint8_t *base;
int pitch;
SDL_Rect DestR;
SDL_Rect SrcR;
double textureAngle;
SDL_RendererFlip flip;
int joystickCount = 0;
bool audioInitialized = false;
void postFlipFunction(){
// We're done rendering, so we end the frame here.
SDL_UnlockTexture(texture);
SDL_RenderCopyEx(renderer, texture, &SrcR, &DestR, textureAngle, NULL, flip);
SDL_RenderPresent(renderer);
}
void audioCleanup(){
audioInitialized = false;
SDL_CloseAudioDevice(dev);
}
void FillAudioDeviceBuffer(void* UserData, Uint8* DeviceBuffer, int Length)
{
_audio->FillAudioBuffer(DeviceBuffer, 0, Length / 4);
}
void audioSetup(){
//modifed from SDL docs: https://wiki.libsdl.org/SDL_OpenAudioDevice
SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */
want.freq = SAMPLERATE;
want.format = AUDIO_S16;
want.channels = 2;
want.samples = 512; // Lower buffer size for reduced audio latency (~23ms vs ~186ms)
want.callback = FillAudioDeviceBuffer;
dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
if (dev == 0) {
Logger_Write("Failed to open audio: %s", SDL_GetError());
} else {
if (have.format != want.format) { /* we let this one thing change. */
Logger_Write("We didn't get requested audio format.");
}
SDL_PauseAudioDevice(dev, 0); /* start audio playing. */
audioInitialized = true;
}
}
void _changeStretch(StretchOption newStretch){
if (newStretch == PixelPerfect) {
_screenWidth = PicoScreenWidth;
_screenHeight = PicoScreenHeight;
}
else if (newStretch == StretchToFit) {
int fitDim = std::min(_windowWidth, _windowHeight);
_screenWidth = fitDim;
_screenHeight = fitDim;
}
else if (newStretch == StretchToFill){
_screenWidth = _windowWidth;
_screenHeight = _windowHeight;
}
else if (newStretch == PixelPerfectStretch) {
int factor = std::min(_windowWidth / PicoScreenWidth, _windowHeight / PicoScreenHeight);
if (factor < 1) factor = 1;
_screenWidth = factor * PicoScreenWidth;
_screenHeight = factor * PicoScreenHeight;
}
else if (newStretch == FourByThreeVertPerfect) {
_screenWidth = _maxNoStretchHeight * 4 / 3;
_screenHeight = _maxNoStretchHeight;
}
else if (newStretch == FourByThreeStretch) {
_screenWidth = _windowHeight * 4 / 3;
_screenHeight = _windowHeight;
}
DestR.x = (_windowWidth - _screenWidth) / 2;
DestR.y = (_windowHeight - _screenHeight) / 2;
DestR.w = _screenWidth;
DestR.h = _screenHeight;
SrcR.x = 0;
SrcR.y = 0;
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 0;
flip = SDL_FLIP_NONE;
}
void Host::setPlatformParams(
int windowWidth,
int windowHeight,
uint32_t sdlWindowFlags,
uint32_t sdlRendererFlags,
uint32_t sdlPixelFormat,
std::string logFilePrefix,
std::string customBiosLua,
std::string cartDirectory)
{
_windowWidth = windowWidth;
_windowHeight = windowHeight;
//assume wide screen, height is limiting factor
int maxNoStretchFactor = _windowHeight / PicoScreenHeight;
_maxNoStretchWidth = maxNoStretchFactor * PicoScreenWidth;
_maxNoStretchHeight = maxNoStretchFactor * PicoScreenHeight;
_screenWidth = _maxNoStretchWidth;
_screenHeight = _maxNoStretchHeight;
_windowFlags = sdlWindowFlags;
_rendererFlags = sdlRendererFlags;
_pixelFormat = sdlPixelFormat;
_logFilePrefix = logFilePrefix;
_customBiosLua = customBiosLua;
_cartDirectory = cartDirectory;
}
void Host::oneTimeSetup(Audio* audio){
#if defined(__PS4__) || defined(__ORBIS__)
uint32_t initFlags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_TIMER | SDL_INIT_EVENTS;
#else
uint32_t initFlags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_TIMER | SDL_INIT_EVENTS;
#endif
if (SDL_Init(initFlags) != 0)
{
Logger_Write("SDL could not initialize: %s\n", SDL_GetError());
return;
}
#if defined(__PS4__) || defined(__ORBIS__)
window = SDL_CreateWindow("PicoPlay", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _windowWidth, _windowHeight, _windowFlags);
#else
window = SDL_CreateWindow("PicoPlay - 8-Bit Fantasy Arcade", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _windowWidth, _windowHeight, _windowFlags);
#endif
if (!window)
{
Logger_Write("Error creating window: %s\n", SDL_GetError());
quit = 1;
return;
}
renderer = SDL_CreateRenderer(window, -1, _rendererFlags);
if (!renderer)
{
SDL_Surface* windowSurface = SDL_GetWindowSurface(window);
if (windowSurface) {
renderer = SDL_CreateSoftwareRenderer(windowSurface);
}
}
if (!renderer)
{
Logger_Write("Error creating renderer: %s\n", SDL_GetError());
quit = 1;
return;
}
texture = SDL_CreateTexture(renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, PicoScreenWidth, PicoScreenHeight);
if (!texture)
{
Logger_Write("Error creating texture: %s\n", SDL_GetError());
quit = 1;
return;
}
atexit(SDL_Quit);
_audio = audio;
audioSetup();
joystickCount = SDL_NumJoysticks();
for (int i = 0; i < joystickCount; i++) {
if (SDL_JoystickOpen(i) == NULL) {
Logger_Write("Failed to open joystick %d: %s\n", i, SDL_GetError());
}
}
last_time = 0;
now_time = 0;
frame_time = 0;
targetFrameTimeMs = 0;
currKDown = 0;
currKHeld = 0;
loadSettingsIni();
_changeStretch(stretch);
scaleX = _screenWidth / (float)PicoScreenWidth;
scaleY = _screenHeight / (float)PicoScreenHeight;
mouseOffsetX = DestR.x;
mouseOffsetY = DestR.y;
}
void Host::oneTimeCleanup(){
saveSettingsIni();
audioCleanup();
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
void Host::setTargetFps(int targetFps){
targetFrameTimeMs = 1000 / targetFps;
}
void Host::changeStretch(){
if (stretchKeyPressed && resizekey == YesResize) {
StretchOption newStretch = stretch;
if (stretch == PixelPerfectStretch) {
newStretch = PixelPerfect;
}
else if (stretch == PixelPerfect) {
newStretch = StretchToFit;
}
else if (stretch == StretchToFit) {
newStretch = StretchToFill;
}
else if (stretch == StretchToFill) {
newStretch = FourByThreeVertPerfect;
}
else if (stretch == FourByThreeVertPerfect) {
newStretch = FourByThreeStretch;
}
else if (stretch == FourByThreeStretch) {
newStretch = PixelPerfectStretch;
}
_changeStretch(newStretch);
stretch = newStretch;
scaleX = _screenWidth / (float)PicoScreenWidth;
scaleY = _screenHeight / (float)PicoScreenHeight;
mouseOffsetX = DestR.x;
mouseOffsetY = DestR.y;
}
}
void Host::forceStretch(StretchOption newStretch) {
_changeStretch(newStretch);
stretch = newStretch;
scaleX = _screenWidth / (float)PicoScreenWidth;
scaleY = _screenHeight / (float)PicoScreenHeight;
mouseOffsetX = DestR.x;
mouseOffsetY = DestR.y;
}
void Host::onWindowResized(int newWidth, int newHeight) {
if (newWidth <= 0 || newHeight <= 0) return;
_windowWidth = newWidth;
_windowHeight = newHeight;
int factor = std::min(_windowWidth / PicoScreenWidth, _windowHeight / PicoScreenHeight);
if (factor < 1) factor = 1;
_maxNoStretchWidth = factor * PicoScreenWidth;
_maxNoStretchHeight = factor * PicoScreenHeight;
_changeStretch(stretch);
scaleX = _screenWidth / (float)PicoScreenWidth;
scaleY = _screenHeight / (float)PicoScreenHeight;
mouseOffsetX = DestR.x;
mouseOffsetY = DestR.y;
}
bool Host::shouldQuit() {
return quit == 1;
}
void Host::waitForTargetFps(){
now_time = SDL_GetTicks();
frame_time = now_time - last_time;
last_time = now_time;
//sleep for remainder of time
if (frame_time < targetFrameTimeMs) {
uint32_t msToSleep = targetFrameTimeMs - frame_time;
SDL_Delay(msToSleep);
last_time += msToSleep;
}
}
void Host::drawFrame(uint8_t* picoFb, uint8_t* screenPaletteMap, uint8_t drawMode){
//clear screen to all black
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
SDL_LockTexture(texture, NULL, &pixels, &pitch);
for (int y = 0; y < PicoScreenHeight; y ++){
for (int x = 0; x < PicoScreenWidth; x ++){
uint8_t c = getPixelNibble(x, y, picoFb);
// Apply 0x8F mask per PICO-8 spec: system color indices are masked with 0x8F
// This maps values like 149 to valid palette indices (149 & 0x8F = 133)
Color col = _paletteColors[screenPaletteMap[c] & 0x8f];
base = ((Uint8 *)pixels) + (4 * ( y * PicoScreenHeight + x));
base[0] = col.Blue;
base[1] = col.Green;
base[2] = col.Red;
base[3] = col.Alpha;
}
}
SrcR.x = 0;
SrcR.y = 0;
switch(drawMode){
case 1:
SrcR.w = 64;
SrcR.h = PicoScreenHeight;
textureAngle = 0;
flip = SDL_FLIP_NONE;
break;
case 2:
SrcR.w = PicoScreenWidth;
SrcR.h = 64;
textureAngle = 0;
flip = SDL_FLIP_NONE;
break;
case 3:
SrcR.w = 64;
SrcR.h = 64;
textureAngle = 0;
flip = SDL_FLIP_NONE;
break;
//todo: mirroring
//case 4,6,7
case 129:
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 0;
flip = SDL_FLIP_HORIZONTAL;
break;
case 130:
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 0;
flip = SDL_FLIP_VERTICAL;
break;
case 131:
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 0;
flip = (SDL_RendererFlip)(SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL);
break;
case 133:
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 90;
flip = SDL_FLIP_NONE;
break;
case 134:
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 180;
flip = SDL_FLIP_NONE;
break;
case 135:
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 270;
flip = SDL_FLIP_NONE;
break;
default:
SrcR.w = PicoScreenWidth;
SrcR.h = PicoScreenHeight;
textureAngle = 0;
flip = SDL_FLIP_NONE;
break;
}
postFlipFunction();
}
bool Host::shouldFillAudioBuff(){
return false;
}
void* Host::getAudioBufferPointer(){
return nullptr;
}
size_t Host::getAudioBufferSize(){
return 0;
}
void Host::playFilledAudioBuffer(){
}
bool Host::shouldRunMainLoop(){
if (shouldQuit()){
return false;
}
return true;
}
const char* Host::logFilePrefix() {
return _logFilePrefix.c_str();
}
std::string Host::customBiosLua() {
return _customBiosLua;
}
std::string Host::getCartDirectory() {
return _cartDirectory;
}