mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2024-11-24 13:49:22 +01:00
Added recomp runtime library and portultra, MM initial boot
This commit is contained in:
parent
7847975e57
commit
ba37150ed1
5
.gitignore
vendored
5
.gitignore
vendored
@ -42,10 +42,7 @@ bld/
|
||||
.vs/
|
||||
|
||||
# Libraries (binaries that aren't in the repo)
|
||||
Lib
|
||||
|
||||
# RT64 (since it's not public yet)
|
||||
RT64
|
||||
lib/
|
||||
|
||||
# Runtime files
|
||||
imgui.ini
|
||||
|
123
MMRecomp.vcxproj
123
MMRecomp.vcxproj
@ -17,7 +17,6 @@
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
@ -53,25 +52,23 @@
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared" >
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
@ -85,18 +82,24 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</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 Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
@ -106,13 +109,20 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</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 Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
@ -120,11 +130,18 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</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 Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
@ -134,17 +151,83 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</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>
|
||||
|
||||
<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" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
@ -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>
|
||||
</Filter>
|
||||
</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>
|
@ -113,8 +113,11 @@
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
|
||||
<EnableParallelCodeGeneration>
|
||||
</EnableParallelCodeGeneration>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<PrecompiledHeaderFile />
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>
|
||||
@ -149,8 +152,11 @@
|
||||
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
|
||||
<EnableParallelCodeGeneration>
|
||||
</EnableParallelCodeGeneration>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<PrecompiledHeaderFile />
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>
|
||||
|
File diff suppressed because it is too large
Load Diff
65
include/rsp.h
Normal file
65
include/rsp.h
Normal 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
199
include/rsp_vu.h
Normal 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
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
61
include/rt64_layer.h
Normal 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
23
include/sections.h
Normal 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
88
portultra/audio.cpp
Normal 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
340
portultra/events.cpp
Normal 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
194
portultra/mesgqueue.cpp
Normal 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
22
portultra/misc_ultra.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
75
portultra/multilibultra.hpp
Normal file
75
portultra/multilibultra.hpp
Normal 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
|
32
portultra/platform_specific.h
Normal file
32
portultra/platform_specific.h
Normal 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
83
portultra/port_main.c
Normal 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
286
portultra/scheduler.cpp
Normal 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);
|
||||
}
|
||||
|
60
portultra/task_pthreads.cpp
Normal file
60
portultra/task_pthreads.cpp
Normal 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
32
portultra/task_win32.cpp
Normal 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
193
portultra/threads.cpp
Normal 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
192
portultra/timer.cpp
Normal 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
221
portultra/ultra64.h
Normal 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
15
portultra/ultrainit.cpp
Normal 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
2
rsp/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
aspMain.cpp
|
||||
njpgdspMain.cpp
|
29
src/ai.cpp
Normal file
29
src/ai.cpp
Normal 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
108
src/cont.cpp
Normal 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
5
src/dp.cpp
Normal 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
21
src/eep.cpp
Normal 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
2587
src/euc-jp.cpp
Normal file
File diff suppressed because it is too large
Load Diff
11
src/euc-jp.h
Normal file
11
src/euc-jp.h
Normal 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
137
src/flash.cpp
Normal 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
80
src/math_routines.cpp
Normal 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
110
src/overlays.cpp
Normal 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(§ion_table[0], §ion_table[num_code_sections], rom,
|
||||
[](const SectionTableEntry& entry, uint32_t addr) {
|
||||
return entry.rom_addr < addr;
|
||||
}
|
||||
);
|
||||
auto upper = std::upper_bound(§ion_table[0], §ion_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(§ion_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(§ion_table[0], §ion_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
31
src/pak.cpp
Normal 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
193
src/pi.cpp
Normal 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
45
src/portultra_stubs.cpp
Normal 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);
|
||||
}
|
||||
|
158
src/portultra_translation.cpp
Normal file
158
src/portultra_translation.cpp
Normal 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
70
src/print.cpp
Normal 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
165
src/recomp.cpp
Normal 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
114
src/rt64_layer.cpp
Normal 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
50
src/sp.cpp
Normal 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
38
src/vi.cpp
Normal 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
582
thirdparty/blockingconcurrentqueue.h
vendored
Normal 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
3747
thirdparty/concurrentqueue.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
425
thirdparty/lightweightsemaphore.h
vendored
Normal file
425
thirdparty/lightweightsemaphore.h
vendored
Normal 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
|
Loading…
Reference in New Issue
Block a user