diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index a30d6ef..378297f 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -152,8 +152,9 @@ set(SWA_PATCHES_CXX_SOURCES "patches/ui/CTitleStateIntro_patches.cpp" "patches/ui/CTitleStateMenu_patches.cpp" "patches/ui/frontend_listener.cpp" + "patches/aspect_ratio_patches.cpp" "patches/audio_patches.cpp" - "patches/camera_patches.cpp" + "patches/camera_patches.cpp" "patches/fps_patches.cpp" "patches/inspire_patches.cpp" "patches/misc_patches.cpp" @@ -379,7 +380,9 @@ function(compile_pixel_shader FILE_PATH) endfunction() compile_vertex_shader(copy_vs) -compile_pixel_shader(csd_filter_ps) +compile_pixel_shader(csd_filter_ps) +compile_vertex_shader(csd_no_tex_vs) +compile_vertex_shader(csd_vs) compile_pixel_shader(enhanced_motion_blur_ps) compile_pixel_shader(gaussian_blur_3x3) compile_pixel_shader(gaussian_blur_5x5) diff --git a/UnleashedRecomp/gpu/imgui/imgui_common.h b/UnleashedRecomp/gpu/imgui/imgui_common.h index 91d5b4e..861e72c 100644 --- a/UnleashedRecomp/gpu/imgui/imgui_common.h +++ b/UnleashedRecomp/gpu/imgui/imgui_common.h @@ -20,6 +20,7 @@ enum class ImGuiCallback : int32_t SetScale = -4, SetMarqueeFade = -5, SetOutline = -6, + SetProceduralOrigin = -7, // -8 is ImDrawCallback_ResetRenderState, don't use! }; @@ -58,6 +59,11 @@ union ImGuiCallbackData { float outline; } setOutline; + + struct + { + float proceduralOrigin[2]; + } setProceduralOrigin; }; extern ImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback); diff --git a/UnleashedRecomp/gpu/shader/csd_no_tex_vs.hlsl b/UnleashedRecomp/gpu/shader/csd_no_tex_vs.hlsl new file mode 100644 index 0000000..9c83bb5 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/csd_no_tex_vs.hlsl @@ -0,0 +1,67 @@ +#include "../../../tools/ShaderRecomp/ShaderRecomp/shader_common.h" + +#ifdef __spirv__ + +#define g_ViewportSize vk::RawBufferLoad(g_PushConstants.VertexShaderConstants + 2880, 0x10) +#define g_Z vk::RawBufferLoad(g_PushConstants.VertexShaderConstants + 3936, 0x10) + +#else + +cbuffer VertexShaderConstants : register(b0, space4) +{ + float4 g_ViewportSize : packoffset(c180); + float4 g_Z : packoffset(c246); +}; + +cbuffer SharedConstants : register(b2, space4) +{ + DEFINE_SHARED_CONSTANTS(); +}; + +#endif + +void main( + [[vk::location(0)]] in float4 iPosition0 : POSITION0, + [[vk::location(8)]] in float4 iColor0 : COLOR0, + out float4 oPos : SV_Position, + out float4 oTexCoord0 : TEXCOORD0, + out float4 oTexCoord1 : TEXCOORD1, + out float4 oTexCoord2 : TEXCOORD2, + out float4 oTexCoord3 : TEXCOORD3, + out float4 oTexCoord4 : TEXCOORD4, + out float4 oTexCoord5 : TEXCOORD5, + out float4 oTexCoord6 : TEXCOORD6, + out float4 oTexCoord7 : TEXCOORD7, + out float4 oTexCoord8 : TEXCOORD8, + out float4 oTexCoord9 : TEXCOORD9, + out float4 oTexCoord10 : TEXCOORD10, + out float4 oTexCoord11 : TEXCOORD11, + out float4 oTexCoord12 : TEXCOORD12, + out float4 oTexCoord13 : TEXCOORD13, + out float4 oTexCoord14 : TEXCOORD14, + out float4 oTexCoord15 : TEXCOORD15, + out float4 oColor0 : COLOR0, + out float4 oColor1 : COLOR1) +{ + oPos.xy = (iPosition0.xy - 0.5) * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0); + oPos.z = g_Z.x; + oPos.w = 1.0; + oTexCoord0 = iColor0.wxyz; + oTexCoord1 = 0.0; + oTexCoord2 = 0.0; + oTexCoord3 = 0.0; + oTexCoord4 = 0.0; + oTexCoord5 = 0.0; + oTexCoord6 = 0.0; + oTexCoord7 = 0.0; + oTexCoord8 = 0.0; + oTexCoord9 = 0.0; + oTexCoord10 = 0.0; + oTexCoord11 = 0.0; + oTexCoord12 = 0.0; + oTexCoord13 = 0.0; + oTexCoord14 = 0.0; + oTexCoord15 = 0.0; + oColor0 = 0.0; + oColor1 = 0.0; +} diff --git a/UnleashedRecomp/gpu/shader/csd_vs.hlsl b/UnleashedRecomp/gpu/shader/csd_vs.hlsl new file mode 100644 index 0000000..c9862fb --- /dev/null +++ b/UnleashedRecomp/gpu/shader/csd_vs.hlsl @@ -0,0 +1,69 @@ +#include "../../../tools/ShaderRecomp/ShaderRecomp/shader_common.h" + +#ifdef __spirv__ + +#define g_ViewportSize vk::RawBufferLoad(g_PushConstants.VertexShaderConstants + 2880, 0x10) +#define g_Z vk::RawBufferLoad(g_PushConstants.VertexShaderConstants + 3936, 0x10) + +#else + +cbuffer VertexShaderConstants : register(b0, space4) +{ + float4 g_ViewportSize : packoffset(c180); + float4 g_Z : packoffset(c246); +}; + +cbuffer SharedConstants : register(b2, space4) +{ + DEFINE_SHARED_CONSTANTS(); +}; + +#endif + +void main( + [[vk::location(0)]] in float4 iPosition0 : POSITION0, + [[vk::location(8)]] in float4 iColor0 : COLOR0, + [[vk::location(4)]] in float4 iTexCoord0 : TEXCOORD0, + out float4 oPos : SV_Position, + out float4 oTexCoord0 : TEXCOORD0, + out float4 oTexCoord1 : TEXCOORD1, + out float4 oTexCoord2 : TEXCOORD2, + out float4 oTexCoord3 : TEXCOORD3, + out float4 oTexCoord4 : TEXCOORD4, + out float4 oTexCoord5 : TEXCOORD5, + out float4 oTexCoord6 : TEXCOORD6, + out float4 oTexCoord7 : TEXCOORD7, + out float4 oTexCoord8 : TEXCOORD8, + out float4 oTexCoord9 : TEXCOORD9, + out float4 oTexCoord10 : TEXCOORD10, + out float4 oTexCoord11 : TEXCOORD11, + out float4 oTexCoord12 : TEXCOORD12, + out float4 oTexCoord13 : TEXCOORD13, + out float4 oTexCoord14 : TEXCOORD14, + out float4 oTexCoord15 : TEXCOORD15, + out float4 oColor0 : COLOR0, + out float4 oColor1 : COLOR1) +{ + oPos.xy = (iPosition0.xy - 0.5) * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0); + oPos.z = g_Z.x; + oPos.w = 1.0; + oTexCoord0 = iColor0.wxyz; + oTexCoord1.xy = iTexCoord0.xy; + oTexCoord1.zw = 0.0; + oTexCoord2 = 0.0; + oTexCoord3 = 0.0; + oTexCoord4 = 0.0; + oTexCoord5 = 0.0; + oTexCoord6 = 0.0; + oTexCoord7 = 0.0; + oTexCoord8 = 0.0; + oTexCoord9 = 0.0; + oTexCoord10 = 0.0; + oTexCoord11 = 0.0; + oTexCoord12 = 0.0; + oTexCoord13 = 0.0; + oTexCoord14 = 0.0; + oTexCoord15 = 0.0; + oColor0 = 0.0; + oColor1 = 0.0; +} diff --git a/UnleashedRecomp/gpu/shader/gamma_correction_ps.hlsl b/UnleashedRecomp/gpu/shader/gamma_correction_ps.hlsl index 48f774f..cf4be92 100644 --- a/UnleashedRecomp/gpu/shader/gamma_correction_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/gamma_correction_ps.hlsl @@ -5,12 +5,17 @@ #define g_Gamma vk::RawBufferLoad(g_PushConstants.SharedConstants + 0) #define g_TextureDescriptorIndex vk::RawBufferLoad(g_PushConstants.SharedConstants + 12) +#define g_ViewportOffset vk::RawBufferLoad(g_PushConstants.SharedConstants + 16) +#define g_ViewportSize vk::RawBufferLoad(g_PushConstants.SharedConstants + 24) + #else cbuffer SharedConstants : register(b2, space4) { - float3 g_Gamma : packoffset(c0.x); - uint g_TextureDescriptorIndex : packoffset(c0.w); + float3 g_Gamma; + uint g_TextureDescriptorIndex; + int2 g_ViewportOffset; + int2 g_ViewportSize; }; #endif @@ -18,7 +23,12 @@ cbuffer SharedConstants : register(b2, space4) float4 main(in float4 position : SV_Position) : SV_Target { Texture2D texture = g_Texture2DDescriptorHeap[g_TextureDescriptorIndex]; - float4 color = texture.Load(int3(position.xy, 0)); + + int2 movedPosition = int2(position.xy) - g_ViewportOffset; + bool boxed = any(movedPosition < 0) || any(movedPosition >= g_ViewportSize); + if (boxed) movedPosition = 0; + + float4 color = boxed ? 0.0 : texture.Load(int3(movedPosition, 0)); color.rgb = pow(color.rgb, g_Gamma); return color; } diff --git a/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli b/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli index a10a7be..20f9955 100644 --- a/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli +++ b/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli @@ -45,7 +45,11 @@ float4 main(in float4 iPosition : SV_Position, in float4 iTexCoord0 : TEXCOORD0) Texture2D texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex]; SamplerState samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex]; - float scale = g_ViewportSize.y / 360.0; + float scale; + if ((g_ViewportSize.x * g_ViewportSize.w) >= (16.0 / 9.0)) + scale = g_ViewportSize.y / 360.0; + else + scale = g_ViewportSize.x / 640.0; float2 offsets[3]; offsets[0] = g_offsets(0).xy * scale; diff --git a/UnleashedRecomp/gpu/shader/imgui_common.hlsli b/UnleashedRecomp/gpu/shader/imgui_common.hlsli index dbc812e..9f96d55 100644 --- a/UnleashedRecomp/gpu/shader/imgui_common.hlsli +++ b/UnleashedRecomp/gpu/shader/imgui_common.hlsli @@ -13,6 +13,7 @@ struct PushConstants float2 InverseDisplaySize; float2 Origin; float2 Scale; + float2 ProceduralOrigin; float Outline; }; diff --git a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl index 18928e0..c105258 100644 --- a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl @@ -65,10 +65,10 @@ float4 PixelAntialiasing(float2 uvTexspace) uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam; uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5); - if (g_PushConstants.InverseDisplaySize.x < g_PushConstants.InverseDisplaySize.y) - uvTexspace *= min(1.0, g_PushConstants.InverseDisplaySize.y * 720.0f); + if ((g_PushConstants.InverseDisplaySize.y / g_PushConstants.InverseDisplaySize.x) >= (4.0 / 3.0)) + uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0f; else - uvTexspace *= min(1.0, g_PushConstants.InverseDisplaySize.x * 1280.0f); + uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0f; return SampleLinear(uvTexspace); } @@ -81,7 +81,7 @@ float median(float r, float g, float b) float4 main(in Interpolators interpolators) : SV_Target { float4 color = interpolators.Color; - color *= PixelAntialiasing(interpolators.Position.xy - 0.5); + color *= PixelAntialiasing(interpolators.Position.xy - (g_PushConstants.ProceduralOrigin + 0.5)); if (g_PushConstants.Texture2DDescriptorIndex != 0) { diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 45dad00..64f0d94 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,8 @@ #ifdef SWA_D3D12 #include "shader/copy_vs.hlsl.dxil.h" #include "shader/csd_filter_ps.hlsl.dxil.h" +#include "shader/csd_no_tex_vs.hlsl.dxil.h" +#include "shader/csd_vs.hlsl.dxil.h" #include "shader/enhanced_motion_blur_ps.hlsl.dxil.h" #include "shader/gamma_correction_ps.hlsl.dxil.h" #include "shader/gaussian_blur_3x3.hlsl.dxil.h" @@ -55,6 +58,8 @@ #include "shader/copy_vs.hlsl.spirv.h" #include "shader/csd_filter_ps.hlsl.spirv.h" +#include "shader/csd_no_tex_vs.hlsl.spirv.h" +#include "shader/csd_vs.hlsl.spirv.h" #include "shader/enhanced_motion_blur_ps.hlsl.spirv.h" #include "shader/gamma_correction_ps.hlsl.spirv.h" #include "shader/gaussian_blur_3x3.hlsl.spirv.h" @@ -1163,6 +1168,7 @@ struct ImGuiPushConstants ImVec2 inverseDisplaySize{}; ImVec2 origin{ 0.0f, 0.0f }; ImVec2 scale{ 1.0f, 1.0f }; + ImVec2 proceduralOrigin{ 0.0f, 0.0f }; float outline{}; }; @@ -1345,6 +1351,20 @@ static void CheckSwapChain() if (g_swapChainValid) g_swapChainValid = g_swapChain->acquireTexture(g_acquireSemaphores[g_frame].get(), &g_backBufferIndex); + + if (g_needsResize) + Video::ComputeViewportDimensions(); + + if (g_aspectRatio >= NARROW_ASPECT_RATIO) + { + g_backBuffer->width = Video::s_viewportWidth * 720 / Video::s_viewportHeight; + g_backBuffer->height = 720; + } + else + { + g_backBuffer->width = 960; + g_backBuffer->height = Video::s_viewportHeight * 960 / Video::s_viewportWidth; + } } static void BeginCommandList() @@ -1358,13 +1378,14 @@ static void BeginCommandList() if (g_swapChainValid) { - bool applyingGammaCorrection = Config::XboxColorCorrection || abs(Config::Brightness - 0.5f) > 0.001f; + uint32_t width = Video::s_viewportWidth; + uint32_t height = Video::s_viewportHeight; - if (applyingGammaCorrection) + bool usingIntermediaryTexture = (width != g_swapChain->getWidth()) || (height != g_swapChain->getHeight()) || + Config::XboxColorCorrection || (abs(Config::Brightness - 0.5f) > 0.001f); + + if (usingIntermediaryTexture) { - uint32_t width = g_swapChain->getWidth(); - uint32_t height = g_swapChain->getHeight(); - if (g_intermediaryBackBufferTextureWidth != width || g_intermediaryBackBufferTextureHeight != height) { @@ -1647,6 +1668,7 @@ void Video::CreateHostDevice(const char *sdlVideoDriver) g_backBuffer->format = BACKBUFFER_FORMAT; g_backBuffer->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(1, 1, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET)); + Video::ComputeViewportDimensions(); CheckSwapChain(); BeginCommandList(); @@ -2044,6 +2066,8 @@ static void DrawImGui() ImGui::End(); #endif + ImGui::GetIO().DisplaySize = { float(Video::s_viewportWidth), float(Video::s_viewportHeight) }; + AchievementMenu::Draw(); OptionsMenu::Draw(); AchievementOverlay::Draw(); @@ -2148,6 +2172,9 @@ static void ProcDrawImGui(const RenderCommand& cmd) case ImGuiCallback::SetOutline: setPushConstants(&pushConstants.outline, &callbackData->setOutline, sizeof(callbackData->setOutline)); break; + case ImGuiCallback::SetProceduralOrigin: + setPushConstants(&pushConstants.proceduralOrigin, &callbackData->setProceduralOrigin, sizeof(callbackData->setProceduralOrigin)); + break; default: assert(false && "Unknown ImGui callback type."); break; @@ -2325,6 +2352,11 @@ static void ProcExecuteCommandList(const RenderCommand& cmd) float gammaG; float gammaB; uint32_t textureDescriptorIndex; + + int32_t viewportOffsetX; + int32_t viewportOffsetY; + int32_t viewportWidth; + int32_t viewportHeight; } constants; if (Config::XboxColorCorrection) @@ -2347,6 +2379,11 @@ static void ProcExecuteCommandList(const RenderCommand& cmd) constants.gammaB = 1.0f / std::clamp(constants.gammaB + offset, 0.1f, 4.0f); constants.textureDescriptorIndex = g_intermediaryBackBufferTextureDescriptorIndex; + constants.viewportOffsetX = (int32_t(g_swapChain->getWidth()) - int32_t(Video::s_viewportWidth)) / 2; + constants.viewportOffsetY = (int32_t(g_swapChain->getHeight()) - int32_t(Video::s_viewportHeight)) / 2; + constants.viewportWidth = Video::s_viewportWidth; + constants.viewportHeight = Video::s_viewportHeight; + auto &framebuffer = g_backBuffer->framebuffers[swapChainTexture]; if (!framebuffer) { @@ -2369,8 +2406,8 @@ static void ProcExecuteCommandList(const RenderCommand& cmd) commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 0); SetRootDescriptor(g_uploadAllocators[g_frame].allocate(&constants, sizeof(constants), 0x100), 2); commandList->setFramebuffer(framebuffer.get()); - commandList->setViewports(RenderViewport(0.0f, 0.0f, g_intermediaryBackBufferTextureWidth, g_intermediaryBackBufferTextureHeight)); - commandList->setScissors(RenderRect(0, 0, g_intermediaryBackBufferTextureWidth, g_intermediaryBackBufferTextureHeight)); + commandList->setViewports(RenderViewport(0.0f, 0.0f, g_swapChain->getWidth(), g_swapChain->getHeight())); + commandList->setScissors(RenderRect(0, 0, g_swapChain->getWidth(), g_swapChain->getHeight())); commandList->drawInstanced(6, 1, 0, 0); commandList->barriers(RenderBarrierStage::GRAPHICS, RenderTextureBarrier(swapChainTexture, RenderTextureLayout::PRESENT)); } @@ -2419,6 +2456,61 @@ static GuestSurface* GetBackBuffer() return g_backBuffer; } +GuestSurface* Video::GetBackBuffer() +{ + return g_backBuffer; +} + +void Video::ComputeViewportDimensions() +{ + uint32_t width = g_swapChain->getWidth(); + uint32_t height = g_swapChain->getHeight(); + float aspectRatio = float(width) / float(height); + + switch (Config::AspectRatio) + { + case EAspectRatio::Wide: + { + if (aspectRatio > WIDE_ASPECT_RATIO) + { + s_viewportWidth = height * 16 / 9; + s_viewportHeight = height; + } + else + { + s_viewportWidth = width; + s_viewportHeight = width * 9 / 16; + } + + break; + } + + case EAspectRatio::Narrow: + case EAspectRatio::OriginalNarrow: + { + if (aspectRatio > NARROW_ASPECT_RATIO) + { + s_viewportWidth = height * 4 / 3; + s_viewportHeight = height; + } + else + { + s_viewportWidth = width; + s_viewportHeight = width * 3 / 4; + } + + break; + } + + default: + s_viewportWidth = width; + s_viewportHeight = height; + break; + } + + AspectRatioPatches::ComputeOffsets(); +} + static RenderFormat ConvertFormat(uint32_t format) { switch (format) @@ -2587,13 +2679,13 @@ static void FlushViewport() if (renderingToBackBuffer) { - uint32_t width = g_swapChain->getWidth(); - uint32_t height = g_swapChain->getHeight(); + float width = Video::s_viewportWidth; + float height = Video::s_viewportHeight; - viewport.x *= width / 1280.0f; - viewport.y *= height / 720.0f; - viewport.width *= width / 1280.0f; - viewport.height *= height / 720.0f; + viewport.x *= width / g_backBuffer->width; + viewport.y *= height / g_backBuffer->height; + viewport.width *= width / g_backBuffer->width; + viewport.height *= height / g_backBuffer->height; } if (viewport.minDepth > viewport.maxDepth) @@ -2614,13 +2706,13 @@ static void FlushViewport() if (renderingToBackBuffer) { - uint32_t width = g_swapChain->getWidth(); - uint32_t height = g_swapChain->getHeight(); + uint32_t width = Video::s_viewportWidth; + uint32_t height = Video::s_viewportHeight; - scissorRect.left = scissorRect.left * width / 1280; - scissorRect.top = scissorRect.top * height / 720; - scissorRect.right = scissorRect.right * width / 1280; - scissorRect.bottom = scissorRect.bottom * height / 720; + scissorRect.left = scissorRect.left * width / g_backBuffer->width; + scissorRect.top = scissorRect.top * height / g_backBuffer->height; + scissorRect.right = scissorRect.right * width / g_backBuffer->width; + scissorRect.bottom = scissorRect.bottom * height / g_backBuffer->height; } commandList->setScissors(scissorRect); @@ -4160,7 +4252,13 @@ static GuestShader* CreateShader(const be* function, ResourceType reso if (findResult->guestShader == nullptr) { shader = g_userHeap.AllocPhysical(resourceType); - shader->shaderCacheEntry = findResult; + + if (hash == 0xB1086A4947A797DE) + shader->shader = CREATE_SHADER(csd_no_tex_vs); + else if (hash == 0xB4CAFC034A37C8A8) + shader->shader = CREATE_SHADER(csd_vs); + else + shader->shaderCacheEntry = findResult; findResult->guestShader = shader; } @@ -4289,7 +4387,7 @@ static void ProcSetPixelShader(const RenderCommand& cmd) default: { - size_t height = round(g_swapChain->getHeight() * Config::ResolutionScale); + size_t height = round(Video::s_viewportHeight * Config::ResolutionScale); if (height > 1440) shaderIndex = GAUSSIAN_BLUR_9X9; @@ -4719,6 +4817,8 @@ static bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataS texture.descriptorIndex = g_textureDescriptorAllocator.allocate(); g_textureDescriptorSet->setTexture(texture.descriptorIndex, texture.texture, RenderTextureLayout::SHADER_READ, texture.textureView.get()); + texture.width = ddsDesc.width; + texture.height = ddsDesc.height; texture.viewDimension = viewDesc.dimension; struct Slice @@ -4929,8 +5029,10 @@ void SetShadowResolutionMidAsmHook(PPCRegister& r11) static void SetResolution(be* device) { - uint32_t width = uint32_t(round(g_swapChain->getWidth() * Config::ResolutionScale)); - uint32_t height = uint32_t(round(g_swapChain->getHeight() * Config::ResolutionScale)); + Video::ComputeViewportDimensions(); + + uint32_t width = uint32_t(round(Video::s_viewportWidth * Config::ResolutionScale)); + uint32_t height = uint32_t(round(Video::s_viewportHeight * Config::ResolutionScale)); device[46] = width == 0 ? 880 : width; device[47] = height == 0 ? 720 : height; } @@ -6409,10 +6511,14 @@ void VideoConfigValueChangedCallback(IConfigDef* config) { // Config options that require internal resolution resize g_needsResize |= + config == &Config::AspectRatio || config == &Config::ResolutionScale || config == &Config::AntiAliasing || config == &Config::ShadowResolution; + if (g_needsResize) + Video::ComputeViewportDimensions(); + // Config options that require pipeline recompilation bool shouldRecompile = config == &Config::AntiAliasing || diff --git a/UnleashedRecomp/gpu/video.h b/UnleashedRecomp/gpu/video.h index d1472f9..9c6a6bc 100644 --- a/UnleashedRecomp/gpu/video.h +++ b/UnleashedRecomp/gpu/video.h @@ -15,11 +15,16 @@ using namespace plume; struct Video { + static inline uint32_t s_viewportWidth; + static inline uint32_t s_viewportHeight; + static void CreateHostDevice(const char *sdlVideoDriver); static void WaitOnSwapChain(); static void Present(); static void StartPipelinePrecompilation(); static void WaitForGPU(); + static struct GuestSurface* GetBackBuffer(); + static void ComputeViewportDimensions(); }; struct GuestSamplerState diff --git a/UnleashedRecomp/locale/config_locale.cpp b/UnleashedRecomp/locale/config_locale.cpp index 2815443..cf0d4db 100644 --- a/UnleashedRecomp/locale/config_locale.cpp +++ b/UnleashedRecomp/locale/config_locale.cpp @@ -61,7 +61,7 @@ CONFIG_DEFINE_LOCALE(ControlTutorial) CONFIG_DEFINE_LOCALE(AchievementNotifications) { - { ELanguage::English, { "Achievement Notifications", "Show notifications for unlocking achievements.\n\nAchievements will still\nbe rewarded with notifications disabled." } } + { ELanguage::English, { "Achievement Notifications", "Show notifications for unlocking achievements.\n\nAchievements will still be rewarded with notifications disabled." } } }; CONFIG_DEFINE_LOCALE(TimeOfDayTransition) @@ -90,7 +90,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EControllerIcons) { ELanguage::English, { - { EControllerIcons::Auto, { "AUTO", "Auto: the game will determine which icons\nto use based on the current input device." } }, + { EControllerIcons::Auto, { "AUTO", "Auto: the game will determine which icons to use based on the current input device." } }, { EControllerIcons::Xbox, { "XBOX", "" } }, { EControllerIcons::PlayStation, { "PLAYSTATION", "" } } } @@ -188,7 +188,8 @@ CONFIG_DEFINE_ENUM_LOCALE(EAspectRatio) { ELanguage::English, { - { EAspectRatio::Auto, { "AUTO", "Auto: the aspect ratio will dynamically adjust to the window size." } } + { EAspectRatio::Auto, { "AUTO", "Auto: the aspect ratio will dynamically adjust to the window size." } }, + { EAspectRatio::OriginalNarrow, { "ORIGINAL 4:3", "" } } } } }; @@ -205,7 +206,7 @@ CONFIG_DEFINE_LOCALE(Fullscreen) CONFIG_DEFINE_LOCALE(VSync) { - { ELanguage::English, { "V-Sync", "Synchronize the game\nto the refresh rate of\nthe display to prevent screen tearing." } } + { ELanguage::English, { "V-Sync", "Synchronize the game to the refresh rate of the display to prevent screen tearing." } } }; CONFIG_DEFINE_LOCALE(FPS) @@ -283,7 +284,7 @@ CONFIG_DEFINE_ENUM_LOCALE(EMotionBlur) { { EMotionBlur::Off, { "OFF", "" } }, { EMotionBlur::Original, { "ORIGINAL", "" } }, - { EMotionBlur::Enhanced, { "ENHANCED", "Enhanced: uses more samples for smoother motion blur at the cost\nof performance." } } + { EMotionBlur::Enhanced, { "ENHANCED", "Enhanced: uses more samples for smoother motion blur at the cost of performance." } } } } }; @@ -293,6 +294,22 @@ CONFIG_DEFINE_LOCALE(XboxColorCorrection) { ELanguage::English, { "Xbox Color Correction", "Use the warm tint from the Xbox version of the game." } } }; +CONFIG_DEFINE_LOCALE(CutsceneAspectRatio) +{ + { ELanguage::English, { "Cutscene Aspect Ratio", "" } } +}; + +CONFIG_DEFINE_ENUM_LOCALE(ECutsceneAspectRatio) +{ + { + ELanguage::English, + { + { ECutsceneAspectRatio::Original, { "ORIGINAL", "" } }, + { ECutsceneAspectRatio::Unlocked, { "UNLOCKED", "" } }, + } + } +}; + CONFIG_DEFINE_LOCALE(UIScaleMode) { { ELanguage::English, { "UI Scale Mode", "Change how the UI scales to the display." } } @@ -303,7 +320,6 @@ CONFIG_DEFINE_ENUM_LOCALE(EUIScaleMode) { ELanguage::English, { - { EUIScaleMode::Stretch, { "STRETCH", "Stretch: the UI will stretch to the display." } }, { EUIScaleMode::Edge, { "EDGE", "Edge: the UI will anchor to the edges of the display." } }, { EUIScaleMode::Centre, { "CENTER", "Center: the UI will anchor to the center of the display." } }, } diff --git a/UnleashedRecomp/patches/aspect_ratio_patches.cpp b/UnleashedRecomp/patches/aspect_ratio_patches.cpp new file mode 100644 index 0000000..68af1df --- /dev/null +++ b/UnleashedRecomp/patches/aspect_ratio_patches.cpp @@ -0,0 +1,1165 @@ +#include +#include +#include +#include +#include + +#include "aspect_ratio_patches.h" +#include "camera_patches.h" + +// These are here for now to not recompile basically all of the project. +namespace Chao::CSD +{ + struct Cast + { + SWA_INSERT_PADDING(0x144); + }; + + struct CastLink + { + be ChildCastIndex; + be SiblingCastIndex; + }; + + struct CastNode + { + be CastCount; + xpointer> pCasts; + be RootCastIndex; + xpointer pCastLinks; + }; + + struct CastIndex + { + xpointer pCastName; + be CastNodeIndex; + be CastIndex; + }; + + struct Scene + { + SWA_INSERT_PADDING(0x24); + be CastNodeCount; + xpointer pCastNodes; + be CastCount; + xpointer pCastIndices; + }; + + struct SceneIndex + { + xpointer pSceneName; + be SceneIndex; + }; + + struct SceneNodeIndex + { + xpointer pSceneNodeName; + be SceneNodeIndex; + }; + + struct SceneNode + { + be SceneCount; + xpointer> pScenes; + xpointer pSceneIndices; + be SceneNodeCount; + xpointer pSceneNodes; + xpointer pSceneNodeIndices; + }; + + struct Project + { + xpointer pRootNode; + }; +} + +static Mutex g_pathMutex; +static std::map g_paths; + +static XXH64_hash_t HashStr(const std::string_view& value) +{ + return XXH3_64bits(value.data(), value.size()); +} + +static void EmplacePath(const void* key, const std::string_view& value) +{ + std::lock_guard lock(g_pathMutex); + g_paths.emplace(key, HashStr(value)); +} + +static void TraverseCast(Chao::CSD::Scene* scene, uint32_t castNodeIndex, Chao::CSD::CastNode* castNode, uint32_t castIndex, const std::string& parentPath) +{ + if (castIndex == ~0) + return; + + TraverseCast(scene, castNodeIndex, castNode, castNode->pCastLinks[castIndex].SiblingCastIndex, parentPath); + + std::string path = parentPath; + + for (size_t i = 0; i < scene->CastCount; i++) + { + auto& index = scene->pCastIndices[i]; + if (index.CastNodeIndex == castNodeIndex && index.CastIndex == castIndex) + { + path += index.pCastName; + break; + } + } + + EmplacePath(castNode->pCasts[castIndex].get(), path); + + if (castNode->RootCastIndex == castIndex) + EmplacePath(castNode, path); + + path += "/"; + + TraverseCast(scene, castNodeIndex, castNode, castNode->pCastLinks[castIndex].ChildCastIndex, path); +} + +static void TraverseScene(Chao::CSD::Scene* scene, std::string path) +{ + EmplacePath(scene, path); + path += "/"; + + for (size_t i = 0; i < scene->CastNodeCount; i++) + { + auto& castNode = scene->pCastNodes[i]; + TraverseCast(scene, i, &castNode, castNode.RootCastIndex, path); + } +} + +static void TraverseSceneNode(Chao::CSD::SceneNode* sceneNode, std::string path) +{ + EmplacePath(sceneNode, path); + path += "/"; + + for (size_t i = 0; i < sceneNode->SceneCount; i++) + { + auto& sceneIndex = sceneNode->pSceneIndices[i]; + TraverseScene(sceneNode->pScenes[sceneIndex.SceneIndex], path + sceneIndex.pSceneName.get()); + } + + for (size_t i = 0; i < sceneNode->SceneNodeCount; i++) + { + auto& sceneNodeIndex = sceneNode->pSceneNodeIndices[i]; + TraverseSceneNode(&sceneNode->pSceneNodes[sceneNodeIndex.SceneNodeIndex], path + sceneNodeIndex.pSceneNodeName.get()); + } +} + +void MakeCsdProjectMidAsmHook(PPCRegister& r3, PPCRegister& r29) +{ + uint8_t* base = g_memory.base; + auto csdProject = reinterpret_cast(base + PPC_LOAD_U32(PPC_LOAD_U32(r3.u32 + 16) + 4)); + auto name = reinterpret_cast(base + PPC_LOAD_U32(r29.u32)); + TraverseSceneNode(csdProject->m_pResource->pRootNode, name); +} + +// Chao::CSD::CMemoryAlloc::Free +PPC_FUNC_IMPL(__imp__sub_825E2E60); +PPC_FUNC(sub_825E2E60) +{ + if (ctx.r4.u32 != NULL && PPC_LOAD_U32(ctx.r4.u32) == 0x4E594946 && PPC_LOAD_U32(ctx.r4.u32 + 0x20) == 0x6E43504A) // NYIF, nCPJ + { + uint32_t fileSize = PPC_LOAD_U32(ctx.r4.u32 + 0x14); + + std::lock_guard lock(g_pathMutex); + const uint8_t* key = base + ctx.r4.u32; + + auto lower = g_paths.lower_bound(key); + auto upper = g_paths.lower_bound(key + fileSize); + + g_paths.erase(lower, upper); + } + + __imp__sub_825E2E60(ctx, base); +} + +static float ComputeScale(float aspectRatio) +{ + return ((aspectRatio * 720.0f) / 1280.0f) / sqrt((aspectRatio * 720.0f) / 1280.0f); +} + +void AspectRatioPatches::ComputeOffsets() +{ + float width = Video::s_viewportWidth; + float height = Video::s_viewportHeight; + + g_aspectRatio = width / height; + g_aspectRatioScale = 1.0f; + + if (g_aspectRatio >= NARROW_ASPECT_RATIO) + { + // height is locked to 720 in this case + g_aspectRatioOffsetX = 0.5f * (g_aspectRatio * 720.0f - 1280.0f); + g_aspectRatioOffsetY = 0.0f; + + // keep same scale above Steam Deck aspect ratio + if (g_aspectRatio < WIDE_ASPECT_RATIO) + { + // interpolate to original 4:3 scale + float steamDeckScale = g_aspectRatio / WIDE_ASPECT_RATIO; + float narrowScale = ComputeScale(NARROW_ASPECT_RATIO); + + float lerpFactor = std::clamp((g_aspectRatio - NARROW_ASPECT_RATIO) / (STEAM_DECK_ASPECT_RATIO - NARROW_ASPECT_RATIO), 0.0f, 1.0f); + g_aspectRatioScale = narrowScale + (steamDeckScale - narrowScale) * lerpFactor; + } + } + else + { + // width is locked to 960 in this case to have 4:3 crop + g_aspectRatioOffsetX = 0.5f * (960.0f - 1280.0f); + g_aspectRatioOffsetY = 0.5f * (960.0f / g_aspectRatio - 720.0f); + g_aspectRatioScale = ComputeScale(NARROW_ASPECT_RATIO); + } + + g_narrowOffsetScale = std::clamp((g_aspectRatio - NARROW_ASPECT_RATIO) / (WIDE_ASPECT_RATIO - NARROW_ASPECT_RATIO), 0.0f, 1.0f); +} + +// SWA::CGameDocument::ComputeScreenPosition +PPC_FUNC_IMPL(__imp__sub_8250FC70); +PPC_FUNC(sub_8250FC70) +{ + __imp__sub_8250FC70(ctx, base); + + auto position = reinterpret_cast*>(base + ctx.r3.u32); + position[0] = position[0] - g_aspectRatioOffsetX; + position[1] = position[1] - g_aspectRatioOffsetY; +} + +void ComputeScreenPositionMidAsmHook(PPCRegister& f1, PPCRegister& f2) +{ + f1.f64 -= g_aspectRatioOffsetX; + f2.f64 -= g_aspectRatioOffsetY; +} + +void WorldMapInfoMidAsmHook(PPCRegister& r4) +{ + // Prevent the game from snapping "cts_parts_sun_moon" + // to "cts_guide_icon" automatically, we will do this ourselves. + r4.u32 = 0x8200A621; +} + +// SWA::CTitleStateWorldMap::Update +PPC_FUNC_IMPL(__imp__sub_8258B558); +PPC_FUNC(sub_8258B558) +{ + auto r3 = ctx.r3; + __imp__sub_8258B558(ctx, base); + + uint32_t worldMapSimpleInfo = PPC_LOAD_U32(r3.u32 + 0x70); + if (worldMapSimpleInfo != NULL) + { + auto setPosition = [&](uint32_t rcPtr, float offsetX = 0.0f, float offsetY = 0.0f) + { + uint32_t scene = PPC_LOAD_U32(rcPtr + 0x4); + if (scene != NULL) + { + scene = PPC_LOAD_U32(scene + 0x4); + if (scene != NULL) + { + ctx.r3.u32 = scene; + ctx.f1.f64 = offsetX + g_narrowOffsetScale * 140.0f; + ctx.f2.f64 = offsetY; + + if (Config::UIScaleMode == EUIScaleMode::Edge && g_narrowOffsetScale >= 1.0f) + ctx.f1.f64 += g_aspectRatioOffsetX; + + sub_830BB3D0(ctx, base); + } + } + }; + + setPosition(worldMapSimpleInfo + 0x2C, 299.0f, -178.0f); + setPosition(worldMapSimpleInfo + 0x34); + setPosition(worldMapSimpleInfo + 0x4C); + + for (uint32_t it = PPC_LOAD_U32(worldMapSimpleInfo + 0x20); it != PPC_LOAD_U32(worldMapSimpleInfo + 0x24); it += 8) + setPosition(it); + + uint32_t menuTextBox = PPC_LOAD_U32(worldMapSimpleInfo + 0x5C); + if (menuTextBox != NULL) + { + uint32_t textBox = PPC_LOAD_U32(menuTextBox + 0x4); + if (textBox != NULL) + { + float value = 708.0f + g_narrowOffsetScale * 140.0f; + if (Config::UIScaleMode == EUIScaleMode::Edge && g_narrowOffsetScale >= 1.0f) + value += g_aspectRatioOffsetX; + + PPC_STORE_U32(textBox + 0x38, reinterpret_cast(value)); + } + } + } +} + +enum +{ + ALIGN_CENTER = 0 << 0, + + ALIGN_TOP = 1 << 0, + ALIGN_LEFT = 1 << 1, + ALIGN_BOTTOM = 1 << 2, + ALIGN_RIGHT = 1 << 3, + + ALIGN_TOP_LEFT = ALIGN_TOP | ALIGN_LEFT, + ALIGN_TOP_RIGHT = ALIGN_TOP | ALIGN_RIGHT, + ALIGN_BOTTOM_LEFT = ALIGN_BOTTOM | ALIGN_LEFT, + ALIGN_BOTTOM_RIGHT = ALIGN_BOTTOM | ALIGN_RIGHT, + + STRETCH_HORIZONTAL = 1 << 4, + STRETCH_VERTICAL = 1 << 5, + + STRETCH = STRETCH_HORIZONTAL | STRETCH_VERTICAL, + + SCALE = 1 << 6, + + WORLD_MAP = 1 << 7, + + EXTEND_LEFT = 1 << 8, + EXTEND_RIGHT = 1 << 9, + + STORE_LEFT_CORNER = 1 << 10, + STORE_RIGHT_CORNER = 1 << 11, + + SKIP = 1 << 12, + + OFFSET_SCALE_LEFT = 1 << 13, + OFFSET_SCALE_RIGHT = 1 << 14, +}; + +struct CsdModifier +{ + uint32_t flags{}; + float cornerMax{}; + uint32_t cornerIndex{}; +}; + +static const ankerl::unordered_dense::map g_modifiers = +{ + // ui_balloon + { HashStr("ui_balloon/window/bg"), { STRETCH } }, + { HashStr("ui_balloon/window/footer"), { ALIGN_BOTTOM } }, + + // ui_boss_gauge + { HashStr("ui_boss_gauge/gauge_bg"), { ALIGN_TOP_RIGHT | SCALE } }, + { HashStr("ui_boss_gauge/gauge_2"), { ALIGN_TOP_RIGHT | SCALE } }, + { HashStr("ui_boss_gauge/gauge_1"), { ALIGN_TOP_RIGHT | SCALE } }, + { HashStr("ui_boss_gauge/gauge_breakpoint"), { ALIGN_TOP_RIGHT | SCALE } }, + + // ui_exstage + { HashStr("ui_exstage/shield/L_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_exstage/shield/L_gauge_effect"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_exstage/shield/L_gauge_effect_2"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_exstage/energy/R_gauge"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_exstage/energy/R_gauge_effect"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_exstage/energy/R_gauge_effect_2"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_exstage/hit/hit_counter_bg"), { ALIGN_RIGHT | SCALE } }, + { HashStr("ui_exstage/hit/hit_counter_num"), { ALIGN_RIGHT | SCALE } }, + + // ui_gate + { HashStr("ui_gate/footer/status_footer"), { ALIGN_BOTTOM } }, + { HashStr("ui_gate/header/status_title"), { ALIGN_TOP | OFFSET_SCALE_LEFT, 652.0f } }, + { HashStr("ui_gate/header/status_title/title_bg/center"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_gate/header/status_title/title_bg/center/h_light"), { ALIGN_TOP | EXTEND_LEFT} }, + { HashStr("ui_gate/header/status_title/title_bg/right"), { ALIGN_TOP | STORE_RIGHT_CORNER } }, + { HashStr("ui_gate/window/window_bg"), { STRETCH } }, + + // ui_general + { HashStr("ui_general/bg"), { STRETCH } }, + { HashStr("ui_general/footer"), { ALIGN_BOTTOM } }, + + // ui_itemresult + { HashStr("ui_itemresult/footer/result_footer"), { ALIGN_BOTTOM } }, + { HashStr("ui_itemresult/main/iresult_title"), { ALIGN_TOP } }, + { HashStr("ui_itemresult/main/iresult_title"), { ALIGN_TOP | OFFSET_SCALE_LEFT, 688.0f } }, + { HashStr("ui_itemresult/main/iresult_title/title_bg/center"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_itemresult/main/iresult_title/title_bg/center/h_light"), { ALIGN_TOP | EXTEND_LEFT} }, + { HashStr("ui_itemresult/main/iresult_title/title_bg/right"), { ALIGN_TOP | STORE_RIGHT_CORNER } }, + + // ui_loading + { HashStr("ui_loading/bg_1"), { STRETCH } }, + { HashStr("ui_loading/bg_2"), { STRETCH } }, + + // ui_missionscreen + { HashStr("ui_missionscreen/player_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_missionscreen/time_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_missionscreen/score_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_missionscreen/item_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_missionscreen/laptime_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_missionscreen/lap_count"), { ALIGN_TOP_RIGHT | SCALE } }, + + // ui_misson + { HashStr("ui_misson/bg"), { STRETCH } }, + { HashStr("ui_misson/footer/footer_B"), { ALIGN_BOTTOM } }, + { HashStr("ui_misson/header/misson_title_B"), { ALIGN_TOP | OFFSET_SCALE_LEFT, 638.0f } }, + { HashStr("ui_misson/header/misson_title_B/title_bg/center"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_misson/header/misson_title_B/title_bg/center/h_light"), { ALIGN_TOP | EXTEND_LEFT} }, + { HashStr("ui_misson/header/misson_title_B/title_bg/right"), { ALIGN_TOP | STORE_RIGHT_CORNER } }, + { HashStr("ui_misson/window/bg_B2/position/bg"), { STRETCH } }, + + // ui_pause + { HashStr("ui_pause/bg"), { STRETCH } }, + { HashStr("ui_pause/footer/footer_A"), { ALIGN_BOTTOM } }, + { HashStr("ui_pause/footer/footer_B"), { ALIGN_BOTTOM } }, + { HashStr("ui_pause/header/status_title"), { ALIGN_TOP | OFFSET_SCALE_LEFT, 585.0f } }, + { HashStr("ui_pause/header/status_title/title_bg/center"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_pause/header/status_title/title_bg/center/h_light"), { ALIGN_TOP | EXTEND_LEFT} }, + { HashStr("ui_pause/header/status_title/title_bg/right"), { ALIGN_TOP | STORE_RIGHT_CORNER } }, + + // ui_playscreen + { HashStr("ui_playscreen/player_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen/time_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen/score_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen/exp_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen/so_speed_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen/so_ringenagy_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen/gauge_frame"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen/ring_count"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen/ring_get"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen/add/speed_count"), { ALIGN_RIGHT | SCALE } }, + { HashStr("ui_playscreen/add/u_info"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_playscreen/add/medal_get_s"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_playscreen/add/medal_get_m"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + + // ui_playscreen_ev + { HashStr("ui_playscreen_ev/player_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/score_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/ring_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/ring_get"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/exp_count"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/add/u_info"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_playscreen_ev/add/medal_get_s"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_playscreen_ev/add/medal_get_m"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/unleash_bg"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/life_bg"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/unleash_body"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/unleash_bar_1"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/unleash_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/unleash_gauge_effect_2"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/unleash_gauge_effect"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/unleash_bar_2"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/life"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield_position"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_01"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_02"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_03"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_04"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_05"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_06"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_07"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_08"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_09"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_10"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_11"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_12"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_13"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_14"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_ev/gauge/shield/shield_15"), { ALIGN_BOTTOM_LEFT | SCALE } }, + + // ui_playscreen_ev_hit + { HashStr("ui_playscreen_ev_hit/hit_counter_bg"), { ALIGN_RIGHT | SCALE } }, + { HashStr("ui_playscreen_ev_hit/hit_counter_num"), { ALIGN_RIGHT | SCALE } }, + { HashStr("ui_playscreen_ev_hit/hit_counter_txt_1"), { ALIGN_RIGHT | SCALE } }, + { HashStr("ui_playscreen_ev_hit/hit_counter_txt_2"), { ALIGN_RIGHT | SCALE } }, + { HashStr("ui_playscreen_ev_hit/chance_attack"), { ALIGN_RIGHT | SCALE } }, + + // ui_playscreen_su + { HashStr("ui_playscreen_su/su_sonic_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_su/gaia_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_playscreen_su/footer"), { ALIGN_BOTTOM_RIGHT | SCALE } }, + + // ui_prov_playscreen + { HashStr("ui_prov_playscreen/so_speed_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_prov_playscreen/so_ringenagy_gauge"), { ALIGN_BOTTOM_LEFT | SCALE } }, + { HashStr("ui_prov_playscreen/bg"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_prov_playscreen/info_1"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_prov_playscreen/info_2"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_prov_playscreen/ring_get_effect"), { ALIGN_BOTTOM_LEFT | SCALE } }, + + // ui_result + { HashStr("ui_result/footer/result_footer"), { ALIGN_BOTTOM } }, + { HashStr("ui_result/main/result_title"), { ALIGN_TOP | OFFSET_SCALE_LEFT, 688.0f } }, + { HashStr("ui_result/main/result_title/title_bg/center"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_result/main/result_title/title_bg/center/h_light"), { ALIGN_TOP | EXTEND_LEFT} }, + { HashStr("ui_result/main/result_title/title_bg/right"), { ALIGN_TOP | STORE_RIGHT_CORNER } }, + { HashStr("ui_result/main/result_num_1"), { OFFSET_SCALE_RIGHT, 669.0f } }, + { HashStr("ui_result/main/result_num_1/num_bg/position_1/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_1/num_bg/position_1/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_1/num_bg/position_1/center_1/left"), { STORE_LEFT_CORNER } }, + { HashStr("ui_result/main/result_num_1/num_bg/position_1/center_1/right"), { SKIP } }, + { HashStr("ui_result/main/result_num_1/num_bg/position_1/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result/main/result_num_2"), { OFFSET_SCALE_RIGHT, 669.0f } }, + { HashStr("ui_result/main/result_num_2/num_bg/position_2/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_2/num_bg/position_2/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_2/num_bg/position_2/center_1/left"), { STORE_LEFT_CORNER } }, + { HashStr("ui_result/main/result_num_2/num_bg/position_2/center_1/right"), { SKIP } }, + { HashStr("ui_result/main/result_num_2/num_bg/position_2/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result/main/result_num_3"), { OFFSET_SCALE_RIGHT, 669.0f } }, + { HashStr("ui_result/main/result_num_3/num_bg/position_3/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_3/num_bg/position_3/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_3/num_bg/position_3/center_1/left"), { STORE_LEFT_CORNER } }, + { HashStr("ui_result/main/result_num_3/num_bg/position_3/center_1/right"), { SKIP } }, + { HashStr("ui_result/main/result_num_3/num_bg/position_3/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result/main/result_num_4"), { OFFSET_SCALE_RIGHT, 669.0f } }, + { HashStr("ui_result/main/result_num_4/num_bg/position_4/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_4/num_bg/position_4/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_4/num_bg/position_4/center_1/left"), { STORE_LEFT_CORNER } }, + { HashStr("ui_result/main/result_num_4/num_bg/position_4/center_1/right"), { SKIP } }, + { HashStr("ui_result/main/result_num_4/num_bg/position_4/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result/main/result_num_5"), { OFFSET_SCALE_RIGHT, 669.0f } }, + { HashStr("ui_result/main/result_num_5/num_bg/position_5/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_5/num_bg/position_5/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result/main/result_num_5/num_bg/position_5/center_1/left"), { STORE_LEFT_CORNER } }, + { HashStr("ui_result/main/result_num_5/num_bg/position_5/center_1/right"), { SKIP } }, + { HashStr("ui_result/main/result_num_5/num_bg/position_5/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result/main/result_num_6"), { OFFSET_SCALE_LEFT, 1094.0f } }, + { HashStr("ui_result/main/result_num_6/num_bg/position_6/center"), { EXTEND_LEFT } }, + { HashStr("ui_result/main/result_num_6/num_bg/position_6/center/h_light"), { EXTEND_LEFT } }, + { HashStr("ui_result/main/result_num_6/num_bg/position_6/center/right"), { STORE_RIGHT_CORNER } }, + { HashStr("ui_result/main/result_num_6/num_bg/position_6/center/left"), { SKIP } }, + { HashStr("ui_result/main/result_num_6/num_bg/position_6/center/left/h_light"), { SKIP } }, + { HashStr("ui_result/newRecode/result_newR/position/newR_brilliance1"), { OFFSET_SCALE_RIGHT | STORE_LEFT_CORNER, 458.4f } }, + { HashStr("ui_result/newRecode/result_newR/position/newR_brilliance2"), { OFFSET_SCALE_RIGHT | STORE_LEFT_CORNER, 458.4f } }, + { HashStr("ui_result/newRecode/result_newR/position/newR_brilliance3"), { OFFSET_SCALE_RIGHT | STORE_LEFT_CORNER, 458.4f } }, + + // ui_result_ex + { HashStr("ui_result_ex/footer/result_footer"), { ALIGN_BOTTOM } }, + { HashStr("ui_result_ex/main/result_title"), { ALIGN_TOP | OFFSET_SCALE_LEFT, 688.0f } }, + { HashStr("ui_result_ex/main/result_title/title_bg/center"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_result_ex/main/result_title/title_bg/center/h_light"), { ALIGN_TOP | EXTEND_LEFT} }, + { HashStr("ui_result_ex/main/result_title/title_bg/right"), { ALIGN_TOP | STORE_RIGHT_CORNER } }, + { HashStr("ui_result_ex/main/number/result_num_1"), { OFFSET_SCALE_LEFT | OFFSET_SCALE_RIGHT, 669.0f } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_1"), { OFFSET_SCALE_RIGHT, 669.0f, 0 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_1/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_1/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_1/center_1/left"), { STORE_LEFT_CORNER, 0, 0 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_1/center_1/right"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_1/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_2"), { OFFSET_SCALE_RIGHT, 669.0f, 1 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_2/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_2/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_2/center_1/left"), { STORE_LEFT_CORNER, 0, 1 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_2/center_1/right"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_2/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_3"), { OFFSET_SCALE_RIGHT, 669.0f, 2 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_3/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_3/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_3/center_1/left"), { STORE_LEFT_CORNER, 0, 2 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_3/center_1/right"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_3/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_4"), { OFFSET_SCALE_RIGHT, 669.0f, 3 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_4/center_1"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_4/center_1/h_light"), { EXTEND_RIGHT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_4/center_1/left"), { STORE_LEFT_CORNER, 0, 3 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_4/center_1/right"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_4/center_1/right/h_light"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_6"), { OFFSET_SCALE_LEFT, 1094.0f, 4 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_6/center"), { EXTEND_LEFT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_6/center/right"), { STORE_RIGHT_CORNER, 0, 4 } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_6/center/h_light"), { EXTEND_LEFT } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_6/center/left"), { SKIP } }, + { HashStr("ui_result_ex/main/number/result_num_1/position_6/center/left/h_light"), { SKIP } }, + { HashStr("ui_result_ex/newRecode/result_newR/position/newR_brilliance1"), { OFFSET_SCALE_RIGHT | STORE_LEFT_CORNER, 458.4f } }, + { HashStr("ui_result_ex/newRecode/result_newR/position/newR_brilliance2"), { OFFSET_SCALE_RIGHT | STORE_LEFT_CORNER, 458.4f } }, + { HashStr("ui_result_ex/newRecode/result_newR/position/newR_brilliance3"), { OFFSET_SCALE_RIGHT | STORE_LEFT_CORNER, 458.4f } }, + + // ui_shop + { HashStr("ui_shop/footer/shop_footer"), { ALIGN_BOTTOM } }, + { HashStr("ui_shop/header/ring"), { ALIGN_TOP } }, + { HashStr("ui_shop/header/shop_nametag"), { ALIGN_TOP } }, + + // ui_start + { HashStr("ui_start/Clear/position/bg/bg_1"), { STRETCH } }, + { HashStr("ui_start/Clear/position/bg/bg_2"), { STRETCH } }, + { HashStr("ui_start/Start/img/bg/bg_1"), { STRETCH } }, + { HashStr("ui_start/Start/img/bg/bg_2"), { STRETCH } }, + + // ui_status + { HashStr("ui_status/footer/status_footer"), { ALIGN_BOTTOM } }, + { HashStr("ui_status/header/status_title"), { ALIGN_TOP | OFFSET_SCALE_LEFT, 617.0f } }, + { HashStr("ui_status/header/status_title/title_bg/center"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_status/header/status_title/title_bg/center/h_light"), { ALIGN_TOP | EXTEND_LEFT } }, + { HashStr("ui_status/header/status_title/title_bg/right"), { ALIGN_TOP | STORE_RIGHT_CORNER } }, + { HashStr("ui_status/logo/logo/bg_position/c_1"), { STRETCH_HORIZONTAL } }, + { HashStr("ui_status/logo/logo/bg_position/c_2"), { STRETCH_HORIZONTAL } }, + { HashStr("ui_status/main/progless/bg/prgs_bg_1"), { OFFSET_SCALE_LEFT, 714.0f } }, + { HashStr("ui_status/main/progless/bg/prgs_bg_1/position/center/right"), { STORE_RIGHT_CORNER } }, + { HashStr("ui_status/main/progless/prgs/prgs_bar_1"), { OFFSET_SCALE_LEFT, 586.0f } }, + { HashStr("ui_status/main/progless/prgs/prgs_bar_1/position/bg/center/right"), { STORE_RIGHT_CORNER } }, + { HashStr("ui_status/main/tag/bg/tag_bg_1"), { OFFSET_SCALE_LEFT, 413.0f } }, + { HashStr("ui_status/main/tag/bg/tag_bg_1/total_1_bg/center"), { EXTEND_LEFT } }, + { HashStr("ui_status/main/tag/bg/tag_bg_1/total_1_bg/center/h_light"), { EXTEND_LEFT } }, + { HashStr("ui_status/main/tag/bg/tag_bg_1/total_1_bg/center/right"), { STORE_RIGHT_CORNER } }, + { HashStr("ui_status/main/tag/bg/tag_bg_1/total_1_bg/center/left"), { SKIP } }, + { HashStr("ui_status/main/tag/bg/tag_bg_1/total_1_bg/center/left/h_light"), { SKIP } }, + { HashStr("ui_status/main/tag/txt/tag_txt_1"), { OFFSET_SCALE_LEFT, 352.0f } }, + { HashStr("ui_status/main/tag/txt/tag_txt_1/position/img"), { STORE_RIGHT_CORNER } }, + { HashStr("ui_status/window/bg"), { STRETCH } }, + + // ui_title + { HashStr("ui_title/bg/bg"), { STRETCH } }, + { HashStr("ui_title/bg/headr"), { ALIGN_TOP | STRETCH_HORIZONTAL } }, + { HashStr("ui_title/bg/footer"), { ALIGN_BOTTOM | STRETCH_HORIZONTAL } }, + { HashStr("ui_title/bg/position"), { ALIGN_BOTTOM } }, + + // ui_townscreen + { HashStr("ui_townscreen/time"), { ALIGN_TOP_RIGHT | SCALE } }, + { HashStr("ui_townscreen/time_effect"), { ALIGN_TOP_RIGHT | SCALE } }, + { HashStr("ui_townscreen/info"), { ALIGN_TOP_LEFT | SCALE } }, + { HashStr("ui_townscreen/cam"), { ALIGN_TOP_RIGHT | SCALE } }, + { HashStr("ui_townscreen/footer"), { ALIGN_BOTTOM } }, + + // ui_worldmap + { HashStr("ui_worldmap/contents/choices/cts_choices_bg"), { STRETCH } }, + { HashStr("ui_worldmap/contents/info/bg/cts_info_bg"), { ALIGN_TOP_LEFT | WORLD_MAP } }, + { HashStr("ui_worldmap/contents/info/bg/info_bg_1"), { ALIGN_TOP_LEFT | WORLD_MAP } }, + { HashStr("ui_worldmap/contents/info/img/info_img_1"), { ALIGN_TOP_LEFT | WORLD_MAP } }, + { HashStr("ui_worldmap/contents/info/img/info_img_2"), { ALIGN_TOP_LEFT | WORLD_MAP } }, + { HashStr("ui_worldmap/contents/info/img/info_img_3"), { ALIGN_TOP_LEFT | WORLD_MAP } }, + { HashStr("ui_worldmap/contents/info/img/info_img_4"), { ALIGN_TOP_LEFT | WORLD_MAP } }, + { HashStr("ui_worldmap/footer/worldmap_footer_bg"), { ALIGN_BOTTOM } }, + { HashStr("ui_worldmap/footer/worldmap_footer_img_A"), { ALIGN_BOTTOM } }, + { HashStr("ui_worldmap/header/worldmap_header_bg"), { ALIGN_TOP } }, + { HashStr("ui_worldmap/header/worldmap_header_img"), { ALIGN_TOP_LEFT | WORLD_MAP } } +}; + +static std::optional FindModifier(uint32_t data) +{ + XXH64_hash_t path; + { + std::lock_guard lock(g_pathMutex); + + auto findResult = g_paths.find(g_memory.Translate(data)); + if (findResult == g_paths.end()) + return {}; + + path = findResult->second; + } + + auto findResult = g_modifiers.find(path); + if (findResult != g_modifiers.end()) + return findResult->second; + + return {}; +} + +static std::optional g_sceneModifier; +static float g_corners[8]; +static bool g_cornerExtract; + +//#define CORNER_DEBUG + +// Chao::CSD::Scene::Render +PPC_FUNC_IMPL(__imp__sub_830C6A00); +PPC_FUNC(sub_830C6A00) +{ + g_sceneModifier = FindModifier(ctx.r3.u32); + + if (g_sceneModifier.has_value() && (g_sceneModifier->flags & (OFFSET_SCALE_LEFT | OFFSET_SCALE_RIGHT)) != 0) + { + auto r3 = ctx.r3; + auto r4 = ctx.r4; + auto r5 = ctx.r5; + auto r6 = ctx.r6; + + // Queue draw calls, but don't actually draw anything. We just want to extract the corner. + g_cornerExtract = true; + __imp__sub_830C6A00(ctx, base); + g_cornerExtract = false; + +#ifdef CORNER_DEBUG + if (g_sceneModifier->cornerMax == FLT_MAX) + { + fmt::print("Corners: "); + for (auto corner : g_corners) + fmt::print("{} ", corner); + + fmt::println(""); + } +#endif + + ctx.r3 = r3; + ctx.r4 = r4; + ctx.r5 = r5; + ctx.r6 = r6; + } + + __imp__sub_830C6A00(ctx, base); +} + +static std::optional g_castNodeModifier; + +void RenderCsdCastNodeMidAsmHook(PPCRegister& r10, PPCRegister& r27) +{ + g_castNodeModifier = FindModifier(r10.u32 + r27.u32); +} + +static std::optional g_castModifier; + +void RenderCsdCastMidAsmHook(PPCRegister& r4) +{ + g_castModifier = FindModifier(r4.u32); +} + +static void Draw(PPCContext& ctx, uint8_t* base, PPCFunc* original, uint32_t stride) +{ + CsdModifier modifier{}; + + if (g_castModifier.has_value()) + { + modifier = g_castModifier.value(); + } + else if (g_castNodeModifier.has_value()) + { + modifier = g_castNodeModifier.value(); + } + else if (g_sceneModifier.has_value()) + { + modifier = g_sceneModifier.value(); + } + + if ((modifier.flags & SKIP) != 0) + { + return; + } + + if (g_cornerExtract) + { + if ((modifier.flags & (STORE_LEFT_CORNER | STORE_RIGHT_CORNER)) != 0) + { + uint32_t vertexIndex = ((modifier.flags & STORE_LEFT_CORNER) != 0) ? 0 : 3; + g_corners[modifier.cornerIndex] = *reinterpret_cast*>(base + ctx.r4.u32 + vertexIndex * stride); + } + + return; + } + + if (Config::UIScaleMode == EUIScaleMode::Centre) + { + if (g_aspectRatio >= WIDE_ASPECT_RATIO) + modifier.flags &= ~(ALIGN_LEFT | ALIGN_RIGHT); + } + + auto backBuffer = Video::GetBackBuffer(); + + uint32_t size = ctx.r5.u32 * stride; + ctx.r1.u32 -= size; + + uint8_t* stack = base + ctx.r1.u32; + memcpy(stack, base + ctx.r4.u32, size); + + float offsetX = 0.0f; + float offsetY = 0.0f; + float scaleX = 1.0f; + float scaleY = 1.0f; + + if ((modifier.flags & STRETCH_HORIZONTAL) != 0) + { + scaleX = backBuffer->width / 1280.0f; + } + else + { + if ((modifier.flags & ALIGN_RIGHT) != 0) + offsetX = g_aspectRatioOffsetX * 2.0f; + else if ((modifier.flags & ALIGN_LEFT) == 0) + offsetX = g_aspectRatioOffsetX; + + if ((modifier.flags & SCALE) != 0) + { + scaleX = g_aspectRatioScale; + + if ((modifier.flags & ALIGN_RIGHT) != 0) + offsetX += 1280.0f * (1.0f - scaleX); + else if ((modifier.flags & ALIGN_LEFT) == 0) + offsetX += 640.0f * (1.0f - scaleX); + } + + if ((modifier.flags & WORLD_MAP) != 0) + { + if ((modifier.flags & ALIGN_LEFT) != 0) + offsetX += (1.0f - g_narrowOffsetScale) * -20.0f; + } + } + + if ((modifier.flags & STRETCH_VERTICAL) != 0) + { + scaleY = backBuffer->height / 720.0f; + } + else + { + if ((modifier.flags & ALIGN_BOTTOM) != 0) + offsetY = g_aspectRatioOffsetY * 2.0f; + else if ((modifier.flags & ALIGN_TOP) == 0) + offsetY = g_aspectRatioOffsetY; + + if ((modifier.flags & SCALE) != 0) + { + scaleY = g_aspectRatioScale; + + if ((modifier.flags & ALIGN_BOTTOM) != 0) + offsetY += 720.0f * (1.0f - scaleY); + else if ((modifier.flags & ALIGN_TOP) == 0) + offsetY += 360.0f * (1.0f - scaleY); + } + } + + if (g_aspectRatio > WIDE_ASPECT_RATIO) + { + CsdModifier offsetScaleModifier{}; + float corner = 0.0f; + + if (g_castModifier.has_value()) + { + offsetScaleModifier = g_castModifier.value(); + + uint32_t vertexIndex = ((offsetScaleModifier.flags & STORE_LEFT_CORNER) != 0) ? 0 : 3; + corner = *reinterpret_cast*>(base + ctx.r4.u32 + vertexIndex * stride); + } + + if (offsetScaleModifier.cornerMax == 0.0f && g_castNodeModifier.has_value()) + { + offsetScaleModifier = g_castNodeModifier.value(); + corner = g_corners[offsetScaleModifier.cornerIndex]; + } + + if (offsetScaleModifier.cornerMax == 0.0f && g_sceneModifier.has_value()) + { + offsetScaleModifier = g_sceneModifier.value(); + corner = g_corners[offsetScaleModifier.cornerIndex]; + } + +#ifdef CORNER_DEBUG + if ((offsetScaleModifier.flags & (OFFSET_SCALE_LEFT | OFFSET_SCALE_RIGHT)) != 0 && offsetScaleModifier.cornerMax == FLT_MAX) + fmt::println("Corner: {}", corner); +#endif + + if ((offsetScaleModifier.flags & OFFSET_SCALE_LEFT) != 0) + offsetX *= corner / offsetScaleModifier.cornerMax; + else if ((offsetScaleModifier.flags & OFFSET_SCALE_RIGHT) != 0) + offsetX = 1280.0f - (1280.0f - offsetX) * (1280.0f - corner) / (1280.0f - offsetScaleModifier.cornerMax); + } + + for (size_t i = 0; i < ctx.r5.u32; i++) + { + auto position = reinterpret_cast*>(stack + i * stride); + + float x = offsetX + position[0] * scaleX; + float y = offsetY + position[1] * scaleY; + + if ((modifier.flags & EXTEND_LEFT) != 0 && (i == 0 || i == 1)) + { + x = std::min(x, 0.0f); + } + else if ((modifier.flags & EXTEND_RIGHT) != 0 && (i == 2 || i == 3)) + { + x = std::max(x, float(backBuffer->width)); + } + + position[0] = round(x / backBuffer->width * Video::s_viewportWidth) / Video::s_viewportWidth * backBuffer->width; + position[1] = round(y / backBuffer->height * Video::s_viewportHeight) / Video::s_viewportHeight * backBuffer->height; + } + + ctx.r4.u32 = ctx.r1.u32; + original(ctx, base); + ctx.r1.u32 += size; +} + +// SWA::CCsdPlatformMirage::Draw +PPC_FUNC_IMPL(__imp__sub_825E2E70); +PPC_FUNC(sub_825E2E70) +{ + Draw(ctx, base, __imp__sub_825E2E70, 0x14); +} + +// SWA::CCsdPlatformMirage::DrawNoTex +PPC_FUNC_IMPL(__imp__sub_825E2E88); +PPC_FUNC(sub_825E2E88) +{ + Draw(ctx, base, __imp__sub_825E2E88, 0xC); +} + +// Hedgehog::MirageDebug::SetScissorRect +PPC_FUNC_IMPL(__imp__sub_82E16C70); +PPC_FUNC(sub_82E16C70) +{ + auto backBuffer = Video::GetBackBuffer(); + auto scissorRect = reinterpret_cast(base + ctx.r4.u32); + + scissorRect->left = scissorRect->left + g_aspectRatioOffsetX; + scissorRect->top = scissorRect->top + g_aspectRatioOffsetY; + scissorRect->right = scissorRect->right + g_aspectRatioOffsetX; + scissorRect->bottom = scissorRect->bottom + g_aspectRatioOffsetY; + + __imp__sub_82E16C70(ctx, base); +} + +// Store whether the primitive should be stretched in available padding space. +static constexpr size_t PRIMITIVE_2D_PADDING_OFFSET = 0x29; +static constexpr size_t PRIMITIVE_2D_PADDING_SIZE = 0x3; + +// Hedgehog::MirageDebug::CPrimitive2D::CPrimitive2D +PPC_FUNC_IMPL(__imp__sub_822D0328); +PPC_FUNC(sub_822D0328) +{ + memset(base + ctx.r3.u32 + PRIMITIVE_2D_PADDING_OFFSET, 0, PRIMITIVE_2D_PADDING_SIZE); + __imp__sub_822D0328(ctx, base); +} + +// Hedgehog::MirageDebug::CPrimitive2D::CPrimitive2D(const Hedgehog::MirageDebug::CPrimitive2D&) +PPC_FUNC_IMPL(__imp__sub_830D2328); +PPC_FUNC(sub_830D2328) +{ + memcpy(base + ctx.r3.u32 + PRIMITIVE_2D_PADDING_OFFSET, base + ctx.r4.u32 + PRIMITIVE_2D_PADDING_OFFSET, PRIMITIVE_2D_PADDING_SIZE); + __imp__sub_830D2328(ctx, base); +} + +void AddPrimitive2DMidAsmHook(PPCRegister& r3) +{ + *(g_memory.base + r3.u32 + PRIMITIVE_2D_PADDING_OFFSET) = 0x01; +} + +// Hedgehog::MirageDebug::CPrimitive2D::Draw +PPC_FUNC_IMPL(__imp__sub_830D1EF0); +PPC_FUNC(sub_830D1EF0) +{ + auto r3 = ctx.r3; + + __imp__sub_830D1EF0(ctx, base); + + if (!PPC_LOAD_U8(r3.u32 + PRIMITIVE_2D_PADDING_OFFSET)) + { + auto backBuffer = Video::GetBackBuffer(); + + struct Vertex + { + be x; + be y; + be z; + be w; + be color; + be u; + be v; + }; + + auto vertex = reinterpret_cast(base + ctx.r4.u32); + + for (size_t i = 0; i < 4; i++) + { + vertex[i].x = vertex[i].x * 1280.0f / backBuffer->width; + vertex[i].y = vertex[i].y * 720.0f / backBuffer->height; + } + + bool letterboxTop = PPC_LOAD_U8(r3.u32 + PRIMITIVE_2D_PADDING_OFFSET + 0x1); + bool letterboxBottom = PPC_LOAD_U8(r3.u32 + PRIMITIVE_2D_PADDING_OFFSET + 0x2); + + if (letterboxTop || letterboxBottom) + { + float halfPixelX = 1.0f / backBuffer->width; + float halfPixelY = 1.0f / backBuffer->height; + + if (letterboxTop) + { + vertex[0].x = -1.0f - halfPixelX; + vertex[0].y = 1.0f + halfPixelY; + + vertex[1].x = 1.0f - halfPixelX; + vertex[1].y = 1.0f + halfPixelY; + + vertex[2].x = -1.0f - halfPixelX; + // vertex[2].y untouched + + vertex[3].x = 1.0f - halfPixelX; + // vertex[3].y untouched + } + else if (letterboxBottom) + { + vertex[0].x = -1.0f - halfPixelX; + // vertex[0].y untouched + + vertex[1].x = 1.0f - halfPixelX; + // vertex[1].y untouched + + vertex[2].x = -1.0f - halfPixelX; + vertex[2].y = -1.0f + halfPixelY; + + vertex[3].x = 1.0f - halfPixelX; + vertex[3].y = -1.0f + halfPixelY; + } + } + } +} + +// Objects are pushed forward by 1m, so the coordinates need to compensate for it. +static const double OBJ_GET_ITEM_TANGENT = tan(M_PI / 8.0); + +// Coordinates are in [-1, 1] range. Automatically fit and centered. +// The tangent is calculated incorrectly in game, causing distortion. +// The hook makes them move to the correct position regardless of FOV. +static float ComputeObjGetItemTangent(float fieldOfView, float aspectRatio) +{ + return tan(AdjustFieldOfView(fieldOfView, aspectRatio) / 2.0) / OBJ_GET_ITEM_TANGENT; +} + +void ObjGetItemFieldOfViewMidAsmHook(PPCRegister& r1, PPCRegister& f1) +{ + if (Config::AspectRatio != EAspectRatio::OriginalNarrow) + *reinterpret_cast*>(g_memory.base + r1.u32 + 0x58) = ComputeObjGetItemTangent(f1.f64, g_aspectRatio); +} + +static double ComputeObjGetItemX(uint32_t type) +{ + if (type >= 47) // Ring, Moon Medal, Sun Medal + { + double x; + + if (type == 47) // Ring + x = 142.0; + else // Medal + x = 1058.0; + + x *= g_aspectRatioScale; + + double scaleOffset = (1280.0 * (1.0 - g_aspectRatioScale)); + + if (Config::UIScaleMode == EUIScaleMode::Edge) + { + if (type != 47) // Medal + x += g_aspectRatioOffsetX * 2.0 + scaleOffset; + } + else if (Config::UIScaleMode == EUIScaleMode::Centre) + { + x += g_aspectRatioOffsetX + scaleOffset; + } + + auto backBuffer = Video::GetBackBuffer(); + return (x - (0.5 * backBuffer->width)) / (0.5 * backBuffer->height) * OBJ_GET_ITEM_TANGENT; + } + + return 0.0; +} + +// SWA::CObjGetItem::GetX +PPC_FUNC_IMPL(__imp__sub_82690660); +PPC_FUNC(sub_82690660) +{ + if (Config::AspectRatio == EAspectRatio::OriginalNarrow) + { + __imp__sub_82690660(ctx, base); + return; + } + + uint32_t type = PPC_LOAD_U32(ctx.r3.u32 + 0xE8); + ctx.f1.f64 = ComputeObjGetItemX(type); +} + +static double ComputeObjGetItemY(uint32_t type) +{ + if (type >= 47) // Ring, Moon Medal, Sun Medal + { + double y; + + if (type == 47) // Ring + y = 642.0; + else if (type == 48) // Moon Medal + y = 632.0; + else if (type == 49) // Sun Medal + y = 582.0; + + y *= g_aspectRatioScale; + y += g_aspectRatioOffsetY * 2.0 + 720.0 * (1.0 - g_aspectRatioScale); + + auto backBuffer = Video::GetBackBuffer(); + return ((0.5 * backBuffer->height) - y) / (0.5 * backBuffer->height) * OBJ_GET_ITEM_TANGENT; + } + + return 0.25; +} + +// SWA::CObjGetItem::GetY +PPC_FUNC_IMPL(__imp__sub_826906A8); +PPC_FUNC(sub_826906A8) +{ + if (Config::AspectRatio == EAspectRatio::OriginalNarrow) + { + __imp__sub_826906A8(ctx, base); + // Game scales Y by 1.4 at 4:3 aspect ratio. + ctx.f1.f64 *= 1.4; + return; + } + + uint32_t type = PPC_LOAD_U32(ctx.r3.u32 + 0xE8); + ctx.f1.f64 = ComputeObjGetItemY(type); +} + +void WorldMapProjectionMidAsmHook(PPCVRegister& v63, PPCVRegister& v62) +{ + v63.f32[3] *= std::max(NARROW_ASPECT_RATIO, g_aspectRatio) / WIDE_ASPECT_RATIO; + v62.f32[2] *= NARROW_ASPECT_RATIO / std::min(NARROW_ASPECT_RATIO, g_aspectRatio); +} + +// CViewRing has the same exact incorrect math as CObjGetItem. +void ViewRingFieldOfViewMidAsmHook(PPCRegister& r1, PPCRegister& f1) +{ + if (Config::AspectRatio != EAspectRatio::OriginalNarrow) + *reinterpret_cast*>(g_memory.base + r1.u32 + 0x54) = ComputeObjGetItemTangent(f1.f64, g_aspectRatio); +} + +void ViewRingYMidAsmHook(PPCRegister& f1) +{ + if (Config::AspectRatio != EAspectRatio::OriginalNarrow) + f1.f64 = -ComputeObjGetItemY(47); // Ring +} + +void ViewRingXMidAsmHook(PPCRegister& f1, PPCVRegister& v62) +{ + if (Config::AspectRatio == EAspectRatio::OriginalNarrow) + { + // Game scales Y by 1.4 at 4:3 aspect ratio. + for (size_t i = 0; i < 4; i++) + v62.f32[i] *= 1.4f; + } + else + { + f1.f64 = -ComputeObjGetItemX(47); // Ring + } +} + +// SWA::Inspire::CLetterbox::Draw +PPC_FUNC_IMPL(__imp__sub_82B8AA40); +PPC_FUNC(sub_82B8AA40) +{ + bool shouldDrawLetterbox = Config::CutsceneAspectRatio != ECutsceneAspectRatio::Unlocked && g_aspectRatio < WIDE_ASPECT_RATIO; + + PPC_STORE_U8(ctx.r3.u32, shouldDrawLetterbox); + if (shouldDrawLetterbox) + { + auto backBuffer = Video::GetBackBuffer(); + uint32_t height = std::min(720u, backBuffer->height); + + PPC_STORE_U32(ctx.r3.u32 + 0xC, backBuffer->width); + PPC_STORE_U32(ctx.r3.u32 + 0x10, height); + PPC_STORE_U32(ctx.r3.u32 + 0x14, (height - backBuffer->width * 9 / 16) / 2); + } + + __imp__sub_82B8AA40(ctx, base); +} + +void InspireLetterboxTopMidAsmHook(PPCRegister& r3) +{ + *(g_memory.base + r3.u32 + PRIMITIVE_2D_PADDING_OFFSET + 0x1) = 0x01; // Letterbox Top + *(g_memory.base + r3.u32 + PRIMITIVE_2D_PADDING_OFFSET + 0x2) = 0x00; // Letterbox Bottom +} + +void InspireLetterboxBottomMidAsmHook(PPCRegister& r3) +{ + *(g_memory.base + r3.u32 + PRIMITIVE_2D_PADDING_OFFSET + 0x1) = 0x00; // Letterbox Top + *(g_memory.base + r3.u32 + PRIMITIVE_2D_PADDING_OFFSET + 0x2) = 0x01; // Letterbox Bottom +} + +void InspireSubtitleMidAsmHook(PPCRegister& r3) +{ + constexpr float NARROW_OFFSET = 485.0f; + constexpr float WIDE_OFFSET = 560.0f; + + *reinterpret_cast*>(g_memory.base + r3.u32 + 0x3C) = NARROW_OFFSET + (WIDE_OFFSET - NARROW_OFFSET) * g_narrowOffsetScale; +} diff --git a/UnleashedRecomp/patches/aspect_ratio_patches.h b/UnleashedRecomp/patches/aspect_ratio_patches.h new file mode 100644 index 0000000..14bae6b --- /dev/null +++ b/UnleashedRecomp/patches/aspect_ratio_patches.h @@ -0,0 +1,16 @@ +#pragma once + +inline constexpr float NARROW_ASPECT_RATIO = 4.0f / 3.0f; +inline constexpr float WIDE_ASPECT_RATIO = 16.0f / 9.0f; +inline constexpr float STEAM_DECK_ASPECT_RATIO = 16.0f / 10.0f; + +inline float g_aspectRatio; +inline float g_aspectRatioOffsetX; +inline float g_aspectRatioOffsetY; +inline float g_aspectRatioScale; +inline float g_narrowOffsetScale; + +struct AspectRatioPatches +{ + static void ComputeOffsets(); +}; diff --git a/UnleashedRecomp/patches/camera_patches.cpp b/UnleashedRecomp/patches/camera_patches.cpp index 17cdf42..a3b3cbc 100644 --- a/UnleashedRecomp/patches/camera_patches.cpp +++ b/UnleashedRecomp/patches/camera_patches.cpp @@ -1,41 +1,41 @@ #include #include #include +#include +#include "camera_patches.h" +#include "aspect_ratio_patches.h" -constexpr float m_baseAspectRatio = 16.0f / 9.0f; - -bool CameraAspectRatioMidAsmHook(PPCRegister& r31) +void CameraAspectRatioMidAsmHook(PPCRegister& r30, PPCRegister& r31) { - auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32); - auto newAspectRatio = (float)GameWindow::s_width / (float)GameWindow::s_height; + r30.u32 = 0; + + auto camera = (SWA::CCamera*)g_memory.Translate(r31.u32); // Dynamically adjust horizontal aspect ratio to window dimensions. - pCamera->m_HorzAspectRatio = newAspectRatio; - - if (auto s_pVertAspectRatio = (be*)g_memory.Translate(0x82028FE0)) - { - // Dynamically adjust vertical aspect ratio for VERT+. - *s_pVertAspectRatio = 2.0f * atan(tan(45.0f / 2.0f) * (m_baseAspectRatio / newAspectRatio)); - } - - // Jump to 4:3 code for VERT+ adjustments if using a narrow aspect ratio. - return newAspectRatio < m_baseAspectRatio; + camera->m_HorzAspectRatio = g_aspectRatio; } -bool CameraBoostAspectRatioMidAsmHook(PPCRegister& r31, PPCRegister& f0, PPCRegister& f10, PPCRegister& f12) +float AdjustFieldOfView(float fieldOfView, float aspectRatio) { - auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32); - - if (GameWindow::s_width < GameWindow::s_height) + if (Config::AspectRatio == EAspectRatio::OriginalNarrow) { - pCamera->m_VertFieldOfView = pCamera->m_HorzFieldOfView + f10.f64; + // Replicate the original incorrect field of view formula if requested. + fieldOfView *= NARROW_ASPECT_RATIO; } - else + else if (aspectRatio < WIDE_ASPECT_RATIO) { - pCamera->m_VertFieldOfView = (f12.f64 / f0.f64) + f10.f64; + // Use proper VERT+ otherwise for narrow aspect ratios. + fieldOfView = 2.0 * atan(tan(0.5 * fieldOfView) / aspectRatio * WIDE_ASPECT_RATIO); } - return true; + return fieldOfView; +} + +void CameraFieldOfViewMidAsmHook(PPCRegister& r31, PPCRegister& f31) +{ + auto camera = (SWA::CCamera*)g_memory.Translate(r31.u32); + + f31.f64 = AdjustFieldOfView(f31.f64, camera->m_HorzAspectRatio); } PPC_FUNC_IMPL(__imp__sub_824697B0); diff --git a/UnleashedRecomp/patches/camera_patches.h b/UnleashedRecomp/patches/camera_patches.h new file mode 100644 index 0000000..949a2dd --- /dev/null +++ b/UnleashedRecomp/patches/camera_patches.h @@ -0,0 +1,3 @@ +#pragma once + +extern float AdjustFieldOfView(float fieldOfView, float aspectRatio); diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index b72efde..7008eef 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -134,8 +134,8 @@ static void DrawHeaderContainer(const char* text) ? Lerp(1, 0, colourMotion) : Lerp(0, 1, colourMotion); - ImVec2 min = { Scale(containerMarginX), Scale(136) }; - ImVec2 max = { min.x + textMarginX * 2 + textSize.x + Scale(5), Scale(196) }; + ImVec2 min = { Scale(g_aspectRatioOffsetX + containerMarginX), Scale(g_aspectRatioOffsetY + 136) }; + ImVec2 max = { min.x + textMarginX * 2 + textSize.x + Scale(5), Scale(g_aspectRatioOffsetY + 196) }; DrawPauseHeaderContainer(g_upWindow.get(), min, max, alpha); @@ -568,8 +568,8 @@ static void DrawContentContainer() ? Hermite(604, 573, motion) : Hermite(573, 604, motion); - ImVec2 min = { Scale(minX), Scale(minY) }; - ImVec2 max = { Scale(maxX), Scale(maxY) }; + ImVec2 min = { Scale(g_aspectRatioOffsetX + minX), Scale(g_aspectRatioOffsetY + minY) }; + ImVec2 max = { Scale(g_aspectRatioOffsetX + maxX), Scale(g_aspectRatioOffsetY + maxY) }; // Transparency fade animation. auto alpha = g_isClosing diff --git a/UnleashedRecomp/ui/button_guide.cpp b/UnleashedRecomp/ui/button_guide.cpp index b983a74..2df8a0e 100644 --- a/UnleashedRecomp/ui/button_guide.cpp +++ b/UnleashedRecomp/ui/button_guide.cpp @@ -229,11 +229,8 @@ void ButtonGuide::Draw() auto drawList = ImGui::GetForegroundDrawList(); auto& res = ImGui::GetIO().DisplaySize; - auto regionMarginX = Scale(g_sideMargins); - auto regionHeight = Scale(102); - - ImVec2 regionMin = { regionMarginX, res.y - regionHeight }; - ImVec2 regionMax = { res.x - regionMarginX, res.y }; + ImVec2 regionMin = { Scale(g_aspectRatioOffsetX + g_sideMargins), Scale(g_aspectRatioOffsetY * 2.0f + 720.0f - 102.0f) }; + ImVec2 regionMax = { Scale(g_aspectRatioOffsetX + 1280.0f - g_sideMargins), Scale(g_aspectRatioOffsetY * 2.0f + 720.0f) }; auto textMarginX = Scale(57); auto textMarginY = Scale(8); diff --git a/UnleashedRecomp/ui/imgui_utils.h b/UnleashedRecomp/ui/imgui_utils.h index f7e6359..d5d5afc 100644 --- a/UnleashedRecomp/ui/imgui_utils.h +++ b/UnleashedRecomp/ui/imgui_utils.h @@ -4,6 +4,7 @@ #include #include #include +#include #define PIXELS_TO_UV_COORDS(textureWidth, textureHeight, x, y, width, height) \ std::make_tuple(ImVec2((float)x / (float)textureWidth, (float)y / (float)textureHeight), \ @@ -95,29 +96,26 @@ inline void ResetOutline() SetOutline(0.0f); } -// Aspect ratio aware. +inline void SetProceduralOrigin(ImVec2 proceduralOrigin) +{ + auto callbackData = AddImGuiCallback(ImGuiCallback::SetProceduralOrigin); + callbackData->setProceduralOrigin.proceduralOrigin[0] = proceduralOrigin.x; + callbackData->setProceduralOrigin.proceduralOrigin[1] = proceduralOrigin.y; +} + +inline void ResetProceduralOrigin() +{ + SetProceduralOrigin({ 0.0f, 0.0f }); +} + inline float Scale(float size) { auto& io = ImGui::GetIO(); - if (io.DisplaySize.x > io.DisplaySize.y) - return size * std::max(1.0f, io.DisplaySize.y / 720.0f); + if (g_aspectRatio >= NARROW_ASPECT_RATIO) + return size * (io.DisplaySize.y / 720.0f); else - return size * std::max(1.0f, io.DisplaySize.x / 1280.0f); -} - -// Not aspect ratio aware. Will stretch. -inline float ScaleX(float x) -{ - auto& io = ImGui::GetIO(); - return x * io.DisplaySize.x / 1280.0f; -} - -// Not aspect ratio aware. Will stretch. -inline float ScaleY(float y) -{ - auto& io = ImGui::GetIO(); - return y * io.DisplaySize.y / 720.0f; + return size * (io.DisplaySize.x / 960.0f); } inline double ComputeMotion(double duration, double offset, double total) diff --git a/UnleashedRecomp/ui/installer_wizard.cpp b/UnleashedRecomp/ui/installer_wizard.cpp index f6f7425..a7b4607 100644 --- a/UnleashedRecomp/ui/installer_wizard.cpp +++ b/UnleashedRecomp/ui/installer_wizard.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -447,8 +448,8 @@ static void DrawLeftImage() GuestTexture *guestTexture = g_installTextures[installTextureIndex % g_installTextures.size()].get(); auto &res = ImGui::GetIO().DisplaySize; auto drawList = ImGui::GetForegroundDrawList(); - ImVec2 min = { Scale(IMAGE_X), Scale(IMAGE_Y) }; - ImVec2 max = { Scale(IMAGE_X + IMAGE_WIDTH), Scale(IMAGE_Y + IMAGE_HEIGHT) }; + ImVec2 min = { Scale(g_aspectRatioOffsetX + IMAGE_X), Scale(g_aspectRatioOffsetY + IMAGE_Y) }; + ImVec2 max = { min.x + Scale(IMAGE_WIDTH), min.y + Scale(IMAGE_HEIGHT) }; drawList->AddImage(guestTexture, min, max, ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, a)); } @@ -511,7 +512,7 @@ static void DrawHeaderIcons() { auto drawList = ImGui::GetForegroundDrawList(); - float iconsPosX = 253.0f; + float iconsPosX = g_aspectRatioOffsetX + 253.0f; float iconsPosY = 79.0f; float iconsScale = 58; @@ -577,7 +578,7 @@ static void DrawScanlineBars() // Installer text const std::string &headerText = Localise(g_currentPage == WizardPage::Installing ? "Installer_Header_Installing" : "Installer_Header_Installer"); auto alphaMotion = ComputeMotionInstaller(g_appearTime, g_disappearTime, TITLE_ANIMATION_TIME, TITLE_ANIMATION_DURATION); - DrawTextWithOutline(g_dfsogeistdFont, Scale(42.0f), { Scale(285.0f), Scale(57.0f) }, IM_COL32(255, 195, 0, 255 * alphaMotion), headerText.c_str(), 4, IM_COL32(0, 0, 0, 255 * alphaMotion), IMGUI_SHADER_MODIFIER_TITLE_BEVEL); + DrawTextWithOutline(g_dfsogeistdFont, Scale(42.0f), { Scale(g_aspectRatioOffsetX + 285.0f), Scale(57.0f) }, IM_COL32(255, 195, 0, 255 * alphaMotion), headerText.c_str(), 4, IM_COL32(0, 0, 0, 255 * alphaMotion), IMGUI_SHADER_MODIFIER_TITLE_BEVEL); // Top bar line drawList->AddLine @@ -601,11 +602,6 @@ static void DrawScanlineBars() DrawVersionString(g_newRodinFont, IM_COL32(255, 255, 255, 70 * alphaMotion)); } -static float AlignToNextGrid(float value) -{ - return floor(value / GRID_SIZE) * GRID_SIZE; -} - static void DrawContainer(ImVec2 min, ImVec2 max, bool isTextArea) { auto &res = ImGui::GetIO().DisplaySize; @@ -641,8 +637,9 @@ static void DrawDescriptionContainer() auto drawList = ImGui::GetForegroundDrawList(); auto fontSize = Scale(26.0f); - ImVec2 descriptionMin = { Scale(AlignToNextGrid(CONTAINER_X)), Scale(AlignToNextGrid(CONTAINER_Y)) }; - ImVec2 descriptionMax = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH)), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT)) }; + ImVec2 descriptionMin = { Scale(g_aspectRatioOffsetX + CONTAINER_X), Scale(g_aspectRatioOffsetY + CONTAINER_Y) }; + ImVec2 descriptionMax = { Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH), Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT) }; + SetProceduralOrigin(descriptionMin); DrawContainer(descriptionMin, descriptionMax, true); char descriptionText[512]; @@ -692,8 +689,8 @@ static void DrawDescriptionContainer() ImVec2 imageMin = { - /* X */ Scale(CONTAINER_X) + (Scale(CONTAINER_WIDTH) / 2) - (imageScale / 2) - (hedgeDevTextSize.x / 2) - hedgeDevTextMarginX, - /* Y */ Scale(CONTAINER_Y) + (Scale(CONTAINER_HEIGHT) / 2) - (imageScale / 2) + imageMarginY + /* X */ Scale(g_aspectRatioOffsetX + CONTAINER_X) + (Scale(CONTAINER_WIDTH) / 2) - (imageScale / 2) - (hedgeDevTextSize.x / 2) - hedgeDevTextMarginX, + /* Y */ Scale(g_aspectRatioOffsetY + CONTAINER_Y) + (Scale(CONTAINER_HEIGHT) / 2) - (imageScale / 2) + imageMarginY }; ImVec2 imageMax = { imageMin.x + imageScale, imageMin.y + imageScale }; @@ -713,9 +710,9 @@ static void DrawDescriptionContainer() auto marqueeTextMarginX = Scale(5); auto marqueeTextMarginY = Scale(15); - ImVec2 textPos = { descriptionMax.x, Scale(CONTAINER_Y) + Scale(CONTAINER_HEIGHT) - marqueeTextSize.y - marqueeTextMarginY }; - ImVec2 textMin = { Scale(CONTAINER_X), textPos.y }; - ImVec2 textMax = { Scale(CONTAINER_X) + Scale(CONTAINER_WIDTH), Scale(CONTAINER_Y) + Scale(CONTAINER_HEIGHT) }; + ImVec2 textPos = { descriptionMax.x, Scale(g_aspectRatioOffsetY + CONTAINER_Y) + Scale(CONTAINER_HEIGHT) - marqueeTextSize.y - marqueeTextMarginY }; + ImVec2 textMin = { Scale(g_aspectRatioOffsetX + CONTAINER_X), textPos.y }; + ImVec2 textMax = { Scale(g_aspectRatioOffsetX + CONTAINER_X) + Scale(CONTAINER_WIDTH), Scale(g_aspectRatioOffsetY + CONTAINER_Y) + Scale(CONTAINER_HEIGHT) }; SetMarqueeFade(textMin, textMax, Scale(32)); DrawTextWithMarquee(g_seuratFont, fontSize, textPos, textMin, textMax, IM_COL32_WHITE, CREDITS_TEXT, g_appearTime, 0.9, Scale(250)); @@ -723,7 +720,7 @@ static void DrawDescriptionContainer() } ImVec2 sideMin = { descriptionMax.x, descriptionMin.y }; - ImVec2 sideMax = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH + SIDE_CONTAINER_WIDTH)), descriptionMax.y }; + ImVec2 sideMax = { res.x, descriptionMax.y }; DrawContainer(sideMin, sideMax, false); drawList->PopClipRect(); @@ -741,6 +738,8 @@ static void DrawDescriptionContainer() { ButtonGuide::Close(); } + + ResetProceduralOrigin(); } static void DrawButtonContainer(ImVec2 min, ImVec2 max, int baser, int baseg, float alpha) @@ -846,16 +845,16 @@ static void ComputeButtonColumnCoordinates(ButtonColumn buttonColumn, float &min switch (buttonColumn) { case ButtonColumnLeft: - minX = Scale(AlignToNextGrid(CONTAINER_X) + CONTAINER_BUTTON_GAP); - maxX = Scale(AlignToNextGrid(CONTAINER_X) + CONTAINER_BUTTON_GAP + CONTAINER_BUTTON_WIDTH); + minX = Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_BUTTON_GAP); + maxX = Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_BUTTON_GAP + CONTAINER_BUTTON_WIDTH); break; case ButtonColumnMiddle: - minX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH / 2.0f) - CONTAINER_BUTTON_WIDTH / 2.0f); - maxX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH / 2.0f) + CONTAINER_BUTTON_WIDTH / 2.0f); + minX = Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH / 2.0f - CONTAINER_BUTTON_WIDTH / 2.0f); + maxX = Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH / 2.0f + CONTAINER_BUTTON_WIDTH / 2.0f); break; case ButtonColumnRight: - minX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - CONTAINER_BUTTON_GAP - CONTAINER_BUTTON_WIDTH); - maxX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - CONTAINER_BUTTON_GAP); + minX = Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH - CONTAINER_BUTTON_GAP - CONTAINER_BUTTON_WIDTH); + maxX = Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH - CONTAINER_BUTTON_GAP); break; } } @@ -867,8 +866,8 @@ static void DrawSourceButton(ButtonColumn buttonColumn, float yRatio, const char ComputeButtonColumnCoordinates(buttonColumn, minX, maxX); float minusY = (CONTAINER_BUTTON_GAP + BUTTON_HEIGHT) * yRatio; - ImVec2 min = { minX, Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) - CONTAINER_BUTTON_GAP - BUTTON_HEIGHT - minusY) }; - ImVec2 max = { maxX, Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) - CONTAINER_BUTTON_GAP - minusY) }; + ImVec2 min = { minX, Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT - CONTAINER_BUTTON_GAP - BUTTON_HEIGHT - minusY) }; + ImVec2 max = { maxX, Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT - CONTAINER_BUTTON_GAP - minusY) }; DrawButton(min, max, sourceText, true, sourceSet, buttonPressed); } @@ -881,8 +880,8 @@ static void DrawProgressBar(float progressRatio) const uint32_t innerColor1 = IM_COL32(0, 32, 0, 255 * alpha); float xPadding = Scale(6.0f); float yPadding = Scale(3.0f); - ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP) }; - ImVec2 max = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; + ImVec2 min = { Scale(g_aspectRatioOffsetX + CONTAINER_X) + BOTTOM_X_GAP, Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; + ImVec2 max = { Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH - BOTTOM_X_GAP), Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; DrawButtonContainer(min, max, 0, 0, alpha); @@ -1069,8 +1068,8 @@ static void DrawLanguagePicker() ComputeButtonColumnCoordinates((i < 3) ? ButtonColumnLeft : ButtonColumnRight, minX, maxX); float minusY = (CONTAINER_BUTTON_GAP + BUTTON_HEIGHT) * (float(i % 3)); - ImVec2 min = { minX, Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) - CONTAINER_BUTTON_GAP - BUTTON_HEIGHT - minusY) }; - ImVec2 max = { maxX, Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) - CONTAINER_BUTTON_GAP - minusY) }; + ImVec2 min = { minX, Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT - CONTAINER_BUTTON_GAP - BUTTON_HEIGHT - minusY) }; + ImVec2 max = { maxX, Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT - CONTAINER_BUTTON_GAP - minusY) }; // TODO: The active button should change its style to show an enabled toggle if it matches the current language. @@ -1095,8 +1094,8 @@ static void DrawSourcePickers() ImVec2 textSize = ComputeTextSize(g_dfsogeistdFont, addFilesText.c_str(), 20.0f, squashRatio, ADD_BUTTON_MAX_TEXT_WIDTH); textSize.x += BUTTON_TEXT_GAP; - ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP) }; - ImVec2 max = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP + textSize.x * squashRatio), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; + ImVec2 min = { Scale(g_aspectRatioOffsetX + CONTAINER_X + BOTTOM_X_GAP), Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; + ImVec2 max = { Scale(g_aspectRatioOffsetX + CONTAINER_X + BOTTOM_X_GAP + textSize.x * squashRatio), Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; DrawButton(min, max, addFilesText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); if (buttonPressed) { @@ -1231,8 +1230,8 @@ static void DrawNextButton() ImVec2 textSize = ComputeTextSize(g_newRodinFont, buttonText.c_str(), 20.0f, squashRatio, NEXT_BUTTON_MAX_TEXT_WIDTH); textSize.x += BUTTON_TEXT_GAP; - ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - textSize.x * squashRatio - BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP) }; - ImVec2 max = { Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH) - BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; + ImVec2 min = { Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH - textSize.x * squashRatio - BOTTOM_X_GAP), Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) }; + ImVec2 max = { Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH - BOTTOM_X_GAP), Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; bool buttonPressed = false; DrawButton(min, max, buttonText.c_str(), false, nextButtonEnabled, buttonPressed, NEXT_BUTTON_MAX_TEXT_WIDTH); @@ -1317,10 +1316,10 @@ static void DrawHorizontalBorder(bool bottomBorder) const uint32_t FADE_COLOR_RIGHT = IM_COL32(155, 225, 155, 0); auto drawList = ImGui::GetForegroundDrawList(); double borderScale = 1.0 - ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_LINE_ANIMATION_TIME, CONTAINER_LINE_ANIMATION_DURATION); - float midX = Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH / 5)); - float minX = std::lerp(Scale(AlignToNextGrid(CONTAINER_X) - BORDER_SIZE - BORDER_OVERSHOOT), midX, borderScale); - float maxX = std::lerp(Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH + SIDE_CONTAINER_WIDTH) + BORDER_OVERSHOOT), midX, borderScale); - float minY = bottomBorder ? Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT)) : Scale(AlignToNextGrid(CONTAINER_Y) - BORDER_SIZE); + float midX = Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH / 5); + float minX = std::lerp(Scale(g_aspectRatioOffsetX + CONTAINER_X - BORDER_SIZE - BORDER_OVERSHOOT), midX, borderScale); + float maxX = std::lerp(Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH + SIDE_CONTAINER_WIDTH + BORDER_OVERSHOOT), midX, borderScale); + float minY = bottomBorder ? Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT) : Scale(g_aspectRatioOffsetY + CONTAINER_Y - BORDER_SIZE); float maxY = minY + Scale(BORDER_SIZE); drawList->AddRectFilledMultiColor ( @@ -1349,11 +1348,11 @@ static void DrawVerticalBorder(bool rightBorder) const uint32_t FADE_COLOR = IM_COL32(155, rightBorder ? 225 : 155, 155, 0); auto drawList = ImGui::GetForegroundDrawList(); double borderScale = 1.0 - ComputeMotionInstaller(g_appearTime, g_disappearTime, CONTAINER_LINE_ANIMATION_TIME, CONTAINER_LINE_ANIMATION_DURATION); - float minX = rightBorder ? Scale(AlignToNextGrid(CONTAINER_X + CONTAINER_WIDTH)) : Scale(AlignToNextGrid(CONTAINER_X) - BORDER_SIZE); + float minX = rightBorder ? Scale(g_aspectRatioOffsetX + CONTAINER_X + CONTAINER_WIDTH) : Scale(g_aspectRatioOffsetX + CONTAINER_X - BORDER_SIZE); float maxX = minX + Scale(BORDER_SIZE); - float midY = Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT / 2)); - float minY = std::lerp(Scale(AlignToNextGrid(CONTAINER_Y) - BORDER_OVERSHOOT), midY, borderScale); - float maxY = std::lerp(Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BORDER_OVERSHOOT), midY, borderScale); + float midY = Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT / 2); + float minY = std::lerp(Scale(g_aspectRatioOffsetY + CONTAINER_Y - BORDER_OVERSHOOT), midY, borderScale); + float maxY = std::lerp(Scale(g_aspectRatioOffsetY + CONTAINER_Y + CONTAINER_HEIGHT + BORDER_OVERSHOOT), midY, borderScale); drawList->AddRectFilledMultiColor ( { minX, minY }, diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index eb8ac15..cb48548 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -42,9 +42,15 @@ static constexpr double CONTAINER_FULL_DURATION = CONTAINER_BACKGROUND_TIME + CO static constexpr double CONTAINER_CATEGORY_TIME = (CONTAINER_INNER_TIME + CONTAINER_BACKGROUND_TIME) / 2.0; static constexpr double CONTAINER_CATEGORY_DURATION = 12.0; -constexpr float COMMON_PADDING_POS_Y = 118.0f; -constexpr float COMMON_PADDING_POS_X = 30.0f; -constexpr float INFO_CONTAINER_POS_X = 870.0f; +static constexpr float CONTAINER_POS_Y = 118.0f; + +static constexpr float SETTINGS_WIDE_GRID_COUNT = 90.0f; +static constexpr float INFO_WIDE_GRID_COUNT = 42.0f; +static constexpr float PADDING_WIDE_GRID_COUNT = 3.0f; + +static constexpr float SETTINGS_NARROW_GRID_COUNT = 70.0f; +static constexpr float INFO_NARROW_GRID_COUNT = 34.0f; +static constexpr float PADDING_NARROW_GRID_COUNT = 1.0f; static constexpr int32_t g_categoryCount = 4; static int32_t g_categoryIndex; @@ -151,12 +157,18 @@ static void DrawScanlineBars() SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + float optionsX; + if (g_aspectRatio >= WIDE_ASPECT_RATIO) + optionsX = g_aspectRatioOffsetX; + else + optionsX = (1.0f - g_narrowOffsetScale) * -20.0f; + // Options text DrawTextWithOutline ( g_dfsogeistdFont, Scale(48.0f), - { Scale(122.0f), Scale(56.0f) }, + { Scale(optionsX + 122.0f), Scale(56.0f) }, IM_COL32(255, 190, 33, 255), Localise("Options_Header_Name").c_str(), 4, @@ -187,7 +199,7 @@ static void DrawScanlineBars() static float AlignToNextGrid(float value) { - return floor(value / GRID_SIZE) * GRID_SIZE; + return round(value / GRID_SIZE) * GRID_SIZE; } static void DrawContainer(ImVec2 min, ImVec2 max) @@ -306,8 +318,11 @@ static bool DrawCategories() auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); + constexpr float NARROW_PADDING_GRID_COUNT = 1.5f; + constexpr float WIDE_PADDING_GRID_COUNT = 3.0f; + float gridSize = Scale(GRID_SIZE); - float textPadding = gridSize * 3.0f; + float textPadding = gridSize * Lerp(NARROW_PADDING_GRID_COUNT, WIDE_PADDING_GRID_COUNT, g_narrowOffsetScale); float tabPadding = gridSize; float size = Scale(32.0f); @@ -449,8 +464,11 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf auto clipRectMax = drawList->GetClipRectMax(); auto& padState = SWA::CInputState::GetInstance()->GetPadState(); + constexpr float OPTION_NARROW_GRID_COUNT = 36.0f; + constexpr float OPTION_WIDE_GRID_COUNT = 54.0f; + auto gridSize = Scale(GRID_SIZE); - auto optionWidth = gridSize * 54.0f; + auto optionWidth = gridSize * floor(Lerp(OPTION_NARROW_GRID_COUNT, OPTION_WIDE_GRID_COUNT, g_narrowOffsetScale)); auto optionHeight = gridSize * 5.5f; auto optionPadding = gridSize * 0.5f; auto valueWidth = Scale(192.0f); @@ -917,6 +935,7 @@ static void DrawConfigOptions() DrawConfigOption(rowCount++, yOffset, &Config::GITextureFiltering, true); DrawConfigOption(rowCount++, yOffset, &Config::MotionBlur, true); DrawConfigOption(rowCount++, yOffset, &Config::XboxColorCorrection, true); + DrawConfigOption(rowCount++, yOffset, &Config::CutsceneAspectRatio, true); DrawConfigOption(rowCount++, yOffset, &Config::UIScaleMode, true); break; @@ -998,13 +1017,11 @@ static void DrawConfigOptions() } } -static void DrawSettingsPanel() +static void DrawSettingsPanel(ImVec2 settingsMin, ImVec2 settingsMax) { auto drawList = ImGui::GetForegroundDrawList(); - ImVec2 settingsMin = { Scale(AlignToNextGrid(COMMON_PADDING_POS_X)), Scale(AlignToNextGrid(COMMON_PADDING_POS_Y)) }; - ImVec2 settingsMax = { Scale(AlignToNextGrid(INFO_CONTAINER_POS_X - COMMON_PADDING_POS_X)), Scale(AlignToNextGrid(720.0f - COMMON_PADDING_POS_Y)) }; - + SetProceduralOrigin(settingsMin); DrawContainer(settingsMin, settingsMax); if (DrawCategories()) @@ -1018,31 +1035,34 @@ static void DrawSettingsPanel() ResetSelection(); } + ResetProceduralOrigin(); + // Pop clip rect from DrawContainer drawList->PopClipRect(); } -static void DrawInfoPanel() +static void DrawInfoPanel(ImVec2 infoMin, ImVec2 infoMax) { auto drawList = ImGui::GetForegroundDrawList(); - ImVec2 infoMin = { Scale(AlignToNextGrid(INFO_CONTAINER_POS_X)), Scale(AlignToNextGrid(COMMON_PADDING_POS_Y)) }; - ImVec2 infoMax = { Scale(AlignToNextGrid(1280.0f - COMMON_PADDING_POS_X)), Scale(AlignToNextGrid(720.0f - COMMON_PADDING_POS_Y)) }; - + SetProceduralOrigin(infoMin); DrawContainer(infoMin, infoMax); auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - ImVec2 thumbnailMax = { clipRectMax.x, clipRectMin.y + Scale(198.0f) }; - if (g_selectedItem) { auto desc = g_selectedItem->GetDescription(Config::Language); auto thumbnail = GetThumbnail(g_selectedItem); - if (thumbnail) - drawList->AddImage(thumbnail, clipRectMin, thumbnailMax); + float aspectRatio = float(thumbnail->width) / thumbnail->height; + float thumbnailHeight = (clipRectMax.x - clipRectMin.x) / aspectRatio; + + ImVec2 thumbnailMin = { clipRectMin.x, clipRectMin.y + Scale(GRID_SIZE / 2.0f) }; + ImVec2 thumbnailMax = { clipRectMax.x, thumbnailMin.y + thumbnailHeight }; + + drawList->AddImage(thumbnail, thumbnailMin, thumbnailMax); if (g_inaccessibleReason) { @@ -1057,8 +1077,8 @@ static void DrawInfoPanel() auto resScale = round(*(float*)g_selectedItem->GetValue() * 1000) / 1000; std::snprintf(buf, sizeof(buf), desc.c_str(), - (int)((float)GameWindow::s_width * resScale), - (int)((float)GameWindow::s_height * resScale)); + (int)((float)Video::s_viewportWidth * resScale), + (int)((float)Video::s_viewportHeight * resScale)); desc = buf; } @@ -1080,6 +1100,8 @@ static void DrawInfoPanel() ); } + ResetProceduralOrigin(); + // Pop clip rect from DrawContainer drawList->PopClipRect(); } @@ -1197,8 +1219,25 @@ void OptionsMenu::Draw() drawList->AddRectFilled({ 0.0f, 0.0f }, res, IM_COL32(0, 0, 0, 223)); DrawScanlineBars(); - DrawSettingsPanel(); - DrawInfoPanel(); + + float settingsGridCount = floor(Lerp(SETTINGS_NARROW_GRID_COUNT, SETTINGS_WIDE_GRID_COUNT, g_narrowOffsetScale)); + float paddingGridCount = Lerp(PADDING_NARROW_GRID_COUNT, PADDING_WIDE_GRID_COUNT, g_narrowOffsetScale); + float infoGridCount = floor(Lerp(INFO_NARROW_GRID_COUNT, INFO_WIDE_GRID_COUNT, g_narrowOffsetScale)); + float totalGridCount = settingsGridCount + paddingGridCount + infoGridCount; + + float offsetX = g_aspectRatioOffsetX + (1280.0f - ((GRID_SIZE * totalGridCount) - 1)) / 2.0f; + float minY = Scale(g_aspectRatioOffsetY + CONTAINER_POS_Y); + float maxY = Scale(g_aspectRatioOffsetY + (720.0f - CONTAINER_POS_Y + 1.0f)); + + DrawSettingsPanel( + { Scale(offsetX), minY }, + { Scale(offsetX + settingsGridCount * GRID_SIZE), maxY } + ); + + DrawInfoPanel( + { Scale(offsetX + (settingsGridCount + paddingGridCount) * GRID_SIZE), minY }, + { Scale(offsetX + totalGridCount * GRID_SIZE), maxY } + ); if (g_isStage) DrawFadeTransition(); diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index 852ef2b..0a9a203 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -148,15 +148,17 @@ CONFIG_DEFINE_ENUM_TEMPLATE(EWindowState) enum class EAspectRatio : uint32_t { Auto, - Square, - Widescreen + Wide, + Narrow, + OriginalNarrow }; CONFIG_DEFINE_ENUM_TEMPLATE(EAspectRatio) { { "Auto", EAspectRatio::Auto }, - { "4:3", EAspectRatio::Square }, - { "16:9", EAspectRatio::Widescreen } + { "16:9", EAspectRatio::Wide }, + { "4:3", EAspectRatio::Narrow }, + { "Original 4:3", EAspectRatio::OriginalNarrow }, }; enum class ETripleBuffering : uint32_t @@ -256,16 +258,26 @@ CONFIG_DEFINE_ENUM_TEMPLATE(EMotionBlur) { "Enhanced", EMotionBlur::Enhanced } }; +enum class ECutsceneAspectRatio : uint32_t +{ + Original, + Unlocked +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(ECutsceneAspectRatio) +{ + { "Original", ECutsceneAspectRatio::Original }, + { "Unlocked", ECutsceneAspectRatio::Unlocked } +}; + enum class EUIScaleMode : uint32_t { - Stretch, Edge, Centre }; CONFIG_DEFINE_ENUM_TEMPLATE(EUIScaleMode) { - { "Stretch", EUIScaleMode::Stretch }, { "Edge", EUIScaleMode::Edge }, { "Centre", EUIScaleMode::Centre }, { "Center", EUIScaleMode::Centre } @@ -661,6 +673,7 @@ public: CONFIG_DEFINE_ENUM("Video", EDepthOfFieldQuality, DepthOfFieldQuality, EDepthOfFieldQuality::Auto); CONFIG_DEFINE_ENUM_LOCALISED("Video", EMotionBlur, MotionBlur, EMotionBlur::Original); CONFIG_DEFINE_LOCALISED("Video", bool, XboxColorCorrection, false); + CONFIG_DEFINE_ENUM_LOCALISED("Video", ECutsceneAspectRatio, CutsceneAspectRatio, ECutsceneAspectRatio::Original); CONFIG_DEFINE_ENUM_LOCALISED("Video", EUIScaleMode, UIScaleMode, EUIScaleMode::Edge); CONFIG_DEFINE_HIDDEN("Exports", bool, AllowCancellingUnleash, false); diff --git a/UnleashedRecompLib/config/SWA.toml b/UnleashedRecompLib/config/SWA.toml index 6161629..0282337 100644 --- a/UnleashedRecompLib/config/SWA.toml +++ b/UnleashedRecompLib/config/SWA.toml @@ -91,16 +91,14 @@ registers = ["r11"] [[midasm_hook]] name = "CameraAspectRatioMidAsmHook" -address = 0x82468E84 -registers = ["r31"] -jump_address_on_true = 0x82468E88 -jump_address_on_false = 0x82468EE0 +address = 0x82468E78 +registers = ["r30", "r31"] [[midasm_hook]] -name = "CameraBoostAspectRatioMidAsmHook" -address = 0x8246BDA0 -registers = ["r31", "f0", "f10", "f12"] -jump_address_on_true = 0x8246BDAC +name = "CameraFieldOfViewMidAsmHook" +address = 0x82468EDC +registers = ["r31", "f31"] +jump_address = 0x82468EE0 [[midasm_hook]] name = "ResetScoreOnRestartMidAsmHook" @@ -590,3 +588,78 @@ jump_address = 0x82B723BC name = "DisableDLCIconMidAsmHook" address = 0x825756B0 jump_address_on_true = 0x825756E0 + +[[midasm_hook]] +name = "MakeCsdProjectMidAsmHook" +address = 0x825E4120 +registers = ["r3", "r29"] + +[[midasm_hook]] +name = "RenderCsdCastNodeMidAsmHook" +address = 0x830C6A58 +registers = ["r10", "r27"] + +[[midasm_hook]] +name = "RenderCsdCastMidAsmHook" +address = 0x830C6A98 +registers = ["r4"] + +[[midasm_hook]] +name = "ComputeScreenPositionMidAsmHook" +address = 0x82923204 +registers = ["f1", "f2"] + +[[midasm_hook]] +name = "ComputeScreenPositionMidAsmHook" +address = 0x82AD7914 +registers = ["f1", "f2"] + +[[midasm_hook]] +name = "WorldMapInfoMidAsmHook" +address = 0x8257AF34 +registers = ["r4"] + +[[midasm_hook]] +name = "AddPrimitive2DMidAsmHook" +address = 0x824DB3E4 +registers = ["r3"] + +[[midasm_hook]] +name = "ObjGetItemFieldOfViewMidAsmHook" +address = 0x82692934 +registers = ["r1", "f1"] + +[[midasm_hook]] +name = "WorldMapProjectionMidAsmHook" +address = 0x82574E00 +registers = ["v63", "v62"] + +[[midasm_hook]] +name = "ViewRingFieldOfViewMidAsmHook" +address = 0x825EBDF0 +registers = ["r1", "f1"] + +[[midasm_hook]] +name = "ViewRingYMidAsmHook" +address = 0x825EBF1C +registers = ["f0"] + +[[midasm_hook]] +name = "ViewRingXMidAsmHook" +address = 0x825EBF68 +registers = ["f0", "v62"] + +[[midasm_hook]] +name = "InspireLetterboxTopMidAsmHook" +address = 0x82B8AB78 +registers = ["r3"] + +[[midasm_hook]] +name = "InspireLetterboxBottomMidAsmHook" +address = 0x82B8ABAC +registers = ["r3"] + +[[midasm_hook]] +name = "InspireSubtitleMidAsmHook" +address = 0x82B949B0 +registers = ["r3"]