/*
 * Copyright 2024 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_debug.h"
#include "vkd3d_common.h"
#include "vkd3d_shader.h"
#include <assert.h>
#include <stdio.h>

void vkd3d_shader_hash_range_parse(FILE *file, struct vkd3d_shader_hash_range **ranges,
        size_t *range_size, size_t *range_count, enum vkd3d_shader_hash_range_kind kind)
{
    vkd3d_shader_hash_t lo_hash;
    vkd3d_shader_hash_t hi_hash;
    size_t new_count = 0;
    char *end_ptr;
    char line[64];

    while (fgets(line, sizeof(line), file))
    {
        if (!vkd3d_shader_hash_range_parse_line(line, &lo_hash, &hi_hash, &end_ptr))
            continue;

        if (lo_hash || hi_hash)
        {
            vkd3d_array_reserve((void **)ranges, range_size,
                    new_count + 1, sizeof(struct vkd3d_shader_hash_range));

            (*ranges)[new_count].lo = lo_hash;
            (*ranges)[new_count].hi = hi_hash;

            if (kind == VKD3D_SHADER_HASH_RANGE_KIND_BARRIERS)
            {
                if (*end_ptr == '\0')
                {
                    (*ranges)[new_count].flags = VKD3D_SHADER_META_FLAG_FORCE_COMPUTE_BARRIER_AFTER_DISPATCH;
                    end_ptr = "post-compute (default)";
                }
                else if (strcmp(end_ptr, "pre-compute") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_META_FLAG_FORCE_COMPUTE_BARRIER_BEFORE_DISPATCH;
                else if (strcmp(end_ptr, "post-compute") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_META_FLAG_FORCE_COMPUTE_BARRIER_AFTER_DISPATCH;
                else if (strcmp(end_ptr, "pre-raster") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_META_FLAG_FORCE_PRE_RASTERIZATION_BEFORE_DISPATCH;
                else if (strcmp(end_ptr, "graphics") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_META_FLAG_FORCE_GRAPHICS_BEFORE_DISPATCH;
                else
                    end_ptr = "N/A";

                INFO("Inserting %s barrier for %016"PRIx64" - %016"PRIx64".\n",
                        end_ptr,
                        (*ranges)[new_count].lo,
                        (*ranges)[new_count].hi);
            }
            else if (kind == VKD3D_SHADER_HASH_RANGE_KIND_QA)
            {
                if (*end_ptr == '\0')
                {
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_ALLOW;
                    end_ptr = "allow (default)";
                }
                else if (strcmp(end_ptr, "allow") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_ALLOW;
                else if (strcmp(end_ptr, "disallow") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_DISALLOW;
                else if (strcmp(end_ptr, "full") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_FULL_QA;
                else if (strcmp(end_ptr, "flush-nan") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_FLUSH_NAN;
                else if (strcmp(end_ptr, "expect-assume") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_EXPECT_ASSUME;
                else if (strcmp(end_ptr, "sync") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_SYNC;
                else if (strcmp(end_ptr, "sync-compute") == 0)
                    (*ranges)[new_count].flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_SYNC_COMPUTE;
                else
                    end_ptr = "N/A";

                INFO("Inserting %s QA check for %016"PRIx64" - %016"PRIx64".\n",
                        end_ptr,
                        (*ranges)[new_count].lo,
                        (*ranges)[new_count].hi);
            }

            new_count++;
        }
    }

    *range_count = new_count;
}

VkMemoryPropertyFlags vkd3d_debug_buffer_memory_properties(struct d3d12_device *device,
        VkMemoryPropertyFlags flags, bool high_throughput)
{
    /* For high-throughput scenarios like sync-val, we absolutely cannot demote to host visible,
     * or we risk 0.00001 fps. */
    if ((vkd3d_config_flags & VKD3D_CONFIG_FLAG_BREADCRUMBS) && !high_throughput)
    {
        /* Expect crashes since we won't have time to flush caches.
         * We use coherent in the debug_channel.h header, but not necessarily guaranteed to be coherent with
         * host reads, so make extra sure. */
        if (device->device_info.device_coherent_memory_features_amd.deviceCoherentMemory)
        {
            flags |= VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD | VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD;
            INFO("Enabling uncached device memory for debug buffer.\n");
        }
        else if (device->device_info.vulkan_1_2_properties.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY)
        {
            /* Writes to sysmem seem to be coherent, but not ReBAR. Very slow, but hey,
             * we're desperate when we're doing breadcrumb + debug ring! */
            flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
                    VK_MEMORY_PROPERTY_HOST_CACHED_BIT |
                    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
            INFO("Forcing (implicitly-coherent) sysmem for debug buffer.\n");
        }
    }

    return flags;
}
