Add system capabilities variable that tracks individual features instead of broad emulator support (#907)

Also implements check for FBE.

* start implementing selftest

* add some more

* gSupportsLibpl destroyed

* SYS_SUPPORTS -> SYSCAP

* add emux check

* make it compile

* play around with it until it writes the ASM i want

* format

* explain what im doing

* hopefully guard emux

* one feedback

* two feedback

* better libpl check

* better comment

* resolve some feedbackg

* add FBE check

* dont need to skip this check if its only for the first frame'

* skip framebuffer check if on a system that we know supports it.

* document that nothing in the repo has emux yet

* goodbye emux

* feed1

* various detectable

---------

Co-authored-by: someone2639 <someone2639@gmail.com>
This commit is contained in:
someone2639
2026-01-17 01:52:48 -05:00
committed by GitHub
parent 85b72619e4
commit 93a0816b6c
3 changed files with 104 additions and 11 deletions

View File

@@ -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,
@@ -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;
}
@@ -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;
}

View File

@@ -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.
*
@@ -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)

View File

@@ -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.
*/
@@ -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.