Skip to content
Merged
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
29 changes: 28 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ set(SNESRECOMP_BUILD_VERSION "${PROJECT_VERSION}" CACHE STRING "Release stamp fo
set(RECOMP_UI_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/recomp-ui" CACHE PATH "recomp-ui checkout")
option(MMX_RASTER_FULL_COPY "Use full-copy raster storage as the correctness/performance reference" OFF)
option(MMX_STAGE_TILE_REUSE "Reuse exact stage-map lookups within one captured draw" ON)
set(_MMX_NATIVE_ROW_PLAN_DEFAULT OFF)
if(_MMX_RASTER_IMPL_DEFAULT STREQUAL "NATIVE" AND MMX_RASTER_IMPL STREQUAL "NATIVE")
set(_MMX_NATIVE_ROW_PLAN_DEFAULT ON)
endif()
option(MMX_NATIVE_ROW_PLAN "Prepare whole-view native row composition plans" ${_MMX_NATIVE_ROW_PLAN_DEFAULT})
set_property(SOURCE src/mmx_renderer_native.c tests/mmx_native_renderer_test.c
src/main.c tests/mmx_adaptive_state_test.c
APPEND PROPERTY COMPILE_DEFINITIONS
MMX_NATIVE_ROW_PLAN=$<BOOL:${MMX_NATIVE_ROW_PLAN}>)
set_property(SOURCE src/mmx_renderer.c APPEND PROPERTY COMPILE_DEFINITIONS
MMX_RASTER_FULL_COPY=$<BOOL:${MMX_RASTER_FULL_COPY}>
MMX_STAGE_TILE_REUSE=$<BOOL:${MMX_STAGE_TILE_REUSE}>)
Expand All @@ -53,7 +62,7 @@ set(SNESRECOMP_ENABLE_MODS ON CACHE BOOL "Shared mod package runtime" FORCE)
# keeps the unmodified Japanese edition and small unit tests independent.
set(SNESRECOMP_NET_ICE ON CACHE BOOL "Internet netplay transport" FORCE)
include(${SNESRECOMP_ROOT}/runner/runner.cmake)
string(APPEND MMX_REPLAY_ENGINE "-apu${SNESRECOMP_APU_TIME_IMPL}-crc${SNESRECOMP_CRC32_IMPL}-raster${MMX_RASTER_IMPL}")
string(APPEND MMX_REPLAY_ENGINE "-apu${SNESRECOMP_APU_TIME_IMPL}-crc${SNESRECOMP_CRC32_IMPL}-raster${MMX_RASTER_IMPL}-row${MMX_NATIVE_ROW_PLAN}")
set_property(SOURCE src/mmx_replay.c APPEND PROPERTY COMPILE_DEFINITIONS
MMX_REPLAY_REVISION="${MMX_REPLAY_REVISION}" MMX_REPLAY_ENGINE="${MMX_REPLAY_ENGINE}")
include(${RECOMP_UI_ROOT}/recomp_ui.cmake)
Expand Down Expand Up @@ -248,6 +257,24 @@ if(BUILD_TESTING)
add_test(NAME mmx_custom_renderer_native COMMAND mmx_renderer_native_test)
set_tests_properties(mmx_custom_renderer_native PROPERTIES
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/renderer-native")
set(mmx_row_plan_test_sources ${mmx_renderer_native_test_sources})
list(REMOVE_ITEM mmx_row_plan_test_sources tests/mmx_native_renderer_test.c)
add_executable(mmx_row_plan_test ${mmx_row_plan_test_sources}
tests/mmx_row_plan_test.c src/mmx_boss_rush_score.cpp)
target_include_directories(mmx_row_plan_test PRIVATE src ${SNESRECOMP_ROOT}/runner/src)
target_compile_features(mmx_row_plan_test PRIVATE cxx_std_17)
if(MSVC)
target_compile_options(mmx_row_plan_test PRIVATE /UNDEBUG)
else()
target_compile_options(mmx_row_plan_test PRIVATE -UNDEBUG)
endif()
if(NOT WIN32)
target_link_libraries(mmx_row_plan_test PRIVATE m)
endif()
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/renderer-row-plan")
add_test(NAME mmx_custom_renderer_row_plan COMMAND mmx_row_plan_test)
set_tests_properties(mmx_custom_renderer_row_plan PROPERTIES
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/renderer-row-plan")
foreach(reference FULLCOPY TRACE)
string(TOLOWER "${reference}" reference_name)
add_executable(mmx_renderer_${reference_name}_test
Expand Down
43 changes: 43 additions & 0 deletions docs/ROW_RENDER_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Whole-view row rendering experiment

`MMX_NATIVE_ROW_PLAN=ON` selects a prepared-row pipeline in the NATIVE custom
renderer. It defaults ON for the qualified Windows x64 NATIVE configuration;
other platforms retain their existing defaults. Explicit OFF selects the
existing NATIVE compositor, and the separate LLE renderer remains buildable.

Each captured line/view prepares its background pixel and private-color planes
before composition. The shared `snes/raster_plan.h` job resolves widened hardware
windows, optional signed color-beam windows, enabled layers and native/margin
boundaries into at most eleven immutable spans. Composition selects main/sub
pixels with span masks rather than repeating window/enable decisions per pixel.

Scene-map reconstruction, native tile rows, palette/CHR ownership, sprite order,
color math, captured raster changes and guest execution remain authoritative.
This first experiment does not assume that reconstructed tiles or asset phases
are linear across every stage boundary. Map-binding spans are a separate possible
follow-up; this package does not implement them or GPU rendering.

The planner has no platform dependency, live PPU mutation, heap allocation or
persistent simulation state. The adapter uses bounded private row scratch;
rewind/rollback formats gain no host pointers or new serialized fields. The build
choice is recorded in the replay engine identity and benchmark JSON. No runtime
switch or cross-build state conversion is provided.

Validation uses the existing renderer scenes, 512 randomized full-color row
comparisons with scalar decisions, 4096 independent span/window comparisons,
ROM-backed Magic Carpet including its negative control, current-build restore,
rewind and fresh-process replay, and matched built-game A/B workloads. Per-game
CPU reductions and neutral results require recorded balanced samples; compilation
and component tests alone are not performance qualification.

Owning issue: `beads-8wg.2.161`; coordinating campaign: `beads-2wy8.1`. Bounded
raw receipts and source/binary/configuration identities are retained under
`G:/Temp/Codex/snesrecomp/full-view-20261027`. No original Xbox runtime or
cross-build netplay compatibility is claimed.

Five balanced A/B pairs per view on an Intel i5-4670 measured median process-CPU
reductions of 7.4% (custom 4:3), 9.6% (16:9) and 12.6% (32:9). The ordinary
stock-renderer workload was neutral/inconclusive. These are additional results
against the preceding optimized MMX build, not additive percentages or original
Xbox qualification. Raw samples, exact binary identities and settings are in
`campaign-results.json` beneath the evidence directory above.
6 changes: 5 additions & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
#ifndef MMX_DESKTOP_ENTRY
#define MMX_DESKTOP_ENTRY main
#endif
#ifndef MMX_NATIVE_ROW_PLAN
#define MMX_NATIVE_ROW_PLAN 0
#endif
extern const RtlGameInfo kMmxGameInfo;
extern int mmx_tier2_diagnostics_enabled(void);

Expand Down Expand Up @@ -249,10 +252,11 @@ static void MmxAfterFrame(const SnesDesktopHostFrameStats *stats) {
double seconds = stats->run_seconds;
printf("SNESRECOMP_BENCHMARK {\"game\":\"Mega Man X\",\"frames\":%u,"
"\"seconds\":%.9f,\"fps\":%.3f,\"ms_per_frame\":%.6f,\"native_raster_lines\":%llu,"
"\"apu_time_impl\":\"%s\",\"raster_impl\":\"%s\",\"audio_output\":%s}\n",
"\"apu_time_impl\":\"%s\",\"raster_impl\":\"%s\",\"row_plan\":%s,\"audio_output\":%s}\n",
stats->frame, seconds, stats->frame / seconds,
seconds * 1000.0 / stats->frame,(unsigned long long)s_benchmark_native_lines,
apu_time_implementation(), MMX_NATIVE_RASTER ? "NATIVE" : "LLE",
MMX_NATIVE_RASTER && MMX_NATIVE_ROW_PLAN ? "true" : "false",
s_benchmark_audio ? "true" : "false");
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/mmx_armor.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ bool MmxArmorValidState(const MmxArmorState *s) {
s->barrier_x>=-0x1000000 && s->barrier_x<=0x1000000 &&
s->barrier_y>=-0x1000000 && s->barrier_y<=0x1000000 &&
s->utility_ticks<=180 && s->radar_stage<=13 && s->radar_closing<=1 &&
s->giga_tick<=242 && s->hyper_energy<=0x1c00 && s->giga_fired<=1 && s->hyper<=1 &&
s->giga_tick<=242 && s->hyper_energy<=0x1c00 && s->giga_fired<=1 && s->hyper_active<=1 &&
!s->utility_reserved &&
s->anim_group<=1 && s->dash_recovery<=9 && !s->reserved_word &&
s->burst<=2 && s->burst_air<=1 && s->burst_fired<=1 && s->burst_transition<=1 &&
Expand Down Expand Up @@ -334,7 +334,7 @@ void MmxArmorPrePlayer(uint8_t *r) {
if(!state.valid || state.stage!=r[0x1f7a]) {
state.valid=1;state.stage=r[0x1f7a];state.barrier_ticks=state.barrier_level=state.barrier_delay=0;
state.dash_used=state.dash_ticks=state.dash_up=state.dash_jump=state.dash_recovery=0;
state.giga_tick=state.giga_fired=state.hyper=0;MmxArmorRefill();
state.giga_tick=state.giga_fired=state.hyper_active=0;MmxArmorRefill();
if(game!=3)state.tracer=0;state.recovery_ticks=0;
state.burst=state.burst_end=state.burst_queued=state.stored_shot=state.charge=state.fire_held=0;
memset(state.shots,0,sizeof(state.shots));state.shot_mask=0;
Expand Down
2 changes: 1 addition & 1 deletion src/mmx_armor.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ typedef struct MmxArmorState {
uint16_t utility_ticks,radar_epoch;
uint8_t radar_stage,radar_closing;
uint16_t giga_tick,hyper_energy;
uint8_t giga_fired,hyper;
uint8_t giga_fired,hyper_active;
uint16_t utility_reserved;
MmxArmorShot shots[8];
} MmxArmorState;
Expand Down
4 changes: 2 additions & 2 deletions src/mmx_armor_combat.inc
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,12 @@ static void buster_player(uint8_t *r) {
bool held=(r[0xbdf]&64)!=0,pressed=(r[0xbe3]&64)!=0;
unsigned live=0;for(unsigned i=0;i<8;++i)if(state.shots[i].active && MmxArmorProjectileOwned(r,0x1228+i*64))live|=1u<<i;
state.shot_mask=(uint16_t)live;
if(state.hyper && state.gold && game==3 && (r[0x1f99]&2) && pressed &&
if(state.hyper_active && state.gold && game==3 && (r[0x1f99]&2) && pressed &&
!state.burst && !state.stored_shot && config[10]) {
unsigned cost=word(config[10]);if((r[0x1f99]&5)==5)cost/=2;
if(cost && state.hyper_energy>=cost) {
state.hyper_energy=(uint16_t)(state.hyper_energy-cost);state.stored_shot=2;start_armor_burst(r,1);
} else state.hyper=0;
} else state.hyper_active=0;
}
if(state.burst==1 && state.burst_fired && pressed)state.burst_queued=1;
if(state.burst_end) {
Expand Down
2 changes: 1 addition & 1 deletion src/mmx_armor_utilities.inc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ bool MmxArmorFrameTick(uint8_t *r,uint16_t pad,bool available) {
bool pressed=combo && state.utility_held!=combo;state.utility_held=(uint8_t)combo;
if(!state.giga_tick && combo==2 && pressed && r[0xd3]==4 &&
!r[0x1f10] && !r[0x1f0c] && !r[0x1f23] && !r[0x1f48]) {
if(game==3) {state.hyper^=1;armor_sound(r,0x2c);}
if(game==3) {state.hyper_active^=1;armor_sound(r,0x2c);}
else if(state.valid && state.stage==r[0x1f7a] && config[9] && state.energy>=0x1c00 &&
!body[0x35] && (body[2]<=10 || body[2]==0x14)) {
state.energy=0;state.giga_tick=1;state.giga_fired=0;state.tracer=0;
Expand Down
14 changes: 14 additions & 0 deletions src/mmx_renderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2310,6 +2310,13 @@ static bool raster_scene_owned(void) {
zero_title_menu() || frame_rush.menu;
}

#ifndef MMX_NATIVE_ROW_PLAN
#define MMX_NATIVE_ROW_PLAN 0
#endif
#if MMX_NATIVE_RASTER && MMX_NATIVE_ROW_PLAN
#include "mmx_row_plan.inc"
#endif

bool MmxRendererDraw(uint32_t *out, MmxRenderView view, bool hud) {
if (!out || !frame.valid || view.width < 256 || view.width > MMX_RENDER_MAX_WIDTH ||
view.extra != (view.width - 256) / 2 || (view.width & 1)) return false;
Expand Down Expand Up @@ -2887,6 +2894,13 @@ bool MmxRendererDraw(uint32_t *out, MmxRenderView view, bool hud) {
native_layers |= 1u << layer;
}
}
#endif
#if MMX_NATIVE_RASTER && MMX_NATIVE_ROW_PLAN
native_render_row(&p, r, y, view, stage, menu, dialogue_backdrop,
crystal_ripple[y], palette_fade, native_background, native_layers,
objects, object_colors, native_rgb, brightness, native_plain_layers,
beams, beam_count, out + y * view.width);
continue;
#endif
for (int sx = 0; sx < view.width; ++sx) {
int x = sx - view.extra;
Expand Down
111 changes: 111 additions & 0 deletions src/mmx_row_plan.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* Included after the compositor's scalar helpers. All data is private to this
* captured row/view. This pipeline never reads or writes the live machine. */
#include "snes/raster_plan.h"
#include <assert.h>

static void native_background_range(const Ppu *p, const Raster *r,
unsigned layer, int y, bool stage, int extra, unsigned begin, unsigned end,
uint16_t *pixels, int *colors) {
int by = y;
unsigned mosaic = (p->mosaic & (1u << layer)) ? (p->mosaic >> 4) + 1 : 0;
if (mosaic) by -= by % (int)mosaic;
for (unsigned sx = begin; sx < end; ++sx) {
int x = (int)sx - extra;
if (mosaic) x -= ((x % (int)mosaic) + (int)mosaic) % (int)mosaic;
pixels[sx] = background(p, r, layer, x, by, stage, &colors[sx]);
}
}

static void native_compose_row(const Ppu *p, const Raster *r,
MmxRenderView view, const uint16_t backgrounds[3][MMX_RENDER_MAX_WIDTH],
const int colors[3][MMX_RENDER_MAX_WIDTH], const uint16_t *objects,
const int *object_colors, const uint32_t *rgb, const uint8_t brightness[32],
const unsigned plain_layers[2], const SnesRenderComposePlan *plan,
bool menu, bool dialogue_backdrop, uint32_t *out) {
(void)view;
for (unsigned s = 0; s < plan->count; ++s) {
const SnesRenderComposeSpan *span = &plan->spans[s];
if (menu && span->margin) continue; /* The output starts cleared. */
const unsigned plain = plain_layers[span->color_window];
for (unsigned sx = span->begin; sx < span->end; ++sx) {
uint16_t main = 0x500, sub = 0x500;
uint16_t b0 = backgrounds[0][sx], b1 = backgrounds[1][sx], b2 = backgrounds[2][sx];
uint16_t candidate = b0 & span->main[0];
if (candidate > main) main = candidate;
candidate = b1 & span->main[1];
if (candidate > main) main = candidate;
candidate = b2 & span->main[2];
if (candidate > main) main = candidate;
uint16_t camouflage_bg = main;
candidate = objects[sx] & span->main[3];
if (candidate > main) main = candidate;
candidate = b0 & span->sub[0];
if (candidate > sub) sub = candidate;
candidate = b1 & span->sub[1];
if (candidate > sub) sub = candidate;
candidate = b2 & span->sub[2];
if (candidate > sub) sub = candidate;
candidate = objects[sx] & span->sub[3];
if (candidate > sub) sub = candidate;
int bg_colors[3] = {colors[0][sx], colors[1][sx], colors[2][sx]};
unsigned layer = (main >> 8) & 15;
bool camouflage = object_colors[sx] >= 0 && (object_colors[sx] & 0x8000) &&
(layer == 4 || layer == 6);
if ((plain & (1u << layer)) && !camouflage)
out[sx] = native_plain_colour(r->palette, rgb, main, object_colors[sx], bg_colors);
else
out[sx] = colour(p, r->palette, brightness, main, sub, span->color_window,
object_colors[sx], bg_colors, dialogue_backdrop && span->margin, camouflage_bg);
}
}
}

static void native_render_row(const Ppu *p, const Raster *r, int y,
MmxRenderView view, bool stage, bool menu, bool dialogue_backdrop,
int ripple, unsigned palette_fade, const uint16_t center[3][256],
unsigned center_layers, const uint16_t *objects, int *object_colors,
const uint32_t *rgb, const uint8_t brightness[32], const unsigned plain_layers[2],
const LightBeam beams[2], unsigned beam_count, uint32_t *out) {
uint16_t backgrounds[3][MMX_RENDER_MAX_WIDTH];
int colors[3][MMX_RENDER_MAX_WIDTH];
for (unsigned layer = 0; layer < 3; ++layer) {
memset(colors[layer], 0xff, (size_t)view.width * sizeof(colors[layer][0]));
if (!((p->screenEnabled[0] | p->screenEnabled[1]) & (1u << layer))) {
memset(backgrounds[layer], 0, (size_t)view.width * sizeof(backgrounds[layer][0]));
continue;
}
int by = y + 1 + ((stage && !layer) ? ripple : 0);
if (center_layers & (1u << layer)) {
memcpy(backgrounds[layer] + view.extra, center[layer], 256 * sizeof(uint16_t));
native_background_range(p, r, layer, by, stage, view.extra, 0,
(unsigned)view.extra, backgrounds[layer], colors[layer]);
native_background_range(p, r, layer, by, stage, view.extra,
(unsigned)view.extra + 256, (unsigned)view.width, backgrounds[layer], colors[layer]);
} else {
native_background_range(p, r, layer, by, stage, view.extra, 0,
(unsigned)view.width, backgrounds[layer], colors[layer]);
}
if (palette_fade) for (int sx = 0; sx < view.width; ++sx)
if (colors[layer][sx] >= 0)
colors[layer][sx] = MmxRenderAssetsFadeColor((uint16_t)colors[layer][sx], palette_fade);
}
if (palette_fade) for (int sx = 0; sx < view.width; ++sx)
if (object_colors[sx] >= 0)
object_colors[sx] = MmxRenderAssetsFadeColor((uint16_t)object_colors[sx], palette_fade) |
(object_colors[sx] & 0x8000);
SnesRenderWindowPlan windows = SnesRenderWindows(p,
p->window1left == 0 ? -view.extra : p->window1left,
p->window1right == 255 ? 255 + view.extra : p->window1right,
p->window2left == 0 ? -view.extra : p->window2left,
p->window2right == 255 ? 255 + view.extra : p->window2right);
SnesRenderInterval color_windows[2];
for (unsigned i = 0; i < beam_count; ++i)
color_windows[i] = (SnesRenderInterval){beams[i].left[y], beams[i].right[y]};
SnesRenderComposePlan plan;
bool planned = SnesRenderPlanCompose(&plan, p, &windows, -view.extra,
(unsigned)view.width, color_windows, beam_count);
assert(planned); /* Validated renderer dimensions and at most two beams. */
if (!planned) return;
native_compose_row(p, r, view, backgrounds, colors, objects, object_colors,
rgb, brightness, plain_layers, &plan, menu, dialogue_backdrop, out);
}
2 changes: 1 addition & 1 deletion src/mods/mmx_armor_plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ static int armor_debug(const char *cmd,const char *args,DebugServerGameSendLine
uint8_t *body=MmxCoopEnabled()?MmxCoopCharacterBody(g_ram,MMX_COOP_X):g_ram+0xba8;
snprintf(out,sizeof(out),"{\"game\":%u,\"gold\":%u,\"charge\":%u,\"burst\":%u,\"stored\":%u,\"shots\":%u,\"tracer\":%u,\"radar_stage\":%u,\"pad\":%u,\"valid\":%u,\"giga\":%u,\"energy\":%u,\"hyper\":%u,\"hyper_energy\":%u,\"x\":%u,\"y\":%u,\"barrier\":%u,\"barrier_level\":%u,\"dash_used\":%u,\"dash_ticks\":%u,\"dash_up\":%u,\"recovery\":%u}",
MmxArmorGame(),s.gold,s.charge,s.burst,s.stored_shot,active,s.tracer,s.radar_stage,RtlGetPadState(0),MmxArmorValidState(&s),
s.giga_tick,s.energy,s.hyper,s.hyper_energy,body?(unsigned)(body[5]|body[6]<<8):0,body?(unsigned)(body[8]|body[9]<<8):0,
s.giga_tick,s.energy,s.hyper_active,s.hyper_energy,body?(unsigned)(body[5]|body[6]<<8):0,body?(unsigned)(body[8]|body[9]<<8):0,
s.barrier_ticks,s.barrier_level,s.dash_used,s.dash_ticks,s.dash_up,s.recovery_ticks);
send(out);return 1;
}
Expand Down
6 changes: 3 additions & 3 deletions tests/mmx_armor_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,15 @@ static void gold(void) {
assert(MmxArmorGetState().hyper_energy==0x1c00);
MmxArmorFrameTick(ram,0,true);MmxArmorFrameTick(ram,8,true);
MmxArmorFrameTick(ram,0,true);MmxArmorFrameTick(ram,0,true);
MmxArmorFrameTick(ram,0xc20,true);assert(MmxArmorGetState().hyper);
MmxArmorFrameTick(ram,0xc20,true);assert(MmxArmorGetState().hyper_active);
tick_buster(true,true);assert(MmxArmorGetState().burst==1 && MmxArmorGetState().hyper_energy==0x1a00);
MmxArmorRefill();assert(MmxArmorGetState().hyper_energy==0x1c00);
gold_recovery();
player();ram[0x1f99]=15;ram[0x1f7a]=3;MmxArmorPrePlayer(ram);MmxArmorHadoukenAward(ram);
MmxArmorState retry=MmxArmorGetState();retry.hyper=1;retry.hyper_energy=12;retry.dash_used=2;
MmxArmorState retry=MmxArmorGetState();retry.hyper_active=1;retry.hyper_energy=12;retry.dash_used=2;
MmxArmorSetState(retry);ram[0xd2]=2;assert(!MmxArmorFrameTick(ram,0,true));
ram[0xd2]=4;MmxArmorPrePlayer(ram);retry=MmxArmorGetState();
assert(retry.gold && retry.hyper_energy==0x1c00 && !retry.hyper && !retry.dash_used);
assert(retry.gold && retry.hyper_energy==0x1c00 && !retry.hyper_active && !retry.dash_used);
assert(MmxArmorFrameTick(ram,0,true) && MmxArmorGetState().tracer);
puts("Gold Hyper Charge cost/refill and source-timed health/Sub Tank recovery passed");
}
Expand Down
5 changes: 4 additions & 1 deletion tests/mmx_native_renderer_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ static uint32_t row_random(void) {
return row_rng;
}

int main(void) {
#ifndef MMX_NATIVE_TEST_ENTRY
#define MMX_NATIVE_TEST_ENTRY main
#endif
int MMX_NATIVE_TEST_ENTRY(void) {
renderer_scene_suite();
MmxRendererReset(); MmxRendererSetRom(NULL, 0);
memset(&frame_rush, 0, sizeof(frame_rush));
Expand Down
Loading
Loading