Compare commits

...

2 commits

Author SHA1 Message Date
Mr-Wiseguy
c75f35be57 Initial tracy profiler support 2025-08-11 01:29:25 -04:00
Mr-Wiseguy
d34934aa7e Implement osSetTime and move update screen to before VI update 2025-08-11 01:28:41 -04:00
13 changed files with 309 additions and 13 deletions

View file

@ -5,6 +5,8 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)
set(LIBRECOMP_TRACY_PATH "" CACHE STRING "Include path for Tracy library, if used.")
# Define the library
add_library(librecomp STATIC
"${CMAKE_CURRENT_SOURCE_DIR}/src/ai.cpp"
@ -52,6 +54,11 @@ target_compile_options(librecomp PRIVATE
-Wno-unused-parameter
)
if(NOT LIBRECOMP_TRACY_PATH STREQUAL "")
target_compile_definitions(librecomp PRIVATE "TRACY_ENABLED")
target_include_directories(librecomp PRIVATE ${ULTRAMODERN_TRACY_PATH})
endif()
if (WIN32)
add_compile_definitions(NOMINMAX)
endif()

View file

@ -3,6 +3,7 @@
#include <string>
#include <ultramodern/ultra64.h>
#include <ultramodern/ultramodern.hpp>
#include <ultramodern/ultramodern_tracy.hpp>
#define VI_NTSC_CLOCK 48681812
@ -13,9 +14,13 @@ extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) {
//freq = VI_NTSC_CLOCK / dacRate;
ctx->r2 = freq;
ultramodern::set_audio_frequency(freq);
std::string tracy_message = "Set AI Freq: " + std::to_string(freq);
TracyMessage(tracy_message.c_str(), tracy_message.size() + 1);
}
extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) {
ZoneScoped;
ultramodern::queue_audio_buffer(rdram, ctx->r4, ctx->r5);
ctx->r2 = 0;
}

View file

@ -10,6 +10,7 @@
#include "librecomp/files.hpp"
#include <ultramodern/ultra64.h>
#include <ultramodern/ultramodern.hpp>
#include <ultramodern/ultramodern_tracy.hpp>
static std::vector<uint8_t> rom;
@ -310,6 +311,7 @@ void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_
}
extern "C" void osPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
ZoneScoped;
uint32_t mb = ctx->r4;
uint32_t pri = ctx->r5;
uint32_t direction = ctx->r6;
@ -327,6 +329,7 @@ extern "C" void osPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
}
extern "C" void osEPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
ZoneScoped;
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5);
uint32_t direction = ctx->r6;
@ -344,6 +347,7 @@ extern "C" void osEPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) {
}
extern "C" void osEPiReadIo_recomp(RDRAM_ARG recomp_context * ctx) {
ZoneScoped;
OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4);
uint32_t devAddr = handle->baseAddress | ctx->r5;
gpr dramAddr = ctx->r6;

View file

@ -1,6 +1,7 @@
#include <cstdio>
#include <fstream>
#include <ultramodern/ultramodern.hpp>
#include <ultramodern/ultramodern_tracy.hpp>
#include "recomp.h"
extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) {
@ -10,6 +11,7 @@ extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) {
bool dump_frame = false;
extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) {
ZoneScoped;
//printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
OSTask* task = TO_PTR(OSTask, ctx->r4);
if (task->t.type == M_GFXTASK) {

View file

@ -73,12 +73,21 @@ extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) {
ctx->r2 = osGetCount();
}
extern "C" void osSetCount_recomp(uint8_t * rdram, recomp_context * ctx) {
osSetCount(ctx->r4);
}
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 osSetTime_recomp(uint8_t * rdram, recomp_context * ctx) {
uint64_t t = ((uint64_t)(ctx->r4) << 32) | ((ctx->r5) & 0xFFFFFFFFu);
osSetTime(t);
}
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);

View file

@ -5,6 +5,8 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)
set(ULTRAMODERN_TRACY_PATH "" CACHE STRING "Include path for Tracy library, if used.")
add_library(ultramodern STATIC
"${CMAKE_CURRENT_SOURCE_DIR}/src/audio.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/error_handling.cpp"
@ -35,6 +37,11 @@ target_compile_options(ultramodern PRIVATE
-Wno-unused-parameter
)
if(NOT ULTRAMODERN_TRACY_PATH STREQUAL "")
target_compile_definitions(ultramodern PRIVATE "TRACY_ENABLED")
target_include_directories(ultramodern PRIVATE ${ULTRAMODERN_TRACY_PATH})
endif()
if (WIN32)
add_compile_definitions(NOMINMAX)
endif()

View file

@ -290,7 +290,9 @@ void osViSetYScale(float scale);
PTR(void) osViGetNextFramebuffer();
PTR(void) osViGetCurrentFramebuffer();
u32 osGetCount();
void osSetCount(u32 count);
OSTime osGetTime();
void osSetTime(OSTime t);
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);

View file

@ -0,0 +1,218 @@
#ifndef __ULTRAMODERN_TRACY_H__
#define __ULTRAMODERN_TRACY_H__
#ifdef TRACY_ENABLED
# define TRACY_ENABLE
# include <tracy/Tracy.hpp>
# include <tracy/TracyC.h>
namespace tracy {
void SetThreadName( const char* name );
void SetThreadNameWithHint( const char* name, int32_t groupHint );
const char* GetThreadName( uint32_t id );
const char* GetEnvVar( const char* name );
}
#define TracySetThreadName(name) tracy::SetThreadName(name)
#define TracySetThreadNameWithHint(name, groupHint) tracy::SetThreadNameWithHint(name, groupHint)
#else
// C++ API
# define TracyNoop
# define ZoneNamed(x,y)
# define ZoneNamedN(x,y,z)
# define ZoneNamedC(x,y,z)
# define ZoneNamedNC(x,y,z,w)
# define ZoneTransient(x,y)
# define ZoneTransientN(x,y,z)
# define ZoneScoped
# define ZoneScopedN(x)
# define ZoneScopedC(x)
# define ZoneScopedNC(x,y)
# define ZoneText(x,y)
# define ZoneTextV(x,y,z)
# define ZoneTextF(x,...)
# define ZoneTextVF(x,y,...)
# define ZoneName(x,y)
# define ZoneNameV(x,y,z)
# define ZoneNameF(x,...)
# define ZoneNameVF(x,y,...)
# define ZoneColor(x)
# define ZoneColorV(x,y)
# define ZoneValue(x)
# define ZoneValueV(x,y)
# define ZoneIsActive false
# define ZoneIsActiveV(x) false
# define FrameMark
# define FrameMarkNamed(x)
# define FrameMarkStart(x)
# define FrameMarkEnd(x)
# define FrameImage(x,y,z,w,a)
# define TracyLockable( type, varname ) type varname
# define TracyLockableN( type, varname, desc ) type varname
# define TracySharedLockable( type, varname ) type varname
# define TracySharedLockableN( type, varname, desc ) type varname
# define LockableBase( type ) type
# define SharedLockableBase( type ) type
# define LockMark(x) (void)x
# define LockableName(x,y,z)
# define TracyPlot(x,y)
# define TracyPlotConfig(x,y,z,w,a)
# define TracyMessage(x,y)
# define TracyMessageL(x)
# define TracyMessageC(x,y,z)
# define TracyMessageLC(x,y)
# define TracyAppInfo(x,y)
# define TracyAlloc(x,y)
# define TracyFree(x)
# define TracySecureAlloc(x,y)
# define TracySecureFree(x)
# define TracyAllocN(x,y,z)
# define TracyFreeN(x,y)
# define TracySecureAllocN(x,y,z)
# define TracySecureFreeN(x,y)
# define ZoneNamedS(x,y,z)
# define ZoneNamedNS(x,y,z,w)
# define ZoneNamedCS(x,y,z,w)
# define ZoneNamedNCS(x,y,z,w,a)
# define ZoneTransientS(x,y,z)
# define ZoneTransientNS(x,y,z,w)
# define ZoneScopedS(x)
# define ZoneScopedNS(x,y)
# define ZoneScopedCS(x,y)
# define ZoneScopedNCS(x,y,z)
# define TracyAllocS(x,y,z)
# define TracyFreeS(x,y)
# define TracySecureAllocS(x,y,z)
# define TracySecureFreeS(x,y)
# define TracyAllocNS(x,y,z,w)
# define TracyFreeNS(x,y,z)
# define TracySecureAllocNS(x,y,z,w)
# define TracySecureFreeNS(x,y,z)
# define TracyMessageS(x,y,z)
# define TracyMessageLS(x,y)
# define TracyMessageCS(x,y,z,w)
# define TracyMessageLCS(x,y,z)
# define TracySourceCallbackRegister(x,y)
# define TracyParameterRegister(x,y)
# define TracyParameterSetup(x,y,z,w)
# define TracyIsConnected false
# define TracyIsStarted false
# define TracySetProgramName(x)
# define TracyFiberEnter(x)
# define TracyFiberEnterHint(x,y)
# define TracyFiberLeave
// C API
typedef const void* TracyCZoneCtx;
typedef const void* TracyCLockCtx;
# define TracyCZone(c,x)
# define TracyCZoneN(c,x,y)
# define TracyCZoneC(c,x,y)
# define TracyCZoneNC(c,x,y,z)
# define TracyCZoneEnd(c)
# define TracyCZoneText(c,x,y)
# define TracyCZoneName(c,x,y)
# define TracyCZoneColor(c,x)
# define TracyCZoneValue(c,x)
# define TracyCAlloc(x,y)
# define TracyCFree(x)
# define TracyCMemoryDiscard(x)
# define TracyCSecureAlloc(x,y)
# define TracyCSecureFree(x)
# define TracyCSecureMemoryDiscard(x)
# define TracyCAllocN(x,y,z)
# define TracyCFreeN(x,y)
# define TracyCSecureAllocN(x,y,z)
# define TracyCSecureFreeN(x,y)
# define TracyCFrameMark
# define TracyCFrameMarkNamed(x)
# define TracyCFrameMarkStart(x)
# define TracyCFrameMarkEnd(x)
# define TracyCFrameImage(x,y,z,w,a)
# define TracyCPlot(x,y)
# define TracyCPlotF(x,y)
# define TracyCPlotI(x,y)
# define TracyCPlotConfig(x,y,z,w,a)
# define TracyCMessage(x,y)
# define TracyCMessageL(x)
# define TracyCMessageC(x,y,z)
# define TracyCMessageLC(x,y)
# define TracyCAppInfo(x,y)
# define TracyCZoneS(x,y,z)
# define TracyCZoneNS(x,y,z,w)
# define TracyCZoneCS(x,y,z,w)
# define TracyCZoneNCS(x,y,z,w,a)
# define TracyCAllocS(x,y,z)
# define TracyCFreeS(x,y)
# define TracyCMemoryDiscardS(x,y)
# define TracyCSecureAllocS(x,y,z)
# define TracyCSecureFreeS(x,y)
# define TracyCSecureMemoryDiscardS(x,y)
# define TracyCAllocNS(x,y,z,w)
# define TracyCFreeNS(x,y,z)
# define TracyCSecureAllocNS(x,y,z,w)
# define TracyCSecureFreeNS(x,y,z)
# define TracyCMessageS(x,y,z)
# define TracyCMessageLS(x,y)
# define TracyCMessageCS(x,y,z,w)
# define TracyCMessageLCS(x,y,z)
# define TracyCLockCtx(l)
# define TracyCLockAnnounce(l)
# define TracyCLockTerminate(l)
# define TracyCLockBeforeLock(l)
# define TracyCLockAfterLock(l)
# define TracyCLockAfterUnlock(l)
# define TracyCLockAfterTryLock(l,x)
# define TracyCLockMark(l)
# define TracyCLockCustomName(l,x,y)
# define TracyCIsConnected 0
# define TracyCIsStarted 0
# define TracyCFiberEnter(fiber)
# define TracyCFiberLeave
// Other tracy functions
#define TracySetThreadName(name)
#define TracySetThreadNameWithHint(name, groupHint)
#endif
#endif

View file

@ -13,6 +13,7 @@
#include "ultramodern/ultra64.h"
#include "ultramodern/ultramodern.hpp"
#include "ultramodern/ultramodern_tracy.hpp"
#include "ultramodern/rsp.hpp"
#include "ultramodern/renderer_context.hpp"
@ -28,6 +29,7 @@ struct SpTaskAction {
};
struct ScreenUpdateAction {
ultramodern::renderer::ViRegs regs;
};
struct UpdateConfigAction {
@ -55,6 +57,7 @@ static struct {
int field;
ViState states[2];
ultramodern::renderer::ViRegs regs;
ultramodern::renderer::ViRegs update_screen_regs;
ViState* get_next_state() {
return &states[cur_state ^ 1];
@ -131,7 +134,7 @@ static struct {
} events_context{};
ultramodern::renderer::ViRegs* ultramodern::renderer::get_vi_regs() {
return &events_context.vi.regs;
return &events_context.vi.update_screen_regs;
}
extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) {
@ -198,8 +201,9 @@ void vi_thread_func() {
next = std::chrono::high_resolution_clock::now();
}
ultramodern::sleep_until(next);
auto time_now = ultramodern::time_since_start();
// Calculate how many VIs have passed
uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1;
uint64_t new_total_vis = (time_now * (60 * ultramodern::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);
}
@ -212,12 +216,13 @@ void vi_thread_func() {
odd = !odd;
}
// Queue a screen update for the graphics thread with the current VI register state.
// Doing this before the VI update is equivalent to updating the screen after the previous frame's scanout finished.
events_context.action_queue.enqueue(ScreenUpdateAction{ events_context.vi.regs });
// Update VI registers and swap VI modes.
events_context.vi.update_vi();
// Queue a screen update for the graphics thread.
events_context.action_queue.enqueue(ScreenUpdateAction{ });
// If the game has started, handle sending VI and AI events.
if (ultramodern::is_game_started()) {
remaining_retraces--;
@ -226,15 +231,16 @@ void vi_thread_func() {
std::lock_guard lock{ events_context.message_mutex };
ViState* cur_state = events_context.vi.get_cur_state();
if (remaining_retraces == 0) {
remaining_retraces = cur_state->retrace_count;
if (cur_state->mq != NULLPTR) {
TracyMessageL("VI Event");
if (osSendMesg(PASS_RDRAM cur_state->mq, cur_state->msg, OS_MESG_NOBLOCK) == -1) {
//printf("Game skipped a VI frame!\n");
}
}
remaining_retraces = cur_state->retrace_count;
}
if (events_context.ai.mq != NULLPTR) {
TracyMessageL("AI Event");
if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) {
//printf("Game skipped a AI frame!\n");
}
@ -364,13 +370,16 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re
ultramodern::measure_input_latency();
[[maybe_unused]] auto renderer_start = std::chrono::high_resolution_clock::now();
renderer_context->send_dl(&task_action->task);
{
ZoneScopedN("Displaylist");
renderer_context->send_dl(&task_action->task);
}
[[maybe_unused]] auto renderer_end = std::chrono::high_resolution_clock::now();
dp_complete();
// printf("Renderer ProcessDList time: %d us\n", static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(renderer_end - renderer_start).count()));
}
else if (const auto* screen_update_action = std::get_if<ScreenUpdateAction>(&action)) {
(void)screen_update_action;
events_context.vi.update_screen_regs = screen_update_action->regs;
renderer_context->update_screen();
display_refresh_rate = renderer_context->get_display_framerate();
resolution_scale = renderer_context->get_resolution_scale();
@ -441,6 +450,7 @@ void set_dummy_vi(bool odd) {
}
extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) {
ZoneScoped;
std::lock_guard lock{ events_context.message_mutex };
events_context.vi.get_next_state()->framebuffer = frameBufPtr;
}

View file

@ -3,6 +3,7 @@
#include "ultramodern/input.hpp"
#include "ultramodern/ultra64.h"
#include "ultramodern/ultramodern.hpp"
#include "ultramodern/ultramodern_tracy.hpp"
static ultramodern::input::callbacks_t input_callbacks {};
@ -104,6 +105,7 @@ extern "C" s32 osContStartQuery(RDRAM_ARG PTR(OSMesgQueue) mq) {
}
extern "C" s32 osContStartReadData(RDRAM_ARG PTR(OSMesgQueue) mq) {
ZoneScoped;
if (input_callbacks.poll_input != nullptr) {
input_callbacks.poll_input();
}
@ -128,6 +130,7 @@ extern "C" void osContGetQuery(RDRAM_ARG PTR(OSContStatus) data_) {
}
extern "C" void osContGetReadData(OSContPad *data) {
ZoneScoped;
for (int controller = 0; controller < max_controllers; controller++) {
uint16_t buttons = 0;
float x = 0.0f;
@ -168,6 +171,7 @@ s32 osMotorStart(RDRAM_ARG PTR(OSPfs) pfs) {
}
s32 __osMotorAccess(RDRAM_ARG PTR(OSPfs) pfs_, s32 flag) {
ZoneScoped;
OSPfs *pfs = TO_PTR(OSPfs, pfs_);
if (input_callbacks.set_rumble != nullptr) {

View file

@ -2,6 +2,7 @@
#include <cstring>
#include "ultramodern/rsp.hpp"
#include "ultramodern/ultramodern_tracy.hpp"
static ultramodern::rsp::callbacks_t rsp_callbacks {};
@ -16,6 +17,7 @@ void ultramodern::rsp::init() {
}
bool ultramodern::rsp::run_task(RDRAM_ARG const OSTask* task) {
ZoneScoped;
assert(rsp_callbacks.run_task != nullptr);
return rsp_callbacks.run_task(PASS_RDRAM task);

View file

@ -5,6 +5,7 @@
#include "ultramodern/ultra64.h"
#include "ultramodern/ultramodern.hpp"
#include "ultramodern/ultramodern_tracy.hpp"
#include "blockingconcurrentqueue.h"
#include "ultramodern/threads.hpp"
@ -146,8 +147,14 @@ void ultramodern::set_native_thread_name(const std::string& name) {
void ultramodern::set_native_thread_priority(ThreadPriority pri) {}
#endif
void wait_for_resumed(RDRAM_ARG UltraThreadContext* thread_context) {
void wait_for_resumed(RDRAM_ARG UltraThreadContext* thread_context, bool first_start = false) {
if (!first_start) {
// TracyMessageL("Pause");
}
thread_context->running.wait();
if (!first_start) {
// TracyMessageL("Resume");
}
// If this thread's context was replaced by another thread or deleted, destroy it again from its own context.
// This will trigger thread cleanup instead.
if (TO_PTR(OSThread, ultramodern::this_thread())->context != thread_context) {
@ -189,9 +196,15 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry
is_game_thread = true;
// Set the thread name
ultramodern::set_native_thread_name(ultramodern::threads::get_game_thread_name(self));
std::string thread_name = ultramodern::threads::get_game_thread_name(self);
// Copy the thread name into the fixed address buffer (for profiling).
ultramodern::set_native_thread_name(thread_name);
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::High);
// Set the thread name in tracy and the thread group to 0x1064 to indicate a game thread.
TracySetThreadNameWithHint(thread_name.c_str(), 0x1064);
// Signal the initialized semaphore to indicate that this thread can be started.
thread_context->initialized.signal();
@ -199,10 +212,13 @@ static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entry
// Wait until the thread is marked as running.
try {
wait_for_resumed(PASS_RDRAM thread_context);
ZoneScopedN("Wait for Start");
wait_for_resumed(PASS_RDRAM thread_context, true);
} catch (ultramodern::thread_terminated& terminated) {
}
TracyMessageL("Start");
// Make sure the thread wasn't replaced or destroyed before it was started.
if (self->context == thread_context) {
debug_printf("[Thread] Thread started: %d\n", self->id);

View file

@ -13,6 +13,8 @@
// Start time for the program
static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now();
// Offset of the duration since program start used to calculate the value for osGetTime.
static int64_t ostime_offset = 0;
// Game speed multiplier (1 means no speedup)
constexpr uint32_t speed_multiplier = 1;
// N64 CPU counter ticks per millisecond
@ -162,12 +164,20 @@ extern "C" u32 osGetCount() {
return (uint32_t)total_count;
}
extern "C" void osSetCount(u32 count) {
assert(false);
}
extern "C" OSTime osGetTime() {
uint64_t total_count = time_now();
uint64_t total_count = time_now() - ostime_offset;
return total_count;
}
extern "C" void osSetTime(OSTime t) {
ostime_offset = time_now() - t;
}
extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) {
OSTimer* t = TO_PTR(OSTimer, t_);