diff --git a/meson.build b/meson.build index 668046293f9..5c5bbbc7eaf 100644 --- a/meson.build +++ b/meson.build @@ -59,6 +59,7 @@ with_glx_direct = get_option('glx-direct') with_osmesa = get_option('osmesa') with_vulkan_overlay_layer = get_option('vulkan-layers').contains('overlay') with_vulkan_device_select_layer = get_option('vulkan-layers').contains('device-select') +with_vulkan_screenshot_layer = get_option('vulkan-layers').contains('screenshot') with_tools = get_option('tools') if with_tools.contains('all') with_tools = [ diff --git a/meson_options.txt b/meson_options.txt index 545f6d20d3a..5916fe7cdc7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -311,7 +311,7 @@ option( 'vulkan-layers', type : 'array', value : [], - choices : ['device-select', 'intel-nullhw', 'overlay'], + choices : ['device-select', 'intel-nullhw', 'overlay', 'screenshot'], description : 'List of vulkan layers to build' ) diff --git a/src/vulkan/meson.build b/src/vulkan/meson.build index 56f35fc90fe..69d35836a12 100644 --- a/src/vulkan/meson.build +++ b/src/vulkan/meson.build @@ -88,3 +88,6 @@ endif if with_vulkan_device_select_layer subdir('device-select-layer') endif +if with_vulkan_screenshot_layer + subdir('screenshot-layer') +endif diff --git a/src/vulkan/screenshot-layer/LICENSE.txt b/src/vulkan/screenshot-layer/LICENSE.txt new file mode 100644 index 00000000000..f965b1fb18e --- /dev/null +++ b/src/vulkan/screenshot-layer/LICENSE.txt @@ -0,0 +1,178 @@ +The screenshot-layer directory was created from using mesa-overlay as a template, then +adding portions of the screenshotting layer from LunarG's VulkanTools repository: +https://github.com/LunarG/VulkanTools/tree/main + +Included below is the original Apache 2.0 license included in VulkanTools. + +=========================================================================================== + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as +defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner +that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that +control, are controlled by, or are under common control with that entity. For the +purposes of this definition, "control" means (i) the power, direct or indirect, to +cause the direction or management of such entity, whether by contract or otherwise, +or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or +(iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions +granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not +limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included in or +attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based +on (or derived from) the Work and for which the editorial revisions, annotations, +elaborations, or other modifications represent, as a whole, an original work of +authorship. For the purposes of this License, Derivative Works shall not include works +that remain separable from, or merely link (or bind by name) to the interfaces of, the +Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the +Work and any modifications or additions to that Work or Derivative Works thereof, that +is intentionally submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. +For the purposes of this definition, "submitted" means any form of electronic, verbal, +or written communication sent to the Licensor or its representatives, including but not +limited to communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose +of discussing and improving the Work, but excluding communication that is conspicuously +marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a +Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each +Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each +Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable (except as stated in this section) patent license to make, +have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such +license applies only to those patent claims licensable by such Contributor that are +necessarily infringed by their Contribution(s) alone or by combination of their +Contribution(s) with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a cross-claim or counterclaim +in a lawsuit) alleging that the Work or a Contribution incorporated within the Work +constitutes direct or contributory patent infringement, then any patent licenses granted +to You under this License for that Work shall terminate as of the date such litigation +is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative +Works thereof in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this +License; and +You must cause any modified files to carry prominent notices stating that You changed +the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all +copyright, patent, trademark, and attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the attribution +notices contained within such NOTICE file, excluding those notices that do not pertain +to any part of the Derivative Works, in at least one of the following places: within a +NOTICE text file distributed as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, within a display +generated by the Derivative Works, if and wherever such third-party notices normally +appear. The contents of the NOTICE file are for informational purposes only and do not +modify the License. You may add Your own attribution notices within Derivative Works +that You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as modifying +the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution +intentionally submitted for inclusion in the Work by You to the Licensor shall be under +the terms and conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of any +separate license agreement you may have executed with Licensor regarding such +Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, +trademarks, service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and reproducing the +content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, +Licensor provides the Work (and each Contributor provides its Contributions) on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, + including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, + MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for + determining the appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort +(including negligence), contract, or otherwise, unless required by applicable law (such +as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor +be liable to You for damages, including any direct, indirect, special, incidental, or +consequential damages of any character arising as a result of this License or out of the +use or inability to use the Work (including but not limited to damages for loss of +goodwill, work stoppage, computer failure or malfunction, or any and all other +commercial damages or losses), even if such Contributor has been advised of the +possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or +Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of +support, warranty, indemnity, or other liability obligations and/or rights consistent +with this License. However, in accepting such obligations, You may act only on Your own +behalf and on Your sole responsibility, not on behalf of any other Contributor, and only +if You agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your accepting +any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: HOW TO APPLY THE APACHE LICENSE TO YOUR WORK +To apply the Apache License to your work, attach the following boilerplate notice, with +the fields enclosed by brackets "[]" replaced with your own identifying information. +(Don't include the brackets!) The text should be enclosed in the appropriate comment +syntax for the file format. We also recommend that a file or class name and description +of purpose be included on the same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/src/vulkan/screenshot-layer/README.rst b/src/vulkan/screenshot-layer/README.rst new file mode 100644 index 00000000000..7ca6ffac7de --- /dev/null +++ b/src/vulkan/screenshot-layer/README.rst @@ -0,0 +1,87 @@ +A Vulkan layer to display information about the running application using an overlay. + +Building +======== + +The overlay layer will be built if :code:`screenshot` is passed as a :code:`vulkan-layers` argument. For example: + +.. code-block:: sh + + meson -Dvulkan-layers=device-select,screenshot builddir/ + ninja -C builddir/ + sudo ninja -C builddir/ install + +See `docs/install.rst `__ for more information. + +Basic Usage +=========== + +Turn on the layer: + +.. code-block:: sh + + VK_LOADER_LAYERS_ENABLE=VK_LAYER_MESA_screenshot /path/to/my_vulkan_app + + +List the help menu: + +.. code-block:: sh + + VK_LOADER_LAYERS_ENABLE=VK_LAYER_MESA_screenshot VK_LAYER_MESA_SCREENSHOT_CONFIG=help /path/to/my_vulkan_app + +Enable log output in stdout/stderr: + +.. code-block:: sh + + VK_LOADER_LAYERS_ENABLE=VK_LAYER_MESA_screenshot VK_LAYER_MESA_SCREENSHOT_CONFIG=log_type= /path/to/my_vulkan_app + +Redirect screenshots taken to a different directory: + +.. code-block:: sh + + VK_LOADER_LAYERS_ENABLE=VK_LAYER_MESA_screenshot VK_LAYER_MESA_SCREENSHOT_CONFIG=output_dir=/path/to/new_dir /path/to/my_vulkan_app + +Capture pre-determined screenshots: + +.. code-block:: sh + + VK_LOADER_LAYERS_ENABLE=VK_LAYER_MESA_screenshot VK_LAYER_MESA_SCREENSHOT_CONFIG=frames=1/5/7/15-4-5 /path/to/my_vulkan_app + +Note: + - Individual frames are separated by '/' and must be listed before the frame range + - The frame range is determined by -- + - Example: '1/5/7/15-4-5' gives individual frames [1,5,7], then the frame range gives [15,20,25,30], combining into [1,5,7,15,20,25,30] + +To capture all frames: + +.. code-block:: sh + + VK_LOADER_LAYERS_ENABLE=VK_LAYER_MESA_screenshot VK_LAYER_MESA_SCREENSHOT_CONFIG=frames=all /path/to/my_vulkan_app + +Direct Socket Control +--------------------- + +Enabling communication with the client can be done with the following setup: + +.. code-block:: sh + + VK_LOADER_LAYERS_ENABLE=VK_LAYER_MESA_screenshot VK_LAYER_MESA_SCREENSHOT_CONFIG=comms /path/to/my_vulkan_app + +The Unix socket may be used directly if needed. Once a client connects to the socket, the overlay layer will immediately +send the following commands to the client: + +.. code-block:: sh + + :MesaOverlayControlVersion=1; + :DeviceName=; + :MesaVersion=; + +The client connected to the overlay layer can trigger a screenshot to be taken by sending the command: + +.. code-block:: sh + + :capture=; + +Note that the screenshot name must include '.png', other image types are not supported. + +.. _docs/install.rst: ../../docs/install.rst diff --git a/src/vulkan/screenshot-layer/VkLayer_MESA_screenshot.json b/src/vulkan/screenshot-layer/VkLayer_MESA_screenshot.json new file mode 100644 index 00000000000..84584ba6717 --- /dev/null +++ b/src/vulkan/screenshot-layer/VkLayer_MESA_screenshot.json @@ -0,0 +1,11 @@ +{ + "file_format_version" : "1.0.0", + "layer" : { + "name": "VK_LAYER_MESA_screenshot", + "type": "GLOBAL", + "library_path": "libVkLayer_MESA_screenshot.so", + "api_version": "1.3.211", + "implementation_version": "1", + "description": "Mesa Screenshot layer" + } +} diff --git a/src/vulkan/screenshot-layer/mesa-screenshot-control.py b/src/vulkan/screenshot-layer/mesa-screenshot-control.py new file mode 100755 index 00000000000..011452483b1 --- /dev/null +++ b/src/vulkan/screenshot-layer/mesa-screenshot-control.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +import socket +import sys +import select +from select import EPOLLIN, EPOLLPRI, EPOLLERR +import time +import argparse + +TIMEOUT = 1.0 # seconds + +VERSION_HEADER = bytearray('MesaScreenshotControlVersion', 'utf-8') +DEVICE_NAME_HEADER = bytearray('DeviceName', 'utf-8') +MESA_VERSION_HEADER = bytearray('MesaVersion', 'utf-8') + +DEFAULT_SERVER_ADDRESS = "\0mesa_screenshot" + +class Connection: + def __init__(self, path): + # Create a Unix Domain socket and connect + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.connect(path) + except socket.error as msg: + print(msg) + sys.exit(1) + + self.sock = sock + + # initialize poll interface and register socket + epoll = select.epoll() + epoll.register(sock, EPOLLIN | EPOLLPRI | EPOLLERR) + self.epoll = epoll + + def recv(self, timeout): + ''' + timeout as float in seconds + returns: + - None on error or disconnection + - bytes() (empty) on timeout + ''' + + events = self.epoll.poll(timeout) + for ev in events: + (fd, event) = ev + if fd != self.sock.fileno(): + continue + + # check for socket error + if event & EPOLLERR: + return None + + # EPOLLIN or EPOLLPRI, just read the message + msg = self.sock.recv(4096) + + # socket disconnected + if len(msg) == 0: + return None + + return msg + + return bytes() + + def send(self, msg): + self.sock.send(msg) + +class MsgParser: + MSGBEGIN = bytes(':', 'utf-8')[0] + MSGEND = bytes(';', 'utf-8')[0] + MSGSEP = bytes('=', 'utf-8')[0] + + def __init__(self, conn): + self.cmdpos = 0 + self.parampos = 0 + self.bufferpos = 0 + self.reading_cmd = False + self.reading_param = False + self.buffer = None + self.cmd = bytearray(4096) + self.param = bytearray(4096) + + self.conn = conn + + def readCmd(self, ncmds, timeout=TIMEOUT): + ''' + returns: + - None on error or disconnection + - bytes() (empty) on timeout + ''' + + parsed = [] + + remaining = timeout + + while remaining > 0 and ncmds > 0: + now = time.monotonic() + + if self.buffer is None: + self.buffer = self.conn.recv(remaining) + self.bufferpos = 0 + + # disconnected or error + if self.buffer is None: + return None + + for i in range(self.bufferpos, len(self.buffer)): + c = self.buffer[i] + self.bufferpos += 1 + if c == self.MSGBEGIN: + self.cmdpos = 0 + self.parampos = 0 + self.reading_cmd = True + self.reading_param = False + elif c == self.MSGEND: + if not self.reading_cmd: + continue + self.reading_cmd = False + self.reading_param = False + + cmd = self.cmd[0:self.cmdpos] + param = self.param[0:self.parampos] + self.reading_cmd = False + self.reading_param = False + + parsed.append((cmd, param)) + ncmds -= 1 + if ncmds == 0: + break + elif c == self.MSGSEP: + if self.reading_cmd: + self.reading_param = True + else: + if self.reading_param: + self.param[self.parampos] = c + self.parampos += 1 + elif self.reading_cmd: + self.cmd[self.cmdpos] = c + self.cmdpos += 1 + + # if we read the entire buffer and didn't finish the command, + # throw it away + self.buffer = None + + # check if we have time for another iteration + elapsed = time.monotonic() - now + remaining = max(0, remaining - elapsed) + + # timeout + return parsed + +def control(args): + if args.socket: + address = '\0' + args.socket + else: + address = DEFAULT_SERVER_ADDRESS + + conn = Connection(address) + msgparser = MsgParser(conn) + + version = None + name = None + mesa_version = None + + msgs = msgparser.readCmd(3) + + for m in msgs: + cmd, param = m + if cmd == VERSION_HEADER: + version = int(param) + elif cmd == DEVICE_NAME_HEADER: + name = param.decode('utf-8') + elif cmd == MESA_VERSION_HEADER: + mesa_version = param.decode('utf-8') + + if version != 1 or name is None or mesa_version is None: + print('ERROR: invalid protocol') + sys.exit(1) + + if args.info: + print(f"Protocol Version: {version}") + print(f"Device Name: {name}") + print(f"Mesa Version: {mesa_version}") + + if args.cmd == 'capture': + if args.filename is None: + args.filename = '' + msg = ':capture=' + args.filename + ';' + conn.send(bytearray(msg, 'utf-8')) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='MESA_screenshot control client') + parser.add_argument('--info', action='store_true', help='Print info from socket') + parser.add_argument('--socket', '-s', type=str, help='Path to socket') + + commands = parser.add_subparsers(help='commands to run', dest='cmd') + commands_parser = commands.add_parser('capture', help='capture [filename.png]') + commands_parser.add_argument('filename', nargs='?', type=str) + + args = parser.parse_args() + + control(args) diff --git a/src/vulkan/screenshot-layer/meson.build b/src/vulkan/screenshot-layer/meson.build new file mode 100644 index 00000000000..14c5dc4f2f9 --- /dev/null +++ b/src/vulkan/screenshot-layer/meson.build @@ -0,0 +1,47 @@ +# Copyright © 2019 Intel Corporation + +# 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. + +vklayer_files = files( + 'screenshot.cpp', + 'screenshot_params.c', +) + +vklayer_mesa_screenshot = shared_library( + 'VkLayer_MESA_screenshot', + vklayer_files, sha1_h, + c_args : [no_override_init_args], + gnu_symbol_visibility : 'hidden', + dependencies : [idep_vulkan_util, idep_mesautil, vulkan_wsi_deps, dep_dl, dependency('libpng')], + include_directories : [inc_include, inc_src], + link_args : cc.get_supported_link_arguments(['-Wl,-Bsymbolic-functions', '-Wl,-z,relro']), + install : true +) + +install_data( + files('VkLayer_MESA_screenshot.json'), + install_dir : join_paths(get_option('datadir'), 'vulkan', 'explicit_layer.d'), + install_tag : 'runtime', +) + +install_data( + 'mesa-screenshot-control.py', + install_dir : get_option('bindir'), + install_mode : 'r-xr-xr-x', +) diff --git a/src/vulkan/screenshot-layer/screenshot.cpp b/src/vulkan/screenshot-layer/screenshot.cpp new file mode 100644 index 00000000000..93b70123d2f --- /dev/null +++ b/src/vulkan/screenshot-layer/screenshot.cpp @@ -0,0 +1,1402 @@ +/* + * Copyright © 2024 Intel Corporation + * + * 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 (including the next + * paragraph) 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. + */ + +/* + * Copyright (C) 2015-2021 Valve Corporation + * Copyright (C) 2015-2021 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Cody Northrop + * Author: David Pinedo + * Author: Jon Ashburn + * Author: Tony Barbour + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "git_sha1.h" + +#include "screenshot_params.h" + +#include "util/u_debug.h" +#include "util/hash_table.h" +#include "util/list.h" +#include "util/ralloc.h" +#include "util/os_time.h" +#include "util/os_socket.h" +#include "util/simple_mtx.h" +#include "util/u_math.h" + +#include "vk_enum_to_str.h" +#include "vk_dispatch_table.h" +#include "vk_util.h" + +typedef pthread_mutex_t loader_platform_thread_mutex; +static inline void loader_platform_thread_create_mutex(loader_platform_thread_mutex *pMutex) { pthread_mutex_init(pMutex, NULL); } +static inline void loader_platform_thread_lock_mutex(loader_platform_thread_mutex *pMutex) { pthread_mutex_lock(pMutex); } +static inline void loader_platform_thread_unlock_mutex(loader_platform_thread_mutex *pMutex) { pthread_mutex_unlock(pMutex); } +static inline void loader_platform_thread_delete_mutex(loader_platform_thread_mutex *pMutex) { pthread_mutex_destroy(pMutex); } + +static int globalLockInitialized = 0; +static loader_platform_thread_mutex globalLock; + +uint32_t MAX_PATH_SIZE = 512; + +/* Mapped from VkInstace/VkPhysicalDevice */ +struct instance_data { + struct vk_instance_dispatch_table vtable; + struct vk_physical_device_dispatch_table pd_vtable; + VkInstance instance; + + struct screenshot_params params; + + int control_client; + int socket_fd; + + /* Enabling switch for taking screenshot */ + bool screenshot_enabled; + + /* Enabling switch for socket communications */ + bool socket_enabled; + bool socket_setup; + + const char *filename; +}; + +pthread_cond_t ptCondition = PTHREAD_COND_INITIALIZER; +pthread_mutex_t ptLock = PTHREAD_MUTEX_INITIALIZER; + +VkFence copyDone; +const VkPipelineStageFlags dstStageWaitBeforeSubmission = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; +const VkSemaphore *pSemaphoreWaitBeforePresent; +uint32_t semaphoreWaitBeforePresentCount; +VkSemaphore semaphoreWaitAfterSubmission; + +/* Mapped from VkDevice */ +struct queue_data; +struct device_data { + struct instance_data *instance; + + PFN_vkSetDeviceLoaderData set_device_loader_data; + + struct vk_device_dispatch_table vtable; + VkPhysicalDevice physical_device; + VkDevice device; + + VkPhysicalDeviceProperties properties; + + struct queue_data *graphic_queue; + struct queue_data **queues; + uint32_t n_queues; +}; + +/* Mapped from VkQueue */ +struct queue_data { + struct device_data *device; + VkQueue queue; + VkQueueFlags flags; + uint32_t index; +}; + +/* Mapped from VkSwapchainKHR */ +struct swapchain_data { + struct device_data *device; + + VkSwapchainKHR swapchain; + VkExtent2D imageExtent; + VkFormat format; + + VkImage image; +}; + +static struct hash_table_u64 *vk_object_to_data = NULL; +static simple_mtx_t vk_object_to_data_mutex = SIMPLE_MTX_INITIALIZER; + +static inline void ensure_vk_object_map(void) +{ + if (!vk_object_to_data) + vk_object_to_data = _mesa_hash_table_u64_create(NULL); +} + +#define HKEY(obj) ((uint64_t)(obj)) +#define FIND(type, obj) ((type *)find_object_data(HKEY(obj))) + +static void *find_object_data(uint64_t obj) +{ + simple_mtx_lock(&vk_object_to_data_mutex); + ensure_vk_object_map(); + void *data = _mesa_hash_table_u64_search(vk_object_to_data, obj); + simple_mtx_unlock(&vk_object_to_data_mutex); + return data; +} + +static void map_object(uint64_t obj, void *data) +{ + simple_mtx_lock(&vk_object_to_data_mutex); + ensure_vk_object_map(); + _mesa_hash_table_u64_insert(vk_object_to_data, obj, data); + simple_mtx_unlock(&vk_object_to_data_mutex); +} + +static void unmap_object(uint64_t obj) +{ + simple_mtx_lock(&vk_object_to_data_mutex); + _mesa_hash_table_u64_remove(vk_object_to_data, obj); + simple_mtx_unlock(&vk_object_to_data_mutex); +} + +#define VK_CHECK(expr) \ + do { \ + VkResult __result = (expr); \ + if (__result != VK_SUCCESS) { \ + LOG(ERROR, "'%s' line %i failed with %s\n", \ + #expr, __LINE__, vk_Result_to_str(__result)); \ + } \ + } while (0) + +static VkLayerInstanceCreateInfo *get_instance_chain_info(const VkInstanceCreateInfo *pCreateInfo, + VkLayerFunction func) +{ + vk_foreach_struct_const(item, pCreateInfo->pNext) { + if (item->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO && + ((VkLayerInstanceCreateInfo *) item)->function == func) + return (VkLayerInstanceCreateInfo *) item; + } + unreachable("instance chain info not found"); + return NULL; +} + +static VkLayerDeviceCreateInfo *get_device_chain_info(const VkDeviceCreateInfo *pCreateInfo, + VkLayerFunction func) +{ + vk_foreach_struct_const(item, pCreateInfo->pNext) { + if (item->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO && + ((VkLayerDeviceCreateInfo *) item)->function == func) + return (VkLayerDeviceCreateInfo *)item; + } + unreachable("device chain info not found"); + return NULL; +} + +/**/ + +static struct instance_data *new_instance_data(VkInstance instance) +{ + struct instance_data *data = rzalloc(NULL, struct instance_data); + data->instance = instance; + data->control_client = -1; + data->socket_fd = -1; + map_object(HKEY(data->instance), data); + return data; +} + +void destroy_instance_data(struct instance_data *data) +{ + destroy_frame_list(data->params.frames); + if (data->socket_fd >= 0) + os_socket_close(data->socket_fd); + unmap_object(HKEY(data->instance)); + ralloc_free(data); +} + +static void instance_data_map_physical_devices(struct instance_data *instance_data, + bool map) +{ + uint32_t physicalDeviceCount = 0; + instance_data->vtable.EnumeratePhysicalDevices(instance_data->instance, + &physicalDeviceCount, + NULL); + + VkPhysicalDevice *physicalDevices = (VkPhysicalDevice *) malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount); + instance_data->vtable.EnumeratePhysicalDevices(instance_data->instance, + &physicalDeviceCount, + physicalDevices); + + for (uint32_t i = 0; i < physicalDeviceCount; i++) { + if (map) + map_object(HKEY(physicalDevices[i]), instance_data); + else + unmap_object(HKEY(physicalDevices[i])); + } + + free(physicalDevices); +} + +/**/ +static struct device_data *new_device_data(VkDevice device, struct instance_data *instance) +{ + struct device_data *data = rzalloc(NULL, struct device_data); + data->instance = instance; + data->device = device; + map_object(HKEY(data->device), data); + return data; +} + +static struct queue_data *new_queue_data(VkQueue queue, + const VkQueueFamilyProperties *family_props, + struct device_data *device_data, + uint32_t index) +{ + struct queue_data *data = rzalloc(device_data, struct queue_data); + data->device = device_data; + data->queue = queue; + data->flags = family_props->queueFlags; + data->index = index; + map_object(HKEY(data->queue), data); + + if ((data->flags & VK_QUEUE_GRAPHICS_BIT) != 0) { + device_data->graphic_queue = data; + } + return data; +} + +static void destroy_queue(struct queue_data *data) +{ + struct device_data *device_data = data->device; + unmap_object(HKEY(data->queue)); + ralloc_free(data); +} + +static void device_map_queues(struct device_data *data, + const VkDeviceCreateInfo *pCreateInfo) +{ + loader_platform_thread_lock_mutex(&globalLock); + for (uint32_t i = 0; i < pCreateInfo->queueCreateInfoCount; i++) + data->n_queues += pCreateInfo->pQueueCreateInfos[i].queueCount; + data->queues = ralloc_array(data, struct queue_data *, data->n_queues); + + struct instance_data *instance_data = data->instance; + uint32_t n_family_props; + instance_data->pd_vtable.GetPhysicalDeviceQueueFamilyProperties(data->physical_device, + &n_family_props, + NULL); + VkQueueFamilyProperties *family_props = + (VkQueueFamilyProperties *)malloc(sizeof(VkQueueFamilyProperties) * n_family_props); + instance_data->pd_vtable.GetPhysicalDeviceQueueFamilyProperties(data->physical_device, + &n_family_props, + family_props); + + uint32_t queue_index = 0; + for (uint32_t i = 0; i < pCreateInfo->queueCreateInfoCount; i++) { + for (uint32_t j = 0; j < pCreateInfo->pQueueCreateInfos[i].queueCount; j++) { + VkQueue queue; + data->vtable.GetDeviceQueue(data->device, + pCreateInfo->pQueueCreateInfos[i].queueFamilyIndex, + j, &queue); + VK_CHECK(data->set_device_loader_data(data->device, queue)); + + data->queues[queue_index] = + new_queue_data(queue, family_props, data, queue_index); + queue_index++; + } + } + + free(family_props); + loader_platform_thread_unlock_mutex(&globalLock); +} + +static void device_unmap_queues(struct device_data *data) +{ + for (uint32_t i = 0; i < data->n_queues; i++) + destroy_queue(data->queues[i]); +} + +static void destroy_device_data(struct device_data *data) +{ + loader_platform_thread_lock_mutex(&globalLock); + unmap_object(HKEY(data->device)); + ralloc_free(data); + loader_platform_thread_unlock_mutex(&globalLock); +} + +static struct swapchain_data *new_swapchain_data(VkSwapchainKHR swapchain, + struct device_data *device_data) +{ + struct instance_data *instance_data = device_data->instance; + struct swapchain_data *data = rzalloc(NULL, struct swapchain_data); + data->device = device_data; + data->swapchain = swapchain; + map_object(HKEY(data->swapchain), data); + return data; +} + +static void destroy_swapchain_data(struct swapchain_data *data) +{ + unmap_object(HKEY(data->swapchain)); + ralloc_free(data); +} + +static void parse_command(struct instance_data *instance_data, + const char *cmd, unsigned cmdlen, + const char *param, unsigned paramlen) +{ + /* parse string (if any) from capture command */ + if (!strncmp(cmd, "capture", cmdlen)) { + instance_data->screenshot_enabled = true; + if (paramlen > 1) { + instance_data->filename = param; + } else { + instance_data->filename = NULL; + } + } +} + +#define BUFSIZE 4096 + +/** + * This function will process commands through the control file. + * + * A command starts with a colon, followed by the command, and followed by an + * option '=' and a parameter. It has to end with a semi-colon. A full command + * + parameter looks like: + * + * :cmd=param; + */ +static void process_char(struct instance_data *instance_data, char c) +{ + static char cmd[BUFSIZE]; + static char param[BUFSIZE]; + + static unsigned cmdpos = 0; + static unsigned parampos = 0; + static bool reading_cmd = false; + static bool reading_param = false; + + switch (c) { + case ':': + cmdpos = 0; + parampos = 0; + reading_cmd = true; + reading_param = false; + break; + case ';': + if (!reading_cmd) + break; + cmd[cmdpos++] = '\0'; + param[parampos++] = '\0'; + parse_command(instance_data, cmd, cmdpos, param, parampos); + reading_cmd = false; + reading_param = false; + break; + case '=': + if (!reading_cmd) + break; + reading_param = true; + break; + default: + if (!reading_cmd) + break; + + if (reading_param) { + /* overflow means an invalid parameter */ + if (parampos >= BUFSIZE - 1) { + reading_cmd = false; + reading_param = false; + break; + } + + param[parampos++] = c; + } else { + /* overflow means an invalid command */ + if (cmdpos >= BUFSIZE - 1) { + reading_cmd = false; + break; + } + + cmd[cmdpos++] = c; + } + } +} + +static void control_send(struct instance_data *instance_data, + const char *cmd, unsigned cmdlen, + const char *param, unsigned paramlen) +{ + unsigned msglen = 0; + char buffer[BUFSIZE]; + + assert(cmdlen + paramlen + 3 < BUFSIZE); + + buffer[msglen++] = ':'; + + memcpy(&buffer[msglen], cmd, cmdlen); + msglen += cmdlen; + + if (paramlen > 0) { + buffer[msglen++] = '='; + memcpy(&buffer[msglen], param, paramlen); + msglen += paramlen; + buffer[msglen++] = ';'; + } + + os_socket_send(instance_data->control_client, buffer, msglen, 0); +} + +static void control_send_connection_string(struct device_data *device_data) +{ + struct instance_data *instance_data = device_data->instance; + + const char *controlVersionCmd = "MesaScreenshotControlVersion"; + const char *controlVersionString = "1"; + + control_send(instance_data, controlVersionCmd, strlen(controlVersionCmd), + controlVersionString, strlen(controlVersionString)); + + const char *deviceCmd = "DeviceName"; + const char *deviceName = device_data->properties.deviceName; + + control_send(instance_data, deviceCmd, strlen(deviceCmd), + deviceName, strlen(deviceName)); + + const char *mesaVersionCmd = "MesaVersion"; + const char *mesaVersionString = "Mesa " PACKAGE_VERSION MESA_GIT_SHA1; + + control_send(instance_data, mesaVersionCmd, strlen(mesaVersionCmd), + mesaVersionString, strlen(mesaVersionString)); +} + +static void control_client_check(struct device_data *device_data) +{ + struct instance_data *instance_data = device_data->instance; + + /* Already connected, just return. */ + if (instance_data->control_client >= 0) + return; + + int socket_fd = os_socket_accept(instance_data->socket_fd); + if (socket_fd == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK && errno != ECONNABORTED) + LOG(ERROR, "socket error: %s\n", strerror(errno)); + return; + } + + if (socket_fd >= 0) { + os_socket_block(socket_fd, false); + instance_data->control_client = socket_fd; + control_send_connection_string(device_data); + } +} + +static void control_client_disconnected(struct instance_data *instance_data) +{ + os_socket_close(instance_data->control_client); + instance_data->control_client = -1; +} + +static void process_control_socket(struct instance_data *instance_data) +{ + const int client = instance_data->control_client; + if (client >= 0) { + char buf[BUFSIZE]; + + while (true) { + ssize_t n = os_socket_recv(client, buf, BUFSIZE, 0); + + if (n == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* nothing to read, try again later */ + break; + } + + if (errno != ECONNRESET) + LOG(ERROR, "Connection failed: %s\n", strerror(errno)); + + control_client_disconnected(instance_data); + } else if (n == 0) { + /* recv() returns 0 when the client disconnects */ + control_client_disconnected(instance_data); + } + + for (ssize_t i = 0; i < n; i++) { + process_char(instance_data, buf[i]); + } + + /* If we try to read BUFSIZE and receive BUFSIZE bytes from the + * socket, there's a good chance that there's still more data to be + * read, so we will try again. Otherwise, simply be done for this + * iteration and try again on the next frame. + */ + if (n < BUFSIZE) + break; + } + } +} + +static VkResult screenshot_CreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) +{ + struct device_data *device_data = FIND(struct device_data, device); + + // Turn on transfer src bit for image copy later on. + VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + VkResult result = device_data->vtable.CreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); + if (result != VK_SUCCESS) return result; + + loader_platform_thread_lock_mutex(&globalLock); + + struct swapchain_data *swapchain_data = new_swapchain_data(*pSwapchain, device_data); + swapchain_data->imageExtent = pCreateInfo->imageExtent; + swapchain_data->format = pCreateInfo->imageFormat; + loader_platform_thread_unlock_mutex(&globalLock); + return result; +} + +static VkResult screenshot_GetSwapchainImagesKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t* pCount, + VkImage* pSwapchainImages) +{ + struct swapchain_data *swapchain_data = FIND(struct swapchain_data, swapchain); + struct vk_device_dispatch_table *vtable = &(swapchain_data->device->vtable); + VkResult result = vtable->GetSwapchainImagesKHR(device, swapchain, pCount, pSwapchainImages); + + loader_platform_thread_lock_mutex(&globalLock); + if (result == VK_SUCCESS) { + // Save only the first image from the first swapchain + if (*pCount > 0) { + if(pSwapchainImages){ + swapchain_data->image = pSwapchainImages[0]; + } + } + } + loader_platform_thread_unlock_mutex(&globalLock); + return result; +} + +static void screenshot_DestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) +{ + if (swapchain == VK_NULL_HANDLE) { + struct device_data *device_data = FIND(struct device_data, device); + device_data->vtable.DestroySwapchainKHR(device, swapchain, pAllocator); + return; + } + + struct swapchain_data *swapchain_data = + FIND(struct swapchain_data, swapchain); + + swapchain_data->device->vtable.DestroySwapchainKHR(device, swapchain, pAllocator); + destroy_swapchain_data(swapchain_data); +} + +/* Convert long int to string */ +static void itoa(uint32_t integer, char *dest_str) +{ + // Our sizes are limited to uin32_t max value: 4,294,967,295 (10 digits) + sprintf(dest_str, "%u", integer); +} + +static bool get_mem_type_from_properties( + VkPhysicalDeviceMemoryProperties* mem_properties, + uint32_t bits_type, + VkFlags requirements_mask, + uint32_t* type_index) +{ + for (uint32_t i = 0; i < 32; i++) { + if ((bits_type & 1) == 1) { + if ((mem_properties->memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) { + *type_index = i; + return true; + } + } + bits_type >>= 1; + } + return false; +} + +// Track allocated resources in writeFile() +// and clean them up when they go out of scope. +struct WriteFileCleanupData { + device_data *dev_data; + VkImage image2; + VkImage image3; + VkDeviceMemory mem2; + VkDeviceMemory mem3; + bool mem2mapped; + bool mem3mapped; + VkCommandBuffer commandBuffer; + VkCommandPool commandPool; + ~WriteFileCleanupData(); +}; + +WriteFileCleanupData::~WriteFileCleanupData() { + if (mem2mapped) dev_data->vtable.UnmapMemory(dev_data->device, mem2); + if (mem2) dev_data->vtable.FreeMemory(dev_data->device, mem2, NULL); + if (image2) dev_data->vtable.DestroyImage(dev_data->device, image2, NULL); + + if (mem3mapped) dev_data->vtable.UnmapMemory(dev_data->device, mem3); + if (mem3) dev_data->vtable.FreeMemory(dev_data->device, mem3, NULL); + if (image3) dev_data->vtable.DestroyImage(dev_data->device, image3, NULL); + + if (commandBuffer) dev_data->vtable.FreeCommandBuffers(dev_data->device, commandPool, 1, &commandBuffer); + if (commandPool) dev_data->vtable.DestroyCommandPool(dev_data->device, commandPool, NULL); +} + +static uint64_t get_time() { + if (LOG_TYPE == DEBUG) { + struct timespec tspec; + long BILLION = 1000000000; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tspec); + uint64_t sec = tspec.tv_sec; + uint64_t nsec = tspec.tv_nsec; + return ((sec * BILLION) + nsec); + } else { + return 0; + } +} + +static void print_time_difference(long int start_time, long int end_time) { + if (end_time > 0) { + LOG(DEBUG, "Time to copy: %u nanoseconds\n", end_time - start_time); + } +} + +// Store all data required for threading the saving to file functionality +struct ThreadSaveData { + struct device_data *device_data; + const char *filename; + const char *pFramebuffer; + VkSubresourceLayout srLayout; + VkFence fence; + uint32_t const width; + uint32_t const height; +}; + +/* Write the copied image to a PNG file */ +void *writePNG(void *data) { + struct ThreadSaveData *threadData = (struct ThreadSaveData*)data; + FILE *file; + size_t length = sizeof(char[MAX_PATH_SIZE]); + const char *tmpStr = ".tmp"; + char *filename = (char *)malloc(length); + char *tmpFilename = (char *)malloc(length + 4); // Allow for ".tmp" + memcpy(filename, threadData->filename, length); + memcpy(tmpFilename, threadData->filename, length); + strcat(tmpFilename, tmpStr); + file = fopen(tmpFilename, "wb"); //create file for output + if (!file) { + LOG(ERROR, "Failed to open output file, '%s', error(%d): %s\n", tmpFilename, errno, strerror(errno)); + pthread_cond_signal(&ptCondition); + return nullptr; + } + VkResult res; + png_byte **row_pointer; + threadData->pFramebuffer += threadData->srLayout.offset; + png_infop info; + int localHeight = threadData->height; + int localWidth = threadData->width; + bool checks_failed = false; + // TODO: Look into runtime version mismatch issue with some VK workloads + png_struct* png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //create structure for write PNG_LIBPNG_VER_STRING + if (!png) { + LOG(ERROR, "Create write struct failed. VER_STRING=%s\n", PNG_LIBPNG_VER_STRING); + checks_failed = true; + } else { + info = png_create_info_struct(png); + if (!info) { + LOG(ERROR, "Create info struct failed\n"); + checks_failed = true; + } else if (setjmp(png_jmpbuf(png))) { + LOG(ERROR, "setjmp() failed\n"); + png_destroy_write_struct(&png, &info); + checks_failed = true; + } + } + if (checks_failed) { + fclose(file); + pthread_cond_signal(&ptCondition); + return nullptr; + } + threadData->device_data->vtable.WaitForFences(threadData->device_data->device, 1, &threadData->fence, VK_TRUE, UINT64_MAX); + auto start_time = get_time(); + const int RGB_NUM_CHANNELS = 3; + row_pointer = (png_byte **)malloc(sizeof(png_byte *) * localHeight); + for (int y = 0; y < localHeight; y++) { + int char_counter = 0; + row_pointer[y] = (png_byte *)malloc(sizeof(png_byte) * RGB_NUM_CHANNELS * localWidth); + for (int x = 0; x < localWidth * RGB_NUM_CHANNELS; x += RGB_NUM_CHANNELS) { + memcpy(&row_pointer[y][x], &threadData->pFramebuffer[char_counter], RGB_NUM_CHANNELS * sizeof(png_byte)); + char_counter += RGB_NUM_CHANNELS; + } + threadData->pFramebuffer += threadData->srLayout.rowPitch; + } + auto end_time = get_time(); + print_time_difference(start_time, end_time); + // We've created all local copies of data, + // so let's signal main thread to continue + pthread_cond_signal(&ptCondition); + png_init_io(png, file); // Initialize file output + png_set_IHDR( // Set image properties + png, // Pointer to png_struct + info, // Pointer to info_struct + localWidth, // Image width + localHeight, // Image height + 8, // Color depth + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT + ); + png_set_compression_level(png, 1); // Z_BEST_SPEED=1 + png_set_compression_strategy(png, 2); // Z_HUFFMAN_ONLY=2 + png_set_filter(png, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB); + png_set_compression_mem_level(png, 9); + png_set_compression_buffer_size(png, 65536); + png_write_info(png, info); // Write png image information to file + png_write_image(png, row_pointer); // Actually write image + png_write_end(png, NULL); // End image writing + free(row_pointer); + fclose(file); + + // Rename file, indicating completion, client should be + // checking for the final file exists. + if (rename(tmpFilename, filename) != 0 ) + LOG(ERROR, "Could not rename from '%s' to '%s'\n", tmpFilename, filename); + else + LOG(INFO, "Successfully renamed from '%s' to '%s'\n", tmpFilename, filename); + + if(filename) + free(filename); + if(tmpFilename) + free(tmpFilename); + return nullptr; +} + +/* Write an image to file. Upon encountering issues, do not impact the + Present operation, */ +static bool write_image( + const char* filename, + VkImage image, + struct device_data* device_data, + struct instance_data* instance_data, + struct swapchain_data* swapchain_data) +{ + VkDevice device = device_data->device; + VkPhysicalDevice physical_device = device_data->physical_device; + VkInstance instance = instance_data->instance; + + uint32_t const width = swapchain_data->imageExtent.width; + uint32_t const height = swapchain_data->imageExtent.height; + VkFormat const format = swapchain_data->format; + + queue_data* queue_data = device_data->graphic_queue; + VkQueue queue = queue_data->queue; + + VkResult err; + + /* Force destination format to be RGB to make writing to file much faster */ + VkFormat destination_format = VK_FORMAT_R8G8B8_UNORM; + + VkFormatProperties device_format_properties; + instance_data->pd_vtable.GetPhysicalDeviceFormatProperties(physical_device, + destination_format, + &device_format_properties); + /* If origin and destination formats are the same, no need to convert */ + bool copyOnly = false; + bool needs_2_steps = false; + if (destination_format == format) { + copyOnly = true; + LOG(DEBUG, "Only copying since the src/dest formats are the same\n"); + } else { + bool const blt_linear = device_format_properties.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false; + bool const blt_optimal = device_format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ? true : false; + if (!blt_linear && !blt_optimal) { + return false; + } else if (!blt_linear && blt_optimal) { + // Can't blit to linear target, but can blit to optimal + needs_2_steps = true; + LOG(DEBUG, "Needs 2 steps\n"); + } + } + + WriteFileCleanupData data = {}; + data.dev_data = device_data; + + VkImageCreateInfo img_create_info2 = { + VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + NULL, + 0, + VK_IMAGE_TYPE_2D, + destination_format, + {width, height, 1}, + 1, + 1, + VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_TILING_LINEAR, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_SHARING_MODE_EXCLUSIVE, + 0, + NULL, + VK_IMAGE_LAYOUT_UNDEFINED, + }; + VkImageCreateInfo img_create_info3 = img_create_info2; + + if (needs_2_steps) { + img_create_info2.tiling = VK_IMAGE_TILING_OPTIMAL; + img_create_info2.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + VkMemoryAllocateInfo mem_alloc_info = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + NULL, + 0, + 0 + }; + VkMemoryRequirements mem_requirements; + VkPhysicalDeviceMemoryProperties mem_properties; + + VK_CHECK(device_data->vtable.CreateImage(device, &img_create_info2, NULL, &data.image2)); + device_data->vtable.GetImageMemoryRequirements(device, data.image2, &mem_requirements); + mem_alloc_info.allocationSize = mem_requirements.size; + instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties(physical_device, &mem_properties); + if(!get_mem_type_from_properties(&mem_properties, + mem_requirements.memoryTypeBits, + needs_2_steps ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : VK_MEMORY_PROPERTY_HOST_CACHED_BIT, + &mem_alloc_info.memoryTypeIndex)) { + LOG(ERROR, "Unable to get memory type from the intermediate/final image properties.\n"); + return false; + } + + VK_CHECK(device_data->vtable.AllocateMemory(device, &mem_alloc_info, NULL, &data.mem2)); + VK_CHECK(device_data->vtable.BindImageMemory(device, data.image2, data.mem2, 0)); + + if (needs_2_steps) { + VK_CHECK(device_data->vtable.CreateImage(device, &img_create_info3, NULL, &data.image3)); + device_data->vtable.GetImageMemoryRequirements(device, data.image3, &mem_requirements); + mem_alloc_info.allocationSize = mem_requirements.size; + instance_data->pd_vtable.GetPhysicalDeviceMemoryProperties(physical_device, &mem_properties); + + if(!get_mem_type_from_properties(&mem_properties, + mem_requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_CACHED_BIT, + &mem_alloc_info.memoryTypeIndex)) { + LOG(ERROR, "Unable to get memory type from the temporary image properties.\n"); + return false; + } + VK_CHECK(device_data->vtable.AllocateMemory(device, &mem_alloc_info, NULL, &data.mem3)); + VK_CHECK(device_data->vtable.BindImageMemory(device, data.image3, data.mem3, 0)); + } + + /* Setup command pool */ + VkCommandPoolCreateInfo cmd_pool_info = {}; + cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + cmd_pool_info.pNext = NULL; + cmd_pool_info.queueFamilyIndex = queue_data->index; + cmd_pool_info.flags = 0; + + VK_CHECK(device_data->vtable.CreateCommandPool(device, &cmd_pool_info, NULL, &data.commandPool)); + + /* Set up command buffer */ + const VkCommandBufferAllocateInfo cmd_buf_alloc_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, + data.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1}; + VK_CHECK(device_data->vtable.AllocateCommandBuffers(device, &cmd_buf_alloc_info, &data.commandBuffer)); + + if (device_data->set_device_loader_data) { + VK_CHECK(device_data->set_device_loader_data(device, (void *)data.commandBuffer)); + } else { + *((const void **)data.commandBuffer) = *(void **)device; + } + + const VkCommandBufferBeginInfo cmd_buf_begin_info = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + NULL, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + VK_CHECK(device_data->vtable.BeginCommandBuffer(data.commandBuffer, &cmd_buf_begin_info)); + + // This barrier is used to transition from/to present Layout + VkImageMemoryBarrier presentMemoryBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + NULL, + VK_ACCESS_MEMORY_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + image, + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + // This barrier is used to transition from a newly-created layout to a blt + // or copy destination layout. + VkImageMemoryBarrier destMemoryBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + NULL, + 0, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + data.image2, + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + // This barrier is used to transition a dest layout to general layout. + VkImageMemoryBarrier generalMemoryBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + NULL, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_MEMORY_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_GENERAL, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + data.image2, + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TRANSFER_BIT; + + device_data->vtable.CmdPipelineBarrier(data.commandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + dstStages, 0, 0, NULL, 0, NULL, 1, &presentMemoryBarrier); + + device_data->vtable.CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL, 1, &destMemoryBarrier); + + const VkImageCopy img_copy = { + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + {0, 0, 0}, + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + {0, 0, 0}, + {width, height, 1} + }; + + if (copyOnly) { + device_data->vtable.CmdCopyImage(data.commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.image2, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &img_copy); + } else { + VkImageBlit imageBlitRegion = {}; + imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlitRegion.srcSubresource.baseArrayLayer = 0; + imageBlitRegion.srcSubresource.layerCount = 1; + imageBlitRegion.srcSubresource.mipLevel = 0; + imageBlitRegion.srcOffsets[1].x = width; + imageBlitRegion.srcOffsets[1].y = height; + imageBlitRegion.srcOffsets[1].z = 1; + imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlitRegion.dstSubresource.baseArrayLayer = 0; + imageBlitRegion.dstSubresource.layerCount = 1; + imageBlitRegion.dstSubresource.mipLevel = 0; + imageBlitRegion.dstOffsets[1].x = width; + imageBlitRegion.dstOffsets[1].y = height; + imageBlitRegion.dstOffsets[1].z = 1; + + device_data->vtable.CmdBlitImage(data.commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.image2, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlitRegion, VK_FILTER_NEAREST); + if (needs_2_steps) { + // image 3 needs to be transitioned from its undefined state to a + // transfer destination. + destMemoryBarrier.image = data.image3; + device_data->vtable.CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL, 1, &destMemoryBarrier); + + // Transition image2 so that it can be read for the upcoming copy to + // image 3. + destMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + destMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + destMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + destMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + destMemoryBarrier.image = data.image2; + device_data->vtable.CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL, 1, + &destMemoryBarrier); + + // This step essentially untiles the image. + device_data->vtable.CmdCopyImage(data.commandBuffer, data.image2, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.image3, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &img_copy); + generalMemoryBarrier.image = data.image3; + } + } + + // The destination needs to be transitioned from the optimal copy format to + // the format we can read with the CPU. + device_data->vtable.CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL, 1, &generalMemoryBarrier); + + // Restore the swap chain image layout to what it was before. + // This may not be strictly needed, but it is generally good to restore + // things to original state. + presentMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + presentMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + presentMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + presentMemoryBarrier.dstAccessMask = 0; + device_data->vtable.CmdPipelineBarrier(data.commandBuffer, srcStages, dstStages, 0, 0, NULL, 0, NULL, 1, + &presentMemoryBarrier); + VK_CHECK(device_data->vtable.EndCommandBuffer(data.commandBuffer)); + + VkSubmitInfo submitInfo; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.pNext = NULL; + submitInfo.waitSemaphoreCount = semaphoreWaitBeforePresentCount; + submitInfo.pWaitSemaphores = pSemaphoreWaitBeforePresent; + submitInfo.pWaitDstStageMask = &dstStageWaitBeforeSubmission; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &data.commandBuffer; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &semaphoreWaitAfterSubmission; + VK_CHECK(device_data->vtable.QueueSubmit(queue, 1, &submitInfo, copyDone)); + + // Map the final image so that the CPU can read it. + const VkImageSubresource img_subresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0}; + VkSubresourceLayout srLayout; + const char *pFramebuffer; + if (!needs_2_steps) { + device_data->vtable.GetImageSubresourceLayout(device, data.image2, &img_subresource, &srLayout); + VK_CHECK(device_data->vtable.MapMemory(device, data.mem2, 0, VK_WHOLE_SIZE, 0, (void **)&pFramebuffer)); + data.mem2mapped = true; + } else { + device_data->vtable.GetImageSubresourceLayout(device, data.image3, &img_subresource, &srLayout); + VK_CHECK(device_data->vtable.MapMemory(device, data.mem3, 0, VK_WHOLE_SIZE, 0, (void **)&pFramebuffer)); + data.mem3mapped = true; + } + + // Thread off I/O operations + pthread_t ioThread; + pthread_mutex_lock(&ptLock); // Grab lock, we need to wait until thread has copied values of pointers + struct ThreadSaveData threadData = {device_data, filename, pFramebuffer, srLayout, copyDone, width, height}; + + // Write the data to a PNG file. + pthread_create(&ioThread, NULL, writePNG, (void *)&threadData); + pthread_detach(ioThread); // Reclaim resources once thread terminates + pthread_cond_wait(&ptCondition, &ptLock); + pthread_mutex_unlock(&ptLock); + + return true; +} + +static VkResult screenshot_QueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo) +{ + struct queue_data *queue_data = FIND(struct queue_data, queue); + struct device_data *device_data = queue_data->device; + struct instance_data *instance_data = device_data->instance; + + VkPresentInfoKHR present_info = *pPresentInfo; + + static uint32_t frame_counter = 0; + + VkResult result = VK_SUCCESS; + loader_platform_thread_lock_mutex(&globalLock); + if (pPresentInfo && pPresentInfo->swapchainCount > 0) { + VkSwapchainKHR swapchain = pPresentInfo->pSwapchains[0]; + + struct swapchain_data *swapchain_data = FIND(struct swapchain_data, swapchain); + + /* Run initial setup with client */ + if(instance_data->params.enabled[SCREENSHOT_PARAM_ENABLED_comms] && instance_data->socket_fd < 0) { + int ret = os_socket_listen_abstract(instance_data->params.control, 1); + if (ret >= 0) { + os_socket_block(ret, false); + instance_data->socket_fd = ret; + } + if (instance_data->socket_fd >= 0) + LOG(INFO, "socket set! Waiting for client input...\n"); + } + + if (instance_data->socket_fd >= 0) { + /* Check for input from client */ + control_client_check(device_data); + process_control_socket(instance_data); + } else if (instance_data->params.frames) { + /* Else check if the frame number is within the given frame list */ + if (instance_data->params.frames->size > 0) { + struct frame_list *list = instance_data->params.frames; + struct frame_node *prev = nullptr; + for (struct frame_node *node = list->head; node!=nullptr; prev = node, node = node->next) { + if (frame_counter < node->frame_num){ + break; + } else if (frame_counter == node->frame_num) { + instance_data->screenshot_enabled = true; + remove_node(list, prev, node); + break; + } else { + LOG(ERROR, "mesa-screenshot: Somehow encountered a higher number " + "than what exists in the frame list. Won't capture frame!\n"); + destroy_frame_list(list); + break; + } + } + } else if(instance_data->params.frames->all_frames) { + instance_data->screenshot_enabled = true; + } + } + + if (instance_data->screenshot_enabled) { + LOG(DEBUG, "Screenshot Authorized!\n"); + uint32_t SUFFIX_SIZE = 4; // strlen('.png') == 4; + uint32_t path_size_used = 0; + const char *SUFFIX = ".png"; + const char *TEMP_DIR = "/tmp/"; + char full_path[MAX_PATH_SIZE]; // Let's increase to 512 to account for large files/paths + char filename[256] = ""; + char frame_counter_str[11]; + bool rename_file = true; + itoa(frame_counter, frame_counter_str); + + /* Check if we have an output directory given from the env options */ + if (instance_data->params.output_dir && + strlen(instance_data->params.output_dir) > 0) { + strcat(full_path, instance_data->params.output_dir); + } else { + memcpy(full_path, TEMP_DIR, strlen(TEMP_DIR)); + } + path_size_used += strlen(full_path); + /* Check if we have a filename from the client */ + if (instance_data->filename && strlen(instance_data->filename) > SUFFIX_SIZE) { + /* Confirm that filename is of form '.png' */ + uint32_t name_len = strlen(instance_data->filename); + const char *suffix_ptr = &instance_data->filename[name_len - SUFFIX_SIZE]; + if (!strcmp(suffix_ptr, SUFFIX)) { + rename_file = false; + strcpy(filename, instance_data->filename); + } + } + if (rename_file) { + strcat(filename, frame_counter_str); + strcat(filename, SUFFIX); + } + path_size_used += strlen(filename); + if(path_size_used <= MAX_PATH_SIZE) { + strcat(full_path, filename); + pSemaphoreWaitBeforePresent = pPresentInfo->pWaitSemaphores; + semaphoreWaitBeforePresentCount = pPresentInfo->waitSemaphoreCount; + VkSemaphoreCreateInfo semaphoreInfo = {}; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + device_data->vtable.CreateSemaphore(device_data->device, &semaphoreInfo, nullptr, &semaphoreWaitAfterSubmission); + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + device_data->vtable.CreateFence(device_data->device, &fenceInfo, nullptr, ©Done); + if(write_image(full_path, + swapchain_data->image, + device_data, + instance_data, + swapchain_data)) { + present_info.pWaitSemaphores = &semaphoreWaitAfterSubmission; // Make semaphore here + present_info.waitSemaphoreCount = 1; + } + } else { + LOG(DEBUG, "Cancelling screenshot due to excessive filepath size (max %u characters)\n", MAX_PATH_SIZE); + } + } + } + frame_counter++; + instance_data->screenshot_enabled = false; + loader_platform_thread_unlock_mutex(&globalLock); + VkResult chain_result = queue_data->device->vtable.QueuePresentKHR(queue, &present_info); + if (pPresentInfo->pResults) + pPresentInfo->pResults[0] = chain_result; + if (chain_result != VK_SUCCESS && result == VK_SUCCESS) + result = chain_result; + + if (semaphoreWaitAfterSubmission != VK_NULL_HANDLE) { + device_data->vtable.DestroySemaphore(device_data->device, semaphoreWaitAfterSubmission, nullptr); + semaphoreWaitAfterSubmission = VK_NULL_HANDLE; + } + if (copyDone != VK_NULL_HANDLE) { + device_data->vtable.DestroyFence(device_data->device, copyDone, nullptr); + copyDone = VK_NULL_HANDLE; + } + return result; +} + +static VkResult screenshot_CreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) +{ + struct instance_data *instance_data = + FIND(struct instance_data, physicalDevice); + VkLayerDeviceCreateInfo *chain_info = + get_device_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); + assert(chain_info->u.pLayerInfo); + PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr; + PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(NULL, "vkCreateDevice"); + if (fpCreateDevice == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // Advance the link info for the next element on the chain + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + + VkDeviceCreateInfo create_info = *pCreateInfo; + + VkResult result = fpCreateDevice(physicalDevice, &create_info, pAllocator, pDevice); + if (result != VK_SUCCESS) return result; + + struct device_data *device_data = new_device_data(*pDevice, instance_data); + device_data->physical_device = physicalDevice; + vk_device_dispatch_table_load(&device_data->vtable, + fpGetDeviceProcAddr, *pDevice); + + instance_data->pd_vtable.GetPhysicalDeviceProperties(device_data->physical_device, + &device_data->properties); + + VkLayerDeviceCreateInfo *load_data_info = + get_device_chain_info(pCreateInfo, VK_LOADER_DATA_CALLBACK); + + device_data->set_device_loader_data = load_data_info->u.pfnSetDeviceLoaderData; + + device_map_queues(device_data, pCreateInfo); + return result; +} + +static void screenshot_DestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator) +{ + struct device_data *device_data = FIND(struct device_data, device); + device_unmap_queues(device_data); + device_data->vtable.DestroyDevice(device, pAllocator); + destroy_device_data(device_data); +} + +static VkResult screenshot_CreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) +{ + VkLayerInstanceCreateInfo *chain_info = + get_instance_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); + + assert(chain_info->u.pLayerInfo); + PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = + chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; + PFN_vkCreateInstance fpCreateInstance = + (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); + if (fpCreateInstance == NULL) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + // Advance the link info for the next element on the chain + chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext; + + VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance); + if (result != VK_SUCCESS) return result; + + struct instance_data *instance_data = new_instance_data(*pInstance); + vk_instance_dispatch_table_load(&instance_data->vtable, + fpGetInstanceProcAddr, + instance_data->instance); + vk_physical_device_dispatch_table_load(&instance_data->pd_vtable, + fpGetInstanceProcAddr, + instance_data->instance); + instance_data_map_physical_devices(instance_data, true); + + parse_screenshot_env(&instance_data->params, getenv("VK_LAYER_MESA_SCREENSHOT_CONFIG")); + + if (!globalLockInitialized) { + loader_platform_thread_create_mutex(&globalLock); + globalLockInitialized = 1; + } + + return result; +} + +static void screenshot_DestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator) +{ + struct instance_data *instance_data = FIND(struct instance_data, instance); + instance_data_map_physical_devices(instance_data, false); + instance_data->vtable.DestroyInstance(instance, pAllocator); + destroy_instance_data(instance_data); +} + +static const struct { + const char *name; + void *ptr; +} name_to_funcptr_map[] = { + { "vkGetInstanceProcAddr", (void *) vkGetInstanceProcAddr }, + { "vkGetDeviceProcAddr", (void *) vkGetDeviceProcAddr }, +#define ADD_HOOK(fn) { "vk" # fn, (void *) screenshot_ ## fn } +#define ADD_ALIAS_HOOK(alias, fn) { "vk" # alias, (void *) screenshot_ ## fn } + ADD_HOOK(CreateSwapchainKHR), + ADD_HOOK(GetSwapchainImagesKHR), + ADD_HOOK(DestroySwapchainKHR), + ADD_HOOK(QueuePresentKHR), + + ADD_HOOK(CreateDevice), + ADD_HOOK(DestroyDevice), + + ADD_HOOK(CreateInstance), + ADD_HOOK(DestroyInstance), +#undef ADD_HOOK +#undef ADD_ALIAS_HOOK +}; + +static void *find_ptr(const char *name) +{ + for (uint32_t i = 0; i < ARRAY_SIZE(name_to_funcptr_map); i++) { + if (strcmp(name, name_to_funcptr_map[i].name) == 0) + return name_to_funcptr_map[i].ptr; + } + + return NULL; +} + +PUBLIC VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev, + const char *funcName) +{ + void *ptr = find_ptr(funcName); + if (ptr) return reinterpret_cast(ptr); + + if (dev == NULL) return NULL; + + struct device_data *device_data = FIND(struct device_data, dev); + if (device_data->vtable.GetDeviceProcAddr == NULL) return NULL; + return device_data->vtable.GetDeviceProcAddr(dev, funcName); +} + +PUBLIC VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, + const char *funcName) +{ + void *ptr = find_ptr(funcName); + if (ptr) return reinterpret_cast(ptr); + + if (instance == NULL) return NULL; + + struct instance_data *instance_data = FIND(struct instance_data, instance); + if (instance_data->vtable.GetInstanceProcAddr == NULL) return NULL; + return instance_data->vtable.GetInstanceProcAddr(instance, funcName); +} diff --git a/src/vulkan/screenshot-layer/screenshot_params.c b/src/vulkan/screenshot-layer/screenshot_params.c new file mode 100644 index 00000000000..5fc7037cba1 --- /dev/null +++ b/src/vulkan/screenshot-layer/screenshot_params.c @@ -0,0 +1,414 @@ +/* + * Copyright © 2024 Intel Corporation + * + * 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 (including the next + * paragraph) 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 +#include +#include +#include + +#include "screenshot_params.h" + +#include "util/os_socket.h" + +enum LogType LOG_TYPE = REQUIRED; + +static const char *print_log_type(enum LogType log_type) { + switch(log_type) + { + case(DEBUG): + return "DEBUG"; + case(ERROR): + return "ERROR"; + case(INFO): + return "INFO"; + case(NO_PREFIX): + return "NO_PREFIX"; + case(REQUIRED): + return "REQUIRED"; + case(WARN): + return "WARN"; + default: + /* Don't show log type*/ + return ""; + } +} + +void LOG(enum LogType log_type, const char *format, ...) { + FILE *file_type; + va_list args; + if (log_type == WARN || log_type == ERROR) { + file_type = stderr; + } else { + file_type = stdout; + } + if (log_type == DEBUG && LOG_TYPE != DEBUG) { + return; + } else if (log_type == INFO && (LOG_TYPE != INFO && LOG_TYPE != DEBUG)) { + return; + } + if (log_type != NO_PREFIX) + fprintf(file_type, "mesa-screenshot: %s: ", print_log_type(log_type)); + va_start(args, format); + vfprintf(file_type, format, args); + va_end(args); +} + +static const char * +parse_control(const char *str) +{ + static char control_str[64]; + if (strlen(str) > 63) { + LOG(ERROR, "control string too long. Must be < 64 chars\n"); + return NULL; + } + strcpy(control_str, str); + + return control_str; +} + +/* Inserts frame nodes in ascending order */ +static void insert_frame(struct frame_list *list, uint32_t new_frame_num) +{ + struct frame_node *new_node, *curr, *next; + new_node = (struct frame_node*)malloc(sizeof(struct frame_node)); + new_node->frame_num = new_frame_num; + new_node->next = NULL; + curr = list->head; + + /* Empty list */ + if (list->head == NULL) + list->head = new_node; + /* Insert as new head of list */ + else if (list->head->frame_num > new_frame_num) { + list->head = new_node; + new_node->next = curr; + /* Traverse list & insert frame number in correct, ascending location */ + } else { + while (curr != NULL) { + if (curr->frame_num == new_frame_num) { + free(new_node); + return; // Avoid inserting duplicates + } + next = curr->next; + if (next) { + if (next->frame_num > new_frame_num) { + curr->next = new_node; + new_node->next = next; + break; + } + } else { + curr->next = new_node; + break; + } + curr = curr->next; + } + } + list->size++; +} + +void remove_node(struct frame_list *list, + struct frame_node *prev, + struct frame_node *node) { + if (node) { + if (prev) + prev->next = node->next; + else { + list->head = node->next; + } + free(node); + list->size--; + } else + LOG(ERROR, "Encountered null node while removing from frame list\n"); +} + +void destroy_frame_list(struct frame_list *list) +{ + struct frame_node *curr, *prev; + if (!list || !list->head) + return; + else { + curr = list->head; + while (curr != NULL) { + prev = curr; + curr = curr->next; + free(prev); + } + } +} + +static unsigned +parse_unsigned(const char *str) +{ + return strtol(str, NULL, 0); +} + +static bool is_frame_delimiter(char c) +{ + return c == 0 || c == '/' || c == '-'; +} + +static struct frame_list * +parse_frames(const char *str) +{ + int32_t range_start; + uint32_t range_counter, range_interval, range_end; + range_start = -1; + range_counter = 0; + uint32_t range_delimit_count = 0; + range_interval = 1; + char *prev_delim = NULL; + char str_buf[256] = {0}; + char *str_buf_ptr; + str_buf_ptr = str_buf; + struct frame_list *list = (struct frame_list*)malloc(sizeof(struct frame_list)); + list->size = 0; + list->all_frames = false; + + if (!strcmp(str, "all")) { + /* Don't bother counting, we want all frames */ + list->all_frames = true; + } else { + while (*str != 0) { // Still string left to parse + for (; !is_frame_delimiter(*str); str++, str_buf_ptr++) { + if (!isdigit(*str)) + { + LOG(ERROR, "mesa-screenshot: syntax error: unexpected non-digit " + "'%c' while parsing the frame numbers\n", *str); + destroy_frame_list(list); + return NULL; + } + *str_buf_ptr = *str; + } + if (strlen(str_buf) == 0) { + LOG(ERROR, "mesa-screenshot: syntax error: empty string given in frame range\n"); + return NULL; + } else if (strlen(str_buf) > 0 && *str == '/') { + if (prev_delim && *prev_delim == '-') { + LOG(ERROR, "mesa-screenshot: syntax error: detected invalid individual " \ + "frame selection (/) after range selection (-)\n"); + return NULL; + } + LOG(DEBUG, "Adding frame: %u\n", parse_unsigned(str_buf)); + insert_frame(list, parse_unsigned(str_buf)); + } else if (strlen(str_buf) > 0 && (*str == '-' || *str == 0 )) { + if (range_delimit_count < 1) { + LOG(DEBUG, "Range start set\n"); + range_start = parse_unsigned(str_buf); + range_delimit_count++; + } else if(range_delimit_count < 2) { + LOG(DEBUG, "Range counter set\n"); + range_counter = parse_unsigned(str_buf); + range_delimit_count++; + } else { + LOG(DEBUG, "Range interval set\n"); + range_interval = parse_unsigned(str_buf); + break; + } + if (*str == 0) { + break; + } + prev_delim = (char *)str; + } + str++; + /* Reset buffer for next set of numbers */ + memset(str_buf, '\0', sizeof(str_buf)); + str_buf_ptr = str_buf; + } + range_end = range_start + (range_counter * range_interval); + if (range_start >= 0) { + int i = range_start; + do { + insert_frame(list, i); + i += range_interval; + } while (i < range_end); + } + } + LOG(INFO, "frame range: "); + if (list->all_frames) { + LOG(NO_PREFIX, "all"); + } else { + for (struct frame_node *iter = list->head; iter != NULL; iter = iter->next) { + LOG(NO_PREFIX, "%u", iter->frame_num); + if(iter->next) { + LOG(NO_PREFIX, ", "); + } + } + } + LOG(NO_PREFIX, "\n"); + return list; +} + +static bool +parse_help(const char *str) +{ + LOG(NO_PREFIX, "Layer params using VK_LAYER_MESA_SCREENSHOT_CONFIG=\n"); +#define SCREENSHOT_PARAM_BOOL(name) \ + LOG(NO_PREFIX, "\t%s=0|1\n", #name); +#define SCREENSHOT_PARAM_CUSTOM(name) + SCREENSHOT_PARAMS +#undef SCREENSHOT_PARAM_BOOL +#undef SCREENSHOT_PARAM_CUSTOM + LOG(NO_PREFIX, "\tlog_type=info|debug (if no selection, no logs besides errors are given)\n"); + LOG(NO_PREFIX, "\toutput_dir='/path/to/dir'\n"); + LOG(NO_PREFIX, "\tframes=Individual frames, separated by '/', followed by " \ + "a range setup, separated by '-', --\n" \ + "\tFor example '1/5/7/15-4-5' = [1,5,7,15,20,25,30]\n" \ + "\tframes='all' will select all frames."); + + return true; +} + +static enum LogType +parse_log_type(const char *str) +{ + if(!strcmp(str, "info")) { + return INFO; + } else if (!strcmp(str, "debug")) { + return DEBUG; + } else { + /* Required logs only */ + return REQUIRED; + } +} + +/* TODO: Improve detection of proper directory path */ +static const char * +parse_output_dir(const char *str) +{ + static char output_dir[256]; + strcpy(output_dir, str); + uint32_t last_char_index = strlen(str)-1; + // Ensure we're in bounds and the last character is '/' + if (last_char_index > 0 && + str[last_char_index] != '/' && + last_char_index < 254) { + output_dir[last_char_index+1] = '/'; + } + DIR *dir = opendir(output_dir); + assert(dir); + closedir(dir); + + return output_dir; +} + +static bool is_delimiter(char c) +{ + return c == 0 || c == ',' || c == ':' || c == ';' || c == '='; +} + +static int +parse_string(const char *s, char *out_param, char *out_value) +{ + int i = 0; + + for (; !is_delimiter(*s); s++, out_param++, i++) + *out_param = *s; + + *out_param = 0; + + if (*s == '=') { + s++; + i++; + for (; !is_delimiter(*s); s++, out_value++, i++) + *out_value = *s; + } else + *(out_value++) = '1'; + *out_value = 0; + + if (*s && is_delimiter(*s)) { + s++; + i++; + } + + if (*s && !i) { + LOG(ERROR, "mesa-screenshot: syntax error: unexpected '%c' (%i) while " + "parsing a string\n", *s, *s); + } + return i; +} + +const char *screenshot_param_names[] = { +#define SCREENSHOT_PARAM_BOOL(name) #name, +#define SCREENSHOT_PARAM_CUSTOM(name) + SCREENSHOT_PARAMS +#undef SCREENSHOT_PARAM_BOOL +#undef SCREENSHOT_PARAM_CUSTOM +}; + +void +parse_screenshot_env(struct screenshot_params *params, + const char *env) +{ + + if (!env) + return; + + uint32_t num; + const char *itr = env; + char key[256], value[256]; + + memset(params, 0, sizeof(*params)); + + params->control = "mesa_screenshot"; + params->frames = NULL; + params->output_dir = NULL; + + /* Loop once first until log options found (if they exist) */ + while ((num = parse_string(itr, key, value)) != 0) { + itr += num; + if (!strcmp("log_type", key)) { + LOG_TYPE = parse_log_type(value); + break; + } + } + /* Reset the iterator */ + itr = env; + + while ((num = parse_string(itr, key, value)) != 0) { + itr += num; + if (!strcmp("log_type", key)) { + /* Skip if matched again*/ + continue; + } +#define SCREENSHOT_PARAM_BOOL(name) \ + if (!strcmp(#name, key)) { \ + params->enabled[SCREENSHOT_PARAM_ENABLED_##name] = \ + strtol(value, NULL, 0); \ + continue; \ + } +#define SCREENSHOT_PARAM_CUSTOM(name) \ + if (!strcmp(#name, key)) { \ + params->name = parse_##name(value); \ + continue; \ + } + SCREENSHOT_PARAMS +#undef SCREENSHOT_PARAM_BOOL +#undef SCREENSHOT_PARAM_CUSTOM + LOG(ERROR, "Unknown option '%s'\n", key); + } +} diff --git a/src/vulkan/screenshot-layer/screenshot_params.h b/src/vulkan/screenshot-layer/screenshot_params.h new file mode 100644 index 00000000000..e980013ed17 --- /dev/null +++ b/src/vulkan/screenshot-layer/screenshot_params.h @@ -0,0 +1,98 @@ +/* + * Copyright © 2024 Intel Corporation + * + * 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 (including the next + * paragraph) 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. + */ + +#ifndef SCREENSHOT_PARAMS_H +#define SCREENSHOT_PARAMS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define SCREENSHOT_PARAMS \ + SCREENSHOT_PARAM_BOOL(comms) \ + SCREENSHOT_PARAM_CUSTOM(control) \ + SCREENSHOT_PARAM_CUSTOM(frames) \ + SCREENSHOT_PARAM_CUSTOM(log_type) \ + SCREENSHOT_PARAM_CUSTOM(output_dir) \ + SCREENSHOT_PARAM_CUSTOM(help) + +enum screenshot_param_enabled { +#define SCREENSHOT_PARAM_BOOL(name) SCREENSHOT_PARAM_ENABLED_##name, +#define SCREENSHOT_PARAM_CUSTOM(name) + SCREENSHOT_PARAMS +#undef SCREENSHOT_PARAM_BOOL +#undef SCREENSHOT_PARAM_CUSTOM + SCREENSHOT_PARAM_ENABLED_MAX +}; + +enum LogType { + DEBUG, + ERROR, + INFO, + NO_PREFIX, // Don't prefix the log with text + REQUIRED, // Non-error logs that must be printed for user + WARN +}; + +extern enum LogType LOG_TYPE; + +struct frame_node { + uint32_t frame_num; + struct frame_node *next; +}; + +/* List should be sorted into ascending order, in terms of frame_node data */ +struct frame_list { + uint32_t size; + bool all_frames; + struct frame_node *head; +}; + +void remove_node(struct frame_list *, struct frame_node *, struct frame_node *); +void destroy_frame_list(struct frame_list *); + +void LOG(enum LogType, const char *, ...); + +struct screenshot_params { + bool enabled[SCREENSHOT_PARAM_ENABLED_MAX]; + struct frame_list *frames; + const char *control; + enum LogType log_type; + const char *output_dir; + bool help; +}; + +const extern char *screenshot_param_names[]; + +void parse_screenshot_env(struct screenshot_params *params, + const char *env); + +#ifdef __cplusplus +} +#endif + +#endif /* SCREENSHOT_PARAMS_H */