#include #include #include #include #include #include using namespace std; #include "sdl2basehost.h" #include "../../../source/hostVmShared.h" #include "../../../source/nibblehelpers.h" #include "../../../source/logger.h" #include "../../../source/filehelpers.h" // sdl #include #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; }