mirror of
				https://github.com/Zelda64Recomp/Zelda64Recomp.git
				synced 2025-10-30 08:03:03 +00:00 
			
		
		
		
	Added recomp runtime library and portultra, MM initial boot
This commit is contained in:
		
							parent
							
								
									7847975e57
								
							
						
					
					
						commit
						ba37150ed1
					
				
					 46 changed files with 29616 additions and 27 deletions
				
			
		
							
								
								
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -42,10 +42,7 @@ bld/ | ||||||
| .vs/ | .vs/ | ||||||
| 
 | 
 | ||||||
| # Libraries (binaries that aren't in the repo) | # Libraries (binaries that aren't in the repo) | ||||||
| Lib | lib/ | ||||||
| 
 |  | ||||||
| # RT64 (since it's not public yet) |  | ||||||
| RT64 |  | ||||||
| 
 | 
 | ||||||
| # Runtime files | # Runtime files | ||||||
| imgui.ini | imgui.ini | ||||||
|  |  | ||||||
							
								
								
									
										123
									
								
								MMRecomp.vcxproj
									
										
									
									
									
								
							
							
						
						
									
										123
									
								
								MMRecomp.vcxproj
									
										
									
									
									
								
							|  | @ -17,7 +17,6 @@ | ||||||
|       <Configuration>Release</Configuration> |       <Configuration>Release</Configuration> | ||||||
|       <Platform>x64</Platform> |       <Platform>x64</Platform> | ||||||
|     </ProjectConfiguration> |     </ProjectConfiguration> | ||||||
| 
 |  | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <PropertyGroup Label="Globals"> |   <PropertyGroup Label="Globals"> | ||||||
|     <VCProjectVersion>16.0</VCProjectVersion> |     <VCProjectVersion>16.0</VCProjectVersion> | ||||||
|  | @ -53,25 +52,23 @@ | ||||||
|     <WholeProgramOptimization>true</WholeProgramOptimization> |     <WholeProgramOptimization>true</WholeProgramOptimization> | ||||||
|     <CharacterSet>Unicode</CharacterSet> |     <CharacterSet>Unicode</CharacterSet> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
| 
 |  | ||||||
|   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> |   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||||||
|   <ImportGroup Label="ExtensionSettings"> |   <ImportGroup Label="ExtensionSettings"> | ||||||
|   </ImportGroup> |   </ImportGroup> | ||||||
|   <ImportGroup Label="Shared" > |   <ImportGroup Label="Shared"> | ||||||
|  |   </ImportGroup> | ||||||
|  |   <ImportGroup 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> | ||||||
|     <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 Label="UserMacros" /> | ||||||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||||
|     <LinkIncremental>true</LinkIncremental> |     <LinkIncremental>true</LinkIncremental> | ||||||
|  | @ -85,18 +82,24 @@ | ||||||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||||
|     <LinkIncremental>false</LinkIncremental> |     <LinkIncremental>false</LinkIncremental> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
| 
 |  | ||||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||||
|     <ClCompile> |     <ClCompile> | ||||||
|       <WarningLevel>Level3</WarningLevel> |       <WarningLevel>Level3</WarningLevel> | ||||||
|       <SDLCheck>true</SDLCheck> |       <SDLCheck>true</SDLCheck> | ||||||
|       <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |       <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||||
|       <ConformanceMode>true</ConformanceMode> |       <ConformanceMode>true</ConformanceMode> | ||||||
|  |       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||||
|  |       <LanguageStandard>stdcpp20</LanguageStandard> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|     <Link> |     <Link> | ||||||
|       <SubSystem>Console</SubSystem> |       <SubSystem>Console</SubSystem> | ||||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||||
|  |       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||||
|     </Link> |     </Link> | ||||||
|  |     <PostBuildEvent> | ||||||
|  |       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||||
|  | XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||||
|  |     </PostBuildEvent> | ||||||
|   </ItemDefinitionGroup> |   </ItemDefinitionGroup> | ||||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||||||
|     <ClCompile> |     <ClCompile> | ||||||
|  | @ -106,13 +109,20 @@ | ||||||
|       <SDLCheck>true</SDLCheck> |       <SDLCheck>true</SDLCheck> | ||||||
|       <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |       <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||||
|       <ConformanceMode>true</ConformanceMode> |       <ConformanceMode>true</ConformanceMode> | ||||||
|  |       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||||
|  |       <LanguageStandard>stdcpp20</LanguageStandard> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|     <Link> |     <Link> | ||||||
|       <SubSystem>Console</SubSystem> |       <SubSystem>Console</SubSystem> | ||||||
|       <EnableCOMDATFolding>true</EnableCOMDATFolding> |       <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||||||
|       <OptimizeReferences>true</OptimizeReferences> |       <OptimizeReferences>true</OptimizeReferences> | ||||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||||
|  |       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||||
|     </Link> |     </Link> | ||||||
|  |     <PostBuildEvent> | ||||||
|  |       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||||
|  | XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||||
|  |     </PostBuildEvent> | ||||||
|   </ItemDefinitionGroup> |   </ItemDefinitionGroup> | ||||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||||||
|     <ClCompile> |     <ClCompile> | ||||||
|  | @ -120,11 +130,18 @@ | ||||||
|       <SDLCheck>true</SDLCheck> |       <SDLCheck>true</SDLCheck> | ||||||
|       <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |       <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||||
|       <ConformanceMode>true</ConformanceMode> |       <ConformanceMode>true</ConformanceMode> | ||||||
|  |       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||||
|  |       <LanguageStandard>stdcpp20</LanguageStandard> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|     <Link> |     <Link> | ||||||
|       <SubSystem>Console</SubSystem> |       <SubSystem>Console</SubSystem> | ||||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||||
|  |       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||||
|     </Link> |     </Link> | ||||||
|  |     <PostBuildEvent> | ||||||
|  |       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||||
|  | XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||||
|  |     </PostBuildEvent> | ||||||
|   </ItemDefinitionGroup> |   </ItemDefinitionGroup> | ||||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||||
|     <ClCompile> |     <ClCompile> | ||||||
|  | @ -134,17 +151,83 @@ | ||||||
|       <SDLCheck>true</SDLCheck> |       <SDLCheck>true</SDLCheck> | ||||||
|       <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> |       <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||||
|       <ConformanceMode>true</ConformanceMode> |       <ConformanceMode>true</ConformanceMode> | ||||||
|  |       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||||
|  |       <LanguageStandard>stdcpp20</LanguageStandard> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|     <Link> |     <Link> | ||||||
|       <SubSystem>Console</SubSystem> |       <SubSystem>Console</SubSystem> | ||||||
|       <EnableCOMDATFolding>true</EnableCOMDATFolding> |       <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||||||
|       <OptimizeReferences>true</OptimizeReferences> |       <OptimizeReferences>true</OptimizeReferences> | ||||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> |       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||||
|  |       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||||
|     </Link> |     </Link> | ||||||
|  |     <PostBuildEvent> | ||||||
|  |       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||||
|  | XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||||
|  |     </PostBuildEvent> | ||||||
|   </ItemDefinitionGroup> |   </ItemDefinitionGroup> | ||||||
| 
 |   <ItemGroup> | ||||||
|   <ItemGroup></ItemGroup> |     <ClCompile Include="portultra\audio.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\events.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\mesgqueue.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\misc_ultra.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\port_main.c" /> | ||||||
|  |     <ClCompile Include="portultra\scheduler.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\task_pthreads.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\task_win32.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\threads.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\timer.cpp" /> | ||||||
|  |     <ClCompile Include="portultra\ultrainit.cpp" /> | ||||||
|  |     <ClCompile Include="RecompiledFuncs\lookup.cpp" /> | ||||||
|  |     <ClCompile Include="rsp\aspMain.cpp"> | ||||||
|  |       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization> | ||||||
|  |       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization> | ||||||
|  |       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Default</BasicRuntimeChecks> | ||||||
|  |       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="rsp\njpgdspMain.cpp"> | ||||||
|  |       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization> | ||||||
|  |       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization> | ||||||
|  |       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Default</BasicRuntimeChecks> | ||||||
|  |       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\ai.cpp" /> | ||||||
|  |     <ClCompile Include="src\cont.cpp" /> | ||||||
|  |     <ClCompile Include="src\dp.cpp" /> | ||||||
|  |     <ClCompile Include="src\eep.cpp" /> | ||||||
|  |     <ClCompile Include="src\euc-jp.cpp" /> | ||||||
|  |     <ClCompile Include="src\flash.cpp" /> | ||||||
|  |     <ClCompile Include="src\math_routines.cpp" /> | ||||||
|  |     <ClCompile Include="src\overlays.cpp" /> | ||||||
|  |     <ClCompile Include="src\pak.cpp" /> | ||||||
|  |     <ClCompile Include="src\pi.cpp" /> | ||||||
|  |     <ClCompile Include="src\portultra_stubs.cpp" /> | ||||||
|  |     <ClCompile Include="src\portultra_translation.cpp" /> | ||||||
|  |     <ClCompile Include="src\print.cpp" /> | ||||||
|  |     <ClCompile Include="src\recomp.cpp" /> | ||||||
|  |     <ClCompile Include="src\rt64_layer.cpp" /> | ||||||
|  |     <ClCompile Include="src\sp.cpp" /> | ||||||
|  |     <ClCompile Include="src\vi.cpp" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ClInclude Include="include\disable_warnings.h" /> | ||||||
|  |     <ClInclude Include="include\recomp.h" /> | ||||||
|  |     <ClInclude Include="include\rsp.h" /> | ||||||
|  |     <ClInclude Include="include\rsp_vu.h" /> | ||||||
|  |     <ClInclude Include="include\rsp_vu_impl.h" /> | ||||||
|  |     <ClInclude Include="include\rt64_layer.h" /> | ||||||
|  |     <ClInclude Include="include\sections.h" /> | ||||||
|  |     <ClInclude Include="portultra\multilibultra.hpp" /> | ||||||
|  |     <ClInclude Include="portultra\platform_specific.h" /> | ||||||
|  |     <ClInclude Include="portultra\ultra64.h" /> | ||||||
|  |     <ClInclude Include="src\euc-jp.h" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="RecompiledFuncs.vcxproj"> | ||||||
|  |       <Project>{7bf5e3f9-c49f-4c84-ab64-7681f028cac2}</Project> | ||||||
|  |     </ProjectReference> | ||||||
|  |   </ItemGroup> | ||||||
|   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||||||
|   <ImportGroup Label="ExtensionTargets"> |   <ImportGroup Label="ExtensionTargets"> | ||||||
|   </ImportGroup> |   </ImportGroup> | ||||||
| </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> |       <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> | ||||||
|     </Filter> |     </Filter> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ClCompile Include="src\ai.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\cont.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\dp.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\eep.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\euc-jp.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\math_routines.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\overlays.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\pak.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\pi.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\portultra_translation.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\print.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\recomp.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\sp.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\vi.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\audio.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\events.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\mesgqueue.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\misc_ultra.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\port_main.c"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\scheduler.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\task_pthreads.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\task_win32.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\threads.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\timer.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="portultra\ultrainit.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\rt64_layer.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\portultra_stubs.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="src\flash.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="RecompiledFuncs\lookup.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="rsp\njpgdspMain.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |     <ClCompile Include="rsp\aspMain.cpp"> | ||||||
|  |       <Filter>Source Files</Filter> | ||||||
|  |     </ClCompile> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ClInclude Include="src\euc-jp.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="include\disable_warnings.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="include\recomp.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="include\rsp.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="include\rsp_vu.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="include\rsp_vu_impl.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="portultra\multilibultra.hpp"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="portultra\platform_specific.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="portultra\ultra64.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="include\rt64_layer.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |     <ClInclude Include="include\sections.h"> | ||||||
|  |       <Filter>Header Files</Filter> | ||||||
|  |     </ClInclude> | ||||||
|  |   </ItemGroup> | ||||||
| </Project> | </Project> | ||||||
|  | @ -113,8 +113,11 @@ | ||||||
|       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||||
|       <ConformanceMode>true</ConformanceMode> |       <ConformanceMode>true</ConformanceMode> | ||||||
|       <PrecompiledHeader>NotUsing</PrecompiledHeader> |       <PrecompiledHeader>NotUsing</PrecompiledHeader> | ||||||
|       <EnableParallelCodeGeneration>true</EnableParallelCodeGeneration> |       <EnableParallelCodeGeneration> | ||||||
|  |       </EnableParallelCodeGeneration> | ||||||
|       <MultiProcessorCompilation>true</MultiProcessorCompilation> |       <MultiProcessorCompilation>true</MultiProcessorCompilation> | ||||||
|  |       <WholeProgramOptimization>false</WholeProgramOptimization> | ||||||
|  |       <PrecompiledHeaderFile /> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|     <Link> |     <Link> | ||||||
|       <SubSystem> |       <SubSystem> | ||||||
|  | @ -149,8 +152,11 @@ | ||||||
|       <PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |       <PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||||
|       <ConformanceMode>true</ConformanceMode> |       <ConformanceMode>true</ConformanceMode> | ||||||
|       <PrecompiledHeader>NotUsing</PrecompiledHeader> |       <PrecompiledHeader>NotUsing</PrecompiledHeader> | ||||||
|       <EnableParallelCodeGeneration>true</EnableParallelCodeGeneration> |       <EnableParallelCodeGeneration> | ||||||
|  |       </EnableParallelCodeGeneration> | ||||||
|       <MultiProcessorCompilation>true</MultiProcessorCompilation> |       <MultiProcessorCompilation>true</MultiProcessorCompilation> | ||||||
|  |       <WholeProgramOptimization>false</WholeProgramOptimization> | ||||||
|  |       <PrecompiledHeaderFile /> | ||||||
|     </ClCompile> |     </ClCompile> | ||||||
|     <Link> |     <Link> | ||||||
|       <SubSystem> |       <SubSystem> | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										65
									
								
								include/rsp.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										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…
	
	Add table
		
		Reference in a new issue
	
	 Mr-Wiseguy
						Mr-Wiseguy