246 lines
6.5 KiB
C++
246 lines
6.5 KiB
C++
/*
|
|
Copyright 2023 flyinghead
|
|
|
|
This file is part of Flycast.
|
|
|
|
Flycast is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Flycast is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "multiboard.h"
|
|
|
|
static Multiboard *multiboard;
|
|
|
|
#ifdef NAOMI_MULTIBOARD
|
|
#include "cfg/cfg.h"
|
|
#ifndef _WIN32
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#endif
|
|
#include "oslib/oslib.h"
|
|
#include "naomi_cart.h"
|
|
#include "naomi_roms.h"
|
|
#include "cfg/option.h"
|
|
#include "hw/sh4/sh4_sched.h"
|
|
#include <thread>
|
|
#include <chrono>
|
|
|
|
constexpr int SyncCycles = 500000;
|
|
|
|
static int schedCallback(int tag, int cycles, int jitter)
|
|
{
|
|
multiboard->syncWait();
|
|
return SyncCycles;
|
|
}
|
|
|
|
Multiboard::Multiboard()
|
|
{
|
|
sharedMem = nullptr;
|
|
|
|
boardId = cfgLoadInt("naomi", "BoardId", 0);
|
|
#ifdef _WIN32
|
|
const char *FileName = "Local\\naomi_multiboard_mem";
|
|
if (isMaster())
|
|
mapFile = CreateFileMapping(
|
|
INVALID_HANDLE_VALUE, // use paging file
|
|
NULL, // default security
|
|
PAGE_READWRITE, // read/write access
|
|
0, // max. object size (high-order)
|
|
sizeof(SharedMemory), // max. object size (low-order)
|
|
FileName); // name of mapping object
|
|
else
|
|
mapFile = OpenFileMapping(
|
|
FILE_MAP_ALL_ACCESS, // read/write access
|
|
FALSE, // do not inherit the name
|
|
FileName); // name of mapping object
|
|
|
|
if (mapFile == NULL)
|
|
{
|
|
ERROR_LOG(NAOMI, "Could not open/create file mapping (%d)", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
INFO_LOG(NAOMI, "Created shared memory: \"%s\"", FileName);
|
|
sharedMem = (SharedMemory *) MapViewOfFile(mapFile, // handle to map object
|
|
FILE_MAP_ALL_ACCESS, // read/write permission
|
|
0,
|
|
0,
|
|
sizeof(SharedMemory));
|
|
|
|
if (sharedMem == nullptr)
|
|
{
|
|
ERROR_LOG(NAOMI, "Could not map view of file (%d)", GetLastError());
|
|
CloseHandle(mapFile);
|
|
mapFile = NULL;
|
|
}
|
|
}
|
|
#else
|
|
#define SHARED_MEM_FILE "/naomi_multiboard_mem"
|
|
|
|
int fd = shm_open(SHARED_MEM_FILE, O_RDWR | (isMaster() ? O_CREAT : 0), 0644);
|
|
if (fd < 0)
|
|
ERROR_LOG(NAOMI, "Can't open mapped file. errno %d", errno);
|
|
else
|
|
{
|
|
if (isMaster() && ftruncate(fd, sizeof(SharedMemory)))
|
|
ERROR_LOG(NAOMI, "Can't ftruncate mapped file. errno %d", errno);
|
|
sharedMem = (SharedMemory *)mmap(nullptr, sizeof(SharedMemory), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
close(fd);
|
|
}
|
|
#endif
|
|
if (sharedMem == nullptr)
|
|
throw FlycastException("Cannot initialize Naomi multiboard shared memory");
|
|
if (isMaster())
|
|
{
|
|
new (sharedMem) SharedMemory();
|
|
sharedMem->boardReady[1] = sharedMem->boardReady[2] = sharedMem->boardReady[3] = true;
|
|
sharedMem->boardSynced[1] = sharedMem->boardSynced[2] = sharedMem->boardSynced[3] = true;
|
|
}
|
|
multiboard = this;
|
|
schedId = sh4_sched_register(0, schedCallback);
|
|
sh4_sched_request(schedId, SyncCycles);
|
|
}
|
|
|
|
void Multiboard::startSlave()
|
|
{
|
|
if (isSlave() || slaveStarted)
|
|
return;
|
|
int slaves;
|
|
if (!strcmp("f355", CurrentCartridge->game->name))
|
|
slaves = 3;
|
|
else if (config::MultiboardSlaves >= 2)
|
|
slaves = CurrentCartridge->game->multiboard;
|
|
else
|
|
slaves = 1;
|
|
boardCount = slaves + 1;
|
|
for (int i = 0; i < boardCount; i++)
|
|
sharedMem->boardReady[i] = false;
|
|
|
|
int x = cfgLoadInt("window", "left", (1920 - 640) / 2);
|
|
int y = cfgLoadInt("window", "top", (1080 - 480) / 2);
|
|
for (int i = 0; i < slaves; i++)
|
|
{
|
|
std::string region = "config:Dreamcast.Region=" + std::to_string(config::Region);
|
|
std::string board = "naomi:BoardId=" + std::to_string(i + 1);
|
|
int slaveX = x;
|
|
int slaveY = y + 480;
|
|
if (slaves == 2) {
|
|
slaveX = i == 0 ? x - 640 : x + 640;
|
|
slaveY = y;
|
|
}
|
|
else if (slaves == 3)
|
|
slaveX = i == 1 ? x - 640 : i == 2 ? x + 640 : x;
|
|
std::string left = "window:left=" + std::to_string(slaveX);
|
|
std::string top = "window:top=" + std::to_string(slaveY);
|
|
const char *args[] = {
|
|
"-config", board.c_str(),
|
|
"-config", region.c_str(),
|
|
"-config", left.c_str(),
|
|
"-config", top.c_str(),
|
|
CurrentCartridge->game->bios == nullptr ? "naomi" : CurrentCartridge->game->bios
|
|
};
|
|
os_RunInstance(ARRAY_SIZE(args), args);
|
|
}
|
|
slaveStarted = true;
|
|
}
|
|
|
|
void Multiboard::syncWait()
|
|
{
|
|
if (isMaster() && !slaveStarted)
|
|
return;
|
|
|
|
{
|
|
std::unique_lock<IpcMutex> lock(sharedMem->mutex);
|
|
sharedMem->boardReady[boardId] = true;
|
|
sharedMem->boardSynced[boardId] = false;
|
|
sharedMem->cond.notify_all();
|
|
}
|
|
do {
|
|
if (isSlave() && sharedMem->exit) {
|
|
NOTICE_LOG(NAOMI, "Slave exiting");
|
|
throw FlycastException("Slave exit");
|
|
}
|
|
{
|
|
std::unique_lock<IpcMutex> lock(sharedMem->mutex);
|
|
bool allReady = true;
|
|
for (const auto& ready : sharedMem->boardReady)
|
|
if (!ready) {
|
|
allReady = false;
|
|
break;
|
|
}
|
|
if (allReady) {
|
|
sharedMem->boardSynced[boardId] = true;
|
|
sharedMem->cond.notify_all();
|
|
break;
|
|
}
|
|
if (sharedMem->cond.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) {
|
|
ERROR_LOG(NAOMI, "Time out waiting for multiboard vsync. Slave %d", isSlave());
|
|
return;
|
|
}
|
|
}
|
|
} while (true);
|
|
if (isMaster())
|
|
{
|
|
do {
|
|
{
|
|
std::unique_lock<IpcMutex> lock(sharedMem->mutex);
|
|
|
|
bool allSynced = true;
|
|
for (const auto& synced : sharedMem->boardSynced)
|
|
if (!synced) {
|
|
allSynced = false;
|
|
break;
|
|
}
|
|
if (allSynced)
|
|
{
|
|
for (int i = 0; i < boardCount; i++)
|
|
sharedMem->boardReady[i] = false;
|
|
break;
|
|
}
|
|
if (sharedMem->cond.wait_for(lock, std::chrono::seconds(5)) == std::cv_status::timeout) {
|
|
ERROR_LOG(NAOMI, "Time out waiting for multiboard vsync");
|
|
break;
|
|
}
|
|
}
|
|
} while (true);
|
|
}
|
|
}
|
|
|
|
Multiboard::~Multiboard()
|
|
{
|
|
if (schedId != -1)
|
|
sh4_sched_unregister(schedId);
|
|
multiboard = nullptr;
|
|
|
|
if (sharedMem != nullptr)
|
|
{
|
|
sharedMem->exit = true;
|
|
sharedMem->cond.notify_all();
|
|
if (isMaster())
|
|
sharedMem->~SharedMemory();
|
|
}
|
|
#ifdef _WIN32
|
|
if (sharedMem != nullptr)
|
|
UnmapViewOfFile(sharedMem);
|
|
if (mapFile != NULL)
|
|
CloseHandle(mapFile);
|
|
#else
|
|
if (sharedMem != nullptr)
|
|
munmap(sharedMem, sizeof(SharedMemory));
|
|
shm_unlink(SHARED_MEM_FILE);
|
|
#endif
|
|
}
|
|
|
|
#endif // NAOMI_MULTIBOARD
|