mirror of
https://github.com/birth1mark/orbis8.git
synced 2026-10-29 04:00:30 +01:00
495 lines
12 KiB
C++
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;
|
|
}
|
|
|