4946: Better forced viewport handling r=def- a=Jupeyy

For #4939

For the performance warnings refering `vkCmdClearAttachments` ignore them, because this function is only called when:
- clear color is changed(open/close editor, switch between entities) to directly flush a clear with the new color
- ~~a viewport is used that is "worse" than 5:4 (I don't really want to switch to non renderpass clear completly, since it *can* improve performance -- https://community.arm.com/arm-community-blogs/b/graphics-gaming-and-vr-blog/posts/vulkan-samples "Therefore, avoid using LOAD_OP_LOAD and vkCmdClearAttachments and use LOAD_OP_CLEAR or LOAD_OP_DONT_CARE whenever possible.")~~
thinking about it, maybe i can also drop this call now, since its dynamic viewport anyway

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


4993: Unselect text after ctrl-u r=Jupeyy a=def-

<!-- What is the motivation for the changes of this pull request -->

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


4998: Refactor tooltips and add some more r=def- a=edg-l

Refactored tooltips so they render last, otherwise stuff rendered after the call is overlapped.

Also renamed "Alpha" to "Opacity" which is more clear to non-devs.

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


4999: Respect GPU type better r=def- a=Jupeyy

fixes #4994

## Checklist

- [ ] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
Co-authored-by: def <dennis@felsin9.de>
Co-authored-by: Edgar Luque <git@edgarluque.com>
This commit is contained in:
bors[bot] 2022-04-18 08:06:49 +00:00 committed by GitHub
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 353 additions and 142 deletions

View file

@ -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

View file

@ -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<const CCommandBuffer::SCommand_VSync *>(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<const CCommandBuffer::SCommand_TrySwapAndScreenshot *>(pBaseCommand)); return true; }};
m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_UPDATE_VIEWPORT)] = {true, [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {
const auto* pCommand = static_cast<const CCommandBuffer::SCommand_Update_Viewport *>(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<const CCommandBuffer::SCommand_Update_Viewport *>(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<const CCommandBuffer::SCommand_Update_Viewport *>(pBaseCommand)); }, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { Cmd_Update_Viewport(static_cast<const CCommandBuffer::SCommand_Update_Viewport *>(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<const CCommandBuffer::SCommand_WindowCreateNtf *>(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<const CCommandBuffer::SCommand_WindowDestroyNtf *>(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<decltype(m_LastPresentedSwapChainImageIndex)>::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<decltype(Viewport.x)>::max());
Viewport.y = clamp(Viewport.y, 0.0f, std::numeric_limits<decltype(Viewport.y)>::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<VkClearAttachment, 1> 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<VkClearRect, 1> aClearRects = {VkClearRect{{{0, 0}, m_VKSwapImgAndViewportExtent.m_SwapImg}, 0, 1}};
std::array<VkClearRect, 1> 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);

View file

@ -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();

View file

@ -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;

View file

@ -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<STWGraphicGPUItem> 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;

View file

@ -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);
}

View file

@ -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)
{

View file

@ -0,0 +1,117 @@
#include "tooltips.h"
#include <game/client/render.h>
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<uintptr_t>(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);
}
}

View file

@ -0,0 +1,59 @@
#ifndef GAME_CLIENT_COMPONENTS_TOOLTIPS_H
#define GAME_CLIENT_COMPONENTS_TOOLTIPS_H
#include <cstdint>
#include <game/client/component.h>
#include <game/client/ui.h>
#include <optional>
#include <unordered_map>
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<uintptr_t, CTooltip> m_Tooltips;
std::optional<std::reference_wrapper<CTooltip>> 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

View file

@ -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);

View file

@ -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
{

View file

@ -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;
}