/*
 * Copyright 2023 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_private.h"
#include "vkd3d_platform.h"
#include "vkd3d_threads.h"
#include <assert.h>
#include <stdio.h>

#define NUM_ENTRIES (256 * 1024)

HRESULT vkd3d_queue_timeline_trace_init(struct vkd3d_queue_timeline_trace *trace, struct d3d12_device *device)
{
    char env[VKD3D_PATH_MAX];
    unsigned int i;

    if (!vkd3d_get_env_var("VKD3D_QUEUE_PROFILE", env, sizeof(env)))
        return S_OK;

    trace->file = fopen(env, "w");
    if (trace->file)
    {
        INFO("Creating timeline trace in: \"%s\".\n", env);
        fputs("[\n", trace->file);
    }
    else
        return S_OK;

    pthread_mutex_init(&trace->lock, NULL);
    pthread_mutex_init(&trace->ready_lock, NULL);

    vkd3d_array_reserve((void**)&trace->vacant_indices, &trace->vacant_indices_size,
            NUM_ENTRIES, sizeof(*trace->vacant_indices));

    /* Reserve entry 0 as sentinel. */
    for (i = 1; i < NUM_ENTRIES; i++)
        trace->vacant_indices[trace->vacant_indices_count++] = i;

    trace->state = vkd3d_calloc(NUM_ENTRIES, sizeof(*trace->state));
    trace->base_ts = vkd3d_get_current_time_ns();

    if (vkd3d_get_env_var("VKD3D_QUEUE_PROFILE_ABSOLUTE", env, sizeof(env)) &&
            env[0] == '1')
    {
        /* Wine logs are QPC relative */
        trace->base_ts = 0;

        /* Force an event at ts = 0 so the trace gets absolute time. */
        fprintf(trace->file,
                "{ \"name\": \"dummy\", \"ph\": \"i\", \"tid\": \"0x%04x\", \"pid\": 0, \"ts\": 0.0 },\n",
                vkd3d_get_current_thread_id());
    }

    trace->active = true;
    return S_OK;
}

static void vkd3d_queue_timeline_trace_free_index(struct vkd3d_queue_timeline_trace *trace, unsigned int index)
{
    assert(trace->state[index].type != VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_NONE);
    trace->state[index].type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_NONE;

    pthread_mutex_lock(&trace->lock);
    assert(trace->vacant_indices_count < trace->vacant_indices_size);
    trace->vacant_indices[trace->vacant_indices_count++] = index;
    pthread_mutex_unlock(&trace->lock);
}

static void vkd3d_queue_timeline_trace_free_indices(struct vkd3d_queue_timeline_trace *trace,
        const unsigned int *indices, size_t count)
{
    size_t i;
    pthread_mutex_lock(&trace->lock);

    for (i = 0; i < count; i++)
    {
        assert(trace->state[indices[i]].type != VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_NONE);
        trace->state[indices[i]].type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_NONE;
    }

    assert(trace->vacant_indices_count + count <= trace->vacant_indices_size);
    memcpy(trace->vacant_indices + trace->vacant_indices_count, indices, count * sizeof(*indices));
    trace->vacant_indices_count += count;
    pthread_mutex_unlock(&trace->lock);
}

static unsigned int vkd3d_queue_timeline_trace_allocate_index(struct vkd3d_queue_timeline_trace *trace, uint64_t *submit_count)
{
    unsigned int index = 0;
    pthread_mutex_lock(&trace->lock);
    if (trace->vacant_indices_count == 0)
    {
        ERR("Failed to allocate queue timeline index.\n");
        goto unlock;
    }
    index = trace->vacant_indices[--trace->vacant_indices_count];
unlock:
    if (submit_count)
        *submit_count = ++trace->submit_count;
    pthread_mutex_unlock(&trace->lock);
    return index;
}

void vkd3d_queue_timeline_trace_cleanup(struct vkd3d_queue_timeline_trace *trace)
{
    if (!trace->active)
        return;

    pthread_mutex_destroy(&trace->lock);
    pthread_mutex_destroy(&trace->ready_lock);
    if (trace->file)
        fclose(trace->file);

    vkd3d_free(trace->vacant_indices);
    vkd3d_free(trace->ready_command_lists);
    vkd3d_free(trace->state);
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_event_signal(struct vkd3d_queue_timeline_trace *trace,
        vkd3d_native_sync_handle handle, d3d12_fence_iface *fence, uint64_t value)
{
    struct vkd3d_queue_timeline_trace_cookie cookie = {0};
    struct vkd3d_queue_timeline_trace_state *state;

    if (!trace->active)
        return cookie;

    cookie.index = vkd3d_queue_timeline_trace_allocate_index(trace, NULL);
    if (!cookie.index)
        return cookie;

    state = &trace->state[cookie.index];
    state->type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_EVENT;
    state->start_ts = vkd3d_get_current_time_ns();

#ifdef _WIN32
    snprintf(state->desc, sizeof(state->desc), "event: %p, fence: %p, value %"PRIu64,
            handle.handle, (void*)fence, value);
#else
    snprintf(state->desc, sizeof(state->desc), "event: %d, fence: %p, value %"PRIu64,
            handle.fd, (void*)fence, value);
#endif

    return cookie;
}

void vkd3d_queue_timeline_trace_complete_event_signal(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_fence_worker *worker,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    const struct vkd3d_queue_timeline_trace_state *state;
    double end_ts, start_ts;
    unsigned int pid;

    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    end_ts = (double)(vkd3d_get_current_time_ns() - trace->base_ts) * 1e-3;
    start_ts = (double)(state->start_ts - trace->base_ts) * 1e-3;

    if (worker)
    {
        pid = worker->queue->submission_thread_tid;
        if (start_ts < worker->timeline.lock_end_event_ts)
            start_ts = worker->timeline.lock_end_event_ts;
        if (end_ts < start_ts)
            end_ts = start_ts;
        worker->timeline.lock_end_event_ts = end_ts;

        fprintf(trace->file, "{ \"name\": \"%s\", \"ph\": \"X\", \"tid\": \"event\", \"pid\": \"0x%04x\", \"ts\": %f, \"dur\": %f },\n",
                state->desc, pid, start_ts, end_ts - start_ts);
    }
    else
    {
        fprintf(trace->file, "{ \"name\": \"%s\", \"ph\": \"X\", \"tid\": \"inline\", \"pid\": \"shared fence\", \"ts\": %f, \"dur\": %f },\n",
                state->desc, start_ts, end_ts - start_ts);
    }

    vkd3d_queue_timeline_trace_free_index(trace, cookie.index);
}

void vkd3d_queue_timeline_trace_complete_present_wait(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    const struct vkd3d_queue_timeline_trace_state *state;
    double end_ts, start_ts;

    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    end_ts = (double)(vkd3d_get_current_time_ns() - trace->base_ts) * 1e-3;
    start_ts = (double)(state->start_ts - trace->base_ts) * 1e-3;

    fprintf(trace->file, "{ \"name\": \"%s\", \"ph\": \"X\", \"tid\": \"wait\", \"pid\": \"present\", \"ts\": %f, \"dur\": %f },\n",
            state->desc, start_ts, end_ts - start_ts);

    vkd3d_queue_timeline_trace_free_index(trace, cookie.index);
}

void vkd3d_queue_timeline_trace_complete_pso_compile(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie, uint64_t pso_hash, const char *completion_kind)
{
    const struct vkd3d_queue_timeline_trace_state *state;
    double end_ts, start_ts;
    unsigned int tid;

    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    end_ts = (double)(vkd3d_get_current_time_ns() - trace->base_ts) * 1e-3;
    start_ts = (double)(state->start_ts - trace->base_ts) * 1e-3;

    tid = vkd3d_get_current_thread_id();
    fprintf(trace->file, "{ \"name\": \"%016"PRIx64" %s\", \"ph\": \"X\", \"tid\": \"0x%04x\", \"pid\": \"pso\", \"ts\": %f, \"dur\": %f },\n",
            pso_hash, completion_kind, tid, start_ts, end_ts - start_ts);

    vkd3d_queue_timeline_trace_free_index(trace, cookie.index);
}

void vkd3d_queue_timeline_trace_complete_blocking(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie, const char *pid)
{
    const struct vkd3d_queue_timeline_trace_state *state;
    double end_ts, start_ts;

    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    end_ts = (double)(vkd3d_get_current_time_ns() - trace->base_ts) * 1e-3;
    start_ts = (double)(state->start_ts - trace->base_ts) * 1e-3;

    fprintf(trace->file, "{ \"name\": \"%s\", \"ph\": \"X\", \"tid\": \"0x%04x\", \"pid\": \"%s\", \"ts\": %f, \"dur\": %f },\n",
            state->desc, state->tid, pid, start_ts, end_ts - start_ts);

    vkd3d_queue_timeline_trace_free_index(trace, cookie.index);
}

void vkd3d_queue_timeline_trace_complete_present_block(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    vkd3d_queue_timeline_trace_complete_blocking(trace, cookie, "IDXGISwapChain::Present()");
}

void vkd3d_queue_timeline_trace_complete_low_latency_sleep(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    vkd3d_queue_timeline_trace_complete_blocking(trace, cookie, "ID3DLowLatencyDevice::LatencySleep()");
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_sparse(struct vkd3d_queue_timeline_trace *trace, uint32_t num_tiles)
{
    struct vkd3d_queue_timeline_trace_cookie cookie = {0};
    struct vkd3d_queue_timeline_trace_state *state;
    uint64_t submission_count;
    if (!trace->active)
        return cookie;

    cookie.index = vkd3d_queue_timeline_trace_allocate_index(trace, &submission_count);
    if (!cookie.index)
        return cookie;

    state = &trace->state[cookie.index];
    state->type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_SUBMISSION;
    state->start_ts = vkd3d_get_current_time_ns();
    snprintf(state->desc, sizeof(state->desc), "SPARSE #%"PRIu64" (%u tiles)", submission_count, num_tiles);
    return cookie;
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_execute(struct vkd3d_queue_timeline_trace *trace,
        ID3D12CommandList * const *command_lists, unsigned int count)
{
    struct vkd3d_queue_timeline_trace_cookie cookie = {0};
    struct vkd3d_queue_timeline_trace_state *state;
    uint64_t submission_count;
    if (!trace->active)
        return cookie;

    cookie.index = vkd3d_queue_timeline_trace_allocate_index(trace, &submission_count);
    if (!cookie.index)
        return cookie;

    state = &trace->state[cookie.index];
    state->type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_SUBMISSION;
    state->start_ts = vkd3d_get_current_time_ns();
    snprintf(state->desc, sizeof(state->desc), "SUBMIT #%"PRIu64" (%u lists)", submission_count, count);

    /* Might be useful later. */
    (void)command_lists;

    return cookie;
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_signal(struct vkd3d_queue_timeline_trace *trace,
        d3d12_fence_iface *fence, uint64_t value)
{
    struct vkd3d_queue_timeline_trace_cookie cookie = {0};
    struct vkd3d_queue_timeline_trace_state *state;
    if (!trace->active)
        return cookie;

    cookie.index = vkd3d_queue_timeline_trace_allocate_index(trace, NULL);
    if (!cookie.index)
        return cookie;

    state = &trace->state[cookie.index];
    state->type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_SIGNAL;
    state->start_ts = vkd3d_get_current_time_ns();
    state->start_submit_ts = state->start_ts;
    snprintf(state->desc, sizeof(state->desc), "SIGNAL %p %"PRIu64, (void*)fence, value);
    return cookie;
}

void vkd3d_queue_timeline_trace_cpu_signal(struct vkd3d_queue_timeline_trace *trace,
        d3d12_fence_iface *fence, uint64_t value)
{
    struct vkd3d_queue_timeline_trace_state *state;
    unsigned int index;

    if (!trace->active)
        return;

    index = vkd3d_queue_timeline_trace_allocate_index(trace, NULL);
    if (!index)
        return;

    state = &trace->state[index];
    state->type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_CPU_SIGNAL;
    state->start_ts = vkd3d_get_current_time_ns();
    state->tid = vkd3d_get_current_thread_id();
    state->record_cookie = value;
    snprintf(state->desc, sizeof(state->desc), "SIGNAL %p %"PRIu64, (void*)fence, value);

    /* Defer actual IO until fence workers are doing something. */
    pthread_mutex_lock(&trace->ready_lock);
    vkd3d_array_reserve((void**)&trace->ready_command_lists, &trace->ready_command_lists_size,
            trace->ready_command_lists_count + 1, sizeof(*trace->ready_command_lists));
    trace->ready_command_lists[trace->ready_command_lists_count++] = index;
    pthread_mutex_unlock(&trace->ready_lock);
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_wait(struct vkd3d_queue_timeline_trace *trace,
        d3d12_fence_iface *fence, uint64_t value)
{
    struct vkd3d_queue_timeline_trace_cookie cookie = {0};
    struct vkd3d_queue_timeline_trace_state *state;
    if (!trace->active)
        return cookie;

    cookie.index = vkd3d_queue_timeline_trace_allocate_index(trace, NULL);
    if (!cookie.index)
        return cookie;

    state = &trace->state[cookie.index];
    state->type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_WAIT;
    state->start_ts = vkd3d_get_current_time_ns();
    state->start_submit_ts = state->start_ts;
    snprintf(state->desc, sizeof(state->desc), "WAIT %p %"PRIu64, (void*)fence, value);
    return cookie;
}

static struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_generic_op(struct vkd3d_queue_timeline_trace *trace,
        enum vkd3d_queue_timeline_trace_state_type type, const char *tag)
{
    struct vkd3d_queue_timeline_trace_cookie cookie = {0};
    struct vkd3d_queue_timeline_trace_state *state;
    if (!trace->active)
        return cookie;

    cookie.index = vkd3d_queue_timeline_trace_allocate_index(trace, NULL);
    if (!cookie.index)
        return cookie;

    state = &trace->state[cookie.index];
    state->type = type;
    state->start_ts = vkd3d_get_current_time_ns();
    state->start_submit_ts = state->start_ts;
    state->tid = vkd3d_get_current_thread_id();
    vkd3d_strlcpy(state->desc, sizeof(state->desc), tag);
    return cookie;
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_swapchain_blit(struct vkd3d_queue_timeline_trace *trace, uint64_t present_id)
{
    char str[128];
    snprintf(str, sizeof(str), "PRESENT (id = %"PRIu64") (blit)", present_id);
    return vkd3d_queue_timeline_trace_register_generic_op(trace, VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_PRESENT_BLIT, str);
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_command_list(struct vkd3d_queue_timeline_trace *trace)
{
    struct vkd3d_queue_timeline_trace_cookie cookie = {0};
    struct vkd3d_queue_timeline_trace_state *state;
    uint64_t submission_count;
    if (!trace->active || !(vkd3d_config_flags & VKD3D_CONFIG_FLAG_QUEUE_PROFILE_EXTRA))
        return cookie;

    cookie.index = vkd3d_queue_timeline_trace_allocate_index(trace, &submission_count);
    if (!cookie.index)
        return cookie;

    state = &trace->state[cookie.index];
    state->type = VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_COMMAND_LIST;
    state->start_ts = vkd3d_get_current_time_ns();
    state->record_cookie = submission_count;
    return cookie;
}

void vkd3d_queue_timeline_trace_close_command_list(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    struct vkd3d_queue_timeline_trace_state *state;
    if (!trace->active || cookie.index == 0 || !(vkd3d_config_flags & VKD3D_CONFIG_FLAG_QUEUE_PROFILE_EXTRA))
        return;

    state = &trace->state[cookie.index];
    state->record_end_ts = vkd3d_get_current_time_ns();
    state->tid = vkd3d_get_current_thread_id();

    /* Defer actual IO until fence workers are doing something. */
    pthread_mutex_lock(&trace->ready_lock);
    vkd3d_array_reserve((void**)&trace->ready_command_lists, &trace->ready_command_lists_size,
            trace->ready_command_lists_count + 1, sizeof(*trace->ready_command_lists));
    trace->ready_command_lists[trace->ready_command_lists_count++] = cookie.index;
    pthread_mutex_unlock(&trace->ready_lock);
}

void vkd3d_queue_timeline_trace_register_instantaneous(struct vkd3d_queue_timeline_trace *trace,
        enum vkd3d_queue_timeline_trace_state_type type, uint64_t value)
{
    struct vkd3d_queue_timeline_trace_state *state;
    unsigned int index;

    /* Most of the instantaneous events are very spammy and rarely show anything actionable. */
    if (type != VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_QUEUE_PRESENT &&
            !(vkd3d_config_flags & VKD3D_CONFIG_FLAG_QUEUE_PROFILE_EXTRA))
        return;

    if (!trace->active)
        return;

    index = vkd3d_queue_timeline_trace_allocate_index(trace, NULL);
    if (!index)
        return;

    state = &trace->state[index];
    state->type = type;
    state->start_ts = vkd3d_get_current_time_ns();
    state->tid = vkd3d_get_current_thread_id();
    state->record_cookie = value;

    /* Defer actual IO until fence workers are doing something. */
    pthread_mutex_lock(&trace->ready_lock);
    vkd3d_array_reserve((void**)&trace->ready_command_lists, &trace->ready_command_lists_size,
            trace->ready_command_lists_count + 1, sizeof(*trace->ready_command_lists));
    trace->ready_command_lists[trace->ready_command_lists_count++] = index;
    pthread_mutex_unlock(&trace->ready_lock);
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_present_wait(struct vkd3d_queue_timeline_trace *trace, uint64_t present_id)
{
    char str[128];
    snprintf(str, sizeof(str), "WAIT (id = %"PRIu64")", present_id);
    return vkd3d_queue_timeline_trace_register_generic_op(trace, VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_PRESENT_WAIT, str);
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_pso_compile(struct vkd3d_queue_timeline_trace *trace)
{
    /* Details are filled in later. */
    return vkd3d_queue_timeline_trace_register_generic_op(trace, VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_PSO_COMPILATION, "");
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_generic_region(struct vkd3d_queue_timeline_trace *trace, const char *tag)
{
    return vkd3d_queue_timeline_trace_register_generic_op(trace, VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_GENERIC_REGION, tag);
}


struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_present_block(struct vkd3d_queue_timeline_trace *trace, uint64_t present_id)
{
    char str[128];
    snprintf(str, sizeof(str), "PRESENT (id = %"PRIu64")", present_id);
    return vkd3d_queue_timeline_trace_register_generic_op(trace, VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_PRESENT_BLOCK, str);
}

struct vkd3d_queue_timeline_trace_cookie
vkd3d_queue_timeline_trace_register_low_latency_sleep(struct vkd3d_queue_timeline_trace *trace, uint64_t present_id)
{
    char str[128];
    snprintf(str, sizeof(str), "LATENCY SLEEP (id = %"PRIu64")", present_id);
    return vkd3d_queue_timeline_trace_register_generic_op(trace, VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_LOW_LATENCY_SLEEP, str);
}

static void vkd3d_queue_timeline_trace_flush_instantaneous(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_fence_worker *worker)
{
    const struct vkd3d_queue_timeline_trace_state *list_state;
    size_t list_count;
    size_t i;

    pthread_mutex_lock(&trace->ready_lock);
    if (trace->ready_command_lists_count)
    {
        /* Copy to local buffer to not stall recording threads while doing IO. */
        vkd3d_array_reserve((void**)&worker->timeline.list_buffer,
                &worker->timeline.list_buffer_size,
                trace->ready_command_lists_count, sizeof(*worker->timeline.list_buffer));
        memcpy(worker->timeline.list_buffer,
                trace->ready_command_lists, trace->ready_command_lists_count * sizeof(*worker->timeline.list_buffer));
        list_count = trace->ready_command_lists_count;
        trace->ready_command_lists_count = 0;
        pthread_mutex_unlock(&trace->ready_lock);

        for (i = 0; i < list_count; i++)
        {
            const char *generic_pid = NULL;
            double start_ts;
            double end_ts;

            list_state = &trace->state[worker->timeline.list_buffer[i]];
            start_ts = (double)(list_state->start_ts - trace->base_ts) * 1e-3;

            switch (list_state->type)
            {
                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_COMMAND_LIST:
                {
                    end_ts = (double)(list_state->record_end_ts - trace->base_ts) * 1e-3;
                    fprintf(trace->file,
                            "{ \"name\": \"%"PRIu64 " (delay %.3f us)\", \"ph\": \"i\", \"tid\": \"0x%04x\", \"pid\": \"cmd reset\", \"ts\": %f },\n",
                            list_state->record_cookie, end_ts - start_ts, list_state->tid, start_ts);
                    fprintf(trace->file,
                            "{ \"name\": \"%"PRIu64" (delay %.3f us)\", \"ph\": \"i\", \"tid\": \"0x%04x\", \"pid\": \"cmd close\", \"ts\": %f },\n",
                            list_state->record_cookie, end_ts - start_ts, list_state->tid, end_ts);
                    break;
                }

                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_QUEUE_PRESENT:
                    generic_pid = "vkQueuePresentKHR";
                    break;

                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_HEAP_ALLOCATION:
                    generic_pid = "heap allocate";
                    break;

                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_VK_ALLOCATE_MEMORY:
                    generic_pid = "vkAllocateMemory";
                    break;

                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_CLEAR_ALLOCATION:
                    generic_pid = "clear allocation";
                    break;

                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_COMMAND_ALLOCATOR_RESET:
                    generic_pid = "command allocator reset";
                    break;

                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_COMMITTED_RESOURCE_ALLOCATION:
                    generic_pid = "committed resource alloc";
                    break;

                case VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_CPU_SIGNAL:
                    fprintf(trace->file,
                            "{ \"name\": \"%s\", \"ph\": \"i\", \"tid\": \"0x%04x\", \"pid\": \"cpu signal\", \"ts\": %f },\n",
                            list_state->desc, list_state->tid, start_ts);
                    break;

                default:
                    break;
            }

            if (generic_pid)
            {
                fprintf(trace->file,
                        "{ \"name\": \"%"PRIu64"\", \"ph\": \"i\", \"tid\": \"0x%04x\", \"pid\": \"%s\", \"ts\": %f },\n",
                        list_state->record_cookie, list_state->tid, generic_pid, start_ts);
            }
        }

        vkd3d_queue_timeline_trace_free_indices(trace, worker->timeline.list_buffer, list_count);
    }
    else
        pthread_mutex_unlock(&trace->ready_lock);
}

void vkd3d_queue_timeline_trace_complete_execute(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_fence_worker *worker,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    double end_ts, start_submit_ts, start_ts, overhead_start_ts, overhead_end_ts;
    const struct vkd3d_queue_timeline_trace_state *state;
    unsigned int pid;
    const char *tid;
    double *ts_lock;

    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    start_ts = (double)(state->start_ts - trace->base_ts) * 1e-3;
    start_submit_ts = (double)(state->start_submit_ts - trace->base_ts) * 1e-3;
    end_ts = (double)(vkd3d_get_current_time_ns() - trace->base_ts) * 1e-3;
    overhead_start_ts = start_ts + 1e-3 * state->overhead_start_offset;
    overhead_end_ts = start_ts + 1e-3 * state->overhead_end_offset;

    if (worker)
    {
        if (state->type == VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_SUBMISSION)
            vkd3d_queue_timeline_trace_flush_instantaneous(trace, worker);

        tid = worker->timeline.tid;
        if (state->type == VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_GENERIC_REGION)
            tid = "regions";

        pid = worker->queue->submission_thread_tid;

        if (state->type == VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_SUBMISSION)
        {
            fprintf(trace->file, "{ \"name\": \"%s\", \"ph\": \"i\", \"tid\": \"cpu\", \"pid\": \"0x%04x\", \"ts\": %f, \"s\": \"t\" },\n",
                    state->desc, pid, start_ts);

            if (start_ts < worker->timeline.lock_end_cpu_ts)
                start_ts = worker->timeline.lock_end_cpu_ts;
            if (start_submit_ts < start_ts)
                start_submit_ts = start_ts;
        }

        if (state->type != VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_GENERIC_REGION)
        {
            ts_lock = &worker->timeline.lock_end_gpu_ts;

            if (start_submit_ts < *ts_lock)
                start_submit_ts = *ts_lock;
            if (end_ts < start_submit_ts)
                end_ts = start_submit_ts;
            *ts_lock = end_ts;
        }

        fprintf(trace->file, "{ \"name\": \"%s\", \"ph\": \"X\", \"tid\": \"%s\", \"pid\": \"0x%04x\", \"ts\": %f, \"dur\": %f },\n",
                state->desc, tid, pid, start_submit_ts, end_ts - start_submit_ts);

        if (state->type == VKD3D_QUEUE_TIMELINE_TRACE_STATE_TYPE_SUBMISSION)
        {
            worker->timeline.lock_end_cpu_ts = start_submit_ts;
            fprintf(trace->file,
                    "{ \"name\": \"%s\", \"ph\": \"X\", \"tid\": \"submit\", \"pid\": \"0x%04x\", \"ts\": %f, \"dur\": %f },\n",
                    state->desc, pid, start_ts, start_submit_ts - start_ts);
            fprintf(trace->file,
                    "{ \"name\": \"%s\", \"ph\": \"X\", \"tid\": \"overhead\", \"pid\": \"0x%04x\", \"ts\": %f, \"dur\": %f },\n",
                    state->desc, pid, overhead_start_ts, overhead_end_ts - overhead_start_ts);
        }
    }

    vkd3d_queue_timeline_trace_free_index(trace, cookie.index);
}

void vkd3d_queue_timeline_trace_begin_execute(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    struct vkd3d_queue_timeline_trace_state *state;
    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    state->start_submit_ts = vkd3d_get_current_time_ns();
}

void vkd3d_queue_timeline_trace_begin_execute_overhead(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    struct vkd3d_queue_timeline_trace_state *state;
    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    state->overhead_start_offset = vkd3d_get_current_time_ns() - state->start_ts;
}

void vkd3d_queue_timeline_trace_end_execute_overhead(struct vkd3d_queue_timeline_trace *trace,
        struct vkd3d_queue_timeline_trace_cookie cookie)
{
    struct vkd3d_queue_timeline_trace_state *state;
    if (!trace->active || cookie.index == 0)
        return;

    state = &trace->state[cookie.index];
    state->overhead_end_offset = vkd3d_get_current_time_ns() - state->start_ts;
}
