/* * Copyright 2020 Hans-Kristian Arntzen for Valve Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #define VKD3D_DBG_CHANNEL VKD3D_DBG_CHANNEL_API #include "vkd3d_renderdoc.h" #include "vkd3d_debug.h" #include "vkd3d_threads.h" #include "vkd3d_spinlock.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #else #include #endif #include #include #include #include #include "renderdoc_app.h" #include "vkd3d_shader.h" static RENDERDOC_API_1_0_0 *renderdoc_api; static vkd3d_shader_hash_t renderdoc_capture_shader_hash; static uint32_t *renderdoc_capture_counts; static size_t renderdoc_capture_counts_count; static bool vkd3d_renderdoc_is_active; static bool vkd3d_renderdoc_global_capture; static void vkd3d_renderdoc_init_capture_count_list(const char *env) { size_t array_size = 0; uint32_t count; char *endp; if (strcmp(env, "-1") == 0) { INFO("Doing one big capture of the entire lifetime of a device.\n"); vkd3d_renderdoc_global_capture = true; return; } while (*env != '\0') { errno = 0; count = strtoul(env, &endp, 0); if (errno != 0) { ERR("Error parsing auto counts.\n"); break; } vkd3d_array_reserve((void**)&renderdoc_capture_counts, &array_size, renderdoc_capture_counts_count + 1, sizeof(*renderdoc_capture_counts)); INFO("Enabling automatic RenderDoc capture of submit #%u.\n", count); renderdoc_capture_counts[renderdoc_capture_counts_count++] = count; if (*endp == ',') env = endp + 1; else if (*endp != '\0') { ERR("Unexpected character %c.\n", *endp); break; } else env = endp; } } static bool vkd3d_renderdoc_enable_submit_counter(uint32_t counter) { size_t i; /* TODO: Can be smarter about this if we have to. */ for (i = 0; i < renderdoc_capture_counts_count; i++) if (renderdoc_capture_counts[i] == counter) return true; return false; } static void vkd3d_renderdoc_init_once(void) { char counts[VKD3D_PATH_MAX]; pRENDERDOC_GetAPI get_api; char env[VKD3D_PATH_MAX]; #ifdef _WIN32 HMODULE renderdoc; FARPROC fn_ptr; #else void *renderdoc; void *fn_ptr; #endif vkd3d_get_env_var("VKD3D_AUTO_CAPTURE_SHADER", env, sizeof(env)); vkd3d_get_env_var("VKD3D_AUTO_CAPTURE_COUNTS", counts, sizeof(counts)); if (strlen(env) == 0 && strlen(counts) == 0) { WARN("VKD3D_AUTO_CAPTURE_SHADER or VKD3D_AUTO_CAPTURE_COUNTS is not set, RenderDoc auto capture will not be enabled.\n"); return; } if (strlen(counts) == 0) WARN("VKD3D_AUTO_CAPTURE_COUNTS is not set, will assume that only the first submission is captured.\n"); if (strlen(env) > 0) renderdoc_capture_shader_hash = strtoull(env, NULL, 16); if (renderdoc_capture_shader_hash) INFO("Enabling RenderDoc capture for shader hash: %016"PRIx64".\n", renderdoc_capture_shader_hash); else INFO("Enabling RenderDoc capture for all shaders.\n"); if (strlen(counts) > 0) vkd3d_renderdoc_init_capture_count_list(counts); else { static uint32_t zero_count; renderdoc_capture_counts = &zero_count; renderdoc_capture_counts_count = 1; } vkd3d_renderdoc_is_active = true; /* The RenderDoc layer must be loaded. */ #ifdef _WIN32 renderdoc = GetModuleHandleA("renderdoc.dll"); #else renderdoc = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD); #endif if (!renderdoc) { ERR("Failed to load existing RenderDoc library, falling back to using magic vkQueue label.\n"); return; } #ifdef _WIN32 fn_ptr = GetProcAddress(renderdoc, "RENDERDOC_GetAPI"); #else fn_ptr = dlsym(renderdoc, "RENDERDOC_GetAPI"); #endif /* Workaround compiler warnings about casting to function pointer. */ memcpy(&get_api, &fn_ptr, sizeof(fn_ptr)); if (!get_api) { ERR("Failed to load RENDERDOC_GetAPI.\n"); return; } if (!get_api(eRENDERDOC_API_Version_1_0_0, (void**)&renderdoc_api)) { ERR("Failed to obtain RenderDoc API.\n"); renderdoc_api = NULL; } } static pthread_once_t vkd3d_renderdoc_once = PTHREAD_ONCE_INIT; void vkd3d_renderdoc_init(void) { pthread_once(&vkd3d_renderdoc_once, vkd3d_renderdoc_init_once); } bool vkd3d_renderdoc_active(void) { return vkd3d_renderdoc_is_active; } bool vkd3d_renderdoc_global_capture_enabled(void) { return vkd3d_renderdoc_global_capture; } bool vkd3d_renderdoc_should_capture_shader_hash(vkd3d_shader_hash_t hash) { return (renderdoc_capture_shader_hash == hash) || (renderdoc_capture_shader_hash == 0); } bool vkd3d_renderdoc_begin_capture(void *instance) { static uint32_t overall_counter; uint32_t counter; if (!vkd3d_renderdoc_global_capture) { counter = vkd3d_atomic_uint32_increment(&overall_counter, vkd3d_memory_order_relaxed) - 1; if (!vkd3d_renderdoc_enable_submit_counter(counter)) return false; } if (renderdoc_api) renderdoc_api->StartFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), NULL); return true; } bool vkd3d_renderdoc_loaded_api(void) { return renderdoc_api != NULL; } void vkd3d_renderdoc_end_capture(void *instance) { if (renderdoc_api) renderdoc_api->EndFrameCapture(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), NULL); } void vkd3d_renderdoc_command_list_check_capture(struct d3d12_command_list *list, struct d3d12_pipeline_state *state) { unsigned int i; if (vkd3d_renderdoc_global_capture_enabled()) return; if (vkd3d_renderdoc_active() && state) { if (state->vk_bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) { if (vkd3d_renderdoc_should_capture_shader_hash(state->compute.code.meta.hash)) { WARN("Triggering RenderDoc capture for this command list.\n"); list->debug_capture = true; } } else if (state->vk_bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { for (i = 0; i < state->graphics.stage_count; i++) { if (vkd3d_renderdoc_should_capture_shader_hash(state->graphics.code[i].meta.hash)) { WARN("Triggering RenderDoc capture for this command list.\n"); list->debug_capture = true; break; } } } } } bool vkd3d_renderdoc_command_queue_begin_capture(struct d3d12_command_queue *command_queue) { const struct vkd3d_vk_device_procs *vk_procs = &command_queue->device->vk_procs; VkDebugUtilsLabelEXT capture_label; bool debug_capture; if (vkd3d_renderdoc_global_capture_enabled()) return false; debug_capture = vkd3d_renderdoc_begin_capture(command_queue->device->vkd3d_instance->vk_instance); if (debug_capture && !vkd3d_renderdoc_loaded_api()) { if (command_queue->device->vk_info.EXT_debug_utils) { /* Magic fallback which lets us bridge the Wine barrier over to Linux RenderDoc. */ capture_label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; capture_label.pNext = NULL; capture_label.pLabelName = "capture-marker,begin_capture"; memset(capture_label.color, 0, sizeof(capture_label.color)); VK_CALL(vkQueueInsertDebugUtilsLabelEXT(command_queue->vkd3d_queue->vk_queue, &capture_label)); } else { ERR("Attempting to use magic tag fallback for RenderDoc captures but VK_EXT_debug_utils is not supported.\n"); debug_capture = false; } } return debug_capture; } void vkd3d_renderdoc_command_queue_end_capture(struct d3d12_command_queue *command_queue) { const struct vkd3d_vk_device_procs *vk_procs = &command_queue->device->vk_procs; VkDebugUtilsLabelEXT capture_label; if (vkd3d_renderdoc_global_capture_enabled()) return; if (!vkd3d_renderdoc_loaded_api()) { /* Magic fallback which lets us bridge the Wine barrier over to Linux RenderDoc. */ capture_label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; capture_label.pNext = NULL; capture_label.pLabelName = "capture-marker,end_capture"; memset(capture_label.color, 0, sizeof(capture_label.color)); VK_CALL(vkQueueInsertDebugUtilsLabelEXT(command_queue->vkd3d_queue->vk_queue, &capture_label)); } else vkd3d_renderdoc_end_capture(command_queue->device->vkd3d_instance->vk_instance); }