Split OoT into its own repo

This commit is contained in:
Mr-Wiseguy 2023-03-22 01:08:48 -04:00
parent 85a04d74e7
commit f4324ee599
45 changed files with 2 additions and 56235 deletions

View file

@ -1,4 +0,0 @@
#ifdef __clang__
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wimplicit-function-declaration"
#endif

239
recomp.h
View file

@ -1,239 +0,0 @@
#ifndef __RECOMP_H__
#define __RECOMP_H__
#include <stdint.h>
#include <math.h>
#include <assert.h>
#include <setjmp.h>
#include <malloc.h>
#if 0 // treat GPRs as 32-bit, should be better codegen
typedef uint32_t gpr;
#define SIGNED(val) \
((int32_t)(val))
#else
typedef uint64_t gpr;
#define SIGNED(val) \
((int64_t)(val))
#endif
#define ADD32(a, b) \
((gpr)(int32_t)((a) + (b)))
#define SUB32(a, b) \
((gpr)(int32_t)((a) - (b)))
#define MEM_W(offset, reg) \
(*(int32_t*)(rdram + ((((reg) + (offset))) - 0xFFFFFFFF80000000)))
//(*(int32_t*)(rdram + ((((reg) + (offset))) & 0x3FFFFFF)))
#define MEM_H(offset, reg) \
(*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
//(*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) & 0x3FFFFFF)))
#define MEM_B(offset, reg) \
(*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
//(*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
#define MEM_HU(offset, reg) \
(*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000)))
//(*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) & 0x3FFFFFF)))
#define MEM_BU(offset, reg) \
(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000)))
//(*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) & 0x3FFFFFF)))
#define SD(val, offset, reg) { \
*(uint32_t*)(rdram + ((((reg) + (offset) + 4)) - 0xFFFFFFFF80000000)) = (uint32_t)((val) >> 0); \
*(uint32_t*)(rdram + ((((reg) + (offset) + 0)) - 0xFFFFFFFF80000000)) = (uint32_t)((val) >> 32); \
}
//#define SD(val, offset, reg) { \
// *(uint32_t*)(rdram + ((((reg) + (offset) + 4)) & 0x3FFFFFF)) = (uint32_t)((val) >> 32); \
// *(uint32_t*)(rdram + ((((reg) + (offset) + 0)) & 0x3FFFFFF)) = (uint32_t)((val) >> 0); \
//}
static inline uint64_t load_doubleword(uint8_t* rdram, gpr reg, gpr offset) {
uint64_t ret = 0;
uint64_t lo = (uint64_t)(uint32_t)MEM_W(reg, offset + 4);
uint64_t hi = (uint64_t)(uint32_t)MEM_W(reg, offset + 0);
ret = (lo << 0) | (hi << 32);
return ret;
}
#define LD(offset, reg) \
load_doubleword(rdram, offset, reg)
// TODO proper lwl/lwr/swl/swr
static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
uint8_t byte0 = val >> 24;
uint8_t byte1 = val >> 16;
uint8_t byte2 = val >> 8;
uint8_t byte3 = val >> 0;
MEM_B(offset + 0, reg) = byte0;
MEM_B(offset + 1, reg) = byte1;
MEM_B(offset + 2, reg) = byte2;
MEM_B(offset + 3, reg) = byte3;
}
static inline gpr do_lwl(uint8_t* rdram, gpr offset, gpr reg) {
uint8_t byte0 = MEM_B(offset + 0, reg);
uint8_t byte1 = MEM_B(offset + 1, reg);
uint8_t byte2 = MEM_B(offset + 2, reg);
uint8_t byte3 = MEM_B(offset + 3, reg);
// Cast to int32_t to sign extend first
return (gpr)(int32_t)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | (byte3 << 0));
}
#define S32(val) \
((int32_t)(val))
#define U32(val) \
((uint32_t)(val))
#define S64(val) \
((int64_t)(val))
#define U64(val) \
((uint64_t)(val))
#define MUL_S(val1, val2) \
((val1) * (val2))
#define MUL_D(val1, val2) \
((val1) * (val2))
#define DIV_S(val1, val2) \
((val1) / (val2))
#define DIV_D(val1, val2) \
((val1) / (val2))
#define CVT_S_W(val) \
((float)((int32_t)(val)))
#define CVT_D_W(val) \
((double)((int32_t)(val)))
#define CVT_D_S(val) \
((double)(val))
#define CVT_S_D(val) \
((float)(val))
#define TRUNC_W_S(val) \
((int32_t)(val))
#define TRUNC_W_D(val) \
((int32_t)(val))
#define TRUNC_L_S(val) \
((int64_t)(val))
#define TRUNC_L_D(val) \
((int64_t)(val))
// TODO rounding mode
#define CVT_W_S(val) \
((int32_t)(val))
#define CVT_W_D(val) \
((int32_t)(val))
#define NAN_CHECK(val) \
assert(val == val)
//#define NAN_CHECK(val)
typedef union {
double d;
struct {
float fl;
float fh;
};
struct {
uint32_t u32l;
uint32_t u32h;
};
uint64_t u64;
} fpr;
typedef struct {
gpr r0, r1, r2, r3, r4, r5, r6, r7,
r8, r9, r10, r11, r12, r13, r14, r15,
r16, r17, r18, r19, r20, r21, r22, r23,
r24, r25, r26, r27, r28, r29, r30, r31;
fpr f0, f2, f4, f6, f8, f10, f12, f14,
f16, f18, f20, f22, f24, f26, f28, f30;
uint64_t hi, lo;
} recomp_context;
#ifdef __cplusplus
extern "C" {
#endif
void switch_error(const char* func, uint32_t vram, uint32_t jtbl);
void do_break(uint32_t vram);
typedef void (recomp_func_t)(uint8_t* rdram, recomp_context* ctx);
recomp_func_t* get_function(int32_t vram);
#define LOOKUP_FUNC(val) \
get_function((int32_t)(val))
extern int32_t section_addresses[];
#define LO16(x) \
((x) & 0xFFFF)
#define HI16(x) \
(((x) >> 16) + (((x) >> 15) & 1))
#define RELOC_HI16(section_index, offset) \
HI16(section_addresses[section_index] + (offset))
#define RELOC_LO16(section_index, offset) \
LO16(section_addresses[section_index] + (offset))
// For the Mario Party games (not working)
//// This has to be in this file so it can be inlined
//struct jmp_buf_storage {
// jmp_buf buffer;
//};
//
//struct RecompJmpBuf {
// int32_t owner;
// struct jmp_buf_storage* storage;
// uint64_t magic;
//};
//
//// Randomly generated constant
//#define SETJMP_MAGIC 0xe17afdfa939a437bu
//
//int32_t osGetThreadEx(void);
//
//#define setjmp_recomp(rdram, ctx) { \
// struct RecompJmpBuf* buf = (struct RecompJmpBuf*)(&rdram[(uint64_t)ctx->r4 - 0xFFFFFFFF80000000]); \
// \
// /* Check if this jump buffer was previously set up */ \
// if (buf->magic == SETJMP_MAGIC) { \
// /* If so, free the old jmp_buf */ \
// free(buf->storage); \
// } \
// \
// buf->magic = SETJMP_MAGIC; \
// buf->owner = osGetThreadEx(); \
// buf->storage = (struct jmp_buf_storage*)calloc(1, sizeof(struct jmp_buf_storage)); \
// ctx->r2 = setjmp(buf->storage->buffer); \
//}
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1,23 +0,0 @@
#ifndef __SECTIONS_H__
#define __SECTIONS_H__
#include <stdint.h>
#include "recomp.h"
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
typedef struct {
recomp_func_t* func;
uint32_t offset;
} FuncEntry;
typedef struct {
uint32_t rom_addr;
uint32_t ram_addr;
uint32_t size;
FuncEntry *funcs;
size_t num_funcs;
size_t index;
} SectionTableEntry;
#endif

View file

@ -975,6 +975,8 @@ bool read_list_file(const char* filename, std::unordered_set<std::string>& entri
while (input_file >> entry) { while (input_file >> entry) {
entries_out.emplace(std::move(entry)); entries_out.emplace(std::move(entry));
} }
return true;
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {

View file

@ -1,185 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{d080900b-5c12-45fc-941b-515a5fea7594}</ProjectGuid>
<RootNamespace>RecompFuncs</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level1</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(SolutionDir)..</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<Optimization>Disabled</Optimization>
<AdditionalOptions>
</AdditionalOptions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level1</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(SolutionDir)..</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level1</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalOptions>
</AdditionalOptions>
<AdditionalIncludeDirectories>$(SolutionDir)..</AdditionalIncludeDirectories>
<PrecompiledHeaderFile />
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<Optimization>Disabled</Optimization>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level1</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<AdditionalOptions>
</AdditionalOptions>
<AdditionalIncludeDirectories>$(SolutionDir)..</AdditionalIncludeDirectories>
<PrecompiledHeaderFile />
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\recomp.h" />
</ItemGroup>
<ItemGroup>
<_WildCardClCompile Include="..\funcs\*.c" />
<ClCompile Include="@(_WildCardClCompile)" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

File diff suppressed because it is too large Load diff

View file

@ -1,44 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.32929.386
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RecompTest", "RecompTest.vcxproj", "{73819ED8-8A5B-4554-B3F3-60257A43F296}"
ProjectSection(ProjectDependencies) = postProject
{D080900B-5C12-45FC-941B-515A5FEA7594} = {D080900B-5C12-45FC-941B-515A5FEA7594}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RecompFuncs", "RecompFuncs\RecompFuncs.vcxproj", "{D080900B-5C12-45FC-941B-515A5FEA7594}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x64.ActiveCfg = Debug|x64
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x64.Build.0 = Debug|x64
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x86.ActiveCfg = Debug|Win32
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x86.Build.0 = Debug|Win32
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x64.ActiveCfg = Release|x64
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x64.Build.0 = Release|x64
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x86.ActiveCfg = Release|Win32
{73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x86.Build.0 = Release|Win32
{D080900B-5C12-45FC-941B-515A5FEA7594}.Debug|x64.ActiveCfg = Debug|x64
{D080900B-5C12-45FC-941B-515A5FEA7594}.Debug|x64.Build.0 = Debug|x64
{D080900B-5C12-45FC-941B-515A5FEA7594}.Debug|x86.ActiveCfg = Debug|Win32
{D080900B-5C12-45FC-941B-515A5FEA7594}.Debug|x86.Build.0 = Debug|Win32
{D080900B-5C12-45FC-941B-515A5FEA7594}.Release|x64.ActiveCfg = Release|x64
{D080900B-5C12-45FC-941B-515A5FEA7594}.Release|x64.Build.0 = Release|x64
{D080900B-5C12-45FC-941B-515A5FEA7594}.Release|x86.ActiveCfg = Release|Win32
{D080900B-5C12-45FC-941B-515A5FEA7594}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DD740FE2-99F7-4C80-B124-06D14447F873}
EndGlobalSection
EndGlobal

View file

@ -1,223 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{73819ED8-8A5B-4554-B3F3-60257A43F296}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>ClangCL</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>ClangCL</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<CopyCppRuntimeToOutputDir>true</CopyCppRuntimeToOutputDir>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<CopyCppRuntimeToOutputDir>true</CopyCppRuntimeToOutputDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<WarningLevel>Level1</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalOptions>/utf-8</AdditionalOptions>
</ClCompile>
<Link>
<TargetMachine>MachineX86</TargetMachine>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<WarningLevel>Level1</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalOptions>/utf-8</AdditionalOptions>
</ClCompile>
<Link>
<TargetMachine>MachineX86</TargetMachine>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)thirdparty;$(ProjectDir)Lib\SDL2-2.24.0\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/utf-8</AdditionalOptions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level1</WarningLevel>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>$(ProjectDir)RT64\$(Configuration)\RT64.lib;$(ProjectDir)Lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
</Link>
<PostBuildEvent>
<Command>XCOPY "$(ProjectDir)RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y
XCOPY "$(ProjectDir)Lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)thirdparty;$(ProjectDir)Lib\SDL2-2.24.0\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/utf-8</AdditionalOptions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level1</WarningLevel>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>$(ProjectDir)RT64\$(Configuration)\RT64.lib;$(ProjectDir)Lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
</Link>
<PostBuildEvent>
<Command>XCOPY "$(ProjectDir)RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y
XCOPY "$(ProjectDir)Lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="funcs\lookup.cpp" />
<ClCompile Include="portultra\audio.cpp">
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization>
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks>
</ClCompile>
<ClCompile Include="portultra\events.cpp" />
<ClCompile Include="portultra\timer.cpp" />
<ClCompile Include="portultra\ultrainit.cpp" />
<ClCompile Include="portultra\port_main.c" />
<ClCompile Include="portultra\mesgqueue.cpp" />
<ClCompile Include="portultra\scheduler.cpp" />
<ClCompile Include="portultra\task_pthreads.cpp" />
<ClCompile Include="portultra\task_win32.cpp" />
<ClCompile Include="portultra\threads.cpp" />
<ClCompile Include="rsp\aspMain.cpp">
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization>
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks>
<SupportJustMyCode Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</SupportJustMyCode>
<InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AnySuitable</InlineFunctionExpansion>
<InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AnySuitable</InlineFunctionExpansion>
</ClCompile>
<ClCompile Include="rsp\njpgdspMain.cpp">
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization>
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks>
<SupportJustMyCode Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</SupportJustMyCode>
<InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AnySuitable</InlineFunctionExpansion>
<InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AnySuitable</InlineFunctionExpansion>
</ClCompile>
<ClCompile Include="RT64\rt64_layer.cpp" />
<ClCompile Include="src\ai.cpp" />
<ClCompile Include="src\cont.cpp" />
<ClCompile Include="src\dp.cpp" />
<ClCompile Include="src\eep.cpp" />
<ClCompile Include="portultra\misc_ultra.cpp" />
<ClCompile Include="src\euc-jp.cpp" />
<ClCompile Include="src\math_routines.cpp" />
<ClCompile Include="src\overlays.cpp" />
<ClCompile Include="src\pak.cpp" />
<ClCompile Include="src\pi.cpp" />
<ClCompile Include="src\portultra_translation.cpp" />
<ClCompile Include="src\print.cpp" />
<ClCompile Include="src\recomp.cpp" />
<ClCompile Include="src\sp.cpp" />
<ClCompile Include="src\vi.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\disable_warnings.h" />
<ClInclude Include="..\recomp.h" />
<ClInclude Include="portultra\multilibultra.hpp" />
<ClInclude Include="portultra\platform_specific.h" />
<ClInclude Include="RT64\rt64_layer.h" />
<ClInclude Include="portultra\ultra64.h" />
<ClInclude Include="src\euc-jp.h" />
<ClInclude Include="thirdparty\blockingconcurrentqueue.h" />
<ClInclude Include="thirdparty\concurrentqueue.h" />
<ClInclude Include="thirdparty\lightweightsemaphore.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="RecompFuncs\RecompFuncs.vcxproj">
<Project>{d080900b-5c12-45fc-941b-515a5fea7594}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

File diff suppressed because it is too large Load diff

View file

@ -1,88 +0,0 @@
#include "ultra64.h"
#include "multilibultra.hpp"
#include "SDL.h"
#include "SDL_audio.h"
#include <cassert>
static SDL_AudioDeviceID audio_device = 0;
static uint32_t sample_rate = 48000;
void Multilibultra::init_audio() {
// Initialize SDL audio.
SDL_InitSubSystem(SDL_INIT_AUDIO);
// Pick an initial dummy sample rate; this will be set by the game later to the true sample rate.
set_audio_frequency(48000);
}
void Multilibultra::set_audio_frequency(uint32_t freq) {
if (audio_device != 0) {
SDL_CloseAudioDevice(audio_device);
}
SDL_AudioSpec spec_desired{
.freq = (int)freq,
.format = AUDIO_S16,
.channels = 2,
.silence = 0, // calculated
.samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering
.padding = 0, // unused
.size = 0, // calculated
.callback = nullptr,//feed_audio, // Use a callback as QueueAudio causes popping
.userdata = nullptr
};
audio_device = SDL_OpenAudioDevice(nullptr, false, &spec_desired, nullptr, 0);
if (audio_device == 0) {
printf("SDL Error: %s\n", SDL_GetError());
fflush(stdout);
assert(false);
}
SDL_PauseAudioDevice(audio_device, 0);
sample_rate = freq;
}
void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t byte_count) {
// Buffer for holding the output of swapping the audio channels. This is reused across
// calls to reduce runtime allocations.
static std::vector<uint16_t> swap_buffer;
// Ensure that the byte count is an integer multiple of samples.
assert((byte_count & 1) == 0);
// Calculate the number of samples from the number of bytes.
uint32_t sample_count = byte_count / sizeof(s16);
// Make sure the swap buffer is large enough to hold all the incoming audio data.
if (sample_count > swap_buffer.size()) {
swap_buffer.resize(sample_count);
}
// Swap the audio channels into the swap buffer to correct for the address xor caused by endianness handling.
s16* audio_data = TO_PTR(s16, audio_data_);
for (size_t i = 0; i < sample_count; i += 2) {
swap_buffer[i + 0] = audio_data[i + 1];
swap_buffer[i + 1] = audio_data[i + 0];
}
// Queue the swapped audio data.
SDL_QueueAudio(audio_device, swap_buffer.data(), byte_count);
}
// If there's ever any audio popping, check here first. Some games are very sensitive to
// the remaining sample count and reporting a number that's too high here can lead to issues.
// Reporting a number that's too low can lead to audio lag in some games.
uint32_t Multilibultra::get_remaining_audio_bytes() {
// Get the number of remaining buffered audio bytes.
uint32_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device);
// Adjust the reported count to be four refreshes in the future, which helps ensure that
// there are enough samples even if the game experiences a small amount of lag. This prevents
// audio popping on games that use the buffered audio byte count to determine how many samples
// to generate.
uint32_t samples_per_vi = (sample_rate / 60);
if (buffered_byte_count > (4u * samples_per_vi)) {
buffered_byte_count -= (4u * samples_per_vi);
} else {
buffered_byte_count = 0;
}
return buffered_byte_count;
}

View file

@ -1,335 +0,0 @@
#include <thread>
#include <atomic>
#include <chrono>
#include <cinttypes>
#include <variant>
#include <unordered_map>
#include <utility>
#include <mutex>
#include <queue>
#include <Windows.h>
#include "SDL.h"
#include "blockingconcurrentqueue.h"
#include "ultra64.h"
#include "multilibultra.hpp"
#include "recomp.h"
#include "../src/rsp.h"
struct SpTaskAction {
OSTask task;
};
struct SwapBuffersAction {
uint32_t origin;
};
using Action = std::variant<SpTaskAction, SwapBuffersAction>;
static struct {
struct {
std::thread thread;
PTR(OSMesgQueue) mq = NULLPTR;
PTR(void) current_buffer = NULLPTR;
PTR(void) next_buffer = NULLPTR;
OSMesg msg = (OSMesg)0;
int retrace_count = 1;
} vi;
struct {
std::thread thread;
PTR(OSMesgQueue) mq = NULLPTR;
OSMesg msg = (OSMesg)0;
} sp;
struct {
std::thread thread;
PTR(OSMesgQueue) mq = NULLPTR;
OSMesg msg = (OSMesg)0;
} dp;
struct {
std::thread thread;
PTR(OSMesgQueue) mq = NULLPTR;
OSMesg msg = (OSMesg)0;
} ai;
struct {
std::thread thread;
PTR(OSMesgQueue) mq = NULLPTR;
OSMesg msg = (OSMesg)0;
} si;
// The same message queue may be used for multiple events, so share a mutex for all of them
std::mutex message_mutex;
uint8_t* rdram;
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
} events_context{};
extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) {
OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
std::lock_guard lock{ events_context.message_mutex };
switch (event_id) {
case OS_EVENT_SP:
events_context.sp.msg = msg;
events_context.sp.mq = mq_;
break;
case OS_EVENT_DP:
events_context.dp.msg = msg;
events_context.dp.mq = mq_;
break;
case OS_EVENT_AI:
events_context.ai.msg = msg;
events_context.ai.mq = mq_;
break;
case OS_EVENT_SI:
events_context.si.msg = msg;
events_context.si.mq = mq_;
}
}
extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) {
std::lock_guard lock{ events_context.message_mutex };
events_context.vi.mq = mq_;
events_context.vi.msg = msg;
events_context.vi.retrace_count = retrace_count;
}
void vi_thread_func() {
using namespace std::chrono_literals;
uint64_t total_vis = 0;
int remaining_retraces = events_context.vi.retrace_count;
while (true) {
// Determine the next VI time (more accurate than adding 16ms each VI interrupt)
auto next = Multilibultra::get_start() + (total_vis * 1000000us) / (60 * Multilibultra::get_speed_multiplier());
//if (next > std::chrono::system_clock::now()) {
// printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n",
// (next - std::chrono::system_clock::now()) / 1us,
// (std::chrono::system_clock::now() - events_context.start) / 1us,
// (next - events_context.start) / 1us);
//} else {
// printf("No need to sleep\n");
//}
std::this_thread::sleep_until(next);
// Calculate how many VIs have passed
uint64_t new_total_vis = (Multilibultra::time_since_start() * (60 * Multilibultra::get_speed_multiplier()) / 1000ms) + 1;
if (new_total_vis > total_vis + 1) {
//printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1);
}
total_vis = new_total_vis;
remaining_retraces--;
{
std::lock_guard lock{ events_context.message_mutex };
uint8_t* rdram = events_context.rdram;
if (remaining_retraces == 0) {
remaining_retraces = events_context.vi.retrace_count;
if (events_context.vi.mq != NULLPTR) {
if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) {
//printf("Game skipped a VI frame!\n");
}
}
}
if (events_context.ai.mq != NULLPTR) {
if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) {
//printf("Game skipped a AI frame!\n");
}
}
}
}
}
void sp_complete() {
uint8_t* rdram = events_context.rdram;
std::lock_guard lock{ events_context.message_mutex };
osSendMesg(PASS_RDRAM events_context.sp.mq, events_context.sp.msg, OS_MESG_NOBLOCK);
}
void dp_complete() {
uint8_t* rdram = events_context.rdram;
std::lock_guard lock{ events_context.message_mutex };
osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK);
}
void RT64Init(uint8_t* rom, uint8_t* rdram);
void RT64SendDL(uint8_t* rdram, const OSTask* task);
void RT64UpdateScreen(uint32_t vi_origin);
std::unordered_map<SDL_Scancode, int> button_map{
{ SDL_Scancode::SDL_SCANCODE_LEFT, 0x0002 }, // c left
{ SDL_Scancode::SDL_SCANCODE_RIGHT, 0x0001 }, // c right
{ SDL_Scancode::SDL_SCANCODE_UP, 0x0008 }, // c up
{ SDL_Scancode::SDL_SCANCODE_DOWN, 0x0004 }, // c down
{ SDL_Scancode::SDL_SCANCODE_RETURN, 0x1000 }, // start
{ SDL_Scancode::SDL_SCANCODE_SPACE, 0x8000 }, // a
{ SDL_Scancode::SDL_SCANCODE_LSHIFT, 0x4000 }, // b
{ SDL_Scancode::SDL_SCANCODE_Q, 0x2000 }, // z
{ SDL_Scancode::SDL_SCANCODE_E, 0x0020 }, // l
{ SDL_Scancode::SDL_SCANCODE_R, 0x0010 }, // r
{ SDL_Scancode::SDL_SCANCODE_J, 0x0200 }, // dpad left
{ SDL_Scancode::SDL_SCANCODE_L, 0x0100 }, // dpad right
{ SDL_Scancode::SDL_SCANCODE_I, 0x0800 }, // dpad up
{ SDL_Scancode::SDL_SCANCODE_K, 0x0400 }, // dpad down
};
extern int button;
extern int stick_x;
extern int stick_y;
int sdl_event_filter(void* userdata, SDL_Event* event) {
switch (event->type) {
case SDL_EventType::SDL_KEYUP:
case SDL_EventType::SDL_KEYDOWN:
{
const Uint8* key_states = SDL_GetKeyboardState(nullptr);
int new_button = 0;
for (const auto& mapping : button_map) {
if (key_states[mapping.first]) {
new_button |= mapping.second;
}
}
button = new_button;
stick_x = 127 * (key_states[SDL_Scancode::SDL_SCANCODE_D] - key_states[SDL_Scancode::SDL_SCANCODE_A]);
stick_y = 127 * (key_states[SDL_Scancode::SDL_SCANCODE_W] - key_states[SDL_Scancode::SDL_SCANCODE_S]);
}
break;
case SDL_EventType::SDL_QUIT:
std::quick_exit(ERROR_SUCCESS);
break;
}
return 1;
}
uint8_t dmem[0x1000];
uint16_t rspReciprocals[512];
uint16_t rspInverseSquareRoots[512];
using RspUcodeFunc = RspExitReason(uint8_t* rdram);
extern RspUcodeFunc njpgdspMain;
extern RspUcodeFunc aspMain;
// From Ares emulator. For license details, see rsp_vu.h
void rsp_constants_init() {
rspReciprocals[0] = u16(~0);
for (u16 index = 1; index < 512; index++) {
u64 a = index + 512;
u64 b = (u64(1) << 34) / a;
rspReciprocals[index] = u16(b + 1 >> 8);
}
for (u16 index = 0; index < 512; index++) {
u64 a = index + 512 >> ((index % 2 == 1) ? 1 : 0);
u64 b = 1 << 17;
//find the largest b where b < 1.0 / sqrt(a)
while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++;
rspInverseSquareRoots[index] = u16(b >> 1);
}
}
// Runs a recompiled RSP microcode
void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_func) {
// Load the OSTask into DMEM
memcpy(&dmem[0xFC0], task, sizeof(OSTask));
// Load the ucode data into DMEM
dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1);
// Run the ucode
RspExitReason exit_reason = ucode_func(rdram);
// Ensure that the ucode exited correctly
assert(exit_reason == RspExitReason::Broke);
sp_complete();
}
void event_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* events_thread_ready) {
using namespace std::chrono_literals;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
fprintf(stderr, "Failed to initialize SDL2: %s\n", SDL_GetError());
std::quick_exit(EXIT_FAILURE);
}
RT64Init(rom, rdram);
SDL_Window* window = SDL_GetWindowFromID(1);
// TODO set this window title in RT64, create the window here and send it to RT64, or something else entirely
// as the current window name visibly changes as RT64 is initialized
SDL_SetWindowTitle(window, "Recomp");
//SDL_SetEventFilter(sdl_event_filter, nullptr);
rsp_constants_init();
// Notify the caller thread that this thread is ready.
events_thread_ready->test_and_set();
events_thread_ready->notify_all();
while (true) {
// Try to pull an action from the queue
Action action;
if (events_context.action_queue.wait_dequeue_timed(action, 1ms)) {
// Determine the action type and act on it
if (const auto* task_action = std::get_if<SpTaskAction>(&action)) {
if (task_action->task.t.type == M_GFXTASK) {
// (TODO let RT64 do this) Tell the game that the RSP and RDP tasks are complete
RT64SendDL(rdram, &task_action->task);
sp_complete();
dp_complete();
} else if (task_action->task.t.type == M_AUDTASK) {
run_rsp_microcode(rdram, &task_action->task, aspMain);
} else if (task_action->task.t.type == M_NJPEGTASK) {
run_rsp_microcode(rdram, &task_action->task, njpgdspMain);
} else {
fprintf(stderr, "Unknown task type: %" PRIu32 "\n", task_action->task.t.type);
assert(false);
std::quick_exit(EXIT_FAILURE);
}
} else if (const auto* swap_action = std::get_if<SwapBuffersAction>(&action)) {
events_context.vi.current_buffer = events_context.vi.next_buffer;
RT64UpdateScreen(swap_action->origin);
}
}
// Handle events
constexpr int max_events_per_frame = 16;
SDL_Event cur_event;
int i = 0;
while (i++ < max_events_per_frame && SDL_PollEvent(&cur_event)) {
sdl_event_filter(nullptr, &cur_event);
}
//SDL_PumpEvents();
}
}
extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) {
events_context.vi.next_buffer = frameBufPtr;
events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + 640 });
}
extern "C" PTR(void) osViGetNextFramebuffer() {
return events_context.vi.next_buffer;
}
extern "C" PTR(void) osViGetCurrentFramebuffer() {
return events_context.vi.current_buffer;
}
void Multilibultra::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) {
OSTask* task = TO_PTR(OSTask, task_);
events_context.action_queue.enqueue(SpTaskAction{ *task });
}
void Multilibultra::send_si_message() {
uint8_t* rdram = events_context.rdram;
osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK);
}
void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom) {
std::atomic_flag events_thread_ready;
events_context.rdram = rdram;
events_context.sp.thread = std::thread{ event_thread_func, rdram, rom, &events_thread_ready };
// Wait for the event thread to be ready before continuing to prevent the game from
// running before we're able to handle RSP tasks.
events_thread_ready.wait(false);
events_context.vi.thread = std::thread{ vi_thread_func };
}

View file

@ -1,194 +0,0 @@
#include <thread>
#include <atomic>
#include "ultra64.h"
#include "multilibultra.hpp"
#include "recomp.h"
extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) {
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
mq->blocked_on_recv = NULLPTR;
mq->blocked_on_send = NULLPTR;
mq->msgCount = count;
mq->msg = msg;
mq->validCount = 0;
mq->first = 0;
}
s32 MQ_GET_COUNT(OSMesgQueue *mq) {
return mq->validCount;
}
s32 MQ_IS_EMPTY(OSMesgQueue *mq) {
return mq->validCount == 0;
}
s32 MQ_IS_FULL(OSMesgQueue* mq) {
return MQ_GET_COUNT(mq) >= mq->msgCount;
}
void thread_queue_insert(RDRAM_ARG PTR(OSThread)* queue, PTR(OSThread) toadd_) {
PTR(OSThread)* cur = queue;
OSThread* toadd = TO_PTR(OSThread, toadd_);
while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) {
cur = &TO_PTR(OSThread, *cur)->next;
}
toadd->next = (*cur);
*cur = toadd_;
}
OSThread* thread_queue_pop(RDRAM_ARG PTR(OSThread)* queue) {
PTR(OSThread) ret = *queue;
*queue = TO_PTR(OSThread, ret)->next;
return TO_PTR(OSThread, ret);
}
bool thread_queue_empty(RDRAM_ARG PTR(OSThread)* queue) {
return *queue == NULLPTR;
}
extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
// Prevent accidentally blocking anything that isn't a game thread
if (!Multilibultra::is_game_thread()) {
flags = OS_MESG_NOBLOCK;
}
Multilibultra::disable_preemption();
if (flags == OS_MESG_NOBLOCK) {
// If non-blocking, fail if the queue is full
if (MQ_IS_FULL(mq)) {
Multilibultra::enable_preemption();
return -1;
}
} else {
// Otherwise, yield this thread until the queue has room
while (MQ_IS_FULL(mq)) {
debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
Multilibultra::enable_preemption();
Multilibultra::pause_self(PASS_RDRAM1);
Multilibultra::disable_preemption();
}
}
s32 last = (mq->first + mq->validCount) % mq->msgCount;
TO_PTR(OSMesg, mq->msg)[last] = msg;
mq->validCount++;
OSThread* to_run = nullptr;
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
}
Multilibultra::enable_preemption();
if (to_run) {
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
if (Multilibultra::is_game_thread()) {
OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread());
if (to_run->priority > self->priority) {
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
} else {
Multilibultra::schedule_running_thread(to_run);
}
} else {
Multilibultra::schedule_running_thread(to_run);
}
}
return 0;
}
extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
Multilibultra::disable_preemption();
if (flags == OS_MESG_NOBLOCK) {
// If non-blocking, fail if the queue is full
if (MQ_IS_FULL(mq)) {
Multilibultra::enable_preemption();
return -1;
}
} else {
// Otherwise, yield this thread in a loop until the queue is no longer full
while (MQ_IS_FULL(mq)) {
debug_printf("[Message Queue] Thread %d is blocked on jam\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
Multilibultra::enable_preemption();
Multilibultra::pause_self(PASS_RDRAM1);
Multilibultra::disable_preemption();
}
}
mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount;
TO_PTR(OSMesg, mq->msg)[mq->first] = msg;
mq->validCount++;
OSThread *to_run = nullptr;
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
}
Multilibultra::enable_preemption();
if (to_run) {
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
if (to_run->priority > self->priority) {
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
} else {
Multilibultra::schedule_running_thread(to_run);
}
}
return 0;
}
extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) {
OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
OSMesg *msg = TO_PTR(OSMesg, msg_);
Multilibultra::disable_preemption();
if (flags == OS_MESG_NOBLOCK) {
// If non-blocking, fail if the queue is empty
if (MQ_IS_EMPTY(mq)) {
Multilibultra::enable_preemption();
return -1;
}
} else {
// Otherwise, yield this thread in a loop until the queue is no longer full
while (MQ_IS_EMPTY(mq)) {
debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
thread_queue_insert(PASS_RDRAM &mq->blocked_on_recv, Multilibultra::this_thread());
Multilibultra::enable_preemption();
Multilibultra::pause_self(PASS_RDRAM1);
Multilibultra::disable_preemption();
}
}
if (msg_ != NULLPTR) {
*msg = TO_PTR(OSMesg, mq->msg)[mq->first];
}
mq->first = (mq->first + 1) % mq->msgCount;
mq->validCount--;
OSThread *to_run = nullptr;
if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_send)) {
to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_send);
}
Multilibultra::enable_preemption();
if (to_run) {
debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
if (to_run->priority > self->priority) {
Multilibultra::swap_to_thread(PASS_RDRAM to_run);
} else {
Multilibultra::schedule_running_thread(to_run);
}
}
return 0;
}

View file

@ -1,22 +0,0 @@
#include "ultra64.h"
#define K0BASE 0x80000000
#define K1BASE 0xA0000000
#define K2BASE 0xC0000000
#define IS_KSEG0(x) ((u32)(x) >= K0BASE && (u32)(x) < K1BASE)
#define IS_KSEG1(x) ((u32)(x) >= K1BASE && (u32)(x) < K2BASE)
#define K0_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg0 to physical */
#define K1_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg1 to physical */
u32 osVirtualToPhysical(PTR(void) addr) {
uintptr_t addr_val = (uintptr_t)addr;
if (IS_KSEG0(addr_val)) {
return K0_TO_PHYS(addr_val);
} else if (IS_KSEG1(addr_val)) {
return K1_TO_PHYS(addr_val);
} else {
// TODO handle TLB mappings
return (u32)addr_val;
}
}

View file

@ -1,70 +0,0 @@
#ifndef __MULTILIBULTRA_HPP__
#define __MULTILIBULTRA_HPP__
#include <thread>
#include <atomic>
#include <mutex>
#include <algorithm>
#include "ultra64.h"
#include "platform_specific.h"
struct UltraThreadContext {
std::thread host_thread;
std::atomic_bool running;
std::atomic_bool initialized;
};
namespace Multilibultra {
void preinit(uint8_t* rdram, uint8_t* rom);
void save_init();
void native_init();
void init_scheduler();
void init_events(uint8_t* rdram, uint8_t* rom);
void init_timers(RDRAM_ARG1);
void native_thread_init(OSThread *t);
void set_self_paused(RDRAM_ARG1);
void wait_for_resumed(RDRAM_ARG1);
void swap_to_thread(RDRAM_ARG OSThread *to);
void pause_thread_impl(OSThread *t);
void pause_thread_native_impl(OSThread *t);
void resume_thread_impl(OSThread *t);
void resume_thread_native_impl(OSThread *t);
void schedule_running_thread(OSThread *t);
void stop_thread(OSThread *t);
void pause_self(RDRAM_ARG1);
void cleanup_thread(OSThread *t);
PTR(OSThread) this_thread();
void disable_preemption();
void enable_preemption();
void notify_scheduler();
void reprioritize_thread(OSThread *t, OSPri pri);
void set_main_thread();
bool is_game_thread();
void submit_rsp_task(RDRAM_ARG PTR(OSTask) task);
void send_si_message();
uint32_t get_speed_multiplier();
std::chrono::system_clock::time_point get_start();
std::chrono::system_clock::duration time_since_start();
void init_audio();
void set_audio_frequency(uint32_t freq);
void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count);
uint32_t get_remaining_audio_bytes();
class preemption_guard {
public:
preemption_guard();
~preemption_guard();
private:
std::lock_guard<std::mutex> lock;
};
} // namespace Multilibultra
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define debug_printf(...)
//#define debug_printf(...) printf(__VA_ARGS__);
#endif

View file

@ -1,32 +0,0 @@
#ifndef __PLATFORM_SPECIFIC_H__
#define __PLATFORM_SPECIFIC_H__
#if defined(__linux__)
//#include <pthread.h>
//
//typedef struct {
// pthread_t t;
// pthread_barrier_t pause_barrier;
// pthread_mutex_t pause_mutex;
// pthread_cond_t pause_cond;
// void (*entrypoint)(void *);
// void *arg;
//} OSThreadNative;
#elif defined(_WIN32)
//#include <pthread.h>
//
//typedef struct {
// pthread_t t;
// pthread_barrier_t pause_barrier;
// pthread_mutex_t pause_mutex;
// pthread_cond_t pause_cond;
// void (*entrypoint)(void*);
// void* arg;
//} OSThreadNative;
#endif
#endif

View file

@ -1,83 +0,0 @@
#if 0
#include <stdio.h>
#include <stdlib.h>
#include "ultra64.h"
#define THREAD_STACK_SIZE 0x1000
u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16);
u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16);
u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16);
u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16);
OSThread idle_thread;
OSThread main_thread;
OSThread thread3;
OSThread thread4;
OSMesgQueue queue;
OSMesg buf[1];
void thread3_func(UNUSED void *arg) {
OSMesg val;
printf("Thread3 recv\n");
fflush(stdout);
osRecvMesg(&queue, &val, OS_MESG_BLOCK);
printf("Thread3 complete: %d\n", (int)(intptr_t)val);
fflush(stdout);
}
void thread4_func(void *arg) {
printf("Thread4 send %d\n", (int)(intptr_t)arg);
fflush(stdout);
osSendMesg(&queue, arg, OS_MESG_BLOCK);
printf("Thread4 complete\n");
fflush(stdout);
}
void main_thread_func(UNUSED void* arg) {
osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0]));
printf("main thread creating thread 3\n");
osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14);
printf("main thread starting thread 3\n");
osStartThread(&thread3);
printf("main thread creating thread 4\n");
osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13);
printf("main thread starting thread 4\n");
osStartThread(&thread4);
while (1) {
printf("main thread doin stuff\n");
sleep(1);
}
}
void idle_thread_func(UNUSED void* arg) {
printf("idle thread\n");
printf("creating main thread\n");
osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11);
printf("starting main thread\n");
osStartThread(&main_thread);
// Set this thread's priority to 0, making it the idle thread
osSetThreadPri(NULL, 0);
// idle
while (1) {
printf("idle thread doin stuff\n");
sleep(1);
}
}
void bootproc(void) {
osInitialize();
osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127);
printf("Starting idle thread\n");
osStartThread(&idle_thread);
}
#endif

View file

@ -1,286 +0,0 @@
#include <thread>
#include <queue>
#include <atomic>
#include <vector>
#include "multilibultra.hpp"
class OSThreadComparator {
public:
bool operator() (OSThread *a, OSThread *b) const {
return a->priority < b->priority;
}
};
class thread_queue_t : public std::priority_queue<OSThread*, std::vector<OSThread*>, OSThreadComparator> {
public:
// TODO comment this
bool remove(const OSThread* value) {
auto it = std::find(this->c.begin(), this->c.end(), value);
if (it == this->c.end()) {
return false;
}
if (it == this->c.begin()) {
// deque the top element
this->pop();
} else {
// remove element and re-heap
this->c.erase(it);
std::make_heap(this->c.begin(), this->c.end(), this->comp);
}
return true;
}
};
static struct {
std::vector<OSThread*> to_schedule;
std::vector<OSThread*> to_stop;
std::vector<OSThread*> to_cleanup;
std::vector<std::pair<OSThread*, OSPri>> to_reprioritize;
std::mutex mutex;
// OSThread* running_thread;
std::atomic_int notify_count;
std::atomic_int action_count;
bool can_preempt;
std::mutex premption_mutex;
} scheduler_context{};
void handle_thread_queueing(thread_queue_t& running_thread_queue) {
std::lock_guard lock{scheduler_context.mutex};
if (!scheduler_context.to_schedule.empty()) {
OSThread* to_schedule = scheduler_context.to_schedule.back();
scheduler_context.to_schedule.pop_back();
scheduler_context.action_count.fetch_sub(1);
debug_printf("[Scheduler] Scheduling thread %d\n", to_schedule->id);
running_thread_queue.push(to_schedule);
}
}
void handle_thread_stopping(thread_queue_t& running_thread_queue) {
std::lock_guard lock{scheduler_context.mutex};
while (!scheduler_context.to_stop.empty()) {
OSThread* to_stop = scheduler_context.to_stop.back();
scheduler_context.to_stop.pop_back();
scheduler_context.action_count.fetch_sub(1);
debug_printf("[Scheduler] Stopping thread %d\n", to_stop->id);
running_thread_queue.remove(to_stop);
}
}
void handle_thread_cleanup(thread_queue_t& running_thread_queue) {
std::lock_guard lock{scheduler_context.mutex};
while (!scheduler_context.to_cleanup.empty()) {
OSThread* to_cleanup = scheduler_context.to_cleanup.back();
scheduler_context.to_cleanup.pop_back();
scheduler_context.action_count.fetch_sub(1);
debug_printf("[Scheduler] Destroying thread %d\n", to_cleanup->id);
running_thread_queue.remove(to_cleanup);
to_cleanup->context->host_thread.join();
delete to_cleanup->context;
}
}
void handle_thread_reprioritization(thread_queue_t& running_thread_queue) {
std::lock_guard lock{scheduler_context.mutex};
while (!scheduler_context.to_reprioritize.empty()) {
const std::pair<OSThread*, OSPri> to_reprioritize = scheduler_context.to_reprioritize.back();
scheduler_context.to_reprioritize.pop_back();
scheduler_context.action_count.fetch_sub(1);
debug_printf("[Scheduler] Reprioritizing thread %d to %d\n", to_reprioritize.first->id, to_reprioritize.second);
running_thread_queue.remove(to_reprioritize.first);
to_reprioritize.first->priority = to_reprioritize.second;
running_thread_queue.push(to_reprioritize.first);
}
}
void handle_scheduler_notifications() {
std::lock_guard lock{scheduler_context.mutex};
int32_t notify_count = scheduler_context.notify_count.exchange(0);
if (notify_count) {
debug_printf("Received %d notifications\n", notify_count);
scheduler_context.action_count.fetch_sub(notify_count);
}
}
void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread) {
if (running_thread_queue.size() > 0) {
OSThread* new_running_thread = running_thread_queue.top();
if (cur_running_thread != new_running_thread) {
if (cur_running_thread && cur_running_thread->state == OSThreadState::RUNNING) {
debug_printf("[Scheduler] Need to wait for thread %d to pause itself\n", cur_running_thread->id);
return;
//debug_printf("[Scheduler] Switching execution from thread %d (%d) to thread %d (%d)\n",
// cur_running_thread->id, cur_running_thread->priority,
// new_running_thread->id, new_running_thread->priority);
//Multilibultra::pause_thread_impl(cur_running_thread);
} else {
debug_printf("[Scheduler] Switching execution to thread %d (%d)\n", new_running_thread->id, new_running_thread->priority);
}
Multilibultra::resume_thread_impl(new_running_thread);
cur_running_thread = new_running_thread;
} else if (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING) {
Multilibultra::resume_thread_impl(cur_running_thread);
}
} else {
cur_running_thread = nullptr;
}
}
void scheduler_func() {
thread_queue_t running_thread_queue{};
OSThread* cur_running_thread = nullptr;
while (true) {
OSThread* old_running_thread = cur_running_thread;
scheduler_context.action_count.wait(0);
std::lock_guard lock{scheduler_context.premption_mutex};
// Handle notifications
handle_scheduler_notifications();
// Handle stopping threads
handle_thread_stopping(running_thread_queue);
// Handle cleaning up threads
handle_thread_cleanup(running_thread_queue);
// Handle queueing threads to run
handle_thread_queueing(running_thread_queue);
// Handle threads that have changed priority
handle_thread_reprioritization(running_thread_queue);
// Determine which thread to run, stopping the current running thread if necessary
swap_running_thread(running_thread_queue, cur_running_thread);
std::this_thread::yield();
if (old_running_thread != cur_running_thread && old_running_thread && cur_running_thread) {
debug_printf("[Scheduler] Swapped from Thread %d (%d) to Thread %d (%d)\n",
old_running_thread->id, old_running_thread->priority, cur_running_thread->id, cur_running_thread->priority);
}
}
}
extern "C" void do_yield() {
std::this_thread::yield();
}
namespace Multilibultra {
void init_scheduler() {
scheduler_context.can_preempt = true;
std::thread scheduler_thread{scheduler_func};
scheduler_thread.detach();
}
void schedule_running_thread(OSThread *t) {
debug_printf("[Scheduler] Queuing Thread %d to be scheduled\n", t->id);
std::lock_guard lock{scheduler_context.mutex};
scheduler_context.to_schedule.push_back(t);
scheduler_context.action_count.fetch_add(1);
scheduler_context.action_count.notify_all();
}
void swap_to_thread(RDRAM_ARG OSThread *to) {
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
debug_printf("[Scheduler] Scheduling swap from thread %d to %d\n", self->id, to->id);
{
std::lock_guard lock{scheduler_context.mutex};
scheduler_context.to_schedule.push_back(to);
Multilibultra::set_self_paused(PASS_RDRAM1);
scheduler_context.action_count.fetch_add(1);
scheduler_context.action_count.notify_all();
}
Multilibultra::wait_for_resumed(PASS_RDRAM1);
}
void reprioritize_thread(OSThread *t, OSPri pri) {
debug_printf("[Scheduler] Adjusting Thread %d priority to %d\n", t->id, pri);
std::lock_guard lock{scheduler_context.mutex};
scheduler_context.to_reprioritize.emplace_back(t, pri);
scheduler_context.action_count.fetch_add(1);
scheduler_context.action_count.notify_all();
}
void pause_self(RDRAM_ARG1) {
OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
debug_printf("[Scheduler] Thread %d pausing itself\n", self->id);
{
std::lock_guard lock{scheduler_context.mutex};
Multilibultra::set_self_paused(PASS_RDRAM1);
scheduler_context.to_stop.push_back(self);
scheduler_context.action_count.fetch_add(1);
scheduler_context.action_count.notify_all();
}
Multilibultra::wait_for_resumed(PASS_RDRAM1);
}
void stop_thread(OSThread *t) {
debug_printf("[Scheduler] Queuing Thread %d to be stopped\n", t->id);
{
std::lock_guard lock{scheduler_context.mutex};
scheduler_context.to_stop.push_back(t);
scheduler_context.action_count.fetch_add(1);
scheduler_context.action_count.notify_all();
}
Multilibultra::pause_thread_impl(t);
}
void cleanup_thread(OSThread *t) {
std::lock_guard lock{scheduler_context.mutex};
scheduler_context.to_cleanup.push_back(t);
scheduler_context.action_count.fetch_add(1);
scheduler_context.action_count.notify_all();
}
void disable_preemption() {
scheduler_context.premption_mutex.lock();
if (Multilibultra::is_game_thread()) {
scheduler_context.can_preempt = false;
}
}
void enable_preemption() {
if (Multilibultra::is_game_thread()) {
scheduler_context.can_preempt = true;
}
#pragma warning(push)
#pragma warning( disable : 26110)
scheduler_context.premption_mutex.unlock();
#pragma warning( pop )
}
// lock's constructor is called first, so can_preempt is set after locking
preemption_guard::preemption_guard() : lock{scheduler_context.premption_mutex} {
scheduler_context.can_preempt = false;
}
// lock's destructor is called last, so can_preempt is set before unlocking
preemption_guard::~preemption_guard() {
scheduler_context.can_preempt = true;
}
void notify_scheduler() {
std::lock_guard lock{scheduler_context.mutex};
scheduler_context.notify_count.fetch_add(1);
scheduler_context.action_count.fetch_add(1);
scheduler_context.action_count.notify_all();
}
}
extern "C" void pause_self(uint8_t* rdram) {
Multilibultra::pause_self(rdram);
}

View file

@ -1,60 +0,0 @@
#ifndef _WIN32
// #include <thread>
// #include <stdexcept>
// #include <atomic>
#include <pthread.h>
#include <signal.h>
#include <limits.h>
#include "ultra64.h"
#include "multilibultra.hpp"
constexpr int pause_thread_signum = SIGUSR1;
// void cleanup_current_thread(OSThread *t) {
// debug_printf("Thread cleanup %d\n", t->id);
// // delete t->context;
// }
void sig_handler(int signum, siginfo_t *info, void *context) {
if (signum == pause_thread_signum) {
OSThread *t = Multilibultra::this_thread();
debug_printf("[Sig] Thread %d paused\n", t->id);
// Wait until the thread is marked as running again
t->context->running.wait(false);
debug_printf("[Sig] Thread %d resumed\n", t->id);
}
}
void Multilibultra::native_init(void) {
// Set up a signal handler to capture pause signals
struct sigaction sigact;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_SIGINFO | SA_RESTART;
sigact.sa_sigaction = sig_handler;
sigaction(pause_thread_signum, &sigact, nullptr);
}
void Multilibultra::native_thread_init(OSThread *t) {
debug_printf("[Native] Init thread %d\n", t->id);
}
void Multilibultra::pause_thread_native_impl(OSThread *t) {
debug_printf("[Native] Pause thread %d\n", t->id);
// Send a pause signal to the thread, which will trigger it to wait on its pause barrier in the signal handler
pthread_kill(t->context->host_thread.native_handle(), pause_thread_signum);
}
void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) {
debug_printf("[Native] Resume thread %d\n", t->id);
// Nothing to do here
}
#endif

View file

@ -1,32 +0,0 @@
#include <Windows.h>
#include "ultra64.h"
#include "multilibultra.hpp"
extern "C" unsigned int sleep(unsigned int seconds) {
Sleep(seconds * 1000);
return 0;
}
void Multilibultra::native_init(void) {
}
void Multilibultra::native_thread_init(OSThread *t) {
debug_printf("[Native] Init thread %d\n", t->id);
}
void Multilibultra::pause_thread_native_impl(OSThread *t) {
debug_printf("[Native] Pause thread %d\n", t->id);
// Pause the thread via the win32 API
SuspendThread(t->context->host_thread.native_handle());
// Perform a synchronous action to ensure that the thread is suspended
// see: https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743
CONTEXT threadContext{};
GetThreadContext(t->context->host_thread.native_handle(), &threadContext);
}
void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) {
debug_printf("[Native] Resume thread %d\n", t->id);
// Resume the thread
ResumeThread(t->context->host_thread.native_handle());
}

View file

@ -1,193 +0,0 @@
#include <cstdio>
#include <thread>
#include <cassert>
#include <string>
#include "ultra64.h"
#include "multilibultra.hpp"
// Native APIs only used to set thread names for easier debugging
#ifdef _WIN32
#include <Windows.h>
#endif
extern "C" void bootproc();
thread_local bool is_main_thread = false;
// Whether this thread is part of the game (i.e. the start thread or one spawned by osCreateThread)
thread_local bool is_game_thread = false;
thread_local PTR(OSThread) thread_self = NULLPTR;
void Multilibultra::set_main_thread() {
::is_game_thread = true;
is_main_thread = true;
}
bool Multilibultra::is_game_thread() {
return ::is_game_thread;
}
#if 0
int main(int argc, char** argv) {
Multilibultra::set_main_thread();
bootproc();
}
#endif
#if 1
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg);
#else
#define run_thread_function(func, sp, arg) func(arg)
#endif
static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) {
OSThread *self = TO_PTR(OSThread, self_);
debug_printf("[Thread] Thread created: %d\n", self->id);
thread_self = self_;
is_game_thread = true;
// Set the thread name
#ifdef _WIN32
std::wstring thread_name = L"Game Thread " + std::to_wstring(self->id);
HRESULT r;
r = SetThreadDescription(
GetCurrentThread(),
thread_name.c_str()
);
#endif
// Perform any necessary native thread initialization.
Multilibultra::native_thread_init(self);
// Set initialized to false to indicate that this thread can be started.
self->context->initialized.store(true);
self->context->initialized.notify_all();
debug_printf("[Thread] Thread waiting to be started: %d\n", self->id);
// Wait until the thread is marked as running.
Multilibultra::set_self_paused(PASS_RDRAM1);
Multilibultra::wait_for_resumed(PASS_RDRAM1);
debug_printf("[Thread] Thread started: %d\n", self->id);
// Run the thread's function with the provided argument.
run_thread_function(PASS_RDRAM entrypoint, self->sp, arg);
// Dispose of this thread after it completes.
Multilibultra::cleanup_thread(self);
}
extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) {
OSThread* t = TO_PTR(OSThread, t_);
debug_printf("[os] Start Thread %d\n", t->id);
// Wait until the thread is initialized to indicate that it's action_queued to be started.
t->context->initialized.wait(false);
debug_printf("[os] Thread %d is ready to be started\n", t->id);
if (thread_self && (t->priority > TO_PTR(OSThread, thread_self)->priority)) {
Multilibultra::swap_to_thread(PASS_RDRAM t);
} else {
Multilibultra::schedule_running_thread(t);
}
// The main thread "becomes" the first thread started, so join on it and exit after it completes.
if (is_main_thread) {
t->context->host_thread.join();
std::exit(EXIT_SUCCESS);
}
}
extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) {
debug_printf("[os] Create Thread %d\n", id);
OSThread *t = TO_PTR(OSThread, t_);
t->next = NULLPTR;
t->priority = pri;
t->id = id;
t->state = OSThreadState::PAUSED;
t->sp = sp - 0x10; // Set up the first stack frame
// Spawn a new thread, which will immediately pause itself and wait until it's been started.
t->context = new UltraThreadContext{};
t->context->initialized.store(false);
t->context->running.store(false);
t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg};
}
extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) {
assert(false);
}
extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) {
assert(false);
}
extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) {
if (t == NULLPTR) {
t = thread_self;
}
bool pause_self = false;
if (pri > TO_PTR(OSThread, thread_self)->priority) {
pause_self = true;
Multilibultra::set_self_paused(PASS_RDRAM1);
} else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) {
pause_self = true;
Multilibultra::set_self_paused(PASS_RDRAM1);
}
Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri);
if (pause_self) {
Multilibultra::wait_for_resumed(PASS_RDRAM1);
}
}
extern "C" OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) t) {
if (t == NULLPTR) {
t = thread_self;
}
return TO_PTR(OSThread, t)->priority;
}
extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) {
if (t == NULLPTR) {
t = thread_self;
}
return TO_PTR(OSThread, t)->id;
}
// TODO yield thread, need a stable priority queue in the scheduler
void Multilibultra::set_self_paused(RDRAM_ARG1) {
debug_printf("[Thread] Thread pausing itself: %d\n", TO_PTR(OSThread, thread_self)->id);
TO_PTR(OSThread, thread_self)->state = OSThreadState::PAUSED;
TO_PTR(OSThread, thread_self)->context->running.store(false);
TO_PTR(OSThread, thread_self)->context->running.notify_all();
}
void Multilibultra::wait_for_resumed(RDRAM_ARG1) {
TO_PTR(OSThread, thread_self)->context->running.wait(false);
}
void Multilibultra::pause_thread_impl(OSThread* t) {
t->state = OSThreadState::PREEMPTED;
t->context->running.store(false);
t->context->running.notify_all();
Multilibultra::pause_thread_native_impl(t);
}
void Multilibultra::resume_thread_impl(OSThread *t) {
if (t->state == OSThreadState::PREEMPTED) {
Multilibultra::resume_thread_native_impl(t);
}
t->state = OSThreadState::RUNNING;
t->context->running.store(true);
t->context->running.notify_all();
}
PTR(OSThread) Multilibultra::this_thread() {
return thread_self;
}

View file

@ -1,187 +0,0 @@
#include <thread>
#include <variant>
#include <set>
#include "blockingconcurrentqueue.h"
#include "ultra64.h"
#include "multilibultra.hpp"
#include "recomp.h"
// Start time for the program
static std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
// Game speed multiplier (1 means no speedup)
constexpr uint32_t speed_multiplier = 1;
// N64 CPU counter ticks per millisecond
constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier;
struct OSTimer {
PTR(OSTimer) unused1;
PTR(OSTimer) unused2;
OSTime interval;
OSTime timestamp;
PTR(OSMesgQueue) mq;
OSMesg msg;
};
struct AddTimerAction {
PTR(OSTask) timer;
};
struct RemoveTimerAction {
PTR(OSTimer) timer;
};
using Action = std::variant<AddTimerAction, RemoveTimerAction>;
struct {
std::thread thread;
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
} timer_context;
uint64_t duration_to_ticks(std::chrono::system_clock::duration duration) {
uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
// More accurate than using a floating point timer, will only overflow after running for 12.47 years
// Units: (micros * (counts/millis)) / (micros/millis) = counts
uint64_t total_count = (delta_micros * counter_per_ms) / 1000;
return total_count;
}
std::chrono::microseconds ticks_to_duration(uint64_t ticks) {
using namespace std::chrono_literals;
return ticks * 1000us / counter_per_ms;
}
std::chrono::system_clock::time_point ticks_to_timepoint(uint64_t ticks) {
return start + ticks_to_duration(ticks);
}
uint64_t time_now() {
return duration_to_ticks(std::chrono::system_clock::now() - start);
}
void timer_thread(RDRAM_ARG1) {
// Lambda comparator function to keep the set ordered
auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) {
OSTimer* a = TO_PTR(OSTimer, a_);
OSTimer* b = TO_PTR(OSTimer, b_);
// Order by timestamp if the timers have different timestamps
if (a->timestamp != b->timestamp) {
return a->timestamp < b->timestamp;
}
// If they have the exact same timestamp then order by address instead
return a < b;
};
// Ordered set of timers that are currently active
std::set<PTR(OSTimer), decltype(timer_sort)> active_timers{timer_sort};
// Lambda to process a timer action to handle adding and removing timers
auto process_timer_action = [&](const Action& action) {
// Determine the action type and act on it
if (const auto* add_action = std::get_if<AddTimerAction>(&action)) {
active_timers.insert(add_action->timer);
} else if (const auto* remove_action = std::get_if<RemoveTimerAction>(&action)) {
active_timers.erase(remove_action->timer);
}
};
while (true) {
// Empty the action queue
Action cur_action;
while (timer_context.action_queue.try_dequeue(cur_action)) {
process_timer_action(cur_action);
}
// If there's no timer to act on, wait for one to come in from the action queue
while (active_timers.empty()) {
timer_context.action_queue.wait_dequeue(cur_action);
process_timer_action(cur_action);
}
// Get the timer that's closest to running out
PTR(OSTimer) cur_timer_ = *active_timers.begin();
OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_);
// Remove the timer from the queue (it may get readded if waiting is interrupted)
active_timers.erase(cur_timer_);
// Determine how long to wait to reach the timer's timestamp
auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::system_clock::now();
auto wait_us = std::chrono::duration_cast<std::chrono::microseconds>(wait_duration);
// Wait for either the duration to complete or a new action to come through
if (timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) {
// Timer was interrupted by a new action
// Add the current timer back to the queue (done first in case the action is to remove this timer)
active_timers.insert(cur_timer_);
// Process the new action
process_timer_action(cur_action);
} else {
// Waiting for the timer completed, so send the timer's message to its message queue
osSendMesg(PASS_RDRAM cur_timer->mq, cur_timer->msg, OS_MESG_NOBLOCK);
// If the timer has a specified interval then reload it with that value
if (cur_timer->interval != 0) {
cur_timer->timestamp = cur_timer->interval + time_now();
active_timers.insert(cur_timer_);
}
}
}
}
void Multilibultra::init_timers(RDRAM_ARG1) {
timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 };
}
uint32_t Multilibultra::get_speed_multiplier() {
return speed_multiplier;
}
std::chrono::system_clock::time_point Multilibultra::get_start() {
return start;
}
std::chrono::system_clock::duration Multilibultra::time_since_start() {
return std::chrono::system_clock::now() - start;
}
extern "C" u32 osGetCount() {
uint64_t total_count = time_now();
// Allow for overflows, which is how osGetCount behaves
return (uint32_t)total_count;
}
extern "C" OSTime osGetTime() {
uint64_t total_count = time_now();
return total_count;
}
extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) {
OSTimer* t = TO_PTR(OSTimer, t_);
// Determine the time when this timer will trigger off
if (countdown == 0) {
// Set the timestamp based on the interval
t->timestamp = interval + time_now();
} else {
t->timestamp = countdown + time_now();
}
t->interval = interval;
t->mq = mq;
t->msg = msg;
timer_context.action_queue.enqueue(AddTimerAction{ t_ });
return 0;
}
extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) {
timer_context.action_queue.enqueue(RemoveTimerAction{ t_ });
// TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was
return 0;
}

View file

@ -1,182 +0,0 @@
#ifndef __ULTRA64_MULTILIBULTRA_H__
#define __ULTRA64_MULTILIBULTRA_H__
#include <stdint.h>
#include "platform_specific.h"
#ifdef __cplusplus
#include <queue>
#endif
#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#define ALIGNED(x) __attribute__((aligned(x)))
#else
#define UNUSED
#define ALIGNED(x)
#endif
typedef int64_t s64;
typedef uint64_t u64;
typedef int32_t s32;
typedef uint32_t u32;
typedef int16_t s16;
typedef uint16_t u16;
typedef int8_t s8;
typedef uint8_t u8;
#define PTR(x) int32_t
#define RDRAM_ARG uint8_t *rdram,
#define RDRAM_ARG1 uint8_t *rdram
#define PASS_RDRAM rdram,
#define PASS_RDRAM1 rdram
#define TO_PTR(type, var) ((type*)(&rdram[(uint64_t)var - 0xFFFFFFFF80000000]))
#ifdef __cplusplus
#define NULLPTR (PTR(void))0
#endif
#ifndef NULL
#define NULL (PTR(void) 0)
#endif
#define OS_MESG_NOBLOCK 0
#define OS_MESG_BLOCK 1
typedef s32 OSPri;
typedef s32 OSId;
typedef u64 OSTime;
#define OS_EVENT_SW1 0 /* CPU SW1 interrupt */
#define OS_EVENT_SW2 1 /* CPU SW2 interrupt */
#define OS_EVENT_CART 2 /* Cartridge interrupt: used by rmon */
#define OS_EVENT_COUNTER 3 /* Counter int: used by VI/Timer Mgr */
#define OS_EVENT_SP 4 /* SP task done interrupt */
#define OS_EVENT_SI 5 /* SI (controller) interrupt */
#define OS_EVENT_AI 6 /* AI interrupt */
#define OS_EVENT_VI 7 /* VI interrupt: used by VI/Timer Mgr */
#define OS_EVENT_PI 8 /* PI interrupt: used by PI Manager */
#define OS_EVENT_DP 9 /* DP full sync interrupt */
#define OS_EVENT_CPU_BREAK 10 /* CPU breakpoint: used by rmon */
#define OS_EVENT_SP_BREAK 11 /* SP breakpoint: used by rmon */
#define OS_EVENT_FAULT 12 /* CPU fault event: used by rmon */
#define OS_EVENT_THREADSTATUS 13 /* CPU thread status: used by rmon */
#define OS_EVENT_PRENMI 14 /* Pre NMI interrupt */
#define M_GFXTASK 1
#define M_AUDTASK 2
#define M_VIDTASK 3
#define M_NJPEGTASK 4
/////////////
// Structs //
/////////////
typedef struct UltraThreadContext UltraThreadContext;
typedef enum {
RUNNING,
PAUSED,
PREEMPTED
} OSThreadState;
typedef struct OSThread_t {
PTR(struct OSThread_t) next; // Next thread in the given queue
OSPri priority;
uint32_t pad1;
uint32_t pad2;
uint16_t flags; // These two are swapped to reflect rdram byteswapping
uint16_t state;
OSId id;
int32_t pad3;
UltraThreadContext* context; // An actual pointer regardless of platform
int32_t sp;
} OSThread;
typedef u32 OSEvent;
typedef PTR(void) OSMesg;
typedef struct OSMesgQueue {
PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */
PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */
s32 validCount; /* Number of messages in the queue */
s32 first; /* Index of the first message in the ring buffer */
s32 msgCount; /* Size of message buffer */
PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */
} OSMesgQueue;
typedef struct {
u32 type;
u32 flags;
PTR(u64) ucode_boot;
u32 ucode_boot_size;
PTR(u64) ucode;
u32 ucode_size;
PTR(u64) ucode_data;
u32 ucode_data_size;
PTR(u64) dram_stack;
u32 dram_stack_size;
PTR(u64) output_buff;
PTR(u64) output_buff_size;
PTR(u64) data_ptr;
u32 data_size;
PTR(u64) yield_data_ptr;
u32 yield_data_size;
} OSTask_t;
typedef union {
OSTask_t t;
int64_t force_structure_alignment;
} OSTask;
///////////////
// Functions //
///////////////
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
void osInitialize(void);
typedef void (thread_func_t)(PTR(void));
void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p);
void osStartThread(RDRAM_ARG PTR(OSThread) t);
void osStopThread(RDRAM_ARG PTR(OSThread) t);
void osDestroyThread(RDRAM_ARG PTR(OSThread) t);
void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri);
OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread);
OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t);
s32 MQ_GET_COUNT(RDRAM_ARG PTR(OSMesgQueue));
s32 MQ_IS_EMPTY(RDRAM_ARG PTR(OSMesgQueue));
s32 MQ_IS_FULL(RDRAM_ARG PTR(OSMesgQueue));
void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg);
void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue), OSMesg, u32);
void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr);
PTR(void) osViGetNextFramebuffer();
PTR(void) osViGetCurrentFramebuffer();
u32 osGetCount();
OSTime osGetTime();
int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg);
int osStopTimer(RDRAM_ARG PTR(OSTimer) timer);
u32 osVirtualToPhysical(PTR(void) addr);
#ifdef __cplusplus
} // extern "C"
#endif
#endif

View file

@ -1,15 +0,0 @@
#include "ultra64.h"
#include "multilibultra.hpp"
void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom) {
Multilibultra::set_main_thread();
Multilibultra::init_events(rdram, rom);
Multilibultra::init_timers(rdram);
Multilibultra::init_audio();
Multilibultra::save_init();
}
extern "C" void osInitialize() {
Multilibultra::init_scheduler();
Multilibultra::native_init();
}

2
test/rsp/.gitignore vendored
View file

@ -1,2 +0,0 @@
njpgdspMain.cpp
aspMain.cpp

View file

@ -1,29 +0,0 @@
#include "recomp.h"
#include <cstdio>
#include <string>
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
#define VI_NTSC_CLOCK 48681812
extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) {
uint32_t freq = ctx->r4;
// This makes actual audio frequency more accurate to console, but may not be desirable
//uint32_t dacRate = (uint32_t)(((float)VI_NTSC_CLOCK / freq) + 0.5f);
//freq = VI_NTSC_CLOCK / dacRate;
ctx->r2 = freq;
Multilibultra::set_audio_frequency(freq);
}
extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
Multilibultra::queue_audio_buffer(rdram, ctx->r4, ctx->r5);
ctx->r2 = 0;
}
extern "C" void osAiGetLength_recomp(uint8_t* rdram, recomp_context* ctx) {
ctx->r2 = Multilibultra::get_remaining_audio_bytes();
}
extern "C" void osAiGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) {
ctx->r2 = 0x00000000; // Pretend the audio DMAs finish instantly
}

View file

@ -1,108 +0,0 @@
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
static int max_controllers = 0;
extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) {
gpr bitpattern = ctx->r5;
gpr status = ctx->r6;
// Set bit 0 to indicate that controller 0 is present
MEM_B(0, bitpattern) = 0x01;
// Mark controller 0 as present
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
MEM_B(2, status) = 0x00; // status: 0 (from joybus)
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
max_controllers = 4;
// Mark controllers 1-3 as not connected
for (size_t controller = 1; controller < max_controllers; controller++) {
// Libultra doesn't write status or type for absent controllers
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
ctx->r2 = 0;
}
extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
Multilibultra::send_si_message();
}
struct OSContPad {
u16 button;
s8 stick_x; /* -80 <= stick_x <= 80 */
s8 stick_y; /* -80 <= stick_y <= 80 */
u8 errno_;
};
int button = 0;
int stick_x = 0;
int stick_y = 0;
void press_button(int button) {
}
void release_button(int button) {
}
extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
int32_t pad = (int32_t)ctx->r4;
if (max_controllers > 0) {
// button
MEM_H(0, pad) = button;
// stick_x
MEM_B(2, pad) = stick_x;
// stick_y
MEM_B(3, pad) = stick_y;
// errno
MEM_B(4, pad) = 0;
}
for (int controller = 1; controller < max_controllers; controller++) {
MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
}
extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
Multilibultra::send_si_message();
}
extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
gpr status = ctx->r4;
// Mark controller 0 as present
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
MEM_B(2, status) = 0x00; // status: 0 (from joybus)
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
// Mark controllers 1-3 as not connected
for (size_t controller = 1; controller < max_controllers; controller++) {
// Libultra doesn't write status or type for absent controllers
MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
}
}
extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) {
max_controllers = std::min((unsigned int)ctx->r4, 4u);
ctx->r2 = 0;
}
extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
}
extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}

View file

@ -1,5 +0,0 @@
#include "recomp.h"
extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}

View file

@ -1,21 +0,0 @@
#include "recomp.h"
extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}

File diff suppressed because it is too large Load diff

View file

@ -1,11 +0,0 @@
#ifndef __EUC_JP_H__
#define __EUC_JP_H__
#include <string>
#include <string_view>
namespace Encoding {
std::string decode_eucjp(std::string_view src);
}
#endif

View file

@ -1,80 +0,0 @@
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
extern "C" void __udivdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
uint64_t ret = a / b;
ctx->r2 = (int32_t)(ret >> 32);
ctx->r3 = (int32_t)(ret >> 0);
}
extern "C" void __divdi3_recomp(uint8_t * rdram, recomp_context * ctx) {
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
int64_t ret = a / b;
ctx->r2 = (int32_t)(ret >> 32);
ctx->r3 = (int32_t)(ret >> 0);
}
extern "C" void __umoddi3_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
uint64_t ret = a % b;
ctx->r2 = (int32_t)(ret >> 32);
ctx->r3 = (int32_t)(ret >> 0);
}
extern "C" void __ull_div_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
uint64_t ret = a / b;
ctx->r2 = (int32_t)(ret >> 32);
ctx->r3 = (int32_t)(ret >> 0);
}
extern "C" void __ll_div_recomp(uint8_t * rdram, recomp_context * ctx) {
int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
int64_t ret = a / b;
ctx->r2 = (int32_t)(ret >> 32);
ctx->r3 = (int32_t)(ret >> 0);
}
extern "C" void __ll_mul_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
uint64_t ret = a * b;
ctx->r2 = (int32_t)(ret >> 32);
ctx->r3 = (int32_t)(ret >> 0);
}
extern "C" void __ull_rem_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu);
uint64_t ret = a % b;
ctx->r2 = (int32_t)(ret >> 32);
ctx->r3 = (int32_t)(ret >> 0);
}
extern "C" void __ull_to_d_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
double ret = (double)a;
ctx->f0.d = ret;
}
extern "C" void __ull_to_f_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu);
float ret = (float)a;
ctx->f0.fl = ret;
}

View file

@ -1,110 +0,0 @@
#include <unordered_map>
#include <algorithm>
#include <vector>
#include "recomp.h"
#include "../funcs/recomp_overlays.inl"
constexpr size_t num_code_sections = ARRLEN(section_table);
// SectionTableEntry sections[] defined in recomp_overlays.inl
struct LoadedSection {
int32_t loaded_ram_addr;
size_t section_table_index;
bool operator<(const LoadedSection& rhs) {
return loaded_ram_addr < rhs.loaded_ram_addr;
}
};
std::vector<LoadedSection> loaded_sections{};
std::unordered_map<int32_t, recomp_func_t*> func_map{};
void load_overlay(size_t section_table_index, int32_t ram) {
const SectionTableEntry& section = section_table[section_table_index];
for (size_t function_index = 0; function_index < section.num_funcs; function_index++) {
const FuncEntry& func = section.funcs[function_index];
func_map[ram + func.offset] = func.func;
}
loaded_sections.emplace_back(ram, section_table_index);
section_addresses[section.index] = ram;
}
extern "C" {
int32_t section_addresses[num_sections];
}
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) {
// Search for the first section that's included in the loaded rom range
// Sections were sorted by `init_overlays` so we can use the bounds functions
auto lower = std::lower_bound(&section_table[0], &section_table[num_code_sections], rom,
[](const SectionTableEntry& entry, uint32_t addr) {
return entry.rom_addr < addr;
}
);
auto upper = std::upper_bound(&section_table[0], &section_table[num_code_sections], (uint32_t)(rom + size),
[](uint32_t addr, const SectionTableEntry& entry) {
return addr < entry.size + entry.rom_addr;
}
);
// Load the overlays that were found
for (auto it = lower; it != upper; ++it) {
load_overlay(std::distance(&section_table[0], it), it->rom_addr - rom + ram_addr);
}
}
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) {
for (auto it = loaded_sections.begin(); it != loaded_sections.end();) {
const auto& section = section_table[it->section_table_index];
// Check if the unloaded region overlaps with the loaded section
if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) {
// Check if the section isn't entirely in the loaded region
if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) {
fprintf(stderr,
"Cannot partially unload section\n"
" rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n"
" unloaded_ram: 0x%08X unloaded_size : 0x%08X\n",
section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size);
std::exit(EXIT_FAILURE);
}
// Determine where each function was loaded to and remove that entry from the function map
for (size_t func_index = 0; func_index < section.num_funcs; func_index++) {
const auto& func = section.funcs[func_index];
uint32_t func_address = func.offset + it->loaded_ram_addr;
func_map.erase(func_address);
}
// Reset the section's address in the address table
section_addresses[section.index] = 0;
// Remove the section from the loaded section map
it = loaded_sections.erase(it);
// Skip incrementing the iterator
continue;
}
++it;
}
}
void init_overlays() {
for (size_t section_index = 0; section_index < num_code_sections; section_index++) {
section_addresses[section_table[section_index].index] = section_table[section_index].ram_addr;
}
// Sort the executable sections by rom address
std::sort(&section_table[0], &section_table[num_code_sections],
[](const SectionTableEntry& a, const SectionTableEntry& b) {
return a.rom_addr < b.rom_addr;
}
);
}
extern "C" recomp_func_t * get_function(int32_t addr) {
auto func_find = func_map.find(addr);
if (func_find == func_map.end()) {
fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
std::exit(EXIT_FAILURE);
}
return func_find->second;
}

View file

@ -1,31 +0,0 @@
#include "recomp.h"
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) {
ctx->r2 = 1; // PFS_ERR_NOPACK
}
extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 1; // PFS_ERR_NOPACK
}
extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 1; // PFS_ERR_NOPACK
}
extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 1; // PFS_ERR_NOPACK
}
extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 1; // PFS_ERR_NOPACK
}
extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 1; // PFS_ERR_NOPACK
}
extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 1; // PFS_ERR_NOPACK
}

View file

@ -1,208 +0,0 @@
#include <memory>
#include <fstream>
#include <array>
#include "recomp.h"
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
struct OSIoMesgHdr {
// These 3 reversed due to endianness
u8 status; /* Return status */
u8 pri; /* Message priority (High or Normal) */
u16 type; /* Message type */
PTR(OSMesgQueue) retQueue; /* Return message queue to notify I/O completion */
};
struct OSIoMesg {
OSIoMesgHdr hdr; /* Message header */
PTR(void) dramAddr; /* RDRAM buffer address (DMA) */
u32 devAddr; /* Device buffer address (DMA) */
u32 size; /* DMA transfer size in bytes */
u32 piHandle; /* PI device handle */
};
struct OSPiHandle {
PTR(OSPiHandle_s) unused; /* point to next handle on the table */
// These four members reversed due to endianness
u8 relDuration; /* domain release duration */
u8 pageSize; /* domain page size */
u8 latency; /* domain latency */
u8 type; /* DEVICE_TYPE_BULK for disk */
// These three members reversed due to endianness
u16 padding; /* struct alignment padding */
u8 domain; /* which domain */
u8 pulse; /* domain pulse width */
u32 baseAddress; /* Domain address */
u32 speed; /* for roms only */
/* The following are "private" elements" */
u32 transferInfo[18]; /* for disk only */
};
// Flashram occupies the same physical address as sram, but that issue is avoided because libultra exposes
// a high-level interface for flashram. Because that high-level interface is reimplemented, low level accesses
// that involve physical addresses don't need to be handled for flashram.
constexpr uint32_t sram_base = 0x08000000;
constexpr uint32_t rom_base = 0x10000000;
constexpr uint32_t k1_to_phys(uint32_t addr) {
return addr & 0x1FFFFFFF;
}
constexpr uint32_t phys_to_k1(uint32_t addr) {
return addr | 0xA0000000;
}
// We need a place in rdram to hold the cart handle, so pick an address in extended rdram
constexpr int32_t cart_handle = 0x80800000;
extern std::unique_ptr<uint8_t[]> rom;
extern size_t rom_size;
extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) {
OSPiHandle* handle = TO_PTR(OSPiHandle, cart_handle);
handle->type = 0; // cart
handle->baseAddress = phys_to_k1(rom_base);
handle->domain = 0;
ctx->r2 = (gpr)cart_handle;
}
extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) {
// TODO use word copies when possible
uint8_t* rom_addr = rom.get() + physical_addr - rom_base;
for (size_t i = 0; i < num_bytes; i++) {
MEM_B(i, ram_address) = *rom_addr;
rom_addr++;
}
}
std::array<char, 0x20000> save_buffer;
const char save_filename[] = "save.bin";
void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) {
for (uint32_t i = 0; i < count; i++) {
save_buffer[offset + i] = MEM_B(i, rdram_address);
}
std::ofstream save_file{ save_filename, std::ios_base::binary };
if (save_file.good()) {
save_file.write(save_buffer.data(), save_buffer.size());
} else {
fprintf(stderr, "Failed to save!\n");
std::exit(EXIT_FAILURE);
}
}
void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) {
for (size_t i = 0; i < count; i++) {
MEM_B(i, rdram_address) = save_buffer[offset + i];
}
}
void Multilibultra::save_init() {
std::ifstream save_file{ save_filename, std::ios_base::binary };
if (save_file.good()) {
save_file.read(save_buffer.data(), save_buffer.size());
} else {
save_buffer.fill(0);
}
}
void do_dma(uint8_t* rdram, PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) {
// TODO asynchronous transfer
// TODO implement unaligned DMA correctly
if (direction == 0) {
if (physical_addr >= rom_base) {
// read cart rom
do_rom_read(rdram, rdram_address, physical_addr, size);
// Send a message to the mq to indicate that the transfer completed
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
} else if (physical_addr >= sram_base) {
// read sram
save_read(rdram, rdram_address, physical_addr - sram_base, size);
// Send a message to the mq to indicate that the transfer completed
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
} else {
fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr);
}
} else {
if (physical_addr >= rom_base) {
// write cart rom
throw std::runtime_error("ROM DMA write unimplemented");
} else if (physical_addr >= sram_base) {
// write sram
save_write(rdram, rdram_address, physical_addr - sram_base, size);
// Send a message to the mq to indicate that the transfer completed
osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK);
} else {
fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr);
}
}
}
extern "C" void osPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) {
uint32_t mb = ctx->r4;
uint32_t pri = ctx->r5;
uint32_t direction = ctx->r6;
uint32_t devAddr = ctx->r7;
gpr dramAddr = MEM_W(0x10, ctx->r29);
uint32_t size = MEM_W(0x14, ctx->r29);
PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29);
uint32_t physical_addr = k1_to_phys(devAddr);
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
do_dma(rdram, mq, dramAddr, physical_addr, size, direction);
ctx->r2 = 0;
}
extern "C" void osEPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) {
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5);
uint32_t direction = ctx->r6;
uint32_t devAddr = handle->baseAddress | mb->devAddr;
gpr dramAddr = mb->dramAddr;
uint32_t size = mb->size;
PTR(OSMesgQueue) mq = mb->hdr.retQueue;
uint32_t physical_addr = k1_to_phys(devAddr);
debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
do_dma(rdram, mq, dramAddr, physical_addr, size, direction);
ctx->r2 = 0;
}
extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) {
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
uint32_t devAddr = handle->baseAddress | ctx->r5;
gpr dramAddr = ctx->r6;
uint32_t physical_addr = k1_to_phys(devAddr);
if (physical_addr > rom_base) {
// cart rom
do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t));
} else {
// sram
assert(false && "SRAM ReadIo unimplemented");
}
ctx->r2 = 0;
}
extern "C" void osPiGetStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 0;
}
extern "C" void osPiRawStartDma_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 0;
}

View file

@ -1,158 +0,0 @@
#include <memory>
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
extern "C" void osInitialize_recomp(uint8_t * rdram, recomp_context * ctx) {
osInitialize();
}
extern "C" void __osInitialize_common_recomp(uint8_t * rdram, recomp_context * ctx) {
osInitialize();
}
extern "C" void osCreateThread_recomp(uint8_t* rdram, recomp_context* ctx) {
osCreateThread(rdram, (int32_t)ctx->r4, (OSId)ctx->r5, (int32_t)ctx->r6, (int32_t)ctx->r7,
(int32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29));
}
extern "C" void osStartThread_recomp(uint8_t* rdram, recomp_context* ctx) {
osStartThread(rdram, (int32_t)ctx->r4);
}
extern "C" void osStopThread_recomp(uint8_t * rdram, recomp_context * ctx) {
osStopThread(rdram, (int32_t)ctx->r4);
}
extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) {
osDestroyThread(rdram, (int32_t)ctx->r4);
}
extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) {
osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5);
}
extern "C" void osGetThreadPri_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = osGetThreadPri(rdram, (int32_t)ctx->r4);
}
extern "C" void osGetThreadId_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = osGetThreadId(rdram, (int32_t)ctx->r4);
}
extern "C" void osCreateMesgQueue_recomp(uint8_t* rdram, recomp_context* ctx) {
osCreateMesgQueue(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
}
extern "C" void osRecvMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
ctx->r2 = osRecvMesg(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6);
}
extern "C" void osSendMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
ctx->r2 = osSendMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
}
extern "C" void osJamMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
ctx->r2 = osJamMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
}
extern "C" void osSetEventMesg_recomp(uint8_t* rdram, recomp_context* ctx) {
osSetEventMesg(rdram, (OSEvent)ctx->r4, (int32_t)ctx->r5, (OSMesg)ctx->r6);
}
extern "C" void osViSetEvent_recomp(uint8_t * rdram, recomp_context * ctx) {
osViSetEvent(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (u32)ctx->r6);
}
extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = osGetCount();
}
extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t total_count = osGetTime();
ctx->r2 = (int32_t)(total_count >> 32);
ctx->r3 = (int32_t)(total_count >> 0);
}
extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu);
uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10);
ctx->r2 = osSetTimer(rdram, (int32_t)ctx->r4, countdown, interval, (int32_t)MEM_W(0x18, ctx->r29), (OSMesg)MEM_W(0x1C, ctx->r29));
}
extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = osStopTimer(rdram, (int32_t)ctx->r4);
}
extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = osVirtualToPhysical((int32_t)ctx->r2);
}
extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
;
}
extern "C" void osInvalICache_recomp(uint8_t * rdram, recomp_context * ctx) {
;
}
extern "C" void osWritebackDCache_recomp(uint8_t * rdram, recomp_context * ctx) {
;
}
extern "C" void osWritebackDCacheAll_recomp(uint8_t * rdram, recomp_context * ctx) {
;
}
extern "C" void osSetIntMask_recomp(uint8_t * rdram, recomp_context * ctx) {
;
}
extern "C" void __osDisableInt_recomp(uint8_t * rdram, recomp_context * ctx) {
;
}
extern "C" void __osRestoreInt_recomp(uint8_t * rdram, recomp_context * ctx) {
;
}
extern "C" void __osSetFpcCsr_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 0;
}
// For the Mario Party games (not working)
//extern "C" void longjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
// RecompJmpBuf* buf = TO_PTR(RecompJmpBuf, ctx->r4);
//
// // Check if this is a buffer that was set up with setjmp
// if (buf->magic == SETJMP_MAGIC) {
// // If so, longjmp to it
// // Setjmp/longjmp does not work across threads, so verify that this buffer was made by this thread
// assert(buf->owner == Multilibultra::this_thread());
// longjmp(buf->storage->buffer, ctx->r5);
// } else {
// // Otherwise, check if it was one built manually by the game with $ra pointing to a function
// gpr sp = MEM_W(0, ctx->r4);
// gpr ra = MEM_W(4, ctx->r4);
// ctx->r29 = sp;
// recomp_func_t* target = LOOKUP_FUNC(ra);
// if (target == nullptr) {
// fprintf(stderr, "Failed to find function for manual longjmp\n");
// std::quick_exit(EXIT_FAILURE);
// }
// target(rdram, ctx);
//
// // TODO kill this thread if the target function returns
// assert(false);
// }
//}
//
//#undef setjmp_recomp
//extern "C" void setjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
// fprintf(stderr, "Program called setjmp_recomp\n");
// std::quick_exit(EXIT_FAILURE);
//}
//
//extern "C" int32_t osGetThreadEx(void) {
// return Multilibultra::this_thread();
//}

View file

@ -1,70 +0,0 @@
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
#include "euc-jp.h"
extern "C" void __checkHardware_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 0;
}
extern "C" void __checkHardware_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 0;
}
extern "C" void __checkHardware_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 0;
}
extern "C" void __osInitialize_msp_recomp(uint8_t * rdram, recomp_context * ctx) {
}
extern "C" void __osInitialize_kmc_recomp(uint8_t * rdram, recomp_context * ctx) {
}
extern "C" void __osInitialize_isv_recomp(uint8_t * rdram, recomp_context * ctx) {
}
extern "C" void isPrintfInit_recomp(uint8_t * rdram, recomp_context * ctx) {
}
extern "C" void __osRdbSend_recomp(uint8_t * rdram, recomp_context * ctx) {
gpr buf = ctx->r4;
size_t size = ctx->r5;
u32 type = (u32)ctx->r6;
std::unique_ptr<char[]> to_print = std::make_unique<char[]>(size + 1);
for (size_t i = 0; i < size; i++) {
to_print[i] = MEM_B(i, buf);
}
to_print[size] = '\x00';
fwrite(to_print.get(), 1, size, stdout);
ctx->r2 = size;
}
extern "C" void is_proutSyncPrintf_recomp(uint8_t * rdram, recomp_context * ctx) {
// Buffering to speed up print performance
static std::vector<char> print_buffer;
gpr buf = ctx->r5;
size_t size = ctx->r6;
for (size_t i = 0; i < size; i++) {
// Add the new character to the buffer
char cur_char = MEM_B(i, buf);
// If the new character is a newline, flush the buffer
if (cur_char == '\n') {
std::string utf8_str = Encoding::decode_eucjp(std::string_view{ print_buffer.data(), print_buffer.size() });
puts(utf8_str.c_str());
print_buffer.clear();
} else {
print_buffer.push_back(cur_char);
}
}
//fwrite(to_print.get(), size, 1, stdout);
ctx->r2 = 1;
}

View file

@ -1,165 +0,0 @@
#ifdef _WIN32
#include <Windows.h>
#endif
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <cmath>
#include <unordered_map>
#include <fstream>
#include <iostream>
#include "recomp.h"
#include "../portultra/multilibultra.hpp"
#ifdef _MSC_VER
inline uint32_t byteswap(uint32_t val) {
return _byteswap_ulong(val);
}
#else
constexpr uint32_t byteswap(uint32_t val) {
return __builtin_bswap32(val);
}
#endif
extern "C" void _bzero(uint8_t* rdram, recomp_context* ctx) {
gpr start_addr = ctx->r4;
gpr size = ctx->r5;
for (uint32_t i = 0; i < size; i++) {
MEM_B(start_addr, i) = 0;
}
}
extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 8 * 1024 * 1024;
}
extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
exit(EXIT_FAILURE);
}
extern "C" void do_break(uint32_t vram) {
printf("Encountered break at original vram 0x%08X\n", vram);
exit(EXIT_FAILURE);
}
void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg) {
recomp_context ctx{};
ctx.r29 = sp;
ctx.r4 = arg;
recomp_func_t* func = get_function(addr);
func(rdram, &ctx);
}
void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t dev_address, size_t num_bytes);
std::unique_ptr<uint8_t[]> rom;
size_t rom_size;
// Recomp generation functions
extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx);
gpr get_entrypoint_address();
const char* get_rom_name();
void init_overlays();
extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size);
extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
#ifdef _WIN32
#include <Windows.h>
#endif
int main(int argc, char **argv) {
//if (argc != 2) {
// printf("Usage: %s [baserom]\n", argv[0]);
// exit(EXIT_SUCCESS);
//}
#ifdef _WIN32
// Set up console output to accept UTF-8 on windows
SetConsoleOutputCP(CP_UTF8);
// Change to a font that supports Japanese characters
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof cfi;
cfi.nFont = 0;
cfi.dwFontSize.X = 0;
cfi.dwFontSize.Y = 16;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
wcscpy_s(cfi.FaceName, L"NSimSun");
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
#else
std::setlocale(LC_ALL, "en_US.UTF-8");
#endif
{
std::basic_ifstream<uint8_t> rom_file{ get_rom_name(), std::ios::binary };
size_t iobuf_size = 0x100000;
std::unique_ptr<uint8_t[]> iobuf = std::make_unique<uint8_t[]>(iobuf_size);
rom_file.rdbuf()->pubsetbuf(iobuf.get(), iobuf_size);
if (!rom_file) {
fprintf(stderr, "Failed to open rom: %s\n", get_rom_name());
exit(EXIT_FAILURE);
}
rom_file.seekg(0, std::ios::end);
rom_size = rom_file.tellg();
rom_file.seekg(0, std::ios::beg);
rom = std::make_unique<uint8_t[]>(rom_size);
rom_file.read(rom.get(), rom_size);
// TODO remove this
// Modify the name in the rom header so RT64 doesn't find it
rom[0x2F] = 'O';
}
// Initialize the overlays
init_overlays();
// Get entrypoint from recomp function
gpr entrypoint = get_entrypoint_address();
// Load overlays in the first 1MB
load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024);
// Allocate rdram_buffer (16MB to give room for any extra addressable data used by recomp)
std::unique_ptr<uint8_t[]> rdram_buffer = std::make_unique<uint8_t[]>(16 * 1024 * 1024);
std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024);
recomp_context context{};
// Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000)
do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000);
// Set up stack pointer
context.r29 = 0xFFFFFFFF803FFFF0u;
// Initialize variables normally set by IPL3
constexpr int32_t osTvType = 0x80000300;
constexpr int32_t osRomType = 0x80000304;
constexpr int32_t osRomBase = 0x80000308;
constexpr int32_t osResetType = 0x8000030c;
constexpr int32_t osCicId = 0x80000310;
constexpr int32_t osVersion = 0x80000314;
constexpr int32_t osMemSize = 0x80000318;
constexpr int32_t osAppNMIBuffer = 0x8000031c;
uint8_t *rdram = rdram_buffer.get();
MEM_W(osTvType, 0) = 1; // NTSC
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
MEM_W(osResetType, 0) = 0; // cold reset
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
debug_printf("[Recomp] Starting\n");
Multilibultra::preinit(rdram_buffer.get(), rom.get());
recomp_entrypoint(rdram_buffer.get(), &context);
debug_printf("[Recomp] Quitting\n");
return EXIT_SUCCESS;
}

View file

@ -1,65 +0,0 @@
#ifndef __RSP_H__
#define __RSP_H__
#include "rsp_vu.h"
#include "recomp.h"
enum class RspExitReason {
Invalid,
Broke,
ImemOverrun,
UnhandledJumpTarget
};
extern uint8_t dmem[];
extern uint16_t rspReciprocals[512];
extern uint16_t rspInverseSquareRoots[512];
#define RSP_MEM_W(offset, addr) \
(*reinterpret_cast<uint32_t*>(dmem + (offset) + (addr)))
#define RSP_MEM_H(offset, addr) \
(*reinterpret_cast<int16_t*>(dmem + (((offset) + (addr)) ^ 2)))
#define RSP_MEM_HU(offset, addr) \
(*reinterpret_cast<uint16_t*>(dmem + (((offset) + (addr)) ^ 2)))
#define RSP_MEM_B(offset, addr) \
(*reinterpret_cast<int8_t*>(dmem + (((offset) + (addr)) ^ 3)))
#define RSP_MEM_BU(offset, addr) \
(*reinterpret_cast<uint8_t*>(dmem + (((offset) + (addr)) ^ 3)))
#define RSP_ADD32(a, b) \
((int32_t)((a) + (b)))
#define RSP_SUB32(a, b) \
((int32_t)((a) - (b)))
#define RSP_SIGNED(val) \
((int32_t)(val))
#define SET_DMA_DMEM(dmem_addr) dma_dmem_address = (dmem_addr)
#define SET_DMA_DRAM(dram_addr) dma_dram_address = (dram_addr)
#define DO_DMA_READ(rd_len) dma_rdram_to_dmem(rdram, dma_dmem_address, dma_dram_address, (rd_len))
#define DO_DMA_WRITE(wr_len) dma_dmem_to_rdram(rdram, dma_dmem_address, dma_dram_address, (wr_len))
static inline void dma_rdram_to_dmem(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t rd_len) {
rd_len += 1; // Read length is inclusive
dram_addr &= 0xFFFFF8;
assert(dmem_addr + rd_len <= 0x1000);
for (uint32_t i = 0; i < rd_len; i++) {
RSP_MEM_B(i, dmem_addr) = MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000));
}
}
static inline void dma_dmem_to_rdram(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t wr_len) {
wr_len += 1; // Write length is inclusive
dram_addr &= 0xFFFFF8;
assert(dmem_addr + wr_len <= 0x1000);
for (uint32_t i = 0; i < wr_len; i++) {
MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)) = RSP_MEM_B(i, dmem_addr);
}
}
#endif

View file

@ -1,199 +0,0 @@
// This file is modified from the Ares N64 emulator core. Ares can
// be found at https://github.com/ares-emulator/ares. The original license
// for this portion of Ares is as follows:
// ----------------------------------------------------------------------
// ares
//
// Copyright(c) 2004 - 2021 ares team, Near et al
//
// Permission to use, copy, modify, and /or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright noticeand this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// ----------------------------------------------------------------------
#include <cstdint>
#define ARCHITECTURE_AMD64
#define ARCHITECTURE_SUPPORTS_SSE4_1 1
#if defined(ARCHITECTURE_AMD64)
#include <nmmintrin.h>
using v128 = __m128i;
#elif defined(ARCHITECTURE_ARM64)
#include <sse2neon.h>
using v128 = __m128i;
#endif
namespace Accuracy {
namespace RSP {
#if ARCHITECTURE_SUPPORTS_SSE4_1
constexpr bool SISD = false;
constexpr bool SIMD = true;
#else
constexpr bool SISD = true;
constexpr bool SIMD = false;
#endif
}
}
using u8 = uint8_t;
using s8 = int8_t;
using u16 = uint16_t;
using s16 = int16_t;
using u32 = uint32_t;
using s32 = int32_t;
using u64 = uint64_t;
using s64 = int64_t;
using uint128_t = uint64_t[2];
template<u32 bits> inline auto sclamp(s64 x) -> s64 {
enum : s64 { b = 1ull << (bits - 1), m = b - 1 };
return (x > m) ? m : (x < -b) ? -b : x;
}
struct RSP {
using r32 = uint32_t;
using cr32 = const r32;
union r128 {
struct { uint64_t u128[2]; };
#if ARCHITECTURE_SUPPORTS_SSE4_1
struct { __m128i v128; };
operator __m128i() const { return v128; }
auto operator=(__m128i value) { v128 = value; }
#endif
auto byte(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; }
auto byte(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; }
auto element(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; }
auto element(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; }
auto u8(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; }
auto u8(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; }
auto s16(u32 index) -> int16_t& { return ((int16_t*)&u128)[7 - index]; }
auto s16(u32 index) const -> int16_t { return ((int16_t*)&u128)[7 - index]; }
auto u16(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; }
auto u16(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; }
//VCx registers
auto get(u32 index) const -> bool { return u16(index) != 0; }
auto set(u32 index, bool value) -> bool { return u16(index) = 0 - value, value; }
//vu-registers.cpp
inline auto operator()(u32 index) const -> r128;
};
using cr128 = const r128;
struct VU {
r128 r[32];
r128 acch, accm, accl;
r128 vcoh, vcol; //16-bit little endian
r128 vcch, vccl; //16-bit little endian
r128 vce; // 8-bit little endian
s16 divin;
s16 divout;
bool divdp;
} vpu;
static constexpr r128 zero{0};
static constexpr r128 invert{(uint64_t)-1, (uint64_t)-1};
inline auto accumulatorGet(u32 index) const -> u64;
inline auto accumulatorSet(u32 index, u64 value) -> void;
inline auto accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16;
inline auto CFC2(r32& rt, u8 rd) -> void;
inline auto CTC2(cr32& rt, u8 rd) -> void;
template<u8 e> inline auto LBV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LDV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LFV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LHV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LLV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LPV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LQV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LRV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LSV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LTV(u8 vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LUV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto LWV(r128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto MFC2(r32& rt, cr128& vs) -> void;
template<u8 e> inline auto MTC2(cr32& rt, r128& vs) -> void;
template<u8 e> inline auto SBV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SDV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SFV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SHV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SLV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SPV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SQV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SRV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SSV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto STV(u8 vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SUV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto SWV(cr128& vt, cr32& rs, s8 imm) -> void;
template<u8 e> inline auto VABS(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VADD(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VADDC(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VAND(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VCH(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VCL(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VCR(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VEQ(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VGE(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VLT(r128& vd, cr128& vs, cr128& vt) -> void;
template<bool U, u8 e>
inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<0, e>(vd, vs, vt); }
template<u8 e> inline auto VMACU(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<1, e>(vd, vs, vt); }
inline auto VMACQ(r128& vd) -> void;
template<u8 e> inline auto VMADH(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMADL(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMADM(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMADN(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMOV(r128& vd, u8 de, cr128& vt) -> void;
template<u8 e> inline auto VMRG(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMUDH(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMUDL(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMUDM(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMUDN(r128& vd, cr128& vs, cr128& vt) -> void;
template<bool U, u8 e>
inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<0, e>(rd, vs, vt); }
template<u8 e> inline auto VMULU(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<1, e>(rd, vs, vt); }
template<u8 e> inline auto VMULQ(r128& rd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VNAND(r128& rd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VNE(r128& vd, cr128& vs, cr128& vt) -> void;
inline auto VNOP() -> void;
template<u8 e> inline auto VNOR(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VNXOR(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VOR(r128& vd, cr128& vs, cr128& vt) -> void;
template<bool L, u8 e>
inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void;
template<u8 e> inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void { VRCP<0, e>(vd, de, vt); }
template<u8 e> inline auto VRCPL(r128& vd, u8 de, cr128& vt) -> void { VRCP<1, e>(vd, de, vt); }
template<u8 e> inline auto VRCPH(r128& vd, u8 de, cr128& vt) -> void;
template<bool D, u8 e>
inline auto VRND(r128& vd, u8 vs, cr128& vt) -> void;
template<u8 e> inline auto VRNDN(r128& vd, u8 vs, cr128& vt) -> void { VRND<0, e>(vd, vs, vt); }
template<u8 e> inline auto VRNDP(r128& vd, u8 vs, cr128& vt) -> void { VRND<1, e>(vd, vs, vt); }
template<bool L, u8 e>
inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void;
template<u8 e> inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void { VRSQ<0, e>(vd, de, vt); }
template<u8 e> inline auto VRSQL(r128& vd, u8 de, cr128& vt) -> void { VRSQ<1, e>(vd, de, vt); }
template<u8 e> inline auto VRSQH(r128& vd, u8 de, cr128& vt) -> void;
template<u8 e> inline auto VSAR(r128& vd, cr128& vs) -> void;
template<u8 e> inline auto VSUB(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VSUBC(r128& vd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VXOR(r128& rd, cr128& vs, cr128& vt) -> void;
template<u8 e> inline auto VZERO(r128& rd, cr128& vs, cr128& vt) -> void;
};

File diff suppressed because it is too large Load diff

View file

@ -1,49 +0,0 @@
#include <cstdio>
#include <fstream>
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) {
// Nothing to do here
}
bool dump_frame = false;
extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) {
//printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
OSTask* task = TO_PTR(OSTask, ctx->r4);
if (task->t.type == M_GFXTASK) {
//printf("[sp] Gfx task: %08X\n", (uint32_t)ctx->r4);
} else if (task->t.type == M_AUDTASK) {
//printf("[sp] Audio task: %08X\n", (uint32_t)ctx->r4);
}
// For debugging
if (dump_frame) {
char addr_str[32];
constexpr size_t ram_size = 0x800000;
std::unique_ptr<char[]> ram_unswapped = std::make_unique<char[]>(ram_size);
sprintf(addr_str, "%08X", task->t.data_ptr);
std::ofstream dump_file{ "../../ramdump" + std::string{ addr_str } + ".bin", std::ios::binary};
for (size_t i = 0; i < ram_size; i++) {
ram_unswapped[i] = rdram[i ^ 3];
}
dump_file.write(ram_unswapped.get(), ram_size);
dump_frame = false;
}
Multilibultra::submit_rsp_task(rdram, ctx->r4);
}
extern "C" void osSpTaskYield_recomp(uint8_t* rdram, recomp_context* ctx) {
// Ignore yield requests (acts as if the task completed before it received the yield request)
}
extern "C" void osSpTaskYielded_recomp(uint8_t* rdram, recomp_context* ctx) {
// Task yield requests are ignored, so always return 0 as tasks will never be yielded
ctx->r2 = 0;
}
extern "C" void __osSpSetPc_recomp(uint8_t* rdram, recomp_context* ctx) {
assert(false);
}

View file

@ -1,38 +0,0 @@
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
extern "C" void osViSetYScale_recomp(uint8_t* rdram, recomp_context * ctx) {
;
}
extern "C" void osViSetXScale_recomp(uint8_t* rdram, recomp_context * ctx) {
;
}
extern "C" void osCreateViManager_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osViBlack_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osViSetSpecialFeatures_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}
extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
ctx->r2 = (gpr)(int32_t)osViGetCurrentFramebuffer();
}
extern "C" void osViGetNextFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
ctx->r2 = (gpr)(int32_t)osViGetNextFramebuffer();
}
extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
osViSwapBuffer(rdram, (int32_t)ctx->r4);
}
extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) {
;
}

View file

@ -1,582 +0,0 @@
// Provides an efficient blocking version of moodycamel::ConcurrentQueue.
// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified
// BSD license, available at the top of concurrentqueue.h.
// Also dual-licensed under the Boost Software License (see LICENSE.md)
// Uses Jeff Preshing's semaphore implementation (under the terms of its
// separate zlib license, see lightweightsemaphore.h).
#pragma once
#include "concurrentqueue.h"
#include "lightweightsemaphore.h"
#include <type_traits>
#include <cerrno>
#include <memory>
#include <chrono>
#include <ctime>
namespace moodycamel
{
// This is a blocking version of the queue. It has an almost identical interface to
// the normal non-blocking version, with the addition of various wait_dequeue() methods
// and the removal of producer-specific dequeue methods.
template<typename T, typename Traits = ConcurrentQueueDefaultTraits>
class BlockingConcurrentQueue
{
private:
typedef ::moodycamel::ConcurrentQueue<T, Traits> ConcurrentQueue;
typedef ::moodycamel::LightweightSemaphore LightweightSemaphore;
public:
typedef typename ConcurrentQueue::producer_token_t producer_token_t;
typedef typename ConcurrentQueue::consumer_token_t consumer_token_t;
typedef typename ConcurrentQueue::index_t index_t;
typedef typename ConcurrentQueue::size_t size_t;
typedef typename std::make_signed<size_t>::type ssize_t;
static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE;
static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD;
static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE;
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE;
static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE;
static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE;
public:
// Creates a queue with at least `capacity` element slots; note that the
// actual number of elements that can be inserted without additional memory
// allocation depends on the number of producers and the block size (e.g. if
// the block size is equal to `capacity`, only a single block will be allocated
// up-front, which means only a single producer will be able to enqueue elements
// without an extra allocation -- blocks aren't shared between producers).
// This method is not thread safe -- it is up to the user to ensure that the
// queue is fully constructed before it starts being used by other threads (this
// includes making the memory effects of construction visible, possibly with a
// memory barrier).
explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE)
: inner(capacity), sema(create<LightweightSemaphore, ssize_t, int>(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
{
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
if (!sema) {
MOODYCAMEL_THROW(std::bad_alloc());
}
}
BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers)
: inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create<LightweightSemaphore, ssize_t, int>(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
{
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
if (!sema) {
MOODYCAMEL_THROW(std::bad_alloc());
}
}
// Disable copying and copy assignment
BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
// Moving is supported, but note that it is *not* a thread-safe operation.
// Nobody can use the queue while it's being moved, and the memory effects
// of that move must be propagated to other threads before they can use it.
// Note: When a queue is moved, its tokens are still valid but can only be
// used with the destination queue (i.e. semantically they are moved along
// with the queue itself).
BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
: inner(std::move(other.inner)), sema(std::move(other.sema))
{ }
inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
{
return swap_internal(other);
}
// Swaps this queue's state with the other's. Not thread-safe.
// Swapping two queues does not invalidate their tokens, however
// the tokens that were created for one queue must be used with
// only the swapped queue (i.e. the tokens are tied to the
// queue's movable state, not the object itself).
inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT
{
swap_internal(other);
}
private:
BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other)
{
if (this == &other) {
return *this;
}
inner.swap(other.inner);
sema.swap(other.sema);
return *this;
}
public:
// Enqueues a single item (by copying it).
// Allocates memory if required. Only fails if memory allocation fails (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(T const& item)
{
if ((details::likely)(inner.enqueue(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible).
// Allocates memory if required. Only fails if memory allocation fails (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(T&& item)
{
if ((details::likely)(inner.enqueue(std::move(item)))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by copying it) using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails (or
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(producer_token_t const& token, T const& item)
{
if ((details::likely)(inner.enqueue(token, item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails (or
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(producer_token_t const& token, T&& item)
{
if ((details::likely)(inner.enqueue(token, std::move(item)))) {
sema->signal();
return true;
}
return false;
}
// Enqueues several items.
// Allocates memory if required. Only fails if memory allocation fails (or
// implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
// is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Note: Use std::make_move_iterator if the elements should be moved instead of copied.
// Thread-safe.
template<typename It>
inline bool enqueue_bulk(It itemFirst, size_t count)
{
if ((details::likely)(inner.enqueue_bulk(std::forward<It>(itemFirst), count))) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues several items using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails
// (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
{
if ((details::likely)(inner.enqueue_bulk(token, std::forward<It>(itemFirst), count))) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues a single item (by copying it).
// Does not allocate memory. Fails if not enough room to enqueue (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
// is 0).
// Thread-safe.
inline bool try_enqueue(T const& item)
{
if (inner.try_enqueue(item)) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible).
// Does not allocate memory (except for one-time implicit producer).
// Fails if not enough room to enqueue (or implicit production is
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
// Thread-safe.
inline bool try_enqueue(T&& item)
{
if (inner.try_enqueue(std::move(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by copying it) using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Thread-safe.
inline bool try_enqueue(producer_token_t const& token, T const& item)
{
if (inner.try_enqueue(token, item)) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Thread-safe.
inline bool try_enqueue(producer_token_t const& token, T&& item)
{
if (inner.try_enqueue(token, std::move(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues several items.
// Does not allocate memory (except for one-time implicit producer).
// Fails if not enough room to enqueue (or implicit production is
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool try_enqueue_bulk(It itemFirst, size_t count)
{
if (inner.try_enqueue_bulk(std::forward<It>(itemFirst), count)) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues several items using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
{
if (inner.try_enqueue_bulk(token, std::forward<It>(itemFirst), count)) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Attempts to dequeue from the queue.
// Returns false if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename U>
inline bool try_dequeue(U& item)
{
if (sema->tryWait()) {
while (!inner.try_dequeue(item)) {
continue;
}
return true;
}
return false;
}
// Attempts to dequeue from the queue using an explicit consumer token.
// Returns false if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename U>
inline bool try_dequeue(consumer_token_t& token, U& item)
{
if (sema->tryWait()) {
while (!inner.try_dequeue(token, item)) {
continue;
}
return true;
}
return false;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued.
// Returns 0 if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename It>
inline size_t try_dequeue_bulk(It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued.
// Returns 0 if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename It>
inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Blocks the current thread until there's something to dequeue, then
// dequeues it.
// Never allocates. Thread-safe.
template<typename U>
inline void wait_dequeue(U& item)
{
while (!sema->wait()) {
continue;
}
while (!inner.try_dequeue(item)) {
continue;
}
}
// Blocks the current thread until either there's something to dequeue
// or the timeout (specified in microseconds) expires. Returns false
// without setting `item` if the timeout expires, otherwise assigns
// to `item` and returns true.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue.
// Never allocates. Thread-safe.
template<typename U>
inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
{
if (!sema->wait(timeout_usecs)) {
return false;
}
while (!inner.try_dequeue(item)) {
continue;
}
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Never allocates. Thread-safe.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Blocks the current thread until there's something to dequeue, then
// dequeues it using an explicit consumer token.
// Never allocates. Thread-safe.
template<typename U>
inline void wait_dequeue(consumer_token_t& token, U& item)
{
while (!sema->wait()) {
continue;
}
while (!inner.try_dequeue(token, item)) {
continue;
}
}
// Blocks the current thread until either there's something to dequeue
// or the timeout (specified in microseconds) expires. Returns false
// without setting `item` if the timeout expires, otherwise assigns
// to `item` and returns true.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue.
// Never allocates. Thread-safe.
template<typename U>
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs)
{
if (!sema->wait(timeout_usecs)) {
return false;
}
while (!inner.try_dequeue(token, item)) {
continue;
}
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Never allocates. Thread-safe.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(token, item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which will
// always be at least one (this method blocks until the queue
// is non-empty) and at most max.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk(It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue_bulk.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Never allocates. Thread-safe.
template<typename It, typename Rep, typename Period>
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_bulk_timed<It&>(itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which will
// always be at least one (this method blocks until the queue
// is non-empty) and at most max.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue_bulk.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Never allocates. Thread-safe.
template<typename It, typename Rep, typename Period>
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_bulk_timed<It&>(token, itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Returns an estimate of the total number of elements currently in the queue. This
// estimate is only accurate if the queue has completely stabilized before it is called
// (i.e. all enqueue and dequeue operations have completed and their memory effects are
// visible on the calling thread, and no further operations start while this method is
// being called).
// Thread-safe.
inline size_t size_approx() const
{
return (size_t)sema->availableApprox();
}
// Returns true if the underlying atomic variables used by
// the queue are lock-free (they should be on most platforms).
// Thread-safe.
static constexpr bool is_lock_free()
{
return ConcurrentQueue::is_lock_free();
}
private:
template<typename U, typename A1, typename A2>
static inline U* create(A1&& a1, A2&& a2)
{
void* p = (Traits::malloc)(sizeof(U));
return p != nullptr ? new (p) U(std::forward<A1>(a1), std::forward<A2>(a2)) : nullptr;
}
template<typename U>
static inline void destroy(U* p)
{
if (p != nullptr) {
p->~U();
}
(Traits::free)(p);
}
private:
ConcurrentQueue inner;
std::unique_ptr<LightweightSemaphore, void (*)(LightweightSemaphore*)> sema;
};
template<typename T, typename Traits>
inline void swap(BlockingConcurrentQueue<T, Traits>& a, BlockingConcurrentQueue<T, Traits>& b) MOODYCAMEL_NOEXCEPT
{
a.swap(b);
}
} // end namespace moodycamel

File diff suppressed because it is too large Load diff

View file

@ -1,425 +0,0 @@
// Provides an efficient implementation of a semaphore (LightweightSemaphore).
// This is an extension of Jeff Preshing's sempahore implementation (licensed
// under the terms of its separate zlib license) that has been adapted and
// extended by Cameron Desrochers.
#pragma once
#include <cstddef> // For std::size_t
#include <atomic>
#include <type_traits> // For std::make_signed<T>
#if defined(_WIN32)
// Avoid including windows.h in a header; we only need a handful of
// items, so we'll redeclare them here (this is relatively safe since
// the API generally has to remain stable between Windows versions).
// I know this is an ugly hack but it still beats polluting the global
// namespace with thousands of generic names or adding a .cpp for nothing.
extern "C" {
struct _SECURITY_ATTRIBUTES;
__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);
__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
}
#elif defined(__MACH__)
#include <mach/mach.h>
#elif defined(__unix__)
#include <semaphore.h>
#if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE)
#if __GLIBC_PREREQ(2,30)
#define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
#endif
#endif
#endif
namespace moodycamel
{
namespace details
{
// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's
// portable + lightweight semaphore implementations, originally from
// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
// LICENSE:
// Copyright (c) 2015 Jeff Preshing
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgement in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
#if defined(_WIN32)
class Semaphore
{
private:
void* m_hSema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
const long maxLong = 0x7fffffff;
m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
assert(m_hSema);
}
~Semaphore()
{
CloseHandle(m_hSema);
}
bool wait()
{
const unsigned long infinite = 0xffffffff;
return WaitForSingleObject(m_hSema, infinite) == 0;
}
bool try_wait()
{
return WaitForSingleObject(m_hSema, 0) == 0;
}
bool timed_wait(std::uint64_t usecs)
{
return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;
}
void signal(int count = 1)
{
while (!ReleaseSemaphore(m_hSema, count, nullptr));
}
};
#elif defined(__MACH__)
//---------------------------------------------------------
// Semaphore (Apple iOS and OSX)
// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
//---------------------------------------------------------
class Semaphore
{
private:
semaphore_t m_sema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
assert(rc == KERN_SUCCESS);
(void)rc;
}
~Semaphore()
{
semaphore_destroy(mach_task_self(), m_sema);
}
bool wait()
{
return semaphore_wait(m_sema) == KERN_SUCCESS;
}
bool try_wait()
{
return timed_wait(0);
}
bool timed_wait(std::uint64_t timeout_usecs)
{
mach_timespec_t ts;
ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);
// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
kern_return_t rc = semaphore_timedwait(m_sema, ts);
return rc == KERN_SUCCESS;
}
void signal()
{
while (semaphore_signal(m_sema) != KERN_SUCCESS);
}
void signal(int count)
{
while (count-- > 0)
{
while (semaphore_signal(m_sema) != KERN_SUCCESS);
}
}
};
#elif defined(__unix__)
//---------------------------------------------------------
// Semaphore (POSIX, Linux)
//---------------------------------------------------------
class Semaphore
{
private:
sem_t m_sema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));
assert(rc == 0);
(void)rc;
}
~Semaphore()
{
sem_destroy(&m_sema);
}
bool wait()
{
// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
int rc;
do {
rc = sem_wait(&m_sema);
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
bool try_wait()
{
int rc;
do {
rc = sem_trywait(&m_sema);
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
bool timed_wait(std::uint64_t usecs)
{
struct timespec ts;
const int usecs_in_1_sec = 1000000;
const int nsecs_in_1_sec = 1000000000;
#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &ts);
#else
clock_gettime(CLOCK_REALTIME, &ts);
#endif
ts.tv_sec += (time_t)(usecs / usecs_in_1_sec);
ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000;
// sem_timedwait bombs if you have more than 1e9 in tv_nsec
// so we have to clean things up before passing it in
if (ts.tv_nsec >= nsecs_in_1_sec) {
ts.tv_nsec -= nsecs_in_1_sec;
++ts.tv_sec;
}
int rc;
do {
#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts);
#else
rc = sem_timedwait(&m_sema, &ts);
#endif
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
void signal()
{
while (sem_post(&m_sema) == -1);
}
void signal(int count)
{
while (count-- > 0)
{
while (sem_post(&m_sema) == -1);
}
}
};
#else
#error Unsupported platform! (No semaphore wrapper available)
#endif
} // end namespace details
//---------------------------------------------------------
// LightweightSemaphore
//---------------------------------------------------------
class LightweightSemaphore
{
public:
typedef std::make_signed<std::size_t>::type ssize_t;
private:
std::atomic<ssize_t> m_count;
details::Semaphore m_sema;
int m_maxSpins;
bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1)
{
ssize_t oldCount;
int spin = m_maxSpins;
while (--spin >= 0)
{
oldCount = m_count.load(std::memory_order_relaxed);
if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
return true;
std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop.
}
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
if (oldCount > 0)
return true;
if (timeout_usecs < 0)
{
if (m_sema.wait())
return true;
}
if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs))
return true;
// At this point, we've timed out waiting for the semaphore, but the
// count is still decremented indicating we may still be waiting on
// it. So we have to re-adjust the count, but only if the semaphore
// wasn't signaled enough times for us too since then. If it was, we
// need to release the semaphore too.
while (true)
{
oldCount = m_count.load(std::memory_order_acquire);
if (oldCount >= 0 && m_sema.try_wait())
return true;
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
return false;
}
}
ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1)
{
assert(max > 0);
ssize_t oldCount;
int spin = m_maxSpins;
while (--spin >= 0)
{
oldCount = m_count.load(std::memory_order_relaxed);
if (oldCount > 0)
{
ssize_t newCount = oldCount > max ? oldCount - max : 0;
if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
return oldCount - newCount;
}
std::atomic_signal_fence(std::memory_order_acquire);
}
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
if (oldCount <= 0)
{
if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs)))
{
while (true)
{
oldCount = m_count.load(std::memory_order_acquire);
if (oldCount >= 0 && m_sema.try_wait())
break;
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
return 0;
}
}
}
if (max > 1)
return 1 + tryWaitMany(max - 1);
return 1;
}
public:
LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins)
{
assert(initialCount >= 0);
assert(maxSpins >= 0);
}
bool tryWait()
{
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
while (oldCount > 0)
{
if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
return true;
}
return false;
}
bool wait()
{
return tryWait() || waitWithPartialSpinning();
}
bool wait(std::int64_t timeout_usecs)
{
return tryWait() || waitWithPartialSpinning(timeout_usecs);
}
// Acquires between 0 and (greedily) max, inclusive
ssize_t tryWaitMany(ssize_t max)
{
assert(max >= 0);
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
while (oldCount > 0)
{
ssize_t newCount = oldCount > max ? oldCount - max : 0;
if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
return oldCount - newCount;
}
return 0;
}
// Acquires at least one, and (greedily) at most max
ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs)
{
assert(max >= 0);
ssize_t result = tryWaitMany(max);
if (result == 0 && max > 0)
result = waitManyWithPartialSpinning(max, timeout_usecs);
return result;
}
ssize_t waitMany(ssize_t max)
{
ssize_t result = waitMany(max, -1);
assert(result > 0);
return result;
}
void signal(ssize_t count = 1)
{
assert(count >= 0);
ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release);
ssize_t toRelease = -oldCount < count ? -oldCount : count;
if (toRelease > 0)
{
m_sema.signal((int)toRelease);
}
}
std::size_t availableApprox() const
{
ssize_t count = m_count.load(std::memory_order_relaxed);
return count > 0 ? static_cast<std::size_t>(count) : 0;
}
};
} // end namespace moodycamel