blob: 22d95984bcc00025cdb4270f0a6f4f5c1d712e79 [file] [log] [blame]
/**************************************************************************
*
* Copyright 2014-2016 Valve Corporation
* Copyright (C) 2014-2016 LunarG, Inc.
* All Rights Reserved.
*
* 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: Jon Ashburn <jon@lunarg.com>
* Author: Peter Lohrmann <peterl@valvesoftware.com>
**************************************************************************/
#include "vktrace.h"
#include "vktrace_process.h"
extern "C" {
#include "vktrace_common.h"
#include "vktrace_filelike.h"
#include "vktrace_interconnect.h"
#include "vktrace_trace_packet_identifiers.h"
#include "vktrace_trace_packet_utils.h"
}
#include <sys/types.h>
#include <sys/stat.h>
vktrace_settings g_settings;
vktrace_settings g_default_settings;
vktrace_SettingInfo g_settings_info[] =
{
// common command options
{ "p", "Program", VKTRACE_SETTING_STRING, { &g_settings.program }, { &g_default_settings.program }, TRUE, "The program to trace."},
{ "a", "Arguments", VKTRACE_SETTING_STRING, { &g_settings.arguments }, { &g_default_settings.arguments }, TRUE, "Command line arguments to pass to trace program."},
{ "w", "WorkingDir", VKTRACE_SETTING_STRING, { &g_settings.working_dir }, { &g_default_settings.working_dir }, TRUE, "The program's working directory."},
{ "o", "OutputTrace", VKTRACE_SETTING_STRING, { &g_settings.output_trace }, { &g_default_settings.output_trace }, TRUE, "Path to the generated output trace file."},
{ "s", "ScreenShot", VKTRACE_SETTING_STRING, { &g_settings.screenshotList }, { &g_default_settings.screenshotList }, TRUE, "Comma separated list of frames to take a snapshot of."},
{ "ptm", "PrintTraceMessages", VKTRACE_SETTING_BOOL, { &g_settings.print_trace_messages }, { &g_default_settings.print_trace_messages }, TRUE, "Print trace messages to vktrace console."},
{ "P", "PMB", VKTRACE_SETTING_BOOL, { &g_settings.enable_pmb }, { &g_default_settings.enable_pmb }, TRUE, "Optimize tracing of persistently mapped buffers, default is FALSE."},
#if _DEBUG
{ "v", "Verbosity", VKTRACE_SETTING_STRING, { &g_settings.verbosity }, { &g_default_settings.verbosity }, TRUE, "Verbosity mode. Modes are \"quiet\", \"errors\", \"warnings\", \"full\", \"debug\"."},
#else
{ "v", "Verbosity", VKTRACE_SETTING_STRING, { &g_settings.verbosity }, { &g_default_settings.verbosity }, TRUE, "Verbosity mode. Modes are \"quiet\", \"errors\", \"warnings\", \"full\"."},
#endif
//{ "z", "pauze", VKTRACE_SETTING_BOOL, &g_settings.pause, &g_default_settings.pause, TRUE, "Wait for a key at startup (so a debugger can be attached)" },
};
vktrace_SettingGroup g_settingGroup =
{
"vktrace",
sizeof(g_settings_info) / sizeof(g_settings_info[0]),
&g_settings_info[0]
};
// ------------------------------------------------------------------------------------------------
#if defined(WIN32)
void MessageLoop()
{
MSG msg = { 0 };
bool quit = false;
while (!quit)
{
if (GetMessage(&msg, NULL, 0, 0) == FALSE)
{
quit = true;
}
else
{
quit = (msg.message == VKTRACE_WM_COMPLETE);
}
}
}
#endif
int PrepareTracers(vktrace_process_capture_trace_thread_info** ppTracerInfo)
{
unsigned int num_tracers = 1;
assert(ppTracerInfo != NULL && *ppTracerInfo == NULL);
*ppTracerInfo = VKTRACE_NEW_ARRAY(vktrace_process_capture_trace_thread_info, num_tracers);
memset(*ppTracerInfo, 0, sizeof(vktrace_process_capture_trace_thread_info) * num_tracers);
// we only support Vulkan tracer
(*ppTracerInfo)[0].tracerId = VKTRACE_TID_VULKAN;
return num_tracers;
}
bool InjectTracersIntoProcess(vktrace_process_info* pInfo)
{
bool bRecordingThreadsCreated = true;
vktrace_thread tracingThread;
if (vktrace_platform_remote_load_library(pInfo->hProcess, NULL, &tracingThread, NULL)) {
// prepare data for capture threads
pInfo->pCaptureThreads[0].pProcessInfo = pInfo;
pInfo->pCaptureThreads[0].recordingThread = VKTRACE_NULL_THREAD;
// create thread to record trace packets from the tracer
pInfo->pCaptureThreads[0].recordingThread = vktrace_platform_create_thread(Process_RunRecordTraceThread, &(pInfo->pCaptureThreads[0]));
if (pInfo->pCaptureThreads[0].recordingThread == VKTRACE_NULL_THREAD) {
vktrace_LogError("Failed to create trace recording thread.");
bRecordingThreadsCreated = false;
}
} else {
// failed to inject a DLL
bRecordingThreadsCreated = false;
}
return bRecordingThreadsCreated;
}
void loggingCallback(VktraceLogLevel level, const char* pMessage)
{
if (level == VKTRACE_LOG_NONE)
return;
switch(level)
{
case VKTRACE_LOG_DEBUG: printf("vktrace debug: %s\n", pMessage); break;
case VKTRACE_LOG_ERROR: printf("vktrace error: %s\n", pMessage); break;
case VKTRACE_LOG_WARNING: printf("vktrace warning: %s\n", pMessage); break;
case VKTRACE_LOG_VERBOSE: printf("vktrace info: %s\n", pMessage); break;
default:
printf("%s\n", pMessage); break;
}
fflush(stdout);
#if defined(WIN32)
#if _DEBUG
OutputDebugString(pMessage);
#endif
#endif
}
// ------------------------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
memset(&g_settings, 0, sizeof(vktrace_settings));
vktrace_LogSetCallback(loggingCallback);
vktrace_LogSetLevel(VKTRACE_LOG_ERROR);
// setup defaults
memset(&g_default_settings, 0, sizeof(vktrace_settings));
g_default_settings.output_trace = vktrace_allocate_and_copy("vktrace_out.vktrace");
g_default_settings.verbosity = "errors";
g_default_settings.screenshotList = NULL;
g_default_settings.enable_pmb = false;;
if (vktrace_SettingGroup_init(&g_settingGroup, NULL, argc, argv, &g_settings.arguments) != 0)
{
// invalid cmd-line parameters
vktrace_SettingGroup_delete(&g_settingGroup);
vktrace_free(g_default_settings.output_trace);
return -1;
}
else
{
// Validate vktrace inputs
BOOL validArgs = TRUE;
if (g_settings.output_trace == NULL || strlen (g_settings.output_trace) == 0)
{
validArgs = FALSE;
}
if (strcmp(g_settings.verbosity, "quiet") == 0)
vktrace_LogSetLevel(VKTRACE_LOG_NONE);
else if (strcmp(g_settings.verbosity, "errors") == 0)
vktrace_LogSetLevel(VKTRACE_LOG_ERROR);
else if (strcmp(g_settings.verbosity, "warnings") == 0)
vktrace_LogSetLevel(VKTRACE_LOG_WARNING);
else if (strcmp(g_settings.verbosity, "full") == 0)
vktrace_LogSetLevel(VKTRACE_LOG_VERBOSE);
#if _DEBUG
else if (strcmp(g_settings.verbosity, "debug") == 0)
vktrace_LogSetLevel(VKTRACE_LOG_DEBUG);
#endif
else
{
vktrace_LogSetLevel(VKTRACE_LOG_ERROR);
validArgs = FALSE;
}
vktrace_set_global_var("_VK_TRACE_VERBOSITY", g_settings.verbosity);
if (validArgs == FALSE)
{
vktrace_SettingGroup_print(&g_settingGroup);
return -1;
}
if (g_settings.program == NULL || strlen(g_settings.program) == 0)
{
vktrace_LogWarning("No program (-p) parameter found: Will run vktrace as server.");
printf("Running vktrace as server...\n");
fflush(stdout);
g_settings.arguments = NULL;
}
else
{
if (g_settings.working_dir == NULL || strlen(g_settings.working_dir) == 0)
{
CHAR* buf = VKTRACE_NEW_ARRAY(CHAR, 4096);
vktrace_LogVerbose("No working directory (-w) parameter found: Assuming executable's path as working directory.");
vktrace_platform_full_path(g_settings.program, 4096, buf);
g_settings.working_dir = vktrace_platform_extract_path(buf);
VKTRACE_DELETE(buf);
}
vktrace_LogVerbose("Running vktrace as parent process will spawn child process: %s", g_settings.program);
if (g_settings.arguments != NULL && strlen(g_settings.arguments) > 0)
{
vktrace_LogVerbose("Args to be passed to child process: '%s'", g_settings.arguments);
}
}
}
if (g_settings.screenshotList)
{
// Export list to screenshot layer
vktrace_set_global_var("_VK_SCREENSHOT", g_settings.screenshotList);
}
else
{
vktrace_set_global_var("_VK_SCREENSHOT","");
}
vktrace_set_global_var("_VKTRACE_OPTIMIZE_PMB", g_settings.enable_pmb?"1":"0");
unsigned int serverIndex = 0;
do {
// Create and start the process or run in server mode
BOOL procStarted = TRUE;
vktrace_process_info procInfo;
memset(&procInfo, 0, sizeof(vktrace_process_info));
if (g_settings.program != NULL)
{
procInfo.exeName = vktrace_allocate_and_copy(g_settings.program);
procInfo.processArgs = vktrace_allocate_and_copy(g_settings.arguments);
procInfo.fullProcessCmdLine = vktrace_copy_and_append(g_settings.program, " ", g_settings.arguments);
procInfo.workingDirectory = vktrace_allocate_and_copy(g_settings.working_dir);
procInfo.traceFilename = vktrace_allocate_and_copy(g_settings.output_trace);
} else
{
const char *pExtension = strrchr(g_settings.output_trace, '.');
char *basename = vktrace_allocate_and_copy_n(g_settings.output_trace, (int) ((pExtension == NULL) ? strlen(g_settings.output_trace) : pExtension - g_settings.output_trace));
char num[16];
#ifdef PLATFORM_LINUX
snprintf(num, 16, "%u", serverIndex);
#elif defined(WIN32)
_snprintf_s(num, 16, _TRUNCATE, "%u", serverIndex);
#endif
procInfo.traceFilename = vktrace_copy_and_append(basename, num, pExtension);
}
procInfo.parentThreadId = vktrace_platform_get_thread_id();
// setup tracer, only Vulkan tracer suppported
PrepareTracers(&procInfo.pCaptureThreads);
if (g_settings.program != NULL)
{
char *instEnv = vktrace_get_global_var("VK_INSTANCE_LAYERS");
// Add ScreenShot layer if enabled
if (g_settings.screenshotList && (!instEnv || !strstr(instEnv, "VK_LAYER_LUNARG_screenshot")))
{
if (!instEnv || strlen(instEnv) == 0)
vktrace_set_global_var("VK_INSTANCE_LAYERS", "VK_LAYER_LUNARG_screenshot");
else
{
char *newEnv = vktrace_copy_and_append(instEnv, VKTRACE_LIST_SEPARATOR, "VK_LAYER_LUNARG_screenshot");
vktrace_set_global_var("VK_INSTANCE_LAYERS", newEnv);
}
instEnv = vktrace_get_global_var("VK_INSTANCE_LAYERS");
}
char *devEnv = vktrace_get_global_var("VK_DEVICE_LAYERS");
if (g_settings.screenshotList && (!devEnv || !strstr(devEnv, "VK_LAYER_LUNARG_screenshot")))
{
if (!devEnv || strlen(devEnv) == 0)
vktrace_set_global_var("VK_DEVICE_LAYERS", "VK_LAYER_LUNARG_screenshot");
else
{
char *newEnv = vktrace_copy_and_append(devEnv, VKTRACE_LIST_SEPARATOR, "VK_LAYER_LUNARG_screenshot");
vktrace_set_global_var("VK_DEVICE_LAYERS", newEnv);
}
devEnv = vktrace_get_global_var("VK_DEVICE_LAYERS");
}
// Add vktrace_layer enable env var if needed
if (!instEnv || strlen(instEnv) == 0)
{
vktrace_set_global_var("VK_INSTANCE_LAYERS", "VK_LAYER_LUNARG_vktrace");
}
else if (instEnv != strstr(instEnv, "VK_LAYER_LUNARG_vktrace"))
{
char *newEnv = vktrace_copy_and_append("VK_LAYER_LUNARG_vktrace", VKTRACE_LIST_SEPARATOR, instEnv);
vktrace_set_global_var("VK_INSTANCE_LAYERS", newEnv);
}
if (!devEnv || strlen(devEnv) == 0)
{
vktrace_set_global_var("VK_DEVICE_LAYERS", "VK_LAYER_LUNARG_vktrace");
}
else if (devEnv != strstr(devEnv, "VK_LAYER_LUNARG_vktrace"))
{
char *newEnv = vktrace_copy_and_append("VK_LAYER_LUNARG_vktrace", VKTRACE_LIST_SEPARATOR, devEnv);
vktrace_set_global_var("VK_DEVICE_LAYERS", newEnv);
}
// call CreateProcess to launch the application
procStarted = vktrace_process_spawn(&procInfo);
}
if (procStarted == FALSE)
{
vktrace_LogError("Failed to setup remote process.");
}
else
{
if (InjectTracersIntoProcess(&procInfo) == FALSE)
{
vktrace_LogError("Failed to setup tracer communication threads.");
return -1;
}
// create watchdog thread to monitor existence of remote process
if (g_settings.program != NULL)
procInfo.watchdogThread = vktrace_platform_create_thread(Process_RunWatchdogThread, &procInfo);
#if defined(PLATFORM_LINUX) || defined(PLATFORM_OSX)
// Sync wait for local threads and remote process to complete.
vktrace_platform_sync_wait_for_thread(&(procInfo.pCaptureThreads[0].recordingThread));
if (g_settings.program != NULL)
vktrace_platform_sync_wait_for_thread(&procInfo.watchdogThread);
#else
vktrace_platform_resume_thread(&procInfo.hThread);
// Now into the main message loop, listen for hotkeys to send over.
MessageLoop();
#endif
}
vktrace_process_info_delete(&procInfo);
serverIndex++;
} while (g_settings.program == NULL);
vktrace_SettingGroup_delete(&g_settingGroup);
vktrace_free(g_default_settings.output_trace);
return 0;
}