diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index 245cf5e..6e6f701 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -30,6 +30,14 @@ jobs: cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. make -j4 + - name: Build layer_gpu_timeline + run: | + export CXX=clang++ + mkdir layer_gpu_timeline/build_rel + cd layer_gpu_timeline/build_rel + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. + make -j4 + - name: Build and run unit tests run: | export CXX=clang++ @@ -56,6 +64,14 @@ jobs: cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. make -j4 + - name: Build layer_gpu_timeline + run: | + export CXX=g++ + mkdir layer_gpu_timeline/build_rel + cd layer_gpu_timeline/build_rel + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. + make -j4 + build-android: name: Android runs-on: ubuntu-22.04 diff --git a/layer_example/source/layer_device_functions.hpp b/layer_example/source/layer_device_functions.hpp index df321c2..f3403c7 100644 --- a/layer_example/source/layer_device_functions.hpp +++ b/layer_example/source/layer_device_functions.hpp @@ -23,6 +23,8 @@ * ---------------------------------------------------------------------------- */ +#include + #include "framework/utils.hpp" /* See Vulkan API for documentation. */ diff --git a/layer_gpu_timeline/CMakeLists.txt b/layer_gpu_timeline/CMakeLists.txt new file mode 100644 index 0000000..d801a1c --- /dev/null +++ b/layer_gpu_timeline/CMakeLists.txt @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: MIT +# ----------------------------------------------------------------------------- +# Copyright (c) 2024 Arm Limited +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.17) + +set(CMAKE_CXX_STANDARD 20) + +project(VkLayerGPUTimeline VERSION 1.0.0) + +# Common configuration +set(LGL_LOG_TAG, "VkLayerGPUTimeline") +include(../source_common/compiler_helper.cmake) + +# Build steps +add_subdirectory(../source_common/comms source_common/comms) +add_subdirectory(../source_common/framework source_common/framework) +add_subdirectory(../source_common/trackers source_common/trackers) +add_subdirectory(source) diff --git a/layer_gpu_timeline/README_LAYER.md b/layer_gpu_timeline/README_LAYER.md new file mode 100644 index 0000000..8f8a56c --- /dev/null +++ b/layer_gpu_timeline/README_LAYER.md @@ -0,0 +1,164 @@ +# Layer: GPU Timeline + +This layer is used with Arm GPUs for tracking submitted schedulable workloads +and emitting semantic information about them. This data can be combined with +the raw workload execution timing information captured using the Android +Perfetto service, providing developers with a richer debug visualization. + +## What devices? + +The Arm GPU driver integration with the Perfetto render stages scheduler event +trace is supported at production quality since the r47p0 driver version. +However, associating semantics from this layer relies on a further integration +with debug labels which requires an r51p0 or later driver version. + +## What workloads? + +A schedulable workload is the smallest workload that the Arm GPU command stream +scheduler will issue to the GPU hardware work queues. This includes the +following workload types: + +* Render passes, split into: + * Vertex or Binning phase + * Fragment or Main phase +* Compute dispatches +* Trace rays +* Transfers to a buffer +* Transfers to an image + +Most workloads are dispatched using a single API call, and are trivial to +manage in the layer. However, render passes are more complex and need extra +handling. In particular: + +* Render passes are issued using multiple API calls. +* Useful render pass properties, such as draw count, are not known until the + render pass recording has ended. +* Dynamic render passes using `vkCmdBeginRendering()` and `vkCmdEndRendering()` + can be suspended and resumed across command buffer boundaries. Properties + such as draw count are not defined by the scope of a single command buffer. + +## Tracking workloads + +This layer tracks workloads encoded in command buffers, and emits semantic +metadata for each workload via a communications side-channel. A host tool +combines the semantic data stream with the Perfetto data stream, using debug +label tags injected by the layer as a common cross-reference to link across +the streams. + +### Workload labelling + +Command stream labelling is implemented using `vkCmdDebugMarkerBeginEXT()` +and `vkCmdDebugMarkerEndEXT()`, wrapping one layer-owned `tagID` label around +each semantic workload. This `tagID` can unambiguously refer to this workload +encoding, and metadata that we do not expect to change per submit will be +emitted using the matching `tagID` as the sole identifier. + +_**TODO:** Dynamic `submitID` tracking is not yet implemented._ + +The `tagID` label is encoded into the recorded command buffer which means, for +reusable command buffers, it is not an unambiguous identifier of a specific +running workload. To allow us to disambiguate specific workload instances, the +layer can optionally add an outer wrapper of `submitID` labels around each +submitted command buffer. This wrapper is only generated if the submit contains +any command buffers that require the generation of a per-submit annex (see the +following section for when this is needed). + +The `submitID.tagID` pair of IDs uniquely identifies a specific running +workload, and can be used to attach an instance-specific metadata annex to a +specific submitted workload rather than to the shared recorded command buffer. + +### Workload metadata for split render passes + +_**TODO:** Split render pass tracking is not yet implemented._ + +Dynamic render passes can be split across multiple Begin/End pairs, including +being split across command buffer boundaries. If these splits occur within a +single primary command buffer, or its secondaries, it is handled transparently +by the layer and it appears as a single message as if no splits occurred. If +these splits occur across primary command buffer boundaries, then some +additional work is required. + +In our design a `tagID` debug marker is only started when the render pass first +starts (not on resume), and stopped at the end of the render pass (not on +suspend). The same `tagID` is used to refer to all parts of the render pass, +no matter how many times it was suspended and resumed. + +If a render pass splits across command buffers, we cannot precompute metrics +based on `tagID` alone, even if the command buffers are one-time use. This is +because we do not know what combination of submitted command buffers will be +used, and so we cannot know what the render pass contains until submit time. +Split render passes will emit a `submitID.tagID` metadata annex containing +the parameters that can only be known at submit time. + +### Workload metadata for compute dispatches + +_**TODO:** Compute workgroup parsing from the SPIR-V is not yet implemented._ + +Compute workload dispatch is simple to track, but one of the metadata items we +want to export is the total size of the work space (work_group_count * +work_group_size). + +The work group count is defined by the API call, but may be an indirect +parameter (see indirect tracking above). + +The work group size is defined by the program pipeline, and is defined in the +SPIR-V via a literal or a build-time specialization constant. To support this +use case we will need to parse the SPIR-V when the pipeline is built, if +SPIR-V is available. + +### Workload metadata for indirect calls + +_**TODO:** Indirect parameter tracking is not yet implemented._ + +One of the valuable pieces of metadata that we want to present is the size of +each workload. For render passes this is captured at API call time, but for +other workloads the size can be an indirect parameter that is not known when +the triggering API call is made. + +To capture indirect parameters we insert a transfer that copies the indirect +parameters into a layer-owned buffer. To ensure exclusive use of the buffer and +avoid data corruption, each buffer region used is unique to a specific `tagID`. +Attempting to submit the same command buffer multiple times will result in +the workload being serialized to avoid racy access to the buffer. Once the +buffer has been retrieved by the layer, a metadata annex containing the +indirect parameters will be emitted using the `submitID.tagID` pair. This may +be some time later than the original submit. + +### Workload metadata for user-defined labels + +The workload metadata captures user-defined labels that the application +provides using `vkCmdDebugMarkerBeginEXT()` and `vkCmdDebugMarkerEndEXT()`. +These are a stack-based debug mechanism where `Begin` pushes a new entry on to +to the stack, and `End` pops the the most recent level off the stack. + +Workloads are labelled with the stack values that existed when the workload +was started. For render passes this is the value on the stack when, e.g., +`vkCmdBeginRenderPass()` was called. We do not capture any labels that exist +inside the render pass. + +The debug label stack belongs to the queue, not to the command buffer, so the +value of the label stack is not known until submit time. The debug information +for a specific `submitID.tagID` pair is therefore provided as an annex at +submit time once the stack can be resolved. + +## Message protocol + +For each workload in a command buffer, or part-workload in the case of a +suspended render pass, we record a JSON metadata blob containing the payload +we want to send. + +The low level protocol message contains: + +* Message type `uint8_t` +* Sequence ID `uint64_t` (optional, implied by message type) +* Tag ID `uint64_t` +* JSON length `uint32_t` +* JSON payload `uint8_t[]` + +Each workload will read whatever properties it can from the `tagID` metadata +and will then merge in all fields from any subsequent `sequenceID.tagID` +metadata that matches. + +- - - + +_Copyright © 2024, Arm Limited and contributors._ diff --git a/layer_gpu_timeline/android_build.sh b/layer_gpu_timeline/android_build.sh new file mode 100644 index 0000000..960b2b0 --- /dev/null +++ b/layer_gpu_timeline/android_build.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MIT +# ---------------------------------------------------------------------------- +# Copyright (c) 2024 Arm Limited +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Configuration + +# Exit immediately if any component command errors +set -e + +BUILD_DIR_64=build_arm64 +BUILD_DIR_PACK=build_package + +# ---------------------------------------------------------------------------- +# Process command line options +if [ "$#" -lt 1 ]; then + BUILD_TYPE=Release +else + BUILD_TYPE=$1 +fi + +# Process command line options +if [ "$#" -lt 2 ]; then + PACKAGE=0 +else + PACKAGE=$2 +fi + +if [ "${PACKAGE}" -gt "0" ]; then + echo "Building a ${BUILD_TYPE} build with packaging" +else + echo "Building a ${BUILD_TYPE} build without packaging" +fi + +# ---------------------------------------------------------------------------- +# Build the 64-bit layer +mkdir -p ${BUILD_DIR_64} +pushd ${BUILD_DIR_64} + +cmake \ + -DCMAKE_SYSTEM_NAME=Android \ + -DANDROID_PLATFORM=29 \ + -DANDROID_ABI=arm64-v8a \ + -DANDROID_TOOLCHAIN=clang \ + -DANDROID_STL=c++_static \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" \ + .. + +make -j1 + +popd + +# ---------------------------------------------------------------------------- +# Build the release package +if [ "${PACKAGE}" -gt "0" ]; then + # Setup the package directories + mkdir -p ${BUILD_DIR_PACK}/bin/android/arm64 + + # Install the 64-bit layer + cp ${BUILD_DIR_64}/source/*.so ${BUILD_DIR_PACK}/bin/android/arm64 +fi diff --git a/layer_gpu_timeline/android_install.py b/layer_gpu_timeline/android_install.py new file mode 100644 index 0000000..dc1e062 --- /dev/null +++ b/layer_gpu_timeline/android_install.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT +# ----------------------------------------------------------------------------- +# Copyright (c) 2024 Arm Limited +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the 'Software'), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- +''' +A simple installer for Android Vulkan layers. +''' + +import argparse +import os +import shlex +import subprocess as sp +import sys + +# Android temp directory +ANDROID_TMP_DIR = '/data/local/tmp/' + +# Expected layer names +EXPECTED_VULKAN_LAYER_NAME = 'VK_LAYER_LGL_GPUTIMELINE' +EXPECTED_VULKAN_LAYER_FILE = 'libVkLayerGPUTimeline.so' + +class Device: + ''' + A basic wrapper around adb, allowing a specific device to be registered. + + Attributes: + device: The name of the device to call, or None for non-specific use. + ''' + + def adb_quiet(self, *args): + ''' + Call `adb` to run a command, but ignore output and errors. + + Args: + *args : List of command line parameters. + ''' + commands = ['adb'] + commands.extend(args) + sp.run(commands, stdout=sp.DEVNULL, stderr=sp.DEVNULL, check=False) + + def adb(self, *args, **kwargs): + ''' + Call `adb` to run command, and capture output and results. + + Args: + *args: List of command line parameters. + **kwargs: text: Is output is text, or binary? + shell: Use the host shell? + quote: Quote arguments before forwarding + + Returns: + The contents of stdout. + + Raises: + CalledProcessError: The subprocess was not successfully executed. + ''' + commands = ['adb'] + commands.extend(args) + + text = kwargs.get('text', True) + shell = kwargs.get('shell', False) + quote = kwargs.get('quote', False) + + # Run on the host shell + if shell: + # Unix shells need a flattened command for shell commands + if os.name != 'nt': + quotedCommands = [] + for command in commands: + if command != '>': + command = shlex.quote(command) + quotedCommands.append(command) + commands = ' '.join(quotedCommands) + + # Run on the device but with shell argument quoting + if quote: + for i, command in enumerate(commands): + commands[i] = shlex.quote(command) + + rep = sp.run(commands, check=True, shell=shell, stdout=sp.PIPE, + stderr=sp.PIPE, universal_newlines=text) + + return rep.stdout + + def adb_run_as(self, package, *args, quiet=False): + ''' + Call `adb` to run command as a package using `run-as` or as root, + if root is accessible. If command will be run as root, this function + will change CWD to the package data directory before executing the command. + + Args: + package: Package name to run-as or change CWD to. + *args: List of command line parameters. + quiet: If True, ignores output from adb. + + Returns: + The contents of stdout or Nothing, if quiet=True. + + Raises: + CalledProcessError: The subprocess was not successfully executed. + ''' + command = ['shell', 'run-as', package] + command.extend(args) + + if quiet: + return self.adb_quiet(*command) + + return self.adb(*command) + + +def enable_vulkan_debug_layer(device, package, layer): + ''' + Args: + device: The device instance. + package: The Android package name. + layer: The layer file path name. + ''' + + print('\nInstalling Vulkan debug layer') + + layer = os.path.normpath(layer) + layerBase = os.path.basename(os.path.normpath(layer)) + + device.adb('push', layer, ANDROID_TMP_DIR) + device.adb_run_as(package, 'cp', ANDROID_TMP_DIR + layerBase, '.') + device.adb('shell', 'settings', 'put', 'global', 'enable_gpu_debug_layers', '1') + device.adb('shell', 'settings', 'put', 'global', 'gpu_debug_app', package) + device.adb('shell', 'settings', 'put', 'global', 'gpu_debug_layers', EXPECTED_VULKAN_LAYER_NAME) + + +def disable_vulkan_debug_layer(device, package, layer): + ''' + Clean up the Vulkan layer installation. + + Args: + device: The device instance. + args: The command arguments. + ''' + print('\nRemoving Vulkan debug layer') + + layerBase = os.path.basename(os.path.normpath(layer)) + + device.adb('shell', 'settings', 'delete', 'global', 'enable_gpu_debug_layers') + device.adb('shell', 'settings', 'delete', 'global gpu_debug_app') + device.adb('shell', 'settings', 'delete', 'global gpu_debug_layers') + device.adb_run_as(package, 'rm', layerBase, quiet=True) + + +def get_layer(): + ''' + Find the debug layer to use in the build directory. + ''' + + base_dir = './build_arm64/source/' + + sym_lib = None + lib = None + + for path in os.listdir(base_dir): + if path.endswith('_sym.so'): + sym_lib = os.path.join(base_dir, path) + elif path.endswith('.so'): + lib = os.path.join(base_dir, path) + + # TODO: If we want to use symbolized layer we need to rename it + return lib + + +def parse_command_line(): + ''' + Parse the command line. + + Returns: + The parsed command line container. + ''' + parser = argparse.ArgumentParser() + + parser.add_argument('--package', required=True, + help='Android package name') + + return parser.parse_args() + + +def main(): + ''' + Script main function. + + Returns: + Process return code. + ''' + args = parse_command_line() + + device = Device() + layer = get_layer() + + enable_vulkan_debug_layer(device, args.package, layer) + + input('Press Enter to disable layers') + + disable_vulkan_debug_layer(device, args.package, layer) + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + print('\n\nERROR: User interrupted execution') diff --git a/layer_gpu_timeline/docs/command_buffer_model.md b/layer_gpu_timeline/docs/command_buffer_model.md new file mode 100644 index 0000000..e7422d7 --- /dev/null +++ b/layer_gpu_timeline/docs/command_buffer_model.md @@ -0,0 +1,155 @@ +# Layer: GPU Timeline - Command Buffer Modelling + +One of the main challenges of this layer driver is modelling behavior in queues +and command buffers that is not known until submit time, and then taking +appropriate actions based on the combination of both the head state of the +queue and the content of the pre-recorded command buffers. + +Our design to solve this is a lightweight software command stream which is +recorded when a command buffer is recorded, and then executed when the +command buffer is submitted to the queue. Just like a real hardware command +stream these commands can update state or trigger some other action we need +performed. + +## Layer commands + +**MARKER_BEGIN(const std::string\*):** + +* Push a new marker into the queue debug label stack. + +**MARKER_END():** + +* Pop the latest marker from the queue debug label stack. + +**RENDERPASS_BEGIN(const json\*):** + +* Set the current workload to a new render pass with the passed metadata. + +**RENDERPASS_RESUME(const json\*):** + +* Update the current workload, which must be a render pass, with extra + draw count metadata. + +**COMPUTE_DISPATCH_BEGIN(const json\*):** + +* Set the current workload to a new compute dispatch with the passed metadata. + +**TRACE_RAYS_BEGIN(const json\*):** + +* Set the current workload to a new trace rays with the passed metadata. + +**BUFFER_TRANSFER_BEGIN(const json\*):** + +* Set the current workload to a new a buffer transfer. + +**IMAGE_TRANSFER(const json\*):** + +* Set the current workload to a new image transfer. + +**WORKLOAD_END():** + +* Mark the current workload as complete, and emit a built metadata entry for + it. + +## Layer command recording + +Command buffer recording is effectively building two separate state +structures for the layer. + +The first is a per-workload or per-restart JSON structure that contains the +metadata we need for that workload. For partial workloads - e.g. a dynamic +render pass begin that has been suspended - this metadata will be partial and +rely on later restart metadata to complete it. + +The second is the layer "command stream" that contains the bytecode commands +to execute when the command buffer is submitted to the queue. These commands +are very simple, consisting of a list of command+pointer pairs, where the +pointer value may be unused by some commands. Commands are stored in a +std::vector, but we reserve enough memory to store 256 commands without +reallocating which is enough for the majority of command buffers we see in +real applications. + +The command stream for a secondary command buffer is inlined into the primary +command buffer during recording. + +### Recording sequence + +When application records a new workload: + + * A `tagID` is assigned and recorded using `vkCmdMarkerBegin()` label in the + Vulkan command stream _before_ the new workload is written to the command + stream. + * If workload is using indirect parameters, then a transfer job to copy + indirect parameters into a layer-owned buffer is emitted _before_ the new + workload. No additional barrier is needed because application barriers must + have already ensured that the indirect parameter buffer is valid. + * A proxy workload object is created in the layer storing the assigned + `tagID` and all settings that are known at command recording time. + * A layer command stream command is recorded into the submit time stream + indicating `_BEGIN` with a pointer to the proxy workload. Note that + this JSON may be modified later for some workloads. + * If workload is using indirect parameters, a layer command stream command is + recorded into the resolve time stream, which will handle cleanup and + emitting the `submitID.tagID` annex message for the indirect data. + * If the command buffer is not ONE_TIME_SUBMIT, if any workload is using + indirect parameters, or contains incomplete render passes, the command + buffer is marked as needing a `submitID` wrapper. + * The user command is written to the Vulkan command stream. + +When application resumes a render pass workload: + + * A `tagID` of zero is assigned, but not emitted to the command stream. + * A layer command stream command is recorded into the submit time stream + indicating `_RESUME` with a pointer to the proxy workload. Note that + this JSON may be modified later for some workloads. + * The user command is written to the Vulkan command stream. + +When application ends a workload: + + * For render pass workloads, any statistics accumulated since the last begin + are rolled up into the proxy workload object. + * For render pass workloads, the user command is written to the Vulkan + command stream. + * The command steam label scope is closed using `vkCmdMarkerEnd()`. + +## Layer command playback + +The persistent state for command playback belongs to the queues the command +buffers are submitted to. The command stream bytecode is run by a bytecode +interpreter associated with the state of the current queue, giving the +interpreter access to the current `submitID` and queue debug label stack. + +### Submitting sequence + +For each command buffer in the user submit: + +* If the command buffer needs a `submitID` we allocate a unique `submitID` and + create two new command buffers that will wrap the user command buffer with an + additional stack layer of debug label containing the `s` string. We will + inject a layer command stream async command to handle freeing the command + buffers. +* The tool will process the submit-time layer commands, executing each command + to either update some state or emit +* If there are any async layer commands, either recorded in the command buffer + or from the wrapping command buffers, we will need to add an async handler. + This cannot safely use the user fence or depend on any user object lifetime, + so we will add a layer-owned timeline semaphore to the submit which we can + wait on to determine when it is safe trigger the async work. + +## Future: Async commands + +One of our longer-term goals is to be able to capture indirect parameters, +which will be available after-the-fact once the GPU has processed the command +buffer. Once we have the data we can emit an annex message containing +parameters for each indirect `submitID.tagID` pair in the command buffer. + +We need to be able to emit the metadata after the commands are complete, +and correctly synchronize use of the indirect capture staging buffer +if command buffers are reissued. My current thinking is that we would +implement this using additional layer commands that are processed on submit, +including support for async commands that run in a separate thread and +wait on the command buffer completion fence before running. + +- - - + +_Copyright © 2024, Arm Limited and contributors._ diff --git a/layer_gpu_timeline/source/CMakeLists.txt b/layer_gpu_timeline/source/CMakeLists.txt new file mode 100644 index 0000000..98a24ff --- /dev/null +++ b/layer_gpu_timeline/source/CMakeLists.txt @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: MIT +# ----------------------------------------------------------------------------- +# Copyright (c) 2024 Arm Limited +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- + +# Set output file names +if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(VK_LAYER VkLayerGPUTimeline_sym) + set(VK_LAYER_STRIP libVkLayerGPUTimeline.so) +else() + set(VK_LAYER VkLayerGPUTimeline) +endif() + +# Set strings used by configure +set(LGL_LAYER_NAME_STR "VK_LAYER_LGL_GPUTIMELINE") +set(LGL_LAYER_DESC_STR "VkLayerGPUTimeline by LGL") + +# Vulkan layer library +configure_file( + version.hpp.in + version.hpp + ESCAPE_QUOTES @ONLY) + +add_library( + ${VK_LAYER} SHARED + device.cpp + entry.cpp + instance.cpp + layer_device_functions_command_buffer.cpp + layer_device_functions_command_pool.cpp + layer_device_functions_debug.cpp + layer_device_functions_dispatch.cpp + layer_device_functions_draw_call.cpp + layer_device_functions_queue.cpp + layer_device_functions_render_pass.cpp + layer_device_functions_trace_rays.cpp + timeline_comms.cpp) + +target_include_directories( + ${VK_LAYER} PRIVATE + ${PROJECT_SOURCE_DIR}/../source_common + ${CMAKE_CURRENT_BINARY_DIR} + .) + +target_include_directories( + ${VK_LAYER} SYSTEM PRIVATE + ../../khronos/vulkan/include) + +lgl_set_build_options(${VK_LAYER}) + +target_compile_definitions( + ${VK_LAYER} PRIVATE + $<$:VK_USE_PLATFORM_ANDROID_KHR=1> + $<$:LGL_LOG_TAG="${LGL_LOG_TAG}">) + +target_link_libraries( + ${VK_LAYER} + lib_layer_comms + lib_layer_framework + lib_layer_trackers + $<$:log>) + +if (CMAKE_BUILD_TYPE STREQUAL "Release") + add_custom_command( + TARGET "${VK_LAYER}" POST_BUILD + DEPENDS "${VK_LAYER}" + COMMAND ${CMAKE_STRIP} + ARGS --strip-all -o ${VK_LAYER_STRIP} $ + COMMENT "Stripped lib${VK_LAYER}.so to ${VK_LAYER_STRIP}") +endif() diff --git a/layer_gpu_timeline/source/device.cpp b/layer_gpu_timeline/source/device.cpp new file mode 100644 index 0000000..57df2df --- /dev/null +++ b/layer_gpu_timeline/source/device.cpp @@ -0,0 +1,103 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include + +#include "comms/comms_module.hpp" +#include "framework/utils.hpp" + +#include "device.hpp" +#include "instance.hpp" + +static std::unordered_map> g_devices; + +/* See header for documentation. */ +void Device::store( + VkDevice handle, + std::unique_ptr device +) { + void* key = getDispatchKey(handle); + g_devices.insert({ key, std::move(device) }); +} + +/* See header for documentation. */ +Device* Device::retrieve( + VkDevice handle) +{ + void* key = getDispatchKey(handle); + assert(isInMap(key, g_devices)); + return g_devices.at(key).get(); +} + +/* See header for documentation. */ +Device* Device::retrieve( + VkQueue handle) +{ + void* key = getDispatchKey(handle); + assert(isInMap(key, g_devices)); + return g_devices.at(key).get(); +} + +/* See header for documentation. */ +Device* Device::retrieve( + VkCommandBuffer handle) +{ + void* key = getDispatchKey(handle); + assert(isInMap(key, g_devices)); + return g_devices.at(key).get(); +} + +/* See header for documentation. */ +void Device::destroy( + Device* device +) { + g_devices.erase(getDispatchKey(device)); +} + +/* See header for documentation. */ +Device::Device( + Instance* _instance, + VkPhysicalDevice _physicalDevice, + VkDevice _device, + PFN_vkGetDeviceProcAddr nlayerGetProcAddress +): instance(_instance), + physicalDevice(_physicalDevice), + device(_device) +{ + initDriverDeviceDispatchTable(device, nlayerGetProcAddress, driver); + + // Connect to the network service + commsWrapper.init(commsModule); +} + +/* See header for documentation. */ +Device::~Device() +{ + +} diff --git a/layer_gpu_timeline/source/device.hpp b/layer_gpu_timeline/source/device.hpp new file mode 100644 index 0000000..f13e249 --- /dev/null +++ b/layer_gpu_timeline/source/device.hpp @@ -0,0 +1,186 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * @file + * Declares the root class for layer management of VkDevice objects. + * + * Role summary + * ============ + * + * Devices represent the core context used by the application to connect to the + * underlying graphics driver. A device object is the dispatch root for the + * Vulkan driver, so device commands all take some form of dispatchable handle + * that can be resolved into a unique per-device key. For the driver this key + * would simply be a pointer directly to the driver-internal device object, but + * for our layer we use a device dispatch key as an index in to the map to find + * the layer's driver object. + * + * Key properties + * ============== + * + * Unlike EGL contexts, Vulkan devices are designed to be used concurrently by + * multiple application threads. An application can have multiple concurrent + * devices (although this is less common than with OpenGL ES applications), and + * use each device from multiple threads. + * + * Access to the layer driver structures must therefore be kept thread-safe. + * For sake of simplicity, we generally implement this by: + * - Holding a global lock whenever any thread is inside layer code. + * - Releasing the global lock whenever the layer calls a driver function. + */ + +#pragma once + +#include + +#include "comms/comms_module.hpp" +#include "framework/device_dispatch_table.hpp" +#include "trackers/device.hpp" + +#include "instance.hpp" +#include "timeline_comms.hpp" + +/** + * @brief This class implements the layer state tracker for a single device. + */ +class Device +{ +public: + /** + * @brief Store a new device into the global store of dispatchable devices. + * + * @param handle The dispatchable device handle to use as an indirect key. + * @param device The @c Device object to store. + */ + static void store( + VkDevice handle, + std::unique_ptr device); + + /** + * @brief Fetch a device from the global store of dispatchable devices. + * + * @param handle The dispatchable device handle to use as an indirect lookup. + */ + static Device* retrieve( + VkDevice handle); + + /** + * @brief Fetch a device from the global store of dispatchable devices. + * + * @param handle The dispatchable queue handle to use as an indirect lookup. + */ + static Device* retrieve( + VkQueue handle); + + /** + * @brief Fetch a device from the global store of dispatchable devices. + * + * @param handle The dispatchable command buffer handle to use as an indirect lookup. + */ + static Device* retrieve( + VkCommandBuffer handle); + + /** + * @brief Drop a device from the global store of dispatchable devices. + * + * @param device The device to drop. + */ + static void destroy( + Device* device); + + /** + * @brief Create a new layer device object. + * + * @param instance The layer instance object this device is created with. + * @param physicalDevice The physical device this logical device is for. + * @param device The device handle this device is created with. + * @param nlayerGetProcAddress The vkGetProcAddress function in the driver/next layer down. + */ + Device( + Instance* instance, + VkPhysicalDevice physicalDevice, + VkDevice device, + PFN_vkGetDeviceProcAddr nlayerGetProcAddress); + + /** + * @brief Destroy this layer device object. + */ + ~Device(); + + /** + * @brief Callback for sending messages + */ + void onWorkloadSubmit(const std::string& message) + { + LAYER_LOG("%s", message.c_str()); + //commsWrapper.txMessage(message); + } + + /** + * @brief Get the cumulative stats for this device. + */ + Tracker::Device& getStateTracker() + { + return stateTracker; + } + +public: + /** + * @brief The driver function dispatch table. + */ + DeviceDispatchTable driver {}; + +private: + /** + * @brief The instance this device is created with. + */ + const Instance* instance; + + /** + * @brief The physical device this device is created with. + */ + const VkPhysicalDevice physicalDevice; + + /** + * @brief The device handle this device is created with. + */ + const VkDevice device; + + /** + * @brief State tracking for this device; + */ + Tracker::Device stateTracker; + + /** + * @brief Communications module for this layer + */ + Comms::CommsModule commsModule { "libGPULayersUDS" }; + + /** + * @brief Communications module message encoder wrapper for this layer + */ + TimelineComms commsWrapper; +}; diff --git a/layer_gpu_timeline/source/entry.cpp b/layer_gpu_timeline/source/entry.cpp new file mode 100644 index 0000000..dc340cd --- /dev/null +++ b/layer_gpu_timeline/source/entry.cpp @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * @file + * This module exposes the entrypoints used by the layer loader. + * + * Note that the Android loader requires more functions to be exposed as + * library symbols than other Vulkan loaders. + */ +#include +#include +#include +#include + +#include "framework/utils.hpp" +#include "framework/entry_utils.hpp" + +std::mutex g_vulkanLock; + +extern "C" { + +/** See Vulkan API for documentation. */ +VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr( + VkDevice device, + const char* pName +) { + return vkGetDeviceProcAddr_default(device, pName); +} + +/** See Vulkan API for documentation. */ +VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr( + VkInstance instance, + const char* pName +) { + return vkGetInstanceProcAddr_default(instance, pName); +} + +/** See Vulkan API for documentation. */ +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties +) { + return vkEnumerateInstanceExtensionProperties_default(pLayerName, pPropertyCount, pProperties); +} + +/** See Vulkan API for documentation. */ +VK_LAYER_EXPORT_ANDROID VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties( + VkPhysicalDevice gpu, + const char* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties +) { + return vkEnumerateDeviceExtensionProperties_default(gpu, pLayerName, pPropertyCount, pProperties); +} + +/** See Vulkan API for documentation. */ +VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( + uint32_t* pPropertyCount, + VkLayerProperties* pProperties +) { + return vkEnumerateInstanceLayerProperties_default(pPropertyCount, pProperties); +} + +/** See Vulkan API for documentation. */ +VK_LAYER_EXPORT_ANDROID VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( + VkPhysicalDevice gpu, + uint32_t* pPropertyCount, + VkLayerProperties* pProperties +) { + return vkEnumerateDeviceLayerProperties_default(gpu, pPropertyCount, pProperties); +} + +} diff --git a/layer_gpu_timeline/source/instance.cpp b/layer_gpu_timeline/source/instance.cpp new file mode 100644 index 0000000..6ac278e --- /dev/null +++ b/layer_gpu_timeline/source/instance.cpp @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include + +#include "framework/utils.hpp" + +#include "instance.hpp" + +static std::unordered_map> g_instances; + +/* See header for documentation. */ +void Instance::store( + VkInstance handle, + std::unique_ptr& instance +) { + void* key = getDispatchKey(handle); + g_instances.insert({ key, std::move(instance) }); +} + +/* See header for documentation. */ +Instance* Instance::retrieve( + VkInstance handle) +{ + void* key = getDispatchKey(handle); + assert(isInMap(key, g_instances)); + return g_instances.at(key).get(); +} + +/* See header for documentation. */ +Instance* Instance::retrieve( + VkPhysicalDevice handle) +{ + void* key = getDispatchKey(handle); + assert(isInMap(key, g_instances)); + return g_instances.at(key).get(); +} + +/* See header for documentation. */ +void Instance::destroy( + Instance* instance +) { + g_instances.erase(getDispatchKey(instance->instance)); +} + +/* See header for documentation. */ +Instance::Instance( + VkInstance _instance, + PFN_vkGetInstanceProcAddr _nlayerGetProcAddress) : + instance(_instance), + nlayerGetProcAddress(_nlayerGetProcAddress) +{ + initDriverInstanceDispatchTable(instance, nlayerGetProcAddress, driver); +} diff --git a/layer_gpu_timeline/source/instance.hpp b/layer_gpu_timeline/source/instance.hpp new file mode 100644 index 0000000..cfda54e --- /dev/null +++ b/layer_gpu_timeline/source/instance.hpp @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * @file + * Declares the root class for layer management of VkInstance objects. + * + * Role summary + * ============ + * + * Instances represent the core context used by the application to connect to + * the OS graphics subsystem prior to connection to a specific device instance. + * An instance object is the dispatch root for the Vulkan subsystem, so + * instance commands all take some form of dispatchable handle that can be + * resolved into a unique per-instance key. For the driver this key would + * simply be a pointer directly to the driver-internal instance object, but for + * our layer we use a instance dispatch key as an index in to the map to find + * the layer's instance object. + * + * Key properties + * ============== + * + * Unlike EGL contexts, Vulkan instances are designed to be used concurrently + * by multiple application threads. An application can have multiple concurrent + * instances (although this is less common than with OpenGL ES applications), + * and use each instance from multiple threads. + * + * Access to the layer driver structures must therefore be kept thread-safe. + * For sake of simplicity, we generally implement this by: + * - Holding a global lock whenever any thread is inside layer code. + * - Releasing the global lock whenever the layer calls a driver function. + */ + +#pragma once + +#include +#include + +#include +#include + +#include "framework/instance_dispatch_table.hpp" + +/** + * @brief This class implements the layer state tracker for a single instance. + * + * These objects are relatively light-weight, as they are rarely used once a VkDevice has been + * created, but we need to track the chain-of-ownership as the instance is the root object that + * the application creates when initializing a rendering context. + */ +class Instance +{ +public: + /** + * @brief Store a new instance into the global store of dispatchable instances. + * + * @param handle The dispatchable instance handle to use as an indirect key. + * @param instance The @c Instance object to store. + */ + static void store( + VkInstance handle, + std::unique_ptr& instance); + + /** + * @brief Fetch an instance from the global store of dispatchable instances. + * + * @param handle The dispatchable instance handle to use as an indirect lookup. + */ + static Instance* retrieve( + VkInstance handle); + + /** + * @brief Fetch an instance from the global store of dispatchable instances. + * + * @param handle The dispatchable physical device handle to use as an indirect lookup. + */ + static Instance* retrieve( + VkPhysicalDevice handle); + + /** + * @brief Drop an instance from the global store of dispatchable instances. + * + * @param instance The instance to drop. + */ + static void destroy( + Instance* instance); + + /** + * @brief Create a new layer instance object. + * + * @param instance The instance handle this instance is created with. + * @param nlayerGetProcAddress The vkGetProcAddress function in the driver/next layer down. + */ + Instance( + VkInstance instance, + PFN_vkGetInstanceProcAddr nlayerGetProcAddress); + +public: + /** + * @brief The instance handle this instance is created with. + */ + VkInstance instance; + + /** + * @brief The next layer's \c vkGetInstanceProcAddr() function pointer. + */ + PFN_vkGetInstanceProcAddr nlayerGetProcAddress; + + /** + * @brief The driver function dispatch table. + */ + InstanceDispatchTable driver {}; +}; diff --git a/layer_gpu_timeline/source/layer_device_functions.hpp b/layer_gpu_timeline/source/layer_device_functions.hpp new file mode 100644 index 0000000..4794141 --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions.hpp @@ -0,0 +1,377 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include + +#include "framework/utils.hpp" + +// Functions for command pools + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateCommandPool( + VkDevice device, + const VkCommandPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkCommandPool* pCommandPool); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkResetCommandPool( + VkDevice device, + VkCommandPool commandPool, + VkCommandPoolResetFlags flags); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkDestroyCommandPool( + VkDevice device, + VkCommandPool commandPool, + const VkAllocationCallbacks* pAllocator); + +// Functions for command buffers + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkAllocateCommandBuffers( + VkDevice device, + const VkCommandBufferAllocateInfo* pAllocateInfo, + VkCommandBuffer* pCommandBuffers); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult layer_vkBeginCommandBuffer( + VkCommandBuffer commandBuffer, + const VkCommandBufferBeginInfo* pBeginInfo); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdExecuteCommands( + VkCommandBuffer commandBuffer, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkResetCommandBuffer( + VkCommandBuffer commandBuffer, + VkCommandBufferResetFlags flags); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkFreeCommandBuffers( + VkDevice device, + VkCommandPool commandPool, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers); + +// Functions for render passes + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateRenderPass( + VkDevice device, + const VkRenderPassCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateRenderPass2( + VkDevice device, + const VkRenderPassCreateInfo2* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateRenderPass2KHR( + VkDevice device, + const VkRenderPassCreateInfo2* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkDestroyRenderPass( + VkDevice device, + VkRenderPass renderPass, + const VkAllocationCallbacks* pAllocator); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderPass( + VkCommandBuffer commandBuffer, + const VkRenderPassBeginInfo* pRenderPassBegin, + VkSubpassContents contents); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderPass2( + VkCommandBuffer commandBuffer, + const VkRenderPassBeginInfo* pRenderPassBegin, + const VkSubpassBeginInfo* pSubpassBeginInfo); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderPass2KHR( + VkCommandBuffer commandBuffer, + const VkRenderPassBeginInfo* pRenderPassBegin, + const VkSubpassBeginInfo* pSubpassBeginInfo); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRendering( + VkCommandBuffer commandBuffer, + const VkRenderingInfo* pRenderingInfo); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderingKHR( + VkCommandBuffer commandBuffer, + const VkRenderingInfo* pRenderingInfo); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdEndRenderPass( + VkCommandBuffer commandBuffer); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdEndRendering( + VkCommandBuffer commandBuffer); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdEndRenderingKHR( + VkCommandBuffer commandBuffer); + +// Functions for draw calls + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDraw( + VkCommandBuffer commandBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexed( + VkCommandBuffer commandBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexedIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + uint32_t drawCount, + uint32_t stride); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexedIndirectCount( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexedIndirectCountKHR( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + uint32_t drawCount, + uint32_t stride); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirectByteCountEXT( + VkCommandBuffer commandBuffer, + uint32_t instanceCount, + uint32_t firstInstance, + VkBuffer counterBuffer, + VkDeviceSize counterBufferOffset, + uint32_t counterOffset, + uint32_t vertexStride); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirectCount( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirectCountKHR( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride); + +// Functions for compute dispatches + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatch( + VkCommandBuffer commandBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatchBase( + VkCommandBuffer commandBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatchBaseKHR( + VkCommandBuffer commandBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatchIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset); + +// Commands for trace rays + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdTraceRaysIndirect2KHR( + VkCommandBuffer commandBuffer, + VkDeviceAddress indirectDeviceAddress); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdTraceRaysIndirectKHR( + VkCommandBuffer commandBuffer, + const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable, + VkDeviceAddress indirectDeviceAddress); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdTraceRaysKHR( + VkCommandBuffer commandBuffer, + const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable, + uint32_t width, + uint32_t height, + uint32_t depth); + +// Functions for debug + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDebugMarkerBeginEXT( + VkCommandBuffer commandBuffer, + const VkDebugMarkerMarkerInfoEXT* pMarkerInfo); + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDebugMarkerEndEXT( + VkCommandBuffer commandBuffer); + +// Functions for queues + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo); + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueueSubmit( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo* pSubmits, + VkFence fence); + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueueSubmit2( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo2* pSubmits, + VkFence fence); + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueueSubmit2KHR( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo2* pSubmits, + VkFence fence); diff --git a/layer_gpu_timeline/source/layer_device_functions_command_buffer.cpp b/layer_gpu_timeline/source/layer_device_functions_command_buffer.cpp new file mode 100644 index 0000000..571a4bf --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_command_buffer.cpp @@ -0,0 +1,163 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" + +extern std::mutex g_vulkanLock; + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkAllocateCommandBuffers( + VkDevice device, + const VkCommandBufferAllocateInfo* pAllocateInfo, + VkCommandBuffer* pCommandBuffers +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + // Release the lock to call into the driver + lock.unlock(); + VkResult result = layer->driver.vkAllocateCommandBuffers( + device, pAllocateInfo, pCommandBuffers); + if (result != VK_SUCCESS) + { + return result; + } + + // Retake the lock to access layer-wide global store + lock.lock(); + auto& tracker = layer->getStateTracker(); + for (uint32_t i = 0; i < pAllocateInfo->commandBufferCount; i++) + { + tracker.allocateCommandBuffer( + pAllocateInfo->commandPool, pCommandBuffers[i]); + } + + return result; +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult layer_vkBeginCommandBuffer( + VkCommandBuffer commandBuffer, + const VkCommandBufferBeginInfo* pBeginInfo +) { + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cmdBuffer = tracker.getCommandBuffer(commandBuffer); + cmdBuffer.reset(); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkBeginCommandBuffer(commandBuffer, pBeginInfo); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkResetCommandBuffer( + VkCommandBuffer commandBuffer, + VkCommandBufferResetFlags flags +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cmdBuffer = tracker.getCommandBuffer(commandBuffer); + cmdBuffer.reset(); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkResetCommandBuffer(commandBuffer, flags); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkFreeCommandBuffers( + VkDevice device, + VkCommandPool commandPool, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + auto& tracker = layer->getStateTracker(); + for (uint32_t i = 0; i < commandBufferCount; i++) + { + tracker.freeCommandBuffer(commandPool, pCommandBuffers[i]); + } + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkFreeCommandBuffers( + device, commandPool, commandBufferCount, pCommandBuffers); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdExecuteCommands( + VkCommandBuffer commandBuffer, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store and device-wide data + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& primary = tracker.getCommandBuffer(commandBuffer); + auto& primaryStats = primary.getStats(); + + for (uint32_t i = 0; i < commandBufferCount; i++) + { + auto& secondary = tracker.getCommandBuffer(pCommandBuffers[i]); + auto& secondaryStats = secondary.getStats(); + primaryStats.mergeCounts(secondaryStats); + } + + // Release the lock to call into the main driver + lock.unlock(); + layer->driver.vkCmdExecuteCommands( + commandBuffer, commandBufferCount, pCommandBuffers); +} diff --git a/layer_gpu_timeline/source/layer_device_functions_command_pool.cpp b/layer_gpu_timeline/source/layer_device_functions_command_pool.cpp new file mode 100644 index 0000000..31bc1b4 --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_command_pool.cpp @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" + +extern std::mutex g_vulkanLock; + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateCommandPool( + VkDevice device, + const VkCommandPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkCommandPool* pCommandPool +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + // Release the lock to call into the driver + lock.unlock(); + VkResult result = layer->driver.vkCreateCommandPool( + device, pCreateInfo, pAllocator, pCommandPool); + if (result != VK_SUCCESS) + { + return result; + } + + // Retake the lock to access layer-wide global store + lock.lock(); + auto& tracker = layer->getStateTracker(); + tracker.createCommandPool(*pCommandPool); + return result; +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkResetCommandPool( + VkDevice device, + VkCommandPool commandPool, + VkCommandPoolResetFlags flags +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + auto& tracker = layer->getStateTracker(); + tracker.getCommandPool(commandPool).reset(); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkResetCommandPool(device, commandPool, flags); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkDestroyCommandPool( + VkDevice device, + VkCommandPool commandPool, + const VkAllocationCallbacks* pAllocator +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + auto& tracker = layer->getStateTracker(); + tracker.destroyCommandPool(commandPool); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkDestroyCommandPool(device, commandPool, pAllocator); +} diff --git a/layer_gpu_timeline/source/layer_device_functions_debug.cpp b/layer_gpu_timeline/source/layer_device_functions_debug.cpp new file mode 100644 index 0000000..64461dd --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_debug.cpp @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" + +extern std::mutex g_vulkanLock; + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDebugMarkerBeginEXT( + VkCommandBuffer commandBuffer, + const VkDebugMarkerMarkerInfoEXT* pMarkerInfo +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + + // Increment the render pass counter in the tracker + cb.debugMarkerBegin(pMarkerInfo->pMarkerName); + + // Note that we do not call the driver for user labels - they are + // emitted via the comms side-channel for each workload to avoid + // polluting the driver tag labelling +} + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDebugMarkerEndEXT( + VkCommandBuffer commandBuffer +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + + // Increment the render pass counter in the tracker + cb.debugMarkerEnd(); + + // Note that we do not call the driver for user labels - they are + // emitted via the comms side-channel for each workload to avoid + // polluting the driver tag labelling +} \ No newline at end of file diff --git a/layer_gpu_timeline/source/layer_device_functions_dispatch.cpp b/layer_gpu_timeline/source/layer_device_functions_dispatch.cpp new file mode 100644 index 0000000..88bf568 --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_dispatch.cpp @@ -0,0 +1,131 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" + +extern std::mutex g_vulkanLock; + +static void registerDispatch( + Device* layer, + VkCommandBuffer commandBuffer +) { + auto& state = layer->getStateTracker(); + auto& stats = state.getCommandBuffer(commandBuffer).getStats(); + stats.incDispatchCount(); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatch( + VkCommandBuffer commandBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDispatch(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDispatch(commandBuffer, groupCountX, groupCountY, groupCountZ); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatchBase( + VkCommandBuffer commandBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDispatch(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDispatchBase(commandBuffer, baseGroupX, baseGroupY, baseGroupZ, groupCountX, groupCountY, groupCountZ); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatchBaseKHR( + VkCommandBuffer commandBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDispatch(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDispatchBaseKHR(commandBuffer, baseGroupX, baseGroupY, baseGroupZ, groupCountX, groupCountY, groupCountZ); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDispatchIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDispatch(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDispatchIndirect(commandBuffer, buffer, offset); +} diff --git a/layer_gpu_timeline/source/layer_device_functions_draw_call.cpp b/layer_gpu_timeline/source/layer_device_functions_draw_call.cpp new file mode 100644 index 0000000..49cf669 --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_draw_call.cpp @@ -0,0 +1,251 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" + +extern std::mutex g_vulkanLock; + +static void registerDrawCall( + Device* layer, + VkCommandBuffer commandBuffer +) { + auto& state = layer->getStateTracker(); + auto& stats = state.getCommandBuffer(commandBuffer).getStats(); + stats.incDrawCallCount(); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDraw( + VkCommandBuffer commandBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDraw(commandBuffer, vertexCount, instanceCount, firstVertex, firstInstance); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexed( + VkCommandBuffer commandBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndexed(commandBuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexedIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + uint32_t drawCount, + uint32_t stride +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndexedIndirect(commandBuffer, buffer, offset, drawCount, stride); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexedIndirectCount( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndexedIndirectCountKHR( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndexedIndirectCountKHR(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirect( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + uint32_t drawCount, + uint32_t stride +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndirect(commandBuffer, buffer, offset, drawCount, stride); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirectByteCountEXT( + VkCommandBuffer commandBuffer, + uint32_t instanceCount, + uint32_t firstInstance, + VkBuffer counterBuffer, + VkDeviceSize counterBufferOffset, + uint32_t counterOffset, + uint32_t vertexStride +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndirectByteCountEXT(commandBuffer, instanceCount, firstInstance, counterBuffer, counterBufferOffset, counterOffset, vertexStride); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirectCount( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdDrawIndirectCountKHR( + VkCommandBuffer commandBuffer, + VkBuffer buffer, + VkDeviceSize offset, + VkBuffer countBuffer, + VkDeviceSize countBufferOffset, + uint32_t maxDrawCount, + uint32_t stride +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerDrawCall(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDrawIndirectCountKHR(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride); +} diff --git a/layer_gpu_timeline/source/layer_device_functions_queue.cpp b/layer_gpu_timeline/source/layer_device_functions_queue.cpp new file mode 100644 index 0000000..906a39e --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_queue.cpp @@ -0,0 +1,129 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" +#include "utils/misc.hpp" + +extern std::mutex g_vulkanLock; + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(queue); + + auto& tracker = layer->getStateTracker(); + tracker.queuePresent(); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkQueuePresentKHR(queue, pPresentInfo); +} + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueueSubmit( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo* pSubmits, + VkFence fence +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(queue); + + using namespace std::placeholders; + auto onSubmit = std::bind(&Device::onWorkloadSubmit, layer, _1); + + auto& tracker = layer->getStateTracker(); + auto& trackQueue = tracker.getQueue(queue); + + for (uint32_t i = 0; i < submitCount; i++) + { + const auto& submit = pSubmits[i]; + for (uint32_t j = 0; j < submit.commandBufferCount; j++) + { + auto& trackCB = tracker.getCommandBuffer(submit.pCommandBuffers[j]); + const auto& LCS = trackCB.getSubmitCommandStream(); + + trackQueue.runSubmitCommandStream(LCS, onSubmit); + } + } + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkQueueSubmit(queue, submitCount, pSubmits, fence); +} + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueueSubmit2( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo2* pSubmits, + VkFence fence +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(queue); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkQueueSubmit2(queue, submitCount, pSubmits, fence); +} + +/* See Vulkan API for documentation. */ +template<> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkQueueSubmit2KHR( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo2* pSubmits, + VkFence fence +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(queue); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkQueueSubmit2KHR(queue, submitCount, pSubmits, fence); +} diff --git a/layer_gpu_timeline/source/layer_device_functions_render_pass.cpp b/layer_gpu_timeline/source/layer_device_functions_render_pass.cpp new file mode 100644 index 0000000..9350e6c --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_render_pass.cpp @@ -0,0 +1,367 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" +#include "framework/utils.hpp" + +extern std::mutex g_vulkanLock; + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateRenderPass( + VkDevice device, + const VkRenderPassCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkCreateRenderPass(device, pCreateInfo, pAllocator, pRenderPass); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateRenderPass2( + VkDevice device, + const VkRenderPassCreateInfo2* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkCreateRenderPass2(device, pCreateInfo, pAllocator, pRenderPass); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateRenderPass2KHR( + VkDevice device, + const VkRenderPassCreateInfo2* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkRenderPass* pRenderPass +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkCreateRenderPass2KHR(device, pCreateInfo, pAllocator, pRenderPass); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkDestroyRenderPass( + VkDevice device, + VkRenderPass renderPass, + const VkAllocationCallbacks* pAllocator +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkDestroyRenderPass(device, renderPass, pAllocator); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderPass( + VkCommandBuffer commandBuffer, + const VkRenderPassBeginInfo* pRenderPassBegin, + VkSubpassContents contents +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + + // Notify the command buffer we are starting a new render pass + uint64_t tagID = cb.renderPassBegin(); + + // Emit the unique workload tag into the command stream + std::string tagLabel = formatString("t%" PRIu64, tagID); + VkDebugMarkerMarkerInfoEXT tagInfo { + .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, + .pNext = nullptr, + .pMarkerName = tagLabel.c_str(), + .color = { 0.0f, 0.0f, 0.0f, 0.0f } + }; + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDebugMarkerBeginEXT(commandBuffer, &tagInfo); + layer->driver.vkCmdBeginRenderPass(commandBuffer, pRenderPassBegin, contents); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderPass2( + VkCommandBuffer commandBuffer, + const VkRenderPassBeginInfo* pRenderPassBegin, + const VkSubpassBeginInfo* pSubpassBeginInfo +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + + // Notify the command buffer we are starting a new render pass + uint64_t tagID = cb.renderPassBegin(); + + // Emit the unique workload tag into the command stream + std::string tagLabel = formatString("t%" PRIu64, tagID); + VkDebugMarkerMarkerInfoEXT tagInfo { + .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, + .pNext = nullptr, + .pMarkerName = tagLabel.c_str(), + .color = { 0.0f, 0.0f, 0.0f, 0.0f } + }; + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDebugMarkerBeginEXT(commandBuffer, &tagInfo); + layer->driver.vkCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderPass2KHR( + VkCommandBuffer commandBuffer, + const VkRenderPassBeginInfo* pRenderPassBegin, + const VkSubpassBeginInfo* pSubpassBeginInfo +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + + // Notify the command buffer we are starting a new render pass + uint64_t tagID = cb.renderPassBegin(); + + // Emit the unique workload tag into the command stream + std::string tagLabel = formatString("t%" PRIu64, tagID); + VkDebugMarkerMarkerInfoEXT tagInfo { + .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, + .pNext = nullptr, + .pMarkerName = tagLabel.c_str(), + .color = { 0.0f, 0.0f, 0.0f, 0.0f } + }; + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdDebugMarkerBeginEXT(commandBuffer, &tagInfo); + layer->driver.vkCmdBeginRenderPass2KHR(commandBuffer, pRenderPassBegin, pSubpassBeginInfo); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRendering( + VkCommandBuffer commandBuffer, + const VkRenderingInfo* pRenderingInfo +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + + bool resuming = pRenderingInfo->flags & VK_RENDERING_RESUMING_BIT; + bool suspending = pRenderingInfo->flags & VK_RENDERING_SUSPENDING_BIT; + + // Notify the command buffer we are starting a new render pass + uint64_t tagID = cb.renderPassBegin(resuming, suspending); + + // Release the lock to call into the driver + lock.unlock(); + + // Emit the label only for new render passes + if (!resuming) + { + // Emit the unique workload tag into the command stream + std::string tagLabel = formatString("t%" PRIu64, tagID); + VkDebugMarkerMarkerInfoEXT tagInfo { + .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, + .pNext = nullptr, + .pMarkerName = tagLabel.c_str(), + .color = { 0.0f, 0.0f, 0.0f, 0.0f } + }; + + layer->driver.vkCmdDebugMarkerBeginEXT(commandBuffer, &tagInfo); + } + + layer->driver.vkCmdBeginRendering(commandBuffer, pRenderingInfo); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginRenderingKHR( + VkCommandBuffer commandBuffer, + const VkRenderingInfo* pRenderingInfo +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + + bool resuming = pRenderingInfo->flags & VK_RENDERING_RESUMING_BIT; + bool suspending = pRenderingInfo->flags & VK_RENDERING_SUSPENDING_BIT; + + // Notify the command buffer we are starting a new render pass + uint64_t tagID = cb.renderPassBegin(resuming, suspending); + + // Release the lock to call into the driver + lock.unlock(); + + // Emit the label only for new render passes + if (!resuming) + { + // Emit the unique workload tag into the command stream + std::string tagLabel = formatString("t%" PRIu64, tagID); + VkDebugMarkerMarkerInfoEXT tagInfo { + .sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT, + .pNext = nullptr, + .pMarkerName = tagLabel.c_str(), + .color = { 0.0f, 0.0f, 0.0f, 0.0f } + }; + + layer->driver.vkCmdDebugMarkerBeginEXT(commandBuffer, &tagInfo); + } + + layer->driver.vkCmdBeginRenderingKHR(commandBuffer, pRenderingInfo); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdEndRenderPass( + VkCommandBuffer commandBuffer +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + // Update the layer command stream in the tracker + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + cb.renderPassEnd(); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdEndRenderPass(commandBuffer); + layer->driver.vkCmdDebugMarkerEndEXT(commandBuffer); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdEndRendering( + VkCommandBuffer commandBuffer +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + // Update the layer command stream in the tracker + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + bool suspending = cb.renderPassEnd(); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdEndRendering(commandBuffer); + if (!suspending) + { + layer->driver.vkCmdDebugMarkerEndEXT(commandBuffer); + } +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdEndRenderingKHR( + VkCommandBuffer commandBuffer +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + // Update the layer command stream in the tracker + auto& tracker = layer->getStateTracker(); + auto& cb = tracker.getCommandBuffer(commandBuffer); + bool suspending = cb.renderPassEnd(); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdEndRenderingKHR(commandBuffer); + if (!suspending) + { + layer->driver.vkCmdDebugMarkerEndEXT(commandBuffer); + } +} diff --git a/layer_gpu_timeline/source/layer_device_functions_trace_rays.cpp b/layer_gpu_timeline/source/layer_device_functions_trace_rays.cpp new file mode 100644 index 0000000..5373747 --- /dev/null +++ b/layer_gpu_timeline/source/layer_device_functions_trace_rays.cpp @@ -0,0 +1,109 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include "device.hpp" +#include "layer_device_functions.hpp" + +extern std::mutex g_vulkanLock; + +static void registerTraceRays( + Device* layer, + VkCommandBuffer commandBuffer +) { + auto& state = layer->getStateTracker(); + auto& stats = state.getCommandBuffer(commandBuffer).getStats(); + stats.incTraceRaysCount(); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdTraceRaysIndirect2KHR( + VkCommandBuffer commandBuffer, + VkDeviceAddress indirectDeviceAddress +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerTraceRays(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdTraceRaysIndirect2KHR(commandBuffer, indirectDeviceAddress); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdTraceRaysIndirectKHR( + VkCommandBuffer commandBuffer, + const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable, + VkDeviceAddress indirectDeviceAddress +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerTraceRays(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdTraceRaysIndirectKHR(commandBuffer, pRaygenShaderBindingTable, pMissShaderBindingTable, pHitShaderBindingTable, pCallableShaderBindingTable, indirectDeviceAddress); +} + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR void VKAPI_CALL layer_vkCmdTraceRaysKHR( + VkCommandBuffer commandBuffer, + const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable, + const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable, + uint32_t width, + uint32_t height, + uint32_t depth +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(commandBuffer); + + registerTraceRays(layer, commandBuffer); + + // Release the lock to call into the driver + lock.unlock(); + layer->driver.vkCmdTraceRaysKHR(commandBuffer, pRaygenShaderBindingTable, pMissShaderBindingTable, pHitShaderBindingTable, pCallableShaderBindingTable, width, height, depth); +} \ No newline at end of file diff --git a/layer_gpu_timeline/source/timeline_comms.cpp b/layer_gpu_timeline/source/timeline_comms.cpp new file mode 100644 index 0000000..2811c5d --- /dev/null +++ b/layer_gpu_timeline/source/timeline_comms.cpp @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include + +#include "timeline_comms.hpp" + +void TimelineComms::init( + Comms::CommsInterface& commsIf) +{ + comms = &commsIf; + if (comms->isConnected()) + { + endpoint = comms->getEndpointID("GPUTimeline"); + } +} + +void TimelineComms::txMessage( + const std::string& message) +{ + // Message endpoint is not available + if (endpoint == 0) + { + return; + } + + auto data = std::make_unique(message.begin(), message.end()); + comms->txAsync(endpoint, std::move(data)); +} diff --git a/layer_gpu_timeline/source/timeline_comms.hpp b/layer_gpu_timeline/source/timeline_comms.hpp new file mode 100644 index 0000000..cbc107f --- /dev/null +++ b/layer_gpu_timeline/source/timeline_comms.hpp @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#pragma once + +#include "comms/comms_interface.hpp" + +class TimelineComms +{ +public: + void init( + Comms::CommsInterface& comms); + + void txMessage( + const std::string& message); + +private: + Comms::EndpointID endpoint { 0 }; + Comms::CommsInterface* comms { nullptr }; +}; diff --git a/layer_gpu_timeline/source/version.hpp.in b/layer_gpu_timeline/source/version.hpp.in new file mode 100644 index 0000000..50c30b9 --- /dev/null +++ b/layer_gpu_timeline/source/version.hpp.in @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * @file + * This header implements placeholder templates that are populated by CMake + * during configure. + */ + +#pragma once + +#define LGL_VER_MAJOR @PROJECT_VERSION_MAJOR@ +#define LGL_VER_MINOR @PROJECT_VERSION_MINOR@ +#define LGL_VER_PATCH @PROJECT_VERSION_PATCH@ +#define LGL_LAYER_NAME "@LGL_LAYER_NAME_STR@" +#define LGL_LAYER_DESC "@LGL_LAYER_DESC_STR@" diff --git a/lgl_host_server.py b/lgl_host_server.py index c7c1382..14d5941 100644 --- a/lgl_host_server.py +++ b/lgl_host_server.py @@ -32,6 +32,7 @@ import threading import lglpy.server +import lglpy.service_gpu_timeline import lglpy.service_test import lglpy.service_log @@ -41,13 +42,18 @@ def main(): # Register all the services with it print(f'Registering host services:') - test_service = lglpy.service_test.TestService() - endpoint_id = server.register_endpoint(test_service) - print(f' - [{endpoint_id}] = {test_service.get_service_name()}') + service = lglpy.service_test.TestService() + endpoint_id = server.register_endpoint(service) + print(f' - [{endpoint_id}] = {service.get_service_name()}') + + service = lglpy.service_log.LogService() + endpoint_id = server.register_endpoint(service) + print(f' - [{endpoint_id}] = {service.get_service_name()}') + + service = lglpy.service_gpu_timeline.GPUTimelineService() + endpoint_id = server.register_endpoint(service) + print(f' - [{endpoint_id}] = {service.get_service_name()}') - log_service = lglpy.service_log.LogService() - endpoint_id = server.register_endpoint(log_service) - print(f' - [{endpoint_id}] = {log_service.get_service_name()}') print() # Start it running diff --git a/lglpy/service_gpu_timeline.py b/lglpy/service_gpu_timeline.py new file mode 100644 index 0000000..69ac031 --- /dev/null +++ b/lglpy/service_gpu_timeline.py @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: MIT +# ----------------------------------------------------------------------------- +# Copyright (c) 2024 Arm Limited +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- + +# This module implements the server-side communications module service that +# implements a basic message endpoint for testing. + +from lglpy.server import Message + +class GPUTimelineService: + + def __init__(self): + pass + + def get_service_name(self) -> str: + return 'GPUTimeline' + + def handle_message(self, message: Message): + payload = message.payload.decode('utf-8') + + print(f'{message.message_type.name}: {payload} ({len(payload)} bytes)') + + return None diff --git a/source_common/CMakeLists.txt b/source_common/CMakeLists.txt index 1f37093..a408717 100644 --- a/source_common/CMakeLists.txt +++ b/source_common/CMakeLists.txt @@ -26,3 +26,4 @@ # Device classes which get specialized for each use case. add_subdirectory(comms) +add_subdirectory(trackers) diff --git a/source_common/trackers/CMakeLists.txt b/source_common/trackers/CMakeLists.txt new file mode 100644 index 0000000..0e703f6 --- /dev/null +++ b/source_common/trackers/CMakeLists.txt @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: MIT +# ----------------------------------------------------------------------------- +# Copyright (c) 2024 Arm Limited +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ----------------------------------------------------------------------------- + +set(LIB_BINARY lib_layer_trackers) + +add_library( + ${LIB_BINARY} STATIC + command_buffer.cpp + device.cpp + layer_command_stream.cpp + queue.cpp) + +target_include_directories( + ${LIB_BINARY} PRIVATE + ../../khronos/vulkan/include + ../../source_third_party + ../) + +lgl_set_build_options(${LIB_BINARY}) + +# No unit tests for this module yet +#if(${LGL_UNITTEST}) +# add_subdirectory(test) +#endif() + diff --git a/source_common/trackers/command_buffer.cpp b/source_common/trackers/command_buffer.cpp new file mode 100644 index 0000000..11cddf5 --- /dev/null +++ b/source_common/trackers/command_buffer.cpp @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include + +#include "trackers/command_buffer.hpp" +#include "framework/utils.hpp" + +namespace Tracker +{ + +/* See header for documentation. */ +void CommandBuffer::reset() +{ + stats.reset(); + workloads.clear(); + workloadCommandStream.clear(); +} + +/** + * @brief Begin a user debug marker range. + */ +void CommandBuffer::debugMarkerBegin( + std::string marker +) { + // Create a workload we can reference later + auto workload = std::make_shared(marker); + workloads.push_back(workload); + + // Add command to update queue debug stack on submit + auto instr = std::make_pair(LCSOpcode::MARKER_BEGIN, workload); + workloadCommandStream.push_back(instr); +} + +/** + * @brief End a user debug marker range. + */ +void CommandBuffer::debugMarkerEnd() +{ + // Add command with empty workload to update queue debug stack on submit + auto workload = std::shared_ptr(); + auto instr = std::make_pair(LCSOpcode::MARKER_END, workload); + workloadCommandStream.push_back(instr); +} + +/** + * @brief End a user render pass. + */ +uint64_t CommandBuffer::renderPassBegin( + bool resuming, + bool suspending +) { + uint64_t tagID { 0 }; + + // Assign ID and update the stats tracker for new render passes only + if (!resuming) + { + tagID = Tracker::LCSWorkload::getTagID(); + stats.incRenderPassCount(); + } + + // TODO: Populate render pass with config information + auto workload = std::make_shared( + tagID, + suspending); + workloads.push_back(workload); + + auto instr = std::make_pair(LCSOpcode::RENDERPASS_BEGIN, workload); + workloadCommandStream.push_back(instr); + + return tagID; +} + +/** + * @brief End a user render pass. + */ +bool CommandBuffer::renderPassEnd() +{ + LCSWorkload* workload = workloads.back().get(); + LCSRenderPass* renderpass = dynamic_cast(workload); + assert(renderpass != nullptr); + + // TODO: Merge stats since begin into the workload object + return renderpass->isSuspending(); +} + +/* See header for documentation. */ +CommandBuffer& CommandPool::allocateCommandBuffer( + VkCommandBuffer commandBuffer +) { + auto result = commandBuffers.insert({ + commandBuffer, + std::make_unique(commandBuffer) + }); + + // Validate that insertion worked + assert(result.second); + + // Return the created command buffer + return *result.first->second.get(); +} + +/* See header for documentation. */ +void CommandPool::freeCommandBuffer( + VkCommandBuffer commandBuffer +) { + commandBuffers.erase(commandBuffer); +} + +/* See header for documentation. */ +void CommandPool::reset() +{ + for (auto& commandBuffer : commandBuffers) + { + commandBuffer.second->reset(); + } +} + +} diff --git a/source_common/trackers/command_buffer.hpp b/source_common/trackers/command_buffer.hpp new file mode 100644 index 0000000..7b88e0d --- /dev/null +++ b/source_common/trackers/command_buffer.hpp @@ -0,0 +1,198 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * \file + * The declaration of Vulkan command pool and command buffer use trackers. + * + * Role summary + * ============ + * + * These trackers are used to monitor the use of command buffers in a frame, + * allowing us to monitor command buffer payloads submitted to a queue. + * + * Key properties + * ============== + * + * Command pools and Command buffers are both lock-free from a single app + * thread, relying on external synchronization above the API if multi-threaded + * use is required. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "trackers/stats.hpp" +#include "trackers/layer_command_stream.hpp" + +namespace Tracker +{ + +/** + * @brief The state tracker for a single command buffer. + */ +class CommandBuffer +{ +public: + /** + * @brief Construct a new command buffer wrapping a Vulkan allocation. + * + * @param handle The Vulkan command buffer handle we are tracking. + */ + CommandBuffer( + VkCommandBuffer handle) : + handle(handle) { }; + + /** + * @brief Get the stats object for this command buffer; + */ + Stats& getStats() + { + return stats; + } + + /** + * @brief Get the layer submit-time command stream for this command buffer. + */ + const std::vector& getSubmitCommandStream() const + { + return workloadCommandStream; + } + + /** + * @brief Begin recording a render pass. + * + * @param resuming If @c true this recording starts with a resume. + * @param suspending If @c true this recording ends with a suspend. + * + * @return Returns the tagID assigned to this workload. Always returns 0 + * if @c resuming an existing workload. + */ + uint64_t renderPassBegin( + bool resuming=false, + bool suspending=false); + + /** + * @brief End the current render pass workload recording. + * + * @return Returns @c true if this is a suspending dynamic render pass or + * @c false otherwise. + */ + bool renderPassEnd(); + + /** + * @brief Begin a user debug marker range. + */ + void debugMarkerBegin( + std::string marker); + + /** + * @brief End a user debug marker range. + */ + void debugMarkerEnd(); + + /** + * @brief Reset the command buffer back into the @a Initial state. + */ + void reset(); + +private: + /** + * @brief The Vulkan API handle of this command buffer. + */ + const VkCommandBuffer handle; + + /** + * @brief The cumulative stats of the commands in this command buffer. + */ + Stats stats; + + /** + * @brief The recorded workloads. + */ + std::vector> workloads; + + /** + * @brief The recorded commands. + */ + std::vector workloadCommandStream; +}; + +/** + * @brief The state tracker for a single command pool. + */ +class CommandPool +{ +public: + /** + * @brief Construct a new command pool wrapping a Vulkan allocation. + * + * @param handle The Vulkan pool buffer handle we are wrapping. + */ + CommandPool( + VkCommandPool handle) : + handle(handle) { }; + + /** + * @brief Allocate a command buffer in the pool with the given handle. + * + * @param commandBuffer The Vulkan handle of the allocated command buffer. + * + * \return The layer wrapper object for the command buffer. + */ + CommandBuffer& allocateCommandBuffer(VkCommandBuffer commandBuffer); + + /** + * @brief Free the command buffer in the pool with the given handle. + * + * @param commandBuffer The Vulkan handle of the command buffer to free. + */ + void freeCommandBuffer(VkCommandBuffer commandBuffer); + + /** + * @brief Reset all allocated command buffers into the @a Initial state. + */ + void reset(); + +private: + /** + * @brief The Vulkan API handle of this command pool. + */ + const VkCommandPool handle; + + /** + * @brief The command buffers currently allocated in this command pool. + */ + std::unordered_map> commandBuffers; +}; + +} diff --git a/source_common/trackers/device.cpp b/source_common/trackers/device.cpp new file mode 100644 index 0000000..343708d --- /dev/null +++ b/source_common/trackers/device.cpp @@ -0,0 +1,138 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include + +#include "trackers/device.hpp" +#include "utils/misc.hpp" + +namespace Tracker +{ + +/* See header for documentation. */ +void Device::createCommandPool( + VkCommandPool commandPool +) { + commandPools.insert({ + commandPool, + std::make_unique(commandPool) + }); +} + +/* See header for documentation. */ +CommandPool& Device::getCommandPool( + VkCommandPool commandPool +) { + assert(isInMap(commandPool, commandPools)); + return *commandPools.at(commandPool); +} + +/* See header for documentation. */ +void Device::allocateCommandBuffer( + VkCommandPool commandPool, + VkCommandBuffer commandBuffer +) { + // Allocate in the pool + auto& pool = getCommandPool(commandPool); + auto& buffer = pool.allocateCommandBuffer(commandBuffer); + + // Insert into the tracker lookup map + [[maybe_unused]] auto result = commandBuffers.insert({ + commandBuffer, + buffer + }); + + assert(result.second); +} + +/* See header for documentation. */ +void Device::freeCommandBuffer( + VkCommandPool commandPool, + VkCommandBuffer commandBuffer +) { + // Remove from the tracker lookup map + commandBuffers.erase(commandBuffer); + + // Remove from the command pool + auto& pool = getCommandPool(commandPool); + pool.freeCommandBuffer(commandBuffer); +} + +/* See header for documentation. */ +CommandBuffer& Device::getCommandBuffer( + VkCommandBuffer commandBuffer +) { + assert(isInMap(commandBuffer, commandBuffers)); + return commandBuffers.at(commandBuffer); +} + +/* See header for documentation. */ +void Device::destroyCommandPool( + VkCommandPool commandPool +) { + commandPools.erase(commandPool); +} + +/* See header for documentation. */ +Queue& Device::getQueue( + VkQueue queue +) { + // Create a tracker for a queue on first use + if (!isInMap(queue, queues)) + { + queues.insert({ + queue, + std::make_unique(queue) + }); + } + + return *queues.at(queue); +} + +/* See header for documentation. */ +void Device::queueSubmit( + VkCommandBuffer commandBuffer +) { + auto& cbStats = getCommandBuffer(commandBuffer).getStats(); + frameStats.mergeCounts(cbStats); +} + + +/* See header for documentation. */ +void Device::queuePresent() +{ + // Update cumulative statistics with the frame statistics + totalStats.incFrameCount(); + totalStats.mergeCounts(frameStats); + + // Reset the frame statistics ready for the next frame + frameStats.reset(); +} + +} diff --git a/source_common/trackers/device.hpp b/source_common/trackers/device.hpp new file mode 100644 index 0000000..26b6a2e --- /dev/null +++ b/source_common/trackers/device.hpp @@ -0,0 +1,166 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * \file + * This module implements basic tracking of Vulkan devices. + * + * Role summary + * ============ + * + * Trackers are used to monitor the use of a device and the various resources + * that exist in the scope of a device. Primarily we use them to implement + * counters that layers can to either emit statistics or be used to trigger + * other layer behavior when a specific count is reached. + */ + +#pragma once + +#include +#include + +#include "trackers/command_buffer.hpp" +#include "trackers/queue.hpp" + +namespace Tracker +{ + +/** + * @brief The state tracker for a single device. + */ +class Device +{ +public: + /** + * @brief Create a new command pool within this device. + * + * @param commandPool The command pool handle to create. + */ + void createCommandPool( + VkCommandPool commandPool); + + /** + * @brief Get the command pool tracker for a Vulkan command pool. + * + * @param commandPool The device handle the layer is tracking. + */ + CommandPool& getCommandPool( + VkCommandPool commandPool); + + /** + * @brief Create a new command buffer in a pool within this device. + * + * @param commandPool The owning command pool. + * @param commandBuffer The device handle the layer is tracking. + */ + void allocateCommandBuffer( + VkCommandPool commandPool, + VkCommandBuffer commandBuffer); + + /** + * @brief Free a command buffer in a pool within this device. + * + * @param commandPool The owning command pool. + * @param commandBuffer The device handle the layer is tracking. + */ + void freeCommandBuffer( + VkCommandPool commandPool, + VkCommandBuffer commandBuffer); + + /** + * @brief Get the command pool tracker for a Vulkan command pool. + * + * @param commandBuffer The device handle the layer is tracking. + */ + CommandBuffer& getCommandBuffer( + VkCommandBuffer commandBuffer); + + /** + * @brief Destroy a command pool within this device. + * + * @param commandPool The command pool handle to destroy. + */ + void destroyCommandPool( + VkCommandPool commandPool); + + /** + * @brief Get the queue tracker for a Vulkan queue. + * + * Note that queue trackers are created on the fly when the queue is + * first used. + * + * @param queue The queue handle the layer is tracking. + */ + Queue& getQueue( + VkQueue queue); + + /** + * @brief Submit a command buffer to a queue within this device. + * + * @param commandBuffer The command buffer handle to submit. + */ + void queueSubmit( + VkCommandBuffer commandBuffer); + + /** + * @brief Submit a display present command to a queue within this device. + */ + void queuePresent(); + +public: + /** + * @brief The set of all queues allocated in this device. + */ + std::unordered_map> queues; + + /** + * @brief The set of all command pools allocated in this device. + */ + std::unordered_map> commandPools; + + /** + * @brief The set of all command buffers allocated in this device. + * + * Note - memory ownership is via the CommandPool, so dispatch references in this map + * must be removed before deleting the command pool that owns the buffer. + */ + std::unordered_map commandBuffers; + + /** + * @brief The cumulative statistics for this device. + * + * Only updated on submit to a present queue. + */ + Stats totalStats; + + /** + * @brief The current frame statistics for this device. + * + * Only updated on submit to a queue. + */ + Stats frameStats; +}; + +} diff --git a/source_common/trackers/layer_command_stream.cpp b/source_common/trackers/layer_command_stream.cpp new file mode 100644 index 0000000..b6a9193 --- /dev/null +++ b/source_common/trackers/layer_command_stream.cpp @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include + +#include "trackers/layer_command_stream.hpp" + +namespace Tracker +{ +/* See header for details. */ +std::atomic LCSWorkload::nextTagID { 1 }; +} diff --git a/source_common/trackers/layer_command_stream.hpp b/source_common/trackers/layer_command_stream.hpp new file mode 100644 index 0000000..f7799a8 --- /dev/null +++ b/source_common/trackers/layer_command_stream.hpp @@ -0,0 +1,173 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * \file + * The declaration of Vulkan command pool and command buffer use trackers. + * + * Role summary + * ============s + * + * These trackers are used to monitor the use of command buffers in a frame, + * allowing us to monitor command buffer payloads submitted to a queue. + * + * Key properties + * ============== + * + * Command pools and Command buffers are both lock-free from a single app + * thread, relying on external synchronization above the API if multi-threaded + * use is required. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "trackers/stats.hpp" +#include "utils/misc.hpp" + +namespace Tracker +{ + +/** + * @brief Enumeration of layer command stream opcodes. + */ +enum class LCSOpcode +{ + MARKER_BEGIN, + MARKER_END, + RENDERPASS_BEGIN, + RENDERPASS_END, // TODO: Does this need to be an opcode? + DISPATCH, + TRACE_RAYS, + BUFFER_TRANSFER, + IMAGE_TRANSFER +}; + +/** + * @brief Baseclass representing a GPU workload in the command stream. + */ +class LCSWorkload +{ +public: + LCSWorkload( + uint64_t tagID): + tagID(tagID) { }; + + virtual ~LCSWorkload() = default; + + virtual std::string getMetadata() const = 0; + + /** + * @brief Get a unique tagID to label a workload in a command buffer. + * + * @return The assigned ID. + */ + static uint64_t getTagID() + { + return nextTagID.fetch_add(1, std::memory_order_relaxed); + } + +protected: + /** + * @brief The assigned tagID for this workload. + * + * Render pass continuations are assigned tagID of zero. + */ + uint64_t tagID; + +private: + /** + * @brief The workload tagID allocator. + */ + static std::atomic nextTagID; +}; + +/** + * @brief Baseclass representing a GPU workload in the command stream. + */ +class LCSRenderPass : public LCSWorkload +{ +public: + LCSRenderPass( + uint64_t tagID, + bool suspending) : + LCSWorkload(tagID), + suspending(suspending) { }; + + + virtual ~LCSRenderPass() = default; + + bool isSuspending() const + { + return suspending; + }; + + virtual std::string getMetadata() const + { + return formatString("Render Pass: t%" PRIu64, tagID); + }; + +private: + bool suspending; +}; + +/** + * @brief Baseclass representing a GPU workload in the command stream. + */ +class LCSMarker : public LCSWorkload +{ +public: + LCSMarker( + const std::string& label) : + LCSWorkload(0), + label(label) { + + }; + + virtual ~LCSMarker() = default; + + virtual std::string getMetadata() const + { + return label; + }; + +private: + std::string label; +}; + +/** + * @brief Instructions are an opcode with a data pointer. + * + * Data pointers may be null for some opcodes. + */ +using LCSInstruction = std::pair>; +} diff --git a/source_common/trackers/queue.cpp b/source_common/trackers/queue.cpp new file mode 100644 index 0000000..77d2d67 --- /dev/null +++ b/source_common/trackers/queue.cpp @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#include + +#include "trackers/queue.hpp" + +namespace Tracker +{ +/* See header for details. */ +std::atomic Queue::nextSubmitID { 1 }; + +/* See header for details. */ +void Queue::runSubmitCommandStream( + const std::vector& stream, + std::function callback +) { + for (auto& instr: stream) + { + LCSOpcode opCode = instr.first; + const LCSWorkload* opData = instr.second.get(); + + if (opCode == LCSOpcode::MARKER_BEGIN) + { + debugStack.push_back(opData->getMetadata()); + } + else if (opCode == LCSOpcode::MARKER_END) + { + debugStack.pop_back(); + } + else if (opCode == LCSOpcode::RENDERPASS_BEGIN) + { + auto* workload = dynamic_cast(opData); + callback(workload->getMetadata()); + std::string log = joinString(debugStack, "|"); + } + } +} + +} diff --git a/source_common/trackers/queue.hpp b/source_common/trackers/queue.hpp new file mode 100644 index 0000000..9c21c52 --- /dev/null +++ b/source_common/trackers/queue.hpp @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * \file + * The declaration of Vulkan queue use trackers. + * + * Role summary + * ============ + * + * These trackers are used to monitor the use of a queue. + * + * Key properties + * ============== + * + * Queues are lock-free from a single app thread, relying on external + * synchronization above the API if multi-threaded use is required. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "framework/utils.hpp" +#include "trackers/layer_command_stream.hpp" + +namespace Tracker +{ + +/** + * @brief The state tracker for a queue. + */ +class Queue +{ +public: + Queue( + VkQueue handle): + handle(handle) { }; + + /** + * @brief Execute a layer command stream. + */ + void runSubmitCommandStream( + const std::vector& stream, + std::function callback); + + /** + * @brief Get a unique submitID to label a command buffer submit. + * + * @return The assigned ID. + */ + static uint64_t getSubmitID() + { + return nextSubmitID.fetch_add(1, std::memory_order_relaxed); + } + +private: + /** + * The handle of the native queue we are wrapping. + */ + VkQueue handle; + + /** + * @brief The stack of debug labels in the tool. + */ + std::vector debugStack; + + /** + * @brief The command buffer submitID allocator. + */ + static std::atomic nextSubmitID; +}; + +} diff --git a/source_common/trackers/stats.hpp b/source_common/trackers/stats.hpp new file mode 100644 index 0000000..2d297ed --- /dev/null +++ b/source_common/trackers/stats.hpp @@ -0,0 +1,227 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2022-2024 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +/** + * \file + * This module implements basic counter tracking of Vulkan workloads. + */ + +#pragma once + +#include +#include +#include + +namespace Tracker +{ + +/** + * @brief Statistics counters for a single device. + * + * These counters are designed to be used hierarchically, so you can use the + * API to aggregate counters into a parent tracker. Not all instances are + * required to track all statistics. + */ +class Stats +{ +public: + /** + * @brief Increment the frame counter. + */ + void incFrameCount() + { + frameCount += 1; + } + + /** + * @brief Increment the render pass counter. + */ + void incRenderPassCount() + { + renderPassCount += 1; + } + + /** + * @brief Increment the draw counter. + */ + void incDrawCallCount() + { + drawCallCount += 1; + } + + /** + * @brief Increment the compute dispatch counter. + */ + void incDispatchCount() + { + dispatchCount += 1; + } + + /** + * @brief Increment the trace rays counter. + */ + void incTraceRaysCount() + { + traceRaysCount += 1; + }; + + /** + * @brief Increment the buffer transfer counter. + */ + void incBufferTransferCount() + { + bufferTransferCount += 1; + } + + /** + * @brief Increment the image transfer counter. + */ + void incImageTransferCount() + { + imageTransferCount += 1; + } + + /** + * @brief Increment all counters with values from another stats object. + */ + void mergeCounts(const Stats& other) + { + frameCount += other.frameCount; + renderPassCount += other.renderPassCount; + drawCallCount += other.drawCallCount; + dispatchCount = other.dispatchCount; + traceRaysCount = other.traceRaysCount; + bufferTransferCount = other.bufferTransferCount; + imageTransferCount = other.imageTransferCount; + } + + /** + * @brief Reset all counters to zero; + */ + void reset() + { + frameCount = 0; + renderPassCount = 0; + drawCallCount = 0; + dispatchCount = 0; + traceRaysCount = 0; + bufferTransferCount = 0; + imageTransferCount = 0; + } + + /** + * @brief Get the frame counter. + */ + uint64_t getFrameCount() const + { + return frameCount; + } + + /** + * @brief Increment the render pass counter. + */ + uint64_t getRenderPassCount() const + { + return renderPassCount; + } + + /** + * @brief Increment the draw counter. + */ + uint64_t getDrawCallCount() const + { + return drawCallCount; + } + + /** + * @brief Increment the compute dispatch counter. + */ + uint64_t getDispatchCount() const + { + return dispatchCount; + } + + /** + * @brief Increment the trace rays counter. + */ + uint64_t getTraceRaysCount() const + { + return traceRaysCount; + }; + + /** + * @brief Increment the buffer transfer counter. + */ + uint64_t getBufferTransferCount() const + { + return bufferTransferCount; + } + + /** + * @brief Increment the image transfer counter. + */ + uint64_t getImageTransferCount() const + { + return imageTransferCount;; + } + +private: + /** + * @brief The number of frames tracked. + */ + uint64_t frameCount { 0 }; + + /** + * @brief The number of render passes tracked. + */ + uint64_t renderPassCount { 0 }; + + /** + * @brief The number of draw calls tracked. + */ + uint64_t drawCallCount { 0 }; + + /** + * @brief The number of compute dispatches tracked. + */ + uint64_t dispatchCount { 0 }; + + /** + * @brief The number of trace rays calls tracked. + */ + uint64_t traceRaysCount { 0 }; + + /** + * @brief The number of buffer transfers tracked. + */ + uint64_t bufferTransferCount { 0 }; + + /** + * @brief The number of image transfers tracked. + */ + uint64_t imageTransferCount { 0 }; +}; + +} diff --git a/source_common/utils/misc.hpp b/source_common/utils/misc.hpp index b55192c..8f02b7d 100644 --- a/source_common/utils/misc.hpp +++ b/source_common/utils/misc.hpp @@ -31,7 +31,10 @@ #pragma once #include +#include +#include #include +#include /** * @brief Macro to stringize a value. @@ -50,7 +53,7 @@ * @param args The variadic values used to populate the template. */ template -std::string fmt_string( +std::string formatString( const std::string& format, Args ... args ) { @@ -68,6 +71,29 @@ std::string fmt_string( return std::string(buf.get(), buf.get() + size - 1); } +/** + * @brief Join a string of parts. + * + * @param parts The list of string parts to join. + * @param separator The delimiter to use when joining the parts. + */ +[[maybe_unused]] static std::string joinString( + const std::vector& parts, + const std::string& separator +) { + std::stringstream out; + for (size_t i = 0; i < parts.size(); i++) + { + out << parts[i]; + if (i != parts.size() - 1) + { + out << separator; + } + } + + return out.str(); +} + /** * @brief Test if an element exists in an iterable container. *