Compare commits

...

4 commits

Author SHA1 Message Date
Reonu
332a45cd64
Fix Skull Kid curse effect in ultrawide (32:9+) (#259) 2024-05-26 07:23:25 +01:00
Reonu
23eb6b7eea
Support for high precision framebuffers and dither noise (RT64) (#252)
* 64 bit framebuffer for rt64

* Remove mention of motion blur cap from readme

* Add graphics.json option to control high precision framebuffers, disable clamping alpha for accumulation blur when using high precision FBs

* Increase dither noise strength at higher resolutions to make it more noticeable

---------

Co-authored-by: Mr-Wiseguy <mrwiseguyromhacking@gmail.com>
2024-05-26 01:24:46 -04:00
Parker
169155953e
Add main volume option to settings (#267)
* This commit adds the option to control the main volume game via a slider
added to the "Sound" tab in the settings menu.
2024-05-26 00:59:15 -04:00
Reonu
41e737249e
Fix "Dawn of the X day" screen ending too early (#266) 2024-05-25 23:41:00 -04:00
19 changed files with 431 additions and 63 deletions

View file

@ -72,7 +72,7 @@ Saving and loading files, going from place to place, and pausing all happen in t
#### Linux and Steam Deck Support
A Linux binary is available for playing on most up-to-date distros, including on the Steam Deck.
To play on Steam Deck, extract the Linux build onto your deck. Then, in desktop mode, right click the Zelda64Recompiled executable file and select "Add to Steam" as shown. From there, you can return to Gaming mode and configure the controls as needed. See the [Steam Deck gyro aim FAQ section](#how-do-i-set-up-gyro-aiming-on-steam-deck) for more detailed instructions.
To play on Steam Deck, extract the Linux build onto your deck. Then, in desktop mode, right click the Zelda64Recompiled executable file and select "Add to Steam". From there, you can return to Gaming mode and configure the controls as needed. See the [Steam Deck gyro aim FAQ section](#how-do-i-set-up-gyro-aiming-on-steam-deck) for more detailed instructions.
## Planned Features
* Dual analog control scheme (with analog camera)
@ -108,7 +108,6 @@ You'll probably also want to change the default behavior so that you don't need
If you want to play a modded ROM or in another language, note that support for modding and other languages will be added to the project itself in the future and will not rely on you supplying a different ROM.
## Known Issues
* The motion blur effect used by the game was capped to prevent ghosting at incredibly high framerates, which causes it to be less noticeable (this is only really noticeable above 120FPS). This may be fixed in the future by offering the option to render to an HDR framebuffer internally, which would allow it to be uncapped.
* Intel GPUs on Linux may not currently work. If you have experience with Vulkan development on Linux, help here would be greatly appreciated!
* The prebuilt Linux binary may not work correctly on some distributions of Linux. If you encounter such an issue, building the project locally yourself is recommended. A Flatpak or AppImage may be provided in the future to solve this issue. Adding the Linux version to Steam and setting "Steam Linux Runtime" as the compatibility tool or launching it via Gamescope may work around the issue. Alternatively, running the Windows version with Proton is known to work well and may also work around this issue.
* Overlays such as MSI Afterburner and other software such as Wallpaper Engine can cause performance issues with this project that prevent the game from rendering correctly. Disabling such software is recommended.

View file

@ -7,30 +7,47 @@
<!-- Options -->
<div class="config__wrapper" data-event-mouseout="set_cur_config_index(-1)">
<div class="config-option" data-event-mouseover="set_cur_config_index(0)">
<label class="config-option__title">Main Volume</label>
<div class="config-option__range-wrapper config-option__list">
<label class="config-option__range-label">{{main_volume}}%</label>
<input
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(0)"
class="nav-vert"
id="main_volume_input"
type="range"
min="0"
max="100"
style="flex: 1; margin: 0dp; nav-up: #tab_sound; nav-down: #bgm_volume_input;"
data-value="main_volume"
/>
</div>
</div>
<div class="config-option" data-event-mouseover="set_cur_config_index(1)">
<label class="config-option__title">Background Music Volume</label>
<div class="config-option__range-wrapper config-option__list">
<label class="config-option__range-label">{{bgm_volume}}%</label>
<input
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(0)"
data-event-focus="set_cur_config_index(1)"
class="nav-vert"
id="bgm_volume_input"
type="range"
min="0"
max="100"
style="flex: 1; margin: 0dp; nav-up: #tab_sound; nav-down: #lhb_on;"
style="flex: 1; margin: 0dp; nav-up: #main_volume_input; nav-down: #lhb_on;"
data-value="bgm_volume"
/>
</div>
</div>
<div class="config-option" data-event-mouseover="set_cur_config_index(1)">
<div class="config-option" data-event-mouseover="set_cur_config_index(2)">
<label class="config-option__title">Low Health Beeps</label>
<div class="config-option__list">
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(1)"
data-event-focus="set_cur_config_index(2)"
name="lhb"
data-checked="low_health_beeps_enabled"
value="1"
@ -42,7 +59,7 @@
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(1)"
data-event-focus="set_cur_config_index(2)"
name="lhb"
data-checked="low_health_beeps_enabled"
value="0"
@ -55,10 +72,13 @@
</div>
<!-- Descriptions -->
<div class="config__wrapper">
<p data-if="cur_config_index == 0">
<p data-if="cur_config_index == 0">
Controls the main volume of the game.
</p>
<p data-if="cur_config_index == 1">
Controls the overall volume of background music.
</p>
<p data-if="cur_config_index == 1">
<p data-if="cur_config_index == 2">
Toggles whether or not the low-health beeping sound plays.
</p>
</div>

View file

@ -3,6 +3,8 @@
namespace recomp {
void reset_sound_settings();
void set_main_volume(int volume);
int get_main_volume();
void set_bgm_volume(int volume);
int get_bgm_volume();
void set_low_health_beeps_enabled(bool enabled);

View file

@ -32,6 +32,7 @@ namespace ultramodern {
void shutdown();
void set_dummy_vi();
uint32_t get_display_framerate();
float get_resolution_scale();
void load_shader_cache(std::span<const char> cache_binary);
private:
RT64SetupResult setup_result;
@ -40,6 +41,7 @@ namespace ultramodern {
RT64::UserConfiguration::Antialiasing RT64MaxMSAA();
bool RT64SamplePositionsSupported();
bool RT64HighPrecisionFBEnabled();
}
void set_rt64_hooks();

@ -1 +1 @@
Subproject commit 1dd801264dbbf7a2f4e8fc483d28664852f50082
Subproject commit 64b9e166f75b4dc44a59983b67c3e9ecc1f4cfd7

View file

@ -98,11 +98,20 @@ void Play_DrawMotionBlur(PlayState* this) {
f32 exponent = 20.0f / recomp_get_target_framerate(gFramerateDivisor);
f32 alpha_float = recomp_powf(alpha / 255.0f, exponent);
// Clamp the blur alpha, which ensures that the output color converges to within a reasonable delta of the target color
// when using an R8G8B8A8 framebuffer as RT64 currently does. Although this makes the effect less noticeable at high framerates,
// when using an R8G8B8A8 framebuffer. Although this makes the effect less noticeable at high framerates,
// not clamping leads to noticeable image retention.
alpha_float = MIN(alpha_float, 0.825f);
// Skip clamping if high precision framebuffers are in use, as there's no risk of ghosting with those.
if (!recomp_high_precision_fb_enabled()) {
alpha_float = MIN(alpha_float, 0.825f);
}
alpha = (s32)(alpha_float * 255.0f);
// @recomp Set the dither noise strength based on the resolution scale to make it easier to see at higher resolutions.
float res_scale = recomp_get_resolution_scale();
float dither_noise_strength = CLAMP(1.0 + (res_scale - 1.0f) / 8.0f, 1.0f, 2.0f);
// recomp_printf("res scale: %5.3f dither noise strength: %5.3f\n", res_scale, dither_noise_strength);
gEXSetDitherNoiseStrength(OVERLAY_DISP++, dither_noise_strength);
if (sMotionBlurStatus == MOTION_BLUR_PROCESS) {
func_80170AE0(&this->pauseBgPreRender, &gfx, alpha);
} else {

View file

@ -1,7 +1,6 @@
#include "patches.h"
#include "transform_ids.h"
#include "overlays/actors/ovl_Object_Kankyo/z_object_kankyo.h"
#include "overlays/actors/ovl_Eff_Stk/z_eff_stk.h"
#include "overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.h"
#include "z64effect.h"
@ -392,51 +391,6 @@ void Effect_DrawAll(GraphicsContext* gfxCtx) {
}
}
extern Gfx object_stk2_DL_008920[];
extern Gfx object_stk2_DL_008A38[];
extern AnimatedMaterial object_stk2_Matanimheader_009F60[];
void EffStk_Draw(Actor* thisx, PlayState* play) {
EffStk* this = (EffStk*)thisx;
s32 pad;
Camera* activeCam = GET_ACTIVE_CAM(play);
Vec3f eye = activeCam->eye;
Vec3f quakeOffset;
quakeOffset = Camera_GetQuakeOffset(activeCam);
OPEN_DISPS(play->state.gfxCtx);
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
Matrix_Translate(eye.x + quakeOffset.x, eye.y + quakeOffset.y, eye.z + quakeOffset.z, MTXMODE_NEW);
Matrix_Scale(0.2f, 0.2f, 0.2f, MTXMODE_APPLY);
Matrix_ReplaceRotation(&play->billboardMtxF);
Matrix_Translate(0.0f, 0.0f, this->unk148, MTXMODE_APPLY);
Mtx* mtx = Matrix_NewMtx(play->state.gfxCtx);
// @recomp Tag the transform. Do not allow edits as this will get edited by the billboard detection and we'll want to skip position during a camera cut too.
if (camera_was_skipped()) {
gEXMatrixGroupDecomposedSkipPosRot(POLY_XLU_DISP++, actor_transform_id(thisx), G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
else {
gEXMatrixGroupDecomposedNormal(POLY_XLU_DISP++, actor_transform_id(thisx), G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
gSPMatrix(POLY_XLU_DISP++, mtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
AnimatedMat_DrawAlphaStep(play, Lib_SegmentedToVirtual(object_stk2_Matanimheader_009F60), 1.0f, this->unk144);
gDPSetColorDither(POLY_XLU_DISP++, G_CD_NOISE);
gDPSetAlphaDither(POLY_XLU_DISP++, G_AD_NOISE);
gSPDisplayList(POLY_XLU_DISP++, object_stk2_DL_008920);
gSPDisplayList(POLY_XLU_DISP++, object_stk2_DL_008A38);
// @recomp Pop the transform tag.
gEXPopMatrixGroup(POLY_XLU_DISP++, G_MTX_MODELVIEW);
CLOSE_DISPS(play->state.gfxCtx);
}
typedef enum {
/* 0x00 */ CLEAR_TAG_EFFECT_AVAILABLE,
/* 0x01 */ CLEAR_TAG_EFFECT_DEBRIS,

View file

@ -2,6 +2,8 @@
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
#include "overlays/actors/ovl_En_Fall/z_en_fall.h"
#include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h"
#include "overlays/gamestates/ovl_daytelop/z_daytelop.h"
#include "z64shrink_window.h"
#define PAGE_BG_WIDTH (PAGE_BG_COLS * PAGE_BG_QUAD_WIDTH)
#define PAGE_BG_HEIGHT (PAGE_BG_ROWS * PAGE_BG_QUAD_HEIGHT)
@ -330,3 +332,47 @@ s32 DemoEffect_OverrideLimbDrawTimewarp(PlayState* play, SkelCurve* skelCurve, s
return true;
}
void* gamestate_relocate(void* addr, GameStateId id) {
GameStateOverlay* ovl = &gGameStateOverlayTable[id];
if ((uintptr_t)addr >= 0x80800000) {
return (void*)((uintptr_t)addr -
(intptr_t)((uintptr_t)ovl->vramStart - (uintptr_t)ovl->loadedRamAddr));
}
else {
recomp_printf("Not an overlay address!: 0x%08X 0x%08X 0x%08X\n", (u32)addr, (u32)ovl->vramStart, (u32)ovl->loadedRamAddr);
return addr;
}
}
void DayTelop_Main(GameState* thisx);
void DayTelop_Destroy(GameState* thisx);
void DayTelop_Noop(DayTelopState* this);
void DayTelop_LoadGraphics(DayTelopState* this);
// @recomp Increase the length of the "Dawn of the X Day" screen to account for faster loading.
void DayTelop_Init(GameState* thisx) {
DayTelopState* this = (DayTelopState*)thisx;
GameState_SetFramerateDivisor(&this->state, 1);
Matrix_Init(&this->state);
ShrinkWindow_Destroy();
View_Init(&this->view, this->state.gfxCtx);
// @recomp Manual relocation, TODO remove when automated.
this->state.main = (GameStateFunc)gamestate_relocate(DayTelop_Main, GAMESTATE_DAYTELOP);
this->state.destroy = (GameStateFunc)gamestate_relocate(DayTelop_Destroy, GAMESTATE_DAYTELOP);
// @recomp Add 120 extra frames (2 seconds with a frame divisor of 1) to account for faster loading.
this->transitionCountdown = 260;
this->fadeInState = DAYTELOP_HOURSTEXT_OFF;
if (gSaveContext.save.day < 9) {
if (gSaveContext.save.day == 0) {
Sram_ClearFlagsAtDawnOfTheFirstDay();
}
Sram_IncrementDay();
}
DayTelop_Noop(this);
DayTelop_LoadGraphics(this);
Audio_PlaySfx(NA_SE_OC_TELOP_IMPACT);
}

View file

@ -5,5 +5,7 @@
DECLARE_FUNC(float, recomp_get_aspect_ratio, float);
DECLARE_FUNC(s32, recomp_get_target_framerate, s32);
DECLARE_FUNC(s32, recomp_high_precision_fb_enabled);
DECLARE_FUNC(float, recomp_get_resolution_scale);
#endif

View file

@ -6,6 +6,8 @@
#include "overlays/actors/ovl_Oceff_Wipe5/z_oceff_wipe5.h"
#include "overlays/actors/ovl_Oceff_Wipe6/z_oceff_wipe6.h"
#include "overlays/actors/ovl_Oceff_Wipe7/z_oceff_wipe7.h"
#include "overlays/actors/ovl_Eff_Stk/z_eff_stk.h"
#include "transform_ids.h"
// @recomp Custom verts to extend the effects past the camera so they cover any aspect ratio.
Vtx time_override_verts[156] = {
@ -779,6 +781,204 @@ Gfx healing_override_dl[] = {
gsSPEndDisplayList(),
};
Vtx skull_kid_curse_1_override_verts[52] = { // 008A38
{{ {71, 23, 0}, 0, {614, 1024}, {255, 255, 255, 0} }},
{{ {121, 88, 500}, 0, {716, 0}, {255, 255, 255, 255} }},
{{ {44, 61, 0}, 0, {819, 1024}, {255, 255, 255, 0} }},
{{ {46, 143, 500}, 0, {921, 0}, {255, 255, 255, 255} }},
{{ {0, 75, 0}, 0, {1024, 1024}, {255, 255, 255, 0} }},
{{ {-46, 143, 500}, 0, {1126, 0}, {255, 255, 255, 255} }},
{{ {-44, 61, 0}, 0, {1228, 1024}, {255, 255, 255, 0} }},
{{ {-121, 88, 500}, 0, {1331, 0}, {255, 255, 255, 255} }},
{{ {-71, 23, 0}, 0, {1433, 1024}, {255, 255, 255, 0} }},
{{ {-150, 0, 500}, 0, {1536, 0}, {255, 255, 255, 255} }},
{{ {-71, -23, 0}, 0, {1638, 1024}, {255, 255, 255, 0} }},
{{ {-121, -88, 500}, 0, {1740, 0}, {255, 255, 255, 255} }},
{{ {-44, -61, 0}, 0, {1843, 1024}, {255, 255, 255, 0} }},
{{ {-46, -143, 500}, 0, {1945, 0}, {255, 255, 255, 255} }},
{{ {0, -75, 0}, 0, {2048, 1024}, {255, 255, 255, 0} }},
{{ {0, -75, 0}, 0, {0, 1024}, {255, 255, 255, 0} }},
{{ {46, -143, 500}, 0, {102, 0}, {255, 255, 255, 255} }},
{{ {44, -61, 0}, 0, {204, 1024}, {255, 255, 255, 0} }},
{{ {121, -88, 500}, 0, {307, 0}, {255, 255, 255, 255} }},
{{ {71, -23, 0}, 0, {409, 1024}, {255, 255, 255, 0} }},
{{ {150, 0, 500}, 0, {512, 0}, {255, 255, 255, 255} }},
{{ {248, 0, 1080}, 0, {580, -1192}, {255, 255, 255, 255} }},
{{ {200, 146, 1080}, 0, {782, -1192}, {255, 255, 255, 255} }},
{{ {200, 146, 1080}, 0, {782, -1192}, {255, 255, 255, 255} }},
{{ {76, 237, 1080}, 0, {989, -1192}, {255, 255, 255, 255} }},
{{ {76, 237, 1080}, 0, {988, -1192}, {255, 255, 255, 255} }},
{{ {-76, 237, 1080}, 0, {1193, -1192}, {255, 255, 255, 255} }},
{{ {-76, 237, 1080}, 0, {1194, -1192}, {255, 255, 255, 255} }},
{{ {-200, 146, 1080}, 0, {1397, -1192}, {255, 255, 255, 255} }},
{{ {-200, 146, 1080}, 0, {1397, -1192}, {255, 255, 255, 255} }},
{{ {-248, 0, 1080}, 0, {1604, -1192}, {255, 255, 255, 255} }},
{{ {-248, 0, 1080}, 0, {1604, -1192}, {255, 255, 255, 255} }},
{{ {-121, -88, 500}, 0, {1740, 0}, {255, 255, 255, 255} }},
{{ {-248, 0, 1080}, 0, {1604, -1192}, {255, 255, 255, 255} }},
{{ {-200, -146, 1080}, 0, {1806, -1192}, {255, 255, 255, 255} }},
{{ {-44, -61, 0}, 0, {1843, 1024}, {255, 255, 255, 0} }},
{{ {-46, -143, 500}, 0, {1945, 0}, {255, 255, 255, 255} }},
{{ {-200, -146, 1080}, 0, {1806, -1192}, {255, 255, 255, 255} }},
{{ {-76, -237, 1080}, 0, {2013, -1192}, {255, 255, 255, 255} }},
{{ {0, -75, 0}, 0, {2048, 1024}, {255, 255, 255, 0} }},
{{ {46, -143, 500}, 0, {2150, 0}, {255, 255, 255, 255} }},
{{ {-76, -237, 1080}, 0, {2012, -1192}, {255, 255, 255, 255} }},
{{ {76, -237, 1080}, 0, {2217, -1192}, {255, 255, 255, 255} }},
{{ {44, -61, 0}, 0, {204, 1024}, {255, 255, 255, 0} }},
{{ {46, -143, 500}, 0, {102, 0}, {255, 255, 255, 255} }},
{{ {121, -88, 500}, 0, {307, 0}, {255, 255, 255, 255} }},
{{ {76, -237, 1080}, 0, {170, -1192}, {255, 255, 255, 255} }},
{{ {200, -146, 1080}, 0, {373, -1192}, {255, 255, 255, 255} }},
{{ {71, -23, 0}, 0, {409, 1024}, {255, 255, 255, 0} }},
{{ {150, 0, 500}, 0, {512, 0}, {255, 255, 255, 255} }},
{{ {200, -146, 1080}, 0, {373, -1192}, {255, 255, 255, 255} }},
{{ {248, 0, 1080}, 0, {580, -1192}, {255, 255, 255, 255} }},
};
extern u64 object_stk2_Tex_008B50[];
extern u64 object_stk2_Tex_008B50[];
Gfx skull_kid_curse_1_override_dl[] = { // 008A38
gsSPTexture(0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON),
gsDPPipeSync(),
gsDPSetCombineLERP(TEXEL1, PRIMITIVE, PRIM_LOD_FRAC, TEXEL0, TEXEL1, 0, PRIM_LOD_FRAC, TEXEL0, PRIMITIVE,
ENVIRONMENT, COMBINED, ENVIRONMENT, COMBINED, 0, SHADE, 0),
gsDPSetPrimColor(0, 0x80, 255, 255, 100, 255),
gsDPSetEnvColor(255, 0, 150, 255),
gsDPSetRenderMode(G_RM_PASS, G_RM_ZB_XLU_SURF2),
gsDPSetTextureLUT(G_TT_NONE),
gsDPLoadTextureBlock(object_stk2_Tex_008B50, G_IM_FMT_I, G_IM_SIZ_8b, 32, 64, 0, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_NOMIRROR | G_TX_WRAP, 5, 6, 1, 15),
gsDPLoadMultiBlock(object_stk2_Tex_008B50, 0x0000, 1, G_IM_FMT_I, G_IM_SIZ_8b, 32, 64, 0, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_NOMIRROR | G_TX_WRAP, 5, 6, 15, 14),
gsSPDisplayList(0x08000000),
gsSPLoadGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_SHADING_SMOOTH),
gsSPVertex(skull_kid_curse_1_override_verts + 0, 32, 0),
gsSP2Triangles(0, 1, 2, 0, 2, 3, 4, 0),
gsSP2Triangles(4, 5, 6, 0, 6, 7, 8, 0),
gsSP2Triangles(8, 9, 10, 0, 10, 11, 12, 0),
gsSP2Triangles(12, 13, 14, 0, 15, 16, 17, 0),
gsSP2Triangles(17, 18, 19, 0, 19, 20, 0, 0),
gsSP2Triangles(0, 20, 1, 0, 1, 20, 21, 0),
gsSP2Triangles(1, 21, 22, 0, 2, 1, 3, 0),
gsSP2Triangles(3, 1, 23, 0, 3, 23, 24, 0),
gsSP2Triangles(4, 3, 5, 0, 5, 3, 25, 0),
gsSP2Triangles(5, 25, 26, 0, 6, 5, 7, 0),
gsSP2Triangles(7, 5, 27, 0, 7, 27, 28, 0),
gsSP2Triangles(8, 7, 9, 0, 9, 7, 29, 0),
gsSP2Triangles(9, 29, 30, 0, 10, 9, 11, 0),
gsSP1Triangle(11, 9, 31, 0),
gsSPVertex(skull_kid_curse_1_override_verts + 32, 20, 0),
gsSP2Triangles(0, 1, 2, 0, 3, 0, 4, 0),
gsSP2Triangles(4, 0, 5, 0, 4, 5, 6, 0),
gsSP2Triangles(7, 4, 8, 0, 8, 4, 9, 0),
gsSP2Triangles(8, 9, 10, 0, 11, 12, 13, 0),
gsSP2Triangles(13, 12, 14, 0, 13, 14, 15, 0),
gsSP2Triangles(16, 13, 17, 0, 17, 13, 18, 0),
gsSP1Triangle(17, 18, 19, 0),
gsSPEndDisplayList(),
};
Vtx skull_kid_curse_2_override_verts[52] = { // 008920
{{ {86, -28, 0}, 0, {409, 1024}, {255, 255, 255, 0} }},
{{ {180, 0, 500}, 0, {512, 0}, {255, 255, 255, 255} }},
{{ {86, 28, 0}, 0, {614, 1024}, {255, 255, 255, 0} }},
{{ {53, -73, 0}, 0, {204, 1024}, {255, 255, 255, 0} }},
{{ {146, -106, 500}, 0, {307, 0}, {255, 255, 255, 255} }},
{{ {0, -90, 0}, 0, {0, 1024}, {255, 255, 255, 0} }},
{{ {56, -171, 500}, 0, {102, 0}, {255, 255, 255, 255} }},
{{ {-53, -73, 0}, 0, {1843, 1024}, {255, 255, 255, 0} }},
{{ {-56, -171, 500}, 0, {1945, 0}, {255, 255, 255, 255} }},
{{ {0, -90, 0}, 0, {2048, 1024}, {255, 255, 255, 0} }},
{{ {-86, -28, 0}, 0, {1638, 1024}, {255, 255, 255, 0} }},
{{ {-146, -106, 500}, 0, {1740, 0}, {255, 255, 255, 255} }},
{{ {-86, 28, 0}, 0, {1433, 1024}, {255, 255, 255, 0} }},
{{ {-180, 0, 500}, 0, {1536, 0}, {255, 255, 255, 255} }},
{{ {-53, 73, 0}, 0, {1228, 1024}, {255, 255, 255, 0} }},
{{ {-146, 106, 500}, 0, {1331, 0}, {255, 255, 255, 255} }},
{{ {0, 90, 0}, 0, {1024, 1024}, {255, 255, 255, 0} }},
{{ {-56, 171, 500}, 0, {1126, 0}, {255, 255, 255, 255} }},
{{ {53, 73, 0}, 0, {819, 1024}, {255, 255, 255, 0} }},
{{ {56, 171, 500}, 0, {921, 0}, {255, 255, 255, 255} }},
{{ {146, 106, 500}, 0, {716, 0}, {255, 255, 255, 255} }},
{{ {218, -159, 1080}, 0, {359, -1185}, {255, 255, 255, 255} }},
{{ {269, 0, 1080}, 0, {562, -1185}, {255, 255, 255, 255} }},
{{ {84, -256, 1080}, 0, {152, -1185}, {255, 255, 255, 255} }},
{{ {218, -159, 1080}, 0, {359, -1185}, {255, 255, 255, 255} }},
{{ {56, -171, 500}, 0, {2150, 0}, {255, 255, 255, 255} }},
{{ {-84, -256, 1080}, 0, {1996, -1185}, {255, 255, 255, 255} }},
{{ {84, -256, 1080}, 0, {2201, -1185}, {255, 255, 255, 255} }},
{{ {-218, -159, 1080}, 0, {1792, -1185}, {255, 255, 255, 255} }},
{{ {-84, -256, 1080}, 0, {1995, -1185}, {255, 255, 255, 255} }},
{{ {-269, 0, 1080}, 0, {1586, -1185}, {255, 255, 255, 255} }},
{{ {-218, -159, 1080}, 0, {1791, -1185}, {255, 255, 255, 255} }},
{{ {-180, 0, 500}, 0, {1536, 0}, {255, 255, 255, 255} }},
{{ {-146, 106, 500}, 0, {1331, 0}, {255, 255, 255, 255} }},
{{ {-218, 159, 1080}, 0, {1383, -1185}, {255, 255, 255, 255} }},
{{ {-269, 0, 1080}, 0, {1586, -1185}, {255, 255, 255, 255} }},
{{ {-53, 73, 0}, 0, {1228, 1024}, {255, 255, 255, 0} }},
{{ {-56, 171, 500}, 0, {1126, 0}, {255, 255, 255, 255} }},
{{ {-84, 256, 1080}, 0, {1176, -1185}, {255, 255, 255, 255} }},
{{ {-218, 159, 1080}, 0, {1383, -1185}, {255, 255, 255, 255} }},
{{ {0, 90, 0}, 0, {1024, 1024}, {255, 255, 255, 0} }},
{{ {56, 171, 500}, 0, {921, 0}, {255, 255, 255, 255} }},
{{ {84, 256, 1080}, 0, {972, -1185}, {255, 255, 255, 255} }},
{{ {-84, 256, 1080}, 0, {1177, -1185}, {255, 255, 255, 255} }},
{{ {53, 73, 0}, 0, {819, 1024}, {255, 255, 255, 0} }},
{{ {146, 106, 500}, 0, {716, 0}, {255, 255, 255, 255} }},
{{ {218, 159, 1080}, 0, {768, -1185}, {255, 255, 255, 255} }},
{{ {84, 256, 1080}, 0, {971, -1185}, {255, 255, 255, 255} }},
{{ {86, 28, 0}, 0, {614, 1024}, {255, 255, 255, 0} }},
{{ {180, 0, 500}, 0, {512, 0}, {255, 255, 255, 255} }},
{{ {269, 0, 1080}, 0, {562, -1185}, {255, 255, 255, 255} }},
{{ {218, 159, 1080}, 0, {767, -1185}, {255, 255, 255, 255} }},
};
extern u64 object_stk2_Tex_009750[];
extern u64 object_stk2_Tex_009350[];
Gfx skull_kid_curse_2_override_dl[] = { // 008920
gsSPTexture(0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON),
gsDPPipeSync(),
gsDPSetCombineLERP(TEXEL1, PRIMITIVE, PRIM_LOD_FRAC, TEXEL0, TEXEL1, 0, PRIM_LOD_FRAC, TEXEL0, PRIMITIVE,
ENVIRONMENT, COMBINED, ENVIRONMENT, COMBINED, 0, SHADE, 0),
gsDPSetPrimColor(0, 0x80, 0, 0, 255, 255),
gsDPSetEnvColor(0, 220, 0, 255),
gsDPSetRenderMode(G_RM_PASS, G_RM_AA_ZB_XLU_INTER2),
gsDPSetTextureLUT(G_TT_NONE),
gsDPLoadTextureBlock(object_stk2_Tex_009750, G_IM_FMT_I, G_IM_SIZ_8b, 32, 64, 0, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_MIRROR | G_TX_WRAP, 5, 6, 15, 14),
gsDPLoadMultiBlock(object_stk2_Tex_009350, 0x0100, 1, G_IM_FMT_I, G_IM_SIZ_8b, 32, 32, 0, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_NOMIRROR | G_TX_WRAP, 5, 5, 15, 1),
gsSPDisplayList(0x09000000),
gsSPLoadGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BACK | G_SHADING_SMOOTH),
gsSPVertex(skull_kid_curse_2_override_verts + 0, 32, 0),
gsSP2Triangles(0, 1, 2, 0, 3, 4, 0, 0),
gsSP2Triangles(5, 6, 3, 0, 7, 8, 9, 0),
gsSP2Triangles(10, 11, 7, 0, 12, 13, 10, 0),
gsSP2Triangles(14, 15, 12, 0, 16, 17, 14, 0),
gsSP2Triangles(18, 19, 16, 0, 2, 20, 18, 0),
gsSP2Triangles(0, 4, 1, 0, 1, 4, 21, 0),
gsSP2Triangles(1, 21, 22, 0, 3, 6, 4, 0),
gsSP2Triangles(4, 6, 23, 0, 4, 23, 24, 0),
gsSP2Triangles(9, 8, 25, 0, 25, 8, 26, 0),
gsSP2Triangles(25, 26, 27, 0, 7, 11, 8, 0),
gsSP2Triangles(8, 11, 28, 0, 8, 28, 29, 0),
gsSP2Triangles(10, 13, 11, 0, 11, 13, 30, 0),
gsSP2Triangles(11, 30, 31, 0, 12, 15, 13, 0),
gsSPVertex(skull_kid_curse_2_override_verts + 32, 20, 0),
gsSP2Triangles(0, 1, 2, 0, 0, 2, 3, 0),
gsSP2Triangles(4, 5, 1, 0, 1, 5, 6, 0),
gsSP2Triangles(1, 6, 7, 0, 8, 9, 5, 0),
gsSP2Triangles(5, 9, 10, 0, 5, 10, 11, 0),
gsSP2Triangles(12, 13, 9, 0, 9, 13, 14, 0),
gsSP2Triangles(9, 14, 15, 0, 16, 17, 13, 0),
gsSP2Triangles(13, 17, 18, 0, 13, 18, 19, 0),
gsSPEndDisplayList(),
};
// Sets the flag to 1 for any vertex that has an alpha of 255.
void set_vertex_flags(Vtx* verts, s32 count) {
for (s32 i = 0; i < count; i++) {
@ -1249,3 +1449,48 @@ void OceffWipe7_Draw(Actor* thisx, PlayState* play) {
CLOSE_DISPS(play->state.gfxCtx);
}
extern AnimatedMaterial object_stk2_Matanimheader_009F60[];
// @recomp Patch the Skull Kid curse effect as well, which works similarly to the ocarina effects.
// In this case, the patch also includes effect transform tagging patches.
void EffStk_Draw(Actor* thisx, PlayState* play) {
EffStk* this = (EffStk*)thisx;
s32 pad;
Camera* activeCam = GET_ACTIVE_CAM(play);
Vec3f eye = activeCam->eye;
Vec3f quakeOffset;
quakeOffset = Camera_GetQuakeOffset(activeCam);
OPEN_DISPS(play->state.gfxCtx);
Gfx_SetupDL25_Xlu(play->state.gfxCtx);
Matrix_Translate(eye.x + quakeOffset.x, eye.y + quakeOffset.y, eye.z + quakeOffset.z, MTXMODE_NEW);
Matrix_Scale(0.2f, 0.2f, 0.2f, MTXMODE_APPLY);
Matrix_ReplaceRotation(&play->billboardMtxF);
Matrix_Translate(0.0f, 0.0f, this->unk148, MTXMODE_APPLY);
Mtx* mtx = Matrix_NewMtx(play->state.gfxCtx);
// @recomp Tag the transform. Do not allow edits as this will get edited by the billboard detection and we'll want to skip position during a camera cut too.
if (camera_was_skipped()) {
gEXMatrixGroupDecomposedSkipPosRot(POLY_XLU_DISP++, actor_transform_id(thisx), G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
else {
gEXMatrixGroupDecomposedNormal(POLY_XLU_DISP++, actor_transform_id(thisx), G_EX_PUSH, G_MTX_MODELVIEW, G_EX_EDIT_NONE);
}
gSPMatrix(POLY_XLU_DISP++, mtx, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
AnimatedMat_DrawAlphaStep(play, Lib_SegmentedToVirtual(object_stk2_Matanimheader_009F60), 1.0f, this->unk144);
gDPSetColorDither(POLY_XLU_DISP++, G_CD_NOISE);
gDPSetAlphaDither(POLY_XLU_DISP++, G_AD_NOISE);
// @recomp Use the new DLs instead of the originals.
gSPDisplayList(POLY_XLU_DISP++, skull_kid_curse_2_override_dl);
gSPDisplayList(POLY_XLU_DISP++, skull_kid_curse_1_override_dl);
// @recomp Pop the transform tag.
gEXPopMatrixGroup(POLY_XLU_DISP++, G_MTX_MODELVIEW);
CLOSE_DISPS(play->state.gfxCtx);
}

View file

@ -52,3 +52,5 @@ osGetTime_recomp = 0x8F000088;
recomp_autosave_enabled = 0x8F00008C;
recomp_load_overlays = 0x8F000090;
osInvalICache_recomp = 0x8F000094;
recomp_high_precision_fb_enabled = 0x8F0000A8;
recomp_get_resolution_scale = 0x8F0000AC;

View file

@ -24,6 +24,7 @@ constexpr auto api_default = ultramodern::GraphicsApi::Auto;
constexpr auto ar_default = RT64::UserConfiguration::AspectRatio::Expand;
constexpr auto msaa_default = RT64::UserConfiguration::Antialiasing::MSAA2X;
constexpr auto rr_default = RT64::UserConfiguration::RefreshRate::Display;
constexpr auto hpfb_default = ultramodern::HighPrecisionFramebuffer::Auto;
constexpr int ds_default = 1;
constexpr int rr_manual_default = 60;
constexpr bool developer_mode_default = false;
@ -93,6 +94,7 @@ namespace ultramodern {
{"ar_option", config.ar_option},
{"msaa_option", config.msaa_option},
{"rr_option", config.rr_option},
{"hpfb_option", config.hpfb_option},
{"rr_manual_value", config.rr_manual_value},
{"developer_mode", config.developer_mode},
};
@ -107,6 +109,7 @@ namespace ultramodern {
config.ar_option = from_or_default(j, "ar_option", ar_default);
config.msaa_option = from_or_default(j, "msaa_option", msaa_default);
config.rr_option = from_or_default(j, "rr_option", rr_default);
config.hpfb_option = from_or_default(j, "hpfb_option", hpfb_default);
config.rr_manual_value = from_or_default(j, "rr_manual_value", rr_manual_default);
config.developer_mode = from_or_default(j, "developer_mode", developer_mode_default);
}
@ -245,6 +248,7 @@ void reset_graphics_options() {
new_config.ar_option = ar_default;
new_config.msaa_option = msaa_default;
new_config.rr_option = rr_default;
new_config.hpfb_option = hpfb_default;
new_config.rr_manual_value = rr_manual_default;
new_config.developer_mode = developer_mode_default;
ultramodern::set_graphics_config(new_config);
@ -344,6 +348,7 @@ void load_controls_config(const std::filesystem::path& path) {
void save_sound_config(const std::filesystem::path& path) {
nlohmann::json config_json{};
config_json["main_volume"] = recomp::get_main_volume();
config_json["bgm_volume"] = recomp::get_bgm_volume();
config_json["low_health_beeps"] = recomp::get_low_health_beeps_enabled();
@ -357,8 +362,8 @@ void load_sound_config(const std::filesystem::path& path) {
config_file >> config_json;
recomp::reset_sound_settings();
call_if_key_exists(recomp::set_main_volume, config_json, "main_volume");
call_if_key_exists(recomp::set_bgm_volume, config_json, "bgm_volume");
call_if_key_exists(recomp::set_low_health_beeps_enabled, config_json, "low_health_beeps");
}

View file

@ -7,6 +7,7 @@
#include "recomp_ui.h"
#include "recomp_sound.h"
#include "recomp_helpers.h"
#include "rt64_layer.h"
#include "../patches/input.h"
#include "../patches/graphics.h"
#include "../patches/sound.h"
@ -78,7 +79,6 @@ extern "C" void recomp_get_targeting_mode(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, static_cast<int>(recomp::get_targeting_mode()));
}
extern "C" void recomp_get_bgm_volume(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, recomp::get_bgm_volume() / 100.0f);
}
@ -102,3 +102,11 @@ extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) {
load_overlays(rom, ram, size);
}
extern "C" void recomp_high_precision_fb_enabled(uint8_t * rdram, recomp_context * ctx) {
_return(ctx, static_cast<s32>(ultramodern::RT64HighPrecisionFBEnabled()));
}
extern "C" void recomp_get_resolution_scale(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, ultramodern::get_resolution_scale());
}

View file

@ -23,6 +23,7 @@
#include "recomp_input.h"
#include "recomp_config.h"
#include "recomp_game.h"
#include "recomp_sound.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@ -191,9 +192,10 @@ void queue_samples(int16_t* audio_data, size_t sample_count) {
// Convert the audio from 16-bit values to floats and swap the audio channels into the
// swap buffer to correct for the address xor caused by endianness handling.
float cur_main_volume = recomp::get_main_volume() / 100.0f; // Get the current main volume, normalized to 0.0-1.0.
for (size_t i = 0; i < sample_count; i += input_channels) {
swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f);
swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f);
swap_buffer[i + 0 + duplicated_input_frames * input_channels] = audio_data[i + 1] * (0.5f / 32768.0f) * cur_main_volume;
swap_buffer[i + 1 + duplicated_input_frames * input_channels] = audio_data[i + 0] * (0.5f / 32768.0f) * cur_main_volume;
}
// TODO handle cases where a chunk is smaller than the duplicated frame count.

View file

@ -354,10 +354,12 @@ void recomp::set_autosave_mode(recomp::AutosaveMode mode) {
}
struct SoundOptionsContext {
std::atomic<int> main_volume; // Option to control the volume of all sound
std::atomic<int> bgm_volume;
std::atomic<int> low_health_beeps_enabled; // RmlUi doesn't seem to like "true"/"false" strings for setting variants so an int is used here instead.
void reset() {
bgm_volume = 100;
main_volume = 100;
low_health_beeps_enabled = (int)true;
}
SoundOptionsContext() {
@ -374,6 +376,17 @@ void recomp::reset_sound_settings() {
}
}
void recomp::set_main_volume(int volume) {
sound_options_context.main_volume.store(volume);
if (sound_options_model_handle) {
sound_options_model_handle.DirtyVariable("main_volume");
}
}
int recomp::get_main_volume() {
return sound_options_context.main_volume.load();
}
void recomp::set_bgm_volume(int volume) {
sound_options_context.bgm_volume.store(volume);
if (sound_options_model_handle) {
@ -852,6 +865,7 @@ public:
sound_options_model_handle = constructor.GetModelHandle();
bind_atomic(constructor, sound_options_model_handle, "main_volume", &sound_options_context.main_volume);
bind_atomic(constructor, sound_options_model_handle, "bgm_volume", &sound_options_context.bgm_volume);
bind_atomic(constructor, sound_options_model_handle, "low_health_beeps_enabled", &sound_options_context.low_health_beeps_enabled);
}

View file

@ -27,6 +27,12 @@ namespace ultramodern {
Vulkan,
OptionCount
};
enum class HighPrecisionFramebuffer {
Auto,
On,
Off,
OptionCount
};
struct GraphicsConfig {
Resolution res_option;
@ -36,6 +42,7 @@ namespace ultramodern {
RT64::UserConfiguration::AspectRatio ar_option;
RT64::UserConfiguration::Antialiasing msaa_option;
RT64::UserConfiguration::RefreshRate rr_option;
HighPrecisionFramebuffer hpfb_option;
int rr_manual_value;
int ds_option;
bool developer_mode;
@ -68,6 +75,12 @@ namespace ultramodern {
{ultramodern::GraphicsApi::D3D12, "D3D12"},
{ultramodern::GraphicsApi::Vulkan, "Vulkan"},
});
NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::HighPrecisionFramebuffer, {
{ultramodern::HighPrecisionFramebuffer::Auto, "Auto"},
{ultramodern::HighPrecisionFramebuffer::On, "On"},
{ultramodern::HighPrecisionFramebuffer::Off, "Off"},
});
};
#endif

View file

@ -279,6 +279,7 @@ ultramodern::GraphicsConfig ultramodern::get_graphics_config() {
}
std::atomic_uint32_t display_refresh_rate = 60;
std::atomic<float> resolution_scale = 1.0f;
uint32_t ultramodern::get_target_framerate(uint32_t original) {
ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config();
@ -298,6 +299,10 @@ uint32_t ultramodern::get_display_refresh_rate() {
return display_refresh_rate.load();
}
float ultramodern::get_resolution_scale() {
return resolution_scale.load();
}
void ultramodern::load_shader_cache(std::span<const char> cache_data) {
events_context.action_queue.enqueue(LoadShaderCacheAction{cache_data});
}
@ -359,6 +364,7 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re
events_context.vi.current_buffer = events_context.vi.next_buffer;
rt64.update_screen(swap_action->origin);
display_refresh_rate = rt64.get_display_framerate();
resolution_scale = rt64.get_resolution_scale();
}
else if (const auto* config_action = std::get_if<UpdateConfigAction>(&action)) {
ultramodern::GraphicsConfig new_config = cur_config;

View file

@ -11,6 +11,7 @@ ultramodern::RT64Context::~RT64Context() = default;
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
static bool sample_positions_supported = false;
static bool high_precision_fb_enabled = false;
static uint8_t DMEM[0x1000];
static uint8_t IMEM[0x1000];
@ -58,6 +59,19 @@ RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampl
return RT64::UserConfiguration::Antialiasing::None;
}
RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::HighPrecisionFramebuffer option) {
switch (option) {
case ultramodern::HighPrecisionFramebuffer::Off:
return RT64::UserConfiguration::InternalColorFormat::Standard;
case ultramodern::HighPrecisionFramebuffer::On:
return RT64::UserConfiguration::InternalColorFormat::High;
case ultramodern::HighPrecisionFramebuffer::Auto:
return RT64::UserConfiguration::InternalColorFormat::Automatic;
default:
return RT64::UserConfiguration::InternalColorFormat::OptionCount;
}
}
void set_application_user_config(RT64::Application* application, const ultramodern::GraphicsConfig& config) {
switch (config.res_option) {
default:
@ -95,6 +109,7 @@ void set_application_user_config(RT64::Application* application, const ultramode
application->userConfig.antialiasing = config.msaa_option;
application->userConfig.refreshRate = config.rr_option;
application->userConfig.refreshRateTarget = config.rr_manual_value;
application->userConfig.internalColorFormat = to_rt64(config.hpfb_option);
}
ultramodern::RT64SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
@ -216,6 +231,8 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle
device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
sample_positions_supported = false;
}
high_precision_fb_enabled = app->shaderLibrary->usesHDR;
}
void ultramodern::RT64Context::send_dl(const OSTask* task) {
@ -261,6 +278,24 @@ uint32_t ultramodern::RT64Context::get_display_framerate() {
return app->presentQueue->ext.sharedResources->swapChainRate;
}
float ultramodern::RT64Context::get_resolution_scale() {
constexpr int ReferenceHeight = 240;
switch (app->userConfig.resolution) {
case RT64::UserConfiguration::Resolution::WindowIntegerScale:
if (app->sharedQueueResources->swapChainHeight > 0) {
return std::max(float((app->sharedQueueResources->swapChainHeight + ReferenceHeight - 1) / ReferenceHeight), 1.0f);
}
else {
return 1.0f;
}
case RT64::UserConfiguration::Resolution::Manual:
return float(app->userConfig.resolutionMultiplier);
case RT64::UserConfiguration::Resolution::Original:
default:
return 1.0f;
}
}
void ultramodern::RT64Context::load_shader_cache(std::span<const char> cache_binary) {
// TODO figure out how to avoid a copy here.
std::istringstream cache_stream{std::string{cache_binary.data(), cache_binary.size()}};
@ -278,3 +313,7 @@ RT64::UserConfiguration::Antialiasing ultramodern::RT64MaxMSAA() {
bool ultramodern::RT64SamplePositionsSupported() {
return sample_positions_supported;
}
bool ultramodern::RT64HighPrecisionFBEnabled() {
return high_precision_fb_enabled;
}

View file

@ -111,9 +111,9 @@ void sleep_milliseconds(uint32_t millis);
void sleep_until(const std::chrono::high_resolution_clock::time_point& time_point);
// Graphics
void get_window_size(uint32_t& width, uint32_t& height);
uint32_t get_target_framerate(uint32_t original);
uint32_t get_display_refresh_rate();
float get_resolution_scale();
void load_shader_cache(std::span<const char> cache_data);
// Audio