Build system & dependency refactoring

Switched to a makefile + clang build system
Now builds as a library by default
Removed SDL2 dependency
Changed RT64 integration to use zilmar spec in order to pass a window
handle
Proper unaligned loads and stores implementation
This commit is contained in:
Mr-Wiseguy 2023-06-22 18:29:55 -04:00
parent 73308e3500
commit 0fdfc5f7fe
13 changed files with 403 additions and 208 deletions

View File

@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.33027.164
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MMRecomp", "MMRecomp.vcxproj", "{14B47028-6A86-4660-A86D-2E69F229E110}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MMRecomp", "MMRecomp.vcxproj", "{14B47028-6A86-4660-A86D-2E69F229E110}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RecompiledFuncs", "RecompiledFuncs.vcxproj", "{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
@ -23,14 +21,6 @@ Global
{14B47028-6A86-4660-A86D-2E69F229E110}.Release|x64.Build.0 = Release|x64 {14B47028-6A86-4660-A86D-2E69F229E110}.Release|x64.Build.0 = Release|x64
{14B47028-6A86-4660-A86D-2E69F229E110}.Release|x86.ActiveCfg = Release|Win32 {14B47028-6A86-4660-A86D-2E69F229E110}.Release|x86.ActiveCfg = Release|Win32
{14B47028-6A86-4660-A86D-2E69F229E110}.Release|x86.Build.0 = Release|Win32 {14B47028-6A86-4660-A86D-2E69F229E110}.Release|x86.Build.0 = Release|Win32
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x64.ActiveCfg = Debug|x64
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x64.Build.0 = Debug|x64
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x86.ActiveCfg = Debug|Win32
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Debug|x86.Build.0 = Debug|Win32
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x64.ActiveCfg = Release|x64
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x64.Build.0 = Release|x64
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x86.ActiveCfg = Release|Win32
{7BF5E3F9-C49F-4C84-AB64-7681F028CAC2}.Release|x86.Build.0 = Release|Win32
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -40,13 +40,13 @@
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries> <UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset> <PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet> <CharacterSet>Unicode</CharacterSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType> <ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries> <UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset> <PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization> <WholeProgramOptimization>true</WholeProgramOptimization>
@ -78,9 +78,15 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental> <LinkIncremental>true</LinkIncremental>
<NMakeBuildCommandLine>make TARGET=$(Configuration) LIB_DIR="$(VC_LibraryPath_VC_x64_Desktop)" UCRT_DIR="$(UniversalCRT_LibraryPath_x64)" SDK_DIR="$(WindowsSDK_LibraryPath)\x64" -j$(NUMBER_OF_PROCESSORS)</NMakeBuildCommandLine>
<NMakeCleanCommandLine>make TARGET=$(Configuration) clean</NMakeCleanCommandLine>
<OutDir>$(SolutionDir)build\$(Configuration)\</OutDir>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental> <LinkIncremental>false</LinkIncremental>
<NMakeBuildCommandLine>make TARGET=$(Configuration) LIB_DIR="$(VC_LibraryPath_VC_x64_Desktop)" UCRT_DIR="$(UniversalCRT_LibraryPath_x64)" SDK_DIR="$(WindowsSDK_LibraryPath)\x64" -j$(NUMBER_OF_PROCESSORS)</NMakeBuildCommandLine>
<NMakeCleanCommandLine>make TARGET=$(Configuration) clean</NMakeCleanCommandLine>
<OutDir>$(SolutionDir)build\$(Configuration)\</OutDir>
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile> <ClCompile>
@ -225,11 +231,6 @@ XCOPY "$(ProjectDir)lib\SDL2-2.24.0\lib\$(Platform)\SDL2.dll" "$(TargetDir)" /S
<ClInclude Include="portultra\ultra64.h" /> <ClInclude Include="portultra\ultra64.h" />
<ClInclude Include="src\euc-jp.h" /> <ClInclude Include="src\euc-jp.h" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="RecompiledFuncs.vcxproj">
<Project>{7bf5e3f9-c49f-4c84-ab64-7681f028cac2}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
</ImportGroup> </ImportGroup>

108
Makefile Normal file
View File

@ -0,0 +1,108 @@
CONFIG ?= Debug
LIB ?= 0
ifeq ($(CONFIG),Debug)
BUILD_DIR := build/debug
FUNC_OPTFLAGS := -Og -g -fno-strict-aliasing
OPTFLAGS := -Og -g -fno-strict-aliasing
# Static C runtime linking
LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -llibcmtd -llibvcruntimed -llibucrtd
# Dynamic
# LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -lmsvcrtd -lvcruntimed -lucrtd
else ifeq ($(CONFIG),Release)
BUILD_DIR := build/release
FUNC_OPTFLAGS := -O2 -g -fno-strict-aliasing
OPTFLAGS := -O2 -g -fno-strict-aliasing
# Static C runtime linking
LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -llibcmt -llibvcruntime -llibucrt
# Dynamic
# LIBS := -Wl,/nodefaultlib:libcmt -Wl,/nodefaultlib:ucrt -Wl,/nodefaultlib:libucrt -lmsvcrt -lvcruntime -lucrt
else
$(error "Invalid build configuration: $(CONFIG)")
endif
SRC_DIRS := portultra src rsp
FUNCS_DIR := RecompiledFuncs
FUNCS_LIB := $(BUILD_DIR)/RecompiledFuncs.lib
FUNCS_C_SRCS := $(wildcard $(FUNCS_DIR)/*.c)
FUNCS_CXX_SRCS := $(wildcard $(FUNCS_DIR)/*.cpp)
FUNC_BUILD_DIR := $(BUILD_DIR)/RecompiledFuncs
FUNCS_C_OBJS := $(addprefix $(BUILD_DIR)/,$(FUNCS_C_SRCS:.c=.o))
FUNCS_CXX_OBJS := $(addprefix $(BUILD_DIR)/,$(FUNCS_CXX_SRCS:.cpp=.o))
ALL_FUNC_OBJS := $(FUNCS_C_OBJS) $(FUNCS_CXX_OBJS)
BUILD_SRC_DIRS := $(addprefix $(BUILD_DIR)/,$(SRC_DIRS))
C_SRCS := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c))
CXX_SRCS := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.cpp))
C_OBJS := $(addprefix $(BUILD_DIR)/,$(C_SRCS:.c=.o))
CXX_OBJS := $(addprefix $(BUILD_DIR)/,$(CXX_SRCS:.cpp=.o))
ALL_OBJS := $(C_OBJS) $(CXX_OBJS)
CC := clang
CXX := clang++
LIB := clang++
LD := clang++
FUNC_CFLAGS := $(FUNC_OPTFLAGS) -c -Wno-unused-but-set-variable
FUNC_CXXFLAGS := $(FUNC_OPTFLAGS) -std=c++20 -c
FUNC_CPPFLAGS := -Iinclude
LIBFLAGS := $(OPTFLAGS) -fuse-ld=llvm-lib
LIB_DIR ?= C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64
UCRT_DIR ?= C:\Program Files (x86)\Windows Kits\10\lib\10.0.22000.0\ucrt\x64;
SDK_DIR ?= C:\Program Files (x86)\Windows Kits\10\lib\10.0.22000.0\um\x64
WARNFLAGS := -Wall -Wextra -Wpedantic -Wno-gnu-anonymous-struct
CFLAGS := -ffunction-sections -fdata-sections $(OPTFLAGS) $(WARNFLAGS) -c
CXXFLAGS := -ffunction-sections -fdata-sections $(OPTFLAGS) $(WARNFLAGS) -std=c++20 -c
CPPFLAGS := -Iinclude -Ithirdparty
LDFLAGS := -v -Wl,/OPT:REF $(OPTFLAGS) $(LIBS) -L"$(LIB_DIR:;=)" -L"$(UCRT_DIR:;=)" -L"$(SDK_DIR:;=)" lib/RT64/$(CONFIG)/RT64.lib
ifeq ($(LIB),1)
TARGET := $(BUILD_DIR)/MMRecomp.dll
LDFLAGS += -shared
else
TARGET := $(BUILD_DIR)/MMRecomp.exe
endif
default: $(TARGET)
clean:
rmdir /S /Q $(subst /,\\,$(BUILD_DIR))
cleanfuncs:
$(FUNCS_CXX_OBJS) : $(BUILD_DIR)/%.o : %.cpp | $(FUNC_BUILD_DIR)
@$(CXX) $(FUNC_CXXFLAGS) $(FUNC_CPPFLAGS) $^ -o $@
$(FUNCS_C_OBJS) : $(BUILD_DIR)/%.o : %.c | $(FUNC_BUILD_DIR)
@$(CC) $(FUNC_CFLAGS) $(FUNC_CPPFLAGS) $^ -o $@
$(FUNCS_LIB): $(ALL_FUNC_OBJS) | $(BUILD_DIR)
$(LIB) $(LIBFLAGS) $(FUNC_BUILD_DIR)/*.o -o $@
$(CXX_OBJS) : $(BUILD_DIR)/%.o : %.cpp | $(BUILD_SRC_DIRS)
$(CXX) -MMD -MF $(@:.o=.d) $(CXXFLAGS) $(CPPFLAGS) $< -o $@
$(C_OBJS) : $(BUILD_DIR)/%.o : %.c | $(BUILD_SRC_DIRS)
$(CC) -MMD -MF $(@:.o=.d) $(CFLAGS) $(CPPFLAGS) $< -o $@
$(TARGET): $(FUNCS_LIB) $(ALL_OBJS) | $(BUILD_SRC_DIRS)
$(LD) $(LDFLAGS) -o $@ $^
$(BUILD_SRC_DIRS) $(FUNC_BUILD_DIR) $(BUILD_DIR):
mkdir $(subst /,\\,$@)
-include $(ALL_OBJS:.o=.d)
MAKEFLAGS += --no-builtin-rules
.SUFFIXES:
.PHONY: default clean cleanfuncs
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true

View File

@ -66,27 +66,68 @@ static inline uint64_t load_doubleword(uint8_t* rdram, gpr reg, gpr offset) {
#define LD(offset, reg) \ #define LD(offset, reg) \
load_doubleword(rdram, offset, reg) load_doubleword(rdram, offset, reg)
// TODO proper lwl/lwr/swl/swr static inline gpr do_lwl(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) { // Calculate the overall address
uint8_t byte0 = (uint8_t)(val >> 24); gpr address = (offset + reg);
uint8_t byte1 = (uint8_t)(val >> 16);
uint8_t byte2 = (uint8_t)(val >> 8);
uint8_t byte3 = (uint8_t)(val >> 0);
MEM_B(offset + 0, reg) = byte0; // Load the aligned word
MEM_B(offset + 1, reg) = byte1; gpr word_address = address & ~0x3;
MEM_B(offset + 2, reg) = byte2; uint32_t loaded_value = MEM_W(0, word_address);
MEM_B(offset + 3, reg) = byte3;
}
static inline gpr do_lwl(uint8_t* rdram, gpr offset, gpr reg) { // Mask the existing value and shift the loaded value appropriately
uint8_t byte0 = MEM_B(offset + 0, reg); gpr misalignment = address & 0x3;
uint8_t byte1 = MEM_B(offset + 1, reg); gpr masked_value = initial_value & ~(0xFFFFFFFFu << (misalignment * 8));
uint8_t byte2 = MEM_B(offset + 2, reg); loaded_value <<= (misalignment * 8);
uint8_t byte3 = MEM_B(offset + 3, reg);
// Cast to int32_t to sign extend first // Cast to int32_t to sign extend first
return (gpr)(int32_t)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | (byte3 << 0)); return (gpr)(int32_t)(masked_value | loaded_value);
}
static inline gpr do_lwr(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) {
// Calculate the overall address
gpr address = (offset + reg);
// Load the aligned word
gpr word_address = address & ~0x3;
uint32_t loaded_value = MEM_W(0, word_address);
// Mask the existing value and shift the loaded value appropriately
gpr misalignment = address & 0x3;
gpr masked_value = initial_value & ~(0xFFFFFFFFu >> (24 - misalignment * 8));
loaded_value >>= (24 - misalignment * 8);
// Cast to int32_t to sign extend first
return (gpr)(int32_t)(masked_value | loaded_value);
}
static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
// Calculate the overall address
gpr address = (offset + reg);
// Get the initial value of the aligned word
gpr word_address = address & ~0x3;
uint32_t initial_value = MEM_W(0, word_address);
// Mask the initial value and shift the input value appropriately
gpr misalignment = address & 0x3;
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu >> (misalignment * 8));
uint32_t shifted_input_value = ((uint32_t)val) >> (misalignment * 8);
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
}
static inline void do_swr(uint8_t* rdram, gpr offset, gpr reg, gpr val) {
// Calculate the overall address
gpr address = (offset + reg);
// Get the initial value of the aligned word
gpr word_address = address & ~0x3;
uint32_t initial_value = MEM_W(0, word_address);
// Mask the initial value and shift the input value appropriately
gpr misalignment = address & 0x3;
uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu << (24 - misalignment * 8));
uint32_t shifted_input_value = ((uint32_t)val) << (24 - misalignment * 8);
MEM_W(0, word_address) = masked_initial_value | shifted_input_value;
} }
#define S32(val) \ #define S32(val) \

View File

@ -2,6 +2,11 @@
#define __RT64_LAYER_H__ #define __RT64_LAYER_H__
typedef struct { typedef struct {
void* hWnd;
void* hStatusBar;
int Reserved;
unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */ unsigned char* HEADER; /* This is the rom header (first 40h bytes of the rom) */
unsigned char* RDRAM; unsigned char* RDRAM;
unsigned char* DMEM; unsigned char* DMEM;
@ -35,9 +40,9 @@ typedef struct {
void (*CheckInterrupts)(void); void (*CheckInterrupts)(void);
unsigned int version; // unsigned int version;
unsigned int* SP_STATUS_REG; // unsigned int* SP_STATUS_REG;
const unsigned int* RDRAM_SIZE; // const unsigned int* RDRAM_SIZE;
} GFX_INFO; } GFX_INFO;
#define DLLEXPORT extern "C" __declspec(dllexport) #define DLLEXPORT extern "C" __declspec(dllexport)
@ -56,6 +61,7 @@ DLLIMPORT void ProcessRDPList(void);
DLLIMPORT void ProcessDList(void); DLLIMPORT void ProcessDList(void);
DLLIMPORT void UpdateScreen(void); DLLIMPORT void UpdateScreen(void);
DLLIMPORT void PumpEvents(void); DLLIMPORT void PumpEvents(void);
DLLIMPORT void ChangeWindow(void);
#endif #endif

View File

@ -1,55 +1,39 @@
#include "ultra64.h" #include "ultra64.h"
#include "multilibultra.hpp" #include "multilibultra.hpp"
#include "SDL.h"
#include "SDL_audio.h"
#include <cassert> #include <cassert>
static SDL_AudioDeviceID audio_device = 0;
static uint32_t sample_rate = 48000; static uint32_t sample_rate = 48000;
static Multilibultra::audio_callbacks_t audio_callbacks;
void Multilibultra::set_audio_callbacks(const audio_callbacks_t* callbacks) {
if (callbacks != nullptr) {
audio_callbacks = *callbacks;
}
}
void Multilibultra::init_audio() { 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. // Pick an initial dummy sample rate; this will be set by the game later to the true sample rate.
set_audio_frequency(48000); set_audio_frequency(48000);
} }
void Multilibultra::set_audio_frequency(uint32_t freq) { void Multilibultra::set_audio_frequency(uint32_t freq) {
if (audio_device != 0) { if (audio_callbacks.set_frequency) {
SDL_CloseAudioDevice(audio_device); audio_callbacks.set_frequency(freq);
} }
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; sample_rate = freq;
} }
void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t byte_count) { void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(int16_t) audio_data_, uint32_t byte_count) {
// Buffer for holding the output of swapping the audio channels. This is reused across // Buffer for holding the output of swapping the audio channels. This is reused across
// calls to reduce runtime allocations. // calls to reduce runtime allocations.
static std::vector<uint16_t> swap_buffer; static std::vector<int16_t> swap_buffer;
// Ensure that the byte count is an integer multiple of samples. // Ensure that the byte count is an integer multiple of samples.
assert((byte_count & 1) == 0); assert((byte_count & 1) == 0);
// Calculate the number of samples from the number of bytes. // Calculate the number of samples from the number of bytes.
uint32_t sample_count = byte_count / sizeof(s16); uint32_t sample_count = byte_count / sizeof(int16_t);
// Make sure the swap buffer is large enough to hold all the incoming audio data. // Make sure the swap buffer is large enough to hold all the incoming audio data.
if (sample_count > swap_buffer.size()) { if (sample_count > swap_buffer.size()) {
@ -57,34 +41,45 @@ void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t
} }
// Swap the audio channels into the swap buffer to correct for the address xor caused by endianness handling. // 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_); int16_t* audio_data = TO_PTR(int16_t, audio_data_);
for (size_t i = 0; i < sample_count; i += 2) { for (size_t i = 0; i < sample_count; i += 2) {
swap_buffer[i + 0] = audio_data[i + 1]; swap_buffer[i + 0] = audio_data[i + 1];
swap_buffer[i + 1] = audio_data[i + 0]; swap_buffer[i + 1] = audio_data[i + 0];
} }
// Queue the swapped audio data. // Queue the swapped audio data.
SDL_QueueAudio(audio_device, swap_buffer.data(), byte_count); if (audio_callbacks.queue_samples) {
audio_callbacks.queue_samples(swap_buffer.data(), sample_count);
}
} }
uint32_t buffer_offset_frames = 1; // For SDL2
//uint32_t buffer_offset_frames = 1;
// For Godot
float buffer_offset_frames = 0.5f;
// If there's ever any audio popping, check here first. Some games are very sensitive to // 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. // 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. // Reporting a number that's too low can lead to audio lag in some games.
uint32_t Multilibultra::get_remaining_audio_bytes() { uint32_t Multilibultra::get_remaining_audio_bytes() {
// Get the number of remaining buffered audio bytes. // Get the number of remaining buffered audio bytes.
uint32_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device); uint32_t buffered_byte_count;
if (audio_callbacks.get_samples_remaining()) {
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that buffered_byte_count = audio_callbacks.get_samples_remaining() * sizeof(int16_t);
// there are enough samples even if the audio thread 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 > (buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) {
buffered_byte_count -= (buffer_offset_frames * sizeof(int16_t) * samples_per_vi);
} else {
buffered_byte_count = 0;
} }
return buffered_byte_count; else {
buffered_byte_count = 100;
}
// Adjust the reported count to be some number of refreshes in the future, which helps ensure that
// there are enough samples even if the audio thread 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 > static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) {
buffered_byte_count -= static_cast<uint32_t>(buffer_offset_frames * sizeof(int16_t) * samples_per_vi);
}
else {
buffered_byte_count = 0;
}
return buffered_byte_count;
} }

View File

@ -9,7 +9,6 @@
#include <queue> #include <queue>
#include <Windows.h> #include <Windows.h>
#include "SDL.h"
#include "blockingconcurrentqueue.h" #include "blockingconcurrentqueue.h"
#include "ultra64.h" #include "ultra64.h"
@ -154,57 +153,10 @@ void dp_complete() {
osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK); osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK);
} }
void RT64Init(uint8_t* rom, uint8_t* rdram); void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle);
void RT64SendDL(uint8_t* rdram, const OSTask* task); void RT64SendDL(uint8_t* rdram, const OSTask* task);
void RT64UpdateScreen(uint32_t vi_origin); void RT64UpdateScreen(uint32_t vi_origin);
void RT64ChangeWindow();
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 = 85 * (key_states[SDL_Scancode::SDL_SCANCODE_D] - key_states[SDL_Scancode::SDL_SCANCODE_A]);
stick_y = 85 * (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]; uint8_t dmem[0x1000];
uint16_t rspReciprocals[512]; uint16_t rspReciprocals[512];
@ -276,19 +228,10 @@ void task_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_rea
} }
} }
void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready) { void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_ready, void* window_handle) {
using namespace std::chrono_literals; using namespace std::chrono_literals;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { RT64Init(rom, rdram, window_handle);
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(); rsp_constants_init();
// Notify the caller thread that this thread is ready. // Notify the caller thread that this thread is ready.
@ -318,15 +261,6 @@ void gfx_thread_func(uint8_t* rdram, uint8_t* rom, std::atomic_flag* thread_read
RT64UpdateScreen(swap_action->origin); 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();
} }
} }
@ -483,11 +417,11 @@ void Multilibultra::send_si_message() {
osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK);
} }
void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom) { void Multilibultra::init_events(uint8_t* rdram, uint8_t* rom, void* window_handle) {
std::atomic_flag gfx_thread_ready; std::atomic_flag gfx_thread_ready;
std::atomic_flag task_thread_ready; std::atomic_flag task_thread_ready;
events_context.rdram = rdram; events_context.rdram = rdram;
events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, rom, &gfx_thread_ready }; events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, rom, &gfx_thread_ready, window_handle };
events_context.sp.task_thread = std::thread{ task_thread_func, rdram, rom, &task_thread_ready }; events_context.sp.task_thread = std::thread{ task_thread_func, rdram, rom, &task_thread_ready };
// Wait for the two sp threads to be ready before continuing to prevent the game from // Wait for the two sp threads to be ready before continuing to prevent the game from

View File

@ -21,10 +21,10 @@ constexpr int32_t cart_handle = 0x80800000;
constexpr int32_t flash_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); 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 constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash
void preinit(uint8_t* rdram, uint8_t* rom); void preinit(uint8_t* rdram, uint8_t* rom, void* window_handle);
void save_init(); void save_init();
void init_scheduler(); void init_scheduler();
void init_events(uint8_t* rdram, uint8_t* rom); void init_events(uint8_t* rdram, uint8_t* rom, void* window_handle);
void init_timers(RDRAM_ARG1); void init_timers(RDRAM_ARG1);
void set_self_paused(RDRAM_ARG1); void set_self_paused(RDRAM_ARG1);
void wait_for_resumed(RDRAM_ARG1); void wait_for_resumed(RDRAM_ARG1);
@ -46,11 +46,30 @@ void send_si_message();
uint32_t get_speed_multiplier(); uint32_t get_speed_multiplier();
std::chrono::system_clock::time_point get_start(); std::chrono::system_clock::time_point get_start();
std::chrono::system_clock::duration time_since_start(); std::chrono::system_clock::duration time_since_start();
// Audio
void init_audio(); void init_audio();
void set_audio_frequency(uint32_t freq); void set_audio_frequency(uint32_t freq);
void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count); void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count);
uint32_t get_remaining_audio_bytes(); uint32_t get_remaining_audio_bytes();
struct audio_callbacks_t {
using queue_samples_t = void(int16_t*, size_t);
using get_samples_remaining_t = size_t();
using set_frequency_t = void(uint32_t);
queue_samples_t* queue_samples;
get_samples_remaining_t* get_samples_remaining;
set_frequency_t* set_frequency;
};
void set_audio_callbacks(const audio_callbacks_t* callbacks);
// Input
struct input_callbacks_t {
using get_input_t = void(uint16_t*, float*, float*);
get_input_t* get_input;
};
void set_input_callbacks(const input_callbacks_t* callback);
class preemption_guard { class preemption_guard {
public: public:
preemption_guard(); preemption_guard();

View File

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

View File

@ -1,6 +1,14 @@
#include "../portultra/multilibultra.hpp" #include "../portultra/multilibultra.hpp"
#include "recomp.h" #include "recomp.h"
static Multilibultra::input_callbacks_t input_callbacks;
void Multilibultra::set_input_callbacks(const input_callbacks_t* callbacks) {
if (callbacks != nullptr) {
input_callbacks = *callbacks;
}
}
static int max_controllers = 0; static int max_controllers = 0;
extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) { extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) {
@ -37,28 +45,24 @@ struct OSContPad {
u8 errno_; 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) { extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) {
int32_t pad = (int32_t)ctx->r4; int32_t pad = (int32_t)ctx->r4;
uint16_t buttons = 0;
float x = 0.0f;
float y = 0.0f;
if (input_callbacks.get_input) {
input_callbacks.get_input(&buttons, &x, &y);
}
if (max_controllers > 0) { if (max_controllers > 0) {
// button // button
MEM_H(0, pad) = button; MEM_H(0, pad) = buttons;
// stick_x // stick_x
MEM_B(2, pad) = stick_x; MEM_B(2, pad) = (int8_t)(127 * x);
// stick_y // stick_y
MEM_B(3, pad) = stick_y; MEM_B(3, pad) = (int8_t)(127 * y);
// errno // errno
MEM_B(4, pad) = 0; MEM_B(4, pad) = 0;
} }

View File

@ -69,30 +69,10 @@ extern "C" void unload_overlays(int32_t ram_addr, uint32_t size);
#include <Windows.h> #include <Windows.h>
#endif #endif
int main(int argc, char **argv) { std::unique_ptr<uint8_t[]> rdram_buffer;
//if (argc != 2) { recomp_context context{};
// 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
__declspec(dllexport) extern "C" void init() {
{ {
std::basic_ifstream<uint8_t> rom_file{ get_rom_name(), std::ios::binary }; std::basic_ifstream<uint8_t> rom_file{ get_rom_name(), std::ios::binary };
@ -128,9 +108,8 @@ int main(int argc, char **argv) {
load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024);
// Allocate rdram_buffer (16MB to give room for any extra addressable data used by recomp) // 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); rdram_buffer = std::make_unique<uint8_t[]>(16 * 1024 * 1024);
std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024); std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024);
recomp_context context{};
// Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000)
do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000); do_rom_read(rdram_buffer.get(), entrypoint, 0x10001000, 0x100000);
@ -152,14 +131,46 @@ int main(int argc, char **argv) {
MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
MEM_W(osResetType, 0) = 0; // cold reset MEM_W(osResetType, 0) = 0; // cold reset
MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
}
debug_printf("[Recomp] Starting\n"); __declspec(dllexport) extern "C" void start(void* window_handle, const Multilibultra::audio_callbacks_t* audio_callbacks, const Multilibultra::input_callbacks_t* input_callbacks) {
Multilibultra::set_audio_callbacks(audio_callbacks);
Multilibultra::set_input_callbacks(input_callbacks);
std::thread game_thread{[](void* window_handle) {
debug_printf("[Recomp] Starting\n");
Multilibultra::preinit(rdram_buffer.get(), rom.get()); Multilibultra::preinit(rdram_buffer.get(), rom.get(), window_handle);
recomp_entrypoint(rdram_buffer.get(), &context); recomp_entrypoint(rdram_buffer.get(), &context);
debug_printf("[Recomp] Quitting\n");
}, window_handle};
debug_printf("[Recomp] Quitting\n"); game_thread.detach();
}
int main(int argc, char **argv) {
#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
init();
start(nullptr, nullptr, nullptr);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -3,7 +3,6 @@
#include "../portultra/multilibultra.hpp" #include "../portultra/multilibultra.hpp"
#include "rt64_layer.h" #include "rt64_layer.h"
#include "SDL.h"
static uint8_t DMEM[0x1000]; static uint8_t DMEM[0x1000];
static uint8_t IMEM[0x1000]; static uint8_t IMEM[0x1000];
@ -44,7 +43,7 @@ void dummy_check_interrupts() {
} }
void RT64Init(uint8_t* rom, uint8_t* rdram) { void RT64Init(uint8_t* rom, uint8_t* rdram, void* window_handle) {
// Dynamic loading // Dynamic loading
//auto RT64 = LoadLibrary("RT64.dll"); //auto RT64 = LoadLibrary("RT64.dll");
//if (RT64 == 0) { //if (RT64 == 0) {
@ -57,6 +56,9 @@ void RT64Init(uint8_t* rom, uint8_t* rdram) {
//GET_FUNC(RT64, UpdateScreen); //GET_FUNC(RT64, UpdateScreen);
GFX_INFO gfx_info{}; GFX_INFO gfx_info{};
gfx_info.hWnd = window_handle;
gfx_info.hStatusBar = nullptr;
gfx_info.HEADER = rom; gfx_info.HEADER = rom;
gfx_info.RDRAM = rdram; gfx_info.RDRAM = rdram;
gfx_info.DMEM = DMEM; gfx_info.DMEM = DMEM;
@ -89,9 +91,6 @@ void RT64Init(uint8_t* rom, uint8_t* rdram) {
gfx_info.VI_Y_SCALE_REG = &VI_Y_SCALE_REG; gfx_info.VI_Y_SCALE_REG = &VI_Y_SCALE_REG;
gfx_info.CheckInterrupts = dummy_check_interrupts; 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); InitiateGFX(gfx_info);
} }
@ -112,3 +111,7 @@ void RT64UpdateScreen(uint32_t vi_origin) {
UpdateScreen(); UpdateScreen();
} }
void RT64ChangeWindow() {
ChangeWindow();
}

84
us.rev1.toml Normal file
View File

@ -0,0 +1,84 @@
# Config file for Majora's Mask NTSC 1.0 Recompilation.
[input]
entrypoint = 0x80080000
# Paths are relative to the location of this config file.
elf_path = "mm.us.rev1.elf"
output_func_path = "RecompiledFuncs"
relocatable_sections_path = "overlays.us.rev1.txt"
[patches]
stubs = [
# Stub out unused functions that directly manipulate RCP status.
"func_80084940",
"func_80084968",
# Stub out an unnecessary function that accesses kseg1 addresses.
"func_800818F4"
]
# Hooks
# Function definition for the overlay loading function.
[[patches.func]]
name = "load_overlays"
args = ["u32", "u32", "u32"]
# Function hooks for overlay loading.
[[patches.hook]]
func = "Idle_InitCodeAndMemory"
calls = "load_overlays"
args = ["a2", "a1", "a3"]
after_vram = 0x800802A4
[[patches.hook]]
func = "Load2_LoadOverlay"
calls = "load_overlays"
# args = [
# "a0", # $a0 contains rom start
# {operation = "load", type = "u32", base = "sp", offset = 0x10}, # sp + 10 contains the ram address
# {operation = "subtract", arguments = ["a1", "a0"]} # Calculate size from rom end - rom start
# ]
args = ["a1", "a0", "a2"]
# This vram address is an instruction in a delay slot. In that case, the recompiler will emit the
# hook call after this instruction is run and before the function is called.
after_vram = 0x80085048
# Single-instruction patches
# Write audio dlists to kseg0 (cached, 0x80...) addresses instead of kseg1 (uncached, 0xA0...) ones.
# This saves the recompiler from needing to handle checking for kseg1 addresses on every memory operation.
[[patches.instruction]]
func = "AudioHeap_WritebackDCache"
vram = 0x8018B510
value = 0x3C010000 # lui $at, 0x2000 -> lui $at, 0x0000
# These two may not be needed with RCP timeout detection disabled
# # Pretend the RSP has already halted so that the game doesn't attempt to directly manipulate RSP registers.
# [[patches.instruction]]
# func = "Sched_HandleAudioCancel"
# vram = 0x801763D8
# value = 0x240F0001 # lw $t7, 0x0($s1) -> li $t7, 1
# # Ditto.
# [[patches.instruction]]
# func = "Sched_HandleGfxCancel"
# vram = 0x80176534
# value = 0x240F0001 # lw $t7, 0x0($s1) -> li $t7, 1
# Disable RCP timeout detection (sometimes throws false positives, not needed)
[[patches.instruction]]
func = "AudioMgr_HandleRetrace"
vram = 0x80172DBC
value = 0x00000000 # jal osSetTimer -> nop
# Ditto.
[[patches.instruction]]
func = "Graph_TaskSet00"
vram = 0x80174218
value = 0x00000000 # jal osSetTimer -> nop
# Prevent the minimap from drawing at a point where it can cause a crash when pausing.
[[patches.instruction]]
func = "func_80106644"
vram = 0x80106684
value = 0x29E10003 # slti $at, $t7, 0x4 -> slti $at, $t7, 0x3