blob: 78d433dbc3db73fa5c87294b4f82818b6009b3b7 [file] [edit]
/*
* 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_descriptor_debug.h"
#include "vkd3d_threads.h"
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
static pthread_once_t debug_once = PTHREAD_ONCE_INIT;
static pthread_mutex_t debug_lock = PTHREAD_MUTEX_INITIALIZER;
static bool descriptor_debug_active_descriptor_checks;
static bool descriptor_debug_active_instruction_checks;
static bool descriptor_debug_active_log;
static FILE *descriptor_debug_file;
struct vkd3d_descriptor_qa_global_info
{
struct vkd3d_descriptor_qa_global_buffer_data *payload_data;
VkDescriptorBufferInfo payload_descriptor;
VkBuffer vk_payload_buffer;
struct vkd3d_device_memory_allocation payload_device_allocation;
uint32_t *control_data;
VkBuffer vk_control_buffer;
struct vkd3d_device_memory_allocation control_device_allocation;
VkDescriptorBufferInfo control_descriptor;
unsigned int num_cookies;
pthread_t ring_thread;
pthread_mutex_t ring_lock;
pthread_cond_t ring_cond;
bool active;
struct vkd3d_shader_hash_range *qa_ranges;
size_t qa_range_size;
size_t qa_range_count;
uint32_t sync_val_cookie_atomic;
bool needs_sync_validation;
};
static const char *debug_descriptor_type(vkd3d_descriptor_qa_flags type_flags)
{
bool has_raw_va = !!(type_flags & VKD3D_DESCRIPTOR_QA_TYPE_RAW_VA_BIT);
switch (type_flags & ~VKD3D_DESCRIPTOR_QA_TYPE_RAW_VA_BIT)
{
case VKD3D_DESCRIPTOR_QA_TYPE_SAMPLER_BIT: return "SAMPLER";
case VKD3D_DESCRIPTOR_QA_TYPE_SAMPLED_IMAGE_BIT: return "SAMPLED_IMAGE";
case VKD3D_DESCRIPTOR_QA_TYPE_STORAGE_IMAGE_BIT: return "STORAGE_IMAGE";
case VKD3D_DESCRIPTOR_QA_TYPE_UNIFORM_BUFFER_BIT: return "UNIFORM_BUFFER";
case VKD3D_DESCRIPTOR_QA_TYPE_STORAGE_BUFFER_BIT: return "STORAGE_BUFFER";
case VKD3D_DESCRIPTOR_QA_TYPE_UNIFORM_TEXEL_BUFFER_BIT: return "UNIFORM_TEXEL_BUFFER";
case VKD3D_DESCRIPTOR_QA_TYPE_STORAGE_TEXEL_BUFFER_BIT: return "STORAGE_TEXEL_BUFFER";
case VKD3D_DESCRIPTOR_QA_TYPE_STORAGE_TEXEL_BUFFER_BIT | VKD3D_DESCRIPTOR_QA_TYPE_STORAGE_BUFFER_BIT:
return has_raw_va ? "STORAGE_TEXEL_BUFFER / STORAGE_BUFFER (w/ counter)" : "STORAGE_TEXEL_BUFFER / STORAGE_BUFFER";
case VKD3D_DESCRIPTOR_QA_TYPE_UNIFORM_TEXEL_BUFFER_BIT | VKD3D_DESCRIPTOR_QA_TYPE_STORAGE_BUFFER_BIT:
return has_raw_va ? "UNIFORM_TEXEL_BUFFER / STORAGE_BUFFER (w/ counter)" : "UNIFORM_TEXEL_BUFFER / STORAGE_BUFFER";
case VKD3D_DESCRIPTOR_QA_TYPE_RT_ACCELERATION_STRUCTURE_BIT:
return "RTAS";
case 0:
return "NONE";
default: return "?";
}
}
static void vkd3d_descriptor_debug_init_once(void)
{
char env[VKD3D_PATH_MAX];
vkd3d_get_env_var("VKD3D_DESCRIPTOR_QA_LOG", env, sizeof(env));
if (strlen(env) > 0)
{
INFO("Enabling VKD3D_DESCRIPTOR_QA_LOG\n");
descriptor_debug_file = fopen(env, "w");
if (!descriptor_debug_file)
ERR("Failed to open file: %s.\n", env);
else
descriptor_debug_active_log = true;
}
if (vkd3d_config_flags & VKD3D_CONFIG_FLAG_DESCRIPTOR_QA_CHECKS)
{
INFO("Enabling descriptor QA checks!\n");
descriptor_debug_active_descriptor_checks = true;
}
else if (vkd3d_config_flags & VKD3D_CONFIG_FLAG_INSTRUCTION_QA_CHECKS)
{
INFO("Enabling instruction-level QA checks!\n");
descriptor_debug_active_instruction_checks = true;
}
}
void vkd3d_descriptor_debug_init(void)
{
pthread_once(&debug_once, vkd3d_descriptor_debug_init_once);
}
bool vkd3d_descriptor_debug_active_log(void)
{
return descriptor_debug_active_log;
}
bool vkd3d_descriptor_debug_active_instruction_qa_checks(void)
{
return descriptor_debug_active_instruction_checks;
}
bool vkd3d_descriptor_debug_active_descriptor_qa_checks(void)
{
return descriptor_debug_active_descriptor_checks;
}
VkDeviceSize vkd3d_descriptor_debug_heap_info_size(unsigned int num_descriptors)
{
return offsetof(struct vkd3d_descriptor_qa_heap_buffer_data, desc) + num_descriptors *
sizeof(struct vkd3d_descriptor_qa_cookie_descriptor);
}
static void vkd3d_descriptor_debug_set_live_status_bit(
struct vkd3d_descriptor_qa_global_info *global_info, struct vkd3d_cookie cookie)
{
if (!global_info || !descriptor_debug_active_descriptor_checks ||
!global_info->active || !global_info->payload_data)
return;
if (cookie.index < global_info->num_cookies)
{
vkd3d_atomic_uint32_or(&global_info->payload_data->live_status_table[cookie.index / 32],
1u << (cookie.index & 31), vkd3d_memory_order_relaxed);
}
else
INFO("Cookie index %u is out of range, cannot be tracked.\n", cookie.index);
}
static void vkd3d_descriptor_debug_unset_live_status_bit(
struct vkd3d_descriptor_qa_global_info *global_info, struct vkd3d_cookie cookie)
{
if (!global_info || !descriptor_debug_active_descriptor_checks ||
!global_info->active || !global_info->payload_data)
return;
if (cookie.index < global_info->num_cookies)
{
vkd3d_atomic_uint32_and(&global_info->payload_data->live_status_table[cookie.index / 32],
~(1u << (cookie.index & 31)), vkd3d_memory_order_relaxed);
}
}
void vkd3d_descriptor_debug_sync_validation_barrier(
struct vkd3d_descriptor_qa_global_info *global_info,
struct d3d12_device *device, VkCommandBuffer vk_cmd_buffer)
{
const struct vkd3d_vk_device_procs *vk_procs;
VkMemoryBarrier2 vk_barrier;
VkDependencyInfo dep;
if (!global_info || !global_info->needs_sync_validation)
return;
vk_procs = &device->vk_procs;
memset(&dep, 0, sizeof(dep));
memset(&vk_barrier, 0, sizeof(vk_barrier));
dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dep.memoryBarrierCount = 1;
dep.pMemoryBarriers = &vk_barrier;
/* Fully serialize dispatches. This makes it easier to catch sync bugs
* since we're guaranteed that a racing read/write in subsequent dispatch will
* observe hazards. */
vk_barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
vk_barrier.srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
vk_barrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
vk_barrier.dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
vk_barrier.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT;
VK_CALL(vkCmdPipelineBarrier2(vk_cmd_buffer, &dep));
}
uint32_t vkd3d_descriptor_debug_update_va_timestamp(
struct vkd3d_descriptor_qa_global_info *global_info,
struct d3d12_device *device, VkCommandBuffer vk_cmd_buffer)
{
const struct vkd3d_vk_device_procs *vk_procs;
uint32_t ts;
if (!global_info)
return 0;
/* It's possible this is called out of order, but it should fine in practice.
* We cannot get false positives this way if application allocates resource before calling ExecuteCommandLists. */
ts = vkd3d_allocate_cookie_va_timestamp() - 1;
vk_procs = &device->vk_procs;
/* Technically needs cross-queue sync too, but there's practically no reason this will ever cause sliced
* writes, so just YOLO it. */
vkd3d_descriptor_debug_sync_validation_barrier(global_info, device, vk_cmd_buffer);
VK_CALL(vkCmdFillBuffer(vk_cmd_buffer,
global_info->vk_payload_buffer,
offsetof(struct vkd3d_descriptor_qa_global_buffer_data, va_map_timestamp),
sizeof(uint32_t), ts));
vkd3d_descriptor_debug_sync_validation_barrier(global_info, device, vk_cmd_buffer);
return ts;
}
uint32_t vkd3d_descriptor_debug_clear_bloom_filter(
struct vkd3d_descriptor_qa_global_info *global_info,
struct d3d12_device *device, VkCommandBuffer vk_cmd_buffer)
{
const struct vkd3d_vk_device_procs *vk_procs;
uint32_t control_size;
uint32_t sync_cookie;
if (!global_info || !global_info->needs_sync_validation)
return 0;
vk_procs = &device->vk_procs;
/* Fully barrier everything. */
vkd3d_descriptor_debug_sync_validation_barrier(global_info, device, vk_cmd_buffer);
control_size = (global_info->num_cookies / 16) * sizeof(uint32_t);
/* This can get rather expensive, but whatever it takes to catch impossible sync bugs ...
* Don't clear the last two values of the control buffer which hold misc cookie data that we don't need to clear. */
VK_CALL(vkCmdFillBuffer(vk_cmd_buffer,
global_info->control_descriptor.buffer, control_size,
global_info->control_descriptor.range - control_size - 2 * sizeof(uint32_t), 0));
sync_cookie = vkd3d_atomic_uint32_increment(&global_info->sync_val_cookie_atomic, vkd3d_memory_order_relaxed);
/* Update the dispatch counter. */
VK_CALL(vkCmdFillBuffer(vk_cmd_buffer,
global_info->control_descriptor.buffer, global_info->control_descriptor.range - 2 * sizeof(uint32_t),
sizeof(uint32_t), sync_cookie));
vkd3d_descriptor_debug_sync_validation_barrier(global_info, device, vk_cmd_buffer);
return sync_cookie;
}
static void *vkd3d_descriptor_debug_qa_check_instruction(void *userdata)
{
struct vkd3d_descriptor_qa_global_info *global_info = userdata;
const struct vkd3d_instruction_qa_payload_data *payload_data;
uint32_t control_words = global_info->num_cookies / 16;
bool active = true;
uint32_t i;
payload_data = (const struct vkd3d_instruction_qa_payload_data *)global_info->payload_data;
while (active)
{
/* Don't spin endlessly, this thread is kicked after a successful fence wait. */
pthread_mutex_lock(&global_info->ring_lock);
if (global_info->active)
pthread_cond_wait(&global_info->ring_cond, &global_info->ring_lock);
active = global_info->active;
pthread_mutex_unlock(&global_info->ring_lock);
for (i = 0; i < control_words; i++)
{
uint32_t word, payload_index;
word = vkd3d_atomic_uint32_load_explicit(&global_info->control_data[i], vkd3d_memory_order_acquire);
/* The upper 16 bits mark that there is valid data. */
word &= 0xffff0000u;
if (word)
{
while (word)
{
payload_index = i * 16 + (vkd3d_bitmask_iter32(&word) - 16);
ERR("QA: non-normal value || shader %016"PRIx64", inst %u, value #%x / %u.\n",
payload_data[payload_index].hash,
payload_data[payload_index].instruction,
payload_data[payload_index].value,
payload_data[payload_index].value);
}
vkd3d_atomic_uint32_store_explicit(&global_info->control_data[i], 0, vkd3d_memory_order_release);
}
}
}
return NULL;
}
static void vkd3d_descriptor_debug_qa_check_report_fault(
struct vkd3d_descriptor_qa_global_info *global_info);
static void *vkd3d_descriptor_debug_qa_check_entry(void *userdata)
{
struct vkd3d_descriptor_qa_global_info *global_info = userdata;
bool active = true;
while (active)
{
/* Don't spin endlessly, this thread is kicked after a successful fence wait. */
pthread_mutex_lock(&global_info->ring_lock);
if (global_info->active)
pthread_cond_wait(&global_info->ring_cond, &global_info->ring_lock);
active = global_info->active;
pthread_mutex_unlock(&global_info->ring_lock);
if (global_info->payload_data->fault_type != 0)
{
vkd3d_descriptor_debug_qa_check_report_fault(global_info);
ERR("Num failed checks: %u\n", global_info->payload_data->fault_atomic);
/* Reset the latch so we can get more reports. */
vkd3d_atomic_uint32_store_explicit(&global_info->payload_data->fault_type, 0, vkd3d_memory_order_relaxed);
vkd3d_atomic_uint32_store_explicit(&global_info->payload_data->fault_atomic, 0, vkd3d_memory_order_release);
}
}
return NULL;
}
void vkd3d_descriptor_debug_kick_qa_check(struct vkd3d_descriptor_qa_global_info *global_info)
{
if (global_info && global_info->active)
pthread_cond_signal(&global_info->ring_cond);
}
const VkDescriptorBufferInfo *vkd3d_descriptor_debug_get_payload_info_descriptor(
struct vkd3d_descriptor_qa_global_info *global_info)
{
if (global_info)
return &global_info->payload_descriptor;
else
return NULL;
}
const VkDescriptorBufferInfo *vkd3d_descriptor_debug_get_control_info_descriptor(
struct vkd3d_descriptor_qa_global_info *global_info)
{
if (global_info)
return &global_info->control_descriptor;
else
return NULL;
}
static void vkd3d_descriptor_debug_parse_shader_ranges(
struct vkd3d_descriptor_qa_global_info *global_info,
enum vkd3d_shader_hash_range_kind kind)
{
char env[VKD3D_PATH_MAX];
FILE *file;
if (vkd3d_get_env_var("VKD3D_QA_HASHES", env, sizeof(env)))
{
file = fopen(env, "r");
if (file)
{
vkd3d_shader_hash_range_parse(file, &global_info->qa_ranges, &global_info->qa_range_size,
&global_info->qa_range_count, kind);
fclose(file);
}
}
}
static HRESULT vkd3d_descriptor_debug_alloc_global_info_instructions(
struct vkd3d_descriptor_qa_global_info **out_global_info,
struct d3d12_device *device)
{
const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
struct vkd3d_descriptor_qa_global_info *global_info;
VkMemoryPropertyFlags memory_properties;
const uint32_t num_payloads = 4096;
D3D12_RESOURCE_DESC1 buffer_desc;
D3D12_HEAP_PROPERTIES heap_info;
uint32_t bloom_buffer_size = 0;
D3D12_HEAP_FLAGS heap_flags;
bool needs_sync_val;
unsigned int i;
VkResult vr;
HRESULT hr;
global_info = vkd3d_calloc(1, sizeof(*global_info));
if (!global_info)
return E_OUTOFMEMORY;
memset(&buffer_desc, 0, sizeof(buffer_desc));
buffer_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
buffer_desc.Width = sizeof(struct vkd3d_instruction_qa_payload_data) * num_payloads;
buffer_desc.Height = 1;
buffer_desc.DepthOrArraySize = 1;
buffer_desc.MipLevels = 1;
buffer_desc.SampleDesc.Count = 1;
buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
buffer_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
/* host-visible device memory */
memset(&heap_info, 0, sizeof(heap_info));
heap_info.Type = D3D12_HEAP_TYPE_UPLOAD;
heap_flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
/* Create the payload buffer. */
if (FAILED(hr = vkd3d_create_buffer(device, &heap_info, heap_flags, &buffer_desc,
"qa-payload", &global_info->vk_payload_buffer)))
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hr;
}
vkd3d_descriptor_debug_parse_shader_ranges(global_info, VKD3D_SHADER_HASH_RANGE_KIND_QA);
/* If we need sync validation, we need to add an augmented buffer that lives after the fault feedback atomics.
* The shader is responsible for figuring this out based on a POT + POT addressing scheme.
* E.g. if the feedback atomics have size 0x1000 and the bloom buffer has size 0x1000000,
* we expect a buffer size of 0x1001000. Using findLSB and findMSB we can partition as appropriate.
* We also add one 64-bit entry to serve as the entry for getting a "unique" ID per invocation,
* so the effective size is 0x1001001 in this case. */
needs_sync_val = false;
for (i = 0; i < global_info->qa_range_count && !needs_sync_val; i++)
{
if (global_info->qa_ranges[i].flags &
(VKD3D_SHADER_HASH_RANGE_QA_FLAG_SYNC | VKD3D_SHADER_HASH_RANGE_QA_FLAG_SYNC_COMPUTE))
{
bloom_buffer_size = 1u << vkd3d_env_var_as_uint("VKD3D_BLOOM_BUFFER_SIZE_LOG2", 24);
INFO("Using bloom buffer size of %u bytes.\n", bloom_buffer_size);
needs_sync_val = true;
}
}
memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
#ifdef VKD3D_ENABLE_BREADCRUMBS
memory_properties = vkd3d_debug_buffer_memory_properties(device, memory_properties, needs_sync_val);
#endif
if (FAILED(hr = vkd3d_allocate_internal_buffer_memory(device, global_info->vk_payload_buffer,
memory_properties, &global_info->payload_device_allocation)))
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hr;
}
if ((vr = VK_CALL(vkMapMemory(device->vk_device, global_info->payload_device_allocation.vk_memory,
0, VK_WHOLE_SIZE, 0, (void**)&global_info->payload_data))))
{
ERR("Failed to map buffer, vr %d.\n", vr);
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hresult_from_vk_result(vr);
}
memset(global_info->payload_data, 0, buffer_desc.Width);
global_info->payload_descriptor.buffer = global_info->vk_payload_buffer;
global_info->payload_descriptor.offset = 0;
global_info->payload_descriptor.range = buffer_desc.Width;
/* Create the atomic buffer. Keep it device local to keep GPU performance high.
* We'll eat the insanely slow readback cost on CPU. */
heap_info.Type = D3D12_HEAP_TYPE_DEFAULT;
buffer_desc.Width = (num_payloads / 16) * sizeof(uint32_t);
if (needs_sync_val)
{
if (vkd3d_config_flags & VKD3D_CONFIG_FLAG_SINGLE_QUEUE)
{
buffer_desc.Width += bloom_buffer_size;
global_info->needs_sync_validation = true;
}
else
{
ERR("Sync validation is enabled, but SINGLE_QUEUE is not. Cannot use sync validation properly!\n");
}
}
/* The last two u32s of the control buffer are reserved for:
* - A unique dispatch counter used to correlate with breadcrumbs
* - An atomic counter for InvocationID locking in sync-val. */
buffer_desc.Width += 2 * sizeof(uint32_t);
if (FAILED(hr = vkd3d_create_buffer(device, &heap_info, heap_flags, &buffer_desc,
"qa-control", &global_info->vk_control_buffer)))
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hr;
}
memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
#ifdef VKD3D_ENABLE_BREADCRUMBS
memory_properties = vkd3d_debug_buffer_memory_properties(device, memory_properties, needs_sync_val);
#endif
if (FAILED(hr = vkd3d_allocate_internal_buffer_memory(device, global_info->vk_control_buffer,
memory_properties, &global_info->control_device_allocation)))
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hr;
}
if ((vr = VK_CALL(vkMapMemory(device->vk_device, global_info->control_device_allocation.vk_memory,
0, VK_WHOLE_SIZE, 0, (void**)&global_info->control_data))))
{
ERR("Failed to map buffer, vr %d.\n", vr);
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hresult_from_vk_result(vr);
}
memset(global_info->control_data, 0, buffer_desc.Width);
global_info->control_descriptor.buffer = global_info->vk_control_buffer;
global_info->control_descriptor.offset = 0;
global_info->control_descriptor.range = buffer_desc.Width;
global_info->num_cookies = num_payloads;
pthread_mutex_init(&global_info->ring_lock, NULL);
pthread_cond_init(&global_info->ring_cond, NULL);
global_info->active = true;
if (pthread_create(&global_info->ring_thread, NULL, vkd3d_descriptor_debug_qa_check_instruction, global_info) != 0)
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return E_OUTOFMEMORY;
}
*out_global_info = global_info;
return S_OK;
}
uint32_t vkd3d_descriptor_debug_get_shader_interface_flags(
struct vkd3d_descriptor_qa_global_info *global_info,
const void *code, size_t size)
{
struct vkd3d_shader_code dxbc;
vkd3d_shader_hash_t hash;
uint32_t flags = 0;
size_t i;
if (!global_info || !global_info->active)
return 0;
dxbc.code = code;
dxbc.size = size;
hash = vkd3d_shader_hash(&dxbc);
if (global_info->qa_range_count)
{
for (i = 0; i < global_info->qa_range_count; i++)
if (global_info->qa_ranges[i].lo <= hash && hash <= global_info->qa_ranges[i].hi)
flags |= global_info->qa_ranges[i].flags;
}
else
{
flags = VKD3D_SHADER_HASH_RANGE_QA_FLAG_ALLOW;
}
if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_FULL_QA)
flags |= VKD3D_SHADER_HASH_RANGE_QA_FLAG_ALLOW;
if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_DISALLOW)
flags = 0;
if (descriptor_debug_active_descriptor_checks)
{
if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_ALLOW)
flags = VKD3D_SHADER_INTERFACE_DESCRIPTOR_QA_BUFFER;
}
else if (descriptor_debug_active_instruction_checks)
{
if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_FULL_QA)
{
flags = VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER_FULL |
VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER;
}
else if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_FLUSH_NAN)
{
flags = VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER_FLUSH_NAN |
VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER;
}
else if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_EXPECT_ASSUME)
{
flags = VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER_EXPECT_ASSUME |
VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER;
}
else if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_SYNC)
{
flags = VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER_SYNC |
VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER;
}
else if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_SYNC_COMPUTE)
{
flags = VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER_SYNC_COMPUTE |
VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER;
}
else if (flags & VKD3D_SHADER_HASH_RANGE_QA_FLAG_ALLOW)
flags = VKD3D_SHADER_INTERFACE_INSTRUCTION_QA_BUFFER;
}
else
{
flags = 0;
}
return flags;
}
static HRESULT vkd3d_descriptor_debug_alloc_global_info_descriptors(
struct vkd3d_descriptor_qa_global_info **out_global_info, unsigned int num_cookies,
struct d3d12_device *device)
{
const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
struct vkd3d_descriptor_qa_global_info *global_info;
VkMemoryPropertyFlags memory_properties;
D3D12_RESOURCE_DESC1 buffer_desc;
D3D12_HEAP_PROPERTIES heap_info;
D3D12_HEAP_FLAGS heap_flags;
VkResult vr;
HRESULT hr;
global_info = vkd3d_calloc(1, sizeof(*global_info));
if (!global_info)
return E_OUTOFMEMORY;
memset(&buffer_desc, 0, sizeof(buffer_desc));
buffer_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
buffer_desc.Width = sizeof(uint32_t) * ((num_cookies + 31) / 32) +
offsetof(struct vkd3d_descriptor_qa_global_buffer_data, live_status_table);
buffer_desc.Height = 1;
buffer_desc.DepthOrArraySize = 1;
buffer_desc.MipLevels = 1;
buffer_desc.SampleDesc.Count = 1;
buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
buffer_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
/* host-visible device memory */
memset(&heap_info, 0, sizeof(heap_info));
heap_info.Type = D3D12_HEAP_TYPE_CUSTOM;
heap_info.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE;
heap_flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
if (FAILED(hr = vkd3d_create_buffer(device, &heap_info, heap_flags, &buffer_desc,
"qa-buffer", &global_info->vk_payload_buffer)))
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hr;
}
memory_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
#ifdef VKD3D_ENABLE_BREADCRUMBS
memory_properties = vkd3d_debug_buffer_memory_properties(device, memory_properties, false);
#endif
if (FAILED(hr = vkd3d_allocate_internal_buffer_memory(device, global_info->vk_payload_buffer,
memory_properties,
&global_info->payload_device_allocation)))
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hr;
}
if ((vr = VK_CALL(vkMapMemory(device->vk_device, global_info->payload_device_allocation.vk_memory,
0, VK_WHOLE_SIZE, 0, (void**)&global_info->payload_data))))
{
ERR("Failed to map buffer, vr %d.\n", vr);
vkd3d_descriptor_debug_free_global_info(global_info, device);
return hresult_from_vk_result(vr);
}
memset(global_info->payload_data, 0, buffer_desc.Width);
/* The NULL descriptor has cookie 0, and is always considered live. */
global_info->payload_data->live_status_table[0] = 1u << 0;
global_info->payload_descriptor.buffer = global_info->vk_payload_buffer;
global_info->payload_descriptor.offset = 0;
global_info->payload_descriptor.range = buffer_desc.Width;
global_info->num_cookies = num_cookies;
pthread_mutex_init(&global_info->ring_lock, NULL);
pthread_cond_init(&global_info->ring_cond, NULL);
global_info->active = true;
if (pthread_create(&global_info->ring_thread, NULL, vkd3d_descriptor_debug_qa_check_entry, global_info) != 0)
{
vkd3d_descriptor_debug_free_global_info(global_info, device);
return E_OUTOFMEMORY;
}
vkd3d_descriptor_debug_parse_shader_ranges(global_info, VKD3D_SHADER_HASH_RANGE_KIND_QA);
*out_global_info = global_info;
return S_OK;
}
HRESULT vkd3d_descriptor_debug_alloc_global_info(
struct vkd3d_descriptor_qa_global_info **out_global_info, unsigned int num_cookies,
struct d3d12_device *device)
{
if (descriptor_debug_active_descriptor_checks)
return vkd3d_descriptor_debug_alloc_global_info_descriptors(out_global_info, num_cookies, device);
else if (descriptor_debug_active_instruction_checks)
return vkd3d_descriptor_debug_alloc_global_info_instructions(out_global_info, device);
else
return E_INVALIDARG;
}
void vkd3d_descriptor_debug_free_global_info(
struct vkd3d_descriptor_qa_global_info *global_info,
struct d3d12_device *device)
{
const struct vkd3d_vk_device_procs *vk_procs = &device->vk_procs;
if (!global_info)
return;
if (global_info->active)
{
pthread_mutex_lock(&global_info->ring_lock);
global_info->active = false;
pthread_cond_signal(&global_info->ring_cond);
pthread_mutex_unlock(&global_info->ring_lock);
pthread_join(global_info->ring_thread, NULL);
pthread_mutex_destroy(&global_info->ring_lock);
pthread_cond_destroy(&global_info->ring_cond);
}
vkd3d_free_device_memory(device, &global_info->control_device_allocation);
VK_CALL(vkDestroyBuffer(device->vk_device, global_info->vk_control_buffer, NULL));
vkd3d_free_device_memory(device, &global_info->payload_device_allocation);
VK_CALL(vkDestroyBuffer(device->vk_device, global_info->vk_payload_buffer, NULL));
vkd3d_free(global_info->qa_ranges);
vkd3d_free(global_info);
}
#define DECL_BUFFER() \
char buffer[4096]; \
char *ptr; \
ptr = buffer; \
*ptr = '\0'
#define FLUSH_BUFFER() do { \
pthread_mutex_lock(&debug_lock); \
fprintf(descriptor_debug_file, "%s\n", buffer); \
pthread_mutex_unlock(&debug_lock); \
fflush(descriptor_debug_file); \
} while (0)
#define APPEND_SNPRINTF(...) do { ptr += strlen(ptr); snprintf(ptr, (buffer + ARRAY_SIZE(buffer)) - ptr, __VA_ARGS__); } while(0)
static void vkd3d_descriptor_debug_qa_check_report_fault(
struct vkd3d_descriptor_qa_global_info *global_info)
{
DECL_BUFFER();
if (global_info->payload_data->fault_type & VKD3D_DESCRIPTOR_FAULT_TYPE_HEAP_OF_OF_RANGE)
APPEND_SNPRINTF("Fault type: HEAP_OUT_OF_RANGE\n");
if (global_info->payload_data->fault_type & VKD3D_DESCRIPTOR_FAULT_TYPE_MISMATCH_DESCRIPTOR_TYPE)
APPEND_SNPRINTF("Fault type: MISMATCH_DESCRIPTOR_TYPE\n");
if (global_info->payload_data->fault_type & VKD3D_DESCRIPTOR_FAULT_TYPE_DESTROYED_RESOURCE)
APPEND_SNPRINTF("Fault type: DESTROYED_RESOURCE\n");
if (global_info->payload_data->fault_type & VKD3D_DESCRIPTOR_FAULT_TYPE_VA_TIMESTAMP_INVALID)
APPEND_SNPRINTF("Fault type: INVALID_TIMESTAMP\n");
APPEND_SNPRINTF("CBV_SRV_UAV heap cookie: %u\n", global_info->payload_data->failed_heap);
APPEND_SNPRINTF("Shader hash and instruction: %016"PRIx64" (%u)\n",
global_info->payload_data->failed_hash, global_info->payload_data->failed_instruction);
APPEND_SNPRINTF("Accessed resource/view cookie: %u\n", global_info->payload_data->failed_cookie);
APPEND_SNPRINTF("Shader desired descriptor type: %u (%s)\n",
global_info->payload_data->failed_descriptor_type_mask,
debug_descriptor_type(global_info->payload_data->failed_descriptor_type_mask));
APPEND_SNPRINTF("Found descriptor type in heap: %u (%s)\n",
global_info->payload_data->actual_descriptor_type_mask,
debug_descriptor_type(global_info->payload_data->actual_descriptor_type_mask));
APPEND_SNPRINTF("Failed heap index: %u\n", global_info->payload_data->failed_offset);
ERR("\n============\n%s==========\n", buffer);
if (!vkd3d_descriptor_debug_active_log())
return;
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_register_heap(
struct vkd3d_descriptor_qa_heap_buffer_data *heap, struct vkd3d_cookie cookie,
const D3D12_DESCRIPTOR_HEAP_DESC *desc)
{
unsigned int i;
DECL_BUFFER();
if (heap)
{
heap->num_descriptors = desc->NumDescriptors;
heap->heap_index = cookie.index;
for (i = 0; i < desc->NumDescriptors; i++)
{
heap->desc[i].cookie = 0;
heap->desc[i].descriptor_type = ~0u;
}
}
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("REGISTER HEAP %u || COUNT = %u", cookie.index, desc->NumDescriptors);
if (desc->Flags & D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)
APPEND_SNPRINTF(" || SHADER");
switch (desc->Type)
{
case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV:
APPEND_SNPRINTF(" || CBV_SRV_UAV");
break;
case D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER:
APPEND_SNPRINTF(" || SAMPLER");
break;
case D3D12_DESCRIPTOR_HEAP_TYPE_RTV:
APPEND_SNPRINTF(" || RTV");
break;
case D3D12_DESCRIPTOR_HEAP_TYPE_DSV:
APPEND_SNPRINTF(" || DSV");
break;
default:
APPEND_SNPRINTF(" || ?");
break;
}
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_unregister_heap(struct vkd3d_cookie cookie)
{
DECL_BUFFER();
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("DESTROY HEAP %u", cookie.index);
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_register_query_heap_cookie(struct vkd3d_descriptor_qa_global_info *global_info,
struct vkd3d_cookie cookie, const D3D12_QUERY_HEAP_DESC *desc)
{
const char *type_desc;
DECL_BUFFER();
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("QUERY HEAP CREATE %u || ", cookie.index);
switch (desc->Type)
{
case D3D12_QUERY_HEAP_TYPE_COPY_QUEUE_TIMESTAMP:
type_desc = "COPY_QUEUE_TIMESTAMP";
break;
case D3D12_QUERY_HEAP_TYPE_OCCLUSION:
type_desc = "OCCLUSION";
break;
case D3D12_QUERY_HEAP_TYPE_TIMESTAMP:
type_desc = "TIMESTAMP";
break;
case D3D12_QUERY_HEAP_TYPE_VIDEO_DECODE_STATISTICS:
type_desc = "VIDEO_DECODE_STATISTICS";
break;
case D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS:
type_desc = "PIPELINE_STATISTICS";
break;
case D3D12_QUERY_HEAP_TYPE_SO_STATISTICS:
type_desc = "SO_STATISTICS";
break;
default:
type_desc = "???";
break;
}
APPEND_SNPRINTF(" || %s || Count = %u", type_desc, desc->Count);
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_register_resource_cookie(struct vkd3d_descriptor_qa_global_info *global_info,
struct vkd3d_cookie cookie, const D3D12_RESOURCE_DESC1 *desc)
{
const char *fmt;
DECL_BUFFER();
vkd3d_descriptor_debug_set_live_status_bit(global_info, cookie);
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("RESOURCE CREATE %u || ", cookie.index);
fmt = debug_dxgi_format(desc->Format);
switch (desc->Dimension)
{
case D3D12_RESOURCE_DIMENSION_BUFFER:
APPEND_SNPRINTF("Buffer");
APPEND_SNPRINTF(" || Size = 0x%"PRIx64" bytes", desc->Width);
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
APPEND_SNPRINTF("Tex1D");
APPEND_SNPRINTF(" || Format = %s || Levels = %u || Layers = %u || Width = %"PRIu64,
fmt, desc->MipLevels, desc->DepthOrArraySize, desc->Width);
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
APPEND_SNPRINTF("Tex2D");
APPEND_SNPRINTF(" || Format = %s || Levels = %u || Layers = %u || Width = %"PRIu64" || Height = %u",
fmt, desc->MipLevels, desc->DepthOrArraySize, desc->Width, desc->Height);
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
APPEND_SNPRINTF("Tex3D");
APPEND_SNPRINTF(" || Format = %s || Levels = %u || Width = %"PRIu64" || Height = %u || Depth = %u",
fmt, desc->MipLevels, desc->Width, desc->Height, desc->DepthOrArraySize);
break;
default:
APPEND_SNPRINTF("Unknown dimension");
break;
}
if (desc->Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
APPEND_SNPRINTF(" || UAV");
if (desc->Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)
APPEND_SNPRINTF(" || RTV");
if (desc->Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)
APPEND_SNPRINTF(" || DSV");
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_register_allocation_cookie(
struct vkd3d_descriptor_qa_global_info *global_info,
struct vkd3d_cookie cookie, const struct vkd3d_allocate_memory_info *info)
{
D3D12_RESOURCE_DESC1 desc;
memset(&desc, 0, sizeof(desc));
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Width = info->memory_requirements.size;
vkd3d_descriptor_debug_register_resource_cookie(global_info, cookie, &desc);
}
void vkd3d_descriptor_debug_register_view_cookie(
struct vkd3d_descriptor_qa_global_info *global_info,
struct vkd3d_cookie cookie, struct vkd3d_cookie resource_cookie)
{
DECL_BUFFER();
vkd3d_descriptor_debug_set_live_status_bit(global_info, cookie);
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("VIEW CREATE %u <- RESOURCE %u", cookie.index, resource_cookie.index);
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_unregister_cookie(
struct vkd3d_descriptor_qa_global_info *global_info,
struct vkd3d_cookie cookie)
{
DECL_BUFFER();
/* Don't unset the null descriptor by mistake. */
if (cookie.index != 0)
vkd3d_descriptor_debug_unset_live_status_bit(global_info, cookie);
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("COOKIE DESTROY %u", cookie.index);
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_write_descriptor(struct vkd3d_descriptor_qa_heap_buffer_data *heap,
struct vkd3d_cookie heap_cookie,
uint32_t offset, vkd3d_descriptor_qa_flags type_flags, struct vkd3d_cookie cookie)
{
DECL_BUFFER();
if (heap && offset < heap->num_descriptors)
{
/* Should never overflow here except if game is literally spamming allocations every frame and we
* wait around for hours/days.
* This case will trigger warnings either way. */
heap->desc[offset].cookie = cookie.index;
heap->desc[offset].descriptor_type = type_flags;
/* Could overflow, but most likely won't be an issue ... */
heap->desc[offset].va_map_timestamp = cookie.va_map_timestamp;
}
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("WRITE HEAP %u || OFFSET = %u || TYPE = %s || COOKIE = %u",
heap_cookie.index, offset, debug_descriptor_type(type_flags), cookie.index);
FLUSH_BUFFER();
}
void vkd3d_descriptor_debug_copy_descriptor(
struct vkd3d_descriptor_qa_heap_buffer_data *dst_heap,
struct vkd3d_cookie dst_heap_cookie, uint32_t dst_offset,
struct vkd3d_descriptor_qa_heap_buffer_data *src_heap,
struct vkd3d_cookie src_heap_cookie, uint32_t src_offset,
struct vkd3d_cookie cookie)
{
DECL_BUFFER();
if (dst_heap && src_heap && dst_offset < dst_heap->num_descriptors && src_offset < src_heap->num_descriptors)
dst_heap->desc[dst_offset] = src_heap->desc[src_offset];
if (!vkd3d_descriptor_debug_active_log())
return;
APPEND_SNPRINTF("COPY DST HEAP %u || DST OFFSET = %u || COOKIE = %u || SRC HEAP %u || SRC OFFSET = %u",
dst_heap_cookie.index, dst_offset, cookie.index, src_heap_cookie.index, src_offset);
FLUSH_BUFFER();
}