vkd3d-proton/libs/vkd3d-shader/dxil.c

542 lines
22 KiB
C

/*
* 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_SHADER
#include "vkd3d_shader_private.h"
#include <dxil_spirv_c.h>
static bool dxil_match_shader_visibility(enum vkd3d_shader_visibility visibility,
dxil_spv_shader_stage stage)
{
if (visibility == VKD3D_SHADER_VISIBILITY_ALL)
return true;
switch (stage)
{
case DXIL_SPV_STAGE_VERTEX:
return visibility == VKD3D_SHADER_VISIBILITY_VERTEX;
case DXIL_SPV_STAGE_HULL:
return visibility == VKD3D_SHADER_VISIBILITY_HULL;
case DXIL_SPV_STAGE_DOMAIN:
return visibility == VKD3D_SHADER_VISIBILITY_DOMAIN;
case DXIL_SPV_STAGE_GEOMETRY:
return visibility == VKD3D_SHADER_VISIBILITY_GEOMETRY;
case DXIL_SPV_STAGE_PIXEL:
return visibility == VKD3D_SHADER_VISIBILITY_PIXEL;
case DXIL_SPV_STAGE_COMPUTE:
return visibility == VKD3D_SHADER_VISIBILITY_COMPUTE;
default:
return false;
}
}
static unsigned dxil_resource_flags_from_kind(dxil_spv_resource_kind kind, bool ssbo)
{
switch (kind)
{
case DXIL_SPV_RESOURCE_KIND_RAW_BUFFER:
case DXIL_SPV_RESOURCE_KIND_STRUCTURED_BUFFER:
if (ssbo)
return VKD3D_SHADER_BINDING_FLAG_BUFFER | VKD3D_SHADER_BINDING_FLAG_RAW_SSBO;
else
return VKD3D_SHADER_BINDING_FLAG_BUFFER;
case DXIL_SPV_RESOURCE_KIND_TYPED_BUFFER:
return VKD3D_SHADER_BINDING_FLAG_BUFFER;
default:
return VKD3D_SHADER_BINDING_FLAG_IMAGE;
}
}
static bool dxil_resource_is_in_range(const struct vkd3d_shader_resource_binding *binding,
const dxil_spv_d3d_binding *d3d_binding)
{
if (binding->register_space != d3d_binding->register_space)
return false;
if (d3d_binding->register_index < binding->register_index)
return false;
return binding->register_count == UINT_MAX ||
((d3d_binding->register_index - binding->register_index) < binding->register_count);
}
static dxil_spv_bool dxil_remap(const struct vkd3d_shader_interface_info *shader_interface_info,
enum vkd3d_shader_descriptor_type descriptor_type,
const dxil_spv_d3d_binding *d3d_binding,
dxil_spv_vulkan_binding *vk_binding,
uint32_t resource_flags)
{
unsigned int binding_count = shader_interface_info->binding_count;
unsigned int i;
for (i = 0; i < binding_count; i++)
{
const struct vkd3d_shader_resource_binding *binding = &shader_interface_info->bindings[i];
const uint32_t mask = ~(VKD3D_SHADER_BINDING_FLAG_BINDLESS | VKD3D_SHADER_BINDING_FLAG_RAW_VA);
uint32_t match_flags = binding->flags & mask;
if (binding->type == descriptor_type &&
dxil_resource_is_in_range(binding, d3d_binding) &&
(match_flags & resource_flags) == resource_flags &&
dxil_match_shader_visibility(binding->shader_visibility, d3d_binding->stage))
{
memset(vk_binding, 0, sizeof(*vk_binding));
if (binding->flags & VKD3D_SHADER_BINDING_FLAG_BINDLESS)
{
vk_binding->bindless.use_heap = DXIL_SPV_TRUE;
vk_binding->bindless.heap_root_offset = binding->descriptor_offset +
d3d_binding->register_index - binding->register_index;
vk_binding->bindless.root_constant_word = binding->descriptor_table +
(shader_interface_info->descriptor_tables.offset / sizeof(uint32_t));
vk_binding->set = binding->binding.set;
vk_binding->binding = binding->binding.binding;
}
else
{
vk_binding->set = binding->binding.set;
vk_binding->binding = binding->binding.binding + d3d_binding->register_index - binding->register_index;
}
return DXIL_SPV_TRUE;
}
}
return DXIL_SPV_FALSE;
}
static dxil_spv_bool dxil_srv_remap(void *userdata, const dxil_spv_d3d_binding *d3d_binding,
dxil_spv_srv_vulkan_binding *vk_binding)
{
const struct vkd3d_shader_interface_info *shader_interface_info = userdata;
unsigned int resource_flags, resource_flags_ssbo;
resource_flags_ssbo = dxil_resource_flags_from_kind(d3d_binding->kind, true);
resource_flags = dxil_resource_flags_from_kind(d3d_binding->kind, false);
bool use_ssbo = resource_flags_ssbo != resource_flags;
if (use_ssbo && dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_SRV,
d3d_binding, &vk_binding->buffer_binding, resource_flags_ssbo))
{
vk_binding->buffer_binding.descriptor_type = DXIL_SPV_VULKAN_DESCRIPTOR_TYPE_SSBO;
if (shader_interface_info->flags & VKD3D_SHADER_INTERFACE_SSBO_OFFSET_BUFFER)
{
vk_binding->offset_binding.set = shader_interface_info->offset_buffer_binding->set;
vk_binding->offset_binding.binding = shader_interface_info->offset_buffer_binding->binding;
}
return DXIL_SPV_TRUE;
}
else
vk_binding->buffer_binding.descriptor_type = DXIL_SPV_VULKAN_DESCRIPTOR_TYPE_TEXEL_BUFFER;
return dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_SRV,
d3d_binding, &vk_binding->buffer_binding, resource_flags);
}
static dxil_spv_bool dxil_sampler_remap(void *userdata, const dxil_spv_d3d_binding *d3d_binding,
dxil_spv_vulkan_binding *vk_binding)
{
const struct vkd3d_shader_interface_info *shader_interface_info = userdata;
return dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_SAMPLER,
d3d_binding, vk_binding, VKD3D_SHADER_BINDING_FLAG_IMAGE);
}
static dxil_spv_bool dxil_input_remap(void *userdata, const dxil_spv_d3d_vertex_input *d3d_input,
dxil_spv_vulkan_vertex_input *vk_input)
{
(void)userdata;
vk_input->location = d3d_input->start_row;
return DXIL_SPV_TRUE;
}
static dxil_spv_bool dxil_output_remap(void *userdata, const dxil_spv_d3d_stream_output *d3d_output,
dxil_spv_vulkan_stream_output *vk_output)
{
const struct vkd3d_shader_transform_feedback_info *xfb_info = userdata;
const struct vkd3d_shader_transform_feedback_element *xfb_element;
unsigned int i, offset, stride;
offset = 0;
xfb_element = NULL;
for (i = 0; i < xfb_info->element_count; ++i)
{
const struct vkd3d_shader_transform_feedback_element *e = &xfb_info->elements[i];
/* TODO: Stream index matching? */
if (!ascii_strcasecmp(e->semantic_name, d3d_output->semantic) && e->semantic_index == d3d_output->semantic_index)
{
xfb_element = e;
break;
}
offset += 4 * e->component_count;
}
if (!xfb_element)
{
vk_output->enable = DXIL_SPV_FALSE;
return DXIL_SPV_TRUE;
}
if (xfb_element->output_slot < xfb_info->buffer_stride_count)
{
stride = xfb_info->buffer_strides[xfb_element->output_slot];
}
else
{
stride = 0;
for (i = 0; i < xfb_info->element_count; ++i)
{
const struct vkd3d_shader_transform_feedback_element *e = &xfb_info->elements[i];
if (e->stream_index == xfb_element->stream_index && e->output_slot == xfb_element->output_slot)
stride += 4 * e->component_count;
}
}
vk_output->enable = DXIL_SPV_TRUE;
vk_output->offset = offset;
vk_output->stride = stride;
vk_output->buffer_index = xfb_element->output_slot;
return DXIL_SPV_TRUE;
}
static dxil_spv_bool dxil_uav_remap(void *userdata, const dxil_spv_uav_d3d_binding *d3d_binding,
dxil_spv_uav_vulkan_binding *vk_binding)
{
const struct vkd3d_shader_interface_info *shader_interface_info = userdata;
unsigned int resource_flags, resource_flags_ssbo;
resource_flags_ssbo = dxil_resource_flags_from_kind(d3d_binding->d3d_binding.kind, true);
resource_flags = dxil_resource_flags_from_kind(d3d_binding->d3d_binding.kind, false);
bool use_ssbo = resource_flags != resource_flags_ssbo;
if (use_ssbo)
{
if (dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_UAV, &d3d_binding->d3d_binding,
&vk_binding->buffer_binding, resource_flags_ssbo))
{
vk_binding->buffer_binding.descriptor_type = DXIL_SPV_VULKAN_DESCRIPTOR_TYPE_SSBO;
if (shader_interface_info->flags & VKD3D_SHADER_INTERFACE_SSBO_OFFSET_BUFFER)
{
vk_binding->offset_binding.set = shader_interface_info->offset_buffer_binding->set;
vk_binding->offset_binding.binding = shader_interface_info->offset_buffer_binding->binding;
}
}
else if (!dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_UAV, &d3d_binding->d3d_binding,
&vk_binding->buffer_binding, resource_flags))
{
return DXIL_SPV_FALSE;
}
else
vk_binding->buffer_binding.descriptor_type = DXIL_SPV_VULKAN_DESCRIPTOR_TYPE_TEXEL_BUFFER;
}
else
{
vk_binding->buffer_binding.descriptor_type = DXIL_SPV_VULKAN_DESCRIPTOR_TYPE_TEXEL_BUFFER;
if (!dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_UAV, &d3d_binding->d3d_binding,
&vk_binding->buffer_binding, resource_flags))
{
return DXIL_SPV_FALSE;
}
}
if (d3d_binding->has_counter)
{
if (!dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_UAV, &d3d_binding->d3d_binding,
&vk_binding->counter_binding, VKD3D_SHADER_BINDING_FLAG_COUNTER))
{
return DXIL_SPV_FALSE;
}
}
return DXIL_SPV_TRUE;
}
static dxil_spv_bool dxil_cbv_remap(void *userdata, const dxil_spv_d3d_binding *d3d_binding,
dxil_spv_cbv_vulkan_binding *vk_binding)
{
const struct vkd3d_shader_interface_info *shader_interface_info = userdata;
unsigned int i;
/* Try to map to root constant -> push constant.
* Do not consider shader visibility here, as DXBC path does not appear to do it either. */
for (i = 0; i < shader_interface_info->push_constant_buffer_count; i++)
{
const struct vkd3d_shader_push_constant_buffer *push = &shader_interface_info->push_constant_buffers[i];
if (push->register_space == d3d_binding->register_space &&
push->register_index == d3d_binding->register_index)
{
memset(vk_binding, 0, sizeof(*vk_binding));
vk_binding->push_constant = DXIL_SPV_TRUE;
vk_binding->vulkan.push_constant.offset_in_words = push->offset / sizeof(uint32_t);
return DXIL_SPV_TRUE;
}
}
return dxil_remap(shader_interface_info, VKD3D_SHADER_DESCRIPTOR_TYPE_CBV,
d3d_binding, &vk_binding->vulkan.uniform_binding,
VKD3D_SHADER_BINDING_FLAG_BUFFER);
}
int vkd3d_shader_compile_dxil(const struct vkd3d_shader_code *dxbc,
struct vkd3d_shader_code *spirv,
const struct vkd3d_shader_interface_info *shader_interface_info,
const struct vkd3d_shader_compile_arguments *compiler_args)
{
const struct vkd3d_shader_transform_feedback_info *xfb_info;
unsigned int non_raw_va_binding_count = 0;
unsigned int raw_va_binding_count = 0;
unsigned int root_constant_words = 0;
dxil_spv_converter converter = NULL;
dxil_spv_parsed_blob blob = NULL;
dxil_spv_compiled_spirv compiled;
unsigned int i, max_size;
vkd3d_shader_hash_t hash;
int ret = VKD3D_OK;
void *code;
hash = vkd3d_shader_hash(dxbc);
spirv->meta.replaced = false;
spirv->meta.hash = hash;
if (vkd3d_shader_replace(hash, &spirv->code, &spirv->size))
{
spirv->meta.replaced = true;
return ret;
}
dxil_spv_begin_thread_allocator_context();
vkd3d_shader_dump_shader(hash, dxbc, "dxil");
if (dxil_spv_parse_dxil_blob(dxbc->code, dxbc->size, &blob) != DXIL_SPV_SUCCESS)
{
ret = VKD3D_ERROR_INVALID_SHADER;
goto end;
}
if (dxil_spv_create_converter(blob, &converter) != DXIL_SPV_SUCCESS)
{
ret = VKD3D_ERROR_INVALID_SHADER;
goto end;
}
/* Figure out how many words we need for push constants. */
for (i = 0; i < shader_interface_info->push_constant_buffer_count; i++)
{
max_size = shader_interface_info->push_constant_buffers[i].offset +
shader_interface_info->push_constant_buffers[i].size;
max_size = (max_size + 3) / 4;
if (max_size > root_constant_words)
root_constant_words = max_size;
}
max_size = shader_interface_info->descriptor_tables.offset / sizeof(uint32_t) +
shader_interface_info->descriptor_tables.count;
if (max_size > root_constant_words)
root_constant_words = max_size;
{
const struct dxil_spv_option_ssbo_alignment helper =
{ { DXIL_SPV_OPTION_SSBO_ALIGNMENT }, shader_interface_info->min_ssbo_alignment };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support SSBO_ALIGNMENT.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
if (shader_interface_info->flags & VKD3D_SHADER_INTERFACE_PUSH_CONSTANTS_AS_UNIFORM_BUFFER)
{
const struct dxil_spv_option_root_constant_inline_uniform_block helper =
{ { DXIL_SPV_OPTION_ROOT_CONSTANT_INLINE_UNIFORM_BLOCK },
shader_interface_info->push_constant_ubo_binding->set,
shader_interface_info->push_constant_ubo_binding->binding,
DXIL_SPV_TRUE };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support PUSH_CONSTANTS_AS_UNIFORM_BUFFER.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
if (shader_interface_info->flags & VKD3D_SHADER_INTERFACE_BINDLESS_CBV_AS_STORAGE_BUFFER)
{
static const struct dxil_spv_option_bindless_cbv_ssbo_emulation helper =
{ { DXIL_SPV_OPTION_BINDLESS_CBV_SSBO_EMULATION },
DXIL_SPV_TRUE };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support BINDLESS_CBV_AS_STORAGE_BUFFER.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
for (i = 0; i < shader_interface_info->binding_count; i++)
{
/* Bindless UAV counters are implemented as physical storage buffer pointers.
* For simplicity, dxil-spirv only accepts either fully RAW VA, or all non-raw VA. */
if ((shader_interface_info->bindings[i].flags &
(VKD3D_SHADER_BINDING_FLAG_COUNTER | VKD3D_SHADER_BINDING_FLAG_BINDLESS)) ==
(VKD3D_SHADER_BINDING_FLAG_COUNTER | VKD3D_SHADER_BINDING_FLAG_BINDLESS))
{
if (shader_interface_info->bindings[i].flags & VKD3D_SHADER_BINDING_FLAG_RAW_VA)
raw_va_binding_count++;
else
non_raw_va_binding_count++;
}
}
if (raw_va_binding_count && non_raw_va_binding_count)
{
ERR("dxil-spirv currently cannot mix and match bindless UAV counters with RAW VA and texel buffer.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
{
const struct dxil_spv_option_physical_storage_buffer helper =
{ { DXIL_SPV_OPTION_PHYSICAL_STORAGE_BUFFER },
raw_va_binding_count ? DXIL_SPV_TRUE : DXIL_SPV_FALSE };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support PHYSICAL_STORAGE_BUFFER.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
if (compiler_args)
{
for (i = 0; i < compiler_args->target_extension_count; i++)
{
if (compiler_args->target_extensions[i] == VKD3D_SHADER_TARGET_EXTENSION_SPV_EXT_DEMOTE_TO_HELPER_INVOCATION)
{
static const dxil_spv_option_shader_demote_to_helper helper =
{ { DXIL_SPV_OPTION_SHADER_DEMOTE_TO_HELPER }, DXIL_SPV_TRUE };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
WARN("dxil-spirv does not support DEMOTE_TO_HELPER. Slower path will be used.\n");
}
}
else if (compiler_args->target_extensions[i] == VKD3D_SHADER_TARGET_EXTENSION_READ_STORAGE_IMAGE_WITHOUT_FORMAT)
{
static const dxil_spv_option_typed_uav_read_without_format helper =
{ { DXIL_SPV_OPTION_TYPED_UAV_READ_WITHOUT_FORMAT }, DXIL_SPV_TRUE };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support TYPED_UAV_READ_WITHOUT_FORMAT.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
}
if (compiler_args->dual_source_blending)
{
static const dxil_spv_option_dual_source_blending helper =
{ { DXIL_SPV_OPTION_DUAL_SOURCE_BLENDING }, DXIL_SPV_TRUE };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support DUAL_SOURCE_BLENDING.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
if (compiler_args->output_swizzle_count != 0)
{
const dxil_spv_option_output_swizzle helper =
{ { DXIL_SPV_OPTION_OUTPUT_SWIZZLE }, compiler_args->output_swizzles, compiler_args->output_swizzle_count };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support OUTPUT_SWIZZLE.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
for (i = 0; i < compiler_args->parameter_count; i++)
{
const struct vkd3d_shader_parameter *argument = &compiler_args->parameters[i];
if (argument->name == VKD3D_SHADER_PARAMETER_NAME_RASTERIZER_SAMPLE_COUNT)
{
bool spec_constant = argument->type == VKD3D_SHADER_PARAMETER_TYPE_SPECIALIZATION_CONSTANT;
const dxil_spv_option_rasterizer_sample_count helper =
{ { DXIL_SPV_OPTION_RASTERIZER_SAMPLE_COUNT },
spec_constant ? argument->specialization_constant.id : argument->immediate_constant.u32,
spec_constant ? DXIL_SPV_TRUE : DXIL_SPV_FALSE };
if (dxil_spv_converter_add_option(converter, &helper.base) != DXIL_SPV_SUCCESS)
{
ERR("dxil-spirv does not support RASTERIZER_SAMPLE_COUNT.\n");
ret = VKD3D_ERROR_NOT_IMPLEMENTED;
goto end;
}
}
}
}
dxil_spv_converter_set_root_constant_word_count(converter, root_constant_words);
dxil_spv_converter_set_srv_remapper(converter, dxil_srv_remap, (void *)shader_interface_info);
dxil_spv_converter_set_sampler_remapper(converter, dxil_sampler_remap, (void *)shader_interface_info);
dxil_spv_converter_set_uav_remapper(converter, dxil_uav_remap, (void *)shader_interface_info);
dxil_spv_converter_set_cbv_remapper(converter, dxil_cbv_remap, (void *)shader_interface_info);
dxil_spv_converter_set_vertex_input_remapper(converter, dxil_input_remap, (void *)shader_interface_info);
xfb_info = vkd3d_find_struct(shader_interface_info->next, TRANSFORM_FEEDBACK_INFO);
if (xfb_info)
dxil_spv_converter_set_stream_output_remapper(converter, dxil_output_remap, (void *)xfb_info);
if (dxil_spv_converter_run(converter) != DXIL_SPV_SUCCESS)
{
ret = VKD3D_ERROR_INVALID_SHADER;
goto end;
}
if (dxil_spv_converter_get_compiled_spirv(converter, &compiled) != DXIL_SPV_SUCCESS)
{
ret = VKD3D_ERROR_INVALID_SHADER;
goto end;
}
if (!(code = vkd3d_malloc(compiled.size)))
{
ret = VKD3D_ERROR_OUT_OF_MEMORY;
goto end;
}
memcpy(code, compiled.data, compiled.size);
spirv->code = code;
spirv->size = compiled.size;
vkd3d_shader_dump_spirv_shader(hash, spirv);
end:
dxil_spv_converter_free(converter);
dxil_spv_parsed_blob_free(blob);
dxil_spv_end_thread_allocator_context();
return ret;
}