Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions src/game/emutest.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extern void __osPiGetAccess(void);
extern void __osPiRelAccess(void);

u8 gEmulator = EMU_CONSOLE;
u8 gSupportsLibpl = FALSE;
u32 gSystemCapabilities = 0;

static inline u32 get_pj64_version() {
// When calling this function, we know that the emulator is some version of Project 64,
Expand Down Expand Up @@ -74,21 +74,37 @@ static u8 check_cache_emulation() {
return cacheEmulated;
}

// Tests various system quirks and initializes gEmulator to the detected emulator(s).
// Also initializes gSystemCapabilities.
u32 detect_emulator() {
// Test to see if the libpl emulator extension is present.
u32 magic;
// Test to see if the libpl emulator extension is present.
#ifdef LIBPL
// We have libpl downloaded as a submodule, just use the API call.
if (libpl_is_supported(LPL_ABI_VERSION_CURRENT)) {
const lpl_plugin_info *plugin_info = libpl_get_graphics_plugin();

// We can query framebuffer emulation from libpl
if (plugin_info->capabilities & LPL_FRAMEBUFFER_EMULATION) {
gSystemCapabilities |= SUPPORTS_SOFTWARE_FRAMEBUFFER;
}
#else // LIBPL
// libpl interacts with the hardware register at 0x1FFB0000,
// so we can still _detect_ it by clearing the register and
// seeing if we get a specific value back.
osPiWriteIo(0x1ffb0000u, 0u);
osPiReadIo(0x1ffb0000u, &magic);
if (magic == 0x00500000u) {
// libpl is supported. Must be ParallelN64
#ifdef LIBPL
gSupportsLibpl = libpl_is_supported(LPL_ABI_VERSION_CURRENT);
#endif
#endif // LIBPL
gSystemCapabilities |= SUPPORTS_LIBPL;
// If libpl is supported, we're on Parallel Launcher
return EMU_PARALLEL_LAUNCHER;
}

// If DPC registers are emulated, this is either console or a very accurate emulator
if ((u32)IO_READ(DPC_PIPEBUSY_REG) | (u32)IO_READ(DPC_TMEM_REG) | (u32)IO_READ(DPC_BUFBUSY_REG)) {
// Assume we have the ability to manipulate the framebuffer too.
gSystemCapabilities |= SUPPORTS_SOFTWARE_FRAMEBUFFER;
return EMU_CONSOLE;
}

Expand All @@ -106,11 +122,14 @@ u32 detect_emulator() {
if (1.0f != round_double_to_float(0.9999999999999999)) {
fcr_set_rounding_mode(roundingMode);
return EMU_WIIVC;
} else {
gSystemCapabilities |= SUPPORTS_FLOAT_ROUNDING_MODE;
}
fcr_set_rounding_mode(roundingMode);

// If cache is emulated, then this is likely Simple64, or some other accurate emulator.
if (check_cache_emulation()) {
gSystemCapabilities |= SUPPORTS_CACHING;
return EMU_OTHER;
}

Expand Down
41 changes: 37 additions & 4 deletions src/game/emutest.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,34 @@ enum Emulator {
EMU_OTHER = (1 << 6), // Any other emulator
};

// initializes gEmulator
/**
* Various detectable system capabilities
*/
enum SystemCapabilities {
// Whether the system caches instructions and data
SUPPORTS_CACHING = (1 << 0),

// Whether the system supports changing how floats get rounded
SUPPORTS_FLOAT_ROUNDING_MODE = (1 << 1),

// Whether the system supports the `libpl` API on Parallel Launcher
SUPPORTS_LIBPL = (1 << 2),

// Whether the system can edit the framebuffer in software
SUPPORTS_SOFTWARE_FRAMEBUFFER = (1 << 3),

// TODO: `emux` is a developing standard.
// SUPPORTS_EMULATOR_EXTENSIONS = (1 << 4), // i.e. `emux`

// TODO: Figure out what these mean and implement them too
// SUPPORTS_DMA_TIMING = 0,
// SUPPORTS_RSP_PIPELINE_STALL_TIMING = 0,
};

extern u32 detect_emulator();

/* gEmulator is an enum that identifies the current emulator.
/**
* gEmulator is an enum that identifies the current emulator.
* The enum values work as a bitfield, so you can use the & and | operators
* to test for multiple emulators or versions at once.
*
Expand All @@ -34,8 +58,17 @@ extern u32 detect_emulator();
*/
extern u8 gEmulator;

// determines whether libpl is safe to use
extern u8 gSupportsLibpl;
/**
* Bitflag that lists all system capabilities, for more granular
* feature detection than gEmulator.
*/
extern u32 gSystemCapabilities;

/**
* Whether the emulator supports libpl.
* Left in for backwards compatibility.
*/
#define gSupportsLibpl (gSystemCapabilities & SUPPORTS_LIBPL)

// Included for backwards compatibility when upgrading from HackerSM64 2.0
#define gIsConsole ((gEmulator & EMU_CONSOLE) != 0)
Expand Down
43 changes: 42 additions & 1 deletion src/game/game_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,31 @@ void draw_reset_bars(void) {
osRecvMesg(&gGameVblankQueue, &gMainReceivedMesg, OS_MESG_BLOCK);
}

/**
* Check if we are emulating the framebuffer
*
* s32 frameIndex:
* 0: Write to the framebuffer, wait to process displaylist
* 1: Check whether the framebuffer write persisted
*/
static void check_fbe(s32 frameIndex) {
// NOTE: For whatever reason, checking against pixel index 12 fails on some versions of GlideN64 (pain).
// So apparently, this value being set to 13 actually matters...???
const s32 fbePixelOffset = 13;
const u16 fbePixelVal = 0xFF01;

if (frameIndex == 0) {
// Write pixel to the framebuffer
gFramebuffers[sRenderingFramebuffer][fbePixelOffset] = fbePixelVal;
} else {
// Check if pixel persisted in the framebuffer after executing the display list
// that clears it (but before updating sRenderingFramebuffer!)
if (gFramebuffers[sRenderingFramebuffer][fbePixelOffset] != fbePixelVal) {
gSystemCapabilities |= SUPPORTS_SOFTWARE_FRAMEBUFFER;
}
}
}

/**
* Initial settings for the first rendered frame.
*/
Expand All @@ -423,7 +448,23 @@ void render_init(void) {
init_rcp(CLEAR_ZBUFFER);
clear_framebuffer(0);
end_master_display_list();
exec_display_list(&gGfxPool->spTask);

// Skip the FBE check if system is console,
// or had already been determined to support framebuffer emulation.
if (gSystemCapabilities & SUPPORTS_SOFTWARE_FRAMEBUFFER) {
exec_display_list(&gGfxPool->spTask);
} else {
check_fbe(0);

exec_display_list(&gGfxPool->spTask);

// Wait for frame rendering to complete to prevent race condition with FBE check
osRecvMesg(&gGfxVblankQueue, &gMainReceivedMesg, OS_MESG_BLOCK);
check_fbe(1);

// Send message back to queue to prevent locking up
osSendMesg(&gGfxVblankQueue, gMainReceivedMesg, OS_MESG_BLOCK);
}

// Skip incrementing the initial framebuffer index on certain emulators so that they display immediately as the Gfx task finishes
// This will break accurate emulators, so only enable on Project64, Parallel Launcher and Mupen.
Expand Down