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/ | ||||
| 
 | ||||
| # Libraries (binaries that aren't in the repo) | ||||
| Lib | ||||
| 
 | ||||
| # RT64 (since it's not public yet) | ||||
| RT64 | ||||
| lib/ | ||||
| 
 | ||||
| # Runtime files | ||||
| imgui.ini | ||||
|  |  | |||
|  | @ -17,7 +17,6 @@ | |||
|       <Configuration>Release</Configuration> | ||||
|       <Platform>x64</Platform> | ||||
|     </ProjectConfiguration> | ||||
| 
 | ||||
|   </ItemGroup> | ||||
|   <PropertyGroup Label="Globals"> | ||||
|     <VCProjectVersion>16.0</VCProjectVersion> | ||||
|  | @ -53,11 +52,10 @@ | |||
|     <WholeProgramOptimization>true</WholeProgramOptimization> | ||||
|     <CharacterSet>Unicode</CharacterSet> | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||||
|   <ImportGroup Label="ExtensionSettings"> | ||||
|   </ImportGroup> | ||||
|   <ImportGroup Label="Shared" > | ||||
|   <ImportGroup Label="Shared"> | ||||
|   </ImportGroup> | ||||
|   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||
|     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||||
|  | @ -71,7 +69,6 @@ | |||
|   <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||
|     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||||
|   </ImportGroup> | ||||
| 
 | ||||
|   <PropertyGroup Label="UserMacros" /> | ||||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||
|     <LinkIncremental>true</LinkIncremental> | ||||
|  | @ -85,18 +82,24 @@ | |||
|   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||
|     <LinkIncremental>false</LinkIncremental> | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> | ||||
|     <ClCompile> | ||||
|       <WarningLevel>Level3</WarningLevel> | ||||
|       <SDLCheck>true</SDLCheck> | ||||
|       <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|       <ConformanceMode>true</ConformanceMode> | ||||
|       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||
|       <LanguageStandard>stdcpp20</LanguageStandard> | ||||
|     </ClCompile> | ||||
|     <Link> | ||||
|       <SubSystem>Console</SubSystem> | ||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||
|     </Link> | ||||
|     <PostBuildEvent> | ||||
|       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||
| XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||
|     </PostBuildEvent> | ||||
|   </ItemDefinitionGroup> | ||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> | ||||
|     <ClCompile> | ||||
|  | @ -106,13 +109,20 @@ | |||
|       <SDLCheck>true</SDLCheck> | ||||
|       <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|       <ConformanceMode>true</ConformanceMode> | ||||
|       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||
|       <LanguageStandard>stdcpp20</LanguageStandard> | ||||
|     </ClCompile> | ||||
|     <Link> | ||||
|       <SubSystem>Console</SubSystem> | ||||
|       <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||||
|       <OptimizeReferences>true</OptimizeReferences> | ||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||
|     </Link> | ||||
|     <PostBuildEvent> | ||||
|       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||
| XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||
|     </PostBuildEvent> | ||||
|   </ItemDefinitionGroup> | ||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> | ||||
|     <ClCompile> | ||||
|  | @ -120,11 +130,18 @@ | |||
|       <SDLCheck>true</SDLCheck> | ||||
|       <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|       <ConformanceMode>true</ConformanceMode> | ||||
|       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||
|       <LanguageStandard>stdcpp20</LanguageStandard> | ||||
|     </ClCompile> | ||||
|     <Link> | ||||
|       <SubSystem>Console</SubSystem> | ||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||
|     </Link> | ||||
|     <PostBuildEvent> | ||||
|       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||
| XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||
|     </PostBuildEvent> | ||||
|   </ItemDefinitionGroup> | ||||
|   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> | ||||
|     <ClCompile> | ||||
|  | @ -134,16 +151,82 @@ | |||
|       <SDLCheck>true</SDLCheck> | ||||
|       <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|       <ConformanceMode>true</ConformanceMode> | ||||
|       <AdditionalIncludeDirectories>$(SolutionDir)include;$(SolutionDir)lib/SDL2-2.24.0\include;$(SolutionDir)thirdparty;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> | ||||
|       <LanguageStandard>stdcpp20</LanguageStandard> | ||||
|     </ClCompile> | ||||
|     <Link> | ||||
|       <SubSystem>Console</SubSystem> | ||||
|       <EnableCOMDATFolding>true</EnableCOMDATFolding> | ||||
|       <OptimizeReferences>true</OptimizeReferences> | ||||
|       <GenerateDebugInformation>true</GenerateDebugInformation> | ||||
|       <AdditionalDependencies>$(ProjectDir)lib\RT64\$(Configuration)\RT64.lib;$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||||
|     </Link> | ||||
|     <PostBuildEvent> | ||||
|       <Command>XCOPY "$(ProjectDir)lib\RT64\$(Configuration)\*.dll" "$(TargetDir)" /S /Y | ||||
| XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S /Y</Command> | ||||
|     </PostBuildEvent> | ||||
|   </ItemDefinitionGroup> | ||||
| 
 | ||||
|   <ItemGroup></ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ClCompile Include="portultra\audio.cpp" /> | ||||
|     <ClCompile Include="portultra\events.cpp" /> | ||||
|     <ClCompile Include="portultra\mesgqueue.cpp" /> | ||||
|     <ClCompile Include="portultra\misc_ultra.cpp" /> | ||||
|     <ClCompile Include="portultra\port_main.c" /> | ||||
|     <ClCompile Include="portultra\scheduler.cpp" /> | ||||
|     <ClCompile Include="portultra\task_pthreads.cpp" /> | ||||
|     <ClCompile Include="portultra\task_win32.cpp" /> | ||||
|     <ClCompile Include="portultra\threads.cpp" /> | ||||
|     <ClCompile Include="portultra\timer.cpp" /> | ||||
|     <ClCompile Include="portultra\ultrainit.cpp" /> | ||||
|     <ClCompile Include="RecompiledFuncs\lookup.cpp" /> | ||||
|     <ClCompile Include="rsp\aspMain.cpp"> | ||||
|       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization> | ||||
|       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization> | ||||
|       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Default</BasicRuntimeChecks> | ||||
|       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="rsp\njpgdspMain.cpp"> | ||||
|       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">MaxSpeed</Optimization> | ||||
|       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MaxSpeed</Optimization> | ||||
|       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Default</BasicRuntimeChecks> | ||||
|       <BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Default</BasicRuntimeChecks> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\ai.cpp" /> | ||||
|     <ClCompile Include="src\cont.cpp" /> | ||||
|     <ClCompile Include="src\dp.cpp" /> | ||||
|     <ClCompile Include="src\eep.cpp" /> | ||||
|     <ClCompile Include="src\euc-jp.cpp" /> | ||||
|     <ClCompile Include="src\flash.cpp" /> | ||||
|     <ClCompile Include="src\math_routines.cpp" /> | ||||
|     <ClCompile Include="src\overlays.cpp" /> | ||||
|     <ClCompile Include="src\pak.cpp" /> | ||||
|     <ClCompile Include="src\pi.cpp" /> | ||||
|     <ClCompile Include="src\portultra_stubs.cpp" /> | ||||
|     <ClCompile Include="src\portultra_translation.cpp" /> | ||||
|     <ClCompile Include="src\print.cpp" /> | ||||
|     <ClCompile Include="src\recomp.cpp" /> | ||||
|     <ClCompile Include="src\rt64_layer.cpp" /> | ||||
|     <ClCompile Include="src\sp.cpp" /> | ||||
|     <ClCompile Include="src\vi.cpp" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ClInclude Include="include\disable_warnings.h" /> | ||||
|     <ClInclude Include="include\recomp.h" /> | ||||
|     <ClInclude Include="include\rsp.h" /> | ||||
|     <ClInclude Include="include\rsp_vu.h" /> | ||||
|     <ClInclude Include="include\rsp_vu_impl.h" /> | ||||
|     <ClInclude Include="include\rt64_layer.h" /> | ||||
|     <ClInclude Include="include\sections.h" /> | ||||
|     <ClInclude Include="portultra\multilibultra.hpp" /> | ||||
|     <ClInclude Include="portultra\platform_specific.h" /> | ||||
|     <ClInclude Include="portultra\ultra64.h" /> | ||||
|     <ClInclude Include="src\euc-jp.h" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="RecompiledFuncs.vcxproj"> | ||||
|       <Project>{7bf5e3f9-c49f-4c84-ab64-7681f028cac2}</Project> | ||||
|     </ProjectReference> | ||||
|   </ItemGroup> | ||||
|   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||||
|   <ImportGroup Label="ExtensionTargets"> | ||||
|   </ImportGroup> | ||||
|  |  | |||
|  | @ -14,4 +14,134 @@ | |||
|       <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> | ||||
|     </Filter> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ClCompile Include="src\ai.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\cont.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\dp.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\eep.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\euc-jp.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\math_routines.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\overlays.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\pak.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\pi.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\portultra_translation.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\print.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\recomp.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\sp.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\vi.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\audio.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\events.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\mesgqueue.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\misc_ultra.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\port_main.c"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\scheduler.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\task_pthreads.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\task_win32.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\threads.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\timer.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="portultra\ultrainit.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\rt64_layer.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\portultra_stubs.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="src\flash.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="RecompiledFuncs\lookup.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="rsp\njpgdspMain.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="rsp\aspMain.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ClInclude Include="src\euc-jp.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="include\disable_warnings.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="include\recomp.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="include\rsp.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="include\rsp_vu.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="include\rsp_vu_impl.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="portultra\multilibultra.hpp"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="portultra\platform_specific.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="portultra\ultra64.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="include\rt64_layer.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="include\sections.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
|  | @ -113,8 +113,11 @@ | |||
|       <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|       <ConformanceMode>true</ConformanceMode> | ||||
|       <PrecompiledHeader>NotUsing</PrecompiledHeader> | ||||
|       <EnableParallelCodeGeneration>true</EnableParallelCodeGeneration> | ||||
|       <EnableParallelCodeGeneration> | ||||
|       </EnableParallelCodeGeneration> | ||||
|       <MultiProcessorCompilation>true</MultiProcessorCompilation> | ||||
|       <WholeProgramOptimization>false</WholeProgramOptimization> | ||||
|       <PrecompiledHeaderFile /> | ||||
|     </ClCompile> | ||||
|     <Link> | ||||
|       <SubSystem> | ||||
|  | @ -149,8 +152,11 @@ | |||
|       <PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||||
|       <ConformanceMode>true</ConformanceMode> | ||||
|       <PrecompiledHeader>NotUsing</PrecompiledHeader> | ||||
|       <EnableParallelCodeGeneration>true</EnableParallelCodeGeneration> | ||||
|       <EnableParallelCodeGeneration> | ||||
|       </EnableParallelCodeGeneration> | ||||
|       <MultiProcessorCompilation>true</MultiProcessorCompilation> | ||||
|       <WholeProgramOptimization>false</WholeProgramOptimization> | ||||
|       <PrecompiledHeaderFile /> | ||||
|     </ClCompile> | ||||
|     <Link> | ||||
|       <SubSystem> | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										65
									
								
								include/rsp.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								include/rsp.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| #ifndef __RSP_H__ | ||||
| #define __RSP_H__ | ||||
| 
 | ||||
| #include "rsp_vu.h" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| enum class RspExitReason { | ||||
|     Invalid, | ||||
|     Broke, | ||||
|     ImemOverrun, | ||||
|     UnhandledJumpTarget | ||||
| }; | ||||
| 
 | ||||
| extern uint8_t dmem[]; | ||||
| extern uint16_t rspReciprocals[512]; | ||||
| extern uint16_t rspInverseSquareRoots[512]; | ||||
| 
 | ||||
| #define RSP_MEM_W(offset, addr) \ | ||||
|     (*reinterpret_cast<uint32_t*>(dmem + (offset) + (addr))) | ||||
| 
 | ||||
| #define RSP_MEM_H(offset, addr) \ | ||||
|     (*reinterpret_cast<int16_t*>(dmem + (((offset) + (addr)) ^ 2))) | ||||
| 
 | ||||
| #define RSP_MEM_HU(offset, addr) \ | ||||
|     (*reinterpret_cast<uint16_t*>(dmem + (((offset) + (addr)) ^ 2))) | ||||
| 
 | ||||
| #define RSP_MEM_B(offset, addr) \ | ||||
|     (*reinterpret_cast<int8_t*>(dmem + (((offset) + (addr)) ^ 3))) | ||||
| 
 | ||||
| #define RSP_MEM_BU(offset, addr) \ | ||||
|     (*reinterpret_cast<uint8_t*>(dmem + (((offset) + (addr)) ^ 3))) | ||||
|      | ||||
| #define RSP_ADD32(a, b) \ | ||||
|     ((int32_t)((a) + (b))) | ||||
|      | ||||
| #define RSP_SUB32(a, b) \ | ||||
|     ((int32_t)((a) - (b))) | ||||
| 
 | ||||
| #define RSP_SIGNED(val) \ | ||||
|     ((int32_t)(val)) | ||||
| 
 | ||||
| #define SET_DMA_DMEM(dmem_addr) dma_dmem_address = (dmem_addr) | ||||
| #define SET_DMA_DRAM(dram_addr) dma_dram_address = (dram_addr) | ||||
| #define DO_DMA_READ(rd_len) dma_rdram_to_dmem(rdram, dma_dmem_address, dma_dram_address, (rd_len)) | ||||
| #define DO_DMA_WRITE(wr_len) dma_dmem_to_rdram(rdram, dma_dmem_address, dma_dram_address, (wr_len)) | ||||
| 
 | ||||
| static inline void dma_rdram_to_dmem(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t rd_len) { | ||||
|     rd_len += 1; // Read length is inclusive
 | ||||
|     dram_addr &= 0xFFFFF8; | ||||
|     assert(dmem_addr + rd_len <= 0x1000); | ||||
|     for (uint32_t i = 0; i < rd_len; i++) { | ||||
|         RSP_MEM_B(i, dmem_addr) = MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static inline void dma_dmem_to_rdram(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t wr_len) { | ||||
|     wr_len += 1; // Write length is inclusive
 | ||||
|     dram_addr &= 0xFFFFF8; | ||||
|     assert(dmem_addr + wr_len <= 0x1000); | ||||
|     for (uint32_t i = 0; i < wr_len; i++) { | ||||
|         MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)) = RSP_MEM_B(i, dmem_addr); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										199
									
								
								include/rsp_vu.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								include/rsp_vu.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,199 @@ | |||
| // This file is modified from the Ares N64 emulator core. Ares can
 | ||||
| // be found at https://github.com/ares-emulator/ares. The original license
 | ||||
| // for this portion of Ares is as follows:
 | ||||
| // ----------------------------------------------------------------------
 | ||||
| // ares
 | ||||
| // 
 | ||||
| // Copyright(c) 2004 - 2021 ares team, Near et al
 | ||||
| // 
 | ||||
| // Permission to use, copy, modify, and /or distribute this software for any
 | ||||
| // purpose with or without fee is hereby granted, provided that the above
 | ||||
| // copyright noticeand this permission notice appear in all copies.
 | ||||
| // 
 | ||||
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | ||||
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | ||||
| // MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | ||||
| // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | ||||
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 | ||||
| // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | ||||
| // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | ||||
| // ----------------------------------------------------------------------
 | ||||
| #include <cstdint> | ||||
| 
 | ||||
| #define ARCHITECTURE_AMD64 | ||||
| #define ARCHITECTURE_SUPPORTS_SSE4_1 1 | ||||
| 
 | ||||
| #if defined(ARCHITECTURE_AMD64) | ||||
| #include <nmmintrin.h> | ||||
| using v128 = __m128i; | ||||
| #elif defined(ARCHITECTURE_ARM64) | ||||
| #include <sse2neon.h> | ||||
| using v128 = __m128i; | ||||
| #endif | ||||
| 
 | ||||
| namespace Accuracy { | ||||
|     namespace RSP { | ||||
| #if ARCHITECTURE_SUPPORTS_SSE4_1 | ||||
|         constexpr bool SISD = false; | ||||
|         constexpr bool SIMD = true; | ||||
| #else | ||||
|         constexpr bool SISD = true; | ||||
|         constexpr bool SIMD = false; | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| using u8 = uint8_t; | ||||
| using s8 = int8_t; | ||||
| using u16 = uint16_t; | ||||
| using s16 = int16_t; | ||||
| using u32 = uint32_t; | ||||
| using s32 = int32_t; | ||||
| using u64 = uint64_t; | ||||
| using s64 = int64_t; | ||||
| using uint128_t = uint64_t[2]; | ||||
| 
 | ||||
| template<u32 bits> inline auto sclamp(s64 x) -> s64 { | ||||
|   enum : s64 { b = 1ull << (bits - 1), m = b - 1 }; | ||||
|   return (x > m) ? m : (x < -b) ? -b : x; | ||||
| } | ||||
| 
 | ||||
| struct RSP { | ||||
|     using r32 = uint32_t; | ||||
|     using cr32 = const r32; | ||||
| 
 | ||||
|     union r128 { | ||||
|         struct { uint64_t u128[2]; }; | ||||
| #if ARCHITECTURE_SUPPORTS_SSE4_1 | ||||
|         struct {   __m128i v128; }; | ||||
| 
 | ||||
|         operator __m128i() const { return v128; } | ||||
|         auto operator=(__m128i value) { v128 = value; } | ||||
| #endif | ||||
| 
 | ||||
|         auto byte(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; } | ||||
|         auto byte(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; } | ||||
| 
 | ||||
|         auto element(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; } | ||||
|         auto element(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; } | ||||
| 
 | ||||
|         auto u8(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; } | ||||
|         auto u8(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; } | ||||
| 
 | ||||
|         auto s16(u32 index) -> int16_t& { return ((int16_t*)&u128)[7 - index]; } | ||||
|         auto s16(u32 index) const -> int16_t { return ((int16_t*)&u128)[7 - index]; } | ||||
| 
 | ||||
|         auto u16(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; } | ||||
|         auto u16(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; } | ||||
| 
 | ||||
|         //VCx registers
 | ||||
|         auto get(u32 index) const -> bool { return u16(index) != 0; } | ||||
|         auto set(u32 index, bool value) -> bool { return u16(index) = 0 - value, value; } | ||||
| 
 | ||||
|         //vu-registers.cpp
 | ||||
|         inline auto operator()(u32 index) const -> r128; | ||||
|     }; | ||||
|     using cr128 = const r128; | ||||
| 
 | ||||
|     struct VU { | ||||
|         r128 r[32]; | ||||
|         r128 acch, accm, accl; | ||||
|         r128 vcoh, vcol;  //16-bit little endian
 | ||||
|         r128 vcch, vccl;  //16-bit little endian
 | ||||
|         r128 vce;         // 8-bit little endian
 | ||||
|         s16 divin; | ||||
|         s16 divout; | ||||
|         bool divdp; | ||||
|     } vpu; | ||||
| 
 | ||||
|     static constexpr r128 zero{0}; | ||||
|     static constexpr r128 invert{(uint64_t)-1, (uint64_t)-1}; | ||||
| 
 | ||||
|     inline auto accumulatorGet(u32 index) const -> u64; | ||||
|     inline auto accumulatorSet(u32 index, u64 value) -> void; | ||||
|     inline auto accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16; | ||||
| 
 | ||||
|     inline auto CFC2(r32& rt, u8 rd) -> void; | ||||
|     inline auto CTC2(cr32& rt, u8 rd) -> void; | ||||
|     template<u8 e> inline auto LBV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LDV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LFV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LHV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LLV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LPV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LQV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LRV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LSV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LTV(u8 vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LUV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto LWV(r128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto MFC2(r32& rt, cr128& vs) -> void; | ||||
|     template<u8 e> inline auto MTC2(cr32& rt, r128& vs) -> void; | ||||
|     template<u8 e> inline auto SBV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SDV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SFV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SHV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SLV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SPV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SQV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SRV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SSV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto STV(u8 vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SUV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto SWV(cr128& vt, cr32& rs, s8 imm) -> void; | ||||
|     template<u8 e> inline auto VABS(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VADD(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VADDC(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VAND(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VCH(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VCL(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VCR(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VEQ(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VGE(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VLT(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<bool U, u8 e> | ||||
|     inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<0, e>(vd, vs, vt); } | ||||
|     template<u8 e> inline auto VMACU(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<1, e>(vd, vs, vt); } | ||||
|     inline auto VMACQ(r128& vd) -> void; | ||||
|     template<u8 e> inline auto VMADH(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMADL(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMADM(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMADN(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMOV(r128& vd, u8 de, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMRG(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMUDH(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMUDL(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMUDM(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMUDN(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<bool U, u8 e> | ||||
|     inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<0, e>(rd, vs, vt); } | ||||
|     template<u8 e> inline auto VMULU(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<1, e>(rd, vs, vt); } | ||||
|     template<u8 e> inline auto VMULQ(r128& rd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VNAND(r128& rd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VNE(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     inline auto VNOP() -> void; | ||||
|     template<u8 e> inline auto VNOR(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VNXOR(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VOR(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<bool L, u8 e> | ||||
|     inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void { VRCP<0, e>(vd, de, vt); } | ||||
|     template<u8 e> inline auto VRCPL(r128& vd, u8 de, cr128& vt) -> void { VRCP<1, e>(vd, de, vt); } | ||||
|     template<u8 e> inline auto VRCPH(r128& vd, u8 de, cr128& vt) -> void; | ||||
|     template<bool D, u8 e> | ||||
|     inline auto VRND(r128& vd, u8 vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VRNDN(r128& vd, u8 vs, cr128& vt) -> void { VRND<0, e>(vd, vs, vt); } | ||||
|     template<u8 e> inline auto VRNDP(r128& vd, u8 vs, cr128& vt) -> void { VRND<1, e>(vd, vs, vt); } | ||||
|     template<bool L, u8 e> | ||||
|     inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void { VRSQ<0, e>(vd, de, vt); } | ||||
|     template<u8 e> inline auto VRSQL(r128& vd, u8 de, cr128& vt) -> void { VRSQ<1, e>(vd, de, vt); } | ||||
|     template<u8 e> inline auto VRSQH(r128& vd, u8 de, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VSAR(r128& vd, cr128& vs) -> void; | ||||
|     template<u8 e> inline auto VSUB(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VSUBC(r128& vd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VXOR(r128& rd, cr128& vs, cr128& vt) -> void; | ||||
|     template<u8 e> inline auto VZERO(r128& rd, cr128& vs, cr128& vt) -> void; | ||||
| }; | ||||
							
								
								
									
										1537
									
								
								include/rsp_vu_impl.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1537
									
								
								include/rsp_vu_impl.h
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										61
									
								
								include/rt64_layer.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								include/rt64_layer.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| #ifndef __RT64_LAYER_H__ | ||||
| #define __RT64_LAYER_H__ | ||||
| 
 | ||||
| typedef struct { | ||||
|     unsigned char* HEADER;  /* This is the rom header (first 40h bytes of the rom) */ | ||||
|     unsigned char* RDRAM; | ||||
|     unsigned char* DMEM; | ||||
|     unsigned char* IMEM; | ||||
| 
 | ||||
|     unsigned int* MI_INTR_REG; | ||||
| 
 | ||||
|     unsigned int* DPC_START_REG; | ||||
|     unsigned int* DPC_END_REG; | ||||
|     unsigned int* DPC_CURRENT_REG; | ||||
|     unsigned int* DPC_STATUS_REG; | ||||
|     unsigned int* DPC_CLOCK_REG; | ||||
|     unsigned int* DPC_BUFBUSY_REG; | ||||
|     unsigned int* DPC_PIPEBUSY_REG; | ||||
|     unsigned int* DPC_TMEM_REG; | ||||
| 
 | ||||
|     unsigned int* VI_STATUS_REG; | ||||
|     unsigned int* VI_ORIGIN_REG; | ||||
|     unsigned int* VI_WIDTH_REG; | ||||
|     unsigned int* VI_INTR_REG; | ||||
|     unsigned int* VI_V_CURRENT_LINE_REG; | ||||
|     unsigned int* VI_TIMING_REG; | ||||
|     unsigned int* VI_V_SYNC_REG; | ||||
|     unsigned int* VI_H_SYNC_REG; | ||||
|     unsigned int* VI_LEAP_REG; | ||||
|     unsigned int* VI_H_START_REG; | ||||
|     unsigned int* VI_V_START_REG; | ||||
|     unsigned int* VI_V_BURST_REG; | ||||
|     unsigned int* VI_X_SCALE_REG; | ||||
|     unsigned int* VI_Y_SCALE_REG; | ||||
| 
 | ||||
|     void (*CheckInterrupts)(void); | ||||
| 
 | ||||
|     unsigned int version; | ||||
|     unsigned int* SP_STATUS_REG; | ||||
|     const unsigned int* RDRAM_SIZE; | ||||
| } GFX_INFO; | ||||
| 
 | ||||
| #define DLLEXPORT extern "C" __declspec(dllexport)   | ||||
| #define DLLIMPORT extern "C" __declspec(dllimport) | ||||
| #define CALL   __cdecl | ||||
| 
 | ||||
| // Dynamic loading
 | ||||
| //DLLEXPORT int (CALL *InitiateGFX)(GFX_INFO Gfx_Info) = nullptr;
 | ||||
| //DLLEXPORT void (CALL *ProcessRDPList)(void) = nullptr;
 | ||||
| //DLLEXPORT void (CALL *ProcessDList)(void) = nullptr;
 | ||||
| //DLLEXPORT void (CALL *UpdateScreen)(void) = nullptr;
 | ||||
| //DLLEXPORT void (CALL *PumpEvents)(void) = nullptr;
 | ||||
| 
 | ||||
| DLLIMPORT int InitiateGFX(GFX_INFO Gfx_Info); | ||||
| DLLIMPORT void ProcessRDPList(void); | ||||
| DLLIMPORT void ProcessDList(void); | ||||
| DLLIMPORT void UpdateScreen(void); | ||||
| DLLIMPORT void PumpEvents(void); | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
							
								
								
									
										23
									
								
								include/sections.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								include/sections.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| #ifndef __SECTIONS_H__ | ||||
| #define __SECTIONS_H__ | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| #define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) | ||||
| 
 | ||||
| typedef struct { | ||||
|     recomp_func_t* func; | ||||
|     uint32_t offset; | ||||
| } FuncEntry; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t rom_addr; | ||||
|     uint32_t ram_addr; | ||||
|     uint32_t size; | ||||
|     FuncEntry *funcs; | ||||
|     size_t num_funcs; | ||||
|     size_t index; | ||||
| } SectionTableEntry; | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										88
									
								
								portultra/audio.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								portultra/audio.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | |||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| #include "SDL.h" | ||||
| #include "SDL_audio.h" | ||||
| #include <cassert> | ||||
| 
 | ||||
| static SDL_AudioDeviceID audio_device = 0; | ||||
| static uint32_t sample_rate = 48000; | ||||
| 
 | ||||
| void Multilibultra::init_audio() { | ||||
| 	// Initialize SDL audio.
 | ||||
| 	SDL_InitSubSystem(SDL_INIT_AUDIO); | ||||
| 	// Pick an initial dummy sample rate; this will be set by the game later to the true sample rate.
 | ||||
| 	set_audio_frequency(48000); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::set_audio_frequency(uint32_t freq) { | ||||
| 	if (audio_device != 0) { | ||||
| 		SDL_CloseAudioDevice(audio_device); | ||||
| 	} | ||||
| 	SDL_AudioSpec spec_desired{ | ||||
| 		.freq = (int)freq, | ||||
| 		.format = AUDIO_S16, | ||||
| 		.channels = 2, | ||||
| 		.silence = 0, // calculated
 | ||||
| 		.samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering
 | ||||
| 		.padding = 0, // unused
 | ||||
| 		.size = 0, // calculated
 | ||||
| 		.callback = nullptr,//feed_audio, // Use a callback as QueueAudio causes popping
 | ||||
| 		.userdata = nullptr | ||||
| 	}; | ||||
| 
 | ||||
| 	audio_device = SDL_OpenAudioDevice(nullptr, false, &spec_desired, nullptr, 0); | ||||
| 	if (audio_device == 0) { | ||||
| 		printf("SDL Error: %s\n", SDL_GetError()); | ||||
| 		fflush(stdout); | ||||
| 		assert(false); | ||||
| 	} | ||||
| 	SDL_PauseAudioDevice(audio_device, 0); | ||||
| 	sample_rate = freq; | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t byte_count) { | ||||
| 	// Buffer for holding the output of swapping the audio channels. This is reused across
 | ||||
| 	// calls to reduce runtime allocations.
 | ||||
| 	static std::vector<uint16_t> swap_buffer; | ||||
| 
 | ||||
| 	// Ensure that the byte count is an integer multiple of samples.
 | ||||
| 	assert((byte_count & 1) == 0); | ||||
| 
 | ||||
| 	// Calculate the number of samples from the number of bytes.
 | ||||
| 	uint32_t sample_count = byte_count / sizeof(s16); | ||||
| 
 | ||||
| 	// Make sure the swap buffer is large enough to hold all the incoming audio data.
 | ||||
| 	if (sample_count > swap_buffer.size()) { | ||||
| 		swap_buffer.resize(sample_count); | ||||
| 	} | ||||
| 
 | ||||
| 	// Swap the audio channels into the swap buffer to correct for the address xor caused by endianness handling.
 | ||||
| 	s16* audio_data = TO_PTR(s16, audio_data_); | ||||
| 	for (size_t i = 0; i < sample_count; i += 2) { | ||||
| 		swap_buffer[i + 0] = audio_data[i + 1]; | ||||
| 		swap_buffer[i + 1] = audio_data[i + 0]; | ||||
| 	} | ||||
| 
 | ||||
| 	// Queue the swapped audio data.
 | ||||
| 	SDL_QueueAudio(audio_device, swap_buffer.data(), byte_count); | ||||
| } | ||||
| 
 | ||||
| // If there's ever any audio popping, check here first. Some games are very sensitive to
 | ||||
| // the remaining sample count and reporting a number that's too high here can lead to issues.
 | ||||
| // Reporting a number that's too low can lead to audio lag in some games.
 | ||||
| uint32_t Multilibultra::get_remaining_audio_bytes() { | ||||
| 	// Get the number of remaining buffered audio bytes.
 | ||||
| 	uint32_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device); | ||||
| 	 | ||||
| 	// Adjust the reported count to be four refreshes in the future, which helps ensure that
 | ||||
| 	// there are enough samples even if the game experiences a small amount of lag. This prevents
 | ||||
| 	// audio popping on games that use the buffered audio byte count to determine how many samples
 | ||||
| 	// to generate.
 | ||||
| 	uint32_t samples_per_vi = (sample_rate / 60); | ||||
| 	if (buffered_byte_count > (4u * samples_per_vi)) { | ||||
| 		buffered_byte_count -= (4u * samples_per_vi); | ||||
| 	} else { | ||||
| 		buffered_byte_count = 0; | ||||
| 	} | ||||
| 	return buffered_byte_count; | ||||
| } | ||||
							
								
								
									
										340
									
								
								portultra/events.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										340
									
								
								portultra/events.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,340 @@ | |||
| #include <thread> | ||||
| #include <atomic> | ||||
| #include <chrono> | ||||
| #include <cinttypes> | ||||
| #include <variant> | ||||
| #include <unordered_map> | ||||
| #include <utility> | ||||
| #include <mutex> | ||||
| #include <queue> | ||||
| 
 | ||||
| #include <Windows.h> | ||||
| #include "SDL.h" | ||||
| #include "blockingconcurrentqueue.h" | ||||
| 
 | ||||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| #include "rsp.h" | ||||
| 
 | ||||
| struct SpTaskAction { | ||||
|     OSTask task; | ||||
| }; | ||||
| 
 | ||||
| struct SwapBuffersAction { | ||||
|     uint32_t origin; | ||||
| }; | ||||
| 
 | ||||
| using Action = std::variant<SpTaskAction, SwapBuffersAction>; | ||||
| 
 | ||||
| static struct { | ||||
|     struct { | ||||
|         std::thread thread; | ||||
|         PTR(OSMesgQueue) mq = NULLPTR; | ||||
|         PTR(void) current_buffer = NULLPTR; | ||||
|         PTR(void) next_buffer = NULLPTR; | ||||
|         OSMesg msg = (OSMesg)0; | ||||
|         int retrace_count = 1; | ||||
|     } vi; | ||||
|     struct { | ||||
|         std::thread thread; | ||||
|         PTR(OSMesgQueue) mq = NULLPTR; | ||||
|         OSMesg msg = (OSMesg)0; | ||||
|     } sp; | ||||
|     struct { | ||||
|         std::thread thread; | ||||
|         PTR(OSMesgQueue) mq = NULLPTR; | ||||
|         OSMesg msg = (OSMesg)0; | ||||
|     } dp; | ||||
|     struct { | ||||
|         std::thread thread; | ||||
|         PTR(OSMesgQueue) mq = NULLPTR; | ||||
|         OSMesg msg = (OSMesg)0; | ||||
|     } ai; | ||||
|     struct { | ||||
|         std::thread thread; | ||||
|         PTR(OSMesgQueue) mq = NULLPTR; | ||||
|         OSMesg msg = (OSMesg)0; | ||||
|     } si; | ||||
|     // The same message queue may be used for multiple events, so share a mutex for all of them
 | ||||
|     std::mutex message_mutex; | ||||
|     uint8_t* rdram; | ||||
|     moodycamel::BlockingConcurrentQueue<Action> action_queue{}; | ||||
| } events_context{}; | ||||
| 
 | ||||
| extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) { | ||||
|     OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_); | ||||
|     std::lock_guard lock{ events_context.message_mutex }; | ||||
| 
 | ||||
|     switch (event_id) { | ||||
|         case OS_EVENT_SP: | ||||
|             events_context.sp.msg = msg; | ||||
|             events_context.sp.mq = mq_; | ||||
|             break; | ||||
|         case OS_EVENT_DP: | ||||
|             events_context.dp.msg = msg; | ||||
|             events_context.dp.mq = mq_; | ||||
|             break; | ||||
|         case OS_EVENT_AI: | ||||
|             events_context.ai.msg = msg; | ||||
|             events_context.ai.mq = mq_; | ||||
|             break; | ||||
|         case OS_EVENT_SI: | ||||
|             events_context.si.msg = msg; | ||||
|             events_context.si.mq = mq_; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) { | ||||
|     std::lock_guard lock{ events_context.message_mutex }; | ||||
|     events_context.vi.mq = mq_; | ||||
|     events_context.vi.msg = msg; | ||||
|     events_context.vi.retrace_count = retrace_count; | ||||
| } | ||||
| 
 | ||||
| void vi_thread_func() { | ||||
|     using namespace std::chrono_literals; | ||||
|      | ||||
|     uint64_t total_vis = 0; | ||||
|     int remaining_retraces = events_context.vi.retrace_count; | ||||
| 
 | ||||
|     while (true) { | ||||
|         // Determine the next VI time (more accurate than adding 16ms each VI interrupt)
 | ||||
|         auto next = Multilibultra::get_start() + (total_vis * 1000000us) / (60 * Multilibultra::get_speed_multiplier()); | ||||
|         //if (next > std::chrono::system_clock::now()) {
 | ||||
|         //    printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n",
 | ||||
|         //        (next - std::chrono::system_clock::now()) / 1us,
 | ||||
|         //        (std::chrono::system_clock::now() - events_context.start) / 1us,
 | ||||
|         //        (next - events_context.start) / 1us);
 | ||||
|         //} else {
 | ||||
|         //    printf("No need to sleep\n");
 | ||||
|         //}
 | ||||
|         std::this_thread::sleep_until(next); | ||||
|         // Calculate how many VIs have passed
 | ||||
|         uint64_t new_total_vis = (Multilibultra::time_since_start() * (60 * Multilibultra::get_speed_multiplier()) / 1000ms) + 1; | ||||
|         if (new_total_vis > total_vis + 1) { | ||||
|             //printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1);
 | ||||
|         } | ||||
|         total_vis = new_total_vis; | ||||
| 
 | ||||
|         remaining_retraces--; | ||||
| 
 | ||||
|         { | ||||
|             std::lock_guard lock{ events_context.message_mutex }; | ||||
|             uint8_t* rdram = events_context.rdram; | ||||
|             if (remaining_retraces == 0) { | ||||
|                 remaining_retraces = events_context.vi.retrace_count; | ||||
| 
 | ||||
|                 if (events_context.vi.mq != NULLPTR) { | ||||
|                     if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) { | ||||
|                         //printf("Game skipped a VI frame!\n");
 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if (events_context.ai.mq != NULLPTR) { | ||||
|                 if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) { | ||||
|                     //printf("Game skipped a AI frame!\n");
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void sp_complete() { | ||||
|     uint8_t* rdram = events_context.rdram; | ||||
|     std::lock_guard lock{ events_context.message_mutex }; | ||||
|     osSendMesg(PASS_RDRAM events_context.sp.mq, events_context.sp.msg, OS_MESG_NOBLOCK); | ||||
| } | ||||
| 
 | ||||
| void dp_complete() { | ||||
|     uint8_t* rdram = events_context.rdram; | ||||
|     std::lock_guard lock{ events_context.message_mutex }; | ||||
|     osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK); | ||||
| } | ||||
| 
 | ||||
| void RT64Init(uint8_t* rom, uint8_t* rdram); | ||||
| void RT64SendDL(uint8_t* rdram, const OSTask* task); | ||||
| void RT64UpdateScreen(uint32_t vi_origin); | ||||
| 
 | ||||
| std::unordered_map<SDL_Scancode, int> button_map{ | ||||
|     { SDL_Scancode::SDL_SCANCODE_LEFT,   0x0002 }, // c left
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_RIGHT,  0x0001 }, // c right
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_UP,     0x0008 }, // c up
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_DOWN,   0x0004 }, // c down
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_RETURN, 0x1000 }, // start
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_SPACE,  0x8000 }, // a
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_LSHIFT, 0x4000 }, // b
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_Q,      0x2000 }, // z
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_E,      0x0020 }, // l
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_R,      0x0010 }, // r
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_J,      0x0200 }, // dpad left
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_L,      0x0100 }, // dpad right
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_I,      0x0800 }, // dpad up
 | ||||
|     { SDL_Scancode::SDL_SCANCODE_K,      0x0400 }, // dpad down
 | ||||
| }; | ||||
| 
 | ||||
| extern int button; | ||||
| extern int stick_x; | ||||
| extern int stick_y; | ||||
| 
 | ||||
| int sdl_event_filter(void* userdata, SDL_Event* event) { | ||||
|     switch (event->type) { | ||||
|     case SDL_EventType::SDL_KEYUP: | ||||
|     case SDL_EventType::SDL_KEYDOWN: | ||||
|         { | ||||
|             const Uint8* key_states = SDL_GetKeyboardState(nullptr); | ||||
|             int new_button = 0; | ||||
| 
 | ||||
|             for (const auto& mapping : button_map) { | ||||
|                 if (key_states[mapping.first]) { | ||||
|                     new_button |= mapping.second; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             button = new_button; | ||||
| 
 | ||||
|             stick_x = 127 * (key_states[SDL_Scancode::SDL_SCANCODE_D] - key_states[SDL_Scancode::SDL_SCANCODE_A]); | ||||
|             stick_y = 127 * (key_states[SDL_Scancode::SDL_SCANCODE_W] - key_states[SDL_Scancode::SDL_SCANCODE_S]); | ||||
|         } | ||||
|         break; | ||||
|     case SDL_EventType::SDL_QUIT: | ||||
|         std::quick_exit(ERROR_SUCCESS); | ||||
|         break; | ||||
|     } | ||||
|     return 1; | ||||
| } | ||||
| 
 | ||||
| uint8_t dmem[0x1000]; | ||||
| uint16_t rspReciprocals[512]; | ||||
| uint16_t rspInverseSquareRoots[512]; | ||||
| 
 | ||||
| using RspUcodeFunc = RspExitReason(uint8_t* rdram); | ||||
| extern RspUcodeFunc njpgdspMain; | ||||
| extern RspUcodeFunc aspMain; | ||||
| 
 | ||||
| // From Ares emulator. For license details, see rsp_vu.h
 | ||||
| void rsp_constants_init() { | ||||
|     rspReciprocals[0] = u16(~0); | ||||
|     for (u16 index = 1; index < 512; index++) { | ||||
|         u64 a = index + 512; | ||||
|         u64 b = (u64(1) << 34) / a; | ||||
|         rspReciprocals[index] = u16(b + 1 >> 8); | ||||
|     } | ||||
| 
 | ||||
|     for (u16 index = 0; index < 512; index++) { | ||||
|         u64 a = index + 512 >> ((index % 2 == 1) ? 1 : 0); | ||||
|         u64 b = 1 << 17; | ||||
|         //find the largest b where b < 1.0 / sqrt(a)
 | ||||
|         while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++; | ||||
|         rspInverseSquareRoots[index] = u16(b >> 1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Runs a recompiled RSP microcode
 | ||||
| void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_func) { | ||||
|     // Load the OSTask into DMEM
 | ||||
|     memcpy(&dmem[0xFC0], task, sizeof(OSTask)); | ||||
|     // Load the ucode data into DMEM
 | ||||
|     dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1); | ||||
|     // Run the ucode
 | ||||
|     RspExitReason exit_reason = ucode_func(rdram); | ||||
|     // Ensure that the ucode exited correctly
 | ||||
|     assert(exit_reason == RspExitReason::Broke); | ||||
|     sp_complete(); | ||||
| } | ||||
| 
 | ||||
| void event_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* events_thread_ready) { | ||||
|     using namespace std::chrono_literals; | ||||
|     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { | ||||
|         fprintf(stderr, "Failed to initialize SDL2: %s\n", SDL_GetError()); | ||||
|         std::quick_exit(EXIT_FAILURE); | ||||
|     } | ||||
|     RT64Init(rom, rdram); | ||||
|     SDL_Window* window = SDL_GetWindowFromID(1); | ||||
|     // TODO set this window title in RT64, create the window here and send it to RT64, or something else entirely
 | ||||
|     // as the current window name visibly changes as RT64 is initialized
 | ||||
|     SDL_SetWindowTitle(window, "Recomp"); | ||||
|     //SDL_SetEventFilter(sdl_event_filter, nullptr);
 | ||||
| 
 | ||||
|     rsp_constants_init(); | ||||
| 
 | ||||
|     // Notify the caller thread that this thread is ready.
 | ||||
|     events_thread_ready->test_and_set(); | ||||
|     events_thread_ready->notify_all(); | ||||
| 
 | ||||
|     while (true) { | ||||
|         // Try to pull an action from the queue
 | ||||
|         Action action; | ||||
|         if (events_context.action_queue.wait_dequeue_timed(action, 1ms)) { | ||||
|             // Determine the action type and act on it
 | ||||
|             if (const auto* task_action = std::get_if<SpTaskAction>(&action)) { | ||||
|                 if (task_action->task.t.type == M_GFXTASK) { | ||||
|                     // (TODO let RT64 do this) Tell the game that the RSP and RDP tasks are complete
 | ||||
|                     RT64SendDL(rdram, &task_action->task); | ||||
|                     sp_complete(); | ||||
|                     dp_complete(); | ||||
|                 } else if (task_action->task.t.type == M_AUDTASK) { | ||||
|                     run_rsp_microcode(rdram, &task_action->task, aspMain); | ||||
|                 } else if (task_action->task.t.type == M_NJPEGTASK) { | ||||
|                     run_rsp_microcode(rdram, &task_action->task, njpgdspMain); | ||||
|                 } else { | ||||
|                     fprintf(stderr, "Unknown task type: %" PRIu32 "\n", task_action->task.t.type); | ||||
|                     assert(false); | ||||
|                     std::quick_exit(EXIT_FAILURE); | ||||
|                 } | ||||
|             } else if (const auto* swap_action = std::get_if<SwapBuffersAction>(&action)) { | ||||
|                 static volatile int i = 0; | ||||
|                 if (i >= 100) { | ||||
|                     i = 0; | ||||
|                 } | ||||
|                 i++; | ||||
|                 events_context.vi.current_buffer = events_context.vi.next_buffer; | ||||
|                 RT64UpdateScreen(swap_action->origin); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Handle events
 | ||||
|         constexpr int max_events_per_frame = 16; | ||||
|         SDL_Event cur_event; | ||||
|         int i = 0; | ||||
|         while (i++ < max_events_per_frame && SDL_PollEvent(&cur_event)) { | ||||
|             sdl_event_filter(nullptr, &cur_event); | ||||
|         } | ||||
|         //SDL_PumpEvents();
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) { | ||||
|     events_context.vi.next_buffer = frameBufPtr; | ||||
|     events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + 640 }); | ||||
| } | ||||
| 
 | ||||
| extern "C" PTR(void) osViGetNextFramebuffer() { | ||||
|     return events_context.vi.next_buffer; | ||||
| } | ||||
| 
 | ||||
| extern "C" PTR(void) osViGetCurrentFramebuffer() { | ||||
|     return events_context.vi.current_buffer; | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) { | ||||
|     OSTask* task = TO_PTR(OSTask, task_); | ||||
|     events_context.action_queue.enqueue(SpTaskAction{ *task }); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::send_si_message() { | ||||
|     uint8_t* rdram = events_context.rdram; | ||||
|     osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom) { | ||||
|     std::atomic_flag events_thread_ready; | ||||
|     events_context.rdram = rdram; | ||||
|     events_context.sp.thread = std::thread{ event_thread_func, rdram, rom, &events_thread_ready }; | ||||
|      | ||||
|     // Wait for the event thread to be ready before continuing to prevent the game from
 | ||||
|     // running before we're able to handle RSP tasks.
 | ||||
|     events_thread_ready.wait(false); | ||||
| 
 | ||||
|     events_context.vi.thread = std::thread{ vi_thread_func }; | ||||
| } | ||||
							
								
								
									
										194
									
								
								portultra/mesgqueue.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								portultra/mesgqueue.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,194 @@ | |||
| #include <thread> | ||||
| #include <atomic> | ||||
| 
 | ||||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) { | ||||
|     OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); | ||||
|     mq->blocked_on_recv = NULLPTR; | ||||
|     mq->blocked_on_send = NULLPTR; | ||||
|     mq->msgCount = count; | ||||
|     mq->msg = msg; | ||||
|     mq->validCount = 0; | ||||
|     mq->first = 0; | ||||
| } | ||||
| 
 | ||||
| s32 MQ_GET_COUNT(OSMesgQueue *mq) { | ||||
|     return mq->validCount; | ||||
| } | ||||
| 
 | ||||
| s32 MQ_IS_EMPTY(OSMesgQueue *mq) { | ||||
|     return mq->validCount == 0; | ||||
| } | ||||
| 
 | ||||
| s32 MQ_IS_FULL(OSMesgQueue* mq) { | ||||
|     return MQ_GET_COUNT(mq) >= mq->msgCount; | ||||
| } | ||||
| 
 | ||||
| void thread_queue_insert(RDRAM_ARG PTR(OSThread)* queue, PTR(OSThread) toadd_) { | ||||
|     PTR(OSThread)* cur = queue; | ||||
|     OSThread* toadd = TO_PTR(OSThread, toadd_);  | ||||
|     while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) { | ||||
|         cur = &TO_PTR(OSThread, *cur)->next; | ||||
|     } | ||||
|     toadd->next = (*cur); | ||||
|     *cur = toadd_; | ||||
| } | ||||
| 
 | ||||
| OSThread* thread_queue_pop(RDRAM_ARG PTR(OSThread)* queue) { | ||||
|     PTR(OSThread) ret = *queue; | ||||
|     *queue = TO_PTR(OSThread, ret)->next; | ||||
|     return TO_PTR(OSThread, ret); | ||||
| } | ||||
| 
 | ||||
| bool thread_queue_empty(RDRAM_ARG PTR(OSThread)* queue) { | ||||
|     return *queue == NULLPTR; | ||||
| } | ||||
| 
 | ||||
| extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) { | ||||
|     OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); | ||||
|      | ||||
|     // Prevent accidentally blocking anything that isn't a game thread
 | ||||
|     if (!Multilibultra::is_game_thread()) { | ||||
|         flags = OS_MESG_NOBLOCK; | ||||
|     } | ||||
| 
 | ||||
|     Multilibultra::disable_preemption(); | ||||
| 
 | ||||
|     if (flags == OS_MESG_NOBLOCK) { | ||||
|         // If non-blocking, fail if the queue is full
 | ||||
|         if (MQ_IS_FULL(mq)) { | ||||
|             Multilibultra::enable_preemption(); | ||||
|             return -1; | ||||
|         } | ||||
|     } else { | ||||
|         // Otherwise, yield this thread until the queue has room
 | ||||
|         while (MQ_IS_FULL(mq)) { | ||||
|             debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, Multilibultra::this_thread())->id); | ||||
|             thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread()); | ||||
|             Multilibultra::enable_preemption(); | ||||
|             Multilibultra::pause_self(PASS_RDRAM1); | ||||
|             Multilibultra::disable_preemption(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     s32 last = (mq->first + mq->validCount) % mq->msgCount; | ||||
|     TO_PTR(OSMesg, mq->msg)[last] = msg; | ||||
|     mq->validCount++; | ||||
|      | ||||
|     OSThread* to_run = nullptr; | ||||
| 
 | ||||
|     if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) { | ||||
|         to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv); | ||||
|     } | ||||
|      | ||||
|     Multilibultra::enable_preemption(); | ||||
|     if (to_run) { | ||||
|         debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id); | ||||
|         if (Multilibultra::is_game_thread()) { | ||||
|             OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread()); | ||||
|             if (to_run->priority > self->priority) { | ||||
|                 Multilibultra::swap_to_thread(PASS_RDRAM to_run); | ||||
|             } else { | ||||
|                 Multilibultra::schedule_running_thread(to_run); | ||||
|             } | ||||
|         } else { | ||||
|             Multilibultra::schedule_running_thread(to_run); | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) { | ||||
|     OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); | ||||
|     Multilibultra::disable_preemption(); | ||||
| 
 | ||||
|     if (flags == OS_MESG_NOBLOCK) { | ||||
|         // If non-blocking, fail if the queue is full
 | ||||
|         if (MQ_IS_FULL(mq)) { | ||||
|             Multilibultra::enable_preemption(); | ||||
|             return -1; | ||||
|         } | ||||
|     } else { | ||||
|         // Otherwise, yield this thread in a loop until the queue is no longer full
 | ||||
|         while (MQ_IS_FULL(mq)) { | ||||
|             debug_printf("[Message Queue] Thread %d is blocked on jam\n", TO_PTR(OSThread, Multilibultra::this_thread())->id); | ||||
|             thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread()); | ||||
|             Multilibultra::enable_preemption(); | ||||
|             Multilibultra::pause_self(PASS_RDRAM1); | ||||
|             Multilibultra::disable_preemption(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount; | ||||
|     TO_PTR(OSMesg, mq->msg)[mq->first] = msg; | ||||
|     mq->validCount++; | ||||
|      | ||||
|     OSThread *to_run = nullptr; | ||||
| 
 | ||||
|     if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) { | ||||
|         to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv); | ||||
|     } | ||||
|      | ||||
|     Multilibultra::enable_preemption(); | ||||
|     if (to_run) { | ||||
|         debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id); | ||||
|         OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); | ||||
|         if (to_run->priority > self->priority) { | ||||
|             Multilibultra::swap_to_thread(PASS_RDRAM to_run); | ||||
|         } else { | ||||
|             Multilibultra::schedule_running_thread(to_run); | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) { | ||||
|     OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); | ||||
|     OSMesg *msg = TO_PTR(OSMesg, msg_); | ||||
|     Multilibultra::disable_preemption(); | ||||
| 
 | ||||
|     if (flags == OS_MESG_NOBLOCK) { | ||||
|         // If non-blocking, fail if the queue is empty
 | ||||
|         if (MQ_IS_EMPTY(mq)) { | ||||
|             Multilibultra::enable_preemption(); | ||||
|             return -1; | ||||
|         } | ||||
|     } else { | ||||
|         // Otherwise, yield this thread in a loop until the queue is no longer full
 | ||||
|         while (MQ_IS_EMPTY(mq)) { | ||||
|             debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, Multilibultra::this_thread())->id); | ||||
|             thread_queue_insert(PASS_RDRAM &mq->blocked_on_recv, Multilibultra::this_thread()); | ||||
|             Multilibultra::enable_preemption(); | ||||
|             Multilibultra::pause_self(PASS_RDRAM1); | ||||
|             Multilibultra::disable_preemption(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (msg_ != NULLPTR) { | ||||
|         *msg = TO_PTR(OSMesg, mq->msg)[mq->first]; | ||||
|     } | ||||
|      | ||||
|     mq->first = (mq->first + 1) % mq->msgCount; | ||||
|     mq->validCount--; | ||||
| 
 | ||||
|     OSThread *to_run = nullptr; | ||||
| 
 | ||||
|     if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_send)) { | ||||
|         to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_send); | ||||
|     } | ||||
|      | ||||
|     Multilibultra::enable_preemption(); | ||||
|     if (to_run) { | ||||
|         debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id); | ||||
|         OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); | ||||
|         if (to_run->priority > self->priority) { | ||||
|             Multilibultra::swap_to_thread(PASS_RDRAM to_run); | ||||
|         } else { | ||||
|             Multilibultra::schedule_running_thread(to_run); | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										22
									
								
								portultra/misc_ultra.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								portultra/misc_ultra.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| #include "ultra64.h" | ||||
| 
 | ||||
| #define	K0BASE		0x80000000 | ||||
| #define	K1BASE		0xA0000000 | ||||
| #define	K2BASE		0xC0000000 | ||||
| #define	IS_KSEG0(x)	((u32)(x) >= K0BASE && (u32)(x) < K1BASE) | ||||
| #define	IS_KSEG1(x)	((u32)(x) >= K1BASE && (u32)(x) < K2BASE) | ||||
| #define	K0_TO_PHYS(x)	((u32)(x)&0x1FFFFFFF)	/* kseg0 to physical */ | ||||
| #define	K1_TO_PHYS(x)	((u32)(x)&0x1FFFFFFF)	/* kseg1 to physical */ | ||||
| 
 | ||||
| u32 osVirtualToPhysical(PTR(void) addr) { | ||||
|     uintptr_t addr_val = (uintptr_t)addr; | ||||
|     if (IS_KSEG0(addr_val)) { | ||||
|         return K0_TO_PHYS(addr_val); | ||||
|     } else if (IS_KSEG1(addr_val)) { | ||||
|         return K1_TO_PHYS(addr_val); | ||||
|     } else { | ||||
|         // TODO handle TLB mappings
 | ||||
|         return (u32)addr_val; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										75
									
								
								portultra/multilibultra.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								portultra/multilibultra.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | |||
| #ifndef __MULTILIBULTRA_HPP__ | ||||
| #define __MULTILIBULTRA_HPP__ | ||||
| 
 | ||||
| #include <thread> | ||||
| #include <atomic> | ||||
| #include <mutex> | ||||
| #include <algorithm> | ||||
| 
 | ||||
| #include "ultra64.h" | ||||
| #include "platform_specific.h" | ||||
| 
 | ||||
| struct UltraThreadContext { | ||||
|     std::thread host_thread; | ||||
|     std::atomic_bool running; | ||||
|     std::atomic_bool initialized; | ||||
| }; | ||||
| 
 | ||||
| namespace Multilibultra { | ||||
| 
 | ||||
| // We need a place in rdram to hold the PI handles, so pick an address in extended rdram
 | ||||
| constexpr int32_t cart_handle = 0x80800000; | ||||
| constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); | ||||
| constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
 | ||||
| 
 | ||||
| void preinit(uint8_t* rdram, uint8_t* rom); | ||||
| void save_init(); | ||||
| void native_init(); | ||||
| void init_scheduler(); | ||||
| void init_events(uint8_t* rdram, uint8_t* rom); | ||||
| void init_timers(RDRAM_ARG1); | ||||
| void native_thread_init(OSThread *t); | ||||
| void set_self_paused(RDRAM_ARG1); | ||||
| void wait_for_resumed(RDRAM_ARG1); | ||||
| void swap_to_thread(RDRAM_ARG OSThread *to); | ||||
| void pause_thread_impl(OSThread *t); | ||||
| void pause_thread_native_impl(OSThread *t); | ||||
| void resume_thread_impl(OSThread *t); | ||||
| void resume_thread_native_impl(OSThread *t); | ||||
| void schedule_running_thread(OSThread *t); | ||||
| void stop_thread(OSThread *t); | ||||
| void pause_self(RDRAM_ARG1); | ||||
| void cleanup_thread(OSThread *t); | ||||
| PTR(OSThread) this_thread(); | ||||
| void disable_preemption(); | ||||
| void enable_preemption(); | ||||
| void notify_scheduler(); | ||||
| void reprioritize_thread(OSThread *t, OSPri pri); | ||||
| void set_main_thread(); | ||||
| bool is_game_thread(); | ||||
| void submit_rsp_task(RDRAM_ARG PTR(OSTask) task); | ||||
| void send_si_message(); | ||||
| uint32_t get_speed_multiplier(); | ||||
| std::chrono::system_clock::time_point get_start(); | ||||
| std::chrono::system_clock::duration time_since_start(); | ||||
| void init_audio(); | ||||
| void set_audio_frequency(uint32_t freq); | ||||
| void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count); | ||||
| uint32_t get_remaining_audio_bytes(); | ||||
| 
 | ||||
| class preemption_guard { | ||||
| public: | ||||
|     preemption_guard(); | ||||
|     ~preemption_guard(); | ||||
| private: | ||||
|     std::lock_guard<std::mutex> lock; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Multilibultra
 | ||||
| 
 | ||||
| #define MIN(a, b) ((a) < (b) ? (a) : (b)) | ||||
| 
 | ||||
| #define debug_printf(...) | ||||
| //#define debug_printf(...) printf(__VA_ARGS__);
 | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										32
									
								
								portultra/platform_specific.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								portultra/platform_specific.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| #ifndef __PLATFORM_SPECIFIC_H__ | ||||
| #define __PLATFORM_SPECIFIC_H__ | ||||
| 
 | ||||
| #if defined(__linux__) | ||||
| 
 | ||||
| //#include <pthread.h>
 | ||||
| //
 | ||||
| //typedef struct {
 | ||||
| //    pthread_t t;
 | ||||
| //    pthread_barrier_t pause_barrier;
 | ||||
| //    pthread_mutex_t pause_mutex;
 | ||||
| //    pthread_cond_t pause_cond;
 | ||||
| //    void (*entrypoint)(void *);
 | ||||
| //    void *arg;
 | ||||
| //} OSThreadNative;
 | ||||
| 
 | ||||
| #elif defined(_WIN32) | ||||
| 
 | ||||
| //#include <pthread.h>
 | ||||
| //
 | ||||
| //typedef struct {
 | ||||
| //    pthread_t t;
 | ||||
| //    pthread_barrier_t pause_barrier;
 | ||||
| //    pthread_mutex_t pause_mutex;
 | ||||
| //    pthread_cond_t pause_cond;
 | ||||
| //    void (*entrypoint)(void*);
 | ||||
| //    void* arg;
 | ||||
| //} OSThreadNative;
 | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										83
									
								
								portultra/port_main.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								portultra/port_main.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | |||
| #if 0 | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include "ultra64.h" | ||||
| 
 | ||||
| #define THREAD_STACK_SIZE 0x1000 | ||||
| 
 | ||||
| u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16); | ||||
| u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16); | ||||
| u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16); | ||||
| u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16); | ||||
| 
 | ||||
| OSThread idle_thread; | ||||
| OSThread main_thread; | ||||
| OSThread thread3; | ||||
| OSThread thread4; | ||||
| 
 | ||||
| OSMesgQueue queue; | ||||
| OSMesg buf[1]; | ||||
| 
 | ||||
| void thread3_func(UNUSED void *arg) { | ||||
|     OSMesg val; | ||||
|     printf("Thread3 recv\n"); | ||||
|     fflush(stdout); | ||||
|     osRecvMesg(&queue, &val, OS_MESG_BLOCK); | ||||
|     printf("Thread3 complete: %d\n", (int)(intptr_t)val); | ||||
|     fflush(stdout); | ||||
| } | ||||
| 
 | ||||
| void thread4_func(void *arg) { | ||||
|     printf("Thread4 send %d\n", (int)(intptr_t)arg); | ||||
|     fflush(stdout); | ||||
|     osSendMesg(&queue, arg, OS_MESG_BLOCK); | ||||
|     printf("Thread4 complete\n"); | ||||
|     fflush(stdout); | ||||
| } | ||||
| 
 | ||||
| void main_thread_func(UNUSED void* arg) { | ||||
|     osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0])); | ||||
| 
 | ||||
|     printf("main thread creating thread 3\n"); | ||||
|     osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14); | ||||
|     printf("main thread starting thread 3\n"); | ||||
|     osStartThread(&thread3); | ||||
|      | ||||
|     printf("main thread creating thread 4\n"); | ||||
|     osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13); | ||||
|     printf("main thread starting thread 4\n"); | ||||
|     osStartThread(&thread4); | ||||
| 
 | ||||
|     while (1) { | ||||
|         printf("main thread doin stuff\n"); | ||||
|         sleep(1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void idle_thread_func(UNUSED void* arg) { | ||||
|     printf("idle thread\n"); | ||||
|     printf("creating main thread\n"); | ||||
|     osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11); | ||||
|     printf("starting main thread\n"); | ||||
|     osStartThread(&main_thread); | ||||
| 
 | ||||
|     // Set this thread's priority to 0, making it the idle thread
 | ||||
|     osSetThreadPri(NULL, 0); | ||||
| 
 | ||||
|     // idle
 | ||||
|     while (1) { | ||||
|         printf("idle thread doin stuff\n"); | ||||
|         sleep(1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void bootproc(void) { | ||||
|     osInitialize(); | ||||
|      | ||||
|     osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127); | ||||
|     printf("Starting idle thread\n"); | ||||
|     osStartThread(&idle_thread); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										286
									
								
								portultra/scheduler.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								portultra/scheduler.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,286 @@ | |||
| #include <thread> | ||||
| #include <queue> | ||||
| #include <atomic> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "multilibultra.hpp" | ||||
| 
 | ||||
| class OSThreadComparator { | ||||
| public: | ||||
|     bool operator() (OSThread *a, OSThread *b) const { | ||||
|         return a->priority < b->priority; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| class thread_queue_t : public std::priority_queue<OSThread*, std::vector<OSThread*>, OSThreadComparator> { | ||||
| public: | ||||
|     // TODO comment this
 | ||||
|     bool remove(const OSThread* value) { | ||||
|         auto it = std::find(this->c.begin(), this->c.end(), value); | ||||
|      | ||||
|         if (it == this->c.end()) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (it == this->c.begin()) { | ||||
|             // deque the top element
 | ||||
|             this->pop(); | ||||
|         } else { | ||||
|             // remove element and re-heap
 | ||||
|             this->c.erase(it); | ||||
|             std::make_heap(this->c.begin(), this->c.end(), this->comp); | ||||
|         } | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static struct { | ||||
|     std::vector<OSThread*> to_schedule; | ||||
|     std::vector<OSThread*> to_stop; | ||||
|     std::vector<OSThread*> to_cleanup; | ||||
|     std::vector<std::pair<OSThread*, OSPri>> to_reprioritize; | ||||
|     std::mutex mutex; | ||||
|     // OSThread* running_thread;
 | ||||
|     std::atomic_int notify_count; | ||||
|     std::atomic_int action_count; | ||||
| 
 | ||||
|     bool can_preempt; | ||||
|     std::mutex premption_mutex; | ||||
| } scheduler_context{}; | ||||
| 
 | ||||
| void handle_thread_queueing(thread_queue_t& running_thread_queue) { | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
| 
 | ||||
|     if (!scheduler_context.to_schedule.empty()) { | ||||
|         OSThread* to_schedule = scheduler_context.to_schedule.back(); | ||||
|         scheduler_context.to_schedule.pop_back(); | ||||
|         scheduler_context.action_count.fetch_sub(1); | ||||
|         debug_printf("[Scheduler] Scheduling thread %d\n", to_schedule->id); | ||||
|         running_thread_queue.push(to_schedule); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void handle_thread_stopping(thread_queue_t& running_thread_queue) { | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
| 
 | ||||
|     while (!scheduler_context.to_stop.empty()) { | ||||
|         OSThread* to_stop = scheduler_context.to_stop.back(); | ||||
|         scheduler_context.to_stop.pop_back(); | ||||
|         scheduler_context.action_count.fetch_sub(1); | ||||
|         debug_printf("[Scheduler] Stopping thread %d\n", to_stop->id); | ||||
|         running_thread_queue.remove(to_stop); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void handle_thread_cleanup(thread_queue_t& running_thread_queue) { | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
| 
 | ||||
|     while (!scheduler_context.to_cleanup.empty()) { | ||||
|         OSThread* to_cleanup = scheduler_context.to_cleanup.back(); | ||||
|         scheduler_context.to_cleanup.pop_back(); | ||||
|         scheduler_context.action_count.fetch_sub(1); | ||||
|          | ||||
|         debug_printf("[Scheduler] Destroying thread %d\n", to_cleanup->id); | ||||
|         running_thread_queue.remove(to_cleanup); | ||||
|         to_cleanup->context->host_thread.join(); | ||||
|         delete to_cleanup->context; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void handle_thread_reprioritization(thread_queue_t& running_thread_queue) { | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
| 
 | ||||
|     while (!scheduler_context.to_reprioritize.empty()) { | ||||
|         const std::pair<OSThread*, OSPri> to_reprioritize = scheduler_context.to_reprioritize.back(); | ||||
|         scheduler_context.to_reprioritize.pop_back(); | ||||
|         scheduler_context.action_count.fetch_sub(1); | ||||
|          | ||||
|         debug_printf("[Scheduler] Reprioritizing thread %d to %d\n", to_reprioritize.first->id, to_reprioritize.second); | ||||
|         running_thread_queue.remove(to_reprioritize.first); | ||||
|         to_reprioritize.first->priority = to_reprioritize.second; | ||||
|         running_thread_queue.push(to_reprioritize.first); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void handle_scheduler_notifications() { | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
|     int32_t notify_count = scheduler_context.notify_count.exchange(0); | ||||
|     if (notify_count) { | ||||
|         debug_printf("Received %d notifications\n", notify_count); | ||||
|         scheduler_context.action_count.fetch_sub(notify_count); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread) { | ||||
|     if (running_thread_queue.size() > 0) { | ||||
|         OSThread* new_running_thread = running_thread_queue.top(); | ||||
|         if (cur_running_thread != new_running_thread) { | ||||
|             if (cur_running_thread && cur_running_thread->state == OSThreadState::RUNNING) { | ||||
|                 debug_printf("[Scheduler] Need to wait for thread %d to pause itself\n", cur_running_thread->id); | ||||
|                 return; | ||||
|                 //debug_printf("[Scheduler] Switching execution from thread %d (%d) to thread %d (%d)\n",
 | ||||
|                 //    cur_running_thread->id, cur_running_thread->priority,
 | ||||
|                 //    new_running_thread->id, new_running_thread->priority);
 | ||||
|                 //Multilibultra::pause_thread_impl(cur_running_thread);
 | ||||
|             } else { | ||||
|                 debug_printf("[Scheduler] Switching execution to thread %d (%d)\n", new_running_thread->id, new_running_thread->priority); | ||||
|             } | ||||
|             Multilibultra::resume_thread_impl(new_running_thread); | ||||
|             cur_running_thread = new_running_thread; | ||||
|         } else if (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING) { | ||||
|             Multilibultra::resume_thread_impl(cur_running_thread); | ||||
|         } | ||||
|     } else { | ||||
|         cur_running_thread = nullptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void scheduler_func() { | ||||
|     thread_queue_t running_thread_queue{}; | ||||
|     OSThread* cur_running_thread = nullptr; | ||||
| 
 | ||||
|     while (true) { | ||||
|         OSThread* old_running_thread = cur_running_thread; | ||||
|         scheduler_context.action_count.wait(0); | ||||
| 
 | ||||
|         std::lock_guard lock{scheduler_context.premption_mutex}; | ||||
|          | ||||
|         // Handle notifications
 | ||||
|         handle_scheduler_notifications(); | ||||
| 
 | ||||
|         // Handle stopping threads
 | ||||
|         handle_thread_stopping(running_thread_queue); | ||||
| 
 | ||||
|         // Handle cleaning up threads
 | ||||
|         handle_thread_cleanup(running_thread_queue); | ||||
| 
 | ||||
|         // Handle queueing threads to run
 | ||||
|         handle_thread_queueing(running_thread_queue); | ||||
| 
 | ||||
|         // Handle threads that have changed priority
 | ||||
|         handle_thread_reprioritization(running_thread_queue); | ||||
| 
 | ||||
|         // Determine which thread to run, stopping the current running thread if necessary
 | ||||
|         swap_running_thread(running_thread_queue, cur_running_thread); | ||||
| 
 | ||||
|         std::this_thread::yield(); | ||||
|         if (old_running_thread != cur_running_thread && old_running_thread && cur_running_thread) { | ||||
|             debug_printf("[Scheduler] Swapped from Thread %d (%d) to Thread %d (%d)\n", | ||||
|                 old_running_thread->id, old_running_thread->priority, cur_running_thread->id, cur_running_thread->priority); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void do_yield() { | ||||
|     std::this_thread::yield(); | ||||
| } | ||||
| 
 | ||||
| namespace Multilibultra { | ||||
| 
 | ||||
| void init_scheduler() { | ||||
|     scheduler_context.can_preempt = true; | ||||
|     std::thread scheduler_thread{scheduler_func}; | ||||
|     scheduler_thread.detach(); | ||||
| } | ||||
| 
 | ||||
| void schedule_running_thread(OSThread *t) { | ||||
|     debug_printf("[Scheduler] Queuing Thread %d to be scheduled\n", t->id); | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
|     scheduler_context.to_schedule.push_back(t); | ||||
|     scheduler_context.action_count.fetch_add(1); | ||||
|     scheduler_context.action_count.notify_all(); | ||||
| } | ||||
| 
 | ||||
| void swap_to_thread(RDRAM_ARG OSThread *to) { | ||||
|     OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); | ||||
|     debug_printf("[Scheduler] Scheduling swap from thread %d to %d\n", self->id, to->id); | ||||
|     { | ||||
|         std::lock_guard lock{scheduler_context.mutex}; | ||||
|         scheduler_context.to_schedule.push_back(to); | ||||
|         Multilibultra::set_self_paused(PASS_RDRAM1); | ||||
|         scheduler_context.action_count.fetch_add(1); | ||||
|         scheduler_context.action_count.notify_all(); | ||||
|     } | ||||
|     Multilibultra::wait_for_resumed(PASS_RDRAM1); | ||||
| } | ||||
| 
 | ||||
| void reprioritize_thread(OSThread *t, OSPri pri) { | ||||
|     debug_printf("[Scheduler] Adjusting Thread %d priority to %d\n", t->id, pri); | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
|     scheduler_context.to_reprioritize.emplace_back(t, pri); | ||||
|     scheduler_context.action_count.fetch_add(1); | ||||
|     scheduler_context.action_count.notify_all(); | ||||
| } | ||||
| 
 | ||||
| void pause_self(RDRAM_ARG1) { | ||||
|     OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread()); | ||||
|     debug_printf("[Scheduler] Thread %d pausing itself\n", self->id); | ||||
|     { | ||||
|         std::lock_guard lock{scheduler_context.mutex}; | ||||
|         Multilibultra::set_self_paused(PASS_RDRAM1); | ||||
|         scheduler_context.to_stop.push_back(self); | ||||
|         scheduler_context.action_count.fetch_add(1); | ||||
|         scheduler_context.action_count.notify_all(); | ||||
|     } | ||||
|     Multilibultra::wait_for_resumed(PASS_RDRAM1); | ||||
| } | ||||
| 
 | ||||
| void stop_thread(OSThread *t) { | ||||
|     debug_printf("[Scheduler] Queuing Thread %d to be stopped\n", t->id); | ||||
|     { | ||||
|         std::lock_guard lock{scheduler_context.mutex}; | ||||
|         scheduler_context.to_stop.push_back(t); | ||||
|         scheduler_context.action_count.fetch_add(1); | ||||
|         scheduler_context.action_count.notify_all(); | ||||
|     } | ||||
|     Multilibultra::pause_thread_impl(t); | ||||
| } | ||||
| 
 | ||||
| void cleanup_thread(OSThread *t) { | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
|     scheduler_context.to_cleanup.push_back(t); | ||||
|     scheduler_context.action_count.fetch_add(1); | ||||
|     scheduler_context.action_count.notify_all(); | ||||
| } | ||||
| 
 | ||||
| void disable_preemption() { | ||||
|     scheduler_context.premption_mutex.lock(); | ||||
|     if (Multilibultra::is_game_thread()) { | ||||
|         scheduler_context.can_preempt = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void enable_preemption() { | ||||
|     if (Multilibultra::is_game_thread()) { | ||||
|         scheduler_context.can_preempt = true; | ||||
|     } | ||||
| #pragma warning(push) | ||||
| #pragma warning( disable : 26110) | ||||
|     scheduler_context.premption_mutex.unlock(); | ||||
| #pragma warning( pop )  | ||||
| } | ||||
| 
 | ||||
| // lock's constructor is called first, so can_preempt is set after locking
 | ||||
| preemption_guard::preemption_guard() : lock{scheduler_context.premption_mutex} { | ||||
|     scheduler_context.can_preempt = false; | ||||
| } | ||||
| 
 | ||||
| // lock's destructor is called last, so can_preempt is set before unlocking
 | ||||
| preemption_guard::~preemption_guard() { | ||||
|     scheduler_context.can_preempt = true; | ||||
| } | ||||
| 
 | ||||
| void notify_scheduler() { | ||||
|     std::lock_guard lock{scheduler_context.mutex}; | ||||
|     scheduler_context.notify_count.fetch_add(1); | ||||
|     scheduler_context.action_count.fetch_add(1); | ||||
|     scheduler_context.action_count.notify_all(); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void pause_self(uint8_t* rdram) { | ||||
|     Multilibultra::pause_self(rdram); | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										60
									
								
								portultra/task_pthreads.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								portultra/task_pthreads.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| #ifndef _WIN32 | ||||
| 
 | ||||
| // #include <thread>
 | ||||
| // #include <stdexcept>
 | ||||
| // #include <atomic>
 | ||||
| 
 | ||||
| #include <pthread.h> | ||||
| #include <signal.h> | ||||
| #include <limits.h> | ||||
| 
 | ||||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| 
 | ||||
| constexpr int pause_thread_signum = SIGUSR1; | ||||
| 
 | ||||
| // void cleanup_current_thread(OSThread *t) {
 | ||||
| //     debug_printf("Thread cleanup %d\n", t->id);
 | ||||
| 
 | ||||
| //     // delete t->context;
 | ||||
| // }
 | ||||
| 
 | ||||
| void sig_handler(int signum, siginfo_t *info, void *context) { | ||||
|     if (signum == pause_thread_signum) { | ||||
|         OSThread *t = Multilibultra::this_thread(); | ||||
| 
 | ||||
|         debug_printf("[Sig] Thread %d paused\n", t->id); | ||||
| 
 | ||||
|         // Wait until the thread is marked as running again
 | ||||
|         t->context->running.wait(false); | ||||
| 
 | ||||
|         debug_printf("[Sig] Thread %d resumed\n", t->id); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::native_init(void) { | ||||
|     // Set up a signal handler to capture pause signals
 | ||||
|     struct sigaction sigact; | ||||
|     sigemptyset(&sigact.sa_mask); | ||||
|     sigact.sa_flags = SA_SIGINFO | SA_RESTART; | ||||
|     sigact.sa_sigaction = sig_handler; | ||||
| 
 | ||||
|     sigaction(pause_thread_signum, &sigact, nullptr); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::native_thread_init(OSThread *t) { | ||||
|     debug_printf("[Native] Init thread %d\n", t->id); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::pause_thread_native_impl(OSThread *t) { | ||||
|     debug_printf("[Native] Pause thread %d\n", t->id); | ||||
|     // Send a pause signal to the thread, which will trigger it to wait on its pause barrier in the signal handler
 | ||||
|     pthread_kill(t->context->host_thread.native_handle(), pause_thread_signum); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) { | ||||
|     debug_printf("[Native] Resume thread %d\n", t->id); | ||||
|     // Nothing to do here
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										32
									
								
								portultra/task_win32.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								portultra/task_win32.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| #include <Windows.h> | ||||
| 
 | ||||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| 
 | ||||
| extern "C" unsigned int sleep(unsigned int seconds) { | ||||
|     Sleep(seconds * 1000); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::native_init(void) { | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::native_thread_init(OSThread *t) { | ||||
|     debug_printf("[Native] Init thread %d\n", t->id); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::pause_thread_native_impl(OSThread *t) { | ||||
|     debug_printf("[Native] Pause thread %d\n", t->id); | ||||
|     // Pause the thread via the win32 API
 | ||||
|     SuspendThread(t->context->host_thread.native_handle()); | ||||
|     // Perform a synchronous action to ensure that the thread is suspended
 | ||||
|     // see: https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743
 | ||||
|     CONTEXT threadContext{}; | ||||
|     GetThreadContext(t->context->host_thread.native_handle(), &threadContext); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) { | ||||
|     debug_printf("[Native] Resume thread %d\n", t->id); | ||||
|     // Resume the thread
 | ||||
|     ResumeThread(t->context->host_thread.native_handle()); | ||||
| } | ||||
							
								
								
									
										193
									
								
								portultra/threads.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								portultra/threads.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,193 @@ | |||
| #include <cstdio> | ||||
| #include <thread> | ||||
| #include <cassert> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| 
 | ||||
| // Native APIs only used to set thread names for easier debugging
 | ||||
| #ifdef _WIN32 | ||||
| #include <Windows.h> | ||||
| #endif | ||||
| 
 | ||||
| extern "C" void bootproc(); | ||||
| 
 | ||||
| thread_local bool is_main_thread = false; | ||||
| // Whether this thread is part of the game (i.e. the start thread or one spawned by osCreateThread)
 | ||||
| thread_local bool is_game_thread = false; | ||||
| thread_local PTR(OSThread) thread_self = NULLPTR; | ||||
| 
 | ||||
| void Multilibultra::set_main_thread() { | ||||
|     ::is_game_thread = true; | ||||
|     is_main_thread = true; | ||||
| } | ||||
| 
 | ||||
| bool Multilibultra::is_game_thread() { | ||||
|     return ::is_game_thread; | ||||
| } | ||||
| 
 | ||||
| #if 0 | ||||
| int main(int argc, char** argv) { | ||||
|     Multilibultra::set_main_thread(); | ||||
| 
 | ||||
|     bootproc(); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #if 1 | ||||
| void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg); | ||||
| #else | ||||
| #define run_thread_function(func, sp, arg) func(arg) | ||||
| #endif | ||||
| 
 | ||||
| static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) { | ||||
|     OSThread *self = TO_PTR(OSThread, self_); | ||||
|     debug_printf("[Thread] Thread created: %d\n", self->id); | ||||
|     thread_self = self_; | ||||
|     is_game_thread = true; | ||||
| 
 | ||||
|     // Set the thread name
 | ||||
| #ifdef _WIN32 | ||||
|     std::wstring thread_name = L"Game Thread " + std::to_wstring(self->id); | ||||
|     HRESULT r; | ||||
|     r = SetThreadDescription( | ||||
|         GetCurrentThread(), | ||||
|         thread_name.c_str() | ||||
|     ); | ||||
| #endif | ||||
| 
 | ||||
|     // Perform any necessary native thread initialization.
 | ||||
|     Multilibultra::native_thread_init(self); | ||||
| 
 | ||||
|     // Set initialized to false to indicate that this thread can be started.
 | ||||
|     self->context->initialized.store(true); | ||||
|     self->context->initialized.notify_all(); | ||||
| 
 | ||||
|     debug_printf("[Thread] Thread waiting to be started: %d\n", self->id); | ||||
| 
 | ||||
|     // Wait until the thread is marked as running.
 | ||||
|     Multilibultra::set_self_paused(PASS_RDRAM1); | ||||
|     Multilibultra::wait_for_resumed(PASS_RDRAM1); | ||||
| 
 | ||||
|     debug_printf("[Thread] Thread started: %d\n", self->id); | ||||
| 
 | ||||
|     // Run the thread's function with the provided argument.
 | ||||
|     run_thread_function(PASS_RDRAM entrypoint, self->sp, arg); | ||||
| 
 | ||||
|     // Dispose of this thread after it completes.
 | ||||
|     Multilibultra::cleanup_thread(self); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) { | ||||
|     OSThread* t = TO_PTR(OSThread, t_); | ||||
|     debug_printf("[os] Start Thread %d\n", t->id); | ||||
| 
 | ||||
|     // Wait until the thread is initialized to indicate that it's action_queued to be started.
 | ||||
|     t->context->initialized.wait(false); | ||||
| 
 | ||||
|     debug_printf("[os] Thread %d is ready to be started\n", t->id); | ||||
| 
 | ||||
|     if (thread_self && (t->priority > TO_PTR(OSThread, thread_self)->priority)) { | ||||
|         Multilibultra::swap_to_thread(PASS_RDRAM t); | ||||
|     } else { | ||||
|         Multilibultra::schedule_running_thread(t); | ||||
|     } | ||||
| 
 | ||||
|     // The main thread "becomes" the first thread started, so join on it and exit after it completes.
 | ||||
|     if (is_main_thread) { | ||||
|         t->context->host_thread.join(); | ||||
|         std::exit(EXIT_SUCCESS); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) { | ||||
|     debug_printf("[os] Create Thread %d\n", id); | ||||
|     OSThread *t = TO_PTR(OSThread, t_); | ||||
|      | ||||
|     t->next = NULLPTR; | ||||
|     t->priority = pri; | ||||
|     t->id = id; | ||||
|     t->state = OSThreadState::PAUSED; | ||||
|     t->sp = sp - 0x10; // Set up the first stack frame
 | ||||
| 
 | ||||
|     // Spawn a new thread, which will immediately pause itself and wait until it's been started.
 | ||||
|     t->context = new UltraThreadContext{}; | ||||
|     t->context->initialized.store(false); | ||||
|     t->context->running.store(false); | ||||
| 
 | ||||
|     t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg}; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) { | ||||
|     if (t == NULLPTR) { | ||||
|         t = thread_self; | ||||
|     } | ||||
|     bool pause_self = false; | ||||
|     if (pri > TO_PTR(OSThread, thread_self)->priority) { | ||||
|         pause_self = true; | ||||
|         Multilibultra::set_self_paused(PASS_RDRAM1); | ||||
|     } else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) { | ||||
|         pause_self = true; | ||||
|         Multilibultra::set_self_paused(PASS_RDRAM1); | ||||
|     } | ||||
|     Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri); | ||||
|     if (pause_self) { | ||||
|         Multilibultra::wait_for_resumed(PASS_RDRAM1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) t) { | ||||
|     if (t == NULLPTR) { | ||||
|         t = thread_self; | ||||
|     } | ||||
|     return TO_PTR(OSThread, t)->priority; | ||||
| } | ||||
| 
 | ||||
| extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) { | ||||
|     if (t == NULLPTR) { | ||||
|         t = thread_self; | ||||
|     } | ||||
|     return TO_PTR(OSThread, t)->id; | ||||
| } | ||||
| 
 | ||||
| // TODO yield thread, need a stable priority queue in the scheduler
 | ||||
| 
 | ||||
| void Multilibultra::set_self_paused(RDRAM_ARG1) { | ||||
|     debug_printf("[Thread] Thread pausing itself: %d\n", TO_PTR(OSThread, thread_self)->id); | ||||
|     TO_PTR(OSThread, thread_self)->state = OSThreadState::PAUSED; | ||||
|     TO_PTR(OSThread, thread_self)->context->running.store(false); | ||||
|     TO_PTR(OSThread, thread_self)->context->running.notify_all(); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::wait_for_resumed(RDRAM_ARG1) { | ||||
|     TO_PTR(OSThread, thread_self)->context->running.wait(false); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::pause_thread_impl(OSThread* t) { | ||||
|     t->state = OSThreadState::PREEMPTED; | ||||
|     t->context->running.store(false); | ||||
|     t->context->running.notify_all(); | ||||
|     Multilibultra::pause_thread_native_impl(t); | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::resume_thread_impl(OSThread *t) { | ||||
|     if (t->state == OSThreadState::PREEMPTED) { | ||||
|         Multilibultra::resume_thread_native_impl(t); | ||||
|     } | ||||
|     t->state = OSThreadState::RUNNING; | ||||
|     t->context->running.store(true); | ||||
|     t->context->running.notify_all(); | ||||
| } | ||||
| 
 | ||||
| PTR(OSThread) Multilibultra::this_thread() { | ||||
|     return thread_self; | ||||
| } | ||||
							
								
								
									
										192
									
								
								portultra/timer.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								portultra/timer.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,192 @@ | |||
| #include <thread> | ||||
| #include <variant> | ||||
| #include <set> | ||||
| #include "blockingconcurrentqueue.h" | ||||
| 
 | ||||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| // Start time for the program
 | ||||
| static std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); | ||||
| // Game speed multiplier (1 means no speedup)
 | ||||
| constexpr uint32_t speed_multiplier = 1; | ||||
| // N64 CPU counter ticks per millisecond
 | ||||
| constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier; | ||||
| 
 | ||||
| struct OSTimer { | ||||
|     PTR(OSTimer) unused1; | ||||
|     PTR(OSTimer) unused2; | ||||
|     OSTime interval; | ||||
|     OSTime timestamp; | ||||
|     PTR(OSMesgQueue) mq; | ||||
|     OSMesg msg; | ||||
| }; | ||||
| 
 | ||||
| struct AddTimerAction { | ||||
|     PTR(OSTask) timer; | ||||
| }; | ||||
| 
 | ||||
| struct RemoveTimerAction { | ||||
|     PTR(OSTimer) timer; | ||||
| }; | ||||
| 
 | ||||
| using Action = std::variant<AddTimerAction, RemoveTimerAction>; | ||||
| 
 | ||||
| struct { | ||||
|     std::thread thread; | ||||
|     moodycamel::BlockingConcurrentQueue<Action> action_queue{}; | ||||
| } timer_context; | ||||
| 
 | ||||
| uint64_t duration_to_ticks(std::chrono::system_clock::duration duration) { | ||||
|     uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count(); | ||||
|     // More accurate than using a floating point timer, will only overflow after running for 12.47 years
 | ||||
|     // Units: (micros * (counts/millis)) / (micros/millis) = counts
 | ||||
|     uint64_t total_count = (delta_micros * counter_per_ms) / 1000; | ||||
| 
 | ||||
|     return total_count; | ||||
| } | ||||
| 
 | ||||
| std::chrono::microseconds ticks_to_duration(uint64_t ticks) { | ||||
|     using namespace std::chrono_literals; | ||||
|     return ticks * 1000us / counter_per_ms; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point ticks_to_timepoint(uint64_t ticks) { | ||||
|     return start + ticks_to_duration(ticks); | ||||
| } | ||||
| 
 | ||||
| uint64_t time_now() { | ||||
|     return duration_to_ticks(std::chrono::system_clock::now() - start); | ||||
| } | ||||
| 
 | ||||
| void timer_thread(RDRAM_ARG1) { | ||||
|     // Lambda comparator function to keep the set ordered
 | ||||
|     auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) { | ||||
|         OSTimer* a = TO_PTR(OSTimer, a_); | ||||
|         OSTimer* b = TO_PTR(OSTimer, b_); | ||||
| 
 | ||||
|         // Order by timestamp if the timers have different timestamps
 | ||||
|         if (a->timestamp != b->timestamp) { | ||||
|             return a->timestamp < b->timestamp; | ||||
|         } | ||||
| 
 | ||||
|         // If they have the exact same timestamp then order by address instead
 | ||||
|         return a < b; | ||||
|     }; | ||||
| 
 | ||||
|     // Ordered set of timers that are currently active
 | ||||
|     std::set<PTR(OSTimer), decltype(timer_sort)> active_timers{timer_sort}; | ||||
|      | ||||
|     // Lambda to process a timer action to handle adding and removing timers
 | ||||
|     auto process_timer_action = [&](const Action& action) { | ||||
|         // Determine the action type and act on it
 | ||||
|         if (const auto* add_action = std::get_if<AddTimerAction>(&action)) { | ||||
|             active_timers.insert(add_action->timer); | ||||
|         } else if (const auto* remove_action = std::get_if<RemoveTimerAction>(&action)) { | ||||
|             active_timers.erase(remove_action->timer); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     while (true) { | ||||
|         // Empty the action queue
 | ||||
|         Action cur_action; | ||||
|         while (timer_context.action_queue.try_dequeue(cur_action)) { | ||||
|             process_timer_action(cur_action); | ||||
|         } | ||||
| 
 | ||||
|         // If there's no timer to act on, wait for one to come in from the action queue
 | ||||
|         while (active_timers.empty()) { | ||||
|             timer_context.action_queue.wait_dequeue(cur_action); | ||||
|             process_timer_action(cur_action); | ||||
|         } | ||||
| 
 | ||||
|         // Get the timer that's closest to running out
 | ||||
|         PTR(OSTimer) cur_timer_ = *active_timers.begin(); | ||||
|         OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_); | ||||
| 
 | ||||
|         // Remove the timer from the queue (it may get readded if waiting is interrupted)
 | ||||
|         active_timers.erase(cur_timer_); | ||||
| 
 | ||||
|         // Determine how long to wait to reach the timer's timestamp
 | ||||
|         auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::system_clock::now(); | ||||
|         auto wait_us = std::chrono::duration_cast<std::chrono::microseconds>(wait_duration); | ||||
| 
 | ||||
|         // Wait for either the duration to complete or a new action to come through
 | ||||
|         if (timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) { | ||||
|             // Timer was interrupted by a new action 
 | ||||
|             // Add the current timer back to the queue (done first in case the action is to remove this timer)
 | ||||
|             active_timers.insert(cur_timer_); | ||||
|             // Process the new action
 | ||||
|             process_timer_action(cur_action); | ||||
|         } else { | ||||
|             // Waiting for the timer completed, so send the timer's message to its message queue
 | ||||
|             osSendMesg(PASS_RDRAM cur_timer->mq, cur_timer->msg, OS_MESG_NOBLOCK); | ||||
|             // If the timer has a specified interval then reload it with that value
 | ||||
|             if (cur_timer->interval != 0) { | ||||
|                 cur_timer->timestamp = cur_timer->interval + time_now(); | ||||
|                 active_timers.insert(cur_timer_); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::init_timers(RDRAM_ARG1) { | ||||
|     timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 }; | ||||
| } | ||||
| 
 | ||||
| uint32_t Multilibultra::get_speed_multiplier() { | ||||
|     return speed_multiplier; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::time_point Multilibultra::get_start() { | ||||
|     return start; | ||||
| } | ||||
| 
 | ||||
| std::chrono::system_clock::duration Multilibultra::time_since_start() { | ||||
|     return std::chrono::system_clock::now() - start; | ||||
| } | ||||
| 
 | ||||
| extern "C" u32 osGetCount() { | ||||
|     uint64_t total_count = time_now(); | ||||
| 
 | ||||
|     // Allow for overflows, which is how osGetCount behaves
 | ||||
|     return (uint32_t)total_count; | ||||
| } | ||||
| 
 | ||||
| extern "C" OSTime osGetTime() { | ||||
|     uint64_t total_count = time_now(); | ||||
| 
 | ||||
|     return total_count; | ||||
| } | ||||
| 
 | ||||
| extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) { | ||||
|     OSTimer* t = TO_PTR(OSTimer, t_); | ||||
| 
 | ||||
|     // HACK: Skip the RCP timeout detection
 | ||||
|     if ((countdown == 140625000 || countdown == 1500000) && (uintptr_t)msg == 666) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     // Determine the time when this timer will trigger off
 | ||||
|     if (countdown == 0) { | ||||
|         // Set the timestamp based on the interval
 | ||||
|         t->timestamp = interval + time_now(); | ||||
|     } else { | ||||
|         t->timestamp = countdown + time_now(); | ||||
|     } | ||||
|     t->interval = interval; | ||||
|     t->mq = mq; | ||||
|     t->msg = msg; | ||||
| 
 | ||||
|     timer_context.action_queue.enqueue(AddTimerAction{ t_ }); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) { | ||||
|     timer_context.action_queue.enqueue(RemoveTimerAction{ t_ }); | ||||
| 
 | ||||
|     // TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										221
									
								
								portultra/ultra64.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								portultra/ultra64.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,221 @@ | |||
| #ifndef __ULTRA64_MULTILIBULTRA_H__ | ||||
| #define __ULTRA64_MULTILIBULTRA_H__ | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include "platform_specific.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| #include <queue> | ||||
| #endif | ||||
| 
 | ||||
| #ifdef __GNUC__ | ||||
| #define UNUSED __attribute__((unused)) | ||||
| #define ALIGNED(x) __attribute__((aligned(x))) | ||||
| #else | ||||
| #define UNUSED | ||||
| #define ALIGNED(x) | ||||
| #endif | ||||
| 
 | ||||
| typedef int64_t s64; | ||||
| typedef uint64_t u64; | ||||
| typedef int32_t s32; | ||||
| typedef uint32_t u32; | ||||
| typedef int16_t s16; | ||||
| typedef uint16_t u16; | ||||
| typedef int8_t s8; | ||||
| typedef uint8_t u8; | ||||
| 
 | ||||
| #define PTR(x) int32_t | ||||
| #define RDRAM_ARG uint8_t *rdram,  | ||||
| #define RDRAM_ARG1 uint8_t *rdram | ||||
| #define PASS_RDRAM rdram,  | ||||
| #define PASS_RDRAM1 rdram | ||||
| #define TO_PTR(type, var) ((type*)(&rdram[(uint64_t)var - 0xFFFFFFFF80000000])) | ||||
| #ifdef __cplusplus | ||||
| #define NULLPTR (PTR(void))0 | ||||
| #endif | ||||
| 
 | ||||
| #ifndef NULL | ||||
| #define NULL (PTR(void) 0) | ||||
| #endif | ||||
| 
 | ||||
| #define OS_MESG_NOBLOCK     0 | ||||
| #define OS_MESG_BLOCK       1 | ||||
| 
 | ||||
| typedef s32 OSPri; | ||||
| typedef s32 OSId; | ||||
| 
 | ||||
| typedef u64	OSTime; | ||||
| 
 | ||||
| #define OS_EVENT_SW1              0     /* CPU SW1 interrupt */ | ||||
| #define OS_EVENT_SW2              1     /* CPU SW2 interrupt */ | ||||
| #define OS_EVENT_CART             2     /* Cartridge interrupt: used by rmon */ | ||||
| #define OS_EVENT_COUNTER          3     /* Counter int: used by VI/Timer Mgr */ | ||||
| #define OS_EVENT_SP               4     /* SP task done interrupt */ | ||||
| #define OS_EVENT_SI               5     /* SI (controller) interrupt */ | ||||
| #define OS_EVENT_AI               6     /* AI interrupt */ | ||||
| #define OS_EVENT_VI               7     /* VI interrupt: used by VI/Timer Mgr */ | ||||
| #define OS_EVENT_PI               8     /* PI interrupt: used by PI Manager */ | ||||
| #define OS_EVENT_DP               9     /* DP full sync interrupt */ | ||||
| #define OS_EVENT_CPU_BREAK        10    /* CPU breakpoint: used by rmon */ | ||||
| #define OS_EVENT_SP_BREAK         11    /* SP breakpoint:  used by rmon */ | ||||
| #define OS_EVENT_FAULT            12    /* CPU fault event: used by rmon */ | ||||
| #define OS_EVENT_THREADSTATUS     13    /* CPU thread status: used by rmon */ | ||||
| #define OS_EVENT_PRENMI           14    /* Pre NMI interrupt */ | ||||
| 
 | ||||
| #define	M_GFXTASK	1 | ||||
| #define	M_AUDTASK	2 | ||||
| #define	M_VIDTASK	3 | ||||
| #define M_NJPEGTASK 4 | ||||
| 
 | ||||
| /////////////
 | ||||
| // Structs //
 | ||||
| /////////////
 | ||||
| 
 | ||||
| // Threads
 | ||||
| 
 | ||||
| typedef struct UltraThreadContext UltraThreadContext; | ||||
| 
 | ||||
| typedef enum { | ||||
|     RUNNING, | ||||
|     PAUSED, | ||||
|     PREEMPTED | ||||
| } OSThreadState; | ||||
| 
 | ||||
| typedef struct OSThread_t { | ||||
|     PTR(struct OSThread_t) next; // Next thread in the given queue
 | ||||
|     OSPri priority; | ||||
|     uint32_t pad1; | ||||
|     uint32_t pad2; | ||||
|     uint16_t flags; // These two are swapped to reflect rdram byteswapping
 | ||||
|     uint16_t state; | ||||
|     OSId id; | ||||
|     int32_t pad3; | ||||
|     UltraThreadContext* context; // An actual pointer regardless of platform
 | ||||
|     int32_t sp; | ||||
| } OSThread; | ||||
| 
 | ||||
| typedef u32 OSEvent; | ||||
| typedef PTR(void) OSMesg; | ||||
| 
 | ||||
| typedef struct OSMesgQueue { | ||||
|     PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */ | ||||
|     PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */  | ||||
|     s32 validCount;            /* Number of messages in the queue */ | ||||
|     s32 first;                 /* Index of the first message in the ring buffer */ | ||||
|     s32 msgCount;              /* Size of message buffer */ | ||||
|     PTR(OSMesg) msg;               /* Pointer to circular buffer to store messages */ | ||||
| } OSMesgQueue; | ||||
| 
 | ||||
| // RSP
 | ||||
| 
 | ||||
| typedef struct { | ||||
|     u32	type; | ||||
|     u32	flags; | ||||
| 
 | ||||
|     PTR(u64) ucode_boot; | ||||
|     u32	ucode_boot_size; | ||||
| 
 | ||||
|     PTR(u64) ucode; | ||||
|     u32	ucode_size; | ||||
| 
 | ||||
|     PTR(u64) ucode_data; | ||||
|     u32	ucode_data_size; | ||||
| 
 | ||||
|     PTR(u64) dram_stack; | ||||
|     u32	dram_stack_size; | ||||
| 
 | ||||
|     PTR(u64) output_buff; | ||||
|     PTR(u64) output_buff_size; | ||||
| 
 | ||||
|     PTR(u64) data_ptr; | ||||
|     u32	data_size; | ||||
| 
 | ||||
|     PTR(u64) yield_data_ptr; | ||||
|     u32	yield_data_size; | ||||
| } OSTask_t; | ||||
| 
 | ||||
| typedef union { | ||||
|     OSTask_t t; | ||||
|     int64_t force_structure_alignment; | ||||
| } OSTask; | ||||
| 
 | ||||
| // PI
 | ||||
| 
 | ||||
| struct OSIoMesgHdr { | ||||
|     // These 3 reversed due to endianness
 | ||||
|     u8 status;                 /* Return status */ | ||||
|     u8 pri;                    /* Message priority (High or Normal) */ | ||||
|     u16 type;                  /* Message type */ | ||||
|     PTR(OSMesgQueue) retQueue; /* Return message queue to notify I/O completion */ | ||||
| }; | ||||
| 
 | ||||
| struct OSIoMesg { | ||||
|     OSIoMesgHdr	hdr;    /* Message header */ | ||||
|     PTR(void) dramAddr;	/* RDRAM buffer address (DMA) */ | ||||
|     u32 devAddr;	    /* Device buffer address (DMA) */ | ||||
|     u32 size;		    /* DMA transfer size in bytes */ | ||||
|     u32 piHandle;	    /* PI device handle */ | ||||
| }; | ||||
| 
 | ||||
| struct OSPiHandle { | ||||
|     PTR(OSPiHandle_s)    unused;        /* point to next handle on the table */ | ||||
|     // These four members reversed due to endianness
 | ||||
|     u8                   relDuration;   /* domain release duration */ | ||||
|     u8                   pageSize;      /* domain page size */ | ||||
|     u8                   latency;       /* domain latency */ | ||||
|     u8                   type;          /* DEVICE_TYPE_BULK for disk */ | ||||
|     // These three members reversed due to endianness
 | ||||
|     u16                  padding;       /* struct alignment padding */ | ||||
|     u8                   domain;        /* which domain */ | ||||
|     u8                   pulse;         /* domain pulse width */ | ||||
|     u32                  baseAddress;   /* Domain address */ | ||||
|     u32                  speed;         /* for roms only */ | ||||
|     /* The following are "private" elements" */ | ||||
|     u32                  transferInfo[18];  /* for disk only */ | ||||
| }; | ||||
| 
 | ||||
| ///////////////
 | ||||
| // Functions //
 | ||||
| ///////////////
 | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif // __cplusplus
 | ||||
| 
 | ||||
| void osInitialize(void); | ||||
| 
 | ||||
| typedef void (thread_func_t)(PTR(void)); | ||||
| 
 | ||||
| void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p); | ||||
| void osStartThread(RDRAM_ARG PTR(OSThread) t); | ||||
| void osStopThread(RDRAM_ARG PTR(OSThread) t); | ||||
| void osDestroyThread(RDRAM_ARG PTR(OSThread) t); | ||||
| void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri); | ||||
| OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread); | ||||
| OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t); | ||||
| 
 | ||||
| s32 MQ_GET_COUNT(RDRAM_ARG PTR(OSMesgQueue)); | ||||
| s32 MQ_IS_EMPTY(RDRAM_ARG PTR(OSMesgQueue)); | ||||
| s32 MQ_IS_FULL(RDRAM_ARG PTR(OSMesgQueue)); | ||||
| 
 | ||||
| void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32); | ||||
| s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32); | ||||
| s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32); | ||||
| s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32); | ||||
| void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg); | ||||
| void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue), OSMesg, u32); | ||||
| void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr); | ||||
| PTR(void) osViGetNextFramebuffer(); | ||||
| PTR(void) osViGetCurrentFramebuffer(); | ||||
| u32 osGetCount(); | ||||
| OSTime osGetTime(); | ||||
| int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg); | ||||
| int osStopTimer(RDRAM_ARG PTR(OSTimer) timer); | ||||
| u32 osVirtualToPhysical(PTR(void) addr); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } // extern "C"
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										15
									
								
								portultra/ultrainit.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								portultra/ultrainit.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| #include "ultra64.h" | ||||
| #include "multilibultra.hpp" | ||||
| 
 | ||||
| void Multilibultra::preinit(uint8_t* rdram, uint8_t* rom) { | ||||
|     Multilibultra::set_main_thread(); | ||||
|     Multilibultra::init_events(rdram, rom); | ||||
|     Multilibultra::init_timers(rdram); | ||||
|     Multilibultra::init_audio(); | ||||
|     Multilibultra::save_init(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osInitialize() { | ||||
|     Multilibultra::init_scheduler(); | ||||
|     Multilibultra::native_init(); | ||||
| } | ||||
							
								
								
									
										2
									
								
								rsp/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								rsp/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| aspMain.cpp | ||||
| njpgdspMain.cpp | ||||
							
								
								
									
										29
									
								
								src/ai.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/ai.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| #include "recomp.h" | ||||
| #include <cstdio> | ||||
| #include <string> | ||||
| #include "../portultra/ultra64.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| 
 | ||||
| #define VI_NTSC_CLOCK 48681812 | ||||
| 
 | ||||
| extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     uint32_t freq = ctx->r4; | ||||
|     // This makes actual audio frequency more accurate to console, but may not be desirable
 | ||||
|     //uint32_t dacRate = (uint32_t)(((float)VI_NTSC_CLOCK / freq) + 0.5f);
 | ||||
|     //freq = VI_NTSC_CLOCK / dacRate;
 | ||||
|     ctx->r2 = freq; | ||||
|     Multilibultra::set_audio_frequency(freq); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     Multilibultra::queue_audio_buffer(rdram, ctx->r4, ctx->r5); | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osAiGetLength_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ctx->r2 = Multilibultra::get_remaining_audio_bytes(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osAiGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ctx->r2 = 0x00000000; // Pretend the audio DMAs finish instantly
 | ||||
| } | ||||
							
								
								
									
										108
									
								
								src/cont.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/cont.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | |||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| static int max_controllers = 0; | ||||
| 
 | ||||
| extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     gpr bitpattern = ctx->r5; | ||||
|     gpr status = ctx->r6; | ||||
| 
 | ||||
|     // Set bit 0 to indicate that controller 0 is present
 | ||||
|     MEM_B(0, bitpattern) = 0x01; | ||||
| 
 | ||||
|     // Mark controller 0 as present
 | ||||
|     MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
 | ||||
|     MEM_B(2, status) = 0x00; // status: 0 (from joybus)
 | ||||
|     MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
 | ||||
| 
 | ||||
|     max_controllers = 4; | ||||
| 
 | ||||
|     // Mark controllers 1-3 as not connected
 | ||||
|     for (size_t controller = 1; controller < max_controllers; controller++) { | ||||
|         // Libultra doesn't write status or type for absent controllers
 | ||||
|         MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
 | ||||
|     } | ||||
| 
 | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     Multilibultra::send_si_message(); | ||||
| } | ||||
| 
 | ||||
| struct OSContPad { | ||||
|     u16 button; | ||||
|     s8 stick_x;		/* -80 <= stick_x <= 80 */ | ||||
|     s8 stick_y;		/* -80 <= stick_y <= 80 */ | ||||
|     u8 errno_; | ||||
| }; | ||||
| 
 | ||||
| int button = 0; | ||||
| int stick_x = 0; | ||||
| int stick_y = 0; | ||||
| 
 | ||||
| void press_button(int button) { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void release_button(int button) { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     int32_t pad = (int32_t)ctx->r4; | ||||
| 
 | ||||
|     if (max_controllers > 0) { | ||||
|         // button
 | ||||
|         MEM_H(0, pad) = button; | ||||
|         // stick_x
 | ||||
|         MEM_B(2, pad) = stick_x; | ||||
|         // stick_y
 | ||||
|         MEM_B(3, pad) = stick_y; | ||||
|         // errno
 | ||||
|         MEM_B(4, pad) = 0; | ||||
|     } | ||||
|     for (int controller = 1; controller < max_controllers; controller++) { | ||||
|         MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     Multilibultra::send_si_message(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     gpr status = ctx->r4; | ||||
| 
 | ||||
|     // Mark controller 0 as present
 | ||||
|     MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
 | ||||
|     MEM_B(2, status) = 0x00; // status: 0 (from joybus)
 | ||||
|     MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
 | ||||
| 
 | ||||
|     // Mark controllers 1-3 as not connected
 | ||||
|     for (size_t controller = 1; controller < max_controllers; controller++) { | ||||
|         // Libultra doesn't write status or type for absent controllers
 | ||||
|         MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     max_controllers = std::min((unsigned int)ctx->r4, 4u); | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
							
								
								
									
										5
									
								
								src/dp.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/dp.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | |||
| #include "recomp.h" | ||||
| 
 | ||||
| extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/eep.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/eep.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| #include "recomp.h" | ||||
| 
 | ||||
| extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
							
								
								
									
										2587
									
								
								src/euc-jp.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2587
									
								
								src/euc-jp.cpp
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										11
									
								
								src/euc-jp.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/euc-jp.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| #ifndef __EUC_JP_H__ | ||||
| #define __EUC_JP_H__ | ||||
| 
 | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| 
 | ||||
| namespace Encoding { | ||||
|     std::string decode_eucjp(std::string_view src); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										137
									
								
								src/flash.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/flash.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | |||
| #include <array> | ||||
| #include <cassert> | ||||
| #include "../portultra/ultra64.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| constexpr uint32_t flash_size = 1024 * 1024 / 8; // 1Mbit
 | ||||
| constexpr uint32_t page_size = 128; | ||||
| constexpr uint32_t pages_per_sector = 128; | ||||
| constexpr uint32_t sector_size = page_size * pages_per_sector; | ||||
| constexpr uint32_t sector_count = flash_size / sector_size; | ||||
| 
 | ||||
| void save_write_ptr(const void* in, uint32_t offset, uint32_t count); | ||||
| void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); | ||||
| void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count); | ||||
| void save_clear(uint32_t start, uint32_t size); | ||||
| 
 | ||||
| std::array<char, page_size> write_buffer; | ||||
| 
 | ||||
| extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	ctx->r2 = Multilibultra::flash_handle; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	PTR(u8) flash_status = ctx->r4; | ||||
| 
 | ||||
| 	MEM_B(0, flash_status) = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	PTR(u32) flash_type = ctx->r4; | ||||
| 	PTR(u32) flash_maker = ctx->r5; | ||||
| 
 | ||||
| 	MEM_B(0, flash_type) = 0; | ||||
| 	MEM_B(0, flash_maker) = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	save_clear(0, Multilibultra::save_size); | ||||
| 
 | ||||
| 	ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	save_clear(0, Multilibultra::save_size); | ||||
| 
 | ||||
| 	ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	uint32_t page_num = (uint32_t)ctx->r4; | ||||
| 	uint32_t sector_num = page_num / sector_size; | ||||
| 
 | ||||
| 	// Prevent out of bounds erase
 | ||||
| 	if (sector_num >= sector_size) { | ||||
| 		ctx->r2 = -1; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	save_clear(sector_num * sector_size, sector_size); | ||||
| 
 | ||||
| 	ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	uint32_t page_num = (uint32_t)ctx->r4; | ||||
| 	uint32_t sector_num = page_num / sector_size; | ||||
| 
 | ||||
| 	// Prevent out of bounds erase
 | ||||
| 	if (sector_num >= sector_size) { | ||||
| 		ctx->r2 = -1; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	save_clear(sector_num * sector_size, sector_size); | ||||
| 
 | ||||
| 	ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	// All erases are blocking in this implementation, so this should always return OK.
 | ||||
| 	ctx->r2 = 0; // FLASH_STATUS_ERASE_OK
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4); | ||||
| 	int32_t pri = ctx->r5; | ||||
| 	PTR(void) dramAddr = ctx->r6; | ||||
| 	PTR(OSMesgQueue) mq = ctx->r7; | ||||
| 	 | ||||
| 	// Copy the input data into the write buffer
 | ||||
| 	for (size_t i = 0; i < page_size; i++) { | ||||
| 		write_buffer[i] = MEM_B(i, dramAddr); | ||||
| 	} | ||||
| 
 | ||||
| 	// Send the message indicating write completion
 | ||||
| 	osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK); | ||||
| 
 | ||||
| 	ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	uint32_t page_num = ctx->r4; | ||||
| 
 | ||||
| 	// Copy the write buffer into the save file
 | ||||
| 	save_write_ptr(write_buffer.data(), page_num * page_size, page_size); | ||||
| 
 | ||||
| 	ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4); | ||||
| 	int32_t pri = ctx->r5; | ||||
| 	uint32_t page_num = ctx->r6; | ||||
| 	PTR(void) dramAddr = ctx->r7; | ||||
| 	uint32_t n_pages = MEM_W(0x10, ctx->r29); | ||||
| 	PTR(OSMesgQueue) mq = MEM_W(0x14, ctx->r29); | ||||
| 
 | ||||
| 	uint32_t offset = page_num * page_size; | ||||
| 	uint32_t count = n_pages * page_size; | ||||
| 
 | ||||
| 	// Read from the save file into the provided buffer
 | ||||
| 	save_read(PASS_RDRAM dramAddr, offset, count); | ||||
| 
 | ||||
| 	// Send the message indicating read completion
 | ||||
| 	osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK); | ||||
| 
 | ||||
| 	ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	assert(false); | ||||
| } | ||||
							
								
								
									
										80
									
								
								src/math_routines.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/math_routines.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | |||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| 
 | ||||
| extern "C" void __udivdi3_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t ret = a / b; | ||||
| 
 | ||||
|     ctx->r2 = (int32_t)(ret >> 32); | ||||
|     ctx->r3 = (int32_t)(ret >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __divdi3_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); | ||||
|     int64_t ret = a / b; | ||||
| 
 | ||||
|     ctx->r2 = (int32_t)(ret >> 32); | ||||
|     ctx->r3 = (int32_t)(ret >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __umoddi3_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t ret = a % b; | ||||
| 
 | ||||
|     ctx->r2 = (int32_t)(ret >> 32); | ||||
|     ctx->r3 = (int32_t)(ret >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __ull_div_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t ret = a / b; | ||||
| 
 | ||||
|     ctx->r2 = (int32_t)(ret >> 32); | ||||
|     ctx->r3 = (int32_t)(ret >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __ll_div_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); | ||||
|     int64_t ret = a / b; | ||||
| 
 | ||||
|     ctx->r2 = (int32_t)(ret >> 32); | ||||
|     ctx->r3 = (int32_t)(ret >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __ll_mul_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t ret = a * b; | ||||
| 
 | ||||
|     ctx->r2 = (int32_t)(ret >> 32); | ||||
|     ctx->r3 = (int32_t)(ret >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __ull_rem_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); | ||||
|     uint64_t ret = a % b; | ||||
| 
 | ||||
|     ctx->r2 = (int32_t)(ret >> 32); | ||||
|     ctx->r3 = (int32_t)(ret >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __ull_to_d_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     double ret = (double)a; | ||||
| 
 | ||||
|     ctx->f0.d = ret; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __ull_to_f_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); | ||||
|     float ret = (float)a; | ||||
| 
 | ||||
|     ctx->f0.fl = ret; | ||||
| } | ||||
							
								
								
									
										110
									
								
								src/overlays.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/overlays.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | |||
| #include <unordered_map> | ||||
| #include <algorithm> | ||||
| #include <vector> | ||||
| #include "recomp.h" | ||||
| #include "../RecompiledFuncs/recomp_overlays.inl" | ||||
| 
 | ||||
| constexpr size_t num_code_sections = ARRLEN(section_table); | ||||
| 
 | ||||
| // SectionTableEntry sections[] defined in recomp_overlays.inl
 | ||||
| 
 | ||||
| struct LoadedSection { | ||||
|     int32_t loaded_ram_addr; | ||||
|     size_t section_table_index; | ||||
| 
 | ||||
|     bool operator<(const LoadedSection& rhs) { | ||||
|         return loaded_ram_addr < rhs.loaded_ram_addr; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| std::vector<LoadedSection> loaded_sections{}; | ||||
| std::unordered_map<int32_t, recomp_func_t*> func_map{}; | ||||
| 
 | ||||
| void load_overlay(size_t section_table_index, int32_t ram) { | ||||
|     const SectionTableEntry& section = section_table[section_table_index]; | ||||
|     for (size_t function_index = 0; function_index < section.num_funcs; function_index++) { | ||||
|         const FuncEntry& func = section.funcs[function_index]; | ||||
|         func_map[ram + func.offset] = func.func; | ||||
|     } | ||||
|     loaded_sections.emplace_back(ram, section_table_index); | ||||
|     section_addresses[section.index] = ram; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| extern "C" { | ||||
| int32_t section_addresses[num_sections]; | ||||
| } | ||||
| 
 | ||||
| extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) { | ||||
|     // Search for the first section that's included in the loaded rom range
 | ||||
|     // Sections were sorted by `init_overlays` so we can use the bounds functions
 | ||||
|     auto lower = std::lower_bound(§ion_table[0], §ion_table[num_code_sections], rom, | ||||
|         [](const SectionTableEntry& entry, uint32_t addr) { | ||||
|             return entry.rom_addr < addr; | ||||
|         } | ||||
|     ); | ||||
|     auto upper = std::upper_bound(§ion_table[0], §ion_table[num_code_sections], (uint32_t)(rom + size), | ||||
|         [](uint32_t addr, const SectionTableEntry& entry) { | ||||
|             return addr < entry.size + entry.rom_addr; | ||||
|         } | ||||
|     ); | ||||
|     // Load the overlays that were found
 | ||||
|     for (auto it = lower; it != upper; ++it) { | ||||
|         load_overlay(std::distance(§ion_table[0], it), it->rom_addr - rom + ram_addr); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) { | ||||
|     for (auto it = loaded_sections.begin(); it != loaded_sections.end();) { | ||||
|         const auto& section = section_table[it->section_table_index]; | ||||
| 
 | ||||
|         // Check if the unloaded region overlaps with the loaded section
 | ||||
|         if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) { | ||||
|             // Check if the section isn't entirely in the loaded region
 | ||||
|             if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) { | ||||
|                 fprintf(stderr, | ||||
|                     "Cannot partially unload section\n" | ||||
|                     "  rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n" | ||||
|                     "  unloaded_ram: 0x%08X unloaded_size : 0x%08X\n", | ||||
|                         section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size); | ||||
|                 std::exit(EXIT_FAILURE); | ||||
|             } | ||||
|             // Determine where each function was loaded to and remove that entry from the function map
 | ||||
|             for (size_t func_index = 0; func_index < section.num_funcs; func_index++) { | ||||
|                 const auto& func = section.funcs[func_index]; | ||||
|                 uint32_t func_address = func.offset + it->loaded_ram_addr; | ||||
|                 func_map.erase(func_address); | ||||
|             } | ||||
|             // Reset the section's address in the address table
 | ||||
|             section_addresses[section.index] = 0; | ||||
|             // Remove the section from the loaded section map
 | ||||
|             it = loaded_sections.erase(it); | ||||
|             // Skip incrementing the iterator
 | ||||
|             continue; | ||||
|         } | ||||
|         ++it; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void init_overlays() { | ||||
|     for (size_t section_index = 0; section_index < num_code_sections; section_index++) { | ||||
|         section_addresses[section_table[section_index].index] = section_table[section_index].ram_addr; | ||||
|     } | ||||
| 
 | ||||
|     // Sort the executable sections by rom address
 | ||||
|     std::sort(§ion_table[0], §ion_table[num_code_sections], | ||||
|         [](const SectionTableEntry& a, const SectionTableEntry& b) { | ||||
|             return a.rom_addr < b.rom_addr; | ||||
|         } | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| extern "C" recomp_func_t * get_function(int32_t addr) { | ||||
|     auto func_find = func_map.find(addr); | ||||
|     if (func_find == func_map.end()) { | ||||
|         fprintf(stderr, "Failed to find function at 0x%08X\n", addr); | ||||
|         std::exit(EXIT_FAILURE); | ||||
|     } | ||||
|     return func_find->second; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										31
									
								
								src/pak.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/pak.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| #include "recomp.h" | ||||
| #include "../portultra/ultra64.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| 
 | ||||
| extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) { | ||||
| 	ctx->r2 = 1; // PFS_ERR_NOPACK
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	ctx->r2 = 1; // PFS_ERR_NOPACK
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	ctx->r2 = 1; // PFS_ERR_NOPACK
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	ctx->r2 = 1; // PFS_ERR_NOPACK
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	ctx->r2 = 1; // PFS_ERR_NOPACK
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	ctx->r2 = 1; // PFS_ERR_NOPACK
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| 	ctx->r2 = 1; // PFS_ERR_NOPACK
 | ||||
| } | ||||
							
								
								
									
										193
									
								
								src/pi.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/pi.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,193 @@ | |||
| #include <memory> | ||||
| #include <fstream> | ||||
| #include <array> | ||||
| #include "recomp.h" | ||||
| #include "../portultra/ultra64.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| 
 | ||||
| // Flashram occupies the same physical address as sram, but that issue is avoided because libultra exposes
 | ||||
| // a high-level interface for flashram. Because that high-level interface is reimplemented, low level accesses
 | ||||
| // that involve physical addresses don't need to be handled for flashram.
 | ||||
| constexpr uint32_t sram_base = 0x08000000; | ||||
| constexpr uint32_t rom_base = 0x10000000; | ||||
| 
 | ||||
| constexpr uint32_t k1_to_phys(uint32_t addr) { | ||||
|     return addr & 0x1FFFFFFF; | ||||
| } | ||||
| 
 | ||||
| constexpr uint32_t phys_to_k1(uint32_t addr) { | ||||
|     return addr | 0xA0000000; | ||||
| } | ||||
| 
 | ||||
| extern std::unique_ptr<uint8_t[]> rom; | ||||
| extern size_t rom_size; | ||||
| 
 | ||||
| extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     OSPiHandle* handle = TO_PTR(OSPiHandle, Multilibultra::cart_handle); | ||||
|     handle->type = 0; // cart
 | ||||
|     handle->baseAddress = phys_to_k1(rom_base); | ||||
|     handle->domain = 0; | ||||
| 
 | ||||
|     ctx->r2 = (gpr)Multilibultra::cart_handle; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) { | ||||
|     // TODO use word copies when possible
 | ||||
|     uint8_t* rom_addr = rom.get() + physical_addr - rom_base; | ||||
|     for (size_t i = 0; i < num_bytes; i++) { | ||||
|         MEM_B(i, ram_address) = *rom_addr; | ||||
|         rom_addr++; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::array<char, 0x20000> save_buffer; | ||||
| const char save_filename[] = "save.bin"; | ||||
| 
 | ||||
| void update_save_file() { | ||||
|     std::ofstream save_file{ save_filename, std::ios_base::binary }; | ||||
| 
 | ||||
|     if (save_file.good()) { | ||||
|         save_file.write(save_buffer.data(), save_buffer.size()); | ||||
|     } else { | ||||
|         fprintf(stderr, "Failed to save!\n"); | ||||
|         std::exit(EXIT_FAILURE); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { | ||||
|     memcpy(&save_buffer[offset], in, count); | ||||
|     update_save_file(); | ||||
| } | ||||
| 
 | ||||
| void save_write(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) { | ||||
|     for (uint32_t i = 0; i < count; i++) { | ||||
|         save_buffer[offset + i] = MEM_B(i, rdram_address); | ||||
|     } | ||||
|     update_save_file(); | ||||
| } | ||||
| 
 | ||||
| void save_read(uint8_t* rdram, gpr rdram_address, uint32_t offset, uint32_t count) { | ||||
|     for (size_t i = 0; i < count; i++) { | ||||
|         MEM_B(i, rdram_address) = save_buffer[offset + i]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void save_clear(uint32_t start, uint32_t size) { | ||||
|     std::fill_n(save_buffer.begin() + start, size, 0); | ||||
|     std::ofstream save_file{ save_filename, std::ios_base::binary }; | ||||
| 
 | ||||
|     if (save_file.good()) { | ||||
|         save_file.write(save_buffer.data(), save_buffer.size()); | ||||
|     } else { | ||||
|         fprintf(stderr, "Failed to save!\n"); | ||||
|         std::exit(EXIT_FAILURE); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Multilibultra::save_init() { | ||||
|     std::ifstream save_file{ save_filename, std::ios_base::binary }; | ||||
| 
 | ||||
|     if (save_file.good()) { | ||||
|         save_file.read(save_buffer.data(), save_buffer.size()); | ||||
|     } else { | ||||
|         save_buffer.fill(0); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void do_dma(uint8_t* rdram, PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) { | ||||
|     // TODO asynchronous transfer
 | ||||
|     // TODO implement unaligned DMA correctly
 | ||||
|     if (direction == 0) { | ||||
|         if (physical_addr >= rom_base) { | ||||
|             // read cart rom
 | ||||
|             do_rom_read(rdram, rdram_address, physical_addr, size); | ||||
| 
 | ||||
|             // Send a message to the mq to indicate that the transfer completed
 | ||||
|             osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); | ||||
|         } else if (physical_addr >= sram_base) { | ||||
|             // read sram
 | ||||
|             save_read(rdram, rdram_address, physical_addr - sram_base, size); | ||||
| 
 | ||||
|             // Send a message to the mq to indicate that the transfer completed
 | ||||
|             osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); | ||||
|         } else { | ||||
|             fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr); | ||||
|         } | ||||
|     } else { | ||||
|         if (physical_addr >= rom_base) { | ||||
|             // write cart rom
 | ||||
|             throw std::runtime_error("ROM DMA write unimplemented"); | ||||
|         } else if (physical_addr >= sram_base) { | ||||
|             // write sram
 | ||||
|             save_write(rdram, rdram_address, physical_addr - sram_base, size); | ||||
| 
 | ||||
|             // Send a message to the mq to indicate that the transfer completed
 | ||||
|             osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); | ||||
|         } else { | ||||
|             fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     uint32_t mb = ctx->r4; | ||||
|     uint32_t pri = ctx->r5; | ||||
|     uint32_t direction = ctx->r6; | ||||
|     uint32_t devAddr = ctx->r7; | ||||
|     gpr dramAddr = MEM_W(0x10, ctx->r29); | ||||
|     uint32_t size = MEM_W(0x14, ctx->r29); | ||||
|     PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29); | ||||
|     uint32_t physical_addr = k1_to_phys(devAddr); | ||||
| 
 | ||||
|     debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); | ||||
| 
 | ||||
|     do_dma(rdram, mq, dramAddr, physical_addr, size, direction); | ||||
| 
 | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osEPiStartDma_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); | ||||
|     OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5); | ||||
|     uint32_t direction = ctx->r6; | ||||
|     uint32_t devAddr = handle->baseAddress | mb->devAddr; | ||||
|     gpr dramAddr = mb->dramAddr; | ||||
|     uint32_t size = mb->size; | ||||
|     PTR(OSMesgQueue) mq = mb->hdr.retQueue; | ||||
|     uint32_t physical_addr = k1_to_phys(devAddr); | ||||
| 
 | ||||
|     debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); | ||||
| 
 | ||||
|     do_dma(rdram, mq, dramAddr, physical_addr, size, direction); | ||||
| 
 | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osEPiReadIo_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); | ||||
|     uint32_t devAddr = handle->baseAddress | ctx->r5; | ||||
|     gpr dramAddr = ctx->r6; | ||||
|     uint32_t physical_addr = k1_to_phys(devAddr); | ||||
| 
 | ||||
|     if (physical_addr > rom_base) { | ||||
|         // cart rom
 | ||||
|         do_rom_read(rdram, dramAddr, physical_addr, sizeof(uint32_t)); | ||||
|     } else { | ||||
|         // sram
 | ||||
|         assert(false && "SRAM ReadIo unimplemented"); | ||||
|     } | ||||
| 
 | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPiGetStatus_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osPiRawStartDma_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
							
								
								
									
										45
									
								
								src/portultra_stubs.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/portultra_stubs.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| #include "../portultra/ultra64.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| // None of these functions need to be reimplemented, so stub them out
 | ||||
| extern "C" void osUnmapTLBAll_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     // TODO this will need to be implemented in the future for any games that actually use the TLB
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceInit_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 11; // CONT_ERR_DEVICE
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceSetWord_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceCheckWord_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceStopReadData_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceMaskDictionary_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceStartReadData_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceControlGain_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceGetReadData_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVoiceClearDictionary_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     assert(false); | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										158
									
								
								src/portultra_translation.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/portultra_translation.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,158 @@ | |||
| #include <memory> | ||||
| #include "../portultra/ultra64.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| extern "C" void osInitialize_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     osInitialize(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osInitialize_common_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     osInitialize(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osCreateThread_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     osCreateThread(rdram, (int32_t)ctx->r4, (OSId)ctx->r5, (int32_t)ctx->r6, (int32_t)ctx->r7, | ||||
|         (int32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29)); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osStartThread_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     osStartThread(rdram, (int32_t)ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osStopThread_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     osStopThread(rdram, (int32_t)ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     osDestroyThread(rdram, (int32_t)ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osGetThreadPri_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = osGetThreadPri(rdram, (int32_t)ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osGetThreadId_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = osGetThreadId(rdram, (int32_t)ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osCreateMesgQueue_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     osCreateMesgQueue(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osRecvMesg_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ctx->r2 = osRecvMesg(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSendMesg_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ctx->r2 = osSendMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osJamMesg_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ctx->r2 = osJamMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSetEventMesg_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     osSetEventMesg(rdram, (OSEvent)ctx->r4, (int32_t)ctx->r5, (OSMesg)ctx->r6); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViSetEvent_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     osViSetEvent(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (u32)ctx->r6); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = osGetCount(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t total_count = osGetTime(); | ||||
|     ctx->r2 = (int32_t)(total_count >> 32); | ||||
|     ctx->r3 = (int32_t)(total_count >> 0); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu); | ||||
|     uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10); | ||||
|     ctx->r2 = osSetTimer(rdram, (int32_t)ctx->r4, countdown, interval, (int32_t)MEM_W(0x18, ctx->r29), (OSMesg)MEM_W(0x1C, ctx->r29)); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = osStopTimer(rdram, (int32_t)ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = osVirtualToPhysical((int32_t)ctx->r2); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osInvalICache_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osWritebackDCache_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osWritebackDCacheAll_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSetIntMask_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osDisableInt_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osRestoreInt_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osSetFpcCsr_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| // For the Mario Party games (not working)
 | ||||
| //extern "C" void longjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
 | ||||
| //    RecompJmpBuf* buf = TO_PTR(RecompJmpBuf, ctx->r4);
 | ||||
| //
 | ||||
| //    // Check if this is a buffer that was set up with setjmp
 | ||||
| //    if (buf->magic == SETJMP_MAGIC) {
 | ||||
| //        // If so, longjmp to it
 | ||||
| //        // Setjmp/longjmp does not work across threads, so verify that this buffer was made by this thread
 | ||||
| //        assert(buf->owner == Multilibultra::this_thread());
 | ||||
| //        longjmp(buf->storage->buffer, ctx->r5);
 | ||||
| //    } else {
 | ||||
| //        // Otherwise, check if it was one built manually by the game with $ra pointing to a function
 | ||||
| //        gpr sp = MEM_W(0, ctx->r4);
 | ||||
| //        gpr ra = MEM_W(4, ctx->r4);
 | ||||
| //        ctx->r29 = sp;
 | ||||
| //        recomp_func_t* target = LOOKUP_FUNC(ra);
 | ||||
| //        if (target == nullptr) {
 | ||||
| //            fprintf(stderr, "Failed to find function for manual longjmp\n");
 | ||||
| //            std::quick_exit(EXIT_FAILURE);
 | ||||
| //        }
 | ||||
| //        target(rdram, ctx);
 | ||||
| //
 | ||||
| //        // TODO kill this thread if the target function returns
 | ||||
| //        assert(false);
 | ||||
| //    }
 | ||||
| //}
 | ||||
| //
 | ||||
| //#undef setjmp_recomp
 | ||||
| //extern "C" void setjmp_recomp(uint8_t * rdram, recomp_context * ctx) {
 | ||||
| //    fprintf(stderr, "Program called setjmp_recomp\n");
 | ||||
| //    std::quick_exit(EXIT_FAILURE);
 | ||||
| //}
 | ||||
| //
 | ||||
| //extern "C" int32_t osGetThreadEx(void) {
 | ||||
| //    return Multilibultra::this_thread();
 | ||||
| //}
 | ||||
							
								
								
									
										70
									
								
								src/print.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/print.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| #include "../portultra/ultra64.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| #include "euc-jp.h" | ||||
| 
 | ||||
| extern "C" void __checkHardware_msp_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __checkHardware_kmc_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __checkHardware_isv_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osInitialize_msp_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osInitialize_kmc_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osInitialize_isv_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| } | ||||
| 
 | ||||
| extern "C" void isPrintfInit_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osRdbSend_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     gpr buf = ctx->r4; | ||||
|     size_t size = ctx->r5; | ||||
|     u32 type = (u32)ctx->r6; | ||||
|     std::unique_ptr<char[]> to_print = std::make_unique<char[]>(size + 1); | ||||
| 
 | ||||
|     for (size_t i = 0; i < size; i++) { | ||||
|         to_print[i] = MEM_B(i, buf); | ||||
|     } | ||||
|     to_print[size] = '\x00'; | ||||
| 
 | ||||
|     fwrite(to_print.get(), 1, size, stdout); | ||||
| 
 | ||||
|     ctx->r2 = size; | ||||
| } | ||||
| 
 | ||||
| extern "C" void is_proutSyncPrintf_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     // Buffering to speed up print performance
 | ||||
|     static std::vector<char> print_buffer; | ||||
| 
 | ||||
|     gpr buf = ctx->r5; | ||||
|     size_t size = ctx->r6; | ||||
| 
 | ||||
|     //for (size_t i = 0; i < size; i++) {
 | ||||
|     //    // Add the new character to the buffer
 | ||||
|     //    char cur_char = MEM_B(i, buf);
 | ||||
| 
 | ||||
|     //    // If the new character is a newline, flush the buffer
 | ||||
|     //    if (cur_char == '\n') {
 | ||||
|     //        std::string utf8_str = Encoding::decode_eucjp(std::string_view{ print_buffer.data(), print_buffer.size() });
 | ||||
|     //        puts(utf8_str.c_str());
 | ||||
|     //        print_buffer.clear();
 | ||||
|     //    } else {
 | ||||
|     //        print_buffer.push_back(cur_char);
 | ||||
|     //    }
 | ||||
|     //}
 | ||||
| 
 | ||||
|     //fwrite(to_print.get(), size, 1, stdout);
 | ||||
| 
 | ||||
|     ctx->r2 = 1; | ||||
| } | ||||
							
								
								
									
										165
									
								
								src/recomp.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/recomp.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,165 @@ | |||
| #ifdef _WIN32 | ||||
| #include <Windows.h> | ||||
| #endif | ||||
| #include <cstdio> | ||||
| #include <cstdlib> | ||||
| #include <memory> | ||||
| #include <cmath> | ||||
| #include <unordered_map> | ||||
| #include <fstream> | ||||
| #include <iostream> | ||||
| #include "recomp.h" | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| inline uint32_t byteswap(uint32_t val) { | ||||
|     return _byteswap_ulong(val); | ||||
| } | ||||
| #else | ||||
| constexpr uint32_t byteswap(uint32_t val) { | ||||
|     return __builtin_bswap32(val); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| extern "C" void _bzero(uint8_t* rdram, recomp_context* ctx) { | ||||
|     gpr start_addr = ctx->r4; | ||||
|     gpr size = ctx->r5; | ||||
| 
 | ||||
|     for (uint32_t i = 0; i < size; i++) { | ||||
|         MEM_B(start_addr, i) = 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) { | ||||
|     ctx->r2 = 8 * 1024 * 1024; | ||||
| } | ||||
| 
 | ||||
| extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) { | ||||
|     printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl); | ||||
|     exit(EXIT_FAILURE); | ||||
| } | ||||
| 
 | ||||
| extern "C" void do_break(uint32_t vram) { | ||||
|     printf("Encountered break at original vram 0x%08X\n", vram); | ||||
|     exit(EXIT_FAILURE); | ||||
| } | ||||
| 
 | ||||
| void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg) { | ||||
|     recomp_context ctx{}; | ||||
|     ctx.r29 = sp; | ||||
|     ctx.r4 = arg; | ||||
|     recomp_func_t* func = get_function(addr); | ||||
|     func(rdram, &ctx); | ||||
| } | ||||
| 
 | ||||
| void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t dev_address, size_t num_bytes); | ||||
| 
 | ||||
| std::unique_ptr<uint8_t[]> rom; | ||||
| size_t rom_size; | ||||
| 
 | ||||
| // Recomp generation functions
 | ||||
| extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx); | ||||
| gpr get_entrypoint_address(); | ||||
| const char* get_rom_name(); | ||||
| void init_overlays(); | ||||
| extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size); | ||||
| extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| #include <Windows.h> | ||||
| #endif | ||||
| 
 | ||||
| int main(int argc, char **argv) { | ||||
|     //if (argc != 2) {
 | ||||
|     //    printf("Usage: %s [baserom]\n", argv[0]);
 | ||||
|     //    exit(EXIT_SUCCESS);
 | ||||
|     //}
 | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|     // Set up console output to accept UTF-8 on windows
 | ||||
|     SetConsoleOutputCP(CP_UTF8); | ||||
| 
 | ||||
|     // Change to a font that supports Japanese characters
 | ||||
|     CONSOLE_FONT_INFOEX cfi; | ||||
|     cfi.cbSize = sizeof cfi; | ||||
|     cfi.nFont = 0; | ||||
|     cfi.dwFontSize.X = 0; | ||||
|     cfi.dwFontSize.Y = 16; | ||||
|     cfi.FontFamily = FF_DONTCARE; | ||||
|     cfi.FontWeight = FW_NORMAL; | ||||
|     wcscpy_s(cfi.FaceName, L"NSimSun"); | ||||
|     SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi); | ||||
| #else | ||||
|     std::setlocale(LC_ALL, "en_US.UTF-8"); | ||||
| #endif | ||||
| 
 | ||||
|     { | ||||
|         std::basic_ifstream<uint8_t> rom_file{ get_rom_name(), std::ios::binary }; | ||||
| 
 | ||||
|         size_t iobuf_size = 0x100000; | ||||
|         std::unique_ptr<uint8_t[]> iobuf = std::make_unique<uint8_t[]>(iobuf_size); | ||||
|         rom_file.rdbuf()->pubsetbuf(iobuf.get(), iobuf_size); | ||||
| 
 | ||||
|         if (!rom_file) { | ||||
|             fprintf(stderr, "Failed to open rom: %s\n", get_rom_name()); | ||||
|             exit(EXIT_FAILURE); | ||||
|         } | ||||
| 
 | ||||
|         rom_file.seekg(0, std::ios::end); | ||||
|         rom_size = rom_file.tellg(); | ||||
|         rom_file.seekg(0, std::ios::beg); | ||||
| 
 | ||||
|         rom = std::make_unique<uint8_t[]>(rom_size); | ||||
| 
 | ||||
|         rom_file.read(rom.get(), rom_size); | ||||
| 
 | ||||
|         // TODO remove this
 | ||||
|         // Modify the name in the rom header so RT64 doesn't find it
 | ||||
|         rom[0x2F] = 'O'; | ||||
|     } | ||||
| 
 | ||||
|     // Initialize the overlays
 | ||||
|     init_overlays(); | ||||
| 
 | ||||
|     // Get entrypoint from recomp function
 | ||||
|     gpr entrypoint = get_entrypoint_address(); | ||||
| 
 | ||||
|     // Load overlays in the first 1MB
 | ||||
|     load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); | ||||
| 
 | ||||
|     // Allocate rdram_buffer (16MB to give room for any extra addressable data used by recomp)
 | ||||
|     std::unique_ptr<uint8_t[]> rdram_buffer = std::make_unique<uint8_t[]>(16 * 1024 * 1024); | ||||
|     std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024); | ||||
|     recomp_context context{}; | ||||
| 
 | ||||
|     // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000)
 | ||||
|     do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); | ||||
| 
 | ||||
|     // Set up stack pointer
 | ||||
|     context.r29 = 0xFFFFFFFF803FFFF0u; | ||||
| 
 | ||||
|     // Initialize variables normally set by IPL3
 | ||||
|     constexpr int32_t osTvType = 0x80000300; | ||||
|     constexpr int32_t osRomType = 0x80000304; | ||||
|     constexpr int32_t osRomBase = 0x80000308; | ||||
|     constexpr int32_t osResetType = 0x8000030c; | ||||
|     constexpr int32_t osCicId = 0x80000310; | ||||
|     constexpr int32_t osVersion = 0x80000314; | ||||
|     constexpr int32_t osMemSize = 0x80000318; | ||||
|     constexpr int32_t osAppNMIBuffer = 0x8000031c; | ||||
|     uint8_t *rdram = rdram_buffer.get(); | ||||
|     MEM_W(osTvType, 0) = 1; // NTSC
 | ||||
|     MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
 | ||||
|     MEM_W(osResetType, 0) = 0; // cold reset
 | ||||
|     MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
 | ||||
| 
 | ||||
|     debug_printf("[Recomp] Starting\n"); | ||||
| 
 | ||||
|     Multilibultra::preinit(rdram_buffer.get(), rom.get()); | ||||
| 
 | ||||
|     recomp_entrypoint(rdram_buffer.get(), &context); | ||||
| 
 | ||||
|     debug_printf("[Recomp] Quitting\n"); | ||||
| 
 | ||||
|     return EXIT_SUCCESS; | ||||
| } | ||||
							
								
								
									
										114
									
								
								src/rt64_layer.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/rt64_layer.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,114 @@ | |||
| #include <memory> | ||||
| #include <Windows.h> | ||||
| 
 | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "rt64_layer.h" | ||||
| #include "SDL.h" | ||||
| 
 | ||||
| static uint8_t DMEM[0x1000]; | ||||
| static uint8_t IMEM[0x1000]; | ||||
| 
 | ||||
| unsigned int MI_INTR_REG = 0; | ||||
| 
 | ||||
| unsigned int DPC_START_REG = 0; | ||||
| unsigned int DPC_END_REG = 0; | ||||
| unsigned int DPC_CURRENT_REG = 0; | ||||
| unsigned int DPC_STATUS_REG = 0; | ||||
| unsigned int DPC_CLOCK_REG = 0; | ||||
| unsigned int DPC_BUFBUSY_REG = 0; | ||||
| unsigned int DPC_PIPEBUSY_REG = 0; | ||||
| unsigned int DPC_TMEM_REG = 0; | ||||
| 
 | ||||
| unsigned int VI_STATUS_REG = 0; | ||||
| unsigned int VI_ORIGIN_REG = 0; | ||||
| unsigned int VI_WIDTH_REG = 0; | ||||
| unsigned int VI_INTR_REG = 0; | ||||
| unsigned int VI_V_CURRENT_LINE_REG = 0; | ||||
| unsigned int VI_TIMING_REG = 0; | ||||
| unsigned int VI_V_SYNC_REG = 0; | ||||
| unsigned int VI_H_SYNC_REG = 0; | ||||
| unsigned int VI_LEAP_REG = 0; | ||||
| unsigned int VI_H_START_REG = 0; | ||||
| unsigned int VI_V_START_REG = 0; | ||||
| unsigned int VI_V_BURST_REG = 0; | ||||
| unsigned int VI_X_SCALE_REG = 0; | ||||
| unsigned int VI_Y_SCALE_REG = 0; | ||||
| 
 | ||||
| unsigned int SP_STATUS_REG = 0; | ||||
| unsigned int RDRAM_SIZE = 0x800000; | ||||
| 
 | ||||
| #define GET_FUNC(lib, name) \ | ||||
|     name = (decltype(name))GetProcAddress(lib, #name) | ||||
| 
 | ||||
| void dummy_check_interrupts() { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void RT64Init(uint8_t* rom, uint8_t* rdram) { | ||||
|     // Dynamic loading
 | ||||
|     //auto RT64 = LoadLibrary("RT64.dll");
 | ||||
|     //if (RT64 == 0) {
 | ||||
|     //    fprintf(stdout, "Failed to load RT64\n");
 | ||||
|     //    std::exit(EXIT_FAILURE);
 | ||||
|     //}
 | ||||
|     //GET_FUNC(RT64, InitiateGFX);
 | ||||
|     //GET_FUNC(RT64, ProcessRDPList);
 | ||||
|     //GET_FUNC(RT64, ProcessDList);
 | ||||
|     //GET_FUNC(RT64, UpdateScreen);
 | ||||
| 
 | ||||
|     GFX_INFO gfx_info{}; | ||||
|     gfx_info.HEADER = rom; | ||||
|     gfx_info.RDRAM = rdram; | ||||
|     gfx_info.DMEM = DMEM; | ||||
|     gfx_info.IMEM = IMEM; | ||||
| 
 | ||||
|     gfx_info.MI_INTR_REG = &MI_INTR_REG; | ||||
| 
 | ||||
|     gfx_info.DPC_START_REG = &DPC_START_REG; | ||||
|     gfx_info.DPC_END_REG = &DPC_END_REG; | ||||
|     gfx_info.DPC_CURRENT_REG = &DPC_CURRENT_REG; | ||||
|     gfx_info.DPC_STATUS_REG = &DPC_STATUS_REG; | ||||
|     gfx_info.DPC_CLOCK_REG = &DPC_CLOCK_REG; | ||||
|     gfx_info.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG; | ||||
|     gfx_info.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG; | ||||
|     gfx_info.DPC_TMEM_REG = &DPC_TMEM_REG; | ||||
| 
 | ||||
|     gfx_info.VI_STATUS_REG = &VI_STATUS_REG; | ||||
|     gfx_info.VI_ORIGIN_REG = &VI_ORIGIN_REG; | ||||
|     gfx_info.VI_WIDTH_REG = &VI_WIDTH_REG; | ||||
|     gfx_info.VI_INTR_REG = &VI_INTR_REG; | ||||
|     gfx_info.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG; | ||||
|     gfx_info.VI_TIMING_REG = &VI_TIMING_REG; | ||||
|     gfx_info.VI_V_SYNC_REG = &VI_V_SYNC_REG; | ||||
|     gfx_info.VI_H_SYNC_REG = &VI_H_SYNC_REG; | ||||
|     gfx_info.VI_LEAP_REG = &VI_LEAP_REG; | ||||
|     gfx_info.VI_H_START_REG = &VI_H_START_REG; | ||||
|     gfx_info.VI_V_START_REG = &VI_V_START_REG; | ||||
|     gfx_info.VI_V_BURST_REG = &VI_V_BURST_REG; | ||||
|     gfx_info.VI_X_SCALE_REG = &VI_X_SCALE_REG; | ||||
|     gfx_info.VI_Y_SCALE_REG = &VI_Y_SCALE_REG; | ||||
| 
 | ||||
|     gfx_info.CheckInterrupts = dummy_check_interrupts; | ||||
|     gfx_info.version = 2; | ||||
|     gfx_info.SP_STATUS_REG = &SP_STATUS_REG; | ||||
|     gfx_info.RDRAM_SIZE = &RDRAM_SIZE; | ||||
| 
 | ||||
| 	InitiateGFX(gfx_info); | ||||
| } | ||||
| 
 | ||||
| void RT64SendDL(uint8_t* rdram, const OSTask* task) { | ||||
|     OSTask task_copy = *task; | ||||
|     task_copy.t.data_ptr &= 0x3FFFFFF; | ||||
|     task_copy.t.ucode &= 0x3FFFFFF; | ||||
|     task_copy.t.ucode_data &= 0x3FFFFFF; | ||||
| 
 | ||||
|     memcpy(DMEM + 0xFC0, &task_copy, 0x40); | ||||
| 
 | ||||
|     ProcessDList(); | ||||
| } | ||||
| 
 | ||||
| void RT64UpdateScreen(uint32_t vi_origin) { | ||||
|     VI_ORIGIN_REG = vi_origin; | ||||
| 
 | ||||
|     UpdateScreen(); | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/sp.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/sp.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,50 @@ | |||
| #include <cstdio> | ||||
| #include <fstream> | ||||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     // Nothing to do here
 | ||||
| } | ||||
| 
 | ||||
| bool dump_frame = false; | ||||
| 
 | ||||
| extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     //printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
 | ||||
|     OSTask* task = TO_PTR(OSTask, ctx->r4); | ||||
|     if (task->t.type == M_GFXTASK) { | ||||
|         //printf("[sp] Gfx task: %08X\n", (uint32_t)ctx->r4);
 | ||||
|     } else if (task->t.type == M_AUDTASK) { | ||||
|         //printf("[sp] Audio task: %08X\n", (uint32_t)ctx->r4);
 | ||||
|     } | ||||
|     // For debugging
 | ||||
|     if (dump_frame) { | ||||
|         char addr_str[32]; | ||||
|         constexpr size_t ram_size = 0x800000; | ||||
|         std::unique_ptr<char[]> ram_unswapped = std::make_unique<char[]>(ram_size); | ||||
|         snprintf(addr_str, sizeof(addr_str) - 1, "%08X", task->t.data_ptr); | ||||
|         addr_str[sizeof(addr_str) - 1] = '\0'; | ||||
|         std::ofstream dump_file{ "ramdump" + std::string{ addr_str } + ".bin", std::ios::binary}; | ||||
| 
 | ||||
|         for (size_t i = 0; i < ram_size; i++) { | ||||
|             ram_unswapped[i] = rdram[i ^ 3]; | ||||
|         } | ||||
| 
 | ||||
|         dump_file.write(ram_unswapped.get(), ram_size); | ||||
|         dump_frame = false; | ||||
|     } | ||||
|     Multilibultra::submit_rsp_task(rdram, ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSpTaskYield_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     // Ignore yield requests (acts as if the task completed before it received the yield request)
 | ||||
| } | ||||
| 
 | ||||
| extern "C" void osSpTaskYielded_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     // Task yield requests are ignored, so always return 0 as tasks will never be yielded
 | ||||
|     ctx->r2 = 0; | ||||
| } | ||||
| 
 | ||||
| extern "C" void __osSpSetPc_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     assert(false); | ||||
| } | ||||
							
								
								
									
										38
									
								
								src/vi.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/vi.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| #include "../portultra/multilibultra.hpp" | ||||
| #include "recomp.h" | ||||
| 
 | ||||
| extern "C" void osViSetYScale_recomp(uint8_t* rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViSetXScale_recomp(uint8_t* rdram, recomp_context * ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osCreateViManager_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViBlack_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViSetSpecialFeatures_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ctx->r2 = (gpr)(int32_t)osViGetCurrentFramebuffer(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViGetNextFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ctx->r2 = (gpr)(int32_t)osViGetNextFramebuffer(); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     osViSwapBuffer(rdram, (int32_t)ctx->r4); | ||||
| } | ||||
| 
 | ||||
| extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) { | ||||
|     ; | ||||
| } | ||||
							
								
								
									
										582
									
								
								thirdparty/blockingconcurrentqueue.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								thirdparty/blockingconcurrentqueue.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,582 @@ | |||
| // Provides an efficient blocking version of moodycamel::ConcurrentQueue.
 | ||||
| // ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified
 | ||||
| // BSD license, available at the top of concurrentqueue.h.
 | ||||
| // Also dual-licensed under the Boost Software License (see LICENSE.md)
 | ||||
| // Uses Jeff Preshing's semaphore implementation (under the terms of its
 | ||||
| // separate zlib license, see lightweightsemaphore.h).
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "concurrentqueue.h" | ||||
| #include "lightweightsemaphore.h" | ||||
| 
 | ||||
| #include <type_traits> | ||||
| #include <cerrno> | ||||
| #include <memory> | ||||
| #include <chrono> | ||||
| #include <ctime> | ||||
| 
 | ||||
| namespace moodycamel | ||||
| { | ||||
| // This is a blocking version of the queue. It has an almost identical interface to
 | ||||
| // the normal non-blocking version, with the addition of various wait_dequeue() methods
 | ||||
| // and the removal of producer-specific dequeue methods.
 | ||||
| template<typename T, typename Traits = ConcurrentQueueDefaultTraits> | ||||
| class BlockingConcurrentQueue | ||||
| { | ||||
| private: | ||||
| 	typedef ::moodycamel::ConcurrentQueue<T, Traits> ConcurrentQueue; | ||||
| 	typedef ::moodycamel::LightweightSemaphore LightweightSemaphore; | ||||
| 
 | ||||
| public: | ||||
| 	typedef typename ConcurrentQueue::producer_token_t producer_token_t; | ||||
| 	typedef typename ConcurrentQueue::consumer_token_t consumer_token_t; | ||||
| 	 | ||||
| 	typedef typename ConcurrentQueue::index_t index_t; | ||||
| 	typedef typename ConcurrentQueue::size_t size_t; | ||||
| 	typedef typename std::make_signed<size_t>::type ssize_t; | ||||
| 	 | ||||
| 	static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE; | ||||
| 	static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD; | ||||
| 	static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE; | ||||
| 	static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE; | ||||
| 	static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; | ||||
| 	static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE; | ||||
| 	static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE; | ||||
| 	 | ||||
| public: | ||||
| 	// Creates a queue with at least `capacity` element slots; note that the
 | ||||
| 	// actual number of elements that can be inserted without additional memory
 | ||||
| 	// allocation depends on the number of producers and the block size (e.g. if
 | ||||
| 	// the block size is equal to `capacity`, only a single block will be allocated
 | ||||
| 	// up-front, which means only a single producer will be able to enqueue elements
 | ||||
| 	// without an extra allocation -- blocks aren't shared between producers).
 | ||||
| 	// This method is not thread safe -- it is up to the user to ensure that the
 | ||||
| 	// queue is fully constructed before it starts being used by other threads (this
 | ||||
| 	// includes making the memory effects of construction visible, possibly with a
 | ||||
| 	// memory barrier).
 | ||||
| 	explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) | ||||
| 		: inner(capacity), sema(create<LightweightSemaphore, ssize_t, int>(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>) | ||||
| 	{ | ||||
| 		assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); | ||||
| 		if (!sema) { | ||||
| 			MOODYCAMEL_THROW(std::bad_alloc()); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) | ||||
| 		: inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create<LightweightSemaphore, ssize_t, int>(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>) | ||||
| 	{ | ||||
| 		assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); | ||||
| 		if (!sema) { | ||||
| 			MOODYCAMEL_THROW(std::bad_alloc()); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Disable copying and copy assignment
 | ||||
| 	BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 	BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 	 | ||||
| 	// Moving is supported, but note that it is *not* a thread-safe operation.
 | ||||
| 	// Nobody can use the queue while it's being moved, and the memory effects
 | ||||
| 	// of that move must be propagated to other threads before they can use it.
 | ||||
| 	// Note: When a queue is moved, its tokens are still valid but can only be
 | ||||
| 	// used with the destination queue (i.e. semantically they are moved along
 | ||||
| 	// with the queue itself).
 | ||||
| 	BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT | ||||
| 		: inner(std::move(other.inner)), sema(std::move(other.sema)) | ||||
| 	{ } | ||||
| 	 | ||||
| 	inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT | ||||
| 	{ | ||||
| 		return swap_internal(other); | ||||
| 	} | ||||
| 	 | ||||
| 	// Swaps this queue's state with the other's. Not thread-safe.
 | ||||
| 	// Swapping two queues does not invalidate their tokens, however
 | ||||
| 	// the tokens that were created for one queue must be used with
 | ||||
| 	// only the swapped queue (i.e. the tokens are tied to the
 | ||||
| 	// queue's movable state, not the object itself).
 | ||||
| 	inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT | ||||
| 	{ | ||||
| 		swap_internal(other); | ||||
| 	} | ||||
| 	 | ||||
| private: | ||||
| 	BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other) | ||||
| 	{ | ||||
| 		if (this == &other) { | ||||
| 			return *this; | ||||
| 		} | ||||
| 		 | ||||
| 		inner.swap(other.inner); | ||||
| 		sema.swap(other.sema); | ||||
| 		return *this; | ||||
| 	} | ||||
| 	 | ||||
| public: | ||||
| 	// Enqueues a single item (by copying it).
 | ||||
| 	// Allocates memory if required. Only fails if memory allocation fails (or implicit
 | ||||
| 	// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
 | ||||
| 	// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool enqueue(T const& item) | ||||
| 	{ | ||||
| 		if ((details::likely)(inner.enqueue(item))) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues a single item (by moving it, if possible).
 | ||||
| 	// Allocates memory if required. Only fails if memory allocation fails (or implicit
 | ||||
| 	// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
 | ||||
| 	// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool enqueue(T&& item) | ||||
| 	{ | ||||
| 		if ((details::likely)(inner.enqueue(std::move(item)))) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues a single item (by copying it) using an explicit producer token.
 | ||||
| 	// Allocates memory if required. Only fails if memory allocation fails (or
 | ||||
| 	// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool enqueue(producer_token_t const& token, T const& item) | ||||
| 	{ | ||||
| 		if ((details::likely)(inner.enqueue(token, item))) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues a single item (by moving it, if possible) using an explicit producer token.
 | ||||
| 	// Allocates memory if required. Only fails if memory allocation fails (or
 | ||||
| 	// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool enqueue(producer_token_t const& token, T&& item) | ||||
| 	{ | ||||
| 		if ((details::likely)(inner.enqueue(token, std::move(item)))) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues several items.
 | ||||
| 	// Allocates memory if required. Only fails if memory allocation fails (or
 | ||||
| 	// implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
 | ||||
| 	// is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
 | ||||
| 	// Note: Use std::make_move_iterator if the elements should be moved instead of copied.
 | ||||
| 	// Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline bool enqueue_bulk(It itemFirst, size_t count) | ||||
| 	{ | ||||
| 		if ((details::likely)(inner.enqueue_bulk(std::forward<It>(itemFirst), count))) { | ||||
| 			sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues several items using an explicit producer token.
 | ||||
| 	// Allocates memory if required. Only fails if memory allocation fails
 | ||||
| 	// (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
 | ||||
| 	// Note: Use std::make_move_iterator if the elements should be moved
 | ||||
| 	// instead of copied.
 | ||||
| 	// Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) | ||||
| 	{ | ||||
| 		if ((details::likely)(inner.enqueue_bulk(token, std::forward<It>(itemFirst), count))) { | ||||
| 			sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues a single item (by copying it).
 | ||||
| 	// Does not allocate memory. Fails if not enough room to enqueue (or implicit
 | ||||
| 	// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
 | ||||
| 	// is 0).
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool try_enqueue(T const& item) | ||||
| 	{ | ||||
| 		if (inner.try_enqueue(item)) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues a single item (by moving it, if possible).
 | ||||
| 	// Does not allocate memory (except for one-time implicit producer).
 | ||||
| 	// Fails if not enough room to enqueue (or implicit production is
 | ||||
| 	// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool try_enqueue(T&& item) | ||||
| 	{ | ||||
| 		if (inner.try_enqueue(std::move(item))) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues a single item (by copying it) using an explicit producer token.
 | ||||
| 	// Does not allocate memory. Fails if not enough room to enqueue.
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool try_enqueue(producer_token_t const& token, T const& item) | ||||
| 	{ | ||||
| 		if (inner.try_enqueue(token, item)) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues a single item (by moving it, if possible) using an explicit producer token.
 | ||||
| 	// Does not allocate memory. Fails if not enough room to enqueue.
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline bool try_enqueue(producer_token_t const& token, T&& item) | ||||
| 	{ | ||||
| 		if (inner.try_enqueue(token, std::move(item))) { | ||||
| 			sema->signal(); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues several items.
 | ||||
| 	// Does not allocate memory (except for one-time implicit producer).
 | ||||
| 	// Fails if not enough room to enqueue (or implicit production is
 | ||||
| 	// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
 | ||||
| 	// Note: Use std::make_move_iterator if the elements should be moved
 | ||||
| 	// instead of copied.
 | ||||
| 	// Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline bool try_enqueue_bulk(It itemFirst, size_t count) | ||||
| 	{ | ||||
| 		if (inner.try_enqueue_bulk(std::forward<It>(itemFirst), count)) { | ||||
| 			sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Enqueues several items using an explicit producer token.
 | ||||
| 	// Does not allocate memory. Fails if not enough room to enqueue.
 | ||||
| 	// Note: Use std::make_move_iterator if the elements should be moved
 | ||||
| 	// instead of copied.
 | ||||
| 	// Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) | ||||
| 	{ | ||||
| 		if (inner.try_enqueue_bulk(token, std::forward<It>(itemFirst), count)) { | ||||
| 			sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 	// Attempts to dequeue from the queue.
 | ||||
| 	// Returns false if all producer streams appeared empty at the time they
 | ||||
| 	// were checked (so, the queue is likely but not guaranteed to be empty).
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U> | ||||
| 	inline bool try_dequeue(U& item) | ||||
| 	{ | ||||
| 		if (sema->tryWait()) { | ||||
| 			while (!inner.try_dequeue(item)) { | ||||
| 				continue; | ||||
| 			} | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Attempts to dequeue from the queue using an explicit consumer token.
 | ||||
| 	// Returns false if all producer streams appeared empty at the time they
 | ||||
| 	// were checked (so, the queue is likely but not guaranteed to be empty).
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U> | ||||
| 	inline bool try_dequeue(consumer_token_t& token, U& item) | ||||
| 	{ | ||||
| 		if (sema->tryWait()) { | ||||
| 			while (!inner.try_dequeue(token, item)) { | ||||
| 				continue; | ||||
| 			} | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// Attempts to dequeue several elements from the queue.
 | ||||
| 	// Returns the number of items actually dequeued.
 | ||||
| 	// Returns 0 if all producer streams appeared empty at the time they
 | ||||
| 	// were checked (so, the queue is likely but not guaranteed to be empty).
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline size_t try_dequeue_bulk(It itemFirst, size_t max) | ||||
| 	{ | ||||
| 		size_t count = 0; | ||||
| 		max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); | ||||
| 		while (count != max) { | ||||
| 			count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count); | ||||
| 		} | ||||
| 		return count; | ||||
| 	} | ||||
| 	 | ||||
| 	// Attempts to dequeue several elements from the queue using an explicit consumer token.
 | ||||
| 	// Returns the number of items actually dequeued.
 | ||||
| 	// Returns 0 if all producer streams appeared empty at the time they
 | ||||
| 	// were checked (so, the queue is likely but not guaranteed to be empty).
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) | ||||
| 	{ | ||||
| 		size_t count = 0; | ||||
| 		max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); | ||||
| 		while (count != max) { | ||||
| 			count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count); | ||||
| 		} | ||||
| 		return count; | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 	 | ||||
| 	// Blocks the current thread until there's something to dequeue, then
 | ||||
| 	// dequeues it.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U> | ||||
| 	inline void wait_dequeue(U& item) | ||||
| 	{ | ||||
| 		while (!sema->wait()) { | ||||
| 			continue; | ||||
| 		} | ||||
| 		while (!inner.try_dequeue(item)) { | ||||
| 			continue; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Blocks the current thread until either there's something to dequeue
 | ||||
| 	// or the timeout (specified in microseconds) expires. Returns false
 | ||||
| 	// without setting `item` if the timeout expires, otherwise assigns
 | ||||
| 	// to `item` and returns true.
 | ||||
| 	// Using a negative timeout indicates an indefinite timeout,
 | ||||
| 	// and is thus functionally equivalent to calling wait_dequeue.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U> | ||||
| 	inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) | ||||
| 	{ | ||||
| 		if (!sema->wait(timeout_usecs)) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		while (!inner.try_dequeue(item)) { | ||||
| 			continue; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
|      | ||||
|     // Blocks the current thread until either there's something to dequeue
 | ||||
| 	// or the timeout expires. Returns false without setting `item` if the
 | ||||
|     // timeout expires, otherwise assigns to `item` and returns true.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U, typename Rep, typename Period> | ||||
| 	inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout) | ||||
|     { | ||||
|         return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count()); | ||||
|     } | ||||
| 	 | ||||
| 	// Blocks the current thread until there's something to dequeue, then
 | ||||
| 	// dequeues it using an explicit consumer token.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U> | ||||
| 	inline void wait_dequeue(consumer_token_t& token, U& item) | ||||
| 	{ | ||||
| 		while (!sema->wait()) { | ||||
| 			continue; | ||||
| 		} | ||||
| 		while (!inner.try_dequeue(token, item)) { | ||||
| 			continue; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Blocks the current thread until either there's something to dequeue
 | ||||
| 	// or the timeout (specified in microseconds) expires. Returns false
 | ||||
| 	// without setting `item` if the timeout expires, otherwise assigns
 | ||||
| 	// to `item` and returns true.
 | ||||
| 	// Using a negative timeout indicates an indefinite timeout,
 | ||||
| 	// and is thus functionally equivalent to calling wait_dequeue.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U> | ||||
| 	inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs) | ||||
| 	{ | ||||
| 		if (!sema->wait(timeout_usecs)) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		while (!inner.try_dequeue(token, item)) { | ||||
| 			continue; | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
|      | ||||
|     // Blocks the current thread until either there's something to dequeue
 | ||||
| 	// or the timeout expires. Returns false without setting `item` if the
 | ||||
|     // timeout expires, otherwise assigns to `item` and returns true.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename U, typename Rep, typename Period> | ||||
| 	inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration<Rep, Period> const& timeout) | ||||
|     { | ||||
|         return wait_dequeue_timed(token, item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count()); | ||||
|     } | ||||
| 	 | ||||
| 	// Attempts to dequeue several elements from the queue.
 | ||||
| 	// Returns the number of items actually dequeued, which will
 | ||||
| 	// always be at least one (this method blocks until the queue
 | ||||
| 	// is non-empty) and at most max.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline size_t wait_dequeue_bulk(It itemFirst, size_t max) | ||||
| 	{ | ||||
| 		size_t count = 0; | ||||
| 		max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); | ||||
| 		while (count != max) { | ||||
| 			count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count); | ||||
| 		} | ||||
| 		return count; | ||||
| 	} | ||||
| 	 | ||||
| 	// Attempts to dequeue several elements from the queue.
 | ||||
| 	// Returns the number of items actually dequeued, which can
 | ||||
| 	// be 0 if the timeout expires while waiting for elements,
 | ||||
| 	// and at most max.
 | ||||
| 	// Using a negative timeout indicates an indefinite timeout,
 | ||||
| 	// and is thus functionally equivalent to calling wait_dequeue_bulk.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs) | ||||
| 	{ | ||||
| 		size_t count = 0; | ||||
| 		max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); | ||||
| 		while (count != max) { | ||||
| 			count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count); | ||||
| 		} | ||||
| 		return count; | ||||
| 	} | ||||
|      | ||||
|     // Attempts to dequeue several elements from the queue.
 | ||||
| 	// Returns the number of items actually dequeued, which can
 | ||||
| 	// be 0 if the timeout expires while waiting for elements,
 | ||||
| 	// and at most max.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It, typename Rep, typename Period> | ||||
| 	inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout) | ||||
|     { | ||||
|         return wait_dequeue_bulk_timed<It&>(itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count()); | ||||
|     } | ||||
| 	 | ||||
| 	// Attempts to dequeue several elements from the queue using an explicit consumer token.
 | ||||
| 	// Returns the number of items actually dequeued, which will
 | ||||
| 	// always be at least one (this method blocks until the queue
 | ||||
| 	// is non-empty) and at most max.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) | ||||
| 	{ | ||||
| 		size_t count = 0; | ||||
| 		max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); | ||||
| 		while (count != max) { | ||||
| 			count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count); | ||||
| 		} | ||||
| 		return count; | ||||
| 	} | ||||
| 	 | ||||
| 	// Attempts to dequeue several elements from the queue using an explicit consumer token.
 | ||||
| 	// Returns the number of items actually dequeued, which can
 | ||||
| 	// be 0 if the timeout expires while waiting for elements,
 | ||||
| 	// and at most max.
 | ||||
| 	// Using a negative timeout indicates an indefinite timeout,
 | ||||
| 	// and is thus functionally equivalent to calling wait_dequeue_bulk.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It> | ||||
| 	inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs) | ||||
| 	{ | ||||
| 		size_t count = 0; | ||||
| 		max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); | ||||
| 		while (count != max) { | ||||
| 			count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count); | ||||
| 		} | ||||
| 		return count; | ||||
| 	} | ||||
| 	 | ||||
| 	// Attempts to dequeue several elements from the queue using an explicit consumer token.
 | ||||
| 	// Returns the number of items actually dequeued, which can
 | ||||
| 	// be 0 if the timeout expires while waiting for elements,
 | ||||
| 	// and at most max.
 | ||||
| 	// Never allocates. Thread-safe.
 | ||||
| 	template<typename It, typename Rep, typename Period> | ||||
| 	inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout) | ||||
|     { | ||||
|         return wait_dequeue_bulk_timed<It&>(token, itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count()); | ||||
|     } | ||||
| 	 | ||||
| 	 | ||||
| 	// Returns an estimate of the total number of elements currently in the queue. This
 | ||||
| 	// estimate is only accurate if the queue has completely stabilized before it is called
 | ||||
| 	// (i.e. all enqueue and dequeue operations have completed and their memory effects are
 | ||||
| 	// visible on the calling thread, and no further operations start while this method is
 | ||||
| 	// being called).
 | ||||
| 	// Thread-safe.
 | ||||
| 	inline size_t size_approx() const | ||||
| 	{ | ||||
| 		return (size_t)sema->availableApprox(); | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 	// Returns true if the underlying atomic variables used by
 | ||||
| 	// the queue are lock-free (they should be on most platforms).
 | ||||
| 	// Thread-safe.
 | ||||
| 	static constexpr bool is_lock_free() | ||||
| 	{ | ||||
| 		return ConcurrentQueue::is_lock_free(); | ||||
| 	} | ||||
| 	 | ||||
| 
 | ||||
| private: | ||||
| 	template<typename U, typename A1, typename A2> | ||||
| 	static inline U* create(A1&& a1, A2&& a2) | ||||
| 	{ | ||||
| 		void* p = (Traits::malloc)(sizeof(U)); | ||||
| 		return p != nullptr ? new (p) U(std::forward<A1>(a1), std::forward<A2>(a2)) : nullptr; | ||||
| 	} | ||||
| 	 | ||||
| 	template<typename U> | ||||
| 	static inline void destroy(U* p) | ||||
| 	{ | ||||
| 		if (p != nullptr) { | ||||
| 			p->~U(); | ||||
| 		} | ||||
| 		(Traits::free)(p); | ||||
| 	} | ||||
| 	 | ||||
| private: | ||||
| 	ConcurrentQueue inner; | ||||
| 	std::unique_ptr<LightweightSemaphore, void (*)(LightweightSemaphore*)> sema; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| template<typename T, typename Traits> | ||||
| inline void swap(BlockingConcurrentQueue<T, Traits>& a, BlockingConcurrentQueue<T, Traits>& b) MOODYCAMEL_NOEXCEPT | ||||
| { | ||||
| 	a.swap(b); | ||||
| } | ||||
| 
 | ||||
| }	// end namespace moodycamel
 | ||||
							
								
								
									
										3747
									
								
								thirdparty/concurrentqueue.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3747
									
								
								thirdparty/concurrentqueue.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										425
									
								
								thirdparty/lightweightsemaphore.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								thirdparty/lightweightsemaphore.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,425 @@ | |||
| // Provides an efficient implementation of a semaphore (LightweightSemaphore).
 | ||||
| // This is an extension of Jeff Preshing's sempahore implementation (licensed 
 | ||||
| // under the terms of its separate zlib license) that has been adapted and
 | ||||
| // extended by Cameron Desrochers.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cstddef> // For std::size_t | ||||
| #include <atomic> | ||||
| #include <type_traits> // For std::make_signed<T> | ||||
| 
 | ||||
| #if defined(_WIN32) | ||||
| // Avoid including windows.h in a header; we only need a handful of
 | ||||
| // items, so we'll redeclare them here (this is relatively safe since
 | ||||
| // the API generally has to remain stable between Windows versions).
 | ||||
| // I know this is an ugly hack but it still beats polluting the global
 | ||||
| // namespace with thousands of generic names or adding a .cpp for nothing.
 | ||||
| extern "C" { | ||||
| 	struct _SECURITY_ATTRIBUTES; | ||||
| 	__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); | ||||
| 	__declspec(dllimport) int __stdcall CloseHandle(void* hObject); | ||||
| 	__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); | ||||
| 	__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); | ||||
| } | ||||
| #elif defined(__MACH__) | ||||
| #include <mach/mach.h> | ||||
| #elif defined(__unix__) | ||||
| #include <semaphore.h> | ||||
| 
 | ||||
| #if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE) | ||||
| #if __GLIBC_PREREQ(2,30) | ||||
| #define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC | ||||
| #endif | ||||
| #endif | ||||
| #endif | ||||
| 
 | ||||
| namespace moodycamel | ||||
| { | ||||
| namespace details | ||||
| { | ||||
| 
 | ||||
| // Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's
 | ||||
| // portable + lightweight semaphore implementations, originally from
 | ||||
| // https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
 | ||||
| // LICENSE:
 | ||||
| // Copyright (c) 2015 Jeff Preshing
 | ||||
| //
 | ||||
| // This software is provided 'as-is', without any express or implied
 | ||||
| // warranty. In no event will the authors be held liable for any damages
 | ||||
| // arising from the use of this software.
 | ||||
| //
 | ||||
| // Permission is granted to anyone to use this software for any purpose,
 | ||||
| // including commercial applications, and to alter it and redistribute it
 | ||||
| // freely, subject to the following restrictions:
 | ||||
| //
 | ||||
| // 1. The origin of this software must not be misrepresented; you must not
 | ||||
| //	claim that you wrote the original software. If you use this software
 | ||||
| //	in a product, an acknowledgement in the product documentation would be
 | ||||
| //	appreciated but is not required.
 | ||||
| // 2. Altered source versions must be plainly marked as such, and must not be
 | ||||
| //	misrepresented as being the original software.
 | ||||
| // 3. This notice may not be removed or altered from any source distribution.
 | ||||
| #if defined(_WIN32) | ||||
| class Semaphore | ||||
| { | ||||
| private: | ||||
| 	void* m_hSema; | ||||
| 	 | ||||
| 	Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 	Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 
 | ||||
| public: | ||||
| 	Semaphore(int initialCount = 0) | ||||
| 	{ | ||||
| 		assert(initialCount >= 0); | ||||
| 		const long maxLong = 0x7fffffff; | ||||
| 		m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); | ||||
| 		assert(m_hSema); | ||||
| 	} | ||||
| 
 | ||||
| 	~Semaphore() | ||||
| 	{ | ||||
| 		CloseHandle(m_hSema); | ||||
| 	} | ||||
| 
 | ||||
| 	bool wait() | ||||
| 	{ | ||||
| 		const unsigned long infinite = 0xffffffff; | ||||
| 		return WaitForSingleObject(m_hSema, infinite) == 0; | ||||
| 	} | ||||
| 	 | ||||
| 	bool try_wait() | ||||
| 	{ | ||||
| 		return WaitForSingleObject(m_hSema, 0) == 0; | ||||
| 	} | ||||
| 	 | ||||
| 	bool timed_wait(std::uint64_t usecs) | ||||
| 	{ | ||||
| 		return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; | ||||
| 	} | ||||
| 
 | ||||
| 	void signal(int count = 1) | ||||
| 	{ | ||||
| 		while (!ReleaseSemaphore(m_hSema, count, nullptr)); | ||||
| 	} | ||||
| }; | ||||
| #elif defined(__MACH__) | ||||
| //---------------------------------------------------------
 | ||||
| // Semaphore (Apple iOS and OSX)
 | ||||
| // Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
 | ||||
| //---------------------------------------------------------
 | ||||
| class Semaphore | ||||
| { | ||||
| private: | ||||
| 	semaphore_t m_sema; | ||||
| 
 | ||||
| 	Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 	Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 
 | ||||
| public: | ||||
| 	Semaphore(int initialCount = 0) | ||||
| 	{ | ||||
| 		assert(initialCount >= 0); | ||||
| 		kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); | ||||
| 		assert(rc == KERN_SUCCESS); | ||||
| 		(void)rc; | ||||
| 	} | ||||
| 
 | ||||
| 	~Semaphore() | ||||
| 	{ | ||||
| 		semaphore_destroy(mach_task_self(), m_sema); | ||||
| 	} | ||||
| 
 | ||||
| 	bool wait() | ||||
| 	{ | ||||
| 		return semaphore_wait(m_sema) == KERN_SUCCESS; | ||||
| 	} | ||||
| 	 | ||||
| 	bool try_wait() | ||||
| 	{ | ||||
| 		return timed_wait(0); | ||||
| 	} | ||||
| 	 | ||||
| 	bool timed_wait(std::uint64_t timeout_usecs) | ||||
| 	{ | ||||
| 		mach_timespec_t ts; | ||||
| 		ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000); | ||||
| 		ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000); | ||||
| 
 | ||||
| 		// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
 | ||||
| 		kern_return_t rc = semaphore_timedwait(m_sema, ts); | ||||
| 		return rc == KERN_SUCCESS; | ||||
| 	} | ||||
| 
 | ||||
| 	void signal() | ||||
| 	{ | ||||
| 		while (semaphore_signal(m_sema) != KERN_SUCCESS); | ||||
| 	} | ||||
| 
 | ||||
| 	void signal(int count) | ||||
| 	{ | ||||
| 		while (count-- > 0) | ||||
| 		{ | ||||
| 			while (semaphore_signal(m_sema) != KERN_SUCCESS); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| #elif defined(__unix__) | ||||
| //---------------------------------------------------------
 | ||||
| // Semaphore (POSIX, Linux)
 | ||||
| //---------------------------------------------------------
 | ||||
| class Semaphore | ||||
| { | ||||
| private: | ||||
| 	sem_t m_sema; | ||||
| 
 | ||||
| 	Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 	Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; | ||||
| 
 | ||||
| public: | ||||
| 	Semaphore(int initialCount = 0) | ||||
| 	{ | ||||
| 		assert(initialCount >= 0); | ||||
| 		int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount)); | ||||
| 		assert(rc == 0); | ||||
| 		(void)rc; | ||||
| 	} | ||||
| 
 | ||||
| 	~Semaphore() | ||||
| 	{ | ||||
| 		sem_destroy(&m_sema); | ||||
| 	} | ||||
| 
 | ||||
| 	bool wait() | ||||
| 	{ | ||||
| 		// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
 | ||||
| 		int rc; | ||||
| 		do { | ||||
| 			rc = sem_wait(&m_sema); | ||||
| 		} while (rc == -1 && errno == EINTR); | ||||
| 		return rc == 0; | ||||
| 	} | ||||
| 
 | ||||
| 	bool try_wait() | ||||
| 	{ | ||||
| 		int rc; | ||||
| 		do { | ||||
| 			rc = sem_trywait(&m_sema); | ||||
| 		} while (rc == -1 && errno == EINTR); | ||||
| 		return rc == 0; | ||||
| 	} | ||||
| 
 | ||||
| 	bool timed_wait(std::uint64_t usecs) | ||||
| 	{ | ||||
| 		struct timespec ts; | ||||
| 		const int usecs_in_1_sec = 1000000; | ||||
| 		const int nsecs_in_1_sec = 1000000000; | ||||
| #ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC | ||||
| 		clock_gettime(CLOCK_MONOTONIC, &ts); | ||||
| #else | ||||
| 		clock_gettime(CLOCK_REALTIME, &ts); | ||||
| #endif | ||||
| 		ts.tv_sec += (time_t)(usecs / usecs_in_1_sec); | ||||
| 		ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000; | ||||
| 		// sem_timedwait bombs if you have more than 1e9 in tv_nsec
 | ||||
| 		// so we have to clean things up before passing it in
 | ||||
| 		if (ts.tv_nsec >= nsecs_in_1_sec) { | ||||
| 			ts.tv_nsec -= nsecs_in_1_sec; | ||||
| 			++ts.tv_sec; | ||||
| 		} | ||||
| 
 | ||||
| 		int rc; | ||||
| 		do { | ||||
| #ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC | ||||
| 			rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts); | ||||
| #else | ||||
| 			rc = sem_timedwait(&m_sema, &ts); | ||||
| #endif | ||||
| 		} while (rc == -1 && errno == EINTR); | ||||
| 		return rc == 0; | ||||
| 	} | ||||
| 
 | ||||
| 	void signal() | ||||
| 	{ | ||||
| 		while (sem_post(&m_sema) == -1); | ||||
| 	} | ||||
| 
 | ||||
| 	void signal(int count) | ||||
| 	{ | ||||
| 		while (count-- > 0) | ||||
| 		{ | ||||
| 			while (sem_post(&m_sema) == -1); | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
| #else | ||||
| #error Unsupported platform! (No semaphore wrapper available) | ||||
| #endif | ||||
| 
 | ||||
| }	// end namespace details
 | ||||
| 
 | ||||
| 
 | ||||
| //---------------------------------------------------------
 | ||||
| // LightweightSemaphore
 | ||||
| //---------------------------------------------------------
 | ||||
| class LightweightSemaphore | ||||
| { | ||||
| public: | ||||
| 	typedef std::make_signed<std::size_t>::type ssize_t; | ||||
| 
 | ||||
| private: | ||||
| 	std::atomic<ssize_t> m_count; | ||||
| 	details::Semaphore m_sema; | ||||
| 	int m_maxSpins; | ||||
| 
 | ||||
| 	bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) | ||||
| 	{ | ||||
| 		ssize_t oldCount; | ||||
| 		int spin = m_maxSpins; | ||||
| 		while (--spin >= 0) | ||||
| 		{ | ||||
| 			oldCount = m_count.load(std::memory_order_relaxed); | ||||
| 			if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) | ||||
| 				return true; | ||||
| 			std::atomic_signal_fence(std::memory_order_acquire);	 // Prevent the compiler from collapsing the loop.
 | ||||
| 		} | ||||
| 		oldCount = m_count.fetch_sub(1, std::memory_order_acquire); | ||||
| 		if (oldCount > 0) | ||||
| 			return true; | ||||
| 		if (timeout_usecs < 0) | ||||
| 		{ | ||||
| 			if (m_sema.wait()) | ||||
| 				return true; | ||||
| 		} | ||||
| 		if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs)) | ||||
| 			return true; | ||||
| 		// At this point, we've timed out waiting for the semaphore, but the
 | ||||
| 		// count is still decremented indicating we may still be waiting on
 | ||||
| 		// it. So we have to re-adjust the count, but only if the semaphore
 | ||||
| 		// wasn't signaled enough times for us too since then. If it was, we
 | ||||
| 		// need to release the semaphore too.
 | ||||
| 		while (true) | ||||
| 		{ | ||||
| 			oldCount = m_count.load(std::memory_order_acquire); | ||||
| 			if (oldCount >= 0 && m_sema.try_wait()) | ||||
| 				return true; | ||||
| 			if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) | ||||
| 				return false; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1) | ||||
| 	{ | ||||
| 		assert(max > 0); | ||||
| 		ssize_t oldCount; | ||||
| 		int spin = m_maxSpins; | ||||
| 		while (--spin >= 0) | ||||
| 		{ | ||||
| 			oldCount = m_count.load(std::memory_order_relaxed); | ||||
| 			if (oldCount > 0) | ||||
| 			{ | ||||
| 				ssize_t newCount = oldCount > max ? oldCount - max : 0; | ||||
| 				if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) | ||||
| 					return oldCount - newCount; | ||||
| 			} | ||||
| 			std::atomic_signal_fence(std::memory_order_acquire); | ||||
| 		} | ||||
| 		oldCount = m_count.fetch_sub(1, std::memory_order_acquire); | ||||
| 		if (oldCount <= 0) | ||||
| 		{ | ||||
| 			if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) | ||||
| 			{ | ||||
| 				while (true) | ||||
| 				{ | ||||
| 					oldCount = m_count.load(std::memory_order_acquire); | ||||
| 					if (oldCount >= 0 && m_sema.try_wait()) | ||||
| 						break; | ||||
| 					if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) | ||||
| 						return 0; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if (max > 1) | ||||
| 			return 1 + tryWaitMany(max - 1); | ||||
| 		return 1; | ||||
| 	} | ||||
| 
 | ||||
| public: | ||||
| 	LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins) | ||||
| 	{ | ||||
| 		assert(initialCount >= 0); | ||||
| 		assert(maxSpins >= 0); | ||||
| 	} | ||||
| 
 | ||||
| 	bool tryWait() | ||||
| 	{ | ||||
| 		ssize_t oldCount = m_count.load(std::memory_order_relaxed); | ||||
| 		while (oldCount > 0) | ||||
| 		{ | ||||
| 			if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) | ||||
| 				return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	bool wait() | ||||
| 	{ | ||||
| 		return tryWait() || waitWithPartialSpinning(); | ||||
| 	} | ||||
| 
 | ||||
| 	bool wait(std::int64_t timeout_usecs) | ||||
| 	{ | ||||
| 		return tryWait() || waitWithPartialSpinning(timeout_usecs); | ||||
| 	} | ||||
| 
 | ||||
| 	// Acquires between 0 and (greedily) max, inclusive
 | ||||
| 	ssize_t tryWaitMany(ssize_t max) | ||||
| 	{ | ||||
| 		assert(max >= 0); | ||||
| 		ssize_t oldCount = m_count.load(std::memory_order_relaxed); | ||||
| 		while (oldCount > 0) | ||||
| 		{ | ||||
| 			ssize_t newCount = oldCount > max ? oldCount - max : 0; | ||||
| 			if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) | ||||
| 				return oldCount - newCount; | ||||
| 		} | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	// Acquires at least one, and (greedily) at most max
 | ||||
| 	ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs) | ||||
| 	{ | ||||
| 		assert(max >= 0); | ||||
| 		ssize_t result = tryWaitMany(max); | ||||
| 		if (result == 0 && max > 0) | ||||
| 			result = waitManyWithPartialSpinning(max, timeout_usecs); | ||||
| 		return result; | ||||
| 	} | ||||
| 	 | ||||
| 	ssize_t waitMany(ssize_t max) | ||||
| 	{ | ||||
| 		ssize_t result = waitMany(max, -1); | ||||
| 		assert(result > 0); | ||||
| 		return result; | ||||
| 	} | ||||
| 
 | ||||
| 	void signal(ssize_t count = 1) | ||||
| 	{ | ||||
| 		assert(count >= 0); | ||||
| 		ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release); | ||||
| 		ssize_t toRelease = -oldCount < count ? -oldCount : count; | ||||
| 		if (toRelease > 0) | ||||
| 		{ | ||||
| 			m_sema.signal((int)toRelease); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	std::size_t availableApprox() const | ||||
| 	{ | ||||
| 		ssize_t count = m_count.load(std::memory_order_relaxed); | ||||
| 		return count > 0 ? static_cast<std::size_t>(count) : 0; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| }   // end namespace moodycamel
 | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Mr-Wiseguy
						Mr-Wiseguy