diff --git a/CMakeLists.txt b/CMakeLists.txt index b48b54a5e..003555ded 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1958,6 +1958,8 @@ if(CLIENT) components/spectator.h components/statboard.cpp components/statboard.h + components/tooltips.cpp + components/tooltips.h components/voting.cpp components/voting.h gameclient.cpp diff --git a/src/engine/client/backend/vulkan/backend_vulkan.cpp b/src/engine/client/backend/vulkan/backend_vulkan.cpp index 88f2d9f03..a030603cc 100644 --- a/src/engine/client/backend/vulkan/backend_vulkan.cpp +++ b/src/engine/client/backend/vulkan/backend_vulkan.cpp @@ -843,8 +843,25 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase struct SSwapImgViewportExtent { - VkExtent2D m_SwapImg; - VkExtent2D m_Viewport; + VkExtent2D m_SwapImageViewport; + bool m_HasForcedViewport = false; + VkExtent2D m_ForcedViewport; + + // the viewport of the resulting presented image on the screen + // if there is a forced viewport the resulting image is smaller + // than the full swap image size + VkExtent2D GetPresentedImageViewport() + { + uint32_t ViewportWidth = m_SwapImageViewport.width; + uint32_t ViewportHeight = m_SwapImageViewport.height; + if(m_HasForcedViewport) + { + ViewportWidth = m_ForcedViewport.width; + ViewportHeight = m_ForcedViewport.height; + } + + return {ViewportWidth, ViewportHeight}; + } }; struct SSwapChainMultiSampleImage @@ -1228,14 +1245,7 @@ protected: m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_VSYNC)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { Cmd_VSync(static_cast(pBaseCommand)); return true; }}; m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_TRY_SWAP_AND_SCREENSHOT)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { Cmd_Screenshot(static_cast(pBaseCommand)); return true; }}; - m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_UPDATE_VIEWPORT)] = {true, [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { - const auto* pCommand = static_cast(pBaseCommand); - if(pCommand->m_ByResize) { - Cmd_Update_Viewport(pCommand, true); - } - else { - Cmd_Update_Viewport_FillExecuteBuffer(ExecBuffer, pCommand); - } }, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { Cmd_Update_Viewport(static_cast(pBaseCommand), false); return true; }}; + m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_UPDATE_VIEWPORT)] = {false, [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_Update_Viewport_FillExecuteBuffer(ExecBuffer, static_cast(pBaseCommand)); }, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { Cmd_Update_Viewport(static_cast(pBaseCommand)); return true; }}; m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_WINDOW_CREATE_NTF)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { Cmd_WindowCreateNtf(static_cast(pBaseCommand)); return false; }}; m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_WINDOW_DESTROY_NTF)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { Cmd_WindowDestroyNtf(static_cast(pBaseCommand)); return false; }}; @@ -1337,8 +1347,9 @@ protected: bool UsesRGBALikeFormat = m_VKSurfFormat.format == VK_FORMAT_R8G8B8A8_UNORM || IsB8G8R8A8; if(UsesRGBALikeFormat && m_LastPresentedSwapChainImageIndex != std::numeric_limits::max()) { - Width = m_VKSwapImgAndViewportExtent.m_Viewport.width; - Height = m_VKSwapImgAndViewportExtent.m_Viewport.height; + auto Viewport = m_VKSwapImgAndViewportExtent.GetPresentedImageViewport(); + Width = Viewport.width; + Height = Viewport.height; Format = CImageInfo::FORMAT_RGBA; size_t ImageTotalSize = (size_t)Width * Height * 4; @@ -1356,7 +1367,7 @@ protected: Region.imageSubresource.baseArrayLayer = 0; Region.imageSubresource.layerCount = 1; Region.imageOffset = {0, 0, 0}; - Region.imageExtent = {m_VKSwapImgAndViewportExtent.m_Viewport.width, m_VKSwapImgAndViewportExtent.m_Viewport.height, 1}; + Region.imageExtent = {Viewport.width, Viewport.height, 1}; auto &SwapImg = m_SwapChainImages[m_LastPresentedSwapChainImageIndex]; @@ -2335,7 +2346,7 @@ protected: RenderPassInfo.renderPass = m_VKRenderPass; RenderPassInfo.framebuffer = m_FramebufferList[m_CurImageIndex]; RenderPassInfo.renderArea.offset = {0, 0}; - RenderPassInfo.renderArea.extent = m_VKSwapImgAndViewportExtent.m_Viewport; + RenderPassInfo.renderArea.extent = m_VKSwapImgAndViewportExtent.m_SwapImageViewport; VkClearValue ClearColorVal = {{{m_aClearColor[0], m_aClearColor[1], m_aClearColor[2], m_aClearColor[3]}}}; RenderPassInfo.clearValueCount = 1; @@ -3021,9 +3032,14 @@ protected: return State.m_BlendMode == CCommandBuffer::BLEND_ADDITIVE ? VULKAN_BACKEND_BLEND_MODE_ADDITATIVE : (State.m_BlendMode == CCommandBuffer::BLEND_NONE ? VULKAN_BACKEND_BLEND_MODE_NONE : VULKAN_BACKEND_BLEND_MODE_ALPHA); } - size_t GetDynamicModeIndex(const CCommandBuffer::SState &State) + size_t GetDynamicModeIndexFromState(const CCommandBuffer::SState &State) { - return (State.m_ClipEnable || m_HasDynamicViewport) ? VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT : VULKAN_BACKEND_CLIP_MODE_NONE; + return (State.m_ClipEnable || m_HasDynamicViewport || m_VKSwapImgAndViewportExtent.m_HasForcedViewport) ? VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT : VULKAN_BACKEND_CLIP_MODE_NONE; + } + + size_t GetDynamicModeIndexFromExecBuffer(const SRenderCommandExecuteBuffer &ExecBuffer) + { + return (ExecBuffer.m_HasDynamicState) ? VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT : VULKAN_BACKEND_CLIP_MODE_NONE; } VkPipeline &GetPipeline(SPipelineContainer &Container, bool IsTextured, size_t BlendModeIndex, size_t DynamicIndex) @@ -3072,17 +3088,17 @@ protected: return GetPipeline(m_TileBorderLinePipeline, IsTextured, BlendModeIndex, DynamicIndex); } - void GetStateIndices(const CCommandBuffer::SState &State, bool &IsTextured, size_t &BlendModeIndex, size_t &DynamicIndex, size_t &AddressModeIndex) + void GetStateIndices(const SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SState &State, bool &IsTextured, size_t &BlendModeIndex, size_t &DynamicIndex, size_t &AddressModeIndex) { IsTextured = GetIsTextured(State); AddressModeIndex = GetAddressModeIndex(State); BlendModeIndex = GetBlendModeIndex(State); - DynamicIndex = GetDynamicModeIndex(State); + DynamicIndex = GetDynamicModeIndexFromExecBuffer(ExecBuffer); } void ExecBufferFillDynamicStates(const CCommandBuffer::SState &State, SRenderCommandExecuteBuffer &ExecBuffer) { - size_t DynamicStateIndex = GetDynamicModeIndex(State); + size_t DynamicStateIndex = GetDynamicModeIndexFromState(State); if(DynamicStateIndex == VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT) { VkViewport Viewport; @@ -3095,22 +3111,53 @@ protected: Viewport.minDepth = 0.0f; Viewport.maxDepth = 1.0f; } + // else check if there is a forced viewport + else if(m_VKSwapImgAndViewportExtent.m_HasForcedViewport) + { + Viewport.x = 0.0f; + Viewport.y = 0.0f; + Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_ForcedViewport.width; + Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_ForcedViewport.height; + Viewport.minDepth = 0.0f; + Viewport.maxDepth = 1.0f; + } else { Viewport.x = 0.0f; Viewport.y = 0.0f; - Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_Viewport.width; - Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_Viewport.height; + Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width; + Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height; Viewport.minDepth = 0.0f; Viewport.maxDepth = 1.0f; } VkRect2D Scissor; // convert from OGL to vulkan clip - int32_t ScissorY = (int32_t)m_VKSwapImgAndViewportExtent.m_Viewport.height - ((int32_t)State.m_ClipY + (int32_t)State.m_ClipH); - uint32_t ScissorH = (int32_t)State.m_ClipH; - Scissor.offset = {(int32_t)State.m_ClipX, ScissorY}; - Scissor.extent = {(uint32_t)State.m_ClipW, ScissorH}; + + // the scissor always assumes the presented viewport, because the front-end keeps the calculation + // for the forced viewport in sync + auto ScissorViewport = m_VKSwapImgAndViewportExtent.GetPresentedImageViewport(); + if(State.m_ClipEnable) + { + int32_t ScissorY = (int32_t)ScissorViewport.height - ((int32_t)State.m_ClipY + (int32_t)State.m_ClipH); + uint32_t ScissorH = (int32_t)State.m_ClipH; + Scissor.offset = {(int32_t)State.m_ClipX, ScissorY}; + Scissor.extent = {(uint32_t)State.m_ClipW, ScissorH}; + } + else + { + Scissor.offset = {0, 0}; + Scissor.extent = {(uint32_t)ScissorViewport.width, (uint32_t)ScissorViewport.height}; + } + + // if there is a dynamic viewport make sure the scissor data is scaled down to that + if(m_HasDynamicViewport) + { + Scissor.offset.x = (int32_t)(((float)Scissor.offset.x / (float)ScissorViewport.width) * (float)m_DynamicViewportSize.width) + m_DynamicViewportOffset.x; + Scissor.offset.y = (int32_t)(((float)Scissor.offset.y / (float)ScissorViewport.height) * (float)m_DynamicViewportSize.height) + m_DynamicViewportOffset.y; + Scissor.extent.width = (uint32_t)(((float)Scissor.extent.width / (float)ScissorViewport.width) * (float)m_DynamicViewportSize.width); + Scissor.extent.height = (uint32_t)(((float)Scissor.extent.height / (float)ScissorViewport.height) * (float)m_DynamicViewportSize.height); + } Viewport.x = clamp(Viewport.x, 0.0f, std::numeric_limits::max()); Viewport.y = clamp(Viewport.y, 0.0f, std::numeric_limits::max()); @@ -3136,7 +3183,7 @@ protected: m_vLastPipeline[RenderThreadIndex] = BindingPipe; } - size_t DynamicStateIndex = GetDynamicModeIndex(State); + size_t DynamicStateIndex = GetDynamicModeIndexFromExecBuffer(ExecBuffer); if(DynamicStateIndex == VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT) { vkCmdSetViewport(CommandBuffer, 0, 1, &ExecBuffer.m_Viewport); @@ -3179,7 +3226,7 @@ protected: size_t BlendModeIndex; size_t DynamicIndex; size_t AddressModeIndex; - GetStateIndices(State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); + GetStateIndices(ExecBuffer, State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); auto &PipeLayout = GetTileLayerPipeLayout(Type, IsTextured, BlendModeIndex, DynamicIndex); auto &PipeLine = GetTileLayerPipe(Type, IsTextured, BlendModeIndex, DynamicIndex); @@ -3243,7 +3290,7 @@ protected: size_t BlendModeIndex; size_t DynamicIndex; size_t AddressModeIndex; - GetStateIndices(State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); + GetStateIndices(ExecBuffer, State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); auto &PipeLayout = Is3DTextured ? GetPipeLayout(m_Standard3DPipeline, IsTextured, BlendModeIndex, DynamicIndex) : GetStandardPipeLayout(IsLineGeometry, IsTextured, BlendModeIndex, DynamicIndex); auto &PipeLine = Is3DTextured ? GetPipeline(m_Standard3DPipeline, IsTextured, BlendModeIndex, DynamicIndex) : GetStandardPipe(IsLineGeometry, IsTextured, BlendModeIndex, DynamicIndex); @@ -3456,6 +3503,20 @@ public: return true; } + STWGraphicGPU::ETWGraphicsGPUType VKGPUTypeToGraphicsGPUType(VkPhysicalDeviceType VKGPUType) + { + if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + return STWGraphicGPU::ETWGraphicsGPUType::GRAPHICS_GPU_TYPE_DISCRETE; + else if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) + return STWGraphicGPU::ETWGraphicsGPUType::GRAPHICS_GPU_TYPE_INTEGRATED; + else if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU) + return STWGraphicGPU::ETWGraphicsGPUType::GRAPHICS_GPU_TYPE_VIRTUAL; + else if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_CPU) + return STWGraphicGPU::ETWGraphicsGPUType::GRAPHICS_GPU_TYPE_CPU; + + return STWGraphicGPU::ETWGraphicsGPUType::GRAPHICS_GPU_TYPE_CPU; + } + bool SelectGPU(char *pRendererName, char *pVendorName, char *pVersionName) { uint32_t DevicesCount = 0; @@ -3474,7 +3535,9 @@ public: m_pGPUList->m_GPUs.reserve(DeviceList.size()); size_t FoundDeviceIndex = 0; - size_t AutoGPUIndex = 0; + size_t FoundGPUType = STWGraphicGPU::ETWGraphicsGPUType::GRAPHICS_GPU_TYPE_INVALID; + + STWGraphicGPU::ETWGraphicsGPUType AutoGPUType = STWGraphicGPU::ETWGraphicsGPUType::GRAPHICS_GPU_TYPE_INVALID; bool IsAutoGPU = str_comp(g_Config.m_GfxGPUName, "auto") == 0; @@ -3484,9 +3547,11 @@ public: auto &DeviceProp = DevicePropList[Index]; + STWGraphicGPU::ETWGraphicsGPUType GPUType = VKGPUTypeToGraphicsGPUType(DeviceProp.deviceType); + STWGraphicGPU::STWGraphicGPUItem NewGPU; str_copy(NewGPU.m_Name, DeviceProp.deviceName, minimum(sizeof(DeviceProp.deviceName), sizeof(NewGPU.m_Name))); - NewGPU.m_IsDiscreteGPU = DeviceProp.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + NewGPU.m_GPUType = GPUType; m_pGPUList->m_GPUs.push_back(NewGPU); Index++; @@ -3494,17 +3559,18 @@ public: int DevAPIMajor = (int)VK_API_VERSION_MAJOR(DeviceProp.apiVersion); int DevAPIMinor = (int)VK_API_VERSION_MINOR(DeviceProp.apiVersion); - if((AutoGPUIndex == 0 && DeviceProp.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) && (DevAPIMajor > gs_BackendVulkanMajor || (DevAPIMajor == gs_BackendVulkanMajor && DevAPIMinor >= gs_BackendVulkanMinor))) + if(GPUType < AutoGPUType && (DevAPIMajor > gs_BackendVulkanMajor || (DevAPIMajor == gs_BackendVulkanMajor && DevAPIMinor >= gs_BackendVulkanMinor))) { str_copy(m_pGPUList->m_AutoGPU.m_Name, DeviceProp.deviceName, minimum(sizeof(DeviceProp.deviceName), sizeof(m_pGPUList->m_AutoGPU.m_Name))); - m_pGPUList->m_AutoGPU.m_IsDiscreteGPU = DeviceProp.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + m_pGPUList->m_AutoGPU.m_GPUType = GPUType; - AutoGPUIndex = Index; + AutoGPUType = GPUType; } - if(((IsAutoGPU && FoundDeviceIndex == 0 && DeviceProp.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) || str_comp(DeviceProp.deviceName, g_Config.m_GfxGPUName) == 0) && (DevAPIMajor > gs_BackendVulkanMajor || (DevAPIMajor == gs_BackendVulkanMajor && DevAPIMinor >= gs_BackendVulkanMinor))) + if(((IsAutoGPU && GPUType < FoundGPUType) || str_comp(DeviceProp.deviceName, g_Config.m_GfxGPUName) == 0) && (DevAPIMajor > gs_BackendVulkanMajor || (DevAPIMajor == gs_BackendVulkanMajor && DevAPIMinor >= gs_BackendVulkanMinor))) { FoundDeviceIndex = Index; + FoundGPUType = GPUType; } } @@ -3769,11 +3835,20 @@ public: } VkExtent2D AutoViewportExtent = RetSize; + bool UsesForcedViewport = false; // keep this in sync with graphics_threaded AdjustViewport's check if(AutoViewportExtent.height > 4 * AutoViewportExtent.width / 5) + { AutoViewportExtent.height = 4 * AutoViewportExtent.width / 5; + UsesForcedViewport = true; + } - return {RetSize, AutoViewportExtent}; + SSwapImgViewportExtent Ext; + Ext.m_SwapImageViewport = RetSize; + Ext.m_ForcedViewport = AutoViewportExtent; + Ext.m_HasForcedViewport = UsesForcedViewport; + + return Ext; } bool GetImageUsage(const VkSurfaceCapabilitiesKHR &VKCapabilities, VkImageUsageFlags &VKOutUsage) @@ -3892,7 +3967,7 @@ public: SwapInfo.minImageCount = SwapImgCount; SwapInfo.imageFormat = m_VKSurfFormat.format; SwapInfo.imageColorSpace = m_VKSurfFormat.colorSpace; - SwapInfo.imageExtent = m_VKSwapImgAndViewportExtent.m_SwapImg; + SwapInfo.imageExtent = m_VKSwapImgAndViewportExtent.m_SwapImageViewport; SwapInfo.imageArrayLayers = 1; SwapInfo.imageUsage = UsageFlags; SwapInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; @@ -4075,7 +4150,7 @@ public: { for(size_t i = 0; i < m_SwapChainImageCount; ++i) { - CreateImage(m_VKSwapImgAndViewportExtent.m_SwapImg.width, m_VKSwapImgAndViewportExtent.m_SwapImg.height, 1, 1, m_VKSurfFormat.format, VK_IMAGE_TILING_OPTIMAL, m_SwapChainMultiSamplingImages[i].m_Image, m_SwapChainMultiSamplingImages[i].m_ImgMem, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + CreateImage(m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width, m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height, 1, 1, m_VKSurfFormat.format, VK_IMAGE_TILING_OPTIMAL, m_SwapChainMultiSamplingImages[i].m_Image, m_SwapChainMultiSamplingImages[i].m_ImgMem, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); m_SwapChainMultiSamplingImages[i].m_ImgView = CreateImageView(m_SwapChainMultiSamplingImages[i].m_Image, m_VKSurfFormat.format, VK_IMAGE_VIEW_TYPE_2D, 1, 1); } } @@ -4187,8 +4262,8 @@ public: FramebufferInfo.renderPass = m_VKRenderPass; FramebufferInfo.attachmentCount = HasMultiSamplingTargets ? aAttachments.size() : aAttachments.size() - 1; FramebufferInfo.pAttachments = HasMultiSamplingTargets ? aAttachments.data() : aAttachments.data() + 1; - FramebufferInfo.width = m_VKSwapImgAndViewportExtent.m_SwapImg.width; - FramebufferInfo.height = m_VKSwapImgAndViewportExtent.m_SwapImg.height; + FramebufferInfo.width = m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width; + FramebufferInfo.height = m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height; FramebufferInfo.layers = 1; if(vkCreateFramebuffer(m_VKDevice, &FramebufferInfo, nullptr, &m_FramebufferList[i]) != VK_SUCCESS) @@ -4342,13 +4417,13 @@ public: Viewport.x = 0.0f; Viewport.y = 0.0f; - Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_Viewport.width; - Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_Viewport.height; + Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width; + Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height; Viewport.minDepth = 0.0f; Viewport.maxDepth = 1.0f; Scissor.offset = {0, 0}; - Scissor.extent = m_VKSwapImgAndViewportExtent.m_Viewport; + Scissor.extent = m_VKSwapImgAndViewportExtent.m_SwapImageViewport; ViewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; ViewportState.viewportCount = 1; @@ -6373,7 +6448,7 @@ public: if(ExecBuffer.m_ClearColorInRenderThread) { std::array aAttachments = {VkClearAttachment{VK_IMAGE_ASPECT_COLOR_BIT, 0, VkClearValue{VkClearColorValue{{pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, pCommand->m_Color.a}}}}}; - std::array aClearRects = {VkClearRect{{{0, 0}, m_VKSwapImgAndViewportExtent.m_SwapImg}, 0, 1}}; + std::array aClearRects = {VkClearRect{{{0, 0}, m_VKSwapImgAndViewportExtent.m_SwapImageViewport}, 0, 1}}; vkCmdClearAttachments(GetGraphicCommandBuffer(ExecBuffer.m_ThreadIndex), aAttachments.size(), aAttachments.data(), aClearRects.size(), aClearRects.data()); } } @@ -6449,9 +6524,9 @@ public: ExecBuffer.m_EstimatedRenderCallCount = 0; } - void Cmd_Update_Viewport(const CCommandBuffer::SCommand_Update_Viewport *pCommand, bool CalledByMainRenderThread) + void Cmd_Update_Viewport(const CCommandBuffer::SCommand_Update_Viewport *pCommand) { - if(pCommand->m_ByResize && CalledByMainRenderThread) + if(pCommand->m_ByResize) { if(IsVerbose()) { @@ -6461,14 +6536,15 @@ public: m_CanvasHeight = (uint32_t)pCommand->m_Height; m_RecreateSwapChain = true; } - else if(!pCommand->m_ByResize && !CalledByMainRenderThread) + else if(!pCommand->m_ByResize) { - if(pCommand->m_X != 0 || pCommand->m_Y != 0 || (uint32_t)pCommand->m_Width != m_VKSwapImgAndViewportExtent.m_Viewport.width || (uint32_t)pCommand->m_Height != m_VKSwapImgAndViewportExtent.m_Viewport.height) + auto Viewport = m_VKSwapImgAndViewportExtent.GetPresentedImageViewport(); + if(pCommand->m_X != 0 || pCommand->m_Y != 0 || (uint32_t)pCommand->m_Width != Viewport.width || (uint32_t)pCommand->m_Height != Viewport.height) { m_HasDynamicViewport = true; // convert viewport from OGL to vulkan - int32_t ViewportY = (int32_t)m_VKSwapImgAndViewportExtent.m_Viewport.height - ((int32_t)pCommand->m_Y + (int32_t)pCommand->m_Height); + int32_t ViewportY = (int32_t)Viewport.height - ((int32_t)pCommand->m_Y + (int32_t)pCommand->m_Height); uint32_t ViewportH = (int32_t)pCommand->m_Height; m_DynamicViewportOffset = {(int32_t)pCommand->m_X, ViewportY}; m_DynamicViewportSize = {(uint32_t)pCommand->m_Width, ViewportH}; @@ -6687,7 +6763,7 @@ public: size_t BlendModeIndex; size_t DynamicIndex; size_t AddressModeIndex; - GetStateIndices(pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); + GetStateIndices(ExecBuffer, pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); auto &PipeLayout = GetPipeLayout(CanBePushed ? m_QuadPushPipeline : m_QuadPipeline, IsTextured, BlendModeIndex, DynamicIndex); auto &PipeLine = GetPipeline(CanBePushed ? m_QuadPushPipeline : m_QuadPipeline, IsTextured, BlendModeIndex, DynamicIndex); @@ -6783,7 +6859,7 @@ public: size_t BlendModeIndex; size_t DynamicIndex; size_t AddressModeIndex; - GetStateIndices(pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); + GetStateIndices(ExecBuffer, pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); IsTextured = true; // text is always textured auto &PipeLayout = GetPipeLayout(m_TextPipeline, IsTextured, BlendModeIndex, DynamicIndex); auto &PipeLine = GetPipeline(m_TextPipeline, IsTextured, BlendModeIndex, DynamicIndex); @@ -6852,7 +6928,7 @@ public: size_t BlendModeIndex; size_t DynamicIndex; size_t AddressModeIndex; - GetStateIndices(pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); + GetStateIndices(ExecBuffer, pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); auto &PipeLayout = GetStandardPipeLayout(false, IsTextured, BlendModeIndex, DynamicIndex); auto &PipeLine = GetStandardPipe(false, IsTextured, BlendModeIndex, DynamicIndex); @@ -6893,7 +6969,7 @@ public: size_t BlendModeIndex; size_t DynamicIndex; size_t AddressModeIndex; - GetStateIndices(pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); + GetStateIndices(ExecBuffer, pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); auto &PipeLayout = GetPipeLayout(IsRotationless ? m_PrimExRotationlessPipeline : m_PrimExPipeline, IsTextured, BlendModeIndex, DynamicIndex); auto &PipeLine = GetPipeline(IsRotationless ? m_PrimExRotationlessPipeline : m_PrimExPipeline, IsTextured, BlendModeIndex, DynamicIndex); @@ -6954,7 +7030,7 @@ public: size_t BlendModeIndex; size_t DynamicIndex; size_t AddressModeIndex; - GetStateIndices(pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); + GetStateIndices(ExecBuffer, pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex); auto &PipeLayout = GetPipeLayout(CanBePushed ? m_SpriteMultiPushPipeline : m_SpriteMultiPipeline, IsTextured, BlendModeIndex, DynamicIndex); auto &PipeLine = GetPipeline(CanBePushed ? m_SpriteMultiPushPipeline : m_SpriteMultiPipeline, IsTextured, BlendModeIndex, DynamicIndex); diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index 695df1cb4..9939b3e49 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -845,7 +845,7 @@ void CGraphics_Threaded::Clear(float r, float g, float b, bool ForceClearNow) Cmd.m_Color.g = g; Cmd.m_Color.b = b; Cmd.m_Color.a = 0; - Cmd.m_ForceClear = ForceClearNow || m_IsForcedViewport; + Cmd.m_ForceClear = ForceClearNow; AddCmd( Cmd, [] { return true; }, "failed to clear graphics."); } @@ -2270,18 +2270,7 @@ void CGraphics_Threaded::AdjustViewport(bool SendViewportChangeToBackend) if(SendViewportChangeToBackend) { - CCommandBuffer::SCommand_Update_Viewport Cmd; - Cmd.m_X = 0; - Cmd.m_Y = 0; - Cmd.m_Width = m_ScreenWidth; - Cmd.m_Height = m_ScreenHeight; - Cmd.m_ByResize = true; - - if(!AddCmd( - Cmd, [] { return true; }, "failed to add resize command")) - { - return; - } + UpdateViewport(0, 0, m_ScreenWidth, m_ScreenWidth, true); } } else @@ -2290,6 +2279,22 @@ void CGraphics_Threaded::AdjustViewport(bool SendViewportChangeToBackend) } } +void CGraphics_Threaded::UpdateViewport(int X, int Y, int W, int H, bool ByResize) +{ + CCommandBuffer::SCommand_Update_Viewport Cmd; + Cmd.m_X = X; + Cmd.m_Y = Y; + Cmd.m_Width = W; + Cmd.m_Height = H; + Cmd.m_ByResize = ByResize; + + if(!AddCmd( + Cmd, [] { return true; }, "failed to add resize command")) + { + return; + } +} + void CGraphics_Threaded::AddBackEndWarningIfExists() { const char *pErrStr = m_pBackend->GetErrorString(); @@ -2589,18 +2594,7 @@ void CGraphics_Threaded::GotResized(int w, int h, int RefreshRate) g_Config.m_GfxScreenRefreshRate = m_ScreenRefreshRate; m_ScreenHiDPIScale = m_ScreenWidth / (float)g_Config.m_GfxScreenWidth; - CCommandBuffer::SCommand_Update_Viewport Cmd; - Cmd.m_X = 0; - Cmd.m_Y = 0; - Cmd.m_Width = m_ScreenWidth; - Cmd.m_Height = m_ScreenHeight; - Cmd.m_ByResize = true; - - if(!AddCmd( - Cmd, [] { return true; }, "failed to add resize command")) - { - return; - } + UpdateViewport(0, 0, m_ScreenWidth, m_ScreenWidth, true); // kick the command buffer and wait KickCommandBuffer(); diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index 48c94927c..3b99bb8fe 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -1246,6 +1246,7 @@ public: void Move(int x, int y) override; void Resize(int w, int h, int RefreshRate) override; void GotResized(int w, int h, int RefreshRate) override; + void UpdateViewport(int X, int Y, int W, int H, bool ByResize) override; void AddWindowResizeListener(WINDOW_RESIZE_FUNC pFunc, void *pUser) override; int GetWindowScreen() override; diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 086ee53ab..a113d4361 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -179,10 +179,21 @@ enum EBackendType struct STWGraphicGPU { + enum ETWGraphicsGPUType + { + GRAPHICS_GPU_TYPE_DISCRETE = 0, + GRAPHICS_GPU_TYPE_INTEGRATED, + GRAPHICS_GPU_TYPE_VIRTUAL, + GRAPHICS_GPU_TYPE_CPU, + + // should stay at last position in this enum + GRAPHICS_GPU_TYPE_INVALID, + }; + struct STWGraphicGPUItem { char m_Name[256]; - bool m_IsDiscreteGPU; + ETWGraphicsGPUType m_GPUType; }; std::vector m_GPUs; STWGraphicGPUItem m_AutoGPU; @@ -249,6 +260,7 @@ public: virtual void Move(int x, int y) = 0; virtual void Resize(int w, int h, int RefreshRate) = 0; virtual void GotResized(int w, int h, int RefreshRate) = 0; + virtual void UpdateViewport(int X, int Y, int W, int H, bool ByResize) = 0; virtual void AddWindowResizeListener(WINDOW_RESIZE_FUNC pFunc, void *pUser) = 0; virtual void WindowDestroyNtf(uint32_t WindowID) = 0; diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index bfad618b6..552ff18b6 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -2833,63 +2833,3 @@ bool CMenus::HandleListInputs(const CUIRect &View, float &ScrollValue, const flo return NewIndex != -1; } - -void CMenus::DoToolTip(const void *pID, const CUIRect *pNearRect, const char *pText, float WidthHint) -{ - static int64_t HoverTime = -1; - - if(!UI()->MouseInside(pNearRect)) - { - if(pID == UI()->ActiveTooltipItem()) - HoverTime = -1; - return; - } - - UI()->SetActiveTooltipItem(pID); - - if(HoverTime == -1) - HoverTime = time_get(); - - // Delay tooltip until 1 second passed. - if(HoverTime > time_get() - time_freq()) - return; - - const float MARGIN = 5.0f; - - CUIRect Rect; - Rect.w = WidthHint; - if(WidthHint < 0.0f) - Rect.w = TextRender()->TextWidth(0, 14.0f, pText, -1, -1.0f) + 4.0f; - Rect.h = 30.0f; - - CUIRect *pScreen = UI()->Screen(); - - // Try the top side. - if(pNearRect->y - Rect.h - MARGIN > pScreen->y) - { - Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN); - Rect.y = pNearRect->y - Rect.h - MARGIN; - } - // Try the bottom side. - else if(pNearRect->y + pNearRect->h + MARGIN < pScreen->h) - { - Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN); - Rect.y = pNearRect->y + pNearRect->h + MARGIN; - } - // Try the right side. - else if(pNearRect->x + pNearRect->w + MARGIN + Rect.w < pScreen->w) - { - Rect.x = pNearRect->x + pNearRect->w + MARGIN; - Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN); - } - // Try the left side. - else if(pNearRect->x - Rect.w - MARGIN > pScreen->x) - { - Rect.x = pNearRect->x - Rect.w - MARGIN; - Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN); - } - - RenderTools()->DrawUIRect(&Rect, ColorRGBA(0.2, 0.2, 0.2, 0.80f), CUI::CORNER_ALL, 5.0f); - Rect.Margin(2.0f, &Rect); - UI()->DoLabel(&Rect, pText, 14.0f, TEXTALIGN_LEFT); -} diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 20adbf3da..dc8bc048c 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -530,7 +530,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) { m_Dummy ^= 1; } - DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings.")); + GameClient()->m_Tooltips.DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings.")); Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); @@ -1265,7 +1265,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView) MainView.HSplitTop(20.0f, &Button, &MainView); if(DoButton_CheckBox(&g_Config.m_GfxHighDetail, Localize("High Detail"), g_Config.m_GfxHighDetail, &Button)) g_Config.m_GfxHighDetail ^= 1; - DoToolTip(&g_Config.m_GfxHighDetail, &Button, Localize("Allows maps to render with more detail.")); + GameClient()->m_Tooltips.DoToolTip(&g_Config.m_GfxHighDetail, &Button, Localize("Allows maps to render with more detail.")); MainView.HSplitTop(20.0f, &Button, &MainView); if(DoButton_CheckBox(&g_Config.m_GfxHighdpi, Localize("Use high DPI"), g_Config.m_GfxHighdpi, &Button)) @@ -2635,6 +2635,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) { g_Config.m_ClRaceGhost ^= 1; } + GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClRaceGhost, &Button, Localize("When you cross the start line, show a ghost tee replicating the movements of your best time.")); if(g_Config.m_ClRaceGhost) { @@ -2686,13 +2687,15 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) Button.VSplitMid(&LeftLeft, &Button); Button.VSplitLeft(50.0f, &Label, &Button); - UI()->DoLabelScaled(&Label, Localize("Alpha"), 14.0f, TEXTALIGN_LEFT); + UI()->DoLabelScaled(&Label, Localize("Opacity"), 14.0f, TEXTALIGN_LEFT); g_Config.m_ClShowOthersAlpha = (int)(UIEx()->DoScrollbarH(&g_Config.m_ClShowOthersAlpha, &Button, g_Config.m_ClShowOthersAlpha / 100.0f) * 100.0f); if(DoButton_CheckBox(&g_Config.m_ClShowOthers, Localize("Show others"), g_Config.m_ClShowOthers == SHOW_OTHERS_ON, &LeftLeft)) { g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ON ? SHOW_OTHERS_ON : SHOW_OTHERS_OFF; } + + GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClShowOthersAlpha, &Button, "Adjust the opacity of entities belonging to other teams, such as tees and nameplates"); } Left.HSplitTop(20.0f, &Button, &Left); @@ -2707,6 +2710,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) { g_Config.m_ClShowQuads ^= 1; } + GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClShowQuads, &Button, Localize("Quads are used for background decoration")); Right.HSplitTop(20.0f, &Label, &Right); Label.VSplitLeft(130.0f, &Label, &Button); @@ -2720,6 +2724,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) { g_Config.m_ClAntiPing ^= 1; } + GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClAntiPing, &Button, Localize("Tries to predict other entities to give a feel of low latency.")); if(g_Config.m_ClAntiPing) { diff --git a/src/game/client/components/tooltips.cpp b/src/game/client/components/tooltips.cpp new file mode 100644 index 000000000..e8c199d8c --- /dev/null +++ b/src/game/client/components/tooltips.cpp @@ -0,0 +1,117 @@ +#include "tooltips.h" + +#include + +CTooltips::CTooltips() +{ + OnReset(); +} + +void CTooltips::OnReset() +{ + HoverTime = -1; + m_Tooltips.clear(); +} + +void CTooltips::SetActiveTooltip(CTooltip &Tooltip) +{ + if(m_ActiveTooltip.has_value()) + return; + + m_ActiveTooltip.emplace(Tooltip); + HoverTime = time_get(); +} + +inline void CTooltips::ClearActiveTooltip() +{ + m_ActiveTooltip.reset(); +} + +void CTooltips::DoToolTip(const void *pID, const CUIRect *pNearRect, const char *pText, float WidthHint) +{ + uintptr_t ID = reinterpret_cast(pID); + + const auto &it = m_Tooltips.find(ID); + + if(it == m_Tooltips.end()) + { + CTooltip NewTooltip = { + *pNearRect, + pText, + WidthHint, + }; + + m_Tooltips[ID] = NewTooltip; + + CTooltip &Tooltip = m_Tooltips[ID]; + + if(UI()->MouseInside(&Tooltip.m_Rect)) + { + SetActiveTooltip(Tooltip); + } + } + else + { + if(UI()->MouseInside(&it->second.m_Rect)) + { + SetActiveTooltip(it->second); + } + } +} + +void CTooltips::OnRender() +{ + if(m_ActiveTooltip.has_value()) + { + CTooltip &Tooltip = m_ActiveTooltip.value(); + + if(!UI()->MouseInside(&Tooltip.m_Rect)) + { + ClearActiveTooltip(); + return; + } + + // Delay tooltip until 1 second passed. + if(HoverTime > time_get() - time_freq()) + return; + + const float MARGIN = 5.0f; + + CUIRect Rect; + Rect.w = Tooltip.m_WidthHint; + if(Tooltip.m_WidthHint < 0.0f) + Rect.w = TextRender()->TextWidth(0, 14.0f, Tooltip.m_pText, -1, -1.0f) + 4.0f; + Rect.h = 30.0f; + + CUIRect *pScreen = UI()->Screen(); + + // Try the top side. + if(Tooltip.m_Rect.y - Rect.h - MARGIN > pScreen->y) + { + Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN); + Rect.y = Tooltip.m_Rect.y - Rect.h - MARGIN; + } + // Try the bottom side. + else if(Tooltip.m_Rect.y + Tooltip.m_Rect.h + MARGIN < pScreen->h) + { + Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN); + Rect.y = Tooltip.m_Rect.y + Tooltip.m_Rect.h + MARGIN; + } + // Try the right side. + else if(Tooltip.m_Rect.x + Tooltip.m_Rect.w + MARGIN + Rect.w < pScreen->w) + { + Rect.x = Tooltip.m_Rect.x + Tooltip.m_Rect.w + MARGIN; + Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN); + } + // Try the left side. + else if(Tooltip.m_Rect.x - Rect.w - MARGIN > pScreen->x) + { + Rect.x = Tooltip.m_Rect.x - Rect.w - MARGIN; + Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN); + } + + RenderTools()->DrawUIRect(&Rect, ColorRGBA(0.2, 0.2, 0.2, 0.80f), CUI::CORNER_ALL, 5.0f); + Rect.Margin(2.0f, &Rect); + UI()->DoLabel(&Rect, Tooltip.m_pText, 14.0f, TEXTALIGN_LEFT); + } +} diff --git a/src/game/client/components/tooltips.h b/src/game/client/components/tooltips.h new file mode 100644 index 000000000..021cd2565 --- /dev/null +++ b/src/game/client/components/tooltips.h @@ -0,0 +1,59 @@ +#ifndef GAME_CLIENT_COMPONENTS_TOOLTIPS_H +#define GAME_CLIENT_COMPONENTS_TOOLTIPS_H + +#include +#include +#include + +#include +#include + +struct CTooltip +{ + CUIRect m_Rect; + const char *m_pText; + float m_WidthHint; +}; + +/** + * A component that manages and renders UI tooltips. + * + * Should be among the last components to render. + */ +class CTooltips : public CComponent +{ + std::unordered_map m_Tooltips; + std::optional> m_ActiveTooltip; + int64_t HoverTime; + + /** + * The passed tooltip is only actually set if there is no currently active tooltip. + * + * @param Tooltip A reference to the tooltip that should be active. + */ + void SetActiveTooltip(CTooltip &Tooltip); + + inline void ClearActiveTooltip(); + +public: + CTooltips(); + virtual int Sizeof() const override { return sizeof(*this); } + + /** + * Adds the tooltip to a cache and renders it when active. + * + * On the first call to this function, the data passed is cached, afterwards the calls are used to detect if the tooltip should be activated. + * + * For now only works correctly with single line tooltips, since Text width calculation gets broken when there are multiple lines. + * + * @param pID The ID of the tooltip. Usually a reference to some g_Config value. + * @param pNearTo Place the tooltip near this rect. + * @param pText The text to display in the tooltip + */ + void DoToolTip(const void *pID, const CUIRect *pNearRect, const char *pText, float WidthHint = -1.0f); + + virtual void OnReset() override; + virtual void OnRender() override; +}; + +#endif diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 2fac3ddca..29924fa1f 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -137,6 +137,7 @@ void CGameClient::OnConsoleInit() m_All.Add(&m_Statboard); m_All.Add(&m_Motd); m_All.Add(&m_Menus); + m_All.Add(&m_Tooltips); m_All.Add(&CMenus::m_Binder); m_All.Add(&m_GameConsole); diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index a76d49274..7a0cd387f 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -53,6 +53,7 @@ #include "components/sounds.h" #include "components/spectator.h" #include "components/statboard.h" +#include "components/tooltips.h" #include "components/voting.h" class CGameInfo @@ -144,6 +145,8 @@ public: CRaceDemo m_RaceDemo; CGhost m_Ghost; + CTooltips m_Tooltips; + private: class CStack { diff --git a/src/game/client/ui_ex.cpp b/src/game/client/ui_ex.cpp index 73a036a2a..c238075f9 100644 --- a/src/game/client/ui_ex.cpp +++ b/src/game/client/ui_ex.cpp @@ -339,6 +339,7 @@ bool CUIEx::DoEditBox(const void *pID, const CUIRect *pRect, char *pStr, unsigne { pStr[0] = '\0'; m_CurCursor = 0; + SetHasSelection(false); ReturnValue = true; }