Added recomp runtime library and portultra, MM initial boot

This commit is contained in:
Mr-Wiseguy 2023-02-19 22:27:35 -05:00
parent 7847975e57
commit ba37150ed1
46 changed files with 29616 additions and 27 deletions

5
.gitignore vendored
View File

@ -42,10 +42,7 @@ bld/
.vs/ .vs/
# Libraries (binaries that aren't in the repo) # Libraries (binaries that aren't in the repo)
Lib lib/
# RT64 (since it's not public yet)
RT64
# Runtime files # Runtime files
imgui.ini imgui.ini

View File

@ -17,7 +17,6 @@
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
<Platform>x64</Platform> <Platform>x64</Platform>
</ProjectConfiguration> </ProjectConfiguration>
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion> <VCProjectVersion>16.0</VCProjectVersion>
@ -53,11 +52,10 @@
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings"> <ImportGroup Label="ExtensionSettings">
</ImportGroup> </ImportGroup>
<ImportGroup Label="Shared" > <ImportGroup Label="Shared">
</ImportGroup> </ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <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" /> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
@ -71,7 +69,6 @@
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <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" /> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup> </ImportGroup>
<PropertyGroup Label="UserMacros" /> <PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental> <LinkIncremental>true</LinkIncremental>
@ -85,18 +82,24 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental> <LinkIncremental>false</LinkIncremental>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile> <ClCompile>
<WarningLevel>Level3</WarningLevel> <WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
<PostBuildEvent>
<Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y
XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile> <ClCompile>
@ -106,13 +109,20 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
<PostBuildEvent>
<Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y
XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile> <ClCompile>
@ -120,11 +130,18 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
<PostBuildEvent>
<Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y
XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile> <ClCompile>
@ -134,16 +151,82 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link> </Link>
<PostBuildEvent>
<Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y
XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command>
</PostBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup>
<ItemGroup></ItemGroup> <ClCompile Include="portultra\audio.cpp" />
<ClCompile Include="portultra\events.cpp" />
<ClCompile Include="portultra\mesgqueue.cpp" />
<ClCompile Include="portultra\misc_ultra.cpp" />
<ClCompile Include="portultra\port_main.c" />
<ClCompile Include="portultra\scheduler.cpp" />
<ClCompile Include="portultra\task_pthreads.cpp" />
<ClCompile Include="portultra\task_win32.cpp" />
<ClCompile Include="portultra\threads.cpp" />
<ClCompile Include="portultra\timer.cpp" />
<ClCompile Include="portultra\ultrainit.cpp" />
<ClCompile Include="RecompiledFuncs\lookup.cpp" />
<ClCompile Include="rsp\aspMain.cpp">
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization>
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Default</BasicRuntimeChecks>
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks>
</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|Win32'">Default</BasicRuntimeChecks>
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks>
</ClCompile>
<ClCompile Include="src\ai.cpp" />
<ClCompile Include="src\cont.cpp" />
<ClCompile Include="src\dp.cpp" />
<ClCompile Include="src\eep.cpp" />
<ClCompile Include="src\euc-jp.cpp" />
<ClCompile Include="src\flash.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_stubs.cpp" />
<ClCompile Include="src\portultra_translation.cpp" />
<ClCompile Include="src\print.cpp" />
<ClCompile Include="src\recomp.cpp" />
<ClCompile Include="src\rt64_layer.cpp" />
<ClCompile Include="src\sp.cpp" />
<ClCompile Include="src\vi.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\disable_warnings.h" />
<ClInclude Include="include\recomp.h" />
<ClInclude Include="include\rsp.h" />
<ClInclude Include="include\rsp_vu.h" />
<ClInclude Include="include\rsp_vu_impl.h" />
<ClInclude Include="include\rt64_layer.h" />
<ClInclude Include="include\sections.h" />
<ClInclude Include="portultra\multilibultra.hpp" />
<ClInclude Include="portultra\platform_specific.h" />
<ClInclude Include="portultra\ultra64.h" />
<ClInclude Include="src\euc-jp.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="RecompiledFuncs.vcxproj">
<Project>{7bf5e3f9-c49f-4c84-ab64-7681f028cac2}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>

View File

@ -14,4 +14,134 @@
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter> </Filter>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ClCompile Include="src\ai.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\cont.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\dp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\eep.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\euc-jp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\math_routines.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\overlays.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\pak.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\pi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\portultra_translation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\print.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\recomp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\sp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\vi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\audio.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\events.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\mesgqueue.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\misc_ultra.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\port_main.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\scheduler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\task_pthreads.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\task_win32.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\threads.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\timer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="portultra\ultrainit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\rt64_layer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\portultra_stubs.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\flash.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="RecompiledFuncs\lookup.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="rsp\njpgdspMain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="rsp\aspMain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\euc-jp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\disable_warnings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\recomp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\rsp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\rsp_vu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\rsp_vu_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="portultra\multilibultra.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="portultra\platform_specific.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="portultra\ultra64.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\rt64_layer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\sections.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project> </Project>

View File

@ -113,8 +113,11 @@
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader> <PrecompiledHeader>NotUsing</PrecompiledHeader>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration> <EnableParallelCodeGeneration>
</EnableParallelCodeGeneration>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PrecompiledHeaderFile />
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem> <SubSystem>
@ -149,8 +152,11 @@
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader> <PrecompiledHeader>NotUsing</PrecompiledHeader>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration> <EnableParallelCodeGeneration>
</EnableParallelCodeGeneration>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PrecompiledHeaderFile />
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem> <SubSystem>

File diff suppressed because it is too large Load Diff

65
include/rsp.h Normal file
View File

@ -0,0 +1,65 @@
#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

199
include/rsp_vu.h Normal file
View File

@ -0,0 +1,199 @@
// 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;
};

1537
include/rsp_vu_impl.h Normal file

File diff suppressed because it is too large Load Diff

61
include/rt64_layer.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef __RT64_LAYER_H__
#define __RT64_LAYER_H__
typedef struct {
unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */
unsigned char* RDRAM;
unsigned char* DMEM;
unsigned char* IMEM;
unsigned int* MI_INTR_REG;
unsigned int* DPC_START_REG;
unsigned int* DPC_END_REG;
unsigned int* DPC_CURRENT_REG;
unsigned int* DPC_STATUS_REG;
unsigned int* DPC_CLOCK_REG;
unsigned int* DPC_BUFBUSY_REG;
unsigned int* DPC_PIPEBUSY_REG;
unsigned int* DPC_TMEM_REG;
unsigned int* VI_STATUS_REG;
unsigned int* VI_ORIGIN_REG;
unsigned int* VI_WIDTH_REG;
unsigned int* VI_INTR_REG;
unsigned int* VI_V_CURRENT_LINE_REG;
unsigned int* VI_TIMING_REG;
unsigned int* VI_V_SYNC_REG;
unsigned int* VI_H_SYNC_REG;
unsigned int* VI_LEAP_REG;
unsigned int* VI_H_START_REG;
unsigned int* VI_V_START_REG;
unsigned int* VI_V_BURST_REG;
unsigned int* VI_X_SCALE_REG;
unsigned int* VI_Y_SCALE_REG;
void (*CheckInterrupts)(void);
unsigned int version;
unsigned int* SP_STATUS_REG;
const unsigned int* RDRAM_SIZE;
} GFX_INFO;
#define DLLEXPORT extern "C" __declspec(dllexport)
#define DLLIMPORT extern "C" __declspec(dllimport)
#define CALL __cdecl
// Dynamic loading
//DLLEXPORT int (CALL *InitiateGFX)(GFX_INFO Gfx_Info) = nullptr;
//DLLEXPORT void (CALL *ProcessRDPList)(void) = nullptr;
//DLLEXPORT void (CALL *ProcessDList)(void) = nullptr;
//DLLEXPORT void (CALL *UpdateScreen)(void) = nullptr;
//DLLEXPORT void (CALL *PumpEvents)(void) = nullptr;
DLLIMPORT int InitiateGFX(GFX_INFO Gfx_Info);
DLLIMPORT void ProcessRDPList(void);
DLLIMPORT void ProcessDList(void);
DLLIMPORT void UpdateScreen(void);
DLLIMPORT void PumpEvents(void);
#endif

23
include/sections.h Normal file
View File

@ -0,0 +1,23 @@
#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

88
portultra/audio.cpp Normal file
View File

@ -0,0 +1,88 @@
#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;
}

340
portultra/events.cpp Normal file
View File

@ -0,0 +1,340 @@
#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 "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)) {
static volatile int i = 0;
if (i >= 100) {
i = 0;
}
i++;
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 };
}

194
portultra/mesgqueue.cpp Normal file
View File

@ -0,0 +1,194 @@
#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;
}

22
portultra/misc_ultra.cpp Normal file
View File

@ -0,0 +1,22 @@
#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

@ -0,0 +1,75 @@
#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 {
// We need a place in rdram to hold the PI handles, so pick an address in extended rdram
constexpr int32_t cart_handle = 0x80800000;
constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle));
constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
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

@ -0,0 +1,32 @@
#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

83
portultra/port_main.c Normal file
View File

@ -0,0 +1,83 @@
#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

286
portultra/scheduler.cpp Normal file
View File

@ -0,0 +1,286 @@
#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

@ -0,0 +1,60 @@
#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

32
portultra/task_win32.cpp Normal file
View File

@ -0,0 +1,32 @@
#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());
}

193
portultra/threads.cpp Normal file
View File

@ -0,0 +1,193 @@
#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;
}

192
portultra/timer.cpp Normal file
View File

@ -0,0 +1,192 @@
#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_);
// HACK: Skip the RCP timeout detection
if ((countdown == 140625000 || countdown == 1500000) && (uintptr_t)msg == 666) {
return 0;
}
// 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;
}

221
portultra/ultra64.h Normal file
View File

@ -0,0 +1,221 @@
#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 //
/////////////
// Threads
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;
// RSP
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;
// PI
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 */
};
///////////////
// 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

15
portultra/ultrainit.cpp Normal file
View File

@ -0,0 +1,15 @@
#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
rsp/.gitignore vendored Normal file
View File

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

29
src/ai.cpp Normal file
View File

@ -0,0 +1,29 @@
#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
}

108
src/cont.cpp Normal file
View File

@ -0,0 +1,108 @@
#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) {
;
}

5
src/dp.cpp Normal file
View File

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

21
src/eep.cpp Normal file
View File

@ -0,0 +1,21 @@
#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) {
;
}

2587
src/euc-jp.cpp Normal file

File diff suppressed because it is too large Load Diff

11
src/euc-jp.h Normal file
View File

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

137
src/flash.cpp Normal file
View File

@ -0,0 +1,137 @@
#include <array>
#include <cassert>
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
constexpr uint32_t flash_size = 1024 * 1024 / 8; // 1Mbit
constexpr uint32_t page_size = 128;
constexpr uint32_t pages_per_sector = 128;
constexpr uint32_t sector_size = page_size * pages_per_sector;
constexpr uint32_t sector_count = flash_size / sector_size;
void save_write_ptr(const void* in, uint32_t offset, uint32_t count);
void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count);
void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count);
void save_clear(uint32_t start, uint32_t size);
std::array<char, page_size> write_buffer;
extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = Multilibultra::flash_handle;
}
extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
PTR(u8) flash_status = ctx->r4;
MEM_B(0, flash_status) = 0;
}
extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) {
PTR(u32) flash_type = ctx->r4;
PTR(u32) flash_maker = ctx->r5;
MEM_B(0, flash_type) = 0;
MEM_B(0, flash_maker) = 0;
}
extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) {
}
extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) {
save_clear(0, Multilibultra::save_size);
ctx->r2 = 0;
}
extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
save_clear(0, Multilibultra::save_size);
ctx->r2 = 0;
}
extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) {
uint32_t page_num = (uint32_t)ctx->r4;
uint32_t sector_num = page_num / sector_size;
// Prevent out of bounds erase
if (sector_num >= sector_size) {
ctx->r2 = -1;
return;
}
save_clear(sector_num * sector_size, sector_size);
ctx->r2 = 0;
}
extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) {
uint32_t page_num = (uint32_t)ctx->r4;
uint32_t sector_num = page_num / sector_size;
// Prevent out of bounds erase
if (sector_num >= sector_size) {
ctx->r2 = -1;
return;
}
save_clear(sector_num * sector_size, sector_size);
ctx->r2 = 0;
}
extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) {
// All erases are blocking in this implementation, so this should always return OK.
ctx->r2 = 0; // FLASH_STATUS_ERASE_OK
}
extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) {
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
int32_t pri = ctx->r5;
PTR(void) dramAddr = ctx->r6;
PTR(OSMesgQueue) mq = ctx->r7;
// Copy the input data into the write buffer
for (size_t i = 0; i < page_size; i++) {
write_buffer[i] = MEM_B(i, dramAddr);
}
// Send the message indicating write completion
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
ctx->r2 = 0;
}
extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) {
uint32_t page_num = ctx->r4;
// Copy the write buffer into the save file
save_write_ptr(write_buffer.data(), page_num * page_size, page_size);
ctx->r2 = 0;
}
extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) {
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4);
int32_t pri = ctx->r5;
uint32_t page_num = ctx->r6;
PTR(void) dramAddr = ctx->r7;
uint32_t n_pages = MEM_W(0x10, ctx->r29);
PTR(OSMesgQueue) mq = MEM_W(0x14, ctx->r29);
uint32_t offset = page_num * page_size;
uint32_t count = n_pages * page_size;
// Read from the save file into the provided buffer
save_read(PASS_RDRAM dramAddr, offset, count);
// Send the message indicating read completion
osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK);
ctx->r2 = 0;
}
extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}

80
src/math_routines.cpp Normal file
View File

@ -0,0 +1,80 @@
#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;
}

110
src/overlays.cpp Normal file
View File

@ -0,0 +1,110 @@
#include <unordered_map>
#include <algorithm>
#include <vector>
#include "recomp.h"
#include "../RecompiledFuncs/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;
}

31
src/pak.cpp Normal file
View File

@ -0,0 +1,31 @@
#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
}

193
src/pi.cpp Normal file
View File

@ -0,0 +1,193 @@
#include <memory>
#include <fstream>
#include <array>
#include "recomp.h"
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
// 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;
}
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, Multilibultra::cart_handle);
handle->type = 0; // cart
handle->baseAddress = phys_to_k1(rom_base);
handle->domain = 0;
ctx->r2 = (gpr)Multilibultra::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 update_save_file() {
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_write_ptr(const void* in, uint32_t offset, uint32_t count) {
memcpy(&save_buffer[offset], in, count);
update_save_file();
}
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);
}
update_save_file();
}
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 save_clear(uint32_t start, uint32_t size) {
std::fill_n(save_buffer.begin() + start, size, 0);
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 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;
}

45
src/portultra_stubs.cpp Normal file
View File

@ -0,0 +1,45 @@
#include "../portultra/ultra64.h"
#include "../portultra/multilibultra.hpp"
#include "recomp.h"
// None of these functions need to be reimplemented, so stub them out
extern "C" void osUnmapTLBAll_recomp(uint8_t * rdram, recomp_context * ctx) {
// TODO this will need to be implemented in the future for any games that actually use the TLB
}
extern "C" void osVoiceInit_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = 11; // CONT_ERR_DEVICE
}
extern "C" void osVoiceSetWord_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}
extern "C" void osVoiceCheckWord_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}
extern "C" void osVoiceStopReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}
extern "C" void osVoiceMaskDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}
extern "C" void osVoiceStartReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}
extern "C" void osVoiceControlGain_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}
extern "C" void osVoiceGetReadData_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}
extern "C" void osVoiceClearDictionary_recomp(uint8_t * rdram, recomp_context * ctx) {
assert(false);
}

View File

@ -0,0 +1,158 @@
#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();
//}

70
src/print.cpp Normal file
View File

@ -0,0 +1,70 @@
#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;
}

165
src/recomp.cpp Normal file
View File

@ -0,0 +1,165 @@
#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;
}

114
src/rt64_layer.cpp Normal file
View File

@ -0,0 +1,114 @@
#include <memory>
#include <Windows.h>
#include "../portultra/multilibultra.hpp"
#include "rt64_layer.h"
#include "SDL.h"
static uint8_t DMEM[0x1000];
static uint8_t IMEM[0x1000];
unsigned int MI_INTR_REG = 0;
unsigned int DPC_START_REG = 0;
unsigned int DPC_END_REG = 0;
unsigned int DPC_CURRENT_REG = 0;
unsigned int DPC_STATUS_REG = 0;
unsigned int DPC_CLOCK_REG = 0;
unsigned int DPC_BUFBUSY_REG = 0;
unsigned int DPC_PIPEBUSY_REG = 0;
unsigned int DPC_TMEM_REG = 0;
unsigned int VI_STATUS_REG = 0;
unsigned int VI_ORIGIN_REG = 0;
unsigned int VI_WIDTH_REG = 0;
unsigned int VI_INTR_REG = 0;
unsigned int VI_V_CURRENT_LINE_REG = 0;
unsigned int VI_TIMING_REG = 0;
unsigned int VI_V_SYNC_REG = 0;
unsigned int VI_H_SYNC_REG = 0;
unsigned int VI_LEAP_REG = 0;
unsigned int VI_H_START_REG = 0;
unsigned int VI_V_START_REG = 0;
unsigned int VI_V_BURST_REG = 0;
unsigned int VI_X_SCALE_REG = 0;
unsigned int VI_Y_SCALE_REG = 0;
unsigned int SP_STATUS_REG = 0;
unsigned int RDRAM_SIZE = 0x800000;
#define GET_FUNC(lib, name) \
name = (decltype(name))GetProcAddress(lib, #name)
void dummy_check_interrupts() {
}
void RT64Init(uint8_t* rom, uint8_t* rdram) {
// Dynamic loading
//auto RT64 = LoadLibrary("RT64.dll");
//if (RT64 == 0) {
// fprintf(stdout, "Failed to load RT64\n");
// std::exit(EXIT_FAILURE);
//}
//GET_FUNC(RT64, InitiateGFX);
//GET_FUNC(RT64, ProcessRDPList);
//GET_FUNC(RT64, ProcessDList);
//GET_FUNC(RT64, UpdateScreen);
GFX_INFO gfx_info{};
gfx_info.HEADER = rom;
gfx_info.RDRAM = rdram;
gfx_info.DMEM = DMEM;
gfx_info.IMEM = IMEM;
gfx_info.MI_INTR_REG = &MI_INTR_REG;
gfx_info.DPC_START_REG = &DPC_START_REG;
gfx_info.DPC_END_REG = &DPC_END_REG;
gfx_info.DPC_CURRENT_REG = &DPC_CURRENT_REG;
gfx_info.DPC_STATUS_REG = &DPC_STATUS_REG;
gfx_info.DPC_CLOCK_REG = &DPC_CLOCK_REG;
gfx_info.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG;
gfx_info.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG;
gfx_info.DPC_TMEM_REG = &DPC_TMEM_REG;
gfx_info.VI_STATUS_REG = &VI_STATUS_REG;
gfx_info.VI_ORIGIN_REG = &VI_ORIGIN_REG;
gfx_info.VI_WIDTH_REG = &VI_WIDTH_REG;
gfx_info.VI_INTR_REG = &VI_INTR_REG;
gfx_info.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG;
gfx_info.VI_TIMING_REG = &VI_TIMING_REG;
gfx_info.VI_V_SYNC_REG = &VI_V_SYNC_REG;
gfx_info.VI_H_SYNC_REG = &VI_H_SYNC_REG;
gfx_info.VI_LEAP_REG = &VI_LEAP_REG;
gfx_info.VI_H_START_REG = &VI_H_START_REG;
gfx_info.VI_V_START_REG = &VI_V_START_REG;
gfx_info.VI_V_BURST_REG = &VI_V_BURST_REG;
gfx_info.VI_X_SCALE_REG = &VI_X_SCALE_REG;
gfx_info.VI_Y_SCALE_REG = &VI_Y_SCALE_REG;
gfx_info.CheckInterrupts = dummy_check_interrupts;
gfx_info.version = 2;
gfx_info.SP_STATUS_REG = &SP_STATUS_REG;
gfx_info.RDRAM_SIZE = &RDRAM_SIZE;
InitiateGFX(gfx_info);
}
void RT64SendDL(uint8_t* rdram, const OSTask* task) {
OSTask task_copy = *task;
task_copy.t.data_ptr &= 0x3FFFFFF;
task_copy.t.ucode &= 0x3FFFFFF;
task_copy.t.ucode_data &= 0x3FFFFFF;
memcpy(DMEM + 0xFC0, &task_copy, 0x40);
ProcessDList();
}
void RT64UpdateScreen(uint32_t vi_origin) {
VI_ORIGIN_REG = vi_origin;
UpdateScreen();
}

50
src/sp.cpp Normal file
View File

@ -0,0 +1,50 @@
#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);
snprintf(addr_str, sizeof(addr_str) - 1, "%08X", task->t.data_ptr);
addr_str[sizeof(addr_str) - 1] = '\0';
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);
}

38
src/vi.cpp Normal file
View File

@ -0,0 +1,38 @@
#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) {
;
}

582
thirdparty/blockingconcurrentqueue.h vendored Normal file
View File

@ -0,0 +1,582 @@
// 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

3747
thirdparty/concurrentqueue.h vendored Normal file

File diff suppressed because it is too large Load Diff

425
thirdparty/lightweightsemaphore.h vendored Normal file
View File

@ -0,0 +1,425 @@
// 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