diff --git a/GPU/GPUTracking/CMakeLists.txt b/GPU/GPUTracking/CMakeLists.txt index 0467b060ead4f..f1541e17b467f 100644 --- a/GPU/GPUTracking/CMakeLists.txt +++ b/GPU/GPUTracking/CMakeLists.txt @@ -27,7 +27,7 @@ set(CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER} "${CMAKE_CXX_FLAGS_${CMAKE_BUILD_T include(cmake/helpers.cmake) -if((ALIGPU_BUILD_TYPE STREQUAL "Standalone" AND GPUCA_BUILD_EVENT_DISPLAY) OR (ALIGPU_BUILD_TYPE STREQUAL "O2" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND TARGET AliceO2::DebugGUI AND OPENGL_FOUND AND GLFW_FOUND AND (NOT DEFINED GPUCA_BUILD_EVENT_DISPLAY OR NOT GPUCA_BUILD_EVENT_DISPLAY))) +if((ALIGPU_BUILD_TYPE STREQUAL "Standalone" AND GPUCA_BUILD_EVENT_DISPLAY) OR (ALIGPU_BUILD_TYPE STREQUAL "O2" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND TARGET AliceO2::DebugGUI AND (NOT DEFINED GPUCA_BUILD_EVENT_DISPLAY OR GPUCA_BUILD_EVENT_DISPLAY))) set(GPUCA_EVENT_DISPLAY ON) endif() if(ROOT_FOUND) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index 2d49faf287c0e..f2a7927f9c1d2 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -499,12 +499,13 @@ AddOption(camLookOrigin, bool, false, "", 0, "Make the camera look at the origin AddOption(camYUp, bool, false, "", 0, "Orient the camera such that the y-axis is always upwards") AddOption(cameraMode, int32_t, 0, "", 0, "Camera mode") AddOption(fullScreen, bool, false, "", 0, "Full Screen") -AddOption(maximized, bool, false, "", 0, "Full Screen") -AddOption(openGLCore, bool, false, "", 0, "Use renderer path for OpenGL core profile") +AddOption(maximized, bool, true, "", 0, "Maximized window") +AddOption(rendererCompatSmoothing, bool, false, "", 0, "Use compat smoothing (if available) of instad of shader for smooth lines / points") AddOption(drawQualityMSAA, int32_t, 0, "", 0, "MultiSample Anti Aliasing") AddOption(drawQualityDownsampleFSAA, int32_t, 0, "", 0, "Downsampling Anti Aliasing") AddOption(drawQualityVSync, bool, true, "", 0, "Enable Vertical Sync") -AddOption(maxFPSRate, int32_t, 0, "", 0, "Do not limit FPS but run at maximum possible rate") +AddOption(maxFPSRate, bool, false, "", 0, "Do not limit FPS but run at maximum possible rate") +AddOption(fpsRate, int32_t, 100, "", 0, "Target frame rate (frames per second) if not running at maximum rate (0 = no limit)") AddOption(useGLIndirectDraw, bool, true, "", 0, "Use OpenGL indirect draws to reduce number of draw calls") AddOption(screenshotScaleFactor, int32_t, 1, "", 0, "Resolution scale factor when taking screenshots") AddOption(fov, int32_t, 45, "", 0, "Display FOV") @@ -513,7 +514,7 @@ EndConfig() // Vulkan Display Settings BeginSubConfig(GPUSettingsDisplayVulkan, vulkan, configStandalone.display, "GLV", 0, "Vulkan display settings", display_vulkan) -AddOption(nFramesInFlight, int32_t, 0, "", 0, "Max number of render frames in flight (0 = as many as swapchain images)") +AddOption(nFramesInFlight, uint32_t, 3, "", 0, "Max number of render frames in flight") AddOption(uniformBuffersInDeviceMemory, bool, 1, "", 0, "Have uniform buffers in host-accessible device memory") AddOption(forceDevice, int32_t, -1, "", 0, "Force Vulkan device number to use") AddHelp("help", 'h') diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index e54110e53aac3..63bec89446d4e 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -68,7 +68,12 @@ GPUChainTracking::GPUChainTracking(GPUReconstruction* rec, uint32_t maxTPCHits, mFlatObjectsDevice.mChainTracking = this; } -GPUChainTracking::~GPUChainTracking() = default; +GPUChainTracking::~GPUChainTracking() +{ + if (mDisplayRunning) { + GetProcessingSettings().eventDisplay->StopDisplay(); + } +} void GPUChainTracking::RegisterPermanentMemoryAndProcessors() { @@ -861,16 +866,13 @@ int32_t GPUChainTracking::RunChainFinalize() } else if (iKey == 'n') { break; } else if (iKey) { - while (GetProcessingSettings().eventDisplay->getSendKey() != 0) { - usleep(1000); - } - GetProcessingSettings().eventDisplay->setSendKey(iKey); + GetProcessingSettings().eventDisplay->sendKey(iKey); } } } while (GetProcessingSettings().eventDisplay->getDisplayControl() == 0); if (GetProcessingSettings().eventDisplay->getDisplayControl() == 2) { mDisplayRunning = false; - GetProcessingSettings().eventDisplay->DisplayExit(); + GetProcessingSettings().eventDisplay->StopDisplay(); const_cast(GetProcessingSettings()).eventDisplay = nullptr; // TODO: fixme - eventDisplay should probably not be put into ProcessingSettings in the first place return GPUReconstruction::retValValue::retDoExit; } diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx index 8ef1b1980dc0c..008bb2a1f1079 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx @@ -39,7 +39,7 @@ GPUO2InterfaceDisplay::GPUO2InterfaceDisplay(const GPUO2InterfaceConfiguration* GPUO2InterfaceDisplay::~GPUO2InterfaceDisplay() = default; -int32_t GPUO2InterfaceDisplay::startDisplay() +int32_t GPUO2InterfaceDisplay::StartUnmanagedDisplay() { int32_t retVal = mDisplay->StartDisplay(); if (retVal) { @@ -71,9 +71,9 @@ int32_t GPUO2InterfaceDisplay::show(const GPUTrackingInOutPointers* ptrs) return 0; } -int32_t GPUO2InterfaceDisplay::endDisplay() +int32_t GPUO2InterfaceDisplay::StopUnmanagedDisplay() { - mFrontend->DisplayExit(); + mFrontend->StopDisplay(); return 0; } diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h index c0946bab0076d..87990adce5ff1 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h @@ -37,9 +37,9 @@ class GPUO2InterfaceDisplay void UpdateCalib(const GPUCalibObjectsConst* calib); void UpdateGRP(const GPUSettingsGRP* grp); - int32_t startDisplay(); + int32_t StartUnmanagedDisplay(); int32_t show(const GPUTrackingInOutPointers* ptrs); - int32_t endDisplay(); + int32_t StopUnmanagedDisplay(); private: std::unique_ptr mDisplay; diff --git a/GPU/GPUTracking/Standalone/CMakeLists.txt b/GPU/GPUTracking/Standalone/CMakeLists.txt index a4c5817d1f7f9..ef02e1c819a0c 100644 --- a/GPU/GPUTracking/Standalone/CMakeLists.txt +++ b/GPU/GPUTracking/Standalone/CMakeLists.txt @@ -96,7 +96,7 @@ if(GPUCA_BUILD_EVENT_DISPLAY) find_package(Fontconfig REQUIRED) endif() if(GPUCA_BUILD_EVENT_DISPLAY_VULKAN) - find_package(Vulkan REQUIRED) + find_package(Vulkan 1.4.328 REQUIRED) if (Vulkan_GLSLC_EXECUTABLE STREQUAL "Vulkan_GLSLC_EXECUTABLE-NOTFOUND") message(FATAL_ERROR ${Vulkan_GLSLC_EXECUTABLE}) endif() diff --git a/GPU/GPUTracking/cmake/vulkan_display.cmake b/GPU/GPUTracking/cmake/vulkan_display.cmake deleted file mode 100644 index 7859742363755..0000000000000 --- a/GPU/GPUTracking/cmake/vulkan_display.cmake +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2019-2020 CERN and copyright holders of ALICE O2. -# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -# All rights not expressly granted are reserved. -# -# This software is distributed under the terms of the GNU General Public -# License v3 (GPL Version 3), copied verbatim in the file "COPYING". -# -# In applying this license CERN does not waive the privileges and immunities -# granted to it by virtue of its status as an Intergovernmental Organization -# or submit itself to any jurisdiction. - -# file vulkan_display.cmake -# author David Rohr - -if (GLSLC STREQUAL "GLSLC-NOTFOUND") - message(FATAL_ERROR "glslc not found") -endif() - -function(add_glslc_shader TARGET SHADER) - get_filename_component(input-file-abs ${SHADER} ABSOLUTE) - FILE(RELATIVE_PATH input-file-rel ${CMAKE_CURRENT_SOURCE_DIR} ${input-file-abs}) - set(spirv-file ${CMAKE_CURRENT_BINARY_DIR}/shaders/${input-file-rel}.spv) - get_filename_component(output-dir ${spirv-file} DIRECTORY) - file(MAKE_DIRECTORY ${output-dir}) - - add_custom_command( - OUTPUT ${spirv-file} - COMMAND ${Vulkan_GLSLC_EXECUTABLE} -o ${spirv-file} ${input-file-abs} -MD -MT ${spirv-file} -MF ${spirv-file}.d - DEPENDS ${input-file-abs} - DEPFILE ${spirv-file}.d - COMMENT "Compiling GLSL to SPIRV: ${SHADER}" - VERBATIM - ) - - add_binary_resource(${TARGET} ${spirv-file}) -endfunction(add_glslc_shader) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index eb7a0dc6d6400..73f4c7da99d6a 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -12,8 +12,6 @@ set(MODULE GPUTrackingDisplay) if(ALIGPU_BUILD_TYPE STREQUAL "O2") - find_package(GLEW) - set_package_properties(GLEW PROPERTIES TYPE OPTIONAL) find_package(X11) set_package_properties(X11 PROPERTIES TYPE OPTIONAL) find_package(GLUT) @@ -28,16 +26,57 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") set_package_properties(O2GPUWayland PROPERTIES TYPE OPTIONAL) find_package(Qt6 COMPONENTS Widgets) set_package_properties(Qt6 PROPERTIES TYPE OPTIONAL) + set(GPUCA_CONFIG_GL3W 1) +endif() + +# glslc compiles the shaders to SPIR-V for both backends +if(Vulkan_GLSLC_EXECUTABLE AND NOT Vulkan_GLSLC_EXECUTABLE MATCHES "NOTFOUND") + set(GPUCA_GLSLC ${Vulkan_GLSLC_EXECUTABLE}) +else() + find_program(GPUCA_GLSLC glslc) +endif() + +# OpenGL backend: OpenGL library, OpenGL 4.6 headers (gl3w: from DebugGUI in O2, bundled in standalone; or GLEW) for SPIR-V shaders, and glslc +if(GPUCA_GLSLC AND TARGET OpenGL::GL) + include(CheckSymbolExists) + if(GPUCA_CONFIG_GL3W) + if(ALIGPU_BUILD_TYPE STREQUAL "O2") + get_target_property(CMAKE_REQUIRED_INCLUDES AliceO2::DebugGUI INTERFACE_INCLUDE_DIRECTORIES) + else() + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty) + endif() + check_symbol_exists(GL_VERSION_4_6 "GL/glcorearb.h" GPUCA_GL3W_HAS_GL_4_6) + if(GPUCA_GL3W_HAS_GL_4_6) + set(GPUCA_EVENT_DISPLAY_OPENGL ON) + endif() + else() + set(CMAKE_REQUIRED_INCLUDES ${GLEW_INCLUDE_DIRS}) + check_symbol_exists(GL_VERSION_4_6 "GL/glew.h" GPUCA_GLEW_HAS_GL_4_6) + if(GPUCA_GLEW_HAS_GL_4_6) + set(GPUCA_EVENT_DISPLAY_OPENGL ON) + endif() + endif() + unset(CMAKE_REQUIRED_INCLUDES) +endif() +if(NOT GPUCA_EVENT_DISPLAY_OPENGL) + if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") + message(FATAL_ERROR "OpenGL event display needs OpenGL, OpenGL 4.6 headers and glslc") + endif() + message(STATUS "OpenGL event display backend disabled: needs OpenGL, OpenGL 4.6 headers and glslc") + set(GLUT_FOUND 0) + set(OPENGL_GLU_FOUND 0) + set(GLEW_FOUND 0) + set(X11_FOUND 0) endif() if(Vulkan_FOUND) string(FIND "${Vulkan_VERSION}" "." vulkan_dot_index) - if(NOT ${Vulkan_VERSION} VERSION_GREATER_EQUAL "1.3.0" OR vulkan_dot_index EQUAL -1 OR Vulkan_GLSLC_EXECUTABLE STREQUAL "Vulkan_GLSLC_EXECUTABLE-NOTFOUND") + if(NOT ${Vulkan_VERSION} VERSION_GREATER_EQUAL "1.4.328" OR vulkan_dot_index EQUAL -1 OR NOT GPUCA_GLSLC) + message(STATUS "Vulkan event display disabled: need Vulkan headers >= 1.4.328 (found ${Vulkan_VERSION}) and glslc") set(Vulkan_FOUND 0) endif() endif() if(Vulkan_FOUND) - include(../cmake/vulkan_display.cmake) set(GPUCA_EVENT_DISPLAY_VULKAN ON) if(O2GPUWayland_FOUND) set(GPUCA_EVENT_DISPLAY_WAYLAND ON) @@ -45,67 +84,76 @@ if(Vulkan_FOUND) endif() if(Freetype_FOUND) set(GPUCA_EVENT_DISPLAY_FREETYPE ON) + if(Fontconfig_FOUND) + set(GPUCA_EVENT_DISPLAY_FONTCONFIG ON) + endif() endif() if(Qt6_FOUND) set(GPUCA_EVENT_DISPLAY_QT ON) endif() +foreach(feature OPENGL VULKAN WAYLAND FREETYPE FONTCONFIG QT) + if(NOT GPUCA_EVENT_DISPLAY_${feature}) + set(GPUCA_EVENT_DISPLAY_${feature} OFF) + endif() +endforeach() set(SRCS ../utils/qsem.cxx GPUDisplay.cxx helpers/GPUDisplayMagneticField.cxx frontend/GPUDisplayFrontend.cxx - frontend/GPUDisplayFrontendGlfw.cxx frontend/GPUDisplayFrontendNone.cxx backend/GPUDisplayBackend.cxx - backend/GPUDisplayBackendNone.cxx - backend/GPUDisplayBackendOpenGL.cxx) + backend/GPUDisplayBackendNone.cxx) set(SRCS_NO_H helpers/GPUDisplayLoader.cxx render/GPUDisplayDraw.cxx render/GPUDisplayImportEvent.cxx helpers/GPUDisplayHelpers.cxx helpers/GPUDisplayAnimation.cxx - helpers/GPUDisplayBackendOpenGLMagneticField.cxx helpers/GPUDisplayQuaternion.cxx helpers/GPUDisplayInterpolation.cxx helpers/GPUDisplayROOT.cxx frontend/GPUDisplayKeys.cxx) -set(HDRS_INSTALL shaders/GPUDisplayShaders.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) - if(GPUCA_EVENT_DISPLAY_WAYLAND) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-client-protocol.h - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-client-protocol.h - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml - COMMENT "Preparing Wayland xdg-shell-client-protocol.h") - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml - COMMENT "Preparing Wayland xdg-shell-protocol.c") +endif() + +if(GPUCA_EVENT_DISPLAY_OPENGL) + set(SRCS ${SRCS} backend/GPUDisplayBackendOpenGL.cxx) + set(SRCS_NO_H ${SRCS_NO_H} helpers/GPUDisplayBackendOpenGLMagneticField.cxx) +endif() + +if(GPUCA_EVENT_DISPLAY_WAYLAND) + set(WAYLAND_PROTOCOL_HEADERS) + set(WAYLAND_PROTOCOL_SOURCES) + foreach(xml ${O2GPUWayland_PROTOCOLS}) # List and existence check in dependencies/FindO2GPUWayland.cmake + get_filename_component(name ${xml} NAME_WE) + string(REGEX REPLACE "(-unstable)?-v[0-9]+$" "" name ${name}) + set(header ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-client-protocol.h) + set(source ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-protocol.c) add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-client-protocol.h + OUTPUT ${header} COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-client-protocol.h - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml - COMMENT "Preparing Wayland xdg-decoration-client-protocol.h") + COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${header} + MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} + COMMENT "Preparing Wayland ${name}-client-protocol.h") add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-protocol.c + OUTPUT ${source} COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-protocol.c - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml - COMMENT "Preparing Wayland xdg-decoration-protocol.c") + COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${source} + MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} + COMMENT "Preparing Wayland ${name}-protocol.c") + list(APPEND WAYLAND_PROTOCOL_HEADERS ${header}) + list(APPEND WAYLAND_PROTOCOL_SOURCES ${source}) + endforeach() - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c APPEND PROPERTY OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-client-protocol.h) - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES} APPEND PROPERTY OBJECT_DEPENDS ${WAYLAND_PROTOCOL_HEADERS}) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) - set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-protocol.c) - endif() + set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES}) endif() if(X11_FOUND) @@ -116,13 +164,16 @@ if(GLUT_FOUND) set(SRCS ${SRCS} frontend/GPUDisplayFrontendGlut.cxx) endif() +if(GLFW_FOUND) + set(SRCS ${SRCS} frontend/GPUDisplayFrontendGlfw.cxx) +endif() + if(GPUCA_EVENT_DISPLAY_QT) - set(CMAKE_AUTOMOC ON) - set(CMAKE_AUTORCC ON) - set(CMAKE_AUTOUIC ON) + qt_wrap_cpp(GPUCA_QT_MOC_SRCS frontend/GPUDisplayGUI.h) # Explicit moc / uic instead of AUTOMOC / AUTOUIC: rerun only if these files change; add new Q_OBJECT headers / .ui files here + qt_wrap_ui(GPUCA_QT_UI_HDRS frontend/GPUDisplayGUI.ui) set(SRCS ${SRCS} frontend/GPUDisplayGUI.cxx frontend/GPUDisplayGUIWrapper.cxx) - set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayGUI.ui) + set(SRCS_NO_H ${SRCS_NO_H} ${GPUCA_QT_MOC_SRCS} ${GPUCA_QT_UI_HDRS}) endif() string(REPLACE ".cxx" ".h" HDRS "${SRCS}") @@ -130,12 +181,10 @@ string(REPLACE ".cxx" ".h" HDRS "${SRCS}") if(ALIGPU_BUILD_TYPE STREQUAL "O2") o2_add_library(${MODULE} TARGETVARNAME targetName - PUBLIC_LINK_LIBRARIES O2::GPUTracking AliceO2::DebugGUI + PUBLIC_LINK_LIBRARIES O2::GPUTracking PUBLIC_INCLUDE_DIRECTORIES . SOURCES ${SRCS} ${SRCS_NO_H}) - target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLFW GPUCA_DISPLAY_GL3W GPUCA_DISPLAY_OPENGL_CORE) - o2_add_executable(field-uniform-exporter TARGETVARNAME exporterName COMPONENT_NAME gpu @@ -161,8 +210,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/setinclude.sh PERMISSIONS OWNER_READ O target_compile_definitions(${targetName} PRIVATE $) -message(STATUS "Building GPU Event Display (Vulkan ${GPUCA_EVENT_DISPLAY_VULKAN} ${Vulkan_VERSION}, Wayland ${GPUCA_EVENT_DISPLAY_WAYLAND}, Freetype ${GPUCA_EVENT_DISPLAY_FREETYPE}, Fontconfig ${Fontconfig_FOUND}, Qt ${GPUCA_EVENT_DISPLAY_QT})") -target_link_libraries(${targetName} PUBLIC ${GLFW_LIBRARIES} OpenGL::GL) +message(STATUS "Building GPU Event Display (OpenGL ${GPUCA_EVENT_DISPLAY_OPENGL}, Vulkan ${GPUCA_EVENT_DISPLAY_VULKAN} ${Vulkan_VERSION}, Wayland ${GPUCA_EVENT_DISPLAY_WAYLAND}, Freetype ${GPUCA_EVENT_DISPLAY_FREETYPE}, Fontconfig ${GPUCA_EVENT_DISPLAY_FONTCONFIG}, Qt ${GPUCA_EVENT_DISPLAY_QT})") target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) if(X11_FOUND) @@ -175,13 +223,23 @@ if(GLUT_FOUND) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLUT) endif() -if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") - if(GLFW_FOUND) - target_link_libraries(${targetName} PRIVATE glfw) - target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLFW) - endif() +if(GLFW_FOUND) + target_link_libraries(${targetName} PRIVATE ${GLFW_LIBRARIES} glfw) + target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLFW) +endif() + +if (OPENGL_GLU_FOUND) + target_link_libraries(${targetName} PRIVATE OpenGL::GLU) +endif() + +if(GPUCA_CONFIG_GL3W) + target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W GPUCA_DISPLAY_OPENGL_CORE) +endif() + +if(ALIGPU_BUILD_TYPE STREQUAL "O2") + target_link_libraries(${targetName} PRIVATE AliceO2::DebugGUI) +elseif(ALIGPU_BUILD_TYPE STREQUAL "Standalone") if(GPUCA_CONFIG_GL3W) - target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W) target_sources(${targetName} PRIVATE ${GPUTRACKING_DIR}/display/3rdparty/gl3w.c) else() target_link_libraries(${targetName} PRIVATE ${GLEW_LIBRARIES}) @@ -190,21 +248,35 @@ if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") target_include_directories(${targetName} SYSTEM PUBLIC ${GPUTRACKING_DIR}/display/3rdparty) endif() -if (OPENGL_GLU_FOUND) - target_link_libraries(${targetName} PRIVATE OpenGL::GLU) +if(GPUCA_EVENT_DISPLAY_OPENGL) + target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_OPENGL) + target_link_libraries(${targetName} PRIVATE OpenGL::GL) +endif() + +if(GPUCA_EVENT_DISPLAY_VULKAN OR GPUCA_EVENT_DISPLAY_OPENGL) + include(shaders.cmake) + set(SHADERS shaders/vertex.vert + shaders/vertexPoint.vert + shaders/vertexSmoothPoint.vert + shaders/vertexTexture.vert + shaders/vertexPassthrough.vert + shaders/fragment.frag + shaders/fragmentSmoothPoint.frag + shaders/vertexSmoothLine.vert + shaders/fragmentSmoothLine.frag + shaders/fragmentText.frag + shaders/fragmentTexture.frag + shaders/field.geom) + foreach(shader ${SHADERS}) + add_glslc_shader(${targetName} ${shader}) + endforeach() + write_glslc_shader_header(${targetName} GPUDisplayLoadShaders.h) endif() if(GPUCA_EVENT_DISPLAY_VULKAN) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_VULKAN) target_link_libraries(${targetName} PRIVATE Vulkan::Vulkan) - add_glslc_shader(${targetName} shaders/fragment.frag) - add_glslc_shader(${targetName} shaders/vertex.vert) - add_glslc_shader(${targetName} shaders/fragmentText.frag) - add_glslc_shader(${targetName} shaders/vertexTexture.vert) - add_glslc_shader(${targetName} shaders/fragmentTexture.frag) - add_glslc_shader(${targetName} shaders/fragmentUniform.frag) - add_glslc_shader(${targetName} shaders/vertexPoint.vert) if(GPUCA_EVENT_DISPLAY_WAYLAND) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_WAYLAND) target_link_libraries(${targetName} PRIVATE GPUWayland::wayland-client) @@ -214,7 +286,7 @@ endif() if(GPUCA_EVENT_DISPLAY_FREETYPE) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_FREETYPE) target_link_libraries(${targetName} PRIVATE Freetype::Freetype) - if(Fontconfig_FOUND) + if(GPUCA_EVENT_DISPLAY_FONTCONFIG) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_FONTCONFIG) target_link_libraries(${targetName} PRIVATE Fontconfig::Fontconfig) endif() @@ -223,6 +295,7 @@ endif() if(GPUCA_EVENT_DISPLAY_QT) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_QT) target_link_libraries(${targetName} PRIVATE Qt6::Widgets) + target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # ui_*.h endif() target_link_libraries(${targetName} PRIVATE TBB::tbb) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 7cad25916940a..2c0e6b447c4ed 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -16,6 +16,7 @@ #include "frontend/GPUDisplayInfo.inc" #include "GPUTPCDef.h" +#include "HandMadeMath.h" #include #include @@ -60,6 +61,8 @@ const GPUSettingsProcessing& GPUDisplay::GetProcessingConfig(GPUChainTracking* c GPUDisplay::GPUDisplay(GPUDisplayFrontend* frontend, GPUChainTracking* chain, GPUQA* qa, const GPUParam* param, const GPUCalibObjectsConst* calib, const GPUSettingsDisplay* config, const GPUSettingsProcessing* proc) : GPUDisplayInterface(), mFrontend(frontend), mChain(chain), mConfig(config ? *config : GetConfig(chain)), mProcessingSettings(proc ? *proc : GetProcessingConfig(chain)), mQA(qa) { + mViewMatrix = std::make_unique(); + mModelMatrix = std::make_unique(); mParam = param ? param : &mChain->GetParam(); mCalib = calib; mCfgL = mConfig.light; @@ -72,7 +75,7 @@ GPUDisplay::GPUDisplay(GPUDisplayFrontend* frontend, GPUChainTracking* chain, GP mBackend->mDisplay = this; frontend->mDisplay = this; frontend->mBackend = mBackend.get(); - mCfgR.openGLCore = mBackend->CoreProfile(); + mCfgR.rendererCompatSmoothing &= !mBackend->CoreProfile(); } void GPUDisplay::calcXYZ(const float* matrix) @@ -146,8 +149,8 @@ void GPUDisplay::ResizeScene(int32_t width, int32_t height, bool init) if (init) { mResetScene = true; - mViewMatrix = MY_HMM_IDENTITY; - mModelMatrix = MY_HMM_IDENTITY; + *mViewMatrix = MY_HMM_IDENTITY; + *mModelMatrix = MY_HMM_IDENTITY; } } @@ -251,6 +254,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla if (mCfgL.drawSector != -1) { scalefactor *= 0.2f; } + float* const mViewMatrixP = &mViewMatrix->Elements[0][0]; float sqrdist = sqrtf(sqrtf(mViewMatrixP[12] * mViewMatrixP[12] + mViewMatrixP[13] * mViewMatrixP[13] + mViewMatrixP[14] * mViewMatrixP[14]) * GL_SCALE_FACTOR) * 0.8f; if (sqrdist < 0.2f) { sqrdist = 0.2f; @@ -267,8 +271,8 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla } else if (mResetScene) { const float initialZpos = mCfgH.projectXY ? 16 : (mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR + 8) : 8); nextViewMatrix = nextViewMatrix * HMM_Translate({{0, 0, -initialZpos}}); - mViewMatrix = MY_HMM_IDENTITY; - mModelMatrix = MY_HMM_IDENTITY; + *mViewMatrix = MY_HMM_IDENTITY; + *mModelMatrix = MY_HMM_IDENTITY; mCfgL.pointSize = 2.0f; mCfgL.lineWidth = 1.4f; @@ -367,23 +371,23 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla nextViewMatrix = nextViewMatrix * HMM_Rotate(rotRoll * mYFactor, {{0, 0, 1}}); } } - nextViewMatrix = nextViewMatrix * mViewMatrix; // Apply previous translation / rotation + nextViewMatrix = nextViewMatrix * *mViewMatrix; // Apply previous translation / rotation if (yUp) { calcXYZ(&nextViewMatrix.Elements[0][0]); nextViewMatrix = HMM_Rotate(mAngle[2] * 180.f / CAMath::Pi(), {{0, 0, 1}}) * nextViewMatrix; } if (rotateModel) { if (rotYaw != 0.f) { - mModelMatrix = HMM_Rotate(rotYaw, {{nextViewMatrix.Elements[0][1], nextViewMatrix.Elements[1][1], nextViewMatrix.Elements[2][1]}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(rotYaw, {{nextViewMatrix.Elements[0][1], nextViewMatrix.Elements[1][1], nextViewMatrix.Elements[2][1]}}) * *mModelMatrix; } if (rotPitch != 0.f) { - mModelMatrix = HMM_Rotate(rotPitch, {{nextViewMatrix.Elements[0][0], nextViewMatrix.Elements[1][0], nextViewMatrix.Elements[2][0]}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(rotPitch, {{nextViewMatrix.Elements[0][0], nextViewMatrix.Elements[1][0], nextViewMatrix.Elements[2][0]}}) * *mModelMatrix; } if (rotRoll != 0.f) { if (rotateModelTPC) { - mModelMatrix = HMM_Rotate(-rotRoll, {{0, 0, 1}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(-rotRoll, {{0, 0, 1}}) * *mModelMatrix; } else { - mModelMatrix = HMM_Rotate(-rotRoll, {{nextViewMatrix.Elements[0][2], nextViewMatrix.Elements[1][2], nextViewMatrix.Elements[2][2]}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(-rotRoll, {{nextViewMatrix.Elements[0][2], nextViewMatrix.Elements[1][2], nextViewMatrix.Elements[2][2]}}) * *mModelMatrix; } } } @@ -414,8 +418,8 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla // Store position if (animateTime < 0) { - mViewMatrix = nextViewMatrix; - calcXYZ(mViewMatrixP); + *mViewMatrix = nextViewMatrix; + calcXYZ(&mViewMatrix->Elements[0][0]); } if (mFrontend->mMouseDn || mFrontend->mMouseDnR) { @@ -465,12 +469,14 @@ void GPUDisplay::DrawGLScene_drawCommands() if (mCfgL.drawTOF) { SetColorTOF(); mBackend->pointSizeFactor(2); - LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[0][tTOFCLUSTER][0], GPUDisplayBackend::POINTS)); + // LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[iSector][tTOFCLUSTER][iCol], GPUDisplayBackend::POINTS)); + drawVertices(mGlDLPoints[0][tTOFCLUSTER][0], GPUDisplayBackend::POINTS); mBackend->pointSizeFactor(1); } if (mCfgL.drawITS) { SetColorITS(); - LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[0][tITSCLUSTER][0], GPUDisplayBackend::POINTS)); + // LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[iSector][tITSCLUSTER][iCol], GPUDisplayBackend::POINTS)); + drawVertices(mGlDLPoints[0][tITSCLUSTER][0], GPUDisplayBackend::POINTS); } if (mCfgL.drawTPC) { SetColorClusters(); @@ -657,7 +663,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) } // Draw Event - nextViewMatrix = nextViewMatrix * mModelMatrix; + nextViewMatrix = nextViewMatrix * *mModelMatrix; const float zFar = ((mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR) : 8.f) + 50.f) * 2.f; const hmm_mat4 proj = HMM_Perspective(mCfgR.fov, (float)mBackend->mRenderWidth / (float)mBackend->mRenderHeight, 0.1f, zFar); mBackend->prepareDraw(proj, nextViewMatrix, doScreenshot || mRequestScreenshot, renderToMixBuffer, mixSlaveImage); @@ -732,6 +738,12 @@ void GPUDisplay::ShowNextEvent(const GPUTrackingInOutPointers* ptrs) void GPUDisplay::BlockTillNextEvent() { mSemLockDisplay.Lock(); } +void GPUDisplay::UnblockDisplay() +{ + mSemLockDisplay.Trylock(); + mSemLockDisplay.Unlock(); +} + void GPUDisplay::WaitTillEventShown() { std::unique_lock lock(mMutexLoadAndShowEvent); diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index b450ac1c18060..541d722e8eaaf 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -25,7 +25,7 @@ #include #include -#include "HandMadeMath.h" +union hmm_mat4; // From HandMadeMath #include "utils/timer.h" #include "utils/vecpod.h" @@ -41,14 +41,18 @@ class GPUTPCGMPropagator; class GPUDisplay : public GPUDisplayInterface { + friend class GPUDisplayFrontend; + public: GPUDisplay(GPUDisplayFrontend* frontend, GPUChainTracking* chain, GPUQA* qa, const GPUParam* param = nullptr, const GPUCalibObjectsConst* calib = nullptr, const GPUSettingsDisplay* config = nullptr, const GPUSettingsProcessing* proc = nullptr); GPUDisplay(const GPUDisplay&) = delete; ~GPUDisplay() override = default; int32_t StartDisplay() override; + void ExitDisplay(); void ShowNextEvent(const GPUTrackingInOutPointers* ptrs = nullptr) override; void BlockTillNextEvent() override; + void UnblockDisplay(); void WaitTillEventShown() override; void SetCollisionFirstCluster(uint32_t collision, int32_t sector, int32_t cluster) override; void UpdateCalib(const GPUCalibObjectsConst* calib) override { mCalib = calib; } @@ -58,7 +62,6 @@ class GPUDisplay : public GPUDisplayInterface int32_t DrawGLScene(); void HandleSendKey(int32_t key); int32_t InitDisplay(bool initFailure = false); - void ExitDisplay(); void ResizeScene(int32_t width, int32_t height, bool init = false); const GPUSettingsDisplayRenderer& cfgR() const { return mCfgR; } @@ -245,8 +248,7 @@ class GPUDisplay : public GPUDisplayInterface float mAngleRollOrigin = -1e9; float mMaxClusterZ = -1; - hmm_mat4 mViewMatrix, mModelMatrix; - float* const mViewMatrixP = &mViewMatrix.Elements[0][0]; + std::unique_ptr mViewMatrix, mModelMatrix; float mXYZ[3]; float mAngle[3]; float mRPhiTheta[3]; diff --git a/GPU/GPUTracking/display/GPUDisplayInterface.h b/GPU/GPUTracking/display/GPUDisplayInterface.h index 7caceb1699da6..5f5a101233191 100644 --- a/GPU/GPUTracking/display/GPUDisplayInterface.h +++ b/GPU/GPUTracking/display/GPUDisplayInterface.h @@ -56,14 +56,11 @@ class GPUDisplayFrontendInterface public: virtual ~GPUDisplayFrontendInterface(); static GPUDisplayFrontendInterface* getFrontend(const char* type); - virtual void DisplayExit() = 0; + virtual void StopDisplay() = 0; virtual bool EnableSendKey() = 0; virtual int32_t getDisplayControl() const = 0; - virtual int32_t getSendKey() const = 0; - virtual int32_t getNeedUpdate() const = 0; virtual void setDisplayControl(int32_t v) = 0; - virtual void setSendKey(int32_t v) = 0; - virtual void setNeedUpdate(int32_t v) = 0; + virtual void sendKey(int32_t v) = 0; virtual const char* frontendName() const = 0; protected: diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index a459a8e290ef6..dd39cb071efc8 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -15,8 +15,10 @@ #include "GPUDisplayBackend.h" #include "helpers/GPUDisplayMagneticField.h" -#include "GPUDisplayBackendOpenGL.h" #include "GPUDisplayBackendNone.h" +#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL +#include "GPUDisplayBackendOpenGL.h" +#endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_VULKAN #include "GPUDisplayBackendVulkan.h" #endif @@ -50,9 +52,12 @@ GPUDisplayBackend* GPUDisplayBackend::getBackend(const char* type) return new GPUDisplayBackendVulkan; } else #endif - if (strcmp(type, "opengl") == 0 || strcmp(type, "auto") == 0) { +#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL + if (strcmp(type, "opengl") == 0 || strcmp(type, "auto") == 0) { return new GPUDisplayBackendOpenGL; - } else if (strcmp(type, "none") == 0) { + } else +#endif + if (strcmp(type, "none") == 0) { return new GPUDisplayBackendNone; } else { GPUError("Requested renderer not available"); @@ -66,9 +71,23 @@ int32_t GPUDisplayBackend::InitBackend() if (retVal) { return retVal; } - if (mDisplay->cfg().noFreetype) { - return retVal; + if (!mDisplay->cfg().noFreetype) { + loadFont(); } + return retVal; +} + +void GPUDisplayBackend::updateFontScale(float scale) +{ + if (mFreetypeInitialized && scale != mFontRasterScale) { + clearFontSymbols(); + mFreetypeInitialized = false; + loadFont(); + } +} + +void GPUDisplayBackend::loadFont() +{ std::string fontName = mDisplay->cfg().font; #ifdef GPUCA_BUILD_EVENT_DISPLAY_FONTCONFIG FcInit(); @@ -97,11 +116,11 @@ int32_t GPUDisplayBackend::InitBackend() FT_Face face; if (FT_Init_FreeType(&ft)) { GPUError("Error initializing freetype"); - return 0; + return; } if (FT_New_Face(ft, fontName.c_str(), 0, &face)) { GPUError("Error loading freetypoe font"); - return 0; + return; } int32_t fontSize = mDisplay->cfg().fontSize; @@ -109,12 +128,13 @@ int32_t GPUDisplayBackend::InitBackend() if (smoothFont()) { fontSize *= 4; // Font size scaled by 4, can be downsampled } - FT_Set_Pixel_Sizes(face, 0, fontSize); + mFontRasterScale = mDisplay->frontend()->getDisplayScale(); + FT_Set_Pixel_Sizes(face, 0, (uint32_t)(fontSize * mFontRasterScale + 0.5f)); // Rasterize at physical pixel size for HiDPI for (uint32_t i = 0; i < 128; i++) { if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { GPUError("Error loading freetype symbol"); - return 0; + return; } const auto& glyph = face->glyph; addFontSymbol(i, glyph->bitmap.width, glyph->bitmap.rows, glyph->bitmap_left, glyph->bitmap_top, glyph->advance.x, glyph->bitmap.buffer); @@ -124,7 +144,6 @@ int32_t GPUDisplayBackend::InitBackend() FT_Done_FreeType(ft); mFreetypeInitialized = true; #endif // GPUCA_BUILD_EVENT_DISPLAY_FREETYPE - return retVal; } void GPUDisplayBackend::ExitBackend() diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 546c53e1e63ff..9342b8a642f33 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -22,10 +22,6 @@ #include #include -#if defined(GPUCA_DISPLAY_GL3W) && !defined(GPUCA_DISPLAY_OPENGL_CORE) -#define GPUCA_DISPLAY_OPENGL_CORE -#endif - union hmm_mat4; namespace o2::gpu @@ -86,6 +82,7 @@ class GPUDisplayBackend virtual int32_t InitBackendA() = 0; virtual void ExitBackendA() = 0; int32_t InitBackend(); + void updateFontScale(float scale); // Rasterize the font again if the display (HiDPI) scale changed void ExitBackend(); virtual void loadDataToGPU(size_t totalVertizes) = 0; virtual void prepareDraw(const hmm_mat4& proj, const hmm_mat4& view, bool requestScreenshot = false, bool toMixBuffer = false, float includeMixImage = 0.f) = 0; @@ -108,6 +105,8 @@ class GPUDisplayBackend protected: virtual void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) = 0; virtual void initializeTextDrawing() = 0; + virtual void clearFontSymbols() {} // Release glyphs / font atlas before loading the font again + void loadFont(); float getDownsampleFactor(bool screenshot = false); void fillIndirectCmdBuffer(); @@ -117,9 +116,12 @@ class GPUDisplayBackend std::vector mIndirectSectorOffset; vecpod mCmdBuffer; bool mFreetypeInitialized = false; + float mFontRasterScale = 1.f; // Display scale the font was rasterized for bool mFrontendCompatTextDraw = false; std::vector mScreenshotPixels; + static constexpr float SMOOTH_POINT_EXTRA_SIZE = 0.6f; + static constexpr float SMOOTH_LINE_EXTRA_WIDTH = 0.8f; int32_t mDownsampleFactor = 1; uint32_t mRenderWidth = 0; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index effe59588da24..1af8f564d4298 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -22,10 +22,10 @@ #else #define gluErrorString(err) "" #endif +#include "HandMadeMath.h" #include "GPUCommonDef.h" #include "GPUDisplayBackendOpenGL.h" -#include "shaders/GPUDisplayShaders.h" #include "GPUDisplay.h" #include "GPULogging.h" #include "GPUParam.h" @@ -35,25 +35,16 @@ using namespace o2::gpu; -#ifdef GPUCA_BUILD_EVENT_DISPLAY_VULKAN -#include "utils/qGetLdBinarySymbols.h" -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertex_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragmentUniform_frag_spv); -#endif - -// Runtime minimum version defined in GPUDisplayFrontend.h, keep in sync! -#define GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#if !defined(GL_VERSION_4_5) || GL_VERSION_4_5 != 1 -#ifdef GPUCA_STANDALONE -#warning Unsupported OpenGL version < 4.5, disabling standalone event display -#error Unsupported OpenGL version < 4.5 -#else -#pragma message "Unsupported OpenGL version < 4.5, disabling standalone event display" -#endif -#undef GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#endif +#define GPUCA_DISPLAY_SHADER_TYPE_OPENGL +#include "GPUDisplayLoadShaders.h" -#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL +uint32_t GPUDisplayBackendOpenGL::loadShader(uint32_t type, const char* spirv, size_t size) +{ + uint32_t shader = glCreateShader(type); + glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, spirv, size); + glSpecializeShader(shader, "main", 0, nullptr, nullptr); + return shader; +} GPUDisplayBackendOpenGL::GPUDisplayBackendOpenGL() { @@ -189,12 +180,12 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType } if (mDisplay->useMultiVBO()) { - if (mDisplay->cfgR().openGLCore) { + if (!mDisplay->cfgR().rendererCompatSmoothing) { // TODO: Remove OpenGL compat profile completely CHKERR(glBindVertexArray(mVertexArray)); } CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[iSector])); #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); } else #endif @@ -204,6 +195,10 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType } } + const uint32_t smoothProgram = tt == POINTS ? (mUseSmoothPointShader ? mShaderProgramSmoothPoint : 0) : (mUseSmoothLineShader ? mShaderProgramSmoothLine : 0); + if (smoothProgram) { + CHKERR(glUseProgram(smoothProgram)); + } if (mDisplay->cfgR().useGLIndirectDraw) { CHKERR(glMultiDrawArraysIndirect(t, (void*)(size_t)((mIndirectSectorOffset[iSector] + first) * sizeof(DrawArraysIndirectCommand)), count, 0)); } else if (OPENGL_EMULATE_MULTI_DRAW) { @@ -214,23 +209,26 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType static_assert(sizeof(GLsizei) == sizeof(*mDisplay->vertexBufferCount()[iSector].data()), "Invalid counter size does not match GLsizei"); CHKERR(glMultiDrawArrays(t, mDisplay->vertexBufferStart()[iSector].data() + first, ((const GLsizei*)mDisplay->vertexBufferCount()[iSector].data()) + first, count)); } + if (smoothProgram) { + CHKERR(glUseProgram(mShaderProgram)); + } return count; } void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) { #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { glColor4f(color[0], color[1], color[2], color[3]); } else #endif { - if (mSPIRVShaders) { - CHKERR(glBindBuffer(GL_UNIFORM_BUFFER, mSPIRVColorBuffer)); - CHKERR(glBufferData(GL_UNIFORM_BUFFER, sizeof(color), &color[0], GL_STATIC_DRAW)); - CHKERR(glBindBufferBase(GL_UNIFORM_BUFFER, 1, mSPIRVColorBuffer)); - } else { - CHKERR(glUniform4fv(mColorId, 1, &color[0])); + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); + if (mUseSmoothPointShader) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_COLOR, 1, &color[0])); + } + if (mUseSmoothLineShader) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_COLOR, 1, &color[0])); } } } @@ -322,34 +320,16 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() #endif setDepthBuffer(); setQuality(); - CHKERR(mVertexShader = glCreateShader(GL_VERTEX_SHADER)); - CHKERR(mFragmentShader = glCreateShader(GL_FRAGMENT_SHADER)); - CHKERR(mVertexShaderTexture = glCreateShader(GL_VERTEX_SHADER)); - CHKERR(mFragmentShaderTexture = glCreateShader(GL_FRAGMENT_SHADER)); - CHKERR(mFragmentShaderText = glCreateShader(GL_FRAGMENT_SHADER)); -#if defined(GL_VERSION_4_6) && GL_VERSION_4_6 == 1 && defined(GPUCA_BUILD_EVENT_DISPLAY_VULKAN) - if (getenv("USE_SPIRV_SHADERS") && atoi(getenv("USE_SPIRV_SHADERS"))) { - CHKERR(glShaderBinary(1, &mVertexShader, GL_SHADER_BINARY_FORMAT_SPIR_V_ARB, _binary_shaders_shaders_vertex_vert_spv_start, _binary_shaders_shaders_vertex_vert_spv_len)); - CHKERR(glSpecializeShader(mVertexShader, "main", 0, nullptr, nullptr)); - CHKERR(glShaderBinary(1, &mFragmentShader, GL_SHADER_BINARY_FORMAT_SPIR_V_ARB, _binary_shaders_shaders_fragmentUniform_frag_spv_start, _binary_shaders_shaders_fragmentUniform_frag_spv_len)); - CHKERR(glSpecializeShader(mFragmentShader, "main", 0, nullptr, nullptr)); - GPUInfo("Using SPIR-V shaders"); - mSPIRVShaders = true; - } else -#endif - { - CHKERR(glShaderSource(mVertexShader, 1, &GPUDisplayShaders::vertexShader, nullptr)); - CHKERR(glCompileShader(mVertexShader)); - CHKERR(glShaderSource(mFragmentShader, 1, &GPUDisplayShaders::fragmentShader, nullptr)); - CHKERR(glCompileShader(mFragmentShader)); - } - CHKERR(glShaderSource(mVertexShaderTexture, 1, &GPUDisplayShaders::vertexShaderTexture, nullptr)); - CHKERR(glCompileShader(mVertexShaderTexture)); - CHKERR(glShaderSource(mFragmentShaderTexture, 1, &GPUDisplayShaders::fragmentShaderTexture, nullptr)); - CHKERR(glCompileShader(mFragmentShaderTexture)); - CHKERR(glShaderSource(mFragmentShaderText, 1, &GPUDisplayShaders::fragmentShaderText, nullptr)); - CHKERR(glCompileShader(mFragmentShaderText)); - if (checkShaderStatus(mVertexShader) || checkShaderStatus(mFragmentShader) || checkShaderStatus(mVertexShaderTexture) || checkShaderStatus(mFragmentShaderTexture) || checkShaderStatus(mFragmentShaderText)) { + CHKERR(mVertexShader = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertex_vert_spv_start, _binary_opengl_vertex_vert_spv_len)); + CHKERR(mFragmentShader = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragment_frag_spv_start, _binary_opengl_fragment_frag_spv_len)); + CHKERR(mVertexShaderTexture = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexTexture_vert_spv_start, _binary_opengl_vertexTexture_vert_spv_len)); + CHKERR(mFragmentShaderTexture = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentTexture_frag_spv_start, _binary_opengl_fragmentTexture_frag_spv_len)); + CHKERR(mFragmentShaderText = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentText_frag_spv_start, _binary_opengl_fragmentText_frag_spv_len)); + CHKERR(mVertexShaderSmoothPoint = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexSmoothPoint_vert_spv_start, _binary_opengl_vertexSmoothPoint_vert_spv_len)); + CHKERR(mFragmentShaderSmoothPoint = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentSmoothPoint_frag_spv_start, _binary_opengl_fragmentSmoothPoint_frag_spv_len)); + CHKERR(mVertexShaderSmoothLine = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexSmoothLine_vert_spv_start, _binary_opengl_vertexSmoothLine_vert_spv_len)); + CHKERR(mFragmentShaderSmoothLine = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentSmoothLine_frag_spv_start, _binary_opengl_fragmentSmoothLine_frag_spv_len)); + if (checkShaderStatus(mVertexShader) || checkShaderStatus(mFragmentShader) || checkShaderStatus(mVertexShaderSmoothPoint) || checkShaderStatus(mFragmentShaderSmoothPoint) || checkShaderStatus(mVertexShaderSmoothLine) || checkShaderStatus(mFragmentShaderSmoothLine) || checkShaderStatus(mVertexShaderTexture) || checkShaderStatus(mFragmentShaderTexture) || checkShaderStatus(mFragmentShaderText)) { return 1; } CHKERR(mShaderProgram = glCreateProgram()); @@ -359,12 +339,19 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() if (checkProgramStatus(mShaderProgram)) { return 1; } - if (mSPIRVShaders) { - CHKERR(glGenBuffers(1, &mSPIRVModelViewBuffer)); - CHKERR(glGenBuffers(1, &mSPIRVColorBuffer)); - } else { - CHKERR(mModelViewProjId = glGetUniformLocation(mShaderProgram, "ModelViewProj")); - CHKERR(mColorId = glGetUniformLocation(mShaderProgram, "color")); + CHKERR(mShaderProgramSmoothPoint = glCreateProgram()); + CHKERR(glAttachShader(mShaderProgramSmoothPoint, mVertexShaderSmoothPoint)); + CHKERR(glAttachShader(mShaderProgramSmoothPoint, mFragmentShaderSmoothPoint)); + CHKERR(glLinkProgram(mShaderProgramSmoothPoint)); + if (checkProgramStatus(mShaderProgramSmoothPoint)) { + return 1; + } + CHKERR(mShaderProgramSmoothLine = glCreateProgram()); + CHKERR(glAttachShader(mShaderProgramSmoothLine, mVertexShaderSmoothLine)); + CHKERR(glAttachShader(mShaderProgramSmoothLine, mFragmentShaderSmoothLine)); + CHKERR(glLinkProgram(mShaderProgramSmoothLine)); + if (checkProgramStatus(mShaderProgramSmoothLine)) { + return 1; } CHKERR(mShaderProgramText = glCreateProgram()); CHKERR(glAttachShader(mShaderProgramText, mVertexShaderTexture)); @@ -373,8 +360,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() if (checkProgramStatus(mShaderProgramText)) { return 1; } - CHKERR(mModelViewProjIdText = glGetUniformLocation(mShaderProgramText, "projection")); - CHKERR(mColorIdText = glGetUniformLocation(mShaderProgramText, "textColor")); CHKERR(mShaderProgramTexture = glCreateProgram()); CHKERR(glAttachShader(mShaderProgramTexture, mVertexShaderTexture)); CHKERR(glAttachShader(mShaderProgramTexture, mFragmentShaderTexture)); @@ -382,8 +367,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() if (checkProgramStatus(mShaderProgramTexture)) { return 1; } - CHKERR(mModelViewProjIdTexture = glGetUniformLocation(mShaderProgramTexture, "projection")); - CHKERR(mAlphaIdTexture = glGetUniformLocation(mShaderProgramTexture, "alpha")); CHKERR(glGenVertexArrays(1, &mVertexArray)); CHKERR(glGenVertexArrays(1, &VAO_texture)); @@ -407,6 +390,12 @@ void GPUDisplayBackendOpenGL::ExitBackendA() CHKERR(glDeleteProgram(mShaderProgram)); CHKERR(glDeleteShader(mVertexShader)); CHKERR(glDeleteShader(mFragmentShader)); + CHKERR(glDeleteProgram(mShaderProgramSmoothPoint)); + CHKERR(glDeleteShader(mVertexShaderSmoothPoint)); + CHKERR(glDeleteShader(mFragmentShaderSmoothPoint)); + CHKERR(glDeleteProgram(mShaderProgramSmoothLine)); + CHKERR(glDeleteShader(mVertexShaderSmoothLine)); + CHKERR(glDeleteShader(mFragmentShaderSmoothLine)); CHKERR(glDeleteProgram(mShaderProgramText)); CHKERR(glDeleteShader(mFragmentShaderText)); CHKERR(glDeleteProgram(mShaderProgramTexture)); @@ -425,10 +414,6 @@ void GPUDisplayBackendOpenGL::ExitBackendA() CHKERR(glDeleteTextures(1, &symbol.texId)); } } - if (mSPIRVShaders) { - CHKERR(glDeleteBuffers(1, &mSPIRVModelViewBuffer)); - CHKERR(glDeleteBuffers(1, &mSPIRVColorBuffer)); - } if (mMagneticFieldVisualization) { ExitMagFieldVisualization(); } @@ -484,13 +469,20 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& setFrameBuffer(0); } clearScreen(); + mUseSmoothPointShader = mDisplay->cfgL().smoothPoints && !mDisplay->cfgR().rendererCompatSmoothing; // The compat profile uses GL_POINT_SMOOTH / GL_LINE_SMOOTH + mUseSmoothLineShader = mDisplay->cfgL().smoothLines && !mDisplay->cfgR().rendererCompatSmoothing; #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgL().smoothPoints && !mDisplay->cfgR().openGLCore) { + if (mUseSmoothPointShader) { // Compatibility context: gl_PointCoord is only defined with point sprites + CHKERR(glEnable(GL_POINT_SPRITE)); + } else { + CHKERR(glDisable(GL_POINT_SPRITE)); + } + if (mDisplay->cfgL().smoothPoints && mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glEnable(GL_POINT_SMOOTH)); } else { CHKERR(glDisable(GL_POINT_SMOOTH)); } - if (mDisplay->cfgL().smoothLines && !mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgL().smoothLines && mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glEnable(GL_LINE_SMOOTH)); } else { CHKERR(glDisable(GL_LINE_SMOOTH)); @@ -501,7 +493,7 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& glViewport(0, 0, mRenderWidth, mRenderHeight); #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glEnableClientState(GL_VERTEX_ARRAY)); CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); } else @@ -513,7 +505,7 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& CHKERR(glEnableVertexAttribArray(0)); } #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glMatrixMode(GL_PROJECTION)); CHKERR(glLoadMatrixf(&proj.Elements[0][0])); CHKERR(glMatrixMode(GL_MODELVIEW)); @@ -522,12 +514,13 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& #endif { const hmm_mat4 modelViewProj = proj * view; - if (mSPIRVShaders) { - glBindBuffer(GL_UNIFORM_BUFFER, mSPIRVModelViewBuffer); - glBufferData(GL_UNIFORM_BUFFER, sizeof(modelViewProj), &modelViewProj, GL_STATIC_DRAW); - glBindBufferBase(GL_UNIFORM_BUFFER, 0, mSPIRVModelViewBuffer); - } else { - CHKERR(glUniformMatrix4fv(mModelViewProjId, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + if (mUseSmoothPointShader) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + } + if (mUseSmoothLineShader) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + CHKERR(glProgramUniform2f(mShaderProgramSmoothLine, UNIFORM_LOCATION_VIEWPORT, mRenderWidth, mRenderHeight)); } if (mMagneticFieldVisualization) { CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); @@ -538,7 +531,7 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& void GPUDisplayBackendOpenGL::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) { #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glDisableClientState(GL_VERTEX_ARRAY)); } else #endif @@ -603,9 +596,9 @@ void GPUDisplayBackendOpenGL::prepareText() CHKERR(glUseProgram(mShaderProgramText)); CHKERR(glActiveTexture(GL_TEXTURE0)); CHKERR(glBindVertexArray(VAO_text)); - CHKERR(glUniformMatrix4fv(mModelViewProjIdText, 1, GL_FALSE, &proj.Elements[0][0])); + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); float color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - CHKERR(glUniform4fv(mColorIdText, 1, &color[0])); + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); } } @@ -623,7 +616,7 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1.f, 1.f); glDisable(GL_DEPTH_TEST); #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMatrixMode(GL_PROJECTION); @@ -635,14 +628,14 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) CHKERR(glUseProgram(mShaderProgramTexture)); CHKERR(glActiveTexture(GL_TEXTURE0)); CHKERR(glBindVertexArray(VAO_texture)); - CHKERR(glUniformMatrix4fv(mModelViewProjIdTexture, 1, GL_FALSE, &proj.Elements[0][0])); - CHKERR(glUniform1f(mAlphaIdTexture, mixSlaveImage)); + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); + CHKERR(glUniform1f(UNIFORM_LOCATION_COLOR, mixSlaveImage)); } CHKERR(glBindTexture(GL_TEXTURE_2D, mMixBuffer.fbCol_id)); #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { glColor4f(1, 1, 1, mixSlaveImage); glBegin(GL_QUADS); glTexCoord2f(0, 0); @@ -669,12 +662,22 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) { - CHKERR(glPointSize(mDisplay->cfgL().pointSize * mDownsampleFactor * factor)); + const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mUseSmoothPointShader ? SMOOTH_POINT_EXTRA_SIZE : 0.f); + if (mUseSmoothPointShader) { + CHKERR(glProgramUniform1f(mShaderProgramSmoothPoint, UNIFORM_LOCATION_POINT_SIZE, size)); + } + CHKERR(glPointSize(size)); } void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) { - CHKERR(glLineWidth(mDisplay->cfgL().lineWidth * mDownsampleFactor * factor)); + float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; + if (mUseSmoothLineShader) { + width += SMOOTH_LINE_EXTRA_WIDTH; + CHKERR(glProgramUniform1f(mShaderProgramSmoothLine, UNIFORM_LOCATION_LINE_WIDTH, width)); + width *= 1.4143f; // Wide lines are parallelograms in OpenGL, widen so that the perpendicular width is covered + } + CHKERR(glLineWidth(width)); } void GPUDisplayBackendOpenGL::addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) @@ -709,6 +712,16 @@ void GPUDisplayBackendOpenGL::addFontSymbol(int32_t symbol, int32_t sizex, int32 CHKERR(glPixelStorei(GL_UNPACK_ALIGNMENT, oldAlign)); } +void GPUDisplayBackendOpenGL::clearFontSymbols() +{ + CHKERR(glDeleteBuffers(1, &VBO_text)); + CHKERR(glDeleteVertexArrays(1, &VAO_text)); + for (auto& symbol : mFontSymbols) { + CHKERR(glDeleteTextures(1, &symbol.texId)); + } + mFontSymbols.clear(); +} + void GPUDisplayBackendOpenGL::initializeTextDrawing() { CHKERR(glGenVertexArrays(1, &VAO_text)); @@ -727,7 +740,7 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { return; } - CHKERR(glUniform4fv(mColorIdText, 1, color)); + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); if (smoothFont()) { scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 @@ -813,32 +826,3 @@ void GPUDisplayBackendOpenGL::updateRenderer(bool withScreenshot) createFB(mMixBuffer, true, true, false, mRenderWidth, mRenderHeight); setQuality(); } - -#else // GPUCA_BUILD_EVENT_DISPLAY_OPENGL -GPUDisplayBackendOpenGL::GPUDisplayBackendOpenGL() -{ -} -int32_t GPUDisplayBackendOpenGL::checkShaderStatus(uint32_t shader) { return 0; } -int32_t GPUDisplayBackendOpenGL::checkProgramStatus(uint32_t program) { return 0; } -int32_t GPUDisplayBackendOpenGL::ExtInit() { throw std::runtime_error("Insufficnet OpenGL version"); } -bool GPUDisplayBackendOpenGL::CoreProfile() { return false; } -uint32_t GPUDisplayBackendOpenGL::DepthBits() { return 0; } -uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType t) { return 0; } -void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) {} -void GPUDisplayBackendOpenGL::setQuality() {} -void GPUDisplayBackendOpenGL::setDepthBuffer() {} -int32_t GPUDisplayBackendOpenGL::InitBackendA() { throw std::runtime_error("Insufficnet OpenGL version"); } -void GPUDisplayBackendOpenGL::ExitBackendA() {} -void GPUDisplayBackendOpenGL::loadDataToGPU(size_t totalVertizes) {} -void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& view, bool requestScreenshot, bool toMixBuffer, float includeMixImage) {} -void GPUDisplayBackendOpenGL::resizeScene(uint32_t width, uint32_t height) {} -void GPUDisplayBackendOpenGL::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) {} -void GPUDisplayBackendOpenGL::finishFrame(bool doScreenshot, bool toMixBuffer, float includeMixImage) {} -void GPUDisplayBackendOpenGL::prepareText() {} -void GPUDisplayBackendOpenGL::finishText() {} -void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) {} -void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) {} -void GPUDisplayBackendOpenGL::addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) {} -void GPUDisplayBackendOpenGL::initializeTextDrawing() {} -void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float* color, float scale) {} -#endif // GPUCA_BUILD_EVENT_DISPLAY_OPENGL diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index d9bb50d7bead8..ee54c1097ad35 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -49,6 +49,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void setFrameBuffer(uint32_t newID = 0); int32_t InitBackendA() override; int32_t InitMagFieldVisualization(); + bool mMagneticFieldFailed = false; // Initialization of the field drawing failed, do not retry void ExitBackendA() override; void ExitMagFieldVisualization(); static int32_t checkShaderStatus(uint32_t shader); @@ -68,9 +69,16 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void lineWidthFactor(float factor) override; size_t needMultiVBO() override { return 0x100000000ll; } void readImageToPixels(); + static uint32_t loadShader(uint32_t type, const char* spirv, size_t size); // Create shader from SPIR-V + static constexpr int32_t UNIFORM_LOCATION_MATRIX = 0; // Explicit uniform locations in the OpenGL variant of the shaders (shaders/*.vert, *.frag) + static constexpr int32_t UNIFORM_LOCATION_COLOR = 4; // color, textColor, alpha + static constexpr int32_t UNIFORM_LOCATION_POINT_SIZE = 5; // vertexSmoothPoint.vert + static constexpr int32_t UNIFORM_LOCATION_VIEWPORT = 6; // vertexSmoothLine.vert + static constexpr int32_t UNIFORM_LOCATION_LINE_WIDTH = 7; // vertexSmoothLine.vert void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; + void clearFontSymbols() override; void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; struct FontSymbolOpenGL : public FontSymbol { @@ -79,12 +87,20 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t mVertexShader; uint32_t mFragmentShader; + uint32_t mVertexShaderSmoothPoint; + uint32_t mFragmentShaderSmoothPoint; + uint32_t mVertexShaderSmoothLine; + uint32_t mFragmentShaderSmoothLine; uint32_t mVertexShaderTexture; uint32_t mVertexShaderPassthrough; uint32_t mFragmentShaderTexture; uint32_t mFragmentShaderText; uint32_t mGeometryShader; uint32_t mShaderProgram; + uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints in the core profile (no GL_POINT_SMOOTH) + bool mUseSmoothPointShader = false; + uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines in the core profile (no GL_LINE_SMOOTH) + bool mUseSmoothLineShader = false; uint32_t mShaderProgramText; uint32_t mShaderProgramTexture; uint32_t mShaderProgramField; @@ -93,14 +109,6 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t mIndirectId; std::vector mVBOId; std::vector mFontSymbols; - int32_t mModelViewProjId; - int32_t mColorId; - int32_t mModelViewProjIdTexture; - int32_t mAlphaIdTexture; - int32_t mModelViewProjIdText; - int32_t mColorIdText; - uint32_t mSPIRVModelViewBuffer; - uint32_t mSPIRVColorBuffer; uint32_t mFieldModelViewBuffer; uint32_t mFieldModelConstantsBuffer; @@ -115,8 +123,6 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t VAO_field, VBO_field; - bool mSPIRVShaders = false; - GLfb mMixBuffer; GLfb mOffscreenBufferMSAA, mOffscreenBuffer; }; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 506cef9db7248..92b3341917a2e 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -15,6 +15,7 @@ #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #include VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE +#include "HandMadeMath.h" #include "GPUCommonDef.h" #include "GPUDisplayBackendVulkan.h" @@ -27,13 +28,8 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE using namespace o2::gpu; -#include "utils/qGetLdBinarySymbols.h" -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertex_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragment_frag_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertexPoint_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertexTexture_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragmentTexture_frag_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragmentText_frag_spv); +#define GPUCA_DISPLAY_SHADER_TYPE_VULKAN +#include "GPUDisplayLoadShaders.h" // #define CHKERR(cmd) {cmd;} #define CHKERR(cmd) \ @@ -83,6 +79,9 @@ static uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags prop return i; } } + if ((properties & vk::MemoryPropertyFlagBits::eDeviceLocal) && (properties & vk::MemoryPropertyFlagBits::eHostVisible)) { // No host-visible device memory (e.g. no ReBAR), fall back to host memory + return findMemoryType(typeFilter, properties & ~vk::MemoryPropertyFlags(vk::MemoryPropertyFlagBits::eDeviceLocal), physDev); + } throw std::runtime_error("failed to find suitable memory type!"); } @@ -126,25 +125,25 @@ vk::Extent2D GPUDisplayBackendVulkan::chooseSwapExtent(const vk::SurfaceCapabili } } -static vk::ShaderModule createShaderModule(const char* code, size_t size, vk::Device device) +static void cmdImageMemoryBarrier(vk::CommandBuffer cmdbuffer, vk::Image image, vk::PipelineStageFlags2 srcStageMask, vk::AccessFlags2 srcAccessMask, vk::PipelineStageFlags2 dstStageMask, vk::AccessFlags2 dstAccessMask, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor) +{ + vk::ImageMemoryBarrier2 barrier{srcStageMask, srcAccessMask, dstStageMask, dstAccessMask, oldLayout, newLayout, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, image, {aspect, 0, 1, 0, 1}}; + cmdbuffer.pipelineBarrier2(vk::DependencyInfo{{}, 0, nullptr, 0, nullptr, 1, &barrier}); +} + +static void cmdBeginRendering(vk::CommandBuffer cmdbuffer, uint32_t width, uint32_t height, const vk::RenderingAttachmentInfo& color, const vk::RenderingAttachmentInfo* depth = nullptr) { - vk::ShaderModuleCreateInfo createInfo{}; - createInfo.codeSize = size; - createInfo.pCode = reinterpret_cast(code); - return device.createShaderModule(createInfo, nullptr); + cmdbuffer.beginRendering(vk::RenderingInfo{{}, vk::Rect2D{{0, 0}, {width, height}}, 1, 0, 1, &color, depth}); + vk::Viewport viewport{0.f, 0.f, (float)width, (float)height, 0.f, 1.f}; + vk::Rect2D scissor{{0, 0}, {width, height}}; + cmdbuffer.setViewport(0, 1, &viewport); + cmdbuffer.setScissor(0, 1, &scissor); } -static void cmdImageMemoryBarrier(vk::CommandBuffer cmdbuffer, vk::Image image, vk::AccessFlags srcAccessMask, vk::AccessFlags dstAccessMask, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::PipelineStageFlags srcStageMask, vk::PipelineStageFlags dstStageMask) +static void cmdBeginRenderingLoad(vk::CommandBuffer cmdbuffer, vk::Image image, vk::ImageView view, uint32_t width, uint32_t height) // Render on top of the content of a color attachment written by a previous pass { - vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}; - vk::ImageMemoryBarrier barrier{}; - barrier.srcAccessMask = srcAccessMask; - barrier.dstAccessMask = dstAccessMask; - barrier.oldLayout = oldLayout; - barrier.newLayout = newLayout; - barrier.image = image; - barrier.subresourceRange = range; - cmdbuffer.pipelineBarrier(srcStageMask, dstStageMask, {}, 0, nullptr, 0, nullptr, 1, &barrier); + cmdImageMemoryBarrier(cmdbuffer, image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentRead | vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eColorAttachmentOptimal); + cmdBeginRendering(cmdbuffer, width, height, vk::RenderingAttachmentInfo{view, vk::ImageLayout::eColorAttachmentOptimal}); // loadOp eLoad, storeOp eStore } void GPUDisplayBackendVulkan::updateSwapChainDetails(const vk::PhysicalDevice& device) @@ -167,22 +166,35 @@ vk::CommandBuffer GPUDisplayBackendVulkan::getSingleTimeCommandBuffer() return commandBuffer; } +template +uint64_t GPUDisplayBackendVulkan::queueSubmit(uint64_t* timelineSignalValue, Args&&... args) +{ + std::lock_guard guard(mQueueMutex); + if (timelineSignalValue) { + *timelineSignalValue = ++mTimelineValue; + } + CHKERR(mGraphicsQueue.submit2(std::forward(args)...)); + return mTimelineValue; +} + void GPUDisplayBackendVulkan::submitSingleTimeCommandBuffer(vk::CommandBuffer commandBuffer) { commandBuffer.end(); - vk::SubmitInfo submitInfo{}; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffer; - static std::mutex fenceMutex; - { - std::lock_guard guard(fenceMutex); - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mSingleCommitFence)); - CHKERR(mDevice.waitForFences(1, &mSingleCommitFence, true, UINT64_MAX)); - CHKERR(mDevice.resetFences(1, &mSingleCommitFence)); - } + vk::CommandBufferSubmitInfo commandBufferInfo{commandBuffer}; + vk::SemaphoreSubmitInfo signalInfo{mTimelineSemaphore, 0, vk::PipelineStageFlagBits2::eAllCommands}; + vk::SubmitInfo2 submitInfo{{}, 0, nullptr, 1, &commandBufferInfo, 1, &signalInfo}; + waitTimeline(queueSubmit(&signalInfo.value, 1, &submitInfo, VkFence(VK_NULL_HANDLE))); mDevice.freeCommandBuffers(mCommandPool, 1, &commandBuffer); } +void GPUDisplayBackendVulkan::waitTimeline(uint64_t value) +{ + if (value) { + vk::SemaphoreWaitInfo waitInfo{{}, 1, &mTimelineSemaphore, &value}; + CHKERR(mDevice.waitSemaphores(&waitInfo, UINT64_MAX)); + } +} + static vk::ImageView createImageViewI(vk::Device device, vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags = vk::ImageAspectFlagBits::eColor, uint32_t mipLevels = 1) { vk::ImageViewCreateInfo viewInfo{}; @@ -273,13 +285,70 @@ static inline void clearVector(T& v, S func, bool downsize = true) // ---------------------------- VULKAN DEVICE MANAGEMENT ---------------------------- -double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions) +template // Feature structs: sType, pNext, then only VkBool32 members (possibly followed by padding) +static bool hasFeatures(const T& available, const T& required) +{ + const vk::Bool32* a = reinterpret_cast(reinterpret_cast(&available) + sizeof(VkBaseOutStructure)); + const vk::Bool32* r = reinterpret_cast(reinterpret_cast(&required) + sizeof(VkBaseOutStructure)); + for (size_t i = 0; i < (sizeof(T) - sizeof(VkBaseOutStructure)) / sizeof(vk::Bool32); i++) { + if (r[i] && !a[i]) { + return false; + } + } + return true; +} + +template +static bool hasFeatures(const vk::StructureChain& available, const vk::StructureChain& required) +{ + return (hasFeatures(available.template get(), required.template get()) && ...); +} + +template +static void clearFeatures(vk::StructureChain& chain) // Zero all feature flags including padding, which the member-wise comparison in hasFeatures reads +{ + ((memset(reinterpret_cast(&chain.template get()) + sizeof(VkBaseOutStructure), 0, sizeof(T) - sizeof(VkBaseOutStructure))), ...); +} + +template +static vk::StructureChain getFeatures(vk::PhysicalDevice device, const vk::StructureChain&) +{ + return device.getFeatures2(); +} + +double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const FeatureChain& reqDeviceFeatures, bool silent) { double score = -1.; vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); - vk::PhysicalDeviceFeatures deviceFeatures = device.getFeatures(); vk::PhysicalDeviceMemoryProperties memoryProperties = device.getMemoryProperties(); - if (!deviceFeatures.geometryShader || !deviceFeatures.wideLines || !deviceFeatures.largePoints) { + if (deviceProperties.apiVersion < VK_API_VERSION_1_4) { + if (!silent) { + GPUInfo("%s ignored, supports only Vulkan %u.%u", &deviceProperties.deviceName[0], VK_API_VERSION_MAJOR(deviceProperties.apiVersion), VK_API_VERSION_MINOR(deviceProperties.apiVersion)); + } + return (-1); + } + + std::vector availableExtensions = device.enumerateDeviceExtensionProperties(nullptr); + uint32_t extensionsFound = 0; + for (uint32_t i = 0; i < reqDeviceExtensions.size(); i++) { + for (uint32_t j = 0; j < availableExtensions.size(); j++) { + if (strcmp(reqDeviceExtensions[i], availableExtensions[j].extensionName) == 0) { + extensionsFound++; + break; + } + } + } + if (extensionsFound < reqDeviceExtensions.size()) { + if (!silent) { + GPUInfo("%s ignored due to missing extensions", &deviceProperties.deviceName[0]); + } + return (-1); + } + + if (!hasFeatures(getFeatures(device, reqDeviceFeatures), reqDeviceFeatures)) { // Query only after the version and extension checks, the chain contains 1.4 and extension structs + if (!silent) { + GPUInfo("%s ignored due to missing features", &deviceProperties.deviceName[0]); + } return (-1); } @@ -287,39 +356,28 @@ double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std bool found = false; for (uint32_t i = 0; i < queueFamilies.size(); i++) { if (!(queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics)) { - return (-1); + continue; } vk::Bool32 presentSupport = device.getSurfaceSupportKHR(i, mSurface); if (!presentSupport) { - return (-1); + continue; } mGraphicsFamily = i; found = true; break; } if (!found) { - GPUInfo("%s ignored due to missing queue properties", &deviceProperties.deviceName[0]); - return (-1); - } - - std::vector availableExtensions = device.enumerateDeviceExtensionProperties(nullptr); - uint32_t extensionsFound = 0; - for (uint32_t i = 0; i < reqDeviceExtensions.size(); i++) { - for (uint32_t j = 0; j < availableExtensions.size(); j++) { - if (strcmp(reqDeviceExtensions[i], availableExtensions[j].extensionName) == 0) { - extensionsFound++; - break; - } + if (!silent) { + GPUInfo("%s ignored due to missing queue properties", &deviceProperties.deviceName[0]); } - } - if (extensionsFound < reqDeviceExtensions.size()) { - GPUInfo("%s ignored due to missing extensions", &deviceProperties.deviceName[0]); return (-1); } updateSwapChainDetails(device); if (mSwapChainDetails.formats.empty() || mSwapChainDetails.presentModes.empty()) { - GPUInfo("%s ignored due to incompatible swap chain", &deviceProperties.deviceName[0]); + if (!silent) { + GPUInfo("%s ignored due to incompatible swap chain", &deviceProperties.deviceName[0]); + } return (-1); } @@ -347,7 +405,7 @@ void GPUDisplayBackendVulkan::createDevice() appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "GPU CI Standalone Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; + appInfo.apiVersion = VK_API_VERSION_1_4; vk::InstanceCreateInfo instanceCreateInfo; instanceCreateInfo.pApplicationInfo = &appInfo; @@ -355,6 +413,19 @@ void GPUDisplayBackendVulkan::createDevice() const char** frontendExtensions; uint32_t frontendExtensionCount = mDisplay->frontend()->getReqVulkanExtensions(frontendExtensions); std::vector reqInstanceExtensions(frontendExtensions, frontendExtensions + frontendExtensionCount); + reqInstanceExtensions.insert(reqInstanceExtensions.end(), {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME}); // The latter two are required by VK_KHR_swapchain_maintenance1 + std::vector availableInstanceExtensions = vk::enumerateInstanceExtensionProperties(nullptr); + if (mDisplay->GetProcessingSettings().debugLevel >= 3) { + std::cout << "available instance extensions: " << availableInstanceExtensions.size() << "\n"; + for (const auto& extension : availableInstanceExtensions) { + std::cout << '\t' << extension.extensionName << '\n'; + } + } + for (const char* ext : reqInstanceExtensions) { + if (std::none_of(availableInstanceExtensions.begin(), availableInstanceExtensions.end(), [ext](const auto& e) { return strcmp(ext, e.extensionName) == 0; })) { + throw std::runtime_error(std::string("Required Vulkan instance extension not available: ") + ext); + } + } const std::vector reqValidationLayers = {"VK_LAYER_KHRONOS_validation"}; auto debugCallback = [](vk::DebugUtilsMessageSeverityFlagBitsEXT messageSeverity, vk::DebugUtilsMessageTypeFlagsEXT messageType, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) -> VkBool32 { @@ -390,7 +461,6 @@ void GPUDisplayBackendVulkan::createDevice() if (checkVulkanLayersSupported(reqValidationLayers)) { throw std::runtime_error("Requested validation layer support not available"); } - reqInstanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); instanceCreateInfo.enabledLayerCount = static_cast(reqValidationLayers.size()); instanceCreateInfo.ppEnabledLayerNames = reqValidationLayers.data(); instanceCreateInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; @@ -413,20 +483,23 @@ void GPUDisplayBackendVulkan::createDevice() GPUInfo("Enabling Vulkan Validation Layers"); mDebugMessenger = mInstance.createDebugUtilsMessengerEXT(debugCreateInfo, nullptr); } - std::vector extensions = vk::enumerateInstanceExtensionProperties(nullptr); - if (mDisplay->GetProcessingSettings().debugLevel >= 3) { - std::cout << "available instance extensions: " << extensions.size() << "\n"; - for (const auto& extension : extensions) { - std::cout << '\t' << extension.extensionName << '\n'; - } - } if (mDisplay->frontend()->getVulkanSurface(&mInstance, &mSurface)) { throw std::runtime_error("Frontend does not provide Vulkan surface"); } - const std::vector reqDeviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + const std::vector reqDeviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME}; + FeatureChain reqDeviceFeatures; // Exactly these features are required and enabled + clearFeatures(reqDeviceFeatures); + auto& reqFeatures10 = reqDeviceFeatures.get().features; + reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = reqFeatures10.multiDrawIndirect = VK_TRUE; + reqDeviceFeatures.get().timelineSemaphore = VK_TRUE; + reqDeviceFeatures.get().pushDescriptor = reqDeviceFeatures.get().maintenance5 = VK_TRUE; + reqDeviceFeatures.get().dynamicRendering = reqDeviceFeatures.get().synchronization2 = VK_TRUE; + reqDeviceFeatures.get().shaderDemoteToHelperInvocation = VK_TRUE; // discard in SPIR-V 1.6 + reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; + reqDeviceFeatures.get().presentId = VK_TRUE; + reqDeviceFeatures.get().presentWait = VK_TRUE; mPhysicalDevice = VkPhysicalDevice(VK_NULL_HANDLE); std::vector devices = mInstance.enumeratePhysicalDevices(); @@ -435,7 +508,7 @@ void GPUDisplayBackendVulkan::createDevice() } double bestScore = -1.; for (uint32_t i = 0; i < devices.size(); i++) { - double score = checkDevice(devices[i], reqDeviceExtensions); + double score = checkDevice(devices[i], reqDeviceExtensions, reqDeviceFeatures); if (mDisplay->GetProcessingSettings().debugLevel >= 2) { vk::PhysicalDeviceProperties deviceProperties = devices[i].getProperties(); GPUInfo("Available Vulkan device %d: %s - Score %f", i, &deviceProperties.deviceName[0], score); @@ -454,21 +527,24 @@ void GPUDisplayBackendVulkan::createDevice() if (!mPhysicalDevice) { throw std::runtime_error("All available Vulkan devices unsuited"); } + if (checkDevice(mPhysicalDevice, reqDeviceExtensions, reqDeviceFeatures, true) <= 0) { + throw std::runtime_error("Selected vulkan device is incompatible"); + } + mSmoothLinesSupported = getFeatures(mPhysicalDevice, reqDeviceFeatures).get().smoothLines; + reqDeviceFeatures.get().smoothLines = mSmoothLinesSupported; updateSwapChainDetails(mPhysicalDevice); vk::PhysicalDeviceProperties deviceProperties = mPhysicalDevice.getProperties(); - vk::PhysicalDeviceFeatures deviceFeatures = mPhysicalDevice.getFeatures(); vk::FormatProperties depth32FormatProperties = mPhysicalDevice.getFormatProperties(vk::Format::eD32Sfloat); vk::FormatProperties depth64FormatProperties = mPhysicalDevice.getFormatProperties(vk::Format::eD32SfloatS8Uint); - vk::FormatProperties formatProperties = mPhysicalDevice.getFormatProperties(mSurfaceFormat.format); GPUInfo("Using physical Vulkan device %s", &deviceProperties.deviceName[0]); mMaxMSAAsupported = getMaxUsableSampleCount(deviceProperties); + mStrictLines = deviceProperties.limits.strictLines; mZSupported = (bool)(depth32FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); mStencilSupported = (bool)(depth64FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); - mCubicFilterSupported = (bool)(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterCubicEXT); bool mailboxSupported = std::find(mSwapChainDetails.presentModes.begin(), mSwapChainDetails.presentModes.end(), vk::PresentModeKHR::eMailbox) != mSwapChainDetails.presentModes.end(); if (mDisplay->GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Cubic Filtering %d, Mailbox present mode %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mCubicFilterSupported, (int32_t)mailboxSupported); + GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Mailbox present mode %d, smooth lines %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported, (int32_t)mSmoothLinesSupported); } vk::DeviceQueueCreateInfo queueCreateInfo{}; @@ -479,7 +555,8 @@ void GPUDisplayBackendVulkan::createDevice() vk::DeviceCreateInfo deviceCreateInfo{}; deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; deviceCreateInfo.queueCreateInfoCount = 1; - deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + deviceCreateInfo.pNext = &reqDeviceFeatures.get(); + deviceCreateInfo.pEnabledFeatures = nullptr; deviceCreateInfo.enabledExtensionCount = static_cast(reqDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = reqDeviceExtensions.data(); mDevice = mPhysicalDevice.createDevice(deviceCreateInfo, nullptr); @@ -490,11 +567,13 @@ void GPUDisplayBackendVulkan::createDevice() poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; poolInfo.queueFamilyIndex = mGraphicsFamily; mCommandPool = mDevice.createCommandPool(poolInfo, nullptr); + mPipelineCache = mDevice.createPipelineCache(vk::PipelineCacheCreateInfo{}, nullptr); } void GPUDisplayBackendVulkan::clearDevice() { mDevice.destroyCommandPool(mCommandPool, nullptr); + mDevice.destroyPipelineCache(mPipelineCache, nullptr); mDevice.destroy(nullptr); mInstance.destroySurfaceKHR(mSurface, nullptr); if (mEnableValidationLayers) { @@ -509,13 +588,14 @@ void GPUDisplayBackendVulkan::createCommandBuffers() vk::CommandBufferAllocateInfo allocInfo{}; allocInfo.commandPool = mCommandPool; allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = mFramesInFlight; - mCommandBufferUpToDate.resize(mFramesInFlight, false); + allocInfo.commandBufferCount = mImageCount; + mCommandBufferUpToDate.resize(mImageCount, false); mCommandBuffers = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersText = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersTexture = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersDownsample = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersMix = mDevice.allocateCommandBuffers(allocInfo); + mCommandBuffersPresent = mDevice.allocateCommandBuffers(allocInfo); } void GPUDisplayBackendVulkan::clearCommandBuffers() @@ -525,42 +605,35 @@ void GPUDisplayBackendVulkan::clearCommandBuffers() mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersTexture.size(), mCommandBuffersTexture.data()); mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersDownsample.size(), mCommandBuffersDownsample.data()); mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersMix.size(), mCommandBuffersMix.data()); + mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersPresent.size(), mCommandBuffersPresent.data()); } // ---------------------------- VULKAN SEMAPHORES AND FENCES ---------------------------- -void GPUDisplayBackendVulkan::createSemaphoresAndFences() +void GPUDisplayBackendVulkan::createSemaphores() { vk::SemaphoreCreateInfo semaphoreInfo{}; - vk::FenceCreateInfo fenceInfo{}; - fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; mImageAvailableSemaphore.resize(mFramesInFlight); - mRenderFinishedSemaphore.resize(mFramesInFlight); - mTextFinishedSemaphore.resize(mFramesInFlight); - mMixFinishedSemaphore.resize(mFramesInFlight); - mDownsampleFinishedSemaphore.resize(mFramesInFlight); - mInFlightFence.resize(mFramesInFlight); + mPresentSemaphore.resize(mImageCount); for (uint32_t i = 0; i < mFramesInFlight; i++) { mImageAvailableSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mRenderFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mTextFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mMixFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mDownsampleFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mInFlightFence[i] = mDevice.createFence(fenceInfo, nullptr); } - fenceInfo.flags = {}; - mSingleCommitFence = mDevice.createFence(fenceInfo, nullptr); + for (uint32_t i = 0; i < mImageCount; i++) { + mPresentSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); + } + vk::SemaphoreTypeCreateInfo timelineTypeInfo{vk::SemaphoreType::eTimeline, 0}; + semaphoreInfo.pNext = &timelineTypeInfo; + mTimelineSemaphore = mDevice.createSemaphore(semaphoreInfo, nullptr); + mTimelineValue = 0; + mFrameTimelineValue.assign(mFramesInFlight, 0); + mImageTimelineValue.assign(mImageCount, 0); } -void GPUDisplayBackendVulkan::clearSemaphoresAndFences() +void GPUDisplayBackendVulkan::clearSemaphores() { clearVector(mImageAvailableSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mRenderFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mTextFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mMixFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mDownsampleFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mInFlightFence, [&](auto& x) { mDevice.destroyFence(x, nullptr); }); - mDevice.destroyFence(mSingleCommitFence, nullptr); + clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); + mDevice.destroySemaphore(mTimelineSemaphore, nullptr); } // ---------------------------- VULKAN UNIFORM LAYOUTS AND BUFFERS ---------------------------- @@ -568,107 +641,50 @@ void GPUDisplayBackendVulkan::clearSemaphoresAndFences() void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() { for (int32_t j = 0; j < 3; j++) { - mUniformBuffersMat[j].resize(mFramesInFlight); - mUniformBuffersCol[j].resize(mFramesInFlight); - for (uint32_t i = 0; i < mFramesInFlight; i++) { + mUniformBuffersMat[j].resize(mImageCount); + for (uint32_t i = 0; i < mImageCount; i++) { mUniformBuffersMat[j][i] = createBuffer(sizeof(hmm_mat4), nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); - mUniformBuffersCol[j][i] = createBuffer(sizeof(float) * 4, nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); } } - std::array poolSizes{}; - poolSizes[0].type = vk::DescriptorType::eUniformBuffer; - poolSizes[0].descriptorCount = (uint32_t)mFramesInFlight * (2 * 3); - poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; - poolSizes[1].descriptorCount = (uint32_t)mFramesInFlight * 2; - vk::DescriptorPoolCreateInfo poolInfo{}; - poolInfo.poolSizeCount = poolSizes.size(); - poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = (uint32_t)mFramesInFlight * 3; - mDescriptorPool = mDevice.createDescriptorPool(poolInfo, nullptr); - vk::DescriptorSetLayoutBinding uboLayoutBindingMat{}; uboLayoutBindingMat.binding = 0; uboLayoutBindingMat.descriptorType = vk::DescriptorType::eUniformBuffer; uboLayoutBindingMat.descriptorCount = 1; uboLayoutBindingMat.stageFlags = vk::ShaderStageFlagBits::eVertex; - vk::DescriptorSetLayoutBinding uboLayoutBindingCol = uboLayoutBindingMat; - uboLayoutBindingCol.binding = 1; - uboLayoutBindingCol.stageFlags = vk::ShaderStageFlagBits::eFragment; vk::DescriptorSetLayoutBinding samplerLayoutBinding{}; samplerLayoutBinding.binding = 2; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = vk::DescriptorType::eCombinedImageSampler; samplerLayoutBinding.stageFlags = vk::ShaderStageFlagBits::eFragment; - vk::DescriptorSetLayoutBinding bindings[3] = {uboLayoutBindingMat, uboLayoutBindingCol, samplerLayoutBinding}; + vk::DescriptorSetLayoutBinding bindings[2] = {uboLayoutBindingMat, samplerLayoutBinding}; vk::DescriptorSetLayoutCreateInfo layoutInfo{}; - layoutInfo.bindingCount = 2; + layoutInfo.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptor; + layoutInfo.bindingCount = 1; layoutInfo.pBindings = bindings; mUniformDescriptor = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); - layoutInfo.bindingCount = 3; + layoutInfo.bindingCount = 2; mUniformDescriptorTexture = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); - vk::DescriptorSetAllocateInfo allocInfo{}; - allocInfo.descriptorPool = mDescriptorPool; - allocInfo.descriptorSetCount = (uint32_t)mFramesInFlight; - for (int32_t j = 0; j < 3; j++) { // 0 = Render, 1 = Text, 2 = Texture - std::vector layouts(mFramesInFlight, j ? mUniformDescriptorTexture : mUniformDescriptor); - allocInfo.pSetLayouts = layouts.data(); - mDescriptorSets[j] = mDevice.allocateDescriptorSets(allocInfo); - - for (int32_t k = 0; k < 2; k++) { - auto& mUniformBuffers = k ? mUniformBuffersCol[j] : mUniformBuffersMat[j]; - for (uint32_t i = 0; i < mFramesInFlight; i++) { - vk::DescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = mUniformBuffers[i].buffer; - bufferInfo.offset = 0; - bufferInfo.range = mUniformBuffers[i].size; - - vk::WriteDescriptorSet descriptorWrite{}; - descriptorWrite.dstSet = mDescriptorSets[j][i]; - descriptorWrite.dstBinding = k; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = vk::DescriptorType::eUniformBuffer; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pBufferInfo = &bufferInfo; - descriptorWrite.pImageInfo = nullptr; - descriptorWrite.pTexelBufferView = nullptr; - mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); - } - } - } - - if (mFontImage.sizex && mFontImage.sizey) { - updateFontTextureDescriptor(); - } + mUpdateUBOdrawMatrix = mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; } void GPUDisplayBackendVulkan::clearUniformLayoutsAndBuffers() { mDevice.destroyDescriptorSetLayout(mUniformDescriptor, nullptr); mDevice.destroyDescriptorSetLayout(mUniformDescriptorTexture, nullptr); - mDevice.destroyDescriptorPool(mDescriptorPool, nullptr); for (int32_t j = 0; j < 3; j++) { clearVector(mUniformBuffersMat[j], [&](auto& x) { clearBuffer(x); }); - clearVector(mUniformBuffersCol[j], [&](auto& x) { clearBuffer(x); }); } } -void GPUDisplayBackendVulkan::setMixDescriptor(int32_t descriptorIndex, int32_t imageIndex) +void GPUDisplayBackendVulkan::cmdPushDescriptors(vk::CommandBuffer commandBuffer, vk::PipelineLayout layout, const VulkanBuffer& ubo, vk::ImageView textureView) { - vk::DescriptorImageInfo imageInfo{}; - imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - imageInfo.sampler = mTextureSampler; - imageInfo.imageView = *mRenderTargetView[imageIndex + mImageCount]; - vk::WriteDescriptorSet descriptorWrite{}; - descriptorWrite.dstSet = mDescriptorSets[2][descriptorIndex]; - descriptorWrite.dstBinding = 2; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = vk::DescriptorType::eCombinedImageSampler; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pImageInfo = &imageInfo; - mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); + vk::DescriptorBufferInfo bufferInfo{ubo.buffer, 0, ubo.size}; + vk::DescriptorImageInfo imageInfo{mTextureSampler, textureView, vk::ImageLayout::eShaderReadOnlyOptimal}; + const vk::WriteDescriptorSet writes[2] = {{{}, 0, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, &bufferInfo}, {{}, 2, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo}}; + commandBuffer.pushDescriptorSet(vk::PipelineBindPoint::eGraphics, layout, 0, textureView ? 2 : 1, writes); } // ---------------------------- VULKAN TEXTURE SAMPLER ---------------------------- @@ -699,26 +715,20 @@ void GPUDisplayBackendVulkan::clearTextureSampler() // ---------------------------- VULKAN SWAPCHAIN MANAGEMENT ---------------------------- -void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing) +void GPUDisplayBackendVulkan::createSwapChain(vk::SwapchainKHR oldSwapChain) { - mDownsampleFactor = getDownsampleFactor(forScreenshot); - mDownsampleFSAA = mDownsampleFactor != 1; - mSwapchainImageReadable = forScreenshot; - updateSwapChainDetails(mPhysicalDevice); mSurfaceFormat = chooseSwapSurfaceFormat(mSwapChainDetails.formats); mPresentMode = chooseSwapPresentMode(mSwapChainDetails.presentModes, mDisplay->cfgR().drawQualityVSync ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate); vk::Extent2D extent = chooseSwapExtent(mSwapChainDetails.capabilities); - uint32_t imageCount = mSwapChainDetails.capabilities.minImageCount + 1; + uint32_t imageCount = std::max(mSwapChainDetails.capabilities.minImageCount + 1, mDisplay->cfg().vulkan.nFramesInFlight + 1); if (mSwapChainDetails.capabilities.maxImageCount > 0 && imageCount > mSwapChainDetails.capabilities.maxImageCount) { imageCount = mSwapChainDetails.capabilities.maxImageCount; } mScreenWidth = extent.width; mScreenHeight = extent.height; - mRenderWidth = mScreenWidth * mDownsampleFactor; - mRenderHeight = mScreenHeight * mDownsampleFactor; vk::SwapchainCreateInfoKHR swapCreateInfo{}; swapCreateInfo.surface = mSurface; @@ -727,7 +737,7 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing swapCreateInfo.imageColorSpace = mSurfaceFormat.colorSpace; swapCreateInfo.imageExtent = extent; swapCreateInfo.imageArrayLayers = 1; - swapCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; + swapCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst; // Transfer for screenshots / FSAA downsampling, so that they need no swapchain recreation swapCreateInfo.imageSharingMode = vk::SharingMode::eExclusive; swapCreateInfo.queueFamilyIndexCount = 0; // Optional swapCreateInfo.pQueueFamilyIndices = nullptr; // Optional @@ -735,61 +745,76 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing swapCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; swapCreateInfo.presentMode = mPresentMode; swapCreateInfo.clipped = true; - swapCreateInfo.oldSwapchain = VkSwapchainKHR(VK_NULL_HANDLE); - if (mSwapchainImageReadable) { - swapCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferSrc; - } - if (mDownsampleFSAA) { - swapCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferDst; - } + swapCreateInfo.oldSwapchain = oldSwapChain; mSwapChain = mDevice.createSwapchainKHR(swapCreateInfo, nullptr); + if (oldSwapChain) { + mDevice.destroySwapchainKHR(oldSwapChain, nullptr); + } mSwapChainImages = mDevice.getSwapchainImagesKHR(mSwapChain); - uint32_t oldFramesInFlight = mFramesInFlight; + uint32_t oldFramesInFlight = mFramesInFlight, oldImageCount = mImageCount; + mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; mImageCount = mSwapChainImages.size(); - mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight == 0 ? mImageCount : mDisplay->cfg().vulkan.nFramesInFlight; - mCommandBufferPerImage = mFramesInFlight == mImageCount; + if (mImageCount > sizeof(mUpdateUBOdrawMatrix) * 8) { + throw std::runtime_error("Too many swapchain images"); + } - if (mFramesInFlight > oldFramesInFlight || !mCommandInfrastructureCreated) { + if (mFramesInFlight != oldFramesInFlight || mImageCount != oldImageCount || !mCommandInfrastructureCreated) { if (mCommandInfrastructureCreated) { - clearUniformLayoutsAndBuffers(); + clearUniformLayoutsAndBuffers(); // TODO: Check what must actually be cleaned up if frames in flight changes clearCommandBuffers(); - clearSemaphoresAndFences(); + clearSemaphores(); } createUniformLayoutsAndBuffers(); createCommandBuffers(); - createSemaphoresAndFences(); + createSemaphores(); mCommandInfrastructureCreated = true; } mSwapChainImageViews.resize(mImageCount); for (uint32_t i = 0; i < mImageCount; i++) { mSwapChainImageViews[i] = createImageViewI(mDevice, mSwapChainImages[i], mSurfaceFormat.format); + mCommandBuffersPresent[i].reset({}); + mCommandBuffersPresent[i].begin(vk::CommandBufferBeginInfo{}); + cmdImageMemoryBarrier(mCommandBuffersPresent[i], mSwapChainImages[i], vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eNone, vk::AccessFlagBits2::eNone, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::ePresentSrcKHR); + mCommandBuffersPresent[i].end(); } } -void GPUDisplayBackendVulkan::clearSwapChain() +void GPUDisplayBackendVulkan::clearSwapChain(bool keepSwapChain) { clearVector(mSwapChainImageViews, [&](auto& x) { mDevice.destroyImageView(x, nullptr); }); - mDevice.destroySwapchainKHR(mSwapChain, nullptr); + if (!keepSwapChain) { + mDevice.destroySwapchainKHR(mSwapChain, nullptr); + } } void GPUDisplayBackendVulkan::recreateRendering(bool forScreenshot, bool forMixing) { + needRecordCommandBuffers(); + const bool needUpdateSwapChain = mMustUpdateSwapChain; + const bool needUpdateOffscreenBuffers = needUpdateSwapChain || mDownsampleFactor != getDownsampleFactor(forScreenshot) || mMSAASampleCount != getMSAASamplesFlag(std::min(mMaxMSAAsupported, mDisplay->cfgR().drawQualityMSAA)) || mZActive != (mZSupported && mDisplay->cfgL().depthBuffer) || mMixingSupported != forMixing; + const bool smoothLinesChanged = mSmoothLines != useNativeSmoothLines(); + if (!needUpdateOffscreenBuffers && !smoothLinesChanged) { + return; + } mDevice.waitIdle(); - bool needUpdateSwapChain = mMustUpdateSwapChain || mDownsampleFactor != getDownsampleFactor(forScreenshot) || mSwapchainImageReadable != forScreenshot; - bool needUpdateOffscreenBuffers = needUpdateSwapChain || mMSAASampleCount != getMSAASamplesFlag(std::min(mMaxMSAAsupported, mDisplay->cfgR().drawQualityMSAA)) || mZActive != (mZSupported && mDisplay->cfgL().depthBuffer) || mMixingSupported != forMixing; - clearPipeline(); + const vk::Format oldFormat = mSurfaceFormat.format; + const vk::SampleCountFlagBits oldMSAASampleCount = mMSAASampleCount; + const bool oldZActive = mZActive; if (needUpdateOffscreenBuffers) { clearOffscreenBuffers(); if (needUpdateSwapChain) { - clearSwapChain(); - createSwapChain(forScreenshot, forMixing); + clearSwapChain(true); + createSwapChain(mSwapChain); } createOffscreenBuffers(forScreenshot, forMixing); + mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed + } + if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive || smoothLinesChanged) { // Viewport / scissor are dynamic + clearPipeline(); + createPipeline(); } - createPipeline(); - needRecordCommandBuffers(); } // ---------------------------- VULKAN OFFSCREEN BUFFERS ---------------------------- @@ -799,80 +824,14 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo mMSAASampleCount = getMSAASamplesFlag(std::min(mMaxMSAAsupported, mDisplay->cfgR().drawQualityMSAA)); mZActive = mZSupported && mDisplay->cfgL().depthBuffer; mMixingSupported = forMixing; - - vk::AttachmentDescription colorAttachment{}; - colorAttachment.format = mSurfaceFormat.format; - colorAttachment.samples = mMSAASampleCount; - colorAttachment.loadOp = vk::AttachmentLoadOp::eClear; - colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; - colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; - colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - colorAttachment.initialLayout = vk::ImageLayout::eUndefined; - colorAttachment.finalLayout = (mMSAASampleCount != vk::SampleCountFlagBits::e1 || mDownsampleFSAA) ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - vk::AttachmentDescription depthAttachment{}; - depthAttachment.format = vk::Format::eD32Sfloat; - depthAttachment.samples = mMSAASampleCount; - depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; - depthAttachment.storeOp = vk::AttachmentStoreOp::eDontCare; - depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; - depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - depthAttachment.initialLayout = vk::ImageLayout::eUndefined; - depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - vk::AttachmentDescription colorAttachmentResolve{}; - colorAttachmentResolve.format = mSurfaceFormat.format; - colorAttachmentResolve.samples = vk::SampleCountFlagBits::e1; - colorAttachmentResolve.loadOp = vk::AttachmentLoadOp::eDontCare; - colorAttachmentResolve.storeOp = vk::AttachmentStoreOp::eStore; - colorAttachmentResolve.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; - colorAttachmentResolve.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - colorAttachmentResolve.initialLayout = vk::ImageLayout::eUndefined; - colorAttachmentResolve.finalLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - int32_t nAttachments = 0; - vk::AttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = nAttachments++; - colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal; - vk::AttachmentReference depthAttachmentRef{}; - // depthAttachmentRef.attachment // below - depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - vk::AttachmentReference colorAttachmentResolveRef{}; - // colorAttachmentResolveRef.attachment // below - colorAttachmentResolveRef.layout = vk::ImageLayout::eColorAttachmentOptimal; - vk::SubpassDescription subpass{}; - subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - vk::SubpassDependency dependency{}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests; - dependency.srcAccessMask = {}; - dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests; - dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite; - - std::vector attachments = {colorAttachment}; - if (mZActive) { - attachments.emplace_back(depthAttachment); - depthAttachmentRef.attachment = nAttachments++; - subpass.pDepthStencilAttachment = &depthAttachmentRef; - } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { - attachments.emplace_back(colorAttachmentResolve); - colorAttachmentResolveRef.attachment = nAttachments++; - subpass.pResolveAttachments = &colorAttachmentResolveRef; - } - - vk::RenderPassCreateInfo renderPassInfo{}; - renderPassInfo.attachmentCount = attachments.size(); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; - mRenderPass = mDevice.createRenderPass(renderPassInfo, nullptr); + mDownsampleFactor = getDownsampleFactor(forScreenshot); + mDownsampleFSAA = mDownsampleFactor != 1; + mRenderWidth = mScreenWidth * mDownsampleFactor; + mRenderHeight = mScreenHeight * mDownsampleFactor; const uint32_t imageCountWithMixImages = mImageCount * (mMixingSupported ? 2 : 1); mRenderTargetView.resize(imageCountWithMixImages); - mFramebuffers.resize(imageCountWithMixImages); + mRenderTargetImage.resize(imageCountWithMixImages); if (mDownsampleFSAA) { mDownsampleImages.resize(imageCountWithMixImages); } @@ -882,45 +841,8 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo if (mZActive) { mZImages.resize(imageCountWithMixImages); } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1 || mZActive || mDownsampleFSAA) { - mFramebuffersText.resize(mImageCount); - } - if (mMixingSupported) { - if (mMSAASampleCount != vk::SampleCountFlagBits::e1 || mZActive || mDownsampleFSAA) { - mFramebuffersTexture.resize(mImageCount); - } - if (!mDownsampleFSAA) { - mMixImages.resize(mImageCount); - } - } - - // Text overlay goes as extra rendering path - renderPassInfo.attachmentCount = 1; // Remove depth and MSAA attachments - renderPassInfo.pAttachments = &colorAttachment; - subpass.pDepthStencilAttachment = nullptr; - subpass.pResolveAttachments = nullptr; - if (mFramebuffersText.size()) { - dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; // Remove early fragment test - dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; // Remove depth/stencil dependencies - } - colorAttachment.loadOp = vk::AttachmentLoadOp::eLoad; // Don't clear the frame buffer - colorAttachment.initialLayout = vk::ImageLayout::ePresentSrcKHR; // Initial layout is not undefined after 1st pass - colorAttachment.samples = vk::SampleCountFlagBits::e1; // No MSAA for Text - colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR; // Might have been overwritten above for 1st pass in case of MSAA - mRenderPassText = mDevice.createRenderPass(renderPassInfo, nullptr); - - if (mMixingSupported) { - if (mDownsampleFSAA) { - colorAttachment.initialLayout = vk::ImageLayout::eColorAttachmentOptimal; - colorAttachment.finalLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - } - if (mFramebuffersTexture.size()) { - dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; // Remove early fragment test - dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; // Remove depth/stencil dependencies - } - mRenderPassTexture = mDevice.createRenderPass(renderPassInfo, nullptr); + if (mMixingSupported && !mDownsampleFSAA) { + mMixImages.resize(mImageCount); } for (uint32_t i = 0; i < imageCountWithMixImages; i++) { @@ -930,56 +852,23 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo createImageI(mDevice, mPhysicalDevice, mMixImages[i - mImageCount].image, mMixImages[i - mImageCount].memory, mRenderWidth, mRenderHeight, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); mMixImages[i - mImageCount].view = createImageViewI(mDevice, mMixImages[i - mImageCount].image, mSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1); } - std::vector att; if (mDownsampleFSAA) { vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | (i >= mImageCount ? vk::ImageUsageFlagBits::eSampled : vk::ImageUsageFlagBits::eTransferSrc); createImageI(mDevice, mPhysicalDevice, mDownsampleImages[i].image, mDownsampleImages[i].memory, mRenderWidth, mRenderHeight, mSurfaceFormat.format, usage, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); mDownsampleImages[i].view = createImageViewI(mDevice, mDownsampleImages[i].image, mSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1); mRenderTargetView[i] = &mDownsampleImages[i].view; + mRenderTargetImage[i] = mDownsampleImages[i].image; } else { mRenderTargetView[i] = i < mImageCount ? &mSwapChainImageViews[i] : &mMixImages[i - mImageCount].view; + mRenderTargetImage[i] = i < mImageCount ? mSwapChainImages[i] : mMixImages[i - mImageCount].image; } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { // First attachment is the render target, either the MSAA buffer or the framebuffer + if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { createImageI(mDevice, mPhysicalDevice, mMSAAImages[i].image, mMSAAImages[i].memory, mRenderWidth, mRenderHeight, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal, mMSAASampleCount); mMSAAImages[i].view = createImageViewI(mDevice, mMSAAImages[i].image, mSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1); - att.emplace_back(mMSAAImages[i].view); - } else { - att.emplace_back(*mRenderTargetView[i]); } if (mZActive) { createImageI(mDevice, mPhysicalDevice, mZImages[i].image, mZImages[i].memory, mRenderWidth, mRenderHeight, vk::Format::eD32Sfloat, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal, mMSAASampleCount); mZImages[i].view = createImageViewI(mDevice, mZImages[i].image, vk::Format::eD32Sfloat, vk::ImageAspectFlagBits::eDepth, 1); - att.emplace_back(mZImages[i].view); - } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { // If we use MSAA, we have to resolve to the framebuffer as the last target - att.emplace_back(*mRenderTargetView[i]); - } - - vk::FramebufferCreateInfo framebufferInfo{}; - framebufferInfo.renderPass = mRenderPass; - framebufferInfo.attachmentCount = att.size(); - framebufferInfo.pAttachments = att.data(); - framebufferInfo.width = mRenderWidth; - framebufferInfo.height = mRenderHeight; - framebufferInfo.layers = 1; - mFramebuffers[i] = mDevice.createFramebuffer(framebufferInfo, nullptr); - - if (i < mImageCount && mFramebuffersText.size()) { - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = &mSwapChainImageViews[i]; - framebufferInfo.renderPass = mRenderPassText; - framebufferInfo.width = mScreenWidth; - framebufferInfo.height = mScreenHeight; - mFramebuffersText[i] = mDevice.createFramebuffer(framebufferInfo, nullptr); - } - - if (i >= mImageCount && mFramebuffersTexture.size()) { - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = mRenderTargetView[i - mImageCount]; - framebufferInfo.renderPass = mRenderPassTexture; - framebufferInfo.width = mRenderWidth; - framebufferInfo.height = mRenderHeight; - mFramebuffersTexture[i - mImageCount] = mDevice.createFramebuffer(framebufferInfo, nullptr); } } @@ -992,30 +881,19 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo {(float)mRenderWidth, 0, 1.0f, 0.0f}, {(float)mRenderWidth, (float)mRenderHeight, 1.0f, 1.0f}}; mMixingTextureVertexArray = createBuffer(sizeof(vertices), &vertices[0][0], vk::BufferUsageFlagBits::eVertexBuffer, 1); - - if (mCommandBufferPerImage) { - for (uint32_t i = 0; i < mFramesInFlight; i++) { - setMixDescriptor(i, i); - } - } } - mFontVertexBuffer.resize(mFramesInFlight); + mFontVertexBuffer.resize(mImageCount); } void GPUDisplayBackendVulkan::clearOffscreenBuffers() { - clearVector(mFramebuffers, [&](auto& x) { mDevice.destroyFramebuffer(x, nullptr); }); clearVector(mMSAAImages, [&](auto& x) { clearImage(x); }); clearVector(mDownsampleImages, [&](auto& x) { clearImage(x); }); clearVector(mZImages, [&](auto& x) { clearImage(x); }); clearVector(mMixImages, [&](auto& x) { clearImage(x); }); - clearVector(mFramebuffersText, [&](auto& x) { mDevice.destroyFramebuffer(x, nullptr); }); - clearVector(mFramebuffersTexture, [&](auto& x) { mDevice.destroyFramebuffer(x, nullptr); }); - mDevice.destroyRenderPass(mRenderPass, nullptr); - mDevice.destroyRenderPass(mRenderPassText, nullptr); + clearVector(mFontVertexBuffer, [&](auto& x) { if (x.size) { clearBuffer(x); } }); if (mMixingSupported) { - mDevice.destroyRenderPass(mRenderPassTexture, nullptr); clearBuffer(mMixingTextureVertexArray); } } @@ -1027,11 +905,11 @@ void GPUDisplayBackendVulkan::createPipeline() vk::PipelineShaderStageCreateInfo shaderStages[2] = {vk::PipelineShaderStageCreateInfo{}, vk::PipelineShaderStageCreateInfo{}}; vk::PipelineShaderStageCreateInfo& vertShaderStageInfo = shaderStages[0]; vertShaderStageInfo.stage = vk::ShaderStageFlagBits::eVertex; - // vertShaderStageInfo.module // below + // vertShaderStageInfo.pNext // below: SPIR-V vertShaderStageInfo.pName = "main"; vk::PipelineShaderStageCreateInfo& fragShaderStageInfo = shaderStages[1]; fragShaderStageInfo.stage = vk::ShaderStageFlagBits::eFragment; - // fragShaderStageInfo.module // below + // fragShaderStageInfo.pNext // below: SPIR-V fragShaderStageInfo.pName = "main"; vk::VertexInputBindingDescription bindingDescription{}; @@ -1054,23 +932,9 @@ void GPUDisplayBackendVulkan::createPipeline() // inputAssembly.topology // below inputAssembly.primitiveRestartEnable = false; - vk::Viewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - // viewport.width // below - // viewport.height // below - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - vk::Rect2D scissor{}; - scissor.offset = vk::Offset2D{0, 0}; - // scissor.extent // below - - vk::PipelineViewportStateCreateInfo viewportState{}; + vk::PipelineViewportStateCreateInfo viewportState{}; // Viewport and scissor are dynamic viewportState.viewportCount = 1; - viewportState.pViewports = &viewport; viewportState.scissorCount = 1; - viewportState.pScissors = &scissor; vk::PipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.depthClampEnable = false; @@ -1120,9 +984,9 @@ void GPUDisplayBackendVulkan::createPipeline() depthStencil.depthBoundsTestEnable = false; depthStencil.stencilTestEnable = false; - vk::DynamicState dynamicStates[] = {vk::DynamicState::eLineWidth}; + vk::DynamicState dynamicStates[] = {vk::DynamicState::eViewport, vk::DynamicState::eScissor, vk::DynamicState::eLineWidth}; vk::PipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.dynamicStateCount = 1; + // dynamicState.dynamicStateCount // below: viewport + scissor for text / texture, + line width for the main pipelines dynamicState.pDynamicStates = dynamicStates; vk::PushConstantRange pushConstantRanges[2] = {vk::PushConstantRange{}, vk::PushConstantRange{}}; @@ -1131,7 +995,7 @@ void GPUDisplayBackendVulkan::createPipeline() pushConstantRanges[0].size = sizeof(float) * 4; pushConstantRanges[1].stageFlags = vk::ShaderStageFlagBits::eVertex; pushConstantRanges[1].offset = pushConstantRanges[0].size; - pushConstantRanges[1].size = sizeof(float); + pushConstantRanges[1].size = 4 * sizeof(float); // Point size, line width, viewport vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &mUniformDescriptor; @@ -1153,57 +1017,62 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.pDynamicState = &dynamicState; // pipelineInfo.layout // below - // pipelineInfo.renderPass // below - pipelineInfo.subpass = 0; + vk::PipelineRenderingCreateInfo renderingInfo{0, 1, &mSurfaceFormat.format}; + pipelineInfo.pNext = &renderingInfo; pipelineInfo.pStages = shaderStages; pipelineInfo.basePipelineHandle = VkPipeline(VK_NULL_HANDLE); // Optional pipelineInfo.basePipelineIndex = -1; // Optional - mPipelines.resize(mMixingSupported ? 5 : 4); + mSmoothLines = useNativeSmoothLines(); + vk::PipelineRasterizationLineStateCreateInfo smoothLineState{}; + smoothLineState.lineRasterizationMode = vk::LineRasterizationMode::eRectangularSmooth; // Coverage goes to alpha, blending is enabled + + mPipelines.resize(8); // 0 points, 1 lines, 2 line strip (drawType index), 3 text, 4 texture, 5-7 smooth (shader-based) points, lines, line strip static constexpr vk::PrimitiveTopology types[3] = {vk::PrimitiveTopology::ePointList, vk::PrimitiveTopology::eLineList, vk::PrimitiveTopology::eLineStrip}; for (uint32_t i = 0; i < mPipelines.size(); i++) { if (i == 4) { // Texture rendering + rasterizer.pNext = nullptr; bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; - vertShaderStageInfo.module = mShaders["vertexTexture"]; - fragShaderStageInfo.module = mShaders["fragmentTexture"]; + vertShaderStageInfo.pNext = &mShaders.at("vertexTexture"); + fragShaderStageInfo.pNext = &mShaders.at("fragmentTexture"); + dynamicState.dynamicStateCount = 2; pipelineInfo.layout = mPipelineLayoutTexture; - pipelineInfo.renderPass = mRenderPassTexture; + renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; pipelineInfo.pDepthStencilState = nullptr; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; - viewport.width = scissor.extent.width = mRenderWidth; - viewport.height = scissor.extent.height = mRenderHeight; } else if (i == 3) { // Text rendering + rasterizer.pNext = nullptr; bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; - vertShaderStageInfo.module = mShaders["vertexTexture"]; - fragShaderStageInfo.module = mShaders["fragmentText"]; + vertShaderStageInfo.pNext = &mShaders.at("vertexTexture"); + fragShaderStageInfo.pNext = &mShaders.at("fragmentText"); + dynamicState.dynamicStateCount = 2; pipelineInfo.layout = mPipelineLayoutTexture; - pipelineInfo.renderPass = mRenderPassText; + renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; pipelineInfo.pDepthStencilState = nullptr; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; - viewport.width = scissor.extent.width = mScreenWidth; - viewport.height = scissor.extent.height = mScreenHeight; } else { // Point / line / line-strip rendering bindingDescription.stride = 3 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32Sfloat; - inputAssembly.topology = types[i]; - vertShaderStageInfo.module = mShaders[types[i] == vk::PrimitiveTopology::ePointList ? "vertexPoint" : "vertex"]; - fragShaderStageInfo.module = mShaders["fragment"]; + const uint32_t drawIdx = i >= 5 ? i - 5 : i; + inputAssembly.topology = types[drawIdx]; + rasterizer.pNext = mSmoothLines && (i == 1 || i == 2) ? &smoothLineState : nullptr; + vertShaderStageInfo.pNext = &mShaders.at(i < 5 ? (drawIdx == 0 ? "vertexPoint" : "vertex") : (drawIdx == 0 ? "vertexSmoothPoint" : "vertexSmoothLine")); + fragShaderStageInfo.pNext = &mShaders.at(i < 5 ? "fragment" : (drawIdx == 0 ? "fragmentSmoothPoint" : "fragmentSmoothLine")); pipelineInfo.layout = mPipelineLayout; - pipelineInfo.renderPass = mRenderPass; + renderingInfo.depthAttachmentFormat = mZActive ? vk::Format::eD32Sfloat : vk::Format::eUndefined; pipelineInfo.pDepthStencilState = mZActive ? &depthStencil : nullptr; + dynamicState.dynamicStateCount = 3; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = mMSAASampleCount; - viewport.width = scissor.extent.width = mRenderWidth; - viewport.height = scissor.extent.height = mRenderHeight; } - CHKERR(mDevice.createGraphicsPipelines(VkPipelineCache(VK_NULL_HANDLE), 1, &pipelineInfo, nullptr, &mPipelines[i])); // TODO: multiple at once + cache? + CHKERR(mDevice.createGraphicsPipelines(mPipelineCache, 1, &pipelineInfo, nullptr, &mPipelines[i])); } } @@ -1215,27 +1084,43 @@ void GPUDisplayBackendVulkan::startFillCommandBuffer(vk::CommandBuffer& commandB beginInfo.flags = {}; commandBuffer.begin(beginInfo); - vk::ClearValue clearValues[2]; - clearValues[0].color = mDisplay->cfgL().invertColors ? vk::ClearColorValue{std::array{1.0f, 1.0f, 1.0f, 1.0f}} : vk::ClearColorValue{std::array{0.0f, 0.0f, 0.0f, 1.0f}}; - clearValues[1].depthStencil = vk::ClearDepthStencilValue{{1.0f, 0}}; + const uint32_t idx = toMixBuffer ? imageIndex + mImageCount : imageIndex; + const bool msaa = mMSAASampleCount != vk::SampleCountFlagBits::e1; + const vk::PipelineStageFlags2 prevStages = vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer | vk::PipelineStageFlagBits2::eFragmentShader; // Previous use of the render target (rendering, blit, mix texture), and the wait stage of the acquire semaphore + cmdImageMemoryBarrier(commandBuffer, mRenderTargetImage[idx], prevStages, vk::AccessFlagBits2::eColorAttachmentWrite | vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal); + if (msaa) { + cmdImageMemoryBarrier(commandBuffer, mMSAAImages[idx].image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal); + } + if (mZActive) { + const vk::PipelineStageFlags2 depthStages = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests; + cmdImageMemoryBarrier(commandBuffer, mZImages[idx].image, depthStages, vk::AccessFlagBits2::eDepthStencilAttachmentWrite, depthStages, vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal, vk::ImageAspectFlagBits::eDepth); + } - vk::RenderPassBeginInfo renderPassInfo{}; - renderPassInfo.renderPass = mRenderPass; - renderPassInfo.framebuffer = toMixBuffer ? mFramebuffers[imageIndex + mImageCount] : mFramebuffers[imageIndex]; - renderPassInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderPassInfo.renderArea.extent = vk::Extent2D{mRenderWidth, mRenderHeight}; - renderPassInfo.clearValueCount = mZActive ? 2 : 1; - renderPassInfo.pClearValues = clearValues; - commandBuffer.beginRenderPass(&renderPassInfo, vk::SubpassContents::eInline); + vk::RenderingAttachmentInfo colorAttachment{msaa ? mMSAAImages[idx].view : *mRenderTargetView[idx], vk::ImageLayout::eColorAttachmentOptimal}; + colorAttachment.loadOp = vk::AttachmentLoadOp::eClear; + colorAttachment.storeOp = msaa ? vk::AttachmentStoreOp::eDontCare : vk::AttachmentStoreOp::eStore; + colorAttachment.clearValue.color = mDisplay->cfgL().invertColors ? vk::ClearColorValue{std::array{1.0f, 1.0f, 1.0f, 1.0f}} : vk::ClearColorValue{std::array{0.0f, 0.0f, 0.0f, 1.0f}}; + if (msaa) { + colorAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; + colorAttachment.resolveImageView = *mRenderTargetView[idx]; + colorAttachment.resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal; + } + vk::RenderingAttachmentInfo depthAttachment{mZActive ? mZImages[idx].view : vk::ImageView{}, vk::ImageLayout::eDepthStencilAttachmentOptimal}; + depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; + depthAttachment.storeOp = vk::AttachmentStoreOp::eDontCare; + depthAttachment.clearValue.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; + cmdBeginRendering(commandBuffer, mRenderWidth, mRenderHeight, colorAttachment, mZActive ? &depthAttachment : nullptr); vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mVBO.buffer, offsets); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayout, 0, 1, &mDescriptorSets[0][mCurrentBufferSet], 0, nullptr); + cmdPushDescriptors(commandBuffer, mPipelineLayout, mUniformBuffersMat[0][imageIndex]); + const float viewport[2] = {(float)mRenderWidth, (float)mRenderHeight}; + commandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, 6 * sizeof(float), sizeof(viewport), viewport); } void GPUDisplayBackendVulkan::endFillCommandBuffer(vk::CommandBuffer& commandBuffer) { - commandBuffer.endRenderPass(); + commandBuffer.endRendering(); commandBuffer.end(); } @@ -1249,32 +1134,28 @@ void GPUDisplayBackendVulkan::clearPipeline() // ---------------------------- VULKAN SHADERS ---------------------------- #define LOAD_SHADER(file, ext) \ - mShaders[#file] = createShaderModule(_binary_shaders_shaders_##file##_##ext##_spv_start, _binary_shaders_shaders_##file##_##ext##_spv_len, mDevice) + mShaders[#file] = vk::ShaderModuleCreateInfo { {}, _binary_vulkan_##file##_##ext##_spv_len, reinterpret_cast(_binary_vulkan_##file##_##ext##_spv_start) } -void GPUDisplayBackendVulkan::createShaders() +void GPUDisplayBackendVulkan::loadShaders() { LOAD_SHADER(vertex, vert); LOAD_SHADER(fragment, frag); + LOAD_SHADER(fragmentSmoothPoint, frag); + LOAD_SHADER(vertexSmoothLine, vert); + LOAD_SHADER(vertexSmoothPoint, vert); + LOAD_SHADER(fragmentSmoothLine, frag); LOAD_SHADER(vertexPoint, vert); LOAD_SHADER(vertexTexture, vert); LOAD_SHADER(fragmentTexture, frag); LOAD_SHADER(fragmentText, frag); } -void GPUDisplayBackendVulkan::clearShaders() -{ - clearVector(mShaders, [&](auto& x) { mDevice.destroyShaderModule(x.second, nullptr); }); -} - // ---------------------------- VULKAN BUFFERS ---------------------------- void GPUDisplayBackendVulkan::writeToBuffer(VulkanBuffer& buffer, size_t size, const void* srcData) { - if (buffer.deviceMemory != 1) { - void* dstData; - CHKERR(mDevice.mapMemory(buffer.memory, 0, buffer.size, {}, &dstData)); - memcpy(dstData, srcData, size); - mDevice.unmapMemory(buffer.memory); + if (buffer.mapped) { + memcpy(buffer.mapped, srcData, size); } else { auto tmp = createBuffer(size, srcData, vk::BufferUsageFlagBits::eTransferSrc, 0); @@ -1319,6 +1200,9 @@ GPUDisplayBackendVulkan::VulkanBuffer GPUDisplayBackendVulkan::createBuffer(size buffer.size = size; buffer.deviceMemory = deviceMemory; + if (deviceMemory != 1) { + CHKERR(mDevice.mapMemory(buffer.memory, 0, VK_WHOLE_SIZE, {}, &buffer.mapped)); + } if (srcData != nullptr) { writeToBuffer(buffer, size, srcData); @@ -1330,7 +1214,7 @@ GPUDisplayBackendVulkan::VulkanBuffer GPUDisplayBackendVulkan::createBuffer(size void GPUDisplayBackendVulkan::clearBuffer(VulkanBuffer& buffer) { mDevice.destroyBuffer(buffer.buffer, nullptr); - mDevice.freeMemory(buffer.memory, nullptr); + mDevice.freeMemory(buffer.memory, nullptr); // Implicitly unmaps } void GPUDisplayBackendVulkan::clearVertexBuffers() @@ -1343,12 +1227,6 @@ void GPUDisplayBackendVulkan::clearVertexBuffers() clearBuffer(mIndirectCommandBuffer); mIndirectCommandBuffer.size = 0; } - for (auto& buf : mFontVertexBuffer) { - if (buf.size) { - clearBuffer(buf); - } - buf.size = 0; - } } // ---------------------------- VULKAN TEXTURES ---------------------------- @@ -1358,7 +1236,7 @@ void GPUDisplayBackendVulkan::writeToImage(VulkanImage& image, const void* srcDa auto tmp = createBuffer(srcSize, srcData, vk::BufferUsageFlagBits::eTransferSrc, 0); vk::CommandBuffer commandBuffer = getSingleTimeCommandBuffer(); - cmdImageMemoryBarrier(commandBuffer, image.image, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(commandBuffer, image.image, vk::PipelineStageFlagBits2::eNone, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); vk::BufferImageCopy region{}; region.bufferOffset = 0; region.bufferRowLength = 0; @@ -1370,7 +1248,7 @@ void GPUDisplayBackendVulkan::writeToImage(VulkanImage& image, const void* srcDa region.imageOffset = vk::Offset3D{0, 0, 0}; region.imageExtent = vk::Extent3D{image.sizex, image.sizey, 1}; commandBuffer.copyBufferToImage(tmp.buffer, image.image, vk::ImageLayout::eTransferDstOptimal, 1, ®ion); - cmdImageMemoryBarrier(commandBuffer, image.image, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader); + cmdImageMemoryBarrier(commandBuffer, image.image, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eFragmentShader, vk::AccessFlagBits2::eShaderSampledRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); submitSingleTimeCommandBuffer(commandBuffer); clearBuffer(tmp); @@ -1404,11 +1282,11 @@ void GPUDisplayBackendVulkan::clearImage(VulkanImage& image) int32_t GPUDisplayBackendVulkan::InitBackendA() { - mEnableValidationLayers = mDisplay->param() && mDisplay->GetProcessingSettings().debugLevel >= 2; - mFramesInFlight = 2; + mEnableValidationLayers = (mDisplay->param() && mDisplay->GetProcessingSettings().debugLevel >= 2) || (getenv("GPUCA_VULKAN_VALIDATION") && atoi(getenv("GPUCA_VULKAN_VALIDATION"))); + mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; createDevice(); - createShaders(); + loadShaders(); createTextureSampler(); createSwapChain(); createOffscreenBuffers(); @@ -1429,12 +1307,11 @@ void GPUDisplayBackendVulkan::ExitBackendA() clearOffscreenBuffers(); clearSwapChain(); if (mCommandInfrastructureCreated) { - clearSemaphoresAndFences(); + clearSemaphores(); clearCommandBuffers(); clearUniformLayoutsAndBuffers(); } clearTextureSampler(); - clearShaders(); clearDevice(); } @@ -1473,13 +1350,14 @@ uint32_t GPUDisplayBackendVulkan::drawVertices(const vboList& v, const drawType if (count == 0) { return 0; } - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return count; } - if (mCurrentCommandBufferLastPipeline != tt) { - mCurrentCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[tt]); - mCurrentCommandBufferLastPipeline = tt; + const int32_t pipeline = tt + ((tt == POINTS ? mDisplay->cfgL().smoothPoints : useSmoothLineShader()) ? 5 : 0); + if (mCurrentCommandBufferLastPipeline != pipeline) { + mCurrentCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[pipeline]); + mCurrentCommandBufferLastPipeline = pipeline; } if (mDisplay->cfgR().useGLIndirectDraw) { mCurrentCommandBuffer.drawIndirect(mIndirectCommandBuffer.buffer, (mIndirectSectorOffset[iSector] + first) * sizeof(DrawArraysIndirectCommand), count, sizeof(DrawArraysIndirectCommand)); @@ -1500,130 +1378,106 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& if (includeMixImage == 0.f) { mCurrentFrame = (mCurrentFrame + 1) % mFramesInFlight; - CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); + waitTimeline(mFrameTimelineValue[mCurrentFrame]); auto getImage = [&]() { - vk::Fence fen = VkFence(VK_NULL_HANDLE); - vk::Semaphore sem = VkSemaphore(VK_NULL_HANDLE); - if (mCommandBufferPerImage) { - fen = mInFlightFence[mCurrentFrame]; - CHKERR(mDevice.resetFences(1, &fen)); - } else { - sem = mImageAvailableSemaphore[mCurrentFrame]; - } - return mDevice.acquireNextImageKHR(mSwapChain, UINT64_MAX, sem, fen, &mCurrentImageIndex); + return mDevice.acquireNextImageKHR(mSwapChain, UINT64_MAX, mImageAvailableSemaphore[mCurrentFrame], VkFence(VK_NULL_HANDLE), &mCurrentImageIndex); }; vk::Result retVal = vk::Result::eSuccess; - bool mustUpdateRendering = mMustUpdateSwapChain; - if (mDisplay->updateRenderPipeline() || (requestScreenshot && !mSwapchainImageReadable) || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot)) { - mustUpdateRendering = true; - } else if (!mMustUpdateSwapChain) { + bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot) || mSmoothLines != useNativeSmoothLines(); + if (!mustUpdateSwapChain) { retVal = getImage(); - } - if (mMustUpdateSwapChain || mustUpdateRendering || retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) { - if (!mustUpdateRendering) { - GPUInfo("Pipeline out of data / suboptimal, recreating"); + if (retVal == vk::Result::eErrorOutOfDateKHR) { + mustUpdateSwapChain = mMustUpdateSwapChain = true; } + } + if (mustUpdateSwapChain) { recreateRendering(requestScreenshot, toMixBuffer); retVal = getImage(); } + mMustUpdateSwapChain = retVal == vk::Result::eSuboptimalKHR; CHKERR(retVal); - if (mCommandBufferPerImage) { - CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); - } - CHKERR(mDevice.resetFences(1, &mInFlightFence[mCurrentFrame])); - mMustUpdateSwapChain = false; - mHasDrawnText = false; - mCurrentBufferSet = mCommandBufferPerImage ? mCurrentImageIndex : mCurrentFrame; + waitTimeline(mImageTimelineValue[mCurrentImageIndex]); // Previous frame using this image's resources (command buffers, uniform buffer, ...) finished + mHasDrawnText = false; + mFrameCommandBuffers.clear(); const hmm_mat4 modelViewProj = proj * view; - writeToBuffer(mUniformBuffersMat[0][mCurrentBufferSet], sizeof(modelViewProj), &modelViewProj); + static_assert(sizeof(modelViewProj) == sizeof(mLastMVP)); + if (memcmp(&modelViewProj, mLastMVP, sizeof(mLastMVP))) { + memcpy(mLastMVP, &modelViewProj, sizeof(mLastMVP)); + mUpdateUBOdrawMatrix = -1; + } + if (mUpdateUBOdrawMatrix & (1u << mCurrentImageIndex)) { + mUpdateUBOdrawMatrix &= ~(1u << mCurrentImageIndex); + writeToBuffer(mUniformBuffersMat[0][mCurrentImageIndex], sizeof(mLastMVP), mLastMVP); + } } - mCurrentCommandBuffer = toMixBuffer ? mCommandBuffersMix[mCurrentBufferSet] : mCommandBuffers[mCurrentBufferSet]; + mCurrentCommandBuffer = toMixBuffer ? mCommandBuffersMix[mCurrentImageIndex] : mCommandBuffers[mCurrentImageIndex]; mCurrentCommandBufferLastPipeline = -1; - if (!mCommandBufferUpToDate[mCurrentBufferSet]) { + if (!mCommandBufferUpToDate[mCurrentImageIndex]) { startFillCommandBuffer(mCurrentCommandBuffer, mCurrentImageIndex, toMixBuffer); } } void GPUDisplayBackendVulkan::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) { - if (!mCommandBufferUpToDate[mCurrentBufferSet]) { + if (!mCommandBufferUpToDate[mCurrentImageIndex]) { endFillCommandBuffer(mCurrentCommandBuffer); - if (!toMixBuffer && includeMixImage == 0.f && mCommandBufferPerImage) { - mCommandBufferUpToDate[mCurrentBufferSet] = true; + if (!toMixBuffer && includeMixImage == 0.f) { + mCommandBufferUpToDate[mCurrentImageIndex] = true; } } } void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, float includeMixImage) { - vk::Semaphore* stageFinishedSemaphore = &mRenderFinishedSemaphore[mCurrentFrame]; - const vk::Fence noFence = VkFence(VK_NULL_HANDLE); - - vk::SubmitInfo submitInfo{}; - vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.pWaitSemaphores = includeMixImage > 0.f ? &mRenderFinishedSemaphore[mCurrentFrame] : (!mCommandBufferPerImage ? &mImageAvailableSemaphore[mCurrentFrame] : nullptr); - submitInfo.waitSemaphoreCount = submitInfo.pWaitSemaphores != nullptr ? 1 : 0; - submitInfo.pWaitDstStageMask = waitStages; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &mCurrentCommandBuffer; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, includeMixImage > 0 || toMixBuffer || mHasDrawnText || mDownsampleFSAA ? noFence : mInFlightFence[mCurrentFrame])); + mFrameCommandBuffers.emplace_back(mCurrentCommandBuffer); if (!toMixBuffer) { if (includeMixImage > 0.f) { - mixImages(mCommandBuffersTexture[mCurrentBufferSet], includeMixImage); - submitInfo.pWaitSemaphores = stageFinishedSemaphore; - waitStages[0] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pCommandBuffers = &mCommandBuffersTexture[mCurrentBufferSet]; - stageFinishedSemaphore = &mMixFinishedSemaphore[mCurrentFrame]; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mHasDrawnText || mDownsampleFSAA ? noFence : mInFlightFence[mCurrentFrame])); + mixImages(mCommandBuffersTexture[mCurrentImageIndex], includeMixImage); + mFrameCommandBuffers.emplace_back(mCommandBuffersTexture[mCurrentImageIndex]); } - if (mDownsampleFSAA) { - downsampleToFramebuffer(mCommandBuffersDownsample[mCurrentBufferSet]); - submitInfo.pCommandBuffers = &mCommandBuffersDownsample[mCurrentBufferSet]; - submitInfo.pWaitSemaphores = stageFinishedSemaphore; - waitStages[0] = {vk::PipelineStageFlagBits::eTransfer}; - submitInfo.waitSemaphoreCount = 1; - stageFinishedSemaphore = &mDownsampleFinishedSemaphore[mCurrentFrame]; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mHasDrawnText ? noFence : mInFlightFence[mCurrentFrame])); + downsampleToFramebuffer(mCommandBuffersDownsample[mCurrentImageIndex]); + mFrameCommandBuffers.emplace_back(mCommandBuffersDownsample[mCurrentImageIndex]); } + vk::SemaphoreSubmitInfo waitInfo{mImageAvailableSemaphore[mCurrentFrame], 0, vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer}; + vk::SubmitInfo2 submitInfo{{}, 1, &waitInfo}; if (doScreenshot) { + submitInfo.commandBufferInfoCount = mFrameCommandBuffers.size(); + submitInfo.pCommandBufferInfos = mFrameCommandBuffers.data(); + queueSubmit(nullptr, 1, &submitInfo, VkFence(VK_NULL_HANDLE)); mDevice.waitIdle(); if (mDisplay->cfgR().screenshotScaleFactor != 1) { - readImageToPixels(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eColorAttachmentOptimal, mScreenshotPixels); + readImageToPixels(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eTransferSrcOptimal, mScreenshotPixels); } else { - readImageToPixels(mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::ePresentSrcKHR, mScreenshotPixels); + readImageToPixels(mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::eColorAttachmentOptimal, mScreenshotPixels); } + mFrameCommandBuffers.clear(); + submitInfo.waitSemaphoreInfoCount = 0; } - if (mHasDrawnText) { - submitInfo.pWaitSemaphores = stageFinishedSemaphore; - waitStages[0] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pCommandBuffers = &mCommandBuffersText[mCurrentBufferSet]; - stageFinishedSemaphore = &mTextFinishedSemaphore[mCurrentFrame]; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mInFlightFence[mCurrentFrame])); + mFrameCommandBuffers.emplace_back(mCommandBuffersText[mCurrentImageIndex]); } + mFrameCommandBuffers.emplace_back(mCommandBuffersPresent[mCurrentImageIndex]); + submitInfo.commandBufferInfoCount = mFrameCommandBuffers.size(); + submitInfo.pCommandBufferInfos = mFrameCommandBuffers.data(); + vk::SemaphoreSubmitInfo signalInfos[2] = {{mPresentSemaphore[mCurrentImageIndex], 0, vk::PipelineStageFlagBits2::eAllCommands}, {mTimelineSemaphore, 0, vk::PipelineStageFlagBits2::eAllCommands}}; + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signalInfos; + mFrameTimelineValue[mCurrentFrame] = mImageTimelineValue[mCurrentImageIndex] = queueSubmit(&signalInfos[1].value, 1, &submitInfo, VkFence(VK_NULL_HANDLE)); - CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); // TODO: I think we need to wait for the fence, so that the image was acquired before we present. Perhaps we can present later to avoid delays vk::PresentInfoKHR presentInfo{}; presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = stageFinishedSemaphore; + presentInfo.pWaitSemaphores = &mPresentSemaphore[mCurrentImageIndex]; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &mSwapChain; presentInfo.pImageIndices = &mCurrentImageIndex; presentInfo.pResults = nullptr; vk::Result retVal = mGraphicsQueue.presentKHR(&presentInfo); - if (retVal == vk::Result::eErrorOutOfDateKHR) { + if (retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) { mMustUpdateSwapChain = true; } else { CHKERR(retVal); @@ -1638,8 +1492,8 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; commandBuffer.begin(beginInfo); - cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); // Src stage chains with the acquire semaphore wait + cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal); vk::Offset3D blitSizeSrc; blitSizeSrc.x = mRenderWidth; @@ -1658,17 +1512,18 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command imageBlitRegion.dstOffsets[1] = blitSizeDst; commandBuffer.blitImage(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eTransferSrcOptimal, mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); - cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::ePresentSrcKHR, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentRead | vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eColorAttachmentOptimal); // Next: text pass and / or present transition commandBuffer.end(); } void GPUDisplayBackendVulkan::prepareText() { - hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); - writeToBuffer(mUniformBuffersMat[1][mCurrentBufferSet], sizeof(proj), &proj); - + if (mUpdateUBOtextMatrix & (1u << mCurrentImageIndex)) { + mUpdateUBOtextMatrix &= ~(1u << mCurrentImageIndex); + hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); + writeToBuffer(mUniformBuffersMat[1][mCurrentImageIndex], sizeof(proj), &proj); + } mFontVertexBufferHost.clear(); mTextDrawCommands.clear(); } @@ -1679,81 +1534,71 @@ void GPUDisplayBackendVulkan::finishText() return; } - mCommandBuffersText[mCurrentBufferSet].reset({}); + mCommandBuffersText[mCurrentImageIndex].reset({}); vk::CommandBufferBeginInfo beginInfo{}; beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; - mCommandBuffersText[mCurrentBufferSet].begin(beginInfo); + mCommandBuffersText[mCurrentImageIndex].begin(beginInfo); - vk::RenderPassBeginInfo renderPassInfo{}; - renderPassInfo.renderPass = mRenderPassText; - renderPassInfo.framebuffer = mFramebuffersText.size() ? mFramebuffersText[mCurrentImageIndex] : mFramebuffers[mCurrentImageIndex]; - renderPassInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderPassInfo.renderArea.extent = vk::Extent2D{mScreenWidth, mScreenHeight}; - renderPassInfo.clearValueCount = 0; - mCommandBuffersText[mCurrentBufferSet].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + cmdBeginRenderingLoad(mCommandBuffersText[mCurrentImageIndex], mSwapChainImages[mCurrentImageIndex], mSwapChainImageViews[mCurrentImageIndex], mScreenWidth, mScreenHeight); - if (mFontVertexBuffer[mCurrentBufferSet].size) { - clearBuffer(mFontVertexBuffer[mCurrentBufferSet]); + VulkanBuffer& fontBuffer = mFontVertexBuffer[mCurrentImageIndex]; + const size_t fontBufferSize = mFontVertexBufferHost.size() * sizeof(mFontVertexBufferHost[0]); + if (fontBuffer.size < fontBufferSize) { // Persistent per image, only grows + if (fontBuffer.size) { + clearBuffer(fontBuffer); + } + fontBuffer = createBuffer(std::max(fontBufferSize, 2 * fontBuffer.size), nullptr, vk::BufferUsageFlagBits::eVertexBuffer, 0); } - mFontVertexBuffer[mCurrentBufferSet] = createBuffer(mFontVertexBufferHost.size() * sizeof(float), mFontVertexBufferHost.data(), vk::BufferUsageFlagBits::eVertexBuffer, 0); + writeToBuffer(fontBuffer, fontBufferSize, mFontVertexBufferHost.data()); - mCommandBuffersText[mCurrentBufferSet].bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[3]); - mCommandBuffersText[mCurrentBufferSet].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[1][mCurrentBufferSet], 0, nullptr); + mCommandBuffersText[mCurrentImageIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[3]); + cmdPushDescriptors(mCommandBuffersText[mCurrentImageIndex], mPipelineLayoutTexture, mUniformBuffersMat[1][mCurrentImageIndex], mFontImage.view); vk::DeviceSize offsets[] = {0}; - mCommandBuffersText[mCurrentBufferSet].bindVertexBuffers(0, 1, &mFontVertexBuffer[mCurrentBufferSet].buffer, offsets); + mCommandBuffersText[mCurrentImageIndex].bindVertexBuffers(0, 1, &fontBuffer.buffer, offsets); for (const auto& cmd : mTextDrawCommands) { - mCommandBuffersText[mCurrentBufferSet].pushConstants(mPipelineLayoutTexture, vk::ShaderStageFlagBits::eFragment, 0, sizeof(cmd.color), cmd.color); - mCommandBuffersText[mCurrentBufferSet].draw(cmd.nVertices, 1, cmd.firstVertex, 0); + mCommandBuffersText[mCurrentImageIndex].pushConstants(mPipelineLayoutTexture, vk::ShaderStageFlagBits::eFragment, 0, sizeof(cmd.color), cmd.color); + mCommandBuffersText[mCurrentImageIndex].draw(cmd.nVertices, 1, cmd.firstVertex, 0); } mFontVertexBufferHost.clear(); - mCommandBuffersText[mCurrentBufferSet].endRenderPass(); - mCommandBuffersText[mCurrentBufferSet].end(); + mCommandBuffersText[mCurrentImageIndex].endRendering(); + mCommandBuffersText[mCurrentImageIndex].end(); } void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float mixSlaveImage) { - hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1, 1); - writeToBuffer(mUniformBuffersMat[2][mCurrentBufferSet], sizeof(proj), &proj); - + if (mUpdateUBOmixMatrix & (1u << mCurrentImageIndex)) { + mUpdateUBOmixMatrix &= ~(1u << mCurrentImageIndex); + hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1, 1); + writeToBuffer(mUniformBuffersMat[2][mCurrentImageIndex], sizeof(proj), &proj); + } commandBuffer.reset({}); vk::CommandBufferBeginInfo beginInfo{}; beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; commandBuffer.begin(beginInfo); vk::Image& image = mDownsampleFSAA ? mDownsampleImages[mCurrentImageIndex + mImageCount].image : mMixImages[mCurrentImageIndex].image; - vk::ImageLayout srcLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - cmdImageMemoryBarrier(commandBuffer, image, {}, vk::AccessFlagBits::eMemoryRead, srcLayout, vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eFragmentShader); - - vk::RenderPassBeginInfo renderPassInfo{}; - renderPassInfo.renderPass = mRenderPassTexture; - renderPassInfo.framebuffer = mFramebuffersTexture.size() ? mFramebuffersTexture[mCurrentImageIndex] : mFramebuffers[mCurrentImageIndex]; - renderPassInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderPassInfo.renderArea.extent = vk::Extent2D{mRenderWidth, mRenderHeight}; - renderPassInfo.clearValueCount = 0; - commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + cmdImageMemoryBarrier(commandBuffer, image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eFragmentShader, vk::AccessFlagBits2::eShaderSampledRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + cmdBeginRenderingLoad(commandBuffer, mRenderTargetImage[mCurrentImageIndex], *mRenderTargetView[mCurrentImageIndex], mRenderWidth, mRenderHeight); commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[4]); - if (!mCommandBufferPerImage) { - setMixDescriptor(mCurrentBufferSet, mCurrentImageIndex); - } - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[2][mCurrentBufferSet], 0, nullptr); + cmdPushDescriptors(commandBuffer, mPipelineLayoutTexture, mUniformBuffersMat[2][mCurrentImageIndex], *mRenderTargetView[mCurrentImageIndex + mImageCount]); vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mMixingTextureVertexArray.buffer, offsets); commandBuffer.pushConstants(mPipelineLayoutTexture, vk::ShaderStageFlagBits::eFragment, 0, sizeof(mixSlaveImage), &mixSlaveImage); commandBuffer.draw(6, 1, 0, 0); - commandBuffer.endRenderPass(); + commandBuffer.endRendering(); commandBuffer.end(); } void GPUDisplayBackendVulkan::ActivateColor(std::array& color) { - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, sizeof(color), color.data()); @@ -1761,19 +1606,35 @@ void GPUDisplayBackendVulkan::ActivateColor(std::array& color) void GPUDisplayBackendVulkan::pointSizeFactor(float factor) { - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } - float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor; + float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mDisplay->cfgL().smoothPoints ? SMOOTH_POINT_EXTRA_SIZE : 0.f); mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, sizeof(std::array), sizeof(size), &size); } +bool GPUDisplayBackendVulkan::useNativeSmoothLines() const +{ + return mDisplay->cfgL().smoothLines && mSmoothLinesSupported && mDisplay->cfgR().rendererCompatSmoothing; +} + +bool GPUDisplayBackendVulkan::useSmoothLineShader() const +{ + return mDisplay->cfgL().smoothLines && !useNativeSmoothLines(); +} + void GPUDisplayBackendVulkan::lineWidthFactor(float factor) { - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } - mCurrentCommandBuffer.setLineWidth(mDisplay->cfgL().lineWidth * mDownsampleFactor * factor); + float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; + if (useSmoothLineShader()) { + width += SMOOTH_LINE_EXTRA_WIDTH; + mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, 5 * sizeof(float), sizeof(width), &width); + width *= mStrictLines ? 1.f : 1.4143f; // Non-strict wide lines may be parallelograms, widen so that the perpendicular width is covered + } + mCurrentCommandBuffer.setLineWidth(width); } void GPUDisplayBackendVulkan::needRecordCommandBuffers() @@ -1851,25 +1712,16 @@ void GPUDisplayBackendVulkan::initializeTextDrawing() } mFontImage = createImage(sizex, sizey, bigImage.get(), sizex * sizey, vk::Format::eR8Unorm); - updateFontTextureDescriptor(); } -void GPUDisplayBackendVulkan::updateFontTextureDescriptor() +void GPUDisplayBackendVulkan::clearFontSymbols() { - vk::DescriptorImageInfo imageInfo{}; - imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - imageInfo.imageView = mFontImage.view; - imageInfo.sampler = mTextureSampler; - for (uint32_t i = 0; i < mFramesInFlight; i++) { - vk::WriteDescriptorSet descriptorWrite{}; - descriptorWrite.dstSet = mDescriptorSets[1][i]; - descriptorWrite.dstBinding = 2; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = vk::DescriptorType::eCombinedImageSampler; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pImageInfo = &imageInfo; - mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); + mDevice.waitIdle(); + if (mFontImage.sizex && mFontImage.sizey) { + clearImage(mFontImage); + mFontImage.sizex = mFontImage.sizey = 0; } + mFontSymbols.clear(); } void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float* color, float scale) @@ -1883,8 +1735,8 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } - for (const char* c = s; *c; c++) { - if ((int32_t)*c > (int32_t)mFontSymbols.size()) { + for (const unsigned char* c = (const unsigned char*)s; *c; c++) { + if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); continue; } @@ -1923,19 +1775,25 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout layout, std::vector& pixels) { + const vk::Format format = mSurfaceFormat.format; + if (format != vk::Format::eB8G8R8A8Unorm && format != vk::Format::eB8G8R8A8Srgb && format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eR8G8B8A8Srgb) { + GPUError("Screenshot not supported for swapchain format %s, only 8 bit BGRA / RGBA", vk::to_string(format).c_str()); + pixels.clear(); + return; + } uint32_t width = mScreenWidth * mDisplay->cfgR().screenshotScaleFactor; uint32_t height = mScreenHeight * mDisplay->cfgR().screenshotScaleFactor; static constexpr int32_t bytesPerPixel = 4; pixels.resize(width * height * bytesPerPixel); - vk::Image dstImage, dstImage2, src2; - vk::DeviceMemory dstImageMemory, dstImageMemory2; - createImageI(mDevice, mPhysicalDevice, dstImage, dstImageMemory, width, height, vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, vk::ImageTiling::eLinear); + vk::Image dstImage, src; + vk::DeviceMemory dstImageMemory; + VulkanBuffer readBuffer = createBuffer(width * height * bytesPerPixel, nullptr, vk::BufferUsageFlagBits::eTransferDst, 0); vk::CommandBuffer cmdBuffer = getSingleTimeCommandBuffer(); - cmdImageMemoryBarrier(cmdBuffer, image, vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferRead, layout, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(cmdBuffer, image, vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eColorAttachmentWrite | vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead, layout, vk::ImageLayout::eTransferSrcOptimal); if (mDisplay->cfgR().screenshotScaleFactor != 1) { - createImageI(mDevice, mPhysicalDevice, dstImage2, dstImageMemory2, width, height, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); - cmdImageMemoryBarrier(cmdBuffer, dstImage2, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + createImageI(mDevice, mPhysicalDevice, dstImage, dstImageMemory, width, height, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); + cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::PipelineStageFlagBits2::eNone, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); vk::Offset3D blitSizeSrc = {(int32_t)mRenderWidth, (int32_t)mRenderHeight, 1}; vk::Offset3D blitSizeDst = {(int32_t)width, (int32_t)height, 1}; vk::ImageBlit imageBlitRegion{}; @@ -1945,42 +1803,39 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout imageBlitRegion.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; imageBlitRegion.dstSubresource.layerCount = 1; imageBlitRegion.dstOffsets[1] = blitSizeDst; - cmdBuffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal, dstImage2, vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); - src2 = dstImage2; - cmdImageMemoryBarrier(cmdBuffer, dstImage2, vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdBuffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal, dstImage, vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); + src = dstImage; + cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal); } else { - src2 = image; - } - - cmdImageMemoryBarrier(cmdBuffer, dstImage, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - vk::ImageCopy imageCopyRegion{}; - imageCopyRegion.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; - imageCopyRegion.srcSubresource.layerCount = 1; - imageCopyRegion.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; - imageCopyRegion.dstSubresource.layerCount = 1; - imageCopyRegion.extent.width = width; - imageCopyRegion.extent.height = height; - imageCopyRegion.extent.depth = 1; - cmdBuffer.copyImage(src2, vk::ImageLayout::eTransferSrcOptimal, dstImage, vk::ImageLayout::eTransferDstOptimal, 1, &imageCopyRegion); - - cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - cmdImageMemoryBarrier(cmdBuffer, image, vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eTransferSrcOptimal, layout, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + src = image; + } + + vk::BufferImageCopy copyRegion{}; + copyRegion.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageExtent = vk::Extent3D{width, height, 1}; + cmdBuffer.copyImageToBuffer(src, vk::ImageLayout::eTransferSrcOptimal, readBuffer.buffer, 1, ©Region); + + vk::BufferMemoryBarrier2 hostBarrier{vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eHost, vk::AccessFlagBits2::eHostRead, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, readBuffer.buffer, 0, VK_WHOLE_SIZE}; + cmdBuffer.pipelineBarrier2(vk::DependencyInfo{{}, 0, nullptr, 1, &hostBarrier, 0, nullptr}); + cmdImageMemoryBarrier(cmdBuffer, image, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eNone, vk::ImageLayout::eTransferSrcOptimal, layout); // Back to the layout the following passes expect submitSingleTimeCommandBuffer(cmdBuffer); - vk::ImageSubresource subResource{vk::ImageAspectFlagBits::eColor, 0, 0}; - vk::SubresourceLayout subResourceLayout = mDevice.getImageSubresourceLayout(dstImage, subResource); - const char* data; - CHKERR(mDevice.mapMemory(dstImageMemory, 0, VK_WHOLE_SIZE, {}, (void**)&data)); - data += subResourceLayout.offset; - for (uint32_t i = 0; i < height; i++) { - memcpy(pixels.data() + i * width * bytesPerPixel, data + (height - i - 1) * width * bytesPerPixel, width * bytesPerPixel); - } - mDevice.unmapMemory(dstImageMemory); - mDevice.freeMemory(dstImageMemory, nullptr); - mDevice.destroyImage(dstImage, nullptr); + const bool swapRB = format == vk::Format::eR8G8B8A8Unorm || format == vk::Format::eR8G8B8A8Srgb; // Raw copy in swapchain format, the BMP needs BGRA + const char* data = (const char*)readBuffer.mapped; + for (uint32_t i = 0; i < height; i++) { // BMP rows are bottom-up + char* dst = pixels.data() + i * width * bytesPerPixel; + memcpy(dst, data + (height - i - 1) * width * bytesPerPixel, width * bytesPerPixel); + if (swapRB) { + for (uint32_t j = 0; j < width; j++) { + std::swap(dst[j * bytesPerPixel], dst[j * bytesPerPixel + 2]); + } + } + } + clearBuffer(readBuffer); if (mDisplay->cfgR().screenshotScaleFactor != 1) { - mDevice.freeMemory(dstImageMemory2, nullptr); - mDevice.destroyImage(dstImage2, nullptr); + mDevice.freeMemory(dstImageMemory, nullptr); + mDevice.destroyImage(dstImage, nullptr); } } @@ -1991,5 +1846,5 @@ uint32_t GPUDisplayBackendVulkan::DepthBits() bool GPUDisplayBackendVulkan::backendNeedRedraw() { - return !mCommandBufferUpToDate[mCurrentBufferSet]; + return !mCommandBufferUpToDate[mCurrentImageIndex]; } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index caefdd25388c1..29d90d4e1e496 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -20,6 +20,7 @@ #include #include +#include #include namespace o2::gpu @@ -44,6 +45,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend vk::DeviceMemory memory; size_t size = 0; int32_t deviceMemory; + void* mapped = nullptr; // Persistent mapping of host-visible buffers (deviceMemory 0 / 2) }; struct VulkanImage { vk::Image image; @@ -76,14 +78,17 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void prepareText() override; void finishText() override; void mixImages(vk::CommandBuffer cmdBuffer, float mixSlaveImage); - void setMixDescriptor(int32_t descriptorIndex, int32_t imageIndex); + void cmdPushDescriptors(vk::CommandBuffer commandBuffer, vk::PipelineLayout layout, const VulkanBuffer& ubo, vk::ImageView textureView = {}); // Push descriptors: UBO at binding 0, optional texture at binding 2 void pointSizeFactor(float factor) override; void lineWidthFactor(float factor) override; + bool useNativeSmoothLines() const; + bool useSmoothLineShader() const; void resizeScene(uint32_t width, uint32_t height) override; float getYFactor() const override { return -1.0f; } int32_t getMaxMSAA() const override { return mMaxMSAAsupported; } - double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions); + using FeatureChain = vk::StructureChain; + double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const FeatureChain& reqDeviceFeatures, bool silent = false); vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities); void transitionImageLayout(vk::CommandBuffer commandBuffer, vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout); VulkanBuffer createBuffer(size_t size, const void* srcData = nullptr, vk::BufferUsageFlags type = vk::BufferUsageFlagBits::eVertexBuffer, int32_t deviceMemory = 1); @@ -97,35 +102,37 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void startFillCommandBuffer(vk::CommandBuffer& commandBuffer, uint32_t imageIndex, bool toMixBuffer = false); void endFillCommandBuffer(vk::CommandBuffer& commandBuffer); vk::CommandBuffer getSingleTimeCommandBuffer(); + void waitTimeline(uint64_t value); + template + uint64_t queueSubmit(uint64_t* timelineSignalValue, Args&&... args); // Submit under mQueueMutex; if timelineSignalValue (the entry in pSignalSemaphoreValues) is set, write the next timeline value there first. Returns the current timeline value void submitSingleTimeCommandBuffer(vk::CommandBuffer commandBuffer); void readImageToPixels(vk::Image image, vk::ImageLayout layout, std::vector& pixels); void downsampleToFramebuffer(vk::CommandBuffer& commandBuffer); void updateSwapChainDetails(const vk::PhysicalDevice& device); - void updateFontTextureDescriptor(); void createDevice(); - void createShaders(); + void loadShaders(); void createTextureSampler(); void createCommandBuffers(); - void createSemaphoresAndFences(); + void createSemaphores(); void createUniformLayoutsAndBuffers(); - void createSwapChain(bool forScreenshot = false, bool forMixing = false); + void createSwapChain(vk::SwapchainKHR oldSwapChain = {}); void createOffscreenBuffers(bool forScreenshot = false, bool forMixing = false); void createPipeline(); void clearDevice(); - void clearShaders(); void clearTextureSampler(); void clearCommandBuffers(); - void clearSemaphoresAndFences(); + void clearSemaphores(); void clearUniformLayoutsAndBuffers(); void clearOffscreenBuffers(); - void clearSwapChain(); + void clearSwapChain(bool keepSwapChain = false); void clearPipeline(); void recreateRendering(bool forScreenshot = false, bool forMixing = false); void needRecordCommandBuffers(); void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; + void clearFontSymbols() override; void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; uint32_t mGraphicsFamily; @@ -145,29 +152,23 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mSwapChainImages; std::vector mSwapChainImageViews; std::vector mRenderTargetView; + std::vector mRenderTargetImage; std::vector mMSAAImages; std::vector mDownsampleImages; std::vector mZImages; std::vector mMixImages; - std::unordered_map mShaders; + std::unordered_map mShaders; // Embedded SPIR-V, chained into the pipeline shader stages (maintenance5, no shader modules) vk::PipelineLayout mPipelineLayout; vk::PipelineLayout mPipelineLayoutTexture; - vk::RenderPass mRenderPass; - vk::RenderPass mRenderPassText; - vk::RenderPass mRenderPassTexture; std::vector mPipelines; - std::vector mFramebuffers; - std::vector mFramebuffersText; - std::vector mFramebuffersTexture; vk::CommandPool mCommandPool; + vk::PipelineCache mPipelineCache; - bool mCommandBufferPerImage = false; bool mCommandInfrastructureCreated = false; uint32_t mImageCount = 0; uint32_t mFramesInFlight = 0; int32_t mCurrentFrame = 0; uint32_t mCurrentImageIndex = 0; - int32_t mCurrentBufferSet = 0; vk::CommandBuffer mCurrentCommandBuffer; int32_t mCurrentCommandBufferLastPipeline = -1; @@ -176,19 +177,23 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mCommandBuffersText; std::vector mCommandBuffersTexture; std::vector mCommandBuffersMix; + std::vector mCommandBuffersPresent; + std::vector mFrameCommandBuffers; // All command buffers of the current frame, submitted as one batch, ordered by barriers std::vector mCommandBufferUpToDate; std::vector mImageAvailableSemaphore; - std::vector mRenderFinishedSemaphore; - std::vector mTextFinishedSemaphore; - std::vector mMixFinishedSemaphore; - std::vector mDownsampleFinishedSemaphore; - std::vector mInFlightFence; + std::vector mPresentSemaphore; + vk::Semaphore mTimelineSemaphore; // Signaled with an increasing value by every queue submit, replaces fences + uint64_t mTimelineValue = 0; // Last value used for a submit + std::vector mFrameTimelineValue; // Per frame slot: value of its last submit, slot (and its acquire semaphore) free once reached + std::vector mImageTimelineValue; // Per swapchain image: value of the last submit using its resources + std::mutex mQueueMutex; // Queue submissions must be externally synchronized std::vector mUniformBuffersMat[3]; - std::vector mUniformBuffersCol[3]; - std::vector mDescriptorSets[3]; + uint32_t mUpdateUBOdrawMatrix = -1; // Bitmask per swapchain image: UBO must be rewritten when the image is drawn next, set to -1 to update all + uint32_t mUpdateUBOtextMatrix = -1; + uint32_t mUpdateUBOmixMatrix = -1; + float mLastMVP[16] = {}; vk::DescriptorSetLayout mUniformDescriptor; vk::DescriptorSetLayout mUniformDescriptorTexture; - vk::DescriptorPool mDescriptorPool; VulkanBuffer mVBO; VulkanBuffer mIndirectCommandBuffer; @@ -201,19 +206,18 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend vecpod mFontVertexBufferHost; bool mHasDrawnText = false; - bool mSwapchainImageReadable = false; vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16; uint32_t mMaxMSAAsupported = 0; bool mZActive = false; + bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines, enabled if available + bool mSmoothLines = false; + bool mStrictLines = false; bool mZSupported = false; bool mStencilSupported = false; - bool mCubicFilterSupported = false; bool mDownsampleFSAA = false; bool mMixingSupported = 0; VulkanBuffer mMixingTextureVertexArray; - - vk::Fence mSingleCommitFence; }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index df5c45c6beaa8..ab6d8162c6d0c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -31,12 +31,19 @@ #include "GPUDisplayFrontendGlut.h" #endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_WAYLAND -#include "GPUDisplayFrontendWayland.h" +namespace o2::gpu +{ +GPUDisplayFrontend* createGPUDisplayFrontendWayland(); +} // namespace o2::gpu #endif #include "GPUDisplayFrontendNone.h" #include "GPULogging.h" #include +#include +#include +#include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT #include "GPUDisplayGUIWrapper.h" @@ -56,23 +63,54 @@ GPUDisplayFrontend::~GPUDisplayFrontend() = default; void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) { GPUDisplayFrontend* me = reinterpret_cast(ptr); - int32_t retVal = me->FrontendMain(); - if (retVal == -1) { + int32_t retVal; + try { + retVal = me->FrontendMain(); + } catch (const std::exception& e) { + GPUError("Error in display frontend: %s", e.what()); + retVal = -1; + } + if (retVal && me->mDisplay->mInitResult == 0) { // Failed before InitDisplay was called, report failure to StartDisplay(), which otherwise waits forever me->InitDisplay(true); } return ((void*)(size_t)retVal); } -void GPUDisplayFrontend::HandleSendKey() +void GPUDisplayFrontend::waitForFPSLimit() { - if (mSendKey) { - mDisplay->HandleSendKey(mSendKey); - mSendKey = 0; + mNeedUpdate = 0; + const auto now = std::chrono::steady_clock::now(); + const auto& cfg = mDisplay->cfgR(); + if (cfg.maxFPSRate || cfg.fpsRate <= 0) { + mNextFrameTime = now; + return; } + const auto period = std::chrono::duration_cast(std::chrono::duration(1. / cfg.fpsRate)); + mNextFrameTime += period; + if (mNextFrameTime < now - period) { // More than one frame behind (slow rendering, first frame), do not try to catch up + mNextFrameTime = now; + } + std::this_thread::sleep_until(mNextFrameTime); +} + +void GPUDisplayFrontend::HandleSendKeys() +{ + std::lock_guard lock(mSendKeyMutex); + for (auto key : mSendKeys) { + mDisplay->HandleSendKey(key); + } + mSendKeys.clear(); +} + +void GPUDisplayFrontend::sendKey(int32_t v) +{ + std::lock_guard lock(mSendKeyMutex); + mSendKeys.emplace_back(v); } void GPUDisplayFrontend::HandleKey(uint8_t key) { mDisplay->HandleKey(key); } int32_t GPUDisplayFrontend::DrawGLScene() { return mDisplay->DrawGLScene(); } + void GPUDisplayFrontend::ResizeScene(int32_t width, int32_t height) { mDisplayHeight = height; @@ -145,7 +183,7 @@ GPUDisplayFrontend* GPUDisplayFrontend::getFrontend(const char* type) #endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_WAYLAND if (strcmp(type, "wayland") == 0 || (strcmp(type, "auto") == 0 && getenv("XDG_SESSION_TYPE") && strcmp(getenv("XDG_SESSION_TYPE"), "wayland") == 0)) { - return new GPUDisplayFrontendWayland; + return createGPUDisplayFrontendWayland(); } else #endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_GLUT @@ -166,6 +204,28 @@ GPUDisplayBackend* GPUDisplayFrontend::backend() return mDisplay->backend(); } +void GPUDisplayFrontend::setWindowState(bool fullScreen, bool maximized) +{ + mDisplay->mCfgR.fullScreen = fullScreen; + mDisplay->mCfgR.maximized = maximized; +} + +void GPUDisplayFrontend::focusLost() +{ + std::fill(std::begin(mKeys), std::end(mKeys), false); + std::fill(std::begin(mKeysShift), std::end(mKeysShift), false); + mMouseDn = mMouseDnR = mMouseDnM = false; +} + +void GPUDisplayFrontend::applyInitialWindowState() +{ + if (mDisplay->cfgR().fullScreen) { + SwitchFullscreen(true); + } else if (mDisplay->cfgR().maximized) { + ToggleMaximized(true); + } +} + int32_t& GPUDisplayFrontend::drawTextFontSize() { return mDisplay->drawTextFontSize(); @@ -188,3 +248,20 @@ int32_t GPUDisplayFrontend::StartDisplay() #endif return (0); } + +void GPUDisplayFrontend::StopDisplay() +{ + { + std::lock_guard lock(mExitStateMutex); + if (mDisplayRunning) { + mDisplayControl = 2; + StopDisplayA(); + mDisplay->UnblockDisplay(); + } + } + while (mDisplayRunning) { + std::this_thread::sleep_for(std::chrono::microseconds(10000)); // TODO: Signal by semaphore + } +} + +void GPUDisplayFrontend::StopDisplayA() {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 0abab8bb0a121..03f5a0524c817 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -18,6 +18,9 @@ #include "GPUCommonDef.h" #include "GPUDisplayInterface.h" #include +#include +#include +#include namespace o2::gpu { @@ -44,12 +47,12 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface TYPE_NONE = 5 }; - // Compile time minimum version defined in GPUDisplay.h, keep in sync! + // Compile time minimum version checked in display/CMakeLists.txt (OpenGL 4.6 headers for SPIR-V shaders), keep in sync! static constexpr int32_t GL_MIN_VERSION_MAJOR = 4; - static constexpr int32_t GL_MIN_VERSION_MINOR = 5; + static constexpr int32_t GL_MIN_VERSION_MINOR = 6; int32_t StartDisplay(); // Start the display. This function returns, and should spawn a thread that runs the display, and calls InitDisplay - void DisplayExit() override = 0; // Stop the display. Display thread should call ExitDisplay and the function returns after the thread has terminated + void StopDisplay() override; // Stop the display. Display thread should call ExitDisplay. Functions waits for display thread to terminate virtual void SwitchFullscreen(bool set) = 0; // Toggle full-screen mode virtual void ToggleMaximized(bool set) = 0; // Maximize window virtual void SetVSync(bool enable) = 0; // Enable / disable vsync @@ -58,15 +61,13 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface GPUDisplayBackend* backend(); static GPUDisplayFrontend* getFrontend(const char* type); virtual void getSize(int32_t& width, int32_t& height) { width = height = 0; } + virtual float getDisplayScale() const { return 1.f; } // HiDPI scale factor: physical pixels per logical pixel, text positions and sizes are scaled by it virtual int32_t getVulkanSurface(void* instance, void* surface) { return 1; } virtual uint32_t getReqVulkanExtensions(const char**& p) { return 0; }; int32_t getDisplayControl() const override { return mDisplayControl; } - int32_t getSendKey() const override { return mSendKey; } - int32_t getNeedUpdate() const override { return mNeedUpdate; } void setDisplayControl(int32_t v) override { mDisplayControl = v; } - void setSendKey(int32_t v) override { mSendKey = v; } - void setNeedUpdate(int32_t v) override { mNeedUpdate = v; } + void sendKey(int32_t v) override; frontendTypes frontendType() const { return mFrontendType; } const char* frontendName() const override { return mFrontendName; } @@ -75,16 +76,14 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface void stopGUI(); bool isGUIRunning(); - // volatile variables to exchange control informations between display and backend - volatile int32_t mDisplayControl = 0; // Control for next event (=1) or quit (=2) - volatile int32_t mSendKey = 0; // Key sent by external entity (usually console), may be ignored by backend. - volatile int32_t mNeedUpdate = 0; // flag that backend shall update the GL window, and call DrawGLScene - protected: virtual int32_t FrontendMain() = 0; + virtual void StopDisplayA(); static void* FrontendThreadWrapper(void*); + void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (no sleep with maxFPSRate or fpsRate <= 0) static constexpr int32_t INIT_WIDTH = 1024, INIT_HEIGHT = 768; // Initial window size, before maximizing + static constexpr int32_t MIN_WIDTH = 200, MIN_HEIGHT = 150; // Minimum window size static constexpr const char* DISPLAY_WINDOW_NAME = "GPU CA Standalone Event Display"; // Title of event display set by backend // Constant key codes for special mKeys (to unify different treatment in X11 / Windows / GLUT / etc.) static constexpr int32_t KEY_UP = 1; @@ -117,9 +116,17 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface static constexpr int32_t KEY_END = 29; static constexpr int32_t KEY_SPACE = 32; // fixed at 32 + // volatile variables to exchange control informations between display and backend + volatile int32_t mDisplayControl = 0; // Control for next event (=1) or quit (=2) + volatile int32_t mNeedUpdate = 0; // flag that backend shall update the GL window, and call DrawGLScene + std::mutex mSendKeyMutex; + std::vector mSendKeys; // Keys sent by external entity (usually console), may be ignored by backend. + std::mutex mExitStateMutex; + // Keyboard / Mouse actions bool mMouseDn = false; // Mouse button down bool mMouseDnR = false; // Right mouse button down + bool mMouseDnM = false; // Middle mouse button down (not used yet) float mMouseDnX, mMouseDnY; // X/Y position where mouse button was pressed float mMouseMvX, mMouseMvY; // Current mouse pointer position int32_t mMouseWheel = 0; // Incremental value of mouse wheel, ca +/- 100 per wheel tick @@ -129,23 +136,27 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface int32_t mDisplayWidth = INIT_WIDTH; int32_t mCanDrawText = 0; // 1 = in compat mode, 2 = with shader - int32_t mMaxFPSRate = 0; // run at highest possible frame rate, do not sleep in between frames + std::chrono::steady_clock::time_point mNextFrameTime; GPUDisplay* mDisplay = nullptr; // Ptr to display, not owning, set by display when it connects to backend GPUDisplayBackend* mBackend = nullptr; // Ptr to backend, not owning frontendTypes mFrontendType = TYPE_INVALID; const char* mFrontendName = nullptr; + volatile bool mDisplayRunning = false; std::unique_ptr mGUI; void HandleKey(uint8_t key); // Callback for handling key presses int32_t DrawGLScene(); // Callback to draw the GL scene - void HandleSendKey(); // Optional callback to handle key press from external source (e.g. stdin by default) + void HandleSendKeys(); // Optional callback to handle key press from external source (e.g. stdin by default) void ResizeScene(int32_t width, int32_t height); // Callback when GL window is resized int32_t InitDisplay(bool initFailure = false); // Callback to initialize the GL Display (to be called in StartDisplay) void ExitDisplay(); // Callback to clean up the GL Display int32_t& drawTextFontSize(); + void setWindowState(bool fullScreen, bool maximized); // Sync the settings if the window manager changed the window state + void focusLost(); // Release all keys and mouse buttons when the window loses focus, since the release events go to another window + void applyInitialWindowState(); // Switch to fullscreen / maximize at startup according to the settings }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 232a9065355a6..789c1c2f5f451 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -26,11 +26,6 @@ #include #include -#ifndef GPUCA_STANDALONE -#include -#include -#endif - using namespace o2::gpu; GPUDisplayFrontendGlfw::GPUDisplayFrontendGlfw() @@ -207,6 +202,8 @@ void GPUDisplayFrontendGlfw::mouseButton_callback(GLFWwindow* window, int32_t bu me->mMouseDn = true; } else if (button == 1) { me->mMouseDnR = true; + } else if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + me->mMouseDnM = true; } me->mMouseDnX = me->mMouseMvX; me->mMouseDnY = me->mMouseMvY; @@ -215,6 +212,8 @@ void GPUDisplayFrontendGlfw::mouseButton_callback(GLFWwindow* window, int32_t bu me->mMouseDn = false; } else if (button == 1) { me->mMouseDnR = false; + } else if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + me->mMouseDnM = false; } } } @@ -223,23 +222,41 @@ void GPUDisplayFrontendGlfw::scroll_callback(GLFWwindow* window, double x, doubl void GPUDisplayFrontendGlfw::cursorPos_callback(GLFWwindow* window, double x, double y) { - me->mMouseMvX = x; - me->mMouseMvY = y; + me->mMouseMvX = x * me->mPixelRatio; // Window coordinates to framebuffer pixels + me->mMouseMvY = y * me->mPixelRatio; } -void GPUDisplayFrontendGlfw::resize_callback(GLFWwindow* window, int32_t width, int32_t height) { me->ResizeScene(width, height); } +void GPUDisplayFrontendGlfw::resize_callback(GLFWwindow* window, int32_t width, int32_t height) // Framebuffer size in pixels +{ + int32_t windowWidth, windowHeight; + glfwGetWindowSize(window, &windowWidth, &windowHeight); + me->mPixelRatio = windowWidth ? (double)width / windowWidth : 1.; + me->ResizeScene(width, height); +} -#ifndef GPUCA_STANDALONE -void GPUDisplayFrontendGlfw::DisplayLoop() +void GPUDisplayFrontendGlfw::focus_callback(GLFWwindow* window, int32_t focused) { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2(me->mDisplayWidth, me->mDisplayHeight)); - ImGui::SetNextWindowBgAlpha(0.f); - ImGui::Begin("Console", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); - me->DrawGLScene(); - ImGui::End(); + if (!focused) { + me->focusLost(); + } +} + +void GPUDisplayFrontendGlfw::contentScale_callback(GLFWwindow* window, float xscale, float yscale) +{ + me->mScale = xscale; +} + +void GPUDisplayFrontendGlfw::maximize_callback(GLFWwindow* window, int32_t maximized) +{ + me->setWindowState(glfwGetWindowMonitor(window) != nullptr, maximized); + if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { // Called before the resize. GLFW keeps the maximized size if the compositor leaves the size to the client when unmaximizing, so restore the last windowed size + if (maximized) { + glfwGetWindowSize(window, &me->mWindowedWidth, &me->mWindowedHeight); + } else { + me->mRestoreWindowedSize = true; // Must be applied after GLFW has processed the configure event, otherwise GLFW resizes to the pending maximized size again + } + } } -#endif int32_t GPUDisplayFrontendGlfw::FrontendMain() { @@ -251,7 +268,6 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } glfwSetErrorCallback(error_callback); - glfwWindowHint(GLFW_MAXIMIZED, 1); if (backend()->backendType() == GPUDisplayBackend::TYPE_VULKAN) { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); } @@ -261,6 +277,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 0); glfwWindowHint(GLFW_OPENGL_PROFILE, mBackend->CoreProfile() ? GLFW_OPENGL_CORE_PROFILE : GLFW_OPENGL_COMPAT_PROFILE); } + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Initial size scaled by the content scale on X11 / Win32 mWindow = glfwCreateWindow(INIT_WIDTH, INIT_HEIGHT, DISPLAY_WINDOW_NAME, nullptr, nullptr); if (!mWindow) { fprintf(stderr, "Error creating glfw window\n"); @@ -276,14 +293,28 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwSetMouseButtonCallback(mWindow, mouseButton_callback); glfwSetScrollCallback(mWindow, scroll_callback); glfwSetCursorPosCallback(mWindow, cursorPos_callback); - glfwSetWindowSizeCallback(mWindow, resize_callback); + glfwSetFramebufferSizeCallback(mWindow, resize_callback); + glfwSetWindowMaximizeCallback(mWindow, maximize_callback); + glfwSetWindowFocusCallback(mWindow, focus_callback); + float xscale, yscale; + glfwGetWindowContentScale(mWindow, &xscale, &yscale); + contentScale_callback(mWindow, xscale, yscale); + glfwSetWindowContentScaleCallback(mWindow, contentScale_callback); + const float limitScale = (glfwGetPlatform() == GLFW_PLATFORM_X11 || glfwGetPlatform() == GLFW_PLATFORM_WIN32) ? mScale : 1.f; // Window coordinates are pixels on X11 / Win32, logical on Wayland / macOS + glfwSetWindowSizeLimits(mWindow, MIN_WIDTH * limitScale, MIN_HEIGHT * limitScale, GLFW_DONT_CARE, GLFW_DONT_CARE); + int32_t fbWidth, fbHeight, windowWidth, windowHeight; + glfwGetFramebufferSize(mWindow, &fbWidth, &fbHeight); + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + mPixelRatio = windowWidth ? (double)fbWidth / windowWidth : 1.; // Updated in resize_callback, which must not be called before InitDisplay + applyInitialWindowState(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glfwSwapInterval(1); } - pthread_mutex_lock(&mSemLockExit); - mGlfwRunning = true; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } if (mBackend->ExtInit()) { fprintf(stderr, "Error initializing GL extension wrapper\n"); @@ -296,7 +327,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } while (!glfwWindowShouldClose(mWindow)) { - HandleSendKey(); + HandleSendKeys(); if (DrawGLScene()) { fprintf(stderr, "Error drawing GL scene\n"); return (1); @@ -304,30 +335,31 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glfwSwapBuffers(mWindow); } + waitForFPSLimit(); glfwPollEvents(); + if (mRestoreWindowedSize) { + mRestoreWindowedSize = false; + if (mWindowedWidth > 0 && mWindowedHeight > 0 && !glfwGetWindowAttrib(mWindow, GLFW_MAXIMIZED)) { + glfwSetWindowSize(mWindow, mWindowedWidth, mWindowedHeight); + } + } } ExitDisplay(); - mDisplayControl = 2; - pthread_mutex_lock(&mSemLockExit); - glfwDestroyWindow(mWindow); - glfwTerminate(); - mGlfwRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayControl = 2; + glfwDestroyWindow(mWindow); + glfwTerminate(); + mDisplayRunning = false; + } return 0; } -void GPUDisplayFrontendGlfw::DisplayExit() +void GPUDisplayFrontendGlfw::StopDisplayA() { - pthread_mutex_lock(&mSemLockExit); - if (mGlfwRunning) { - glfwSetWindowShouldClose(mWindow, true); - } - pthread_mutex_unlock(&mSemLockExit); - while (mGlfwRunning) { - usleep(10000); - } + glfwSetWindowShouldClose(mWindow, true); } void GPUDisplayFrontendGlfw::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) @@ -361,11 +393,7 @@ void GPUDisplayFrontendGlfw::SetVSync(bool enable) { glfwSwapInterval(enable); } bool GPUDisplayFrontendGlfw::EnableSendKey() { -#ifdef GPUCA_STANDALONE return true; -#else - return false; -#endif } void GPUDisplayFrontendGlfw::getSize(int32_t& width, int32_t& height) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index 540514a606145..31b9a8b5211d7 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -16,7 +16,6 @@ #define GPUDISPLAYFRONTENDGLFW_H #include "GPUDisplayFrontend.h" -#include struct GLFWwindow; @@ -28,13 +27,14 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend GPUDisplayFrontendGlfw(); ~GPUDisplayFrontendGlfw() override = default; - void DisplayExit() override; + void StopDisplayA() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; bool EnableSendKey() override; void getSize(int32_t& width, int32_t& height) override; + float getDisplayScale() const override { return mScale; } int32_t getVulkanSurface(void* instance, void* surface) override; uint32_t getReqVulkanExtensions(const char**& p) override; @@ -49,13 +49,19 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend static void scroll_callback(GLFWwindow* mWindow, double x, double y); static void cursorPos_callback(GLFWwindow* mWindow, double x, double y); static void resize_callback(GLFWwindow* mWindow, int32_t width, int32_t height); + static void maximize_callback(GLFWwindow* mWindow, int32_t maximized); + static void focus_callback(GLFWwindow* mWindow, int32_t focused); + static void contentScale_callback(GLFWwindow* mWindow, float xscale, float yscale); static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t scancode, int32_t mods, int32_t& keyOut, int32_t& keyPressOut); GLFWwindow* mWindow; + float mScale = 1.f; // Content scale for text / UI + int32_t mWindowedWidth = 0; // Window size before maximizing (Wayland) + int32_t mWindowedHeight = 0; + bool mRestoreWindowedSize = false; + double mPixelRatio = 1.; // Framebuffer pixels per window coordinate (1 on X11 / Win32, scale on Wayland / macOS) - volatile bool mGlfwRunning = false; - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; int32_t mWindowX = 0; int32_t mWindowY = 0; int32_t mWindowWidth = INIT_WIDTH; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 1b2f2a21150c3..6b75eb7493627 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -32,15 +32,16 @@ GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() mFrontendName = "GLUT"; } -void GPUDisplayFrontendGlut::displayFunc() +void GPUDisplayFrontendGlut::displayFunc() // TODO: Functions should be lambdas, to get rid of the static me variable! { me->DrawGLScene(); glutSwapBuffers(); + me->waitForFPSLimit(); } void GPUDisplayFrontendGlut::glutLoopFunc() { - me->HandleSendKey(); + me->HandleSendKeys(); displayFunc(); } @@ -194,6 +195,8 @@ void GPUDisplayFrontendGlut::mouseFunc(int32_t button, int32_t state, int32_t x, me->mMouseDn = true; } else if (button == GLUT_RIGHT_BUTTON) { me->mMouseDnR = true; + } else if (button == GLUT_MIDDLE_BUTTON) { + me->mMouseDnM = true; } me->mMouseDnX = x; me->mMouseDnY = y; @@ -202,6 +205,8 @@ void GPUDisplayFrontendGlut::mouseFunc(int32_t button, int32_t state, int32_t x, me->mMouseDn = false; } else if (button == GLUT_RIGHT_BUTTON) { me->mMouseDnR = false; + } else if (button == GLUT_MIDDLE_BUTTON) { + me->mMouseDnM = false; } } } @@ -258,31 +263,26 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() glutMotionFunc(mouseMoveFunc); glutPassiveMotionFunc(mouseMoveFunc); glutMouseWheelFunc(mMouseWheelFunc); - ToggleMaximized(true); + applyInitialWindowState(); - pthread_mutex_lock(&mSemLockExit); - mGlutRunning = true; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } glutMainLoop(); - mDisplayControl = 2; - pthread_mutex_lock(&mSemLockExit); - mGlutRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayControl = 2; + mDisplayRunning = false; + } ExitDisplay(); return 0; } -void GPUDisplayFrontendGlut::DisplayExit() +void GPUDisplayFrontendGlut::StopDisplayA() { - pthread_mutex_lock(&mSemLockExit); - if (mGlutRunning) { - glutLeaveMainLoop(); - } - pthread_mutex_unlock(&mSemLockExit); - while (mGlutRunning) { - usleep(10000); - } + glutLeaveMainLoop(); } void GPUDisplayFrontendGlut::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index 9351349e2287d..d595fedcb16cf 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -16,7 +16,6 @@ #define GPUDISPLAYFRONTENDGLUT_H #include "GPUDisplayFrontend.h" -#include namespace o2::gpu { @@ -26,7 +25,7 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend GPUDisplayFrontendGlut(); ~GPUDisplayFrontendGlut() override = default; - void DisplayExit() override; + void StopDisplayA() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; @@ -48,9 +47,6 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t& keyOut, int32_t& keyPressOut, bool special); - volatile bool mGlutRunning = false; - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - int32_t mWidth = INIT_WIDTH; int32_t mHeight = INIT_HEIGHT; bool mFullScreen = false; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index 8a7eab7e00526..73bba8c9458aa 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -29,7 +29,8 @@ int32_t GPUDisplayFrontendNone::FrontendMain() } do { DrawGLScene(); - HandleSendKey(); + waitForFPSLimit(); + HandleSendKeys(); } while (mDisplayControl != 2); return 0; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h index 3c7b67c35a0ce..c6eab47c60441 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h @@ -25,7 +25,6 @@ class GPUDisplayFrontendNone : public GPUDisplayFrontend GPUDisplayFrontendNone(); ~GPUDisplayFrontendNone() override = default; - void DisplayExit() override {} void SwitchFullscreen(bool set) override {} void ToggleMaximized(bool set) override {} void SetVSync(bool enable) override {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 5a42954c90fa7..987217f93f336 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -23,35 +23,29 @@ #include #include #include +#include #include -#include +#include +#include #include #include #include #include +#include #include #include #include "xdg-shell-client-protocol.h" #include "xdg-decoration-client-protocol.h" +#include "viewporter-client-protocol.h" +#include "cursor-shape-client-protocol.h" #include #include using namespace o2::gpu; -namespace o2::gpu::internal -{ -template -struct CCallWrapper { - std::function func; - static T callback(void* context, Args... args) - { - const CCallWrapper* funcwrap = reinterpret_cast(context); - return funcwrap->func(std::forward(args)...); - } -}; -} // namespace o2::gpu::internal +GPUDisplayFrontend* o2::gpu::createGPUDisplayFrontendWayland() { return new GPUDisplayFrontendWayland; } GPUDisplayFrontendWayland::GPUDisplayFrontendWayland() { @@ -68,9 +62,9 @@ int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) int32_t retVal = 0; if (mXKBkeymap) { xkb_keysym_t sym = xkb_state_key_get_one_sym(mXKBstate, key + 8); - if (sym == 65453) { + if (sym == XKB_KEY_KP_Subtract) { return ('-'); - } else if (sym == 65451) { + } else if (sym == XKB_KEY_KP_Add) { return ('+'); } else if (sym == XKB_KEY_Shift_L || sym == XKB_KEY_Shift_R) { return (KEY_SHIFT); @@ -142,37 +136,6 @@ int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) return retVal; } -void GPUDisplayFrontendWayland::createBuffer(uint32_t width, uint32_t height) -{ - const uint32_t stride = width * 4; - const uint32_t size = stride * height; - if (ftruncate(mFd, size) < 0) { - throw std::runtime_error("Error setting waysland shm file size"); - } - void* shm_data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, mFd, 0); - if (shm_data == MAP_FAILED) { - throw std::runtime_error("wayland mmap failed"); - } - memset(shm_data, 0, size); - munmap(shm_data, size); - - mPool = wl_shm_create_pool(mShm, mFd, size); - mBuffer = wl_shm_pool_create_buffer(mPool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888); - mDisplayWidth = width; - mDisplayHeight = height; - wl_surface_attach(mSurface, mBuffer, 0, 0); - wl_surface_commit(mSurface); -} - -void GPUDisplayFrontendWayland::recreateBuffer(uint32_t width, uint32_t height) -{ - wl_surface_attach(mSurface, nullptr, 0, 0); - wl_surface_commit(mSurface); - wl_buffer_destroy(mBuffer); - wl_shm_pool_destroy(mPool); - createBuffer(width, height); -} - int32_t GPUDisplayFrontendWayland::FrontendMain() { if (backend()->backendType() != GPUDisplayBackend::TYPE_VULKAN) { @@ -194,19 +157,23 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() throw std::runtime_error("Could not create wayland registry"); } - mFd = memfd_create("/tmp/ca-gpu-display-wayland-memfile", 0); - if (mFd < 0) { - throw std::runtime_error("Error creating wayland shm segment file descriptor"); - } - auto pointer_enter = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (!me->mCursorShapeDevice && me->mCursorShapeManager) { + me->mCursorShapeDevice = wp_cursor_shape_manager_v1_get_pointer(me->mCursorShapeManager, wl_pointer); + } + if (me->mCursorShapeDevice) { + wp_cursor_shape_device_v1_set_shape(me->mCursorShapeDevice, serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + } }; auto pointer_leave = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* wl_surface) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mMouseDn = me->mMouseDnR = me->mMouseDnM = false; }; auto pointer_motion = [](void* data, wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; - me->mMouseMvX = wl_fixed_to_double(surface_x); - me->mMouseMvY = wl_fixed_to_double(surface_y); + me->mMouseMvX = wl_fixed_to_double(surface_x) * me->mScale; + me->mMouseMvY = wl_fixed_to_double(surface_y) * me->mScale; }; auto pointer_button = [](void* data, wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; @@ -215,6 +182,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() me->mMouseDnR = true; } else if (button == BTN_LEFT) { me->mMouseDn = true; + } else if (button == BTN_MIDDLE) { + me->mMouseDnM = true; } me->mMouseDnX = me->mMouseMvX; me->mMouseDnY = me->mMouseMvY; @@ -223,19 +192,41 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() me->mMouseDnR = false; } else if (button == BTN_LEFT) { me->mMouseDn = false; + } else if (button == BTN_MIDDLE) { + me->mMouseDnM = false; } } }; + auto pointer_frame = [](void* data, wl_pointer* wl_pointer) { ((GPUDisplayFrontendWayland*)data)->applyScroll(); }; auto pointer_axis = [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { - if (axis == 0) { - ((GPUDisplayFrontendWayland*)data)->mMouseWheel += wl_fixed_to_double(value) * (-100.f / 15.f); + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + me->mAxisValue += wl_fixed_to_double(value); + if (me->mSeatVersion < WL_POINTER_FRAME_SINCE_VERSION) { + me->applyScroll(); + } + } + }; + auto pointer_axis_source = [](void* data, wl_pointer* wl_pointer, uint32_t axis_source) {}; + auto pointer_axis_stop = [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis) {}; + auto pointer_axis_discrete = [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) {}; +#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION + auto pointer_axis_value120 = [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t value120) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + me->mAxisValue120 += value120; + me->mHasAxisValue120 = true; } }; +#endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" - const wl_pointer_listener pointer_listener = {.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion, .button = pointer_button, .axis = pointer_axis, .frame = nullptr, .axis_source = nullptr, .axis_stop = nullptr, .axis_discrete = nullptr}; + mPointerListener = wl_pointer_listener{.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion, .button = pointer_button, .axis = pointer_axis, .frame = pointer_frame, .axis_source = pointer_axis_source, .axis_stop = pointer_axis_stop, .axis_discrete = pointer_axis_discrete, +#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION + .axis_value120 = pointer_axis_value120 +#endif + }; #pragma GCC diagnostic pop - auto keyboard_keymap = [](void* data, wl_keyboard* wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; if (me->mXKBkeymap) { @@ -249,86 +240,162 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() close(fd); }; auto keyboard_enter = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) {}; - auto keyboard_leave = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, wl_surface* surface) {}; + auto keyboard_leave = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, wl_surface* surface) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->focusLost(); + me->mRepeatKeyCode = -1; + }; auto keyboard_key = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; int32_t symbol = me->GetKey(key, state); int32_t keyPress = (symbol >= 'a' && symbol <= 'z') ? symbol + 'A' - 'a' : symbol; - if (state == XKB_KEY_DOWN) { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { me->mKeys[keyPress] = true; me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; me->HandleKey(symbol); + if (symbol && me->mRepeatRate > 0 && me->mXKBkeymap && xkb_keymap_key_repeats(me->mXKBkeymap, key + 8)) { // Wayland compositors do not repeat keys, the client must do it + me->mRepeatKeyCode = key; + me->mRepeatSymbol = symbol; + me->mRepeatNext = std::chrono::steady_clock::now() + std::chrono::milliseconds(me->mRepeatDelay); + } } else { me->mKeys[keyPress] = false; me->mKeysShift[keyPress] = false; + if ((int64_t)key == me->mRepeatKeyCode) { + me->mRepeatKeyCode = -1; + } } }; auto keyboard_modifiers = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; xkb_state_update_mask(me->mXKBstate, mods_depressed, mods_latched, mods_locked, 0, 0, group); }; - auto keyboard_repat = [](void* data, wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) {}; - const wl_keyboard_listener keyboard_listener = {.keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_modifiers, .repeat_info = keyboard_repat}; + auto keyboard_repat = [](void* data, wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mRepeatRate = rate; + me->mRepeatDelay = delay; + if (rate <= 0) { + me->mRepeatKeyCode = -1; + } + }; + mKeyboardListener = wl_keyboard_listener{.keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_modifiers, .repeat_info = keyboard_repat}; auto xdg_wm_base_ping = [](void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); }; - const xdg_wm_base_listener xdg_wm_base_listener = { + mXdgWmBaseListener = xdg_wm_base_listener{ .ping = xdg_wm_base_ping, }; - auto seat_capabilities = [&](struct wl_seat* seat, uint32_t capabilities) { - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - mPointer = wl_seat_get_pointer(mSeat); - wl_pointer_add_listener(mPointer, &pointer_listener, this); + auto seat_capabilities = [](void* data, struct wl_seat* seat, uint32_t capabilities) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !me->mPointer) { + me->mPointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(me->mPointer, &me->mPointerListener, me); } - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - mKeyboard = wl_seat_get_keyboard(mSeat); - wl_keyboard_add_listener(mKeyboard, &keyboard_listener, this); + if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !me->mKeyboard) { + me->mKeyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(me->mKeyboard, &me->mKeyboardListener, me); } }; - auto seat_capabilities_c = internal::CCallWrapper{[seat_capabilities](wl_seat* seat, uint32_t capabilities) { seat_capabilities(seat, capabilities); }}; - auto seat_name = [](void* data, struct wl_seat* seat, const char* name) { if (((GPUDisplayFrontendWayland*)data)->mDisplay->GetProcessingSettings().debugLevel >= 2) { GPUInfo("Wayland seat: %s", name); } }; - const wl_seat_listener seat_listener = { - .capabilities = seat_capabilities_c.callback, + mSeatListener = wl_seat_listener{ + .capabilities = seat_capabilities, .name = seat_name, }; - auto registry_global = [&](wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - if (mDisplay->GetProcessingSettings().debugLevel >= 3) { - GPUInfo("Available interface %s", interface); + auto output_geometry = [](void* data, wl_output* output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform) {}; + auto output_mode = [](void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {}; + auto output_done = [](void* data, wl_output* output) {}; + auto output_scale = [](void* data, wl_output* output, int32_t factor) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (auto* o = me->findOutput(output)) { + o->scale = factor; + me->updateScale(); + } + }; +#ifdef WL_OUTPUT_NAME_SINCE_VERSION + auto output_name = [](void* data, wl_output* output, const char* name) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (auto* o = me->findOutput(output)) { + o->name = name; + } + if (me->mDisplay->GetProcessingSettings().debugLevel >= 2) { + GPUInfo("Wayland output: %s", name); } + }; + auto output_description = [](void* data, wl_output* output, const char* description) {}; +#endif + mOutputListener = wl_output_listener{ + .geometry = output_geometry, + .mode = output_mode, + .done = output_done, + .scale = output_scale, +#ifdef WL_OUTPUT_NAME_SINCE_VERSION + .name = output_name, + .description = output_description, +#endif + }; + + auto registry_global = [](void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (me->mDisplay->GetProcessingSettings().debugLevel >= 3) { + GPUInfo("Available interface %s (version %u)", interface, version); + } + auto bindVersion = [version](const wl_interface& iface, uint32_t maxVersion) { return std::min({version, (uint32_t)iface.version, maxVersion}); }; // Highest version supported by compositor, generated headers, and this code if (strcmp(interface, wl_output_interface.name) == 0) { - mOutput = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 1); - // wl_output_add_listener(mOutput, &output_listener, this); + uint32_t v = bindVersion(wl_output_interface, 4); + wl_output* output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, v); + me->mOutputs.emplace_back(OutputInfo{output, name, v, 1, ""}); + wl_output_add_listener(output, &me->mOutputListener, me); } else if (strcmp(interface, wl_compositor_interface.name) == 0) { - mCompositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, 1); - } else if (strcmp(interface, wl_shm_interface.name) == 0) { - mShm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, 1); + me->mCompositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, bindVersion(wl_compositor_interface, 6)); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - mXdgBase = (xdg_wm_base*)wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(mXdgBase, &xdg_wm_base_listener, this); - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - mSeat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, 1); - wl_seat_add_listener(mSeat, &seat_listener, &seat_capabilities_c); - } else if (strcmp(interface, zxdg_toplevel_decoration_v1_interface.name) == 0) { - mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_toplevel_decoration_v1_interface, 1); + me->mXdgBase = (xdg_wm_base*)wl_registry_bind(registry, name, &xdg_wm_base_interface, bindVersion(xdg_wm_base_interface, 6)); + xdg_wm_base_add_listener(me->mXdgBase, &me->mXdgWmBaseListener, me); + } else if (strcmp(interface, wl_seat_interface.name) == 0 && !me->mSeat) { + me->mSeatVersion = bindVersion(wl_seat_interface, 8); + me->mSeat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, me->mSeatVersion); + wl_seat_add_listener(me->mSeat, &me->mSeatListener, me); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + me->mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + me->mCursorShapeManager = (wp_cursor_shape_manager_v1*)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + me->mViewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, 1); + } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + me->mFractionalScaleManager = (wp_fractional_scale_manager_v1*)wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1); } }; + auto registry_global_remove = [](void* data, wl_registry* registry, uint32_t name) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + for (auto it = me->mOutputs.begin(); it != me->mOutputs.end(); it++) { + if (it->globalName == name) { + std::erase(me->mSurfaceOutputs, it->output); + if (it->version >= WL_OUTPUT_RELEASE_SINCE_VERSION) { + wl_output_release(it->output); + } else { + wl_output_destroy(it->output); + } + me->mOutputs.erase(it); + me->updateScale(); + break; + } + } + }; + mRegistryListener = wl_registry_listener{.global = registry_global, .global_remove = registry_global_remove}; - auto registry_global_c = internal::CCallWrapper{[registry_global](wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { registry_global(registry, name, interface, version); }}; - auto registry_global_remove = [](void* a, wl_registry* b, uint32_t c) {}; - const wl_registry_listener registry_listener = {.global = ®istry_global_c.callback, .global_remove = registry_global_remove}; - - wl_registry_add_listener(mRegistry, ®istry_listener, ®istry_global_c); - wl_display_roundtrip(mWayland); + wl_registry_add_listener(mRegistry, &mRegistryListener, this); + if (wl_display_roundtrip(mWayland) == -1) { + reportError("initial roundtrip"); + throw std::runtime_error("Wayland initialization failed"); + } - if (mCompositor == nullptr || mShm == nullptr || mXdgBase == nullptr || mSeat == nullptr || mOutput == nullptr) { + if (mCompositor == nullptr || mXdgBase == nullptr || mSeat == nullptr) { throw std::runtime_error("Error getting wayland objects"); } @@ -336,6 +403,47 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() if (mSurface == nullptr) { throw std::runtime_error("Error creating wayland surface"); } + + auto surface_enter = [](void* data, wl_surface* surface, wl_output* output) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mSurfaceOutputs.emplace_back(output); + me->updateScale(); + }; + auto surface_leave = [](void* data, wl_surface* surface, wl_output* output) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + std::erase(me->mSurfaceOutputs, output); + me->updateScale(); + }; +#ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION + auto surface_preferred_buffer_scale = [](void* data, wl_surface* surface, int32_t factor) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mPreferredScale = factor; + me->updateScale(); + }; + auto surface_preferred_buffer_transform = [](void* data, wl_surface* surface, uint32_t transform) {}; +#endif + mSurfaceListener = wl_surface_listener{ + .enter = surface_enter, + .leave = surface_leave, +#ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION + .preferred_buffer_scale = surface_preferred_buffer_scale, + .preferred_buffer_transform = surface_preferred_buffer_transform, +#endif + }; + wl_surface_add_listener(mSurface, &mSurfaceListener, this); + + if (mViewporter && mFractionalScaleManager) { + auto fractional_scale_preferred_scale = [](void* data, wp_fractional_scale_v1* fractional_scale, uint32_t scale) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mFractionalScale120 = scale; + me->updateScale(); + }; + mFractionalScaleListener = wp_fractional_scale_v1_listener{.preferred_scale = fractional_scale_preferred_scale}; + mViewport = wp_viewporter_get_viewport(mViewporter, mSurface); + mFractionalScale = wp_fractional_scale_manager_v1_get_fractional_scale(mFractionalScaleManager, mSurface); + wp_fractional_scale_v1_add_listener(mFractionalScale, &mFractionalScaleListener, this); + } + mXdgSurface = xdg_wm_base_get_xdg_surface(mXdgBase, mSurface); if (mXdgSurface == nullptr) { throw std::runtime_error("Error creating wayland xdg surface"); @@ -349,59 +457,145 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } me->mWidthRequested = width; me->mHeightRequested = height; + bool fullScreen = false, maximized = false; + me->mSuspended = false; + const uint32_t* state = (const uint32_t*)states->data; + for (size_t i = 0; i < states->size / sizeof(uint32_t); i++) { + if (state[i] == XDG_TOPLEVEL_STATE_FULLSCREEN) { + fullScreen = true; + } else if (state[i] == XDG_TOPLEVEL_STATE_MAXIMIZED) { + maximized = true; +#ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION + } else if (state[i] == XDG_TOPLEVEL_STATE_SUSPENDED) { + me->mSuspended = true; +#endif + } + } + me->setWindowState(fullScreen, maximized); + me->mStateFullScreenOrMaximized = fullScreen || maximized; }; auto xdg_surface_handle_configure = [](void* data, xdg_surface* surface, uint32_t serial) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; xdg_surface_ack_configure(me->mXdgSurface, serial); - if (me->mWidthRequested && me->mHeightRequested && (me->mWidthRequested != me->mDisplayWidth || me->mHeightRequested != me->mDisplayHeight)) { - me->recreateBuffer(me->mWidthRequested, me->mHeightRequested); - me->ResizeScene(me->mDisplayWidth, me->mDisplayHeight); - } else { - wl_surface_commit(((GPUDisplayFrontendWayland*)data)->mSurface); + if (me->mWidthRequested && me->mHeightRequested) { + me->mWidthLogical = me->mWidthRequested; + me->mHeightLogical = me->mHeightRequested; + if (!me->mStateFullScreenOrMaximized) { + me->mWidthWindowed = me->mWidthRequested; + me->mHeightWindowed = me->mHeightRequested; + } + } else if (!me->mStateFullScreenOrMaximized) { + me->mWidthLogical = me->mWidthWindowed; + me->mHeightLogical = me->mHeightWindowed; + if (me->mBoundsWidth && me->mBoundsHeight) { + me->mWidthLogical = std::min(me->mWidthLogical, me->mBoundsWidth); + me->mHeightLogical = std::min(me->mHeightLogical, me->mBoundsHeight); + } } + me->updateSize(); me->mWidthRequested = me->mHeightRequested = 0; }; auto xdg_toplevel_handle_close = [](void* data, xdg_toplevel* toplevel) { ((GPUDisplayFrontendWayland*)data)->mDisplayControl = 2; }; +#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION + auto xdg_toplevel_handle_configure_bounds = [](void* data, xdg_toplevel* toplevel, int32_t width, int32_t height) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mBoundsWidth = width; + me->mBoundsHeight = height; + }; +#endif +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + auto xdg_toplevel_handle_wm_capabilities = [](void* data, xdg_toplevel* toplevel, wl_array* capabilities) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mCanFullscreen = me->mCanMaximize = false; + const uint32_t* cap = (const uint32_t*)capabilities->data; + for (size_t i = 0; i < capabilities->size / sizeof(uint32_t); i++) { + me->mCanFullscreen |= cap[i] == XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN; + me->mCanMaximize |= cap[i] == XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE; + } + }; +#endif -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" - const xdg_surface_listener xdg_surface_listener = { + mXdgSurfaceListener = xdg_surface_listener{ .configure = xdg_surface_handle_configure, }; - const xdg_toplevel_listener xdg_toplevel_listener = { + mXdgToplevelListener = xdg_toplevel_listener{ .configure = xdg_toplevel_handle_configure, .close = xdg_toplevel_handle_close, - .configure_bounds = nullptr}; -#pragma GCC diagnostic pop +#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION + .configure_bounds = xdg_toplevel_handle_configure_bounds, +#endif +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + .wm_capabilities = xdg_toplevel_handle_wm_capabilities, +#endif + }; - xdg_surface_add_listener(mXdgSurface, &xdg_surface_listener, this); - xdg_toplevel_add_listener(mXdgToplevel, &xdg_toplevel_listener, this); + xdg_surface_add_listener(mXdgSurface, &mXdgSurfaceListener, this); + xdg_toplevel_add_listener(mXdgToplevel, &mXdgToplevelListener, this); xdg_toplevel_set_title(mXdgToplevel, DISPLAY_WINDOW_NAME); - - if (mDecManager) { - printf("Enabling decoration\n"); + xdg_toplevel_set_min_size(mXdgToplevel, MIN_WIDTH, MIN_HEIGHT); // Logical size, avoids degenerate swapchains / projections + xdg_toplevel_set_app_id(mXdgToplevel, "o2-gpu-event-display"); // Used by the compositor for window grouping, window rules, and matching a .desktop file + applyInitialWindowState(); // Must be requested before the first commit + + if (mDecManager) { // Must be requested before the first commit. Without the protocol (e.g. GNOME), the window has no decorations + mXdgDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(mDecManager, mXdgToplevel); + zxdg_toplevel_decoration_v1_set_mode(mXdgDecoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } else if (mDisplay->GetProcessingSettings().debugLevel >= 0) { + GPUInfo("Wayland compositor does not support server-side decorations"); } - wl_surface_commit(mSurface); - wl_display_roundtrip(mWayland); - - createBuffer(INIT_WIDTH, INIT_HEIGHT); + wl_surface_commit(mSurface); // Initial commit without buffer, the compositor answers with the first configure + if (wl_display_roundtrip(mWayland) == -1) { + reportError("initial roundtrip"); + throw std::runtime_error("Wayland initialization failed"); + } if (InitDisplay()) { return (1); } - while (wl_display_dispatch(mWayland) != -1 && mDisplayControl != 2) { - HandleSendKey(); - DrawGLScene(); - wl_surface_damage(mSurface, 0, 0, mDisplayWidth, mDisplayHeight); - usleep(10000); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } + + while (mDisplayControl != 2) { + bool error = false; + while (!error && wl_display_prepare_read(mWayland) != 0) { + error = wl_display_dispatch_pending(mWayland) == -1; + } + if (error) { + reportError("dispatching events"); + break; + } + wl_display_flush(mWayland); + pollfd pfd = {wl_display_get_fd(mWayland), POLLIN, 0}; + if (poll(&pfd, 1, 0) > 0) { + if (wl_display_read_events(mWayland) == -1) { + reportError("reading events"); + break; + } + } else { + wl_display_cancel_read(mWayland); + } + if (wl_display_dispatch_pending(mWayland) == -1) { + reportError("dispatching events"); + break; + } + if (mDisplayControl == 2) { + break; + } + HandleSendKeys(); + handleKeyRepeat(); + if (!mSuspended) { // Window not visible, compositor would not release swapchain images + DrawGLScene(); + } + waitForFPSLimit(); } ExitDisplay(); @@ -413,50 +607,85 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } xkb_context_unref(mXKBcontext); - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = true; - pthread_mutex_unlock(&mSemLockExit); - - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = false; + } + if (mCursorShapeDevice) { + wp_cursor_shape_device_v1_destroy(mCursorShapeDevice); + mCursorShapeDevice = nullptr; + } + if (mCursorShapeManager) { + wp_cursor_shape_manager_v1_destroy(mCursorShapeManager); + mCursorShapeManager = nullptr; + } if (mPointer) { - wl_pointer_release(mPointer); + mSeatVersion >= WL_POINTER_RELEASE_SINCE_VERSION ? wl_pointer_release(mPointer) : wl_pointer_destroy(mPointer); mPointer = nullptr; } if (mKeyboard) { - wl_keyboard_release(mKeyboard); + mSeatVersion >= WL_KEYBOARD_RELEASE_SINCE_VERSION ? wl_keyboard_release(mKeyboard) : wl_keyboard_destroy(mKeyboard); mKeyboard = nullptr; } + if (mXdgDecoration) { + zxdg_toplevel_decoration_v1_destroy(mXdgDecoration); + mXdgDecoration = nullptr; + } + if (mFractionalScale) { + wp_fractional_scale_v1_destroy(mFractionalScale); + mFractionalScale = nullptr; + } + if (mViewport) { + wp_viewport_destroy(mViewport); + mViewport = nullptr; + } xdg_toplevel_destroy(mXdgToplevel); xdg_surface_destroy(mXdgSurface); wl_surface_destroy(mSurface); - wl_buffer_destroy(mBuffer); - wl_shm_pool_destroy(mPool); + if (mFractionalScaleManager) { + wp_fractional_scale_manager_v1_destroy(mFractionalScaleManager); + mFractionalScaleManager = nullptr; + } + if (mViewporter) { + wp_viewporter_destroy(mViewporter); + mViewporter = nullptr; + } + if (mDecManager) { + zxdg_decoration_manager_v1_destroy(mDecManager); + mDecManager = nullptr; + } + for (auto& o : mOutputs) { + if (o.version >= WL_OUTPUT_RELEASE_SINCE_VERSION) { + wl_output_release(o.output); + } else { + wl_output_destroy(o.output); + } + } + mOutputs.clear(); + mSurfaceOutputs.clear(); + if (mSeat) { + if (mSeatVersion >= WL_SEAT_RELEASE_SINCE_VERSION) { + wl_seat_release(mSeat); + } else { + wl_seat_destroy(mSeat); + } + mSeat = nullptr; + } + xdg_wm_base_destroy(mXdgBase); + wl_compositor_destroy(mCompositor); wl_registry_destroy(mRegistry); wl_display_disconnect(mWayland); - close(mFd); return (0); } -void GPUDisplayFrontendWayland::DisplayExit() -{ - pthread_mutex_lock(&mSemLockExit); - if (mDisplayRunning) { - mDisplayControl = 2; - } - pthread_mutex_unlock(&mSemLockExit); - while (mDisplayRunning) { - usleep(10000); - } -} - void GPUDisplayFrontendWayland::SwitchFullscreen(bool set) { - if (set) { - xdg_toplevel_set_fullscreen(mXdgToplevel, mOutput); + if (!mCanFullscreen) { + GPUWarning("Wayland compositor does not support fullscreen"); + } else if (set) { + xdg_toplevel_set_fullscreen(mXdgToplevel, mSurfaceOutputs.size() ? mSurfaceOutputs.back() : nullptr); } else { xdg_toplevel_unset_fullscreen(mXdgToplevel); } @@ -464,13 +693,97 @@ void GPUDisplayFrontendWayland::SwitchFullscreen(bool set) void GPUDisplayFrontendWayland::ToggleMaximized(bool set) { - if (set) { + if (!mCanMaximize) { + GPUWarning("Wayland compositor does not support maximizing windows"); + } else if (set) { xdg_toplevel_set_maximized(mXdgToplevel); } else { xdg_toplevel_unset_maximized(mXdgToplevel); } } +GPUDisplayFrontendWayland::OutputInfo* GPUDisplayFrontendWayland::findOutput(wl_output* output) +{ + auto it = std::find_if(mOutputs.begin(), mOutputs.end(), [output](const auto& o) { return o.output == output; }); + return it == mOutputs.end() ? nullptr : &*it; +} + +void GPUDisplayFrontendWayland::updateScale() +{ + double scale; + if (mFractionalScale) { + scale = mFractionalScale120 ? mFractionalScale120 / 120. : 1.; + } else { + int32_t intScale = mPreferredScale; + if (intScale == 0) { + intScale = 1; + for (auto* output : mSurfaceOutputs) { + if (auto* o = findOutput(output)) { + intScale = std::max(intScale, o->scale); + } + } + } + scale = intScale; + } + if (scale != mScale && mSurface) { + mScale = scale; + if (!mFractionalScale) { + wl_surface_set_buffer_scale(mSurface, (int32_t)mScale); // Applied with the next commit, i.e. the next present, which happens after the swapchain was recreated with the new size by ResizeScene + } + updateSize(); + } +} + +void GPUDisplayFrontendWayland::updateSize() +{ + if (mViewport) { + wp_viewport_set_destination(mViewport, mWidthLogical, mHeightLogical); // Surface size in logical coordinates, independent of the buffer size, applied with the next present + } + const int32_t width = mFractionalScale ? (int32_t)std::lround(mWidthLogical * mScale) : (mWidthLogical * (int32_t)mScale); + const int32_t height = mFractionalScale ? (int32_t)std::lround(mHeightLogical * mScale) : (mHeightLogical * (int32_t)mScale); + if (width != mDisplayWidth || height != mDisplayHeight) { + if (mDisplayRunning) { + ResizeScene(width, height); // Swapchain is recreated by the backend, Vulkan WSI commits the new buffer + } else { + mDisplayWidth = width; // Initial size, used by InitDisplay via getSize() + mDisplayHeight = height; + } + } +} + +void GPUDisplayFrontendWayland::reportError(const char* where) +{ + const int32_t err = wl_display_get_error(mWayland); + if (err == EPROTO) { + const wl_interface* iface = nullptr; + uint32_t id = 0; + const uint32_t code = wl_display_get_protocol_error(mWayland, &iface, &id); + GPUError("Wayland protocol error %u on %s@%u while %s", code, iface ? iface->name : "unknown interface", id, where); + } else { + GPUError("Wayland error while %s: %s", where, strerror(err)); + } +} + +void GPUDisplayFrontendWayland::handleKeyRepeat() +{ + if (mRepeatKeyCode < 0) { + return; + } + const auto now = std::chrono::steady_clock::now(); + while (now >= mRepeatNext && mRepeatKeyCode >= 0) { + HandleKey(mRepeatSymbol); + mRepeatNext += std::chrono::microseconds(1000000 / mRepeatRate); + } +} + +void GPUDisplayFrontendWayland::applyScroll() +{ + mMouseWheel += mHasAxisValue120 ? (mAxisValue120 * -100 / 120) : (int32_t)(mAxisValue * (-100. / 15.)); + mAxisValue = 0; + mAxisValue120 = 0; + mHasAxisValue120 = false; +} + void GPUDisplayFrontendWayland::SetVSync(bool enable) { } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 55676c694cfef..61c95907d500d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -16,13 +16,17 @@ #define GPUDISPLAYFRONTENDWAYLAND_H #include "GPUDisplayFrontend.h" -#include #include +#include "xdg-shell-client-protocol.h" +#include "fractional-scale-client-protocol.h" +#include +#include -struct xdg_wm_base; -struct xdg_toplevel; -struct xdg_surface; struct zxdg_decoration_manager_v1; +struct wp_viewporter; +struct wp_viewport; +struct wp_cursor_shape_manager_v1; +struct wp_cursor_shape_device_v1; struct zxdg_toplevel_decoration_v1; struct xkb_context; struct xkb_keymap; @@ -36,23 +40,32 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend GPUDisplayFrontendWayland(); ~GPUDisplayFrontendWayland() override = default; - void DisplayExit() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; + float getDisplayScale() const override { return mScale; } int32_t getVulkanSurface(void* instance, void* surface) override; uint32_t getReqVulkanExtensions(const char**& p) override; private: int32_t FrontendMain() override; int32_t GetKey(uint32_t key, uint32_t state); - void createBuffer(uint32_t width, uint32_t height); - void recreateBuffer(uint32_t width, uint32_t height); - - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - volatile bool mDisplayRunning = false; + void updateScale(); // Scale from wp_fractional_scale_v1, else integer wl_surface.preferred_buffer_scale, or max scale of the outputs the surface is on (older compositors) + void updateSize(); // Physical size = logical size * scale, resizes the scene if changed + void reportError(const char* where); // Print the Wayland display error (protocol error with interface and object, or errno) after a call returned -1 + void handleKeyRepeat(); // Call HandleKey for the held key according to wl_keyboard.repeat_info + void applyScroll(); // Apply scroll events of one wl_pointer.frame: high-resolution wheel (120 = 1 notch = 100 mMouseWheel units) if available, otherwise continuous axis (touchpad, ~15 units per notch) + + struct OutputInfo { + wl_output* output; + uint32_t globalName; + uint32_t version; + int32_t scale = 1; + std::string name; + }; + OutputInfo* findOutput(wl_output* output); wl_display* mWayland = nullptr; wl_registry* mRegistry = nullptr; @@ -62,28 +75,67 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend wl_pointer* mPointer = nullptr; wl_keyboard* mKeyboard = nullptr; xdg_wm_base* mXdgBase = nullptr; - wl_shm* mShm = nullptr; wl_surface* mSurface = nullptr; xdg_surface* mXdgSurface = nullptr; xdg_toplevel* mXdgToplevel = nullptr; - wl_output* mOutput = nullptr; - - int32_t mFd = 0; - wl_shm_pool* mPool; - wl_buffer* mBuffer; + wl_registry_listener mRegistryListener{}; + wl_seat_listener mSeatListener{}; + wl_pointer_listener mPointerListener{}; + wl_keyboard_listener mKeyboardListener{}; + wl_output_listener mOutputListener{}; + wl_surface_listener mSurfaceListener{}; + xdg_wm_base_listener mXdgWmBaseListener{}; + xdg_surface_listener mXdgSurfaceListener{}; + xdg_toplevel_listener mXdgToplevelListener{}; + wp_fractional_scale_v1_listener mFractionalScaleListener{}; + + std::vector mOutputs; + std::vector mSurfaceOutputs; // Outputs the surface is on, most recently entered last + uint32_t mSeatVersion = 0; xkb_context* mXKBcontext = nullptr; xkb_keymap* mXKBkeymap = nullptr; xkb_state* mXKBstate = nullptr; zxdg_decoration_manager_v1* mDecManager = nullptr; - // zxdg_toplevel_decoration_v1* mXdgDecoration = nullptr; + zxdg_toplevel_decoration_v1* mXdgDecoration = nullptr; + + wp_viewporter* mViewporter = nullptr; // Fractional scaling (if both are available): buffer scale 1, buffer at physical size, viewport destination = logical size + wp_fractional_scale_manager_v1* mFractionalScaleManager = nullptr; + wp_cursor_shape_manager_v1* mCursorShapeManager = nullptr; // Sets the default cursor on pointer enter, otherwise the cursor image is undefined + wp_cursor_shape_device_v1* mCursorShapeDevice = nullptr; + wp_viewport* mViewport = nullptr; + wp_fractional_scale_v1* mFractionalScale = nullptr; int32_t mWidthRequested = 0; int32_t mHeightRequested = 0; + int32_t mWidthLogical = INIT_WIDTH; + int32_t mHeightLogical = INIT_HEIGHT; + int32_t mWidthWindowed = INIT_WIDTH; // Last size when neither maximized nor fullscreen + int32_t mHeightWindowed = INIT_HEIGHT; + bool mStateFullScreenOrMaximized = false; + int32_t mBoundsWidth = 0; + int32_t mBoundsHeight = 0; + double mScale = 1; // Physical pixels per logical pixel + int32_t mPreferredScale = 0; // Integer scale from wl_surface.preferred_buffer_scale, 0 = not sent by compositor + uint32_t mFractionalScale120 = 0; // Scale * 120 from wp_fractional_scale_v1, 0 = not sent yet + bool mSuspended = false; + bool mCanFullscreen = true; + bool mCanMaximize = true; + + int32_t mRepeatRate = 0; // Keys per second, 0 = no repeat + int32_t mRepeatDelay = 0; // ms before the first repeat + int64_t mRepeatKeyCode = -1; // Evdev key code of the held key to repeat, -1 = none + int32_t mRepeatSymbol = 0; + std::chrono::steady_clock::time_point mRepeatNext; + + double mAxisValue = 0; // Accumulated scroll values of one wl_pointer.frame + int32_t mAxisValue120 = 0; + bool mHasAxisValue120 = false; }; +GPUDisplayFrontend* createGPUDisplayFrontendWayland(); } // namespace o2::gpu #endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index 30148e0cb00ee..14a65e6634476 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -359,6 +359,7 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() } else { DrawGLScene(); // Draw The Scene SwapBuffers(hDC); // Swap Buffers (Double Buffering) + waitForFPSLimit(); } } } @@ -370,7 +371,6 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() return (0); } -void DisplayExit() {} void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) {} void SwitchFullscreen(bool set) {} void ToggleMaximized(bool set) {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h index cac5b62c4cc63..e28a9f4ced75b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h @@ -25,7 +25,6 @@ class GPUDisplayFrontendWindows : public GPUDisplayFrontend GPUDisplayFrontendWindows(); ~GPUDisplayFrontendWindows() override = default; - void DisplayExit() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index be56fc8a1e546..e832ee88bd751 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -21,9 +21,10 @@ #include #include #include -#include #include +#include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_VULKAN #include @@ -195,20 +196,33 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t eventBase; // Open a connection to the X server - mDisplay = XOpenDisplay(nullptr); + mXDisplay = XOpenDisplay(nullptr); - if (mDisplay == nullptr) { + if (mXDisplay == nullptr) { GPUError("could not open display"); return (-1); } + XrmInitialize(); + if (const char* resources = XResourceManagerString(mXDisplay)) { + XrmDatabase db = XrmGetStringDatabase(resources); + char* type = nullptr; + XrmValue value; + if (db && XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && value.addr && atof(value.addr) > 0) { + mScale = atof(value.addr) / 96.; + } + if (db) { + XrmDestroyDatabase(db); + } + } + // Make sure OpenGL's GLX extension supported - if (!glXQueryExtension(mDisplay, &errorBase, &eventBase)) { + if (!glXQueryExtension(mXDisplay, &errorBase, &eventBase)) { GPUError("X server has no OpenGL GLX extension"); return (-1); } - const char* glxExt = glXQueryExtensionsString(mDisplay, DefaultScreen(mDisplay)); + const char* glxExt = glXQueryExtensionsString(mXDisplay, DefaultScreen(mXDisplay)); if (strstr(glxExt, "GLX_EXT_swap_control") == nullptr) { GPUError("No vsync support!"); vsync_supported = false; @@ -224,14 +238,14 @@ int32_t GPUDisplayFrontendX11::FrontendMain() GLXFBConfig fbconfig = nullptr; int32_t fbcount; - GLXFBConfig* fbc = glXChooseFBConfig(mDisplay, DefaultScreen(mDisplay), attribs, &fbcount); + GLXFBConfig* fbc = glXChooseFBConfig(mXDisplay, DefaultScreen(mXDisplay), attribs, &fbcount); if (fbc == nullptr || fbcount == 0) { GPUError("Failed to get MSAA GLXFBConfig"); return (-1); } fbconfig = fbc[0]; XFree(fbc); - visualInfo = glXGetVisualFromFBConfig(mDisplay, fbconfig); + visualInfo = glXGetVisualFromFBConfig(mXDisplay, fbconfig); if (visualInfo == nullptr) { GPUError("no RGB visual with depth buffer"); @@ -247,9 +261,9 @@ int32_t GPUDisplayFrontendX11::FrontendMain() GLX_CONTEXT_MINOR_VERSION_ARB, GL_MIN_VERSION_MINOR, GLX_CONTEXT_PROFILE_MASK_ARB, mBackend->CoreProfile() ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB : GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, None}; - glxContext = glXCreateContextAttribsARB(mDisplay, fbconfig, nullptr, GL_TRUE, context_attribs); + glxContext = glXCreateContextAttribsARB(mXDisplay, fbconfig, nullptr, GL_TRUE, context_attribs); } else { - glxContext = glXCreateContext(mDisplay, visualInfo, nullptr, GL_TRUE); + glxContext = glXCreateContext(mXDisplay, visualInfo, nullptr, GL_TRUE); } if (glxContext == nullptr) { GPUError("could not create rendering context"); @@ -257,27 +271,31 @@ int32_t GPUDisplayFrontendX11::FrontendMain() } } - Window win = RootWindow(mDisplay, visualInfo->screen); - colorMap = XCreateColormap(mDisplay, win, visualInfo->visual, AllocNone); + Window win = RootWindow(mXDisplay, visualInfo->screen); + colorMap = XCreateColormap(mXDisplay, win, visualInfo->visual, AllocNone); windowAttributes.colormap = colorMap; windowAttributes.border_pixel = 0; - windowAttributes.event_mask = ExposureMask | VisibilityChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | SubstructureNotifyMask | FocusChangeMask; + windowAttributes.event_mask = ExposureMask | VisibilityChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | SubstructureNotifyMask | FocusChangeMask | PropertyChangeMask; // Create an X window with the selected visual - mWindow = XCreateWindow(mDisplay, win, 50, 50, INIT_WIDTH, INIT_HEIGHT, // Position / Width and height of window + mWindow = XCreateWindow(mXDisplay, win, 50, 50, INIT_WIDTH * mScale, INIT_HEIGHT * mScale, // Position / Width and height of window 0, visualInfo->depth, InputOutput, visualInfo->visual, CWBorderPixel | CWColormap | CWEventMask, &windowAttributes); - XSetStandardProperties(mDisplay, mWindow, DISPLAY_WINDOW_NAME, DISPLAY_WINDOW_NAME, None, nullptr, 0, nullptr); + XSetStandardProperties(mXDisplay, mWindow, DISPLAY_WINDOW_NAME, DISPLAY_WINDOW_NAME, None, nullptr, 0, nullptr); + XSizeHints sizeHints{}; + sizeHints.flags = PMinSize; + sizeHints.min_width = MIN_WIDTH * mScale; + sizeHints.min_height = MIN_HEIGHT * mScale; + XSetWMNormalHints(mXDisplay, mWindow, &sizeHints); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glXMakeCurrent(mDisplay, mWindow, glxContext); + glXMakeCurrent(mXDisplay, mWindow, glxContext); } - XMapWindow(mDisplay, mWindow); + XMapWindow(mXDisplay, mWindow); - // Maximize window - ToggleMaximized(true); + applyInitialWindowState(); // Receive signal when window closed - Atom WM_DELETE_WINDOW = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False); - XSetWMProtocols(mDisplay, mWindow, &WM_DELETE_WINDOW, 1); + Atom WM_DELETE_WINDOW = XInternAtom(mXDisplay, "WM_DELETE_WINDOW", False); + XSetWMProtocols(mXDisplay, mWindow, &WM_DELETE_WINDOW, 1); #ifndef GPUCA_DISPLAY_OPENGL_CORE XFontStruct* font_info = nullptr; if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { @@ -288,7 +306,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (-1); } const char* f = "fixed"; - font_info = XLoadQueryFont(mDisplay, f); + font_info = XLoadQueryFont(mXDisplay, f); if (!font_info) { GPUError("XLoadQueryFont failed."); return (-1); @@ -309,9 +327,8 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (-1); } - XMapWindow(mDisplay, mWindow); - XFlush(mDisplay); - int32_t x11_fd = ConnectionNumber(mDisplay); + XMapWindow(mXDisplay, mWindow); + XFlush(mXDisplay); // Enable vsync if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL && vsync_supported) { @@ -320,56 +337,28 @@ int32_t GPUDisplayFrontendX11::FrontendMain() GPUError("Cannot enable vsync"); return (-1); } - mGlXSwapIntervalEXT(mDisplay, glXGetCurrentDrawable(), 1); + mGlXSwapIntervalEXT(mXDisplay, glXGetCurrentDrawable(), 1); } if (InitDisplay()) { return (1); } - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = true; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } - std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); while (1) { - int32_t num_ready_fds; - struct timeval tv; - fd_set in_fds; - int32_t waitCount = 0; do { - FD_ZERO(&in_fds); - FD_SET(x11_fd, &in_fds); - tv.tv_usec = 10000; - tv.tv_sec = 0; - std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now(); - bool allowMax = mMaxFPSRate && std::chrono::duration_cast>(t2 - t1).count() < 0.01; - t1 = t2; - num_ready_fds = allowMax || XPending(mDisplay) || select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv); - if (num_ready_fds < 0) { - GPUError("Error (num_ready_fds)"); - } if (mDisplayControl == 2) { break; } - if (mSendKey) { - mNeedUpdate = 1; - } - if (waitCount++ != 100) { - mNeedUpdate = 1; - } - } while (!(num_ready_fds || mNeedUpdate)); - mNeedUpdate = 0; - - do { - if (mDisplayControl == 2) { - break; - } - HandleSendKey(); - if (!XPending(mDisplay)) { + HandleSendKeys(); + if (!XPending(mXDisplay)) { event.type = Expose; } else { - XNextEvent(mDisplay, &event); + XNextEvent(mXDisplay, &event); } switch (event.type) { case ButtonPress: { @@ -380,6 +369,8 @@ int32_t GPUDisplayFrontendX11::FrontendMain() } else { if (event.xbutton.button == 1) { mMouseDn = true; + } else if (event.xbutton.button == 2) { + mMouseDnM = true; } else { mMouseDnR = true; } @@ -393,6 +384,8 @@ int32_t GPUDisplayFrontendX11::FrontendMain() if (event.xbutton.button != 4 && event.xbutton.button != 5) { if (event.xbutton.button == 1) { mMouseDn = false; + } else if (event.xbutton.button == 2) { + mMouseDnM = false; } else { mMouseDnR = false; } @@ -423,6 +416,18 @@ int32_t GPUDisplayFrontendX11::FrontendMain() break; } + case FocusOut: { + focusLost(); + break; + } + + case PropertyNotify: { + if (event.xproperty.atom == XInternAtom(mXDisplay, "_NET_WM_STATE", False)) { + updateWindowState(); + } + break; + } + case Expose: { break; } @@ -433,56 +438,67 @@ int32_t GPUDisplayFrontendX11::FrontendMain() } case ClientMessage: { - if (event.xclient.message_type == XInternAtom(mDisplay, "_NET_WM_STATE", False)) { - XFlush(mDisplay); + if (event.xclient.message_type == XInternAtom(mXDisplay, "_NET_WM_STATE", False)) { + XFlush(mXDisplay); } else { mDisplayControl = 2; } break; } } - } while (XPending(mDisplay)); // Loop to compress events + } while (XPending(mXDisplay)); // Loop to compress events if (mDisplayControl == 2) { break; } DrawGLScene(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glXSwapBuffers(mDisplay, mWindow); + glXSwapBuffers(mXDisplay, mWindow); } + waitForFPSLimit(); } #ifndef GPUCA_DISPLAY_OPENGL_CORE if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glDeleteLists(mFontBase, 256); - XUnloadFont(mDisplay, font_info->fid); + XUnloadFont(mXDisplay, font_info->fid); } #endif ExitDisplay(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glXDestroyContext(mDisplay, glxContext); + glXDestroyContext(mXDisplay, glxContext); } XFree(visualInfo); - XDestroyWindow(mDisplay, mWindow); - XCloseDisplay(mDisplay); + XDestroyWindow(mXDisplay, mWindow); + XCloseDisplay(mXDisplay); - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = false; + } return (0); } -void GPUDisplayFrontendX11::DisplayExit() +void GPUDisplayFrontendX11::updateWindowState() { - pthread_mutex_lock(&mSemLockExit); - if (mDisplayRunning) { - mDisplayControl = 2; - } - pthread_mutex_unlock(&mSemLockExit); - while (mDisplayRunning) { - usleep(10000); - } + Atom type; + int32_t format; + unsigned long nItems, bytesAfter; + unsigned char* data = nullptr; + if (XGetWindowProperty(mXDisplay, mWindow, XInternAtom(mXDisplay, "_NET_WM_STATE", False), 0, 1024, False, XA_ATOM, &type, &format, &nItems, &bytesAfter, &data) != Success || data == nullptr) { + return; + } + const Atom fullScreen = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", False), maxHorz = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False), maxVert = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False); + bool isFullScreen = false, isMaxHorz = false, isMaxVert = false; + for (unsigned long i = 0; i < nItems; i++) { + const Atom a = ((Atom*)data)[i]; + isFullScreen |= a == fullScreen; + isMaxHorz |= a == maxHorz; + isMaxVert |= a == maxVert; + } + XFree(data); + setWindowState(isFullScreen, isMaxHorz && isMaxVert); } void GPUDisplayFrontendX11::SwitchFullscreen(bool set) @@ -491,12 +507,12 @@ void GPUDisplayFrontendX11::SwitchFullscreen(bool set) memset(&xev, 0, sizeof(xev)); xev.type = ClientMessage; xev.xclient.window = mWindow; - xev.xclient.message_type = XInternAtom(mDisplay, "_NET_WM_STATE", False); + xev.xclient.message_type = XInternAtom(mXDisplay, "_NET_WM_STATE", False); xev.xclient.format = 32; xev.xclient.data.l[0] = 2; // _NET_WM_STATE_TOGGLE - xev.xclient.data.l[1] = XInternAtom(mDisplay, "_NET_WM_STATE_FULLSCREEN", True); + xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", True); xev.xclient.data.l[2] = 0; - XSendEvent(mDisplay, DefaultRootWindow(mDisplay), False, SubstructureNotifyMask, &xev); + XSendEvent(mXDisplay, DefaultRootWindow(mXDisplay), False, SubstructureNotifyMask, &xev); } void GPUDisplayFrontendX11::ToggleMaximized(bool set) @@ -505,18 +521,18 @@ void GPUDisplayFrontendX11::ToggleMaximized(bool set) memset(&xev, 0, sizeof(xev)); xev.type = ClientMessage; xev.xclient.window = mWindow; - xev.xclient.message_type = XInternAtom(mDisplay, "_NET_WM_STATE", False); + xev.xclient.message_type = XInternAtom(mXDisplay, "_NET_WM_STATE", False); xev.xclient.format = 32; xev.xclient.data.l[0] = set ? 1 : 2; //_NET_WM_STATE_ADD - xev.xclient.data.l[1] = XInternAtom(mDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False); - xev.xclient.data.l[2] = XInternAtom(mDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False); - XSendEvent(mDisplay, DefaultRootWindow(mDisplay), False, SubstructureNotifyMask, &xev); + xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + xev.xclient.data.l[2] = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False); + XSendEvent(mXDisplay, DefaultRootWindow(mXDisplay), False, SubstructureNotifyMask, &xev); } void GPUDisplayFrontendX11::SetVSync(bool enable) { if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL && vsync_supported) { - mGlXSwapIntervalEXT(mDisplay, glXGetCurrentDrawable(), (int32_t)enable); + mGlXSwapIntervalEXT(mXDisplay, glXGetCurrentDrawable(), (int32_t)enable); } } @@ -525,7 +541,7 @@ void GPUDisplayFrontendX11::getSize(int32_t& width, int32_t& height) Window root_return; int32_t x_return, y_return; uint32_t width_return, height_return, border_width_return, depth_return; - if (XGetGeometry(mDisplay, mWindow, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return) == 0) { + if (XGetGeometry(mXDisplay, mWindow, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return) == 0) { throw std::runtime_error("Cannot query X11 window geometry"); } width = width_return; @@ -538,7 +554,7 @@ int32_t GPUDisplayFrontendX11::getVulkanSurface(void* instance, void* surface) VkXlibSurfaceCreateInfoKHR info{}; info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; info.flags = 0; - info.dpy = mDisplay; + info.dpy = mXDisplay; info.window = mWindow; return vkCreateXlibSurfaceKHR(*(VkInstance*)instance, &info, nullptr, (VkSurfaceKHR*)surface) != VK_SUCCESS; #else diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h index 7ea38271f2ee9..bca7def502aaa 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h @@ -16,7 +16,6 @@ #define GPUDISPLAYFRONTENDX11_H #include "GPUDisplayFrontend.h" -#include #include #include #include @@ -30,12 +29,12 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend GPUDisplayFrontendX11(); ~GPUDisplayFrontendX11() override = default; - void DisplayExit() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; + float getDisplayScale() const override { return mScale; } int32_t getVulkanSurface(void* instance, void* surface) override; uint32_t getReqVulkanExtensions(const char**& p) override; @@ -44,13 +43,12 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend int32_t GetKey(int32_t key); void GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut); - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - volatile bool mDisplayRunning = false; - GLuint mFontBase; - Display* mDisplay = nullptr; + Display* mXDisplay = nullptr; Window mWindow; + void updateWindowState(); + float mScale = 1.f; // From the Xft.dpi X resource (desktop scaling, e.g. KDE with "Apply scaling themselves" for X11 apps) PFNGLXSWAPINTERVALEXTPROC mGlXSwapIntervalEXT = nullptr; bool vsync_supported = false; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 019292228b157..f02fd7914de0b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -14,7 +14,7 @@ #include "GPUDisplayGUI.h" #include "GPUDisplayGUIWrapper.h" -#include "./ui_GPUDisplayGUI.h" +#include "ui_GPUDisplayGUI.h" using namespace o2::gpu; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index 6dc09545733fe..fd94451741e85 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -54,7 +54,7 @@ const char* HelpText[] = { "[e] / [f] Rotate left / right", "[+] / [-] Increase / decrease point size (Hold SHIFT for lines)", "[b] Change FOV (field of view)", - "['] Switch between OpenGL core / compat code path", + "['] Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines)", "[MOUSE 1] Look around", "[MOUSE 2] Strafe camera", "[MOUSE 1+2] Zoom / Rotate", @@ -158,7 +158,7 @@ void GPUDisplay::HandleKey(uint8_t key) mCfgR.maximized ^= 1; SetInfo("Toggling Maximized window (%d)", (int32_t)mCfgR.maximized); } else if (key == 'R') { - mCfgR.maxFPSRate ^= 1; + mCfgR.maxFPSRate = !mCfgR.maxFPSRate; SetInfo("FPS rate %s", mCfgR.maxFPSRate ? "not limited" : "limited"); } else if (key == 'H') { mPrintInfoText += 1; @@ -189,15 +189,14 @@ void GPUDisplay::HandleKey(uint8_t key) } SetInfo("Set FOV to %f", mCfgR.fov); } else if (key == 39) { // character = "'" - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { #ifdef GPUCA_DISPLAY_OPENGL_CORE + if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { SetInfo("OpenGL compat profile not available, using core profile", 1); -#else - mCfgR.openGLCore ^= 1; - SetInfo("Using renderer path for OpenGL %s profile", mCfgR.openGLCore ? "core" : "compat"); + } else #endif - } else { - SetInfo("OpenGL options only available with OpenGL backend"); + { + mCfgR.rendererCompatSmoothing ^= 1; + SetInfo("Using renderer path for %s profile", mCfgR.rendererCompatSmoothing ? "compat" : "core (shader)"); } } else if (key == 'B') { mCfgH.markAdjacentClusters++; @@ -513,9 +512,6 @@ void GPUDisplay::HandleKey(uint8_t key) if (oldCfgR.maximized != mCfgR.maximized) { mFrontend->ToggleMaximized(mCfgR.maximized); } - if (oldCfgR.maxFPSRate != mCfgR.maxFPSRate) { - mFrontend->mMaxFPSRate = mCfgR.maxFPSRate; - } if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { mUpdateEventData = true; } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx index cc9ec2e766c4d..1d35e2c3f714e 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx @@ -15,6 +15,7 @@ #include "GPUDisplay.h" #include "frontend/GPUDisplayInfo.inc" #include "GPUCommonMath.h" +#include "HandMadeMath.h" using namespace o2::gpu; @@ -56,7 +57,7 @@ void GPUDisplay::setAnimationPoint() mAnimateVectors[8].emplace_back(0); } else { // Quaternions float v[4]; - createQuaternionFromMatrix(v, mViewMatrixP); + createQuaternionFromMatrix(v, &mViewMatrix->Elements[0][0]); if (mAnimateVectors[0].size()) { mAnimateCloseQuaternion(v, mAnimateVectors[5].back(), mAnimateVectors[6].back(), mAnimateVectors[7].back(), mAnimateVectors[8].back()); } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx index 4472fcd087708..00dbb35dd5221 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx @@ -22,26 +22,18 @@ #else #define gluErrorString(err) "" #endif -#ifndef GPUCA_NO_FMT -#include -#endif +#include "HandMadeMath.h" #include "GPUCommonDef.h" #include "GPUDisplayMagneticField.h" #include "backend/GPUDisplayBackendOpenGL.h" -#include "shaders/GPUDisplayShaders.h" #include "GPUDisplay.h" #include "GPULogging.h" using namespace o2::gpu; -// Runtime minimum version defined in GPUDisplayFrontend.h, keep in sync! -#define GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#if !defined(GL_VERSION_4_5) || GL_VERSION_4_5 != 1 -#undef GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#endif - -#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL +#define GPUCA_DISPLAY_SHADER_TYPE_OPENGL +#include "GPUDisplayLoadShaders.h" #define CHKERR(cmd) \ do { \ @@ -55,40 +47,19 @@ using namespace o2::gpu; int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() { -#ifndef GPUCA_NO_FMT mMagneticFieldVisualization = std::make_unique(); + if (!mMagneticFieldVisualization->mValid) { + GPUError("Magnetic field data not available, field drawing disabled"); + mMagneticFieldVisualization.reset(); + return 1; + } mMagneticFieldVisualization->generateSeedPoints(mDisplay->cfgL().bFieldLinesCount); - CHKERR(mVertexShaderPassthrough = glCreateShader(GL_VERTEX_SHADER)); - CHKERR(mGeometryShader = glCreateShader(GL_GEOMETRY_SHADER)); - - CHKERR(glShaderSource(mVertexShaderPassthrough, 1, &GPUDisplayShaders::vertexShaderPassthrough, nullptr)); - CHKERR(glCompileShader(mVertexShaderPassthrough)); - - const auto constantsExpanded = fmt::format(GPUDisplayShaders::fieldModelShaderConstants, - fmt::arg("dimensions", GPUDisplayMagneticField::DIMENSIONS), - fmt::arg("solZSegs", GPUDisplayMagneticField::MAX_SOLENOID_Z_SEGMENTS), - fmt::arg("solPSegs", GPUDisplayMagneticField::MAX_SOLENOID_P_SEGMENTS), - fmt::arg("solRSegs", GPUDisplayMagneticField::MAX_SOLENOID_R_SEGMENTS), - fmt::arg("solParams", GPUDisplayMagneticField::MAX_SOLENOID_PARAMETERIZATIONS), - fmt::arg("solRows", GPUDisplayMagneticField::MAX_SOLENOID_ROWS), - fmt::arg("solColumns", GPUDisplayMagneticField::MAX_SOLENOID_COLUMNS), - fmt::arg("solCoeffs", GPUDisplayMagneticField::MAX_SOLENOID_COEFFICIENTS), - fmt::arg("dipZSegs", GPUDisplayMagneticField::MAX_DIPOLE_Z_SEGMENTS), - fmt::arg("dipYSegs", GPUDisplayMagneticField::MAX_DIPOLE_Y_SEGMENTS), - fmt::arg("dipXSegs", GPUDisplayMagneticField::MAX_DIPOLE_X_SEGMENTS), - fmt::arg("dipParams", GPUDisplayMagneticField::MAX_DIPOLE_PARAMETERIZATIONS), - fmt::arg("dipRows", GPUDisplayMagneticField::MAX_DIPOLE_ROWS), - fmt::arg("dipColumns", GPUDisplayMagneticField::MAX_DIPOLE_COLUMNS), - fmt::arg("dipCoeffs", GPUDisplayMagneticField::MAX_DIPOLE_COEFFICIENTS), - fmt::arg("maxChebOrder", GPUDisplayMagneticField::MAX_CHEBYSHEV_ORDER)); - - std::array geomShaderSource = {GPUDisplayShaders::geometryShaderP1, constantsExpanded.c_str(), GPUDisplayShaders::fieldModelShaderCode, GPUDisplayShaders::geometryShaderP2}; - - CHKERR(glShaderSource(mGeometryShader, geomShaderSource.size(), geomShaderSource.data(), nullptr)); - CHKERR(glCompileShader(mGeometryShader)); + CHKERR(mVertexShaderPassthrough = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexPassthrough_vert_spv_start, _binary_opengl_vertexPassthrough_vert_spv_len)); + CHKERR(mGeometryShader = loadShader(GL_GEOMETRY_SHADER, _binary_opengl_field_geom_spv_start, _binary_opengl_field_geom_spv_len)); if (checkShaderStatus(mVertexShaderPassthrough) || checkShaderStatus(mGeometryShader)) { + mMagneticFieldVisualization.reset(); return 1; } @@ -99,6 +70,7 @@ int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() CHKERR(glLinkProgram(mShaderProgramField)); if (checkProgramStatus(mShaderProgramField)) { + mMagneticFieldVisualization.reset(); return 1; } @@ -131,9 +103,6 @@ int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() CHKERR(glNamedBufferData(mDipoleSegmentsBuffer, sizeof(GPUDisplayMagneticField::DipoleSegmentsUniform), mMagneticFieldVisualization->mDipoleSegments.get(), GL_STREAM_DRAW)); CHKERR(glCreateBuffers(1, &mDipoleParameterizationBuffer)); CHKERR(glNamedBufferData(mDipoleParameterizationBuffer, sizeof(GPUDisplayMagneticField::DipoleParameterizationUniform), mMagneticFieldVisualization->mDipoleParameterization.get(), GL_STREAM_DRAW)); -#else - throw std::runtime_error("Magnetic field needs fmt"); -#endif return 0; } @@ -141,7 +110,10 @@ int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() uint32_t GPUDisplayBackendOpenGL::drawField() { if (!mMagneticFieldVisualization) { - return InitMagFieldVisualization(); // next frame will fill MVP matrix + if (!mMagneticFieldFailed && InitMagFieldVisualization()) { + mMagneticFieldFailed = true; + } + return 0; // next frame will fill MVP matrix } if (mMagneticFieldVisualization->mFieldLineSeedPoints.size() != (uint32_t)mDisplay->cfgL().bFieldLinesCount) { @@ -157,8 +129,7 @@ uint32_t GPUDisplayBackendOpenGL::drawField() CHKERR(glUseProgram(mShaderProgramField)); CHKERR(glBindBufferBase(GL_UNIFORM_BUFFER, 0, mFieldModelViewBuffer)); const std::array drawColor = {1.f, 0.f, 0.f, 1.f}; - const auto color = glGetUniformLocation(mShaderProgramField, "color"); - CHKERR(glUniform4fv(color, 1, drawColor.data())); + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, drawColor.data())); CHKERR(glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, mFieldModelConstantsBuffer)); CHKERR(glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, mSolenoidSegmentsBuffer)); @@ -188,12 +159,3 @@ void GPUDisplayBackendOpenGL::ExitMagFieldVisualization() CHKERR(glDeleteBuffers(1, &VBO_field)); CHKERR(glDeleteVertexArrays(1, &VAO_field)); } - -#else // GPUCA_BUILD_EVENT_DISPLAY_OPENGL -int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() -{ - return 0; -} -uint32_t GPUDisplayBackendOpenGL::drawField() { return 0; } -void GPUDisplayBackendOpenGL::ExitMagFieldVisualization() {} -#endif // GPUCA_BUILD_EVENT_DISPLAY_OPENGL diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index 8726563c0ec39..d0a6685efc742 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -99,6 +99,7 @@ void GPUDisplay::DoScreenshot(const char* filename, std::vector& pixels, f void GPUDisplay::showInfo(const char* info) { + mBackend->updateFontScale(mFrontend->getDisplayScale()); mBackend->prepareText(); float colorValue = mCfgL.invertColors ? 0.f : 1.f; OpenGLPrint(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); @@ -122,11 +123,14 @@ void GPUDisplay::showInfo(const char* info) void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) { if (mBackend->mFreetypeInitialized) { + const float scale = mFrontend->getDisplayScale(); + x *= scale; + y *= scale; if (!fromBotton) { y = mBackend->mScreenHeight - y; } float color[4] = {r, g, b, a}; - mBackend->OpenGLPrint(s, x, y, color, 1.0f); + mBackend->OpenGLPrint(s, x, y, color, scale / mBackend->mFontRasterScale); } else if (mFrontend->mCanDrawText) { mFrontend->OpenGLPrint(s, x, y, r, g, b, a, fromBotton); } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx index adfbc62ec47e1..683f2780aa8da 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx @@ -32,13 +32,13 @@ using namespace o2::gpu; GPUDisplayMagneticField::GPUDisplayMagneticField() { - initializeUniforms(); + mValid = initializeUniforms() == 0; } #ifndef GPUCA_STANDALONE GPUDisplayMagneticField::GPUDisplayMagneticField(o2::field::MagneticField* field) { - initializeUniformsFromField(field); + mValid = initializeUniformsFromField(field) == 0; } #endif diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h index 72ac83280d29c..85d6d639e9c43 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h +++ b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h @@ -16,6 +16,7 @@ #define GPUDISPLAYMAGNETICFIELD_H #include "GPUCommonDef.h" +#include "GPUDisplayMagneticFieldConstants.h" // Shared with the field shader #include #include @@ -33,22 +34,22 @@ class GPUDisplayMagneticField GPUDisplayMagneticField(o2::field::MagneticField* field); #endif - static constexpr std::size_t DIMENSIONS = 3; - static constexpr std::size_t MAX_SOLENOID_Z_SEGMENTS = 32; - static constexpr std::size_t MAX_SOLENOID_P_SEGMENTS = 512; - static constexpr std::size_t MAX_SOLENOID_R_SEGMENTS = 4096; - static constexpr std::size_t MAX_DIPOLE_Z_SEGMENTS = 128; - static constexpr std::size_t MAX_DIPOLE_Y_SEGMENTS = 2048; - static constexpr std::size_t MAX_DIPOLE_X_SEGMENTS = 16384; - static constexpr std::size_t MAX_SOLENOID_PARAMETERIZATIONS = 2048; - static constexpr std::size_t MAX_SOLENOID_ROWS = 16384; - static constexpr std::size_t MAX_SOLENOID_COLUMNS = 65536; - static constexpr std::size_t MAX_SOLENOID_COEFFICIENTS = 131072; - static constexpr std::size_t MAX_DIPOLE_PARAMETERIZATIONS = 2048; - static constexpr std::size_t MAX_DIPOLE_ROWS = 16384; - static constexpr std::size_t MAX_DIPOLE_COLUMNS = 65536; - static constexpr std::size_t MAX_DIPOLE_COEFFICIENTS = 262144; - static constexpr std::size_t MAX_CHEBYSHEV_ORDER = 32; + static constexpr std::size_t DIMENSIONS = GPUCA_DISPLAY_FIELD_DIMENSIONS; + static constexpr std::size_t MAX_SOLENOID_Z_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_Z_SEGMENTS; + static constexpr std::size_t MAX_SOLENOID_P_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_P_SEGMENTS; + static constexpr std::size_t MAX_SOLENOID_R_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_R_SEGMENTS; + static constexpr std::size_t MAX_DIPOLE_Z_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Z_SEGMENTS; + static constexpr std::size_t MAX_DIPOLE_Y_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Y_SEGMENTS; + static constexpr std::size_t MAX_DIPOLE_X_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_X_SEGMENTS; + static constexpr std::size_t MAX_SOLENOID_PARAMETERIZATIONS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_PARAMETERIZATIONS; + static constexpr std::size_t MAX_SOLENOID_ROWS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_ROWS; + static constexpr std::size_t MAX_SOLENOID_COLUMNS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COLUMNS; + static constexpr std::size_t MAX_SOLENOID_COEFFICIENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COEFFICIENTS; + static constexpr std::size_t MAX_DIPOLE_PARAMETERIZATIONS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_PARAMETERIZATIONS; + static constexpr std::size_t MAX_DIPOLE_ROWS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_ROWS; + static constexpr std::size_t MAX_DIPOLE_COLUMNS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COLUMNS; + static constexpr std::size_t MAX_DIPOLE_COEFFICIENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COEFFICIENTS; + static constexpr std::size_t MAX_CHEBYSHEV_ORDER = GPUCA_DISPLAY_FIELD_MAX_CHEBYSHEV_ORDER; struct RenderConstantsUniform { uint32_t StepCount; @@ -134,6 +135,7 @@ class GPUDisplayMagneticField std::size_t mDipColumns; std::size_t mDipCoefficients; + bool mValid = false; // Field data loaded, buffers below filled std::unique_ptr mRenderConstantsUniform; std::unique_ptr mSolenoidSegments; std::unique_ptr mDipoleSegments; diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticFieldConstants.h b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticFieldConstants.h new file mode 100644 index 0000000000000..5bc1c52562862 --- /dev/null +++ b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticFieldConstants.h @@ -0,0 +1,36 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayMagneticFieldConstants.h +/// \author David Rohr +/// \brief Array sizes of the magnetic field parameterization, shared by GPUDisplayMagneticField (C++) and the field shader (GLSL), so only plain #defines + +#ifndef GPUDISPLAYMAGNETICFIELDCONSTANTS_H +#define GPUDISPLAYMAGNETICFIELDCONSTANTS_H + +#define GPUCA_DISPLAY_FIELD_DIMENSIONS 3 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_Z_SEGMENTS 32 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_P_SEGMENTS 512 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_R_SEGMENTS 4096 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Z_SEGMENTS 128 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Y_SEGMENTS 2048 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_X_SEGMENTS 16384 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_PARAMETERIZATIONS 2048 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_ROWS 16384 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COLUMNS 65536 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COEFFICIENTS 131072 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_PARAMETERIZATIONS 2048 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_ROWS 16384 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COLUMNS 65536 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COEFFICIENTS 262144 +#define GPUCA_DISPLAY_FIELD_MAX_CHEBYSHEV_ORDER 32 + +#endif diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx index 07a05e585d422..7108386edccf4 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx @@ -59,6 +59,7 @@ int32_t GPUDisplay::buildTrackFilter() int32_t GPUDisplay::buildTrackFilter() { + return 0; } #endif diff --git a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx index 523a8d8b71dd7..a18b3e3fe2140 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx @@ -195,7 +195,9 @@ void GPUDisplay::DrawClusters(int32_t iSector) for (int32_t i = 0; i < N_POINTS_TYPE_TPC; i++) { uint32_t count = vertexCache[iCol][i].size(); mClusterBufferSizeCache[iSector][iCol][i] = std::max(mClusterBufferSizeCache[iSector][iCol][i], count); - memcpy((void*)(mVertexBuffer[iSector].data() + startCountInner), (const void*)vertexCache[iCol][i].data(), count * sizeof(vertexCache[iCol][i][0])); + if (count) { + memcpy((void*)(mVertexBuffer[iSector].data() + startCountInner), (const void*)vertexCache[iCol][i].data(), count * sizeof(vertexCache[iCol][i][0])); + } size_t startCount = mVertexBufferStart[iSector].size(); insertVertexList(iSector, startCountInner, startCountInner + count); startCountInner += count; @@ -398,7 +400,7 @@ void GPUDisplay::DrawFinal(int32_t iSector, int32_t /*iCol*/, const GPUTPCGMProp } // Print TRD part of track - auto tmpDoTRDTracklets = [&](const auto& trk) { + auto tmpDoTRDTracklets = [this, &drawing, &lastSide, iSector](const auto& trk) { for (int32_t k = 5; k >= 0; k--) { int32_t cid = trk.getTrackletIndex(k); if (cid < 0) { @@ -817,9 +819,9 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() } int32_t numThreads = getNumThreads(); - tbb::task_arena(numThreads).execute([&] { + tbb::task_arena(numThreads).execute([this, &timer, numThreads] { if (mChain && (mChain->GetRecoSteps() & gpudatatypes::RecoStep::TPCSectorTracking)) { - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { GPUTPCTracker& tracker = (GPUTPCTracker&)sectorTracker(iSector); tracker.SetPointersDataLinks(tracker.LinkTmpMemory()); mGlDLLines[iSector][tINITLINK] = DrawLinks(tracker, tINITLINK, true); @@ -829,7 +831,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Links:\t\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { const GPUTPCTracker& tracker = sectorTracker(iSector); mGlDLLines[iSector][tLINK] = DrawLinks(tracker, tLINK); @@ -845,7 +847,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Seeds:\t\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { const GPUTPCTracker& tracker = sectorTracker(iSector); mGlDLLines[iSector][tEXTRAPOLATEDTRACK] = DrawTracks(tracker, 1); // clang-format off }, tbb::simple_partitioner()); // clang-format on @@ -853,7 +855,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Sector Tracks:\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } } - tbb::parallel_for(0, numThreads, [&](int32_t iThread) { + tbb::parallel_for(0, numThreads, [this](int32_t iThread) { mThreadTracks[iThread].resize(mNCollissions); for (int32_t i = 0; i < mNCollissions; i++) { for (int32_t j = 0; j < NSECTORS; j++) { @@ -865,9 +867,9 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() }, tbb::simple_partitioner()); // clang-format on if (mConfig.showTPCTracksFromO2Format) { #ifndef GPUCA_RUN2 - uint32_t col = 0; - tbb::parallel_for(0, mIOPtrs->nOutputTracksTPCO2, [&](auto i) { + tbb::parallel_for(0, mIOPtrs->nOutputTracksTPCO2, [this](auto i) { uint8_t sector, row; + uint32_t col = 0; if (mIOPtrs->clustersNative) { mIOPtrs->outputTracksTPCO2[i].getCluster(mIOPtrs->outputClusRefsTPCO2, 0, *mIOPtrs->clustersNative, sector, row); } else { @@ -880,7 +882,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() }); #endif } else { - tbb::parallel_for(0, mIOPtrs->nMergedTracks, [&](auto i) { + tbb::parallel_for(0, mIOPtrs->nMergedTracks, [this](auto i) { const GPUTPCGMMergedTrack* track = &mIOPtrs->mergedTracks[i]; if (track->NClusters() == 0) { return; @@ -904,7 +906,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() }); } for (uint32_t col = 0; col < mIOPtrs->nMCInfosTPCCol; col++) { - tbb::parallel_for(mIOPtrs->mcInfosTPCCol[col].first, mIOPtrs->mcInfosTPCCol[col].first + mIOPtrs->mcInfosTPCCol[col].num, [&](uint32_t i) { + tbb::parallel_for(mIOPtrs->mcInfosTPCCol[col].first, mIOPtrs->mcInfosTPCCol[col].first + mIOPtrs->mcInfosTPCCol[col].num, [this, col](uint32_t i) { const GPUTPCMCInfo& mc = mIOPtrs->mcInfosTPC[i]; if (mc.charge == 0.f) { return; @@ -933,7 +935,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() prop.SetMaterialTPC(); prop.SetPolynomialField(&mParam->polynomialField); - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this, numThreads, prop](int32_t iSector) { int32_t numThread = GPUReconstruction::getHostThreadIndex(); for (int32_t iCol = 0; iCol < mNCollissions; iCol++) { mThreadBuffers[numThread].clear(); @@ -959,20 +961,21 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Merged Tracks:\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { DrawClusters(iSector); // clang-format off }, tbb::simple_partitioner()); // clang-format on if (timer.IsRunning()) { GPUInfo("Display Time: Vertex Clusters:\t\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } }); - // End omp parallel + // End tbb task arena mGlDLFinalITS = DrawFinalITS(); for (int32_t iSector = 0; iSector < NSECTORS; iSector++) { for (int32_t i = N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD + N_POINTS_TYPE_TOF; i < N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD + N_POINTS_TYPE_TOF + N_POINTS_TYPE_ITS; i++) { for (int32_t iCol = 0; iCol < mNCollissions; iCol++) { mGlDLPoints[iSector][i][iCol] = DrawSpacePointsITS(iSector, i, iCol); + break; // TODO: Only collision 0 filled for now } } break; // TODO: Only sector 0 filled for now @@ -997,6 +1000,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() for (int32_t i = N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD; i < N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD + N_POINTS_TYPE_TOF; i++) { for (int32_t iCol = 0; iCol < mNCollissions; iCol++) { mGlDLPoints[iSector][i][iCol] = DrawSpacePointsTOF(iSector, i, iCol); + break; // TODO: Only collision 0 filled for now } } break; // TODO: Only sector 0 filled for now diff --git a/GPU/GPUTracking/display/shaders.cmake b/GPU/GPUTracking/display/shaders.cmake new file mode 100644 index 0000000000000..2c232672115ef --- /dev/null +++ b/GPU/GPUTracking/display/shaders.cmake @@ -0,0 +1,68 @@ +# Copyright 2019-2026 CERN and copyright holders of ALICE O2. +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +# All rights not expressly granted are reserved. +# +# This software is distributed under the terms of the GNU General Public +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. + +# file shaders.cmake +# author David Rohr + +# Compiles a GLSL shader with glslc to SPIR-V for Vulkan (${CMAKE_CURRENT_BINARY_DIR}/vulkan/) and / or OpenGL (.../opengl/), depending on GPUCA_EVENT_DISPLAY_VULKAN / _OPENGL, and embeds it as binary resource. +# The shader sources use #ifdef VULKAN (defined by glslc for Vulkan targets) for API differences. The resource symbols are collected for write_glslc_shader_header. +function(add_glslc_shader TARGET SHADER) + get_filename_component(input-file-abs ${SHADER} ABSOLUTE) + get_filename_component(input-file-name ${SHADER} NAME) + foreach(api vulkan opengl) + string(TOUPPER ${api} api-upper) + if(NOT GPUCA_EVENT_DISPLAY_${api-upper}) + continue() + endif() + if(api STREQUAL "vulkan") + set(target-env vulkan1.4) + else() + set(target-env opengl4.5) # SPIR-V 1.0 for OpenGL 4.6 (ARB_gl_spirv) + endif() + set(spirv-file ${CMAKE_CURRENT_BINARY_DIR}/${api}/${input-file-name}.spv) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${api}) + add_custom_command( + OUTPUT ${spirv-file} + COMMAND ${GPUCA_GLSLC} --target-env=${target-env} -o ${spirv-file} ${input-file-abs} -MD -MT ${spirv-file} -MF ${spirv-file}.d + DEPENDS ${input-file-abs} + DEPFILE ${spirv-file}.d + COMMENT "Compiling GLSL to SPIR-V (${api}): ${SHADER}" + VERBATIM + ) + add_binary_resource(${TARGET} ${spirv-file}) + string(MAKE_C_IDENTIFIER "${api}/${input-file-name}.spv" symbol) # Same name as the symbol created by ld --format binary + set_property(GLOBAL APPEND PROPERTY GPUCA_DISPLAY_SHADER_SYMBOLS_${api} ${symbol}) + endforeach() +endfunction(add_glslc_shader) + +# Generates the header declaring the symbols of all shaders added with add_glslc_shader (_binary___spv_start / _len), for the API selected by GPUCA_DISPLAY_SHADER_TYPE_VULKAN / _OPENGL, which the including file defines +function(write_glslc_shader_header TARGET HEADER) + set(content "// Generated by GPU/GPUTracking/display/shaders.cmake, do not edit\n\n#include \"utils/qGetLdBinarySymbols.h\"\n\n") + set(directive "#if") + foreach(api vulkan opengl) + get_property(symbols GLOBAL PROPERTY GPUCA_DISPLAY_SHADER_SYMBOLS_${api}) + if(NOT symbols) + continue() # Not compiled for this API, falls through to the #error + endif() + string(TOUPPER ${api} api-upper) + string(APPEND content "${directive} defined(GPUCA_DISPLAY_SHADER_TYPE_${api-upper})\n") + set(directive "#elif") + foreach(symbol ${symbols}) + string(APPEND content "QGET_LD_BINARY_SYMBOLS(${symbol});\n") + endforeach() + endforeach() + if(directive STREQUAL "#if") + string(APPEND content "#if 0\n") + endif() + string(APPEND content "#else\n#error GPUCA_DISPLAY_SHADER_TYPE_[VULKAN|OPENGL] undefined, or shaders not compiled for this type\n#endif\n") + file(CONFIGURE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${HEADER} CONTENT "${content}" @ONLY) # Only rewritten if changed + target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +endfunction(write_glslc_shader_header) diff --git a/GPU/GPUTracking/display/shaders/GPUDisplayShaders.h b/GPU/GPUTracking/display/shaders/GPUDisplayShaders.h deleted file mode 100644 index 88162ef29fda3..0000000000000 --- a/GPU/GPUTracking/display/shaders/GPUDisplayShaders.h +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// \file GPUDisplayShaders.h -/// \author David Rohr - -#ifndef GPUDISPLAYSHADERS_H -#define GPUDISPLAYSHADERS_H - -#include "GPUCommonDef.h" -namespace o2::gpu -{ - -struct GPUDisplayShaders { - static constexpr const char* vertexShader = R"( -#version 450 core -layout (location = 0) in vec3 pos; -uniform mat4 ModelViewProj; - -void main() -{ - gl_Position = ModelViewProj * vec4(pos.x, pos.y, pos.z, 1.0); -} -)"; - - static constexpr const char* fragmentShader = R"( -#version 450 core -out vec4 fragColor; -uniform vec4 color; - -void main() -{ - fragColor = vec4(color.x, color.y, color.z, 1.f); -} -)"; - - static constexpr const char* vertexShaderTexture = R"( -#version 450 core -layout (location = 0) in vec4 vertex; -out vec2 TexCoords; - -uniform mat4 projection; - -void main() -{ - gl_Position = projection * vec4(vertex.xy, 0.0, 1.0); - TexCoords = vertex.zw; -} -)"; - - static constexpr const char* fragmentShaderTexture = R"( -#version 450 core -in vec2 TexCoords; -out vec4 color; - -uniform sampler2D tex; -uniform float alpha; - -void main() -{ - color = vec4(texture(tex, TexCoords).rgb, alpha); -} -)"; - - static constexpr const char* fragmentShaderText = R"( -#version 450 core -in vec2 TexCoords; -out vec4 color; - -uniform sampler2D text; -uniform vec4 textColor; - -void main() -{ - vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); - color = textColor * sampled; -} -)"; - - static constexpr const char* vertexShaderPassthrough = R"( -#version 450 core -layout (location = 0) in vec3 pos; -void main() -{ - gl_Position = vec4(pos, 1.0); -} -)"; - - static constexpr const char* geometryShaderP1 = R"( - #version 450 core -)"; - - static constexpr const char* fieldModelShaderConstants = R"( -#define DIMENSIONS {dimensions} - -#define SOL_Z_SEGS {solZSegs} -#define SOL_P_SEGS {solPSegs} -#define SOL_R_SEGS {solRSegs} - -#define SOL_PARAMS {solParams} -#define SOL_ROWS {solRows} -#define SOL_COLUMNS {solColumns} -#define SOL_COEFFS {solCoeffs} - -#define DIP_Z_SEGS {dipZSegs} -#define DIP_Y_SEGS {dipYSegs} -#define DIP_X_SEGS {dipXSegs} - -#define DIP_PARAMS {dipParams} -#define DIP_ROWS {dipRows} -#define DIP_COLUMNS {dipColumns} -#define DIP_COEFFS {dipCoeffs} - -#define MAX_CHEB_ORDER {maxChebOrder} -)"; - - static constexpr const char* fieldModelShaderCode = R"( -layout(std430, binding = 0) restrict readonly buffer field_config_ssbo { - uint32_t StepCount; - float StepSize; -} field_config; - -layout(std430, binding = 1) restrict readonly buffer sol_segment_ssbo { - float MinZ; - float MaxZ; - float MultiplicativeFactor; - - int32_t ZSegments; - - float SegZSol[SOL_Z_SEGS]; - - int32_t BegSegPSol[SOL_Z_SEGS]; - int32_t NSegPSol[SOL_Z_SEGS]; - - float SegPSol[SOL_P_SEGS]; - int32_t BegSegRSol[SOL_P_SEGS]; - int32_t NSegRSol[SOL_P_SEGS]; - - float SegRSol[SOL_R_SEGS]; - int32_t SegIDSol[SOL_R_SEGS]; -} sol_segment; - -layout(std430, binding = 2) restrict readonly buffer dip_segment_ssbo { - float MinZ; - float MaxZ; - float MultiplicativeFactor; - - int32_t ZSegments; - - float SegZDip[DIP_Z_SEGS]; - - int32_t BegSegYDip[DIP_Z_SEGS]; - int32_t NSegYDip[DIP_Z_SEGS]; - - float SegYDip[DIP_Y_SEGS]; - int32_t BegSegXDip[DIP_Y_SEGS]; - int32_t NSegXDip[DIP_Y_SEGS]; - - float SegXDip[DIP_X_SEGS]; - int32_t SegIDDip[DIP_X_SEGS]; -} dip_segment; - -layout(std430, binding = 3) restrict readonly buffer sol_params_ssbo { - float BOffsets[DIMENSIONS*SOL_PARAMS]; - float BScales[DIMENSIONS*SOL_PARAMS]; - float BMin[DIMENSIONS*SOL_PARAMS]; - float BMax[DIMENSIONS*SOL_PARAMS]; - int32_t NRows[DIMENSIONS*SOL_PARAMS]; - int32_t ColsAtRowOffset[DIMENSIONS*SOL_PARAMS]; - int32_t CofsAtRowOffset[DIMENSIONS*SOL_PARAMS]; - - int32_t NColsAtRow[SOL_ROWS]; - int32_t CofsAtColOffset[SOL_ROWS]; - - int32_t NCofsAtCol[SOL_COLUMNS]; - int32_t AtColCoefOffset[SOL_COLUMNS]; - - float Coeffs[SOL_COEFFS]; -} sol_params; - -layout(std430, binding = 4) restrict readonly buffer dip_params_ssbo { - float BOffsets[DIMENSIONS*DIP_PARAMS]; - float BScales[DIMENSIONS*DIP_PARAMS]; - float BMin[DIMENSIONS*DIP_PARAMS]; - float BMax[DIMENSIONS*DIP_PARAMS]; - int32_t NRows[DIMENSIONS*DIP_PARAMS]; - int32_t ColsAtRowOffset[DIMENSIONS*DIP_PARAMS]; - int32_t CofsAtRowOffset[DIMENSIONS*DIP_PARAMS]; - - int32_t NColsAtRow[DIP_ROWS]; - int32_t CofsAtColOffset[DIP_ROWS]; - - int32_t NCofsAtCol[DIP_COLUMNS]; - int32_t AtColCoefOffset[DIP_COLUMNS]; - - float Coeffs[DIP_COEFFS]; -} dip_params; - -float tmpCfs1[MAX_CHEB_ORDER]; -float tmpCfs0[MAX_CHEB_ORDER]; - -vec3 CarttoCyl(vec3 pos) { - return vec3(length(pos.xy), atan(pos.y, pos.x), pos.z); -} - -int32_t findSolSegment(vec3 pos) { - int32_t rid,pid,zid; - - for(zid=0; zid < sol_segment.ZSegments; zid++) if(pos.z=0;i--) { - b.zy = b.yx; - b.x = arr[i]; - b.x = dot(t1, b); - } - - const vec3 t = vec3(1, -x, 0); - return dot(t, b); -} - -float cheb1DParamsSol(float x, int32_t coeff_offset, int32_t ncf) { - if(ncf <= 0) return 0.0f; - - const float x2 = 2*x; - - vec3 b = vec3(sol_params.Coeffs[coeff_offset + (--ncf)], 0, 0); - --ncf; - - const vec3 t1 = vec3(1, x2, -1); - - for (int32_t i=ncf;i>=0;i--) { - b.zy = b.yx; - b.x = sol_params.Coeffs[coeff_offset + i]; - b.x = dot(t1, b); - } - - const vec3 t = vec3(1, -x, 0); - return dot(t, b); -} - -float cheb1DParamsDip(float x, int32_t coeff_offset, int32_t ncf) { - if(ncf <= 0) return 0.0f; - - const float x2 = 2*x; - - vec3 b = vec3(dip_params.Coeffs[coeff_offset + (--ncf)], 0, 0); - --ncf; - - const vec3 t1 = vec3(1, x2, -1); - - for (int32_t i=ncf;i>=0;i--) { - b.zy = b.yx; - b.x = dip_params.Coeffs[coeff_offset + i]; - b.x = dot(t1, b); - } - - const vec3 t = vec3(1, -x, 0); - return dot(t, b); -} - -bool IsBetween(vec3 sMin, vec3 val, vec3 sMax) { - return all(lessThanEqual(sMin, val)) && all(lessThanEqual(val, sMax)); -} - -bool IsInsideSol(int32_t segID, vec3 rphiz) { - const int32_t index = DIMENSIONS*segID; - - const vec3 seg_min = vec3(sol_params.BMin[index+0], sol_params.BMin[index+1], sol_params.BMin[index+2]); - const vec3 seg_max = vec3(sol_params.BMax[index+0], sol_params.BMax[index+1], sol_params.BMax[index+2]); - - return IsBetween(seg_min, rphiz, seg_max); -} - -bool IsInsideDip(int32_t segID, vec3 pos) { - const int32_t index = DIMENSIONS*segID; - - const vec3 seg_min = vec3(dip_params.BMin[index+0], dip_params.BMin[index+1], dip_params.BMin[index+2]); - const vec3 seg_max = vec3(dip_params.BMax[index+0], dip_params.BMax[index+1], dip_params.BMax[index+2]); - - return IsBetween(seg_min, pos, seg_max); -} - -float Eval3DSol(int32_t segID, int32_t dim, vec3 internal) { - const int32_t index = DIMENSIONS*segID; - const int32_t n_rows = sol_params.NRows[index+dim]; - const int32_t cols_at_row_offset = sol_params.ColsAtRowOffset[index+dim]; - const int32_t coeffs_at_row_offset = sol_params.CofsAtRowOffset[index+dim]; - - for(int32_t row = 0; row < n_rows; row++) { - const int32_t n_cols = sol_params.NColsAtRow[cols_at_row_offset+row]; - const int32_t coeff_at_col_offset = sol_params.CofsAtColOffset[cols_at_row_offset+row]; - - for(int32_t col = 0; col < n_cols; col++) { - const int32_t n_coeffs = sol_params.NCofsAtCol[coeff_at_col_offset+col]; - const int32_t per_col_coeff_offset = sol_params.AtColCoefOffset[coeff_at_col_offset+col]; - - const int32_t coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; - - tmpCfs1[col] = cheb1DParamsSol(internal.z, coeffs_offset,n_coeffs); - } - tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); - } - - return cheb1DArray(internal.x, tmpCfs0, n_rows); -} - -vec3 EvalSol(int32_t segID, vec3 rphiz) { - const vec3 internal = mapToInternalSol(segID, rphiz); - return vec3(Eval3DSol(segID, 0, internal), Eval3DSol(segID, 1, internal), Eval3DSol(segID, 2, internal)); -} - -float Eval3DDip(int32_t segID, int32_t dim, vec3 internal) { - const int32_t index = DIMENSIONS*segID; - const int32_t n_rows = dip_params.NRows[index+dim]; - const int32_t cols_at_row_offset = dip_params.ColsAtRowOffset[index+dim]; - const int32_t coeffs_at_row_offset = dip_params.CofsAtRowOffset[index+dim]; - - for(int32_t row = 0; row < n_rows; row++) { - const int32_t n_cols = dip_params.NColsAtRow[cols_at_row_offset+row]; - const int32_t coeff_at_col_offset = dip_params.CofsAtColOffset[cols_at_row_offset+row]; - - for(int32_t col = 0; col < n_cols; col++) { - const int32_t n_coeffs = dip_params.NCofsAtCol[coeff_at_col_offset+col]; - const int32_t per_col_coeff_offset = dip_params.AtColCoefOffset[coeff_at_col_offset+col]; - - const int32_t coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; - - tmpCfs1[col] = cheb1DParamsDip(internal.z, coeffs_offset, n_coeffs); - } - tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); - } - - return cheb1DArray(internal.x, tmpCfs0, n_rows); -} - -vec3 EvalDip(int32_t segID, vec3 pos) { - const vec3 internal = mapToInternalDip(segID, pos); - return vec3(Eval3DDip(segID, 0, internal), Eval3DDip(segID, 1, internal), Eval3DDip(segID, 2, internal)); -} - -vec3 CyltoCartCylB(vec3 rphiz, vec3 brphiz) { - const float btr = length(brphiz.xy); - const float psiPLUSphi = atan(brphiz.y, brphiz.x) + rphiz.y; - - return vec3(btr*cos(psiPLUSphi), btr*sin(psiPLUSphi), brphiz.z); -} - -vec3 MachineField(vec3 pos) { - return vec3(0); -} - -vec3 SolDipField(vec3 pos) { - if(pos.z > sol_segment.MinZ) { - const vec3 rphiz = CarttoCyl(pos); - const int32_t segID = findSolSegment(rphiz); - if(segID >=0 && IsInsideSol(segID, rphiz)) { - const vec3 brphiz = EvalSol(segID, rphiz); - return CyltoCartCylB(rphiz, brphiz) * sol_segment.MultiplicativeFactor; - } - } - - const int32_t segID = findDipSegment(pos); - if(segID >= 0 && IsInsideDip(segID, pos)) { - return EvalDip(segID, pos) * dip_segment.MultiplicativeFactor; - } - - return vec3(0); -} - -const float MinZ = dip_segment.MinZ; -const float MaxZ = sol_segment.MaxZ; - -vec3 Field(vec3 pos) { - if(pos.z > MinZ && pos.z < MaxZ) { - return SolDipField(pos); - } - return vec3(0); -} -)"; - - static constexpr const char* geometryShaderP2 = R"( -layout (points) in; -layout (line_strip, max_vertices = 256) out; - -layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; - -const float positionScale = 100.0f; - -void main() { - vec3 position = gl_in[0].gl_Position.xyz; - - for(uint32_t i = 0; i < field_config.StepCount; i++) { - gl_Position = um.ModelViewProj * vec4(position/positionScale, 1.0f); - EmitVertex(); - const vec3 b_vec = Field(position); - position -= b_vec * field_config.StepSize; - } - EndPrimitive(); -} -)"; -}; -} // namespace o2::gpu - -#endif diff --git a/GPU/GPUTracking/display/shaders/field.geom b/GPU/GPUTracking/display/shaders/field.geom new file mode 100644 index 0000000000000..40a129777f5ae --- /dev/null +++ b/GPU/GPUTracking/display/shaders/field.geom @@ -0,0 +1,378 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file field.geom +/// \author David Rohr +/// \brief Magnetic field lines: integrates the field from each seed point (input point) and emits the line strip + +#version 460 core +#extension GL_GOOGLE_include_directive : require +#include "../helpers/GPUDisplayMagneticFieldConstants.h" + +#define DIMENSIONS GPUCA_DISPLAY_FIELD_DIMENSIONS +#define SOL_Z_SEGS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_Z_SEGMENTS +#define SOL_P_SEGS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_P_SEGMENTS +#define SOL_R_SEGS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_R_SEGMENTS +#define SOL_PARAMS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_PARAMETERIZATIONS +#define SOL_ROWS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_ROWS +#define SOL_COLUMNS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COLUMNS +#define SOL_COEFFS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COEFFICIENTS +#define DIP_Z_SEGS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Z_SEGMENTS +#define DIP_Y_SEGS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Y_SEGMENTS +#define DIP_X_SEGS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_X_SEGMENTS +#define DIP_PARAMS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_PARAMETERIZATIONS +#define DIP_ROWS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_ROWS +#define DIP_COLUMNS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COLUMNS +#define DIP_COEFFS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COEFFICIENTS +#define MAX_CHEB_ORDER GPUCA_DISPLAY_FIELD_MAX_CHEBYSHEV_ORDER + +#ifdef VULKAN +#define FIELD_SSBO_SET set = 1, // Vulkan: UBO and SSBOs share the binding numbers of a descriptor set +#else +#define FIELD_SSBO_SET +#endif + +layout(std430, FIELD_SSBO_SET binding = 0) restrict readonly buffer field_config_ssbo { + uint StepCount; + float StepSize; +} field_config; + +layout(std430, FIELD_SSBO_SET binding = 1) restrict readonly buffer sol_segment_ssbo { + float MinZ; + float MaxZ; + float MultiplicativeFactor; + + int ZSegments; + + float SegZSol[SOL_Z_SEGS]; + + int BegSegPSol[SOL_Z_SEGS]; + int NSegPSol[SOL_Z_SEGS]; + + float SegPSol[SOL_P_SEGS]; + int BegSegRSol[SOL_P_SEGS]; + int NSegRSol[SOL_P_SEGS]; + + float SegRSol[SOL_R_SEGS]; + int SegIDSol[SOL_R_SEGS]; +} sol_segment; + +layout(std430, FIELD_SSBO_SET binding = 2) restrict readonly buffer dip_segment_ssbo { + float MinZ; + float MaxZ; + float MultiplicativeFactor; + + int ZSegments; + + float SegZDip[DIP_Z_SEGS]; + + int BegSegYDip[DIP_Z_SEGS]; + int NSegYDip[DIP_Z_SEGS]; + + float SegYDip[DIP_Y_SEGS]; + int BegSegXDip[DIP_Y_SEGS]; + int NSegXDip[DIP_Y_SEGS]; + + float SegXDip[DIP_X_SEGS]; + int SegIDDip[DIP_X_SEGS]; +} dip_segment; + +layout(std430, FIELD_SSBO_SET binding = 3) restrict readonly buffer sol_params_ssbo { + float BOffsets[DIMENSIONS*SOL_PARAMS]; + float BScales[DIMENSIONS*SOL_PARAMS]; + float BMin[DIMENSIONS*SOL_PARAMS]; + float BMax[DIMENSIONS*SOL_PARAMS]; + int NRows[DIMENSIONS*SOL_PARAMS]; + int ColsAtRowOffset[DIMENSIONS*SOL_PARAMS]; + int CofsAtRowOffset[DIMENSIONS*SOL_PARAMS]; + + int NColsAtRow[SOL_ROWS]; + int CofsAtColOffset[SOL_ROWS]; + + int NCofsAtCol[SOL_COLUMNS]; + int AtColCoefOffset[SOL_COLUMNS]; + + float Coeffs[SOL_COEFFS]; +} sol_params; + +layout(std430, FIELD_SSBO_SET binding = 4) restrict readonly buffer dip_params_ssbo { + float BOffsets[DIMENSIONS*DIP_PARAMS]; + float BScales[DIMENSIONS*DIP_PARAMS]; + float BMin[DIMENSIONS*DIP_PARAMS]; + float BMax[DIMENSIONS*DIP_PARAMS]; + int NRows[DIMENSIONS*DIP_PARAMS]; + int ColsAtRowOffset[DIMENSIONS*DIP_PARAMS]; + int CofsAtRowOffset[DIMENSIONS*DIP_PARAMS]; + + int NColsAtRow[DIP_ROWS]; + int CofsAtColOffset[DIP_ROWS]; + + int NCofsAtCol[DIP_COLUMNS]; + int AtColCoefOffset[DIP_COLUMNS]; + + float Coeffs[DIP_COEFFS]; +} dip_params; + +float tmpCfs1[MAX_CHEB_ORDER]; +float tmpCfs0[MAX_CHEB_ORDER]; + +vec3 CarttoCyl(vec3 pos) { + return vec3(length(pos.xy), atan(pos.y, pos.x), pos.z); +} + +int findSolSegment(vec3 pos) { + int rid,pid,zid; + + for(zid=0; zid < sol_segment.ZSegments; zid++) if(pos.z=0;i--) { + b.zy = b.yx; + b.x = arr[i]; + b.x = dot(t1, b); + } + + const vec3 t = vec3(1, -x, 0); + return dot(t, b); +} + +float cheb1DParamsSol(float x, int coeff_offset, int ncf) { + if(ncf <= 0) return 0.0f; + + const float x2 = 2*x; + + vec3 b = vec3(sol_params.Coeffs[coeff_offset + (--ncf)], 0, 0); + --ncf; + + const vec3 t1 = vec3(1, x2, -1); + + for (int i=ncf;i>=0;i--) { + b.zy = b.yx; + b.x = sol_params.Coeffs[coeff_offset + i]; + b.x = dot(t1, b); + } + + const vec3 t = vec3(1, -x, 0); + return dot(t, b); +} + +float cheb1DParamsDip(float x, int coeff_offset, int ncf) { + if(ncf <= 0) return 0.0f; + + const float x2 = 2*x; + + vec3 b = vec3(dip_params.Coeffs[coeff_offset + (--ncf)], 0, 0); + --ncf; + + const vec3 t1 = vec3(1, x2, -1); + + for (int i=ncf;i>=0;i--) { + b.zy = b.yx; + b.x = dip_params.Coeffs[coeff_offset + i]; + b.x = dot(t1, b); + } + + const vec3 t = vec3(1, -x, 0); + return dot(t, b); +} + +bool IsBetween(vec3 sMin, vec3 val, vec3 sMax) { + return all(lessThanEqual(sMin, val)) && all(lessThanEqual(val, sMax)); +} + +bool IsInsideSol(int segID, vec3 rphiz) { + const int index = DIMENSIONS*segID; + + const vec3 seg_min = vec3(sol_params.BMin[index+0], sol_params.BMin[index+1], sol_params.BMin[index+2]); + const vec3 seg_max = vec3(sol_params.BMax[index+0], sol_params.BMax[index+1], sol_params.BMax[index+2]); + + return IsBetween(seg_min, rphiz, seg_max); +} + +bool IsInsideDip(int segID, vec3 pos) { + const int index = DIMENSIONS*segID; + + const vec3 seg_min = vec3(dip_params.BMin[index+0], dip_params.BMin[index+1], dip_params.BMin[index+2]); + const vec3 seg_max = vec3(dip_params.BMax[index+0], dip_params.BMax[index+1], dip_params.BMax[index+2]); + + return IsBetween(seg_min, pos, seg_max); +} + +float Eval3DSol(int segID, int dim, vec3 internal) { + const int index = DIMENSIONS*segID; + const int n_rows = sol_params.NRows[index+dim]; + const int cols_at_row_offset = sol_params.ColsAtRowOffset[index+dim]; + const int coeffs_at_row_offset = sol_params.CofsAtRowOffset[index+dim]; + + for(int row = 0; row < n_rows; row++) { + const int n_cols = sol_params.NColsAtRow[cols_at_row_offset+row]; + const int coeff_at_col_offset = sol_params.CofsAtColOffset[cols_at_row_offset+row]; + + for(int col = 0; col < n_cols; col++) { + const int n_coeffs = sol_params.NCofsAtCol[coeff_at_col_offset+col]; + const int per_col_coeff_offset = sol_params.AtColCoefOffset[coeff_at_col_offset+col]; + + const int coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; + + tmpCfs1[col] = cheb1DParamsSol(internal.z, coeffs_offset,n_coeffs); + } + tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); + } + + return cheb1DArray(internal.x, tmpCfs0, n_rows); +} + +vec3 EvalSol(int segID, vec3 rphiz) { + const vec3 internal = mapToInternalSol(segID, rphiz); + return vec3(Eval3DSol(segID, 0, internal), Eval3DSol(segID, 1, internal), Eval3DSol(segID, 2, internal)); +} + +float Eval3DDip(int segID, int dim, vec3 internal) { + const int index = DIMENSIONS*segID; + const int n_rows = dip_params.NRows[index+dim]; + const int cols_at_row_offset = dip_params.ColsAtRowOffset[index+dim]; + const int coeffs_at_row_offset = dip_params.CofsAtRowOffset[index+dim]; + + for(int row = 0; row < n_rows; row++) { + const int n_cols = dip_params.NColsAtRow[cols_at_row_offset+row]; + const int coeff_at_col_offset = dip_params.CofsAtColOffset[cols_at_row_offset+row]; + + for(int col = 0; col < n_cols; col++) { + const int n_coeffs = dip_params.NCofsAtCol[coeff_at_col_offset+col]; + const int per_col_coeff_offset = dip_params.AtColCoefOffset[coeff_at_col_offset+col]; + + const int coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; + + tmpCfs1[col] = cheb1DParamsDip(internal.z, coeffs_offset, n_coeffs); + } + tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); + } + + return cheb1DArray(internal.x, tmpCfs0, n_rows); +} + +vec3 EvalDip(int segID, vec3 pos) { + const vec3 internal = mapToInternalDip(segID, pos); + return vec3(Eval3DDip(segID, 0, internal), Eval3DDip(segID, 1, internal), Eval3DDip(segID, 2, internal)); +} + +vec3 CyltoCartCylB(vec3 rphiz, vec3 brphiz) { + const float btr = length(brphiz.xy); + const float psiPLUSphi = atan(brphiz.y, brphiz.x) + rphiz.y; + + return vec3(btr*cos(psiPLUSphi), btr*sin(psiPLUSphi), brphiz.z); +} + +vec3 MachineField(vec3 pos) { + return vec3(0); +} + +vec3 SolDipField(vec3 pos) { + if(pos.z > sol_segment.MinZ) { + const vec3 rphiz = CarttoCyl(pos); + const int segID = findSolSegment(rphiz); + if(segID >=0 && IsInsideSol(segID, rphiz)) { + const vec3 brphiz = EvalSol(segID, rphiz); + return CyltoCartCylB(rphiz, brphiz) * sol_segment.MultiplicativeFactor; + } + } + + const int segID = findDipSegment(pos); + if(segID >= 0 && IsInsideDip(segID, pos)) { + return EvalDip(segID, pos) * dip_segment.MultiplicativeFactor; + } + + return vec3(0); +} + +vec3 Field(vec3 pos) { + if(pos.z > dip_segment.MinZ && pos.z < sol_segment.MaxZ) { + return SolDipField(pos); + } + return vec3(0); +} + +layout (points) in; +layout (line_strip, max_vertices = 256) out; + +layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; + +const float positionScale = 100.0f; + +void main() { + vec3 position = gl_in[0].gl_Position.xyz; + + for(uint i = 0; i < field_config.StepCount; i++) { + gl_Position = um.ModelViewProj * vec4(position/positionScale, 1.0f); + EmitVertex(); + const vec3 b_vec = Field(position); + position -= b_vec * field_config.StepSize; + } + EndPrimitive(); +} diff --git a/GPU/GPUTracking/display/shaders/fragment.frag b/GPU/GPUTracking/display/shaders/fragment.frag index e6fe7c28a54ba..7245834ded276 100644 --- a/GPU/GPUTracking/display/shaders/fragment.frag +++ b/GPU/GPUTracking/display/shaders/fragment.frag @@ -1,8 +1,14 @@ #version 460 core layout (location = 0) out vec4 outColor; +#ifdef VULKAN layout (push_constant) uniform pushColor { vec4 color; } pc; +#define COLOR pc.color +#else +layout (location = 4) uniform vec4 color; +#define COLOR color +#endif void main() { - outColor = vec4(pc.color.x, pc.color.y, pc.color.z, 1.f); + outColor = vec4(COLOR.x, COLOR.y, COLOR.z, 1.f); } diff --git a/GPU/GPUTracking/display/shaders/fragmentSmoothLine.frag b/GPU/GPUTracking/display/shaders/fragmentSmoothLine.frag new file mode 100644 index 0000000000000..16d6ff0c537e4 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/fragmentSmoothLine.frag @@ -0,0 +1,35 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file fragmentSmoothLine.frag +/// \author David Rohr + +#version 460 core +layout (location = 0) out vec4 outColor; +layout (location = 1) noperspective in vec2 centerPos; +layout (location = 2) flat in float alphaOffset; +#ifdef VULKAN +layout (push_constant) uniform pushColor { vec4 color; } pc; +#define COLOR pc.color +#else +layout (location = 4) uniform vec4 color; +#define COLOR color +#endif + +void main() // Antialiased line edge (1 pixel ramp at the border of the line widened by SMOOTH_LINE_EXTRA_WIDTH), replaces GL_LINE_SMOOTH / Vulkan smoothLines +{ + vec2 p = gl_FragCoord.xy - centerPos; + float alpha = clamp(alphaOffset - 0.5f * dot(p, p), 0.f, 1.f); // half width - (d^2 + 0.5) / 2 in pixels: sqrt(d^2) linearized around d = 1 pixel (no sqrt), 0.25 margin to make it a bit brighter, as for the points + if (alpha <= 0.f) { + discard; // No depth write outside the line + } + outColor = vec4(COLOR.x, COLOR.y, COLOR.z, alpha); +} diff --git a/GPU/GPUTracking/display/shaders/fragmentSmoothPoint.frag b/GPU/GPUTracking/display/shaders/fragmentSmoothPoint.frag new file mode 100644 index 0000000000000..161ed5ad72470 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/fragmentSmoothPoint.frag @@ -0,0 +1,34 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file fragmentSmoothPoint.frag +/// \author David Rohr + +#version 460 core +layout (location = 0) out vec4 outColor; +layout (location = 1) flat in float pointSize; +#ifdef VULKAN +layout (push_constant) uniform pushColor { vec4 color; } pc; +#define COLOR pc.color +#else +layout (location = 4) uniform vec4 color; +#define COLOR color +#endif + +void main() // Round point with antialiased edge, replaces GL_POINT_SMOOTH. The point size is increased by SMOOTH_POINT_EXTRA_SIZE, the outermost pixel is the alpha ramp +{ + vec2 p = gl_PointCoord - vec2(0.5f); + float alpha = clamp(pointSize * (0.5f - 0.5f * pointSize * dot(p, p)) - 0.25f, 0.f, 1.f); // radius - (d^2 + 1) / 2 in pixels: sqrt(d^2) linearized around d = 1 pixel, avoids the sqrt, adding 0.25 margin to make it a bit brighter + if (alpha <= 0.f) { + discard; // No depth write outside the disc + } + outColor = vec4(COLOR.x, COLOR.y, COLOR.z, alpha); +} diff --git a/GPU/GPUTracking/display/shaders/fragmentText.frag b/GPU/GPUTracking/display/shaders/fragmentText.frag index 37098586281ae..70f5017933960 100644 --- a/GPU/GPUTracking/display/shaders/fragmentText.frag +++ b/GPU/GPUTracking/display/shaders/fragmentText.frag @@ -1,11 +1,18 @@ #version 460 core layout (location = 0) out vec4 outColor; layout (location = 2) in vec2 TexCoords; +#ifdef VULKAN layout (binding = 2) uniform sampler2D text; layout (push_constant) uniform pushColor { vec4 textColor; } pc; +#define TEXT_COLOR pc.textColor +#else +layout (binding = 0) uniform sampler2D text; +layout (location = 4) uniform vec4 textColor; +#define TEXT_COLOR textColor +#endif void main() { - vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); - outColor = pc.textColor * sampled; + vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); + outColor = TEXT_COLOR * sampled; } diff --git a/GPU/GPUTracking/display/shaders/fragmentTexture.frag b/GPU/GPUTracking/display/shaders/fragmentTexture.frag index 6c92d9b2b27b7..786fcc8b40c91 100644 --- a/GPU/GPUTracking/display/shaders/fragmentTexture.frag +++ b/GPU/GPUTracking/display/shaders/fragmentTexture.frag @@ -1,10 +1,17 @@ #version 460 core layout (location = 0) out vec4 outColor; layout (location = 2) in vec2 TexCoords; +#ifdef VULKAN layout (binding = 2) uniform sampler2D tex; layout (push_constant) uniform pushAlpha { float alpha; } pa; +#define ALPHA pa.alpha +#else +layout (binding = 0) uniform sampler2D tex; +layout (location = 4) uniform float alpha; +#define ALPHA alpha +#endif void main() { - outColor = vec4(texture(tex, TexCoords).rgb, pa.alpha); + outColor = vec4(texture(tex, TexCoords).rgb, ALPHA); } diff --git a/GPU/GPUTracking/display/shaders/fragmentUniform.frag b/GPU/GPUTracking/display/shaders/fragmentUniform.frag deleted file mode 100644 index 4299af3daedb7..0000000000000 --- a/GPU/GPUTracking/display/shaders/fragmentUniform.frag +++ /dev/null @@ -1,8 +0,0 @@ -#version 460 core -layout (location = 0) out vec4 outColor; -layout (binding = 1) uniform uniformColor { vec4 color; } uc; - -void main() -{ - outColor = vec4(uc.color.x, uc.color.y, uc.color.z, 1.f); -} diff --git a/GPU/GPUTracking/display/shaders/vertex.vert b/GPU/GPUTracking/display/shaders/vertex.vert index 5bb1475c05856..55e2acdeee45f 100644 --- a/GPU/GPUTracking/display/shaders/vertex.vert +++ b/GPU/GPUTracking/display/shaders/vertex.vert @@ -1,8 +1,14 @@ #version 460 core layout (location = 0) in vec3 pos; +#ifdef VULKAN layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; +#define MODEL_VIEW_PROJ um.ModelViewProj +#else +layout (location = 0) uniform mat4 ModelViewProj; +#define MODEL_VIEW_PROJ ModelViewProj +#endif void main() { - gl_Position = um.ModelViewProj * vec4(pos.x, pos.y, pos.z, 1.0); + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); } diff --git a/GPU/GPUTracking/display/shaders/vertexPassthrough.vert b/GPU/GPUTracking/display/shaders/vertexPassthrough.vert new file mode 100644 index 0000000000000..7f6ed6a8e55aa --- /dev/null +++ b/GPU/GPUTracking/display/shaders/vertexPassthrough.vert @@ -0,0 +1,21 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file vertexPassthrough.vert +/// \author David Rohr + +#version 460 core +layout (location = 0) in vec3 pos; + +void main() +{ + gl_Position = vec4(pos, 1.0); +} diff --git a/GPU/GPUTracking/display/shaders/vertexPoint.vert b/GPU/GPUTracking/display/shaders/vertexPoint.vert index 8b87073407298..c8b572c61044a 100644 --- a/GPU/GPUTracking/display/shaders/vertexPoint.vert +++ b/GPU/GPUTracking/display/shaders/vertexPoint.vert @@ -1,10 +1,19 @@ #version 460 core layout (location = 0) in vec3 pos; +#ifdef VULKAN layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; layout (push_constant) uniform pushSize { layout(offset = 16) float size; } ps; +#define MODEL_VIEW_PROJ um.ModelViewProj +#define POINT_SIZE ps.size +#else +layout (location = 0) uniform mat4 ModelViewProj; +layout (location = 5) uniform float size; +#define MODEL_VIEW_PROJ ModelViewProj +#define POINT_SIZE size +#endif void main() { - gl_Position = um.ModelViewProj * vec4(pos.x, pos.y, pos.z, 1.0); - gl_PointSize = ps.size; + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); + gl_PointSize = POINT_SIZE; } diff --git a/GPU/GPUTracking/display/shaders/vertexSmoothLine.vert b/GPU/GPUTracking/display/shaders/vertexSmoothLine.vert new file mode 100644 index 0000000000000..7ab9dba518b51 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/vertexSmoothLine.vert @@ -0,0 +1,39 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file vertexSmoothLine.vert +/// \author David Rohr + +#version 460 core +layout (location = 0) in vec3 pos; +layout (location = 1) noperspective out vec2 centerPos; // Window position, interpolated along the line, i.e. the closest point on the center line for each fragment of a (wide) line +layout (location = 2) flat out float alphaOffset; // Half line width - 0.25, see fragmentSmoothLine.frag +#ifdef VULKAN +layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; +layout (push_constant) uniform pushLine { layout(offset = 20) float lineWidth; layout(offset = 24) vec2 viewport; } pl; +#define MODEL_VIEW_PROJ um.ModelViewProj +#define LINE_WIDTH pl.lineWidth +#define VIEWPORT pl.viewport +#else +layout (location = 0) uniform mat4 ModelViewProj; +layout (location = 6) uniform vec2 viewport; +layout (location = 7) uniform float lineWidth; +#define MODEL_VIEW_PROJ ModelViewProj +#define LINE_WIDTH lineWidth +#define VIEWPORT viewport +#endif + +void main() +{ + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); + centerPos = (gl_Position.xy / gl_Position.w * 0.5f + 0.5f) * VIEWPORT; + alphaOffset = LINE_WIDTH * 0.5f - 0.25f; +} diff --git a/GPU/GPUTracking/display/shaders/vertexSmoothPoint.vert b/GPU/GPUTracking/display/shaders/vertexSmoothPoint.vert new file mode 100644 index 0000000000000..1c7c39f02d166 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/vertexSmoothPoint.vert @@ -0,0 +1,35 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file vertexSmoothPoint.vert +/// \author David Rohr + +#version 460 core +layout (location = 0) in vec3 pos; +layout (location = 1) flat out float pointSize; +#ifdef VULKAN +layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; +layout (push_constant) uniform pushSize { layout(offset = 16) float size; } ps; +#define MODEL_VIEW_PROJ um.ModelViewProj +#define POINT_SIZE ps.size +#else +layout (location = 0) uniform mat4 ModelViewProj; +layout (location = 5) uniform float size; +#define MODEL_VIEW_PROJ ModelViewProj +#define POINT_SIZE size +#endif + +void main() +{ + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); + gl_PointSize = POINT_SIZE; // Ignored in OpenGL (no GL_PROGRAM_POINT_SIZE), there glPointSize applies + pointSize = POINT_SIZE; +} diff --git a/GPU/GPUTracking/display/shaders/vertexTexture.vert b/GPU/GPUTracking/display/shaders/vertexTexture.vert index 62442c0131efb..984e05601dc94 100644 --- a/GPU/GPUTracking/display/shaders/vertexTexture.vert +++ b/GPU/GPUTracking/display/shaders/vertexTexture.vert @@ -1,10 +1,16 @@ #version 460 core layout (location = 0) in vec4 vertex; layout (location = 2) out vec2 TexCoords; +#ifdef VULKAN layout (binding = 0) uniform uniformMatrix { mat4 projection; } um; +#define PROJECTION um.projection +#else +layout (location = 0) uniform mat4 projection; +#define PROJECTION projection +#endif void main() { - gl_Position = um.projection * vec4(vertex.xy, 0.0, 1.0); + gl_Position = PROJECTION * vec4(vertex.xy, 0.0, 1.0); TexCoords = vertex.zw; } diff --git a/GPU/Workflow/src/O2GPUDPLDisplay.cxx b/GPU/Workflow/src/O2GPUDPLDisplay.cxx index fa68289517e68..3d10a47adda4f 100644 --- a/GPU/Workflow/src/O2GPUDPLDisplay.cxx +++ b/GPU/Workflow/src/O2GPUDPLDisplay.cxx @@ -129,7 +129,7 @@ void O2GPUDPLDisplaySpec::run(ProcessingContext& pc) } if (mDisplayStarted == false) { - if (mDisplay->startDisplay()) { + if (mDisplay->StartUnmanagedDisplay()) { throw std::runtime_error("Error starting event display"); } mDisplayStarted = true; @@ -143,7 +143,7 @@ void O2GPUDPLDisplaySpec::run(ProcessingContext& pc) ptrs.settingsTF = mTFSettings.get(); if (mDisplay->show(&ptrs)) { - mDisplay->endDisplay(); + mDisplay->StopUnmanagedDisplay(); mDisplayShutDown = true; } } @@ -153,7 +153,7 @@ void O2GPUDPLDisplaySpec::endOfStream(EndOfStreamContext& ec) if (mDisplayShutDown) { return; } - mDisplay->endDisplay(); + mDisplay->StopUnmanagedDisplay(); mDisplayShutDown = true; } diff --git a/dependencies/FindO2GPUWayland.cmake b/dependencies/FindO2GPUWayland.cmake index cc13848df9616..7ada679c97ce1 100644 --- a/dependencies/FindO2GPUWayland.cmake +++ b/dependencies/FindO2GPUWayland.cmake @@ -25,10 +25,18 @@ find_program(${PKGNAME}_SCANNER wayland-scanner) find_package(PkgConfig) if(PkgConfig_FOUND) pkg_get_variable(${PKGNAME}_PROTOCOLS_DIR wayland-protocols pkgdatadir) - if(NOT ${PKGNAME}_PROTOCOLS_DIR OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") - message(STATUS "wayland xdg protocol missing in ${${PKGNAME}_PROTOCOLS_DIR}") - unset(${PKGNAME}_PROTOCOLS_DIR) - endif() + set(${PKGNAME}_PROTOCOLS stable/xdg-shell/xdg-shell.xml + stable/viewporter/viewporter.xml + stable/tablet/tablet-v2.xml + unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + staging/fractional-scale/fractional-scale-v1.xml + staging/cursor-shape/cursor-shape-v1.xml) + foreach(xml ${${PKGNAME}_PROTOCOLS}) + if(${PKGNAME}_PROTOCOLS_DIR AND NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/${xml}") + message(STATUS "wayland protocol ${xml} missing in ${${PKGNAME}_PROTOCOLS_DIR}") + unset(${PKGNAME}_PROTOCOLS_DIR) + endif() + endforeach() endif() include(FindPackageHandleStandardArgs)