diff --git a/src/game/emutest.c b/src/game/emutest.c index 6b94f9e23..87005f36f 100644 --- a/src/game/emutest.c +++ b/src/game/emutest.c @@ -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; } diff --git a/src/game/emutest.h b/src/game/emutest.h index 5776b1221..fca7610a3 100644 --- a/src/game/emutest.h +++ b/src/game/emutest.h @@ -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) diff --git a/src/game/game_init.c b/src/game/game_init.c index 2a8de60f8..443eb9c5b 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -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.