diff --git a/UnleashedRecomp/gpu/imgui_common.h b/UnleashedRecomp/gpu/imgui_common.h new file mode 100644 index 00000000..d550bf38 --- /dev/null +++ b/UnleashedRecomp/gpu/imgui_common.h @@ -0,0 +1,32 @@ +#pragma once + +#define IMGUI_SHADER_MODIFIER_NONE 0 +#define IMGUI_SHADER_MODIFIER_SCANLINE 1 +#define IMGUI_SHADER_MODIFIER_CHECKERBOARD 2 +#define IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON 3 + +#ifdef __cplusplus + +enum class ImGuiCallback : int32_t +{ + SetGradient = -1, + SetShaderModifier = -2 +}; + +union ImGuiCallbackData +{ + struct + { + float gradientMin[2]; + float gradientMax[2]; + uint32_t gradientTop; + uint32_t gradientBottom; + } setGradient; + + struct + { + uint32_t shaderModifier; + } setShaderModifier; +}; + +#endif diff --git a/UnleashedRecomp/gpu/shader/imgui_common.hlsli b/UnleashedRecomp/gpu/shader/imgui_common.hlsli index d54f5295..fb838d7f 100644 --- a/UnleashedRecomp/gpu/shader/imgui_common.hlsli +++ b/UnleashedRecomp/gpu/shader/imgui_common.hlsli @@ -2,6 +2,11 @@ struct PushConstants { + float2 GradientMin; + float2 GradientMax; + uint GradientTop; + uint GradientBottom; + uint ShaderModifier; uint Texture2DDescriptorIndex; float2 InverseDisplaySize; }; diff --git a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl index bc0cd761..43400a85 100644 --- a/UnleashedRecomp/gpu/shader/imgui_ps.hlsl +++ b/UnleashedRecomp/gpu/shader/imgui_ps.hlsl @@ -1,11 +1,90 @@ #include "imgui_common.hlsli" +#include "../imgui_common.h" + +float4 DecodeColor(uint color) +{ + return float4(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF, (color >> 24) & 0xFF) / 255.0; +} + +float SamplePoint(int2 position) +{ + switch (g_PushConstants.ShaderModifier) + { + case IMGUI_SHADER_MODIFIER_SCANLINE: + { + if (int(position.y) % 2 == 0) + return 0.0; + + break; + } + case IMGUI_SHADER_MODIFIER_CHECKERBOARD: + { + int remnantX = int(position.x) % 9; + int remnantY = int(position.y) % 9; + + if (remnantX == 0 || remnantY == 0) + return 0.0; + + if ((remnantY % 2) == 0) + return 0.5; + + break; + } + case IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON: + { + if (int(position.y) % 2 == 0) + return 0.5; + + break; + } + } + + return 1.0; +} + +float SampleLinear(float2 uvTexspace) +{ + int2 integerPart = floor(uvTexspace); + float2 fracPart = frac(uvTexspace); + + float topLeft = SamplePoint(integerPart + float2(0, 0)); + float topRight = SamplePoint(integerPart + float2(1, 0)); + float bottomLeft = SamplePoint(integerPart + float2(0, 1)); + float bottomRight = SamplePoint(integerPart + float2(1, 1)); + + float top = lerp(topLeft, topRight, fracPart.x); + float bottom = lerp(bottomLeft, bottomRight, fracPart.x); + + return lerp(top, bottom, fracPart.y); +} + +float PixelAntialiasing(float2 uvTexspace) +{ + float2 seam = floor(uvTexspace + 0.5); + 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); + else + uvTexspace *= min(1.0, g_PushConstants.InverseDisplaySize.x * 1280.0f); + + return SampleLinear(uvTexspace); +} float4 main(in Interpolators interpolators) : SV_Target { float4 color = interpolators.Color; + color.a *= PixelAntialiasing(interpolators.Position.xy - 0.5); if (g_PushConstants.Texture2DDescriptorIndex != 0) color *= g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex].Sample(g_SamplerDescriptorHeap[0], interpolators.UV); + if (any(g_PushConstants.GradientMin != g_PushConstants.GradientMax)) + { + float2 factor = saturate((interpolators.Position.xy - g_PushConstants.GradientMin) / (g_PushConstants.GradientMax - g_PushConstants.GradientMin)); + color *= lerp(DecodeColor(g_PushConstants.GradientTop), DecodeColor(g_PushConstants.GradientBottom), factor.y); + } + return color; } diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 2c65e2be..d170ab49 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -10,6 +10,7 @@ #include #include "imgui_snapshot.h" +#include "imgui_common.h" #include "gpu/video.h" #include "ui/window.h" #include "config.h" @@ -985,6 +986,17 @@ static void ExecuteCopyCommandList(const T& function) static constexpr uint32_t PITCH_ALIGNMENT = 0x100; static constexpr uint32_t PLACEMENT_ALIGNMENT = 0x200; +struct ImGuiPushConstants +{ + ImVec2 gradientMin{}; + ImVec2 gradientMax{}; + ImU32 gradientTop{}; + ImU32 gradientBottom{}; + uint32_t shaderModifier{}; + uint32_t texture2DDescriptorIndex{}; + ImVec2 inverseDisplaySize{}; +}; + static void CreateImGuiBackend() { ImGuiIO& io = ImGui::GetIO(); @@ -1062,7 +1074,7 @@ static void CreateImGuiBackend() descriptorSetBuilder.end(true, SAMPLER_DESCRIPTOR_SIZE); pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder); - pipelineLayoutBuilder.addPushConstant(0, 2, 12, RenderShaderStageFlag::VERTEX | RenderShaderStageFlag::PIXEL); + pipelineLayoutBuilder.addPushConstant(0, 2, sizeof(ImGuiPushConstants), RenderShaderStageFlag::VERTEX | RenderShaderStageFlag::PIXEL); pipelineLayoutBuilder.end(); g_imPipelineLayout = pipelineLayoutBuilder.create(g_device.get()); @@ -1596,8 +1608,9 @@ static void ProcDrawImGui(const RenderCommand& cmd) auto& drawData = g_imSnapshot.DrawData; commandList->setViewports(RenderViewport(drawData.DisplayPos.x, drawData.DisplayPos.y, drawData.DisplaySize.x, drawData.DisplaySize.y)); - float inverseDisplaySize[] = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y }; - commandList->setGraphicsPushConstants(0, inverseDisplaySize, 4, 8); + ImGuiPushConstants pushConstants{}; + pushConstants.inverseDisplaySize = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y }; + commandList->setGraphicsPushConstants(0, &pushConstants); for (int i = 0; i < drawData.CmdListsCount; i++) { @@ -1616,14 +1629,30 @@ static void ProcDrawImGui(const RenderCommand& cmd) for (int j = 0; j < drawList->CmdBuffer.Size; j++) { auto& drawCmd = drawList->CmdBuffer[j]; + if (drawCmd.UserCallback != nullptr) + { + auto callbackData = reinterpret_cast(drawCmd.UserCallbackData); - if (drawCmd.ClipRect.z <= drawCmd.ClipRect.x || drawCmd.ClipRect.w <= drawCmd.ClipRect.y) - continue; + switch (static_cast(reinterpret_cast(drawCmd.UserCallback))) + { + case ImGuiCallback::SetGradient: + commandList->setGraphicsPushConstants(0, &callbackData->setGradient, offsetof(ImGuiPushConstants, gradientMin), sizeof(callbackData->setGradient)); + break; + case ImGuiCallback::SetShaderModifier: + commandList->setGraphicsPushConstants(0, &callbackData->setShaderModifier, offsetof(ImGuiPushConstants, shaderModifier), sizeof(callbackData->setShaderModifier)); + break; + } + } + else + { + if (drawCmd.ClipRect.z <= drawCmd.ClipRect.x || drawCmd.ClipRect.w <= drawCmd.ClipRect.y) + continue; - uint32_t descriptorIndex = uint32_t(drawCmd.GetTexID()); - commandList->setGraphicsPushConstants(0, &descriptorIndex, 0, 4); - commandList->setScissors(RenderRect(int32_t(drawCmd.ClipRect.x), int32_t(drawCmd.ClipRect.y), int32_t(drawCmd.ClipRect.z), int32_t(drawCmd.ClipRect.w))); - commandList->drawIndexedInstanced(drawCmd.ElemCount, 1, drawCmd.IdxOffset, drawCmd.VtxOffset, 0); + uint32_t descriptorIndex = uint32_t(drawCmd.GetTexID()); + commandList->setGraphicsPushConstants(0, &descriptorIndex, offsetof(ImGuiPushConstants, texture2DDescriptorIndex), sizeof(descriptorIndex)); + commandList->setScissors(RenderRect(int32_t(drawCmd.ClipRect.x), int32_t(drawCmd.ClipRect.y), int32_t(drawCmd.ClipRect.z), int32_t(drawCmd.ClipRect.w))); + commandList->drawIndexedInstanced(drawCmd.ElemCount, 1, drawCmd.IdxOffset, drawCmd.VtxOffset, 0); + } } } } diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index 50d134e2..88b663db 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -1,6 +1,7 @@ #include "options_menu.h" #include "window.h" #include +#include static ImFont* g_seuratFont; static ImFont* g_dfsogeistdFont; @@ -14,6 +15,45 @@ void OptionsMenu::Init() g_newRodinFont = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-DB.otf", 16.0f); } +static std::vector> g_callbackData; +static uint32_t g_callbackDataIndex = 0; + +static ImGuiCallbackData* AddCallback(ImGuiCallback callback) +{ + if (g_callbackDataIndex >= g_callbackData.size()) + g_callbackData.emplace_back(std::make_unique()); + + auto& callbackData = g_callbackData[g_callbackDataIndex]; + ++g_callbackDataIndex; + + ImGui::GetForegroundDrawList()->AddCallback(reinterpret_cast(callback), callbackData.get()); + + return callbackData.get(); +} + +static void SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom) +{ + auto callbackData = AddCallback(ImGuiCallback::SetGradient); + callbackData->setGradient.gradientMin[0] = min.x; + callbackData->setGradient.gradientMin[1] = min.y; + callbackData->setGradient.gradientMax[0] = max.x; + callbackData->setGradient.gradientMax[1] = max.y; + callbackData->setGradient.gradientTop = top; + callbackData->setGradient.gradientBottom = bottom; +} + +static void ResetGradient() +{ + auto callbackData = AddCallback(ImGuiCallback::SetGradient); + memset(&callbackData->setGradient, 0, sizeof(callbackData->setGradient)); +} + +static void SetShaderModifier(uint32_t shaderModifier) +{ + auto callbackData = AddCallback(ImGuiCallback::SetShaderModifier); + callbackData->setShaderModifier.shaderModifier = shaderModifier; +} + static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, int32_t outlineSize, ImU32 outlineColor) { auto drawList = ImGui::GetForegroundDrawList(); @@ -55,8 +95,8 @@ static float Scale(float size) static void DrawScanlineBars() { - constexpr uint32_t COLOR0 = IM_COL32(58, 71, 15, 0); - constexpr uint32_t COLOR1 = IM_COL32(58, 71, 15, 255); + constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0); + constexpr uint32_t COLOR1 = IM_COL32(203, 255, 0, 55); constexpr uint32_t OUTLINE_COLOR = IM_COL32(115, 178, 104, 255); float height = Scale(105.0f); @@ -64,6 +104,8 @@ static void DrawScanlineBars() auto& res = ImGui::GetIO().DisplaySize; auto drawList = ImGui::GetForegroundDrawList(); + SetShaderModifier(IMGUI_SHADER_MODIFIER_SCANLINE); + // Top bar drawList->AddRectFilledMultiColor( { 0.0f, 0.0f }, @@ -73,44 +115,60 @@ static void DrawScanlineBars() COLOR1, COLOR1); - drawList->AddLine( - { 0.0f, height }, - { res.x, height }, - OUTLINE_COLOR); - - // Options text - DrawTextWithOutline(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", Scale(4), IM_COL32_BLACK); - // Bottom bar drawList->AddRectFilledMultiColor( - { res.x, res.y }, + { res.x, res.y }, { 0.0f, res.y - height }, - COLOR0, + COLOR0, COLOR0, COLOR1, COLOR1); + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); + + // Options text + DrawTextWithOutline(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", Scale(4), IM_COL32_BLACK); + + // Top bar line + drawList->AddLine( + { 0.0f, height }, + { res.x, height }, + OUTLINE_COLOR); + + // Bottom bar line drawList->AddLine( { 0.0f, res.y - height }, { res.x, res.y - height }, OUTLINE_COLOR); } -static constexpr size_t GRID_SIZE = 8; +static constexpr size_t GRID_SIZE = 9; + +static float AlignToNextGrid(float value) +{ + return floor(value / GRID_SIZE) * GRID_SIZE; +} static void DrawContainer(const ImVec2& min, const ImVec2& max) { auto& res = ImGui::GetIO().DisplaySize; auto drawList = ImGui::GetForegroundDrawList(); + constexpr uint32_t BACKGROUND_COLOR = IM_COL32(0, 0, 0, 223); constexpr uint32_t COLOR = IM_COL32(0, 49, 0, 255); constexpr uint32_t INNER_COLOR = IM_COL32(0, 33, 0, 255); constexpr uint32_t LINE_COLOR = IM_COL32(0, 89, 0, 255); float gridSize = Scale(GRID_SIZE); + drawList->AddRectFilled(min, max, BACKGROUND_COLOR); // Background + + SetShaderModifier(IMGUI_SHADER_MODIFIER_CHECKERBOARD); + drawList->AddRectFilled(min, max, COLOR); // Container - drawList->AddRectFilled({ min.x + gridSize, min.y + gridSize }, {max.x - gridSize, max.y - gridSize}, INNER_COLOR); // Inner container + drawList->AddRectFilled({ min.x + gridSize, min.y + gridSize }, { max.x - gridSize, max.y - gridSize }, INNER_COLOR); // Inner container + + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); // Top line drawList->AddLine({ min.x + gridSize, min.y + gridSize }, { min.x + gridSize, min.y + gridSize * 2.0f }, LINE_COLOR, Scale(1)); // Vertical left @@ -123,7 +181,7 @@ static void DrawContainer(const ImVec2& min, const ImVec2& max) drawList->AddLine({ max.x - gridSize, max.y - gridSize }, { max.x - gridSize, max.y - gridSize * 2.0f }, LINE_COLOR, Scale(1)); // Vertical right // The draw area - drawList->PushClipRect({ min.x + gridSize * 2.0f, min.y + gridSize * 2.0f }, { max.x - gridSize * 2.0f, max.y - gridSize * 2.0f }); + drawList->PushClipRect({ min.x + gridSize * 2.0f, min.y + gridSize * 2.0f }, { max.x - gridSize * 2.0f + 1.0f, max.y - gridSize * 2.0f + 1.0f }); } static constexpr const char* CATEGORIES[] = @@ -148,10 +206,10 @@ static void DrawCategories() auto clipRectMax = drawList->GetClipRectMax(); float gridSize = Scale(GRID_SIZE); - float tabPadding = gridSize * 2.0f; + float tabPadding = gridSize; float tabWidth = ComputeSizeWithPadding(clipRectMax.x - clipRectMin.x, tabPadding, std::size(CATEGORIES)); float tabWidthWithPadding = tabWidth + tabPadding; - float tabHeight = gridSize * 4.5f; + float tabHeight = gridSize * 4.0f; for (size_t i = 0; i < std::size(CATEGORIES); i++) { @@ -160,23 +218,40 @@ static void DrawCategories() if (g_categoryIndex == i || ImGui::IsMouseHoveringRect(min, max, false)) { - drawList->AddRectFilled(min, max, IM_COL32(0, 96, 0, 255)); + SetShaderModifier(IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON); + + drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 130, 0, 223), IM_COL32(0, 130, 0, 178), IM_COL32(0, 130, 0, 223), IM_COL32(0, 130, 0, 178)); + drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 0, 0, 13), IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 55), IM_COL32(0, 0, 0, 6)); + drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 130, 0, 13), IM_COL32(0, 130, 0, 111), IM_COL32(0, 130, 0, 0), IM_COL32(0, 130, 0, 55)); + + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) g_categoryIndex = i; } - float size = Scale(24.0f); + float size = Scale(32.0f); auto textSize = g_dfsogeistdFont->CalcTextSizeA(size, FLT_MAX, 0.0f, CATEGORIES[i]); + min.x += (tabWidth - textSize.x) / 2.0f; + min.y += (tabHeight - textSize.y) / 2.0f; + + SetGradient( + min, + { min.x + textSize.x, min.y + textSize.y }, + IM_COL32(128, 255, 0, 255), + IM_COL32(255, 192, 0, 255)); + DrawTextWithOutline( g_dfsogeistdFont, size, - { min.x + (tabWidth - textSize.x) / 2.0f, min.y + (tabHeight - textSize.y) / 2.0f }, - IM_COL32(150, 230, 30, 255), + min, + IM_COL32_WHITE, CATEGORIES[i], Scale(3), IM_COL32_BLACK); + + ResetGradient(); } drawList->PushClipRect({ clipRectMin.x, clipRectMin.y + gridSize * 6.0f }, clipRectMax); @@ -188,8 +263,8 @@ static void DrawConfigOptions() auto clipRectMin = drawList->GetClipRectMin(); auto clipRectMax = drawList->GetClipRectMax(); - constexpr ImU32 COLOR0 = IM_COL32(113, 57, 17, 255); - constexpr ImU32 COLOR1 = IM_COL32(74, 138, 25, 255); + constexpr ImU32 COLOR0 = IM_COL32(0xE2, 0x71, 0x22, 0x80); + constexpr ImU32 COLOR1 = IM_COL32(0x92, 0xFF, 0x31, 0x80); float gridSize = Scale(GRID_SIZE); float optionWidth = gridSize * 56.0f; @@ -220,7 +295,13 @@ static void DrawConfigOptions() min = { max.x + (clipRectMax.x - max.x - valueWidth) / 2.0f, min.y + (optionHeight - valueHeight) / 2.0f }; max = { min.x + valueWidth, min.y + valueHeight }; - drawList->AddRectFilled(min, max, IM_COL32(0, 96, 0, 255)); + SetShaderModifier(IMGUI_SHADER_MODIFIER_SCANLINE_BUTTON); + + drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 130, 0, 223), IM_COL32(0, 130, 0, 178), IM_COL32(0, 130, 0, 223), IM_COL32(0, 130, 0, 178)); + drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 0, 0, 13), IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 55), IM_COL32(0, 0, 0, 6)); + drawList->AddRectFilledMultiColor(min, max, IM_COL32(0, 130, 0, 13), IM_COL32(0, 130, 0, 111), IM_COL32(0, 130, 0, 0), IM_COL32(0, 130, 0, 55)); + + SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE); auto valueText = config->ToString(); std::transform(valueText.begin(), valueText.end(), valueText.begin(), toupper); @@ -228,21 +309,35 @@ static void DrawConfigOptions() size = Scale(16.0f); textSize = g_newRodinFont->CalcTextSizeA(size, FLT_MAX, 0.0f, valueText.c_str()); + min.x += ((max.x - min.x) - textSize.x) / 2.0f; + min.y += ((max.y - min.y) - textSize.y) / 2.0f; + + SetGradient( + min, + {min.x + textSize.x, min.y + textSize.y}, + IM_COL32(192, 255, 0, 255), + IM_COL32(128, 170, 0, 255) + ); + DrawTextWithOutline( g_newRodinFont, size, - {min.x + ((max.x - min.x) - textSize.x) / 2.0f, min.y + ((max.y - min.y) - textSize.y) / 2.0f }, - IM_COL32(167, 234, 67, 255), + min, + IM_COL32_WHITE, valueText.c_str(), Scale(2), IM_COL32_BLACK); + ResetGradient(); + ++currentRow; } } void OptionsMenu::Draw() { + g_callbackDataIndex = 0; + auto& res = ImGui::GetIO().DisplaySize; auto drawList = ImGui::GetForegroundDrawList(); @@ -250,11 +345,11 @@ void OptionsMenu::Draw() DrawScanlineBars(); - float containerPosX = Scale(/*276.0f*/250.0f); - float containerPosY = Scale(118.0f); + constexpr float CONTAINER_POS_X = 250.0f; + constexpr float CONTAINER_POS_Y = 118.0f; - ImVec2 min = { containerPosX, containerPosY }; - ImVec2 max = { res.x - containerPosX, res.y - containerPosY }; + ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_POS_X)), Scale(AlignToNextGrid(CONTAINER_POS_Y)) }; + ImVec2 max = { Scale(AlignToNextGrid(1280.0f - CONTAINER_POS_X)), Scale(AlignToNextGrid(720.0f - CONTAINER_POS_Y)) }; DrawContainer(min, max);