Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- tests/shader_runner_d3d12.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tests/shader_runner_d3d12.c b/tests/shader_runner_d3d12.c index 06221691e..c3eb56864 100644 --- a/tests/shader_runner_d3d12.c +++ b/tests/shader_runner_d3d12.c @@ -296,8 +296,6 @@ static void d3d12_runner_draw(struct shader_runner *r, ID3D12GraphicsCommandList_ClearRenderTargetView(command_list, test_context->rtv, clear_color, 0, NULL); ID3D12GraphicsCommandList_SetPipelineState(command_list, pso); ID3D12GraphicsCommandList_DrawInstanced(command_list, vertex_count, 1, 0, 0); - transition_resource_state(command_list, test_context->render_target, - D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE);
/* Finish the command list so that we can destroy objects. */ hr = ID3D12GraphicsCommandList_Close(command_list); @@ -314,11 +312,15 @@ static void d3d12_runner_probe_vec4(struct shader_runner *r, struct test_context *test_context = &runner->test_context; struct resource_readback rb;
+ transition_resource_state(test_context->list, test_context->render_target, + D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE); get_texture_readback_with_command_list(test_context->render_target, 0, &rb, test_context->queue, test_context->list); check_readback_data_vec4(&rb, rect, v, ulps); release_resource_readback(&rb); reset_command_list(test_context->list, test_context->allocator); + transition_resource_state(test_context->list, test_context->render_target, + D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET); }
static const struct shader_runner_ops d3d12_runner_ops =
We will need to allocate some structures in the Vulkan backend; this is easier if we don't need to worry about allocating them dynamically.
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- tests/shader_runner.c | 5 +++-- tests/shader_runner.h | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/tests/shader_runner.c b/tests/shader_runner.c index 0aca7830e..2bdc6bd15 100644 --- a/tests/shader_runner.c +++ b/tests/shader_runner.c @@ -327,7 +327,9 @@ static void set_resource(struct shader_runner *runner, struct resource *resource } }
- runner->resources = realloc(runner->resources, (runner->resource_count + 1) * sizeof(*runner->resources)); + if (runner->resource_count == MAX_RESOURCES) + fatal_error("Too many resources declared.\n"); + runner->resources[runner->resource_count++] = resource; }
@@ -846,7 +848,6 @@ out: if (runner->resources[i]) runner->ops->destroy_resource(runner, runner->resources[i]); } - free(runner->resources);
fclose(f);
diff --git a/tests/shader_runner.h b/tests/shader_runner.h index 8dc77673b..3bfbf48df 100644 --- a/tests/shader_runner.h +++ b/tests/shader_runner.h @@ -87,6 +87,8 @@ struct input_element unsigned int index; };
+#define MAX_RESOURCES 32 + struct shader_runner { const struct shader_runner_ops *ops; @@ -98,7 +100,7 @@ struct shader_runner uint32_t *uniforms; size_t uniform_count, uniform_capacity;
- struct resource **resources; + struct resource *resources[MAX_RESOURCES]; size_t resource_count;
struct sampler *samplers;
Signed-off-by: Henri Verbeet hverbeet@codeweavers.com
We will need to allocate some structures in the Vulkan backend; this is easier if we don't need to worry about allocating them dynamically.
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- tests/shader_runner.c | 15 ++++++--------- tests/shader_runner.h | 3 ++- 2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/tests/shader_runner.c b/tests/shader_runner.c index 2bdc6bd15..bf3bc0d85 100644 --- a/tests/shader_runner.c +++ b/tests/shader_runner.c @@ -725,17 +725,14 @@ void run_shader_tests(struct shader_runner *runner, int argc, char **argv, const { state = STATE_SAMPLER;
- if ((current_sampler = get_sampler(runner, index))) + if (!(current_sampler = get_sampler(runner, index))) { - memset(current_sampler, 0, sizeof(*current_sampler)); - } - else - { - runner->samplers = realloc(runner->samplers, - ++runner->sampler_count * sizeof(*runner->samplers)); - current_sampler = &runner->samplers[runner->sampler_count - 1]; - memset(current_sampler, 0, sizeof(*current_sampler)); + if (runner->sampler_count == MAX_SAMPLERS) + fatal_error("Too many samplers declared.\n"); + + current_sampler = &runner->samplers[runner->sampler_count++]; } + memset(current_sampler, 0, sizeof(*current_sampler)); current_sampler->slot = index; current_sampler->filter = D3D12_FILTER_MIN_MAG_MIP_POINT; current_sampler->u_address = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; diff --git a/tests/shader_runner.h b/tests/shader_runner.h index 3bfbf48df..5d08206f2 100644 --- a/tests/shader_runner.h +++ b/tests/shader_runner.h @@ -88,6 +88,7 @@ struct input_element };
#define MAX_RESOURCES 32 +#define MAX_SAMPLERS 32
struct shader_runner { @@ -103,7 +104,7 @@ struct shader_runner struct resource *resources[MAX_RESOURCES]; size_t resource_count;
- struct sampler *samplers; + struct sampler samplers[MAX_SAMPLERS]; size_t sampler_count;
struct input_element *input_elements;
Signed-off-by: Henri Verbeet hverbeet@codeweavers.com
Signed-off-by: Zebediah Figura zfigura@codeweavers.com --- Makefile.am | 5 +- tests/shader_runner.c | 2 + tests/shader_runner.h | 2 + tests/shader_runner_vulkan.c | 1160 ++++++++++++++++++++++++++++++++++ tests/vulkan_procs.h | 106 ++++ 5 files changed, 1273 insertions(+), 2 deletions(-) create mode 100644 tests/shader_runner_vulkan.c create mode 100644 tests/vulkan_procs.h
diff --git a/Makefile.am b/Makefile.am index 737dc260d..e2a355762 100644 --- a/Makefile.am +++ b/Makefile.am @@ -297,7 +297,7 @@ vkd3d_compiler_SOURCES = programs/vkd3d-compiler/main.c vkd3d_compiler_CFLAGS = $(AM_CFLAGS) @NCURSES_CFLAGS@ vkd3d_compiler_LDADD = libvkd3d-shader.la @NCURSES_LIBS@
-LDADD = libvkd3d.la libvkd3d-utils.la +LDADD = libvkd3d.la libvkd3d-shader.la libvkd3d-utils.la AM_DEFAULT_SOURCE_EXT = .c
TEST_EXTENSIONS = .shader_test @@ -311,7 +311,8 @@ tests_hlsl_d3d12_LDADD = $(LDADD) @VULKAN_LIBS@ tests_shader_runner_LDADD = $(LDADD) @VULKAN_LIBS@ tests_shader_runner_SOURCES = \ tests/shader_runner.c \ - tests/shader_runner_d3d12.c + tests/shader_runner_d3d12.c \ + tests/shader_runner_vulkan.c tests_vkd3d_api_LDADD = libvkd3d.la @VULKAN_LIBS@ tests_vkd3d_shader_api_LDADD = libvkd3d-shader.la SHADER_TEST_LOG_COMPILER = tests/shader_runner diff --git a/tests/shader_runner.c b/tests/shader_runner.c index bf3bc0d85..54b667fc7 100644 --- a/tests/shader_runner.c +++ b/tests/shader_runner.c @@ -856,6 +856,8 @@ START_TEST(shader_runner) #ifdef _WIN32 run_shader_tests_d3d9(argc, argv); run_shader_tests_d3d11(argc, argv); +#else + run_shader_tests_vulkan(argc, argv); #endif run_shader_tests_d3d12(argc, argv); } diff --git a/tests/shader_runner.h b/tests/shader_runner.h index 5d08206f2..19218ab55 100644 --- a/tests/shader_runner.h +++ b/tests/shader_runner.h @@ -131,5 +131,7 @@ void run_shader_tests(struct shader_runner *runner, int argc, char **argv, const #ifdef _WIN32 void run_shader_tests_d3d9(int argc, char **argv); void run_shader_tests_d3d11(int argc, char **argv); +#else +void run_shader_tests_vulkan(int argc, char **argv); #endif void run_shader_tests_d3d12(int argc, char **argv); diff --git a/tests/shader_runner_vulkan.c b/tests/shader_runner_vulkan.c new file mode 100644 index 000000000..9728f192b --- /dev/null +++ b/tests/shader_runner_vulkan.c @@ -0,0 +1,1160 @@ +/* + * Shader runner which uses libvkd3d-shader to compile HLSL -> D3D bytecode -> SPIR-V + * + * Copyright 2020-2022 Zebediah Figura for CodeWeavers + * + * 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 + */ + +#include "config.h" +#include "vulkan/vulkan.h" +#include "vkd3d_shader.h" +#include "vkd3d.h" +#include "shader_runner.h" +#define VKD3D_TEST_NO_DEFS +#include "vkd3d_test.h" + +struct vulkan_resource +{ + struct resource r; + + VkBuffer buffer; + VkImage image; + VkImageView view; + VkDeviceMemory memory; + + uint32_t binding; +}; + +static struct vulkan_resource *vulkan_resource(struct resource *r) +{ + return CONTAINING_RECORD(r, struct vulkan_resource, r); +} + +#define DECLARE_VK_PFN(name) PFN_##name name; + +struct vulkan_shader_runner +{ + struct shader_runner r; + + VkInstance instance; + VkPhysicalDevice phys_device; + VkDevice device; + VkQueue queue; + VkCommandPool command_pool; + VkCommandBuffer cmd_buffer; + VkRenderPass render_pass; + VkDescriptorPool descriptor_pool; + + VkImage render_target; + VkDeviceMemory rt_memory; + VkImageView rtv; + VkFramebuffer fb; + + struct vulkan_sampler + { + VkSampler vk_sampler; + uint32_t binding; + } samplers[MAX_SAMPLERS]; + + DECLARE_VK_PFN(vkCreateInstance); +#define VK_INSTANCE_PFN DECLARE_VK_PFN +#define VK_DEVICE_PFN DECLARE_VK_PFN +#include "vulkan_procs.h" +}; + +static struct vulkan_shader_runner *vulkan_shader_runner(struct shader_runner *r) +{ + return CONTAINING_RECORD(r, struct vulkan_shader_runner, r); +} + +#define VK_CALL(f) (runner->f) + +static void begin_command_buffer(struct vulkan_shader_runner *runner) +{ + VkCommandBufferBeginInfo buffer_begin_desc = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + + VK_CALL(vkBeginCommandBuffer(runner->cmd_buffer, &buffer_begin_desc)); +} + +static void end_command_buffer(struct vulkan_shader_runner *runner) +{ + VkSubmitInfo submit_desc = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO}; + + VK_CALL(vkEndCommandBuffer(runner->cmd_buffer)); + + submit_desc.commandBufferCount = 1; + submit_desc.pCommandBuffers = &runner->cmd_buffer; + VK_CALL(vkQueueSubmit(runner->queue, 1, &submit_desc, VK_NULL_HANDLE)); + VK_CALL(vkQueueWaitIdle(runner->queue)); +} + +static void transition_image_layout(struct vulkan_shader_runner *runner, + VkImage image, VkImageLayout src_layout, VkImageLayout dst_layout) +{ + VkImageMemoryBarrier barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + + barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + barrier.oldLayout = src_layout; + barrier.newLayout = dst_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + + VK_CALL(vkCmdPipelineBarrier(runner->cmd_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &barrier)); +} + +static unsigned int select_vulkan_memory_type(const struct vulkan_shader_runner *runner, + uint32_t memory_type_mask, VkMemoryPropertyFlags required_flags) +{ + VkPhysicalDeviceMemoryProperties memory_info; + unsigned int i; + + VK_CALL(vkGetPhysicalDeviceMemoryProperties(runner->phys_device, &memory_info)); + + for (i = 0; i < memory_info.memoryTypeCount; ++i) + { + if (!(memory_type_mask & (1u << i))) + continue; + if ((memory_info.memoryTypes[i].propertyFlags & required_flags) == required_flags) + return i; + } + + fatal_error("No valid memory types found matching mask %#x, property flags %#x.\n", + memory_type_mask, required_flags); +} + +static VkDeviceMemory allocate_memory(const struct vulkan_shader_runner *runner, + const VkMemoryRequirements *memory_reqs, VkMemoryPropertyFlags flags) +{ + VkMemoryAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + VkDeviceMemory vk_memory; + VkResult vr; + + alloc_info.allocationSize = memory_reqs->size; + alloc_info.memoryTypeIndex = select_vulkan_memory_type(runner, + memory_reqs->memoryTypeBits, flags); + vr = VK_CALL(vkAllocateMemory(runner->device, &alloc_info, NULL, &vk_memory)); + ok(vr == VK_SUCCESS, "Got unexpected VkResult %d.\n", vr); + + return vk_memory; +} + +static VkBuffer create_buffer(const struct vulkan_shader_runner *runner, VkDeviceSize size, + VkBufferUsageFlags usage, VkMemoryPropertyFlags memory_flags, VkDeviceMemory *memory) +{ + VkBufferCreateInfo buffer_info = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + VkMemoryRequirements memory_reqs; + VkBuffer buffer; + + buffer_info.size = size; + buffer_info.usage = usage; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VK_CALL(vkCreateBuffer(runner->device, &buffer_info, NULL, &buffer)); + VK_CALL(vkGetBufferMemoryRequirements(runner->device, buffer, &memory_reqs)); + *memory = allocate_memory(runner, &memory_reqs, memory_flags); + VK_CALL(vkBindBufferMemory(runner->device, buffer, *memory, 0)); + + return buffer; +} + +static VkImage create_2d_image(const struct vulkan_shader_runner *runner, uint32_t width, uint32_t height, + VkImageUsageFlags usage, VkFormat format, VkDeviceMemory *memory) +{ + VkImageCreateInfo image_info = {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + VkMemoryRequirements memory_reqs; + VkImage image; + + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = format; + image_info.extent.width = width; + image_info.extent.height = height; + image_info.extent.depth = 1; + image_info.mipLevels = 1; + image_info.arrayLayers = 1; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = usage; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + VK_CALL(vkCreateImage(runner->device, &image_info, NULL, &image)); + + VK_CALL(vkGetImageMemoryRequirements(runner->device, image, &memory_reqs)); + *memory = allocate_memory(runner, &memory_reqs, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CALL(vkBindImageMemory(runner->device, image, *memory, 0)); + + return image; +} + +static VkImageView create_2d_image_view(const struct vulkan_shader_runner *runner, VkImage image, VkFormat format) +{ + VkImageViewCreateInfo view_info = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + VkImageView view; + + view_info.image = image; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = format; + view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_info.subresourceRange.baseMipLevel = 0; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.baseArrayLayer = 0; + view_info.subresourceRange.layerCount = 1; + + VK_CALL(vkCreateImageView(runner->device, &view_info, NULL, &view)); + return view; +} + +static struct resource *vulkan_runner_create_resource(struct shader_runner *r, const struct resource_params *params) +{ + struct vulkan_shader_runner *runner = vulkan_shader_runner(r); + + struct vulkan_resource *resource; + VkDevice device = runner->device; + VkBufferImageCopy region = {0}; + VkDeviceMemory staging_memory; + VkBuffer staging_buffer; + VkFormat format; + void *data; + + resource = calloc(1, sizeof(*resource)); + + resource->r.slot = params->slot; + resource->r.type = params->type; + + switch (params->type) + { + case RESOURCE_TYPE_TEXTURE: + format = vkd3d_get_vk_format(params->format); + + resource->image = create_2d_image(runner, params->width, params->height, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, format, &resource->memory); + resource->view = create_2d_image_view(runner, resource->image, format); + + staging_buffer = create_buffer(runner, params->data_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &staging_memory); + VK_CALL(vkMapMemory(device, staging_memory, 0, VK_WHOLE_SIZE, 0, &data)); + memcpy(data, params->data, params->data_size); + VK_CALL(vkUnmapMemory(device, staging_memory)); + + begin_command_buffer(runner); + + transition_image_layout(runner, resource->image, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = params->width; + region.imageExtent.height = params->height; + region.imageExtent.depth = 1; + VK_CALL(vkCmdCopyBufferToImage(runner->cmd_buffer, staging_buffer, resource->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion)); + + transition_image_layout(runner, resource->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + end_command_buffer(runner); + + VK_CALL(vkFreeMemory(device, staging_memory, NULL)); + VK_CALL(vkDestroyBuffer(device, staging_buffer, NULL)); + break; + + case RESOURCE_TYPE_VERTEX_BUFFER: + resource->buffer = create_buffer(runner, params->data_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &resource->memory); + + VK_CALL(vkMapMemory(device, resource->memory, 0, VK_WHOLE_SIZE, 0, &data)); + memcpy(data, params->data, params->data_size); + VK_CALL(vkUnmapMemory(device, resource->memory)); + break; + } + + return &resource->r; +} + +static void vulkan_runner_destroy_resource(struct shader_runner *r, struct resource *res) +{ + struct vulkan_shader_runner *runner = vulkan_shader_runner(r); + struct vulkan_resource *resource = vulkan_resource(res); + VkDevice device = runner->device; + + if (resource->memory) + VK_CALL(vkFreeMemory(device, resource->memory, NULL)); + if (resource->image) + VK_CALL(vkDestroyImage(device, resource->image, NULL)); + if (resource->view) + VK_CALL(vkDestroyImageView(device, resource->view, NULL)); + if (resource->buffer) + VK_CALL(vkDestroyBuffer(device, resource->buffer, NULL)); + + free(resource); +} + +static bool compile_shader(const struct vulkan_shader_runner *runner, const char *source, const char *type, + struct vkd3d_shader_code *dxbc, struct vkd3d_shader_code *spirv) +{ + struct vkd3d_shader_spirv_target_info spirv_info = {.type = VKD3D_SHADER_STRUCTURE_TYPE_SPIRV_TARGET_INFO}; + struct vkd3d_shader_interface_info interface_info = {.type = VKD3D_SHADER_STRUCTURE_TYPE_INTERFACE_INFO}; + struct vkd3d_shader_hlsl_source_info hlsl_info = {.type = VKD3D_SHADER_STRUCTURE_TYPE_HLSL_SOURCE_INFO}; + struct vkd3d_shader_compile_info info = {.type = VKD3D_SHADER_STRUCTURE_TYPE_COMPILE_INFO}; + struct vkd3d_shader_resource_binding bindings[MAX_RESOURCES + MAX_SAMPLERS]; + struct vkd3d_shader_push_constant_buffer push_constants; + struct vkd3d_shader_resource_binding *binding; + char profile[7]; + unsigned int i; + char *messages; + int ret; + + static const char *const shader_models[] = + { + [SHADER_MODEL_2_0] = "4_0", + [SHADER_MODEL_4_0] = "4_0", + [SHADER_MODEL_4_1] = "4_1", + [SHADER_MODEL_5_0] = "5_0", + [SHADER_MODEL_5_1] = "5_1", + }; + + info.next = &hlsl_info; + info.source.code = source; + info.source.size = strlen(source); + info.source_type = VKD3D_SHADER_SOURCE_HLSL; + info.target_type = VKD3D_SHADER_TARGET_DXBC_TPF; + info.log_level = VKD3D_SHADER_LOG_WARNING; + + hlsl_info.entry_point = "main"; + sprintf(profile, "%s_%s", type, shader_models[runner->r.minimum_shader_model]); + hlsl_info.profile = profile; + + ret = vkd3d_shader_compile(&info, dxbc, &messages); + ok(!ret, "Failed to compile shader, error %d.\n", ret); + if (messages && vkd3d_test_state.debug_level) + trace("%s\n", messages); + vkd3d_shader_free_messages(messages); + if (ret) + return false; + + info.next = &spirv_info; + info.source = *dxbc; + info.source_type = VKD3D_SHADER_SOURCE_DXBC_TPF; + info.target_type = VKD3D_SHADER_TARGET_SPIRV_BINARY; + + spirv_info.next = &interface_info; + spirv_info.environment = VKD3D_SHADER_SPIRV_ENVIRONMENT_VULKAN_1_0; + + push_constants.register_space = 0; + push_constants.register_index = 0; + push_constants.shader_visibility = VKD3D_SHADER_VISIBILITY_ALL; + push_constants.offset = 0; + push_constants.size = runner->r.uniform_count * sizeof(*runner->r.uniforms); + + for (i = 0; i < runner->r.resource_count; ++i) + { + const struct vulkan_resource *resource = vulkan_resource(runner->r.resources[i]); + + switch (resource->r.type) + { + case RESOURCE_TYPE_VERTEX_BUFFER: + break; + + case RESOURCE_TYPE_TEXTURE: + binding = &bindings[interface_info.binding_count++]; + binding->type = VKD3D_SHADER_DESCRIPTOR_TYPE_SRV; + binding->register_space = 0; + binding->register_index = resource->r.slot; + binding->shader_visibility = VKD3D_SHADER_VISIBILITY_ALL; + binding->flags = VKD3D_SHADER_BINDING_FLAG_IMAGE; + binding->binding.set = 0; + binding->binding.binding = resource->binding; + binding->binding.count = 1; + break; + } + } + + for (i = 0; i < runner->r.sampler_count; ++i) + { + binding = &bindings[interface_info.binding_count++]; + binding->type = VKD3D_SHADER_DESCRIPTOR_TYPE_SAMPLER; + binding->register_space = 0; + binding->register_index = runner->r.samplers[i].slot; + binding->shader_visibility = VKD3D_SHADER_VISIBILITY_ALL; + binding->flags = VKD3D_SHADER_BINDING_FLAG_IMAGE; + binding->binding.set = 0; + binding->binding.binding = runner->samplers[i].binding; + binding->binding.count = 1; + } + + interface_info.bindings = bindings; + + interface_info.push_constant_buffer_count = 1; + interface_info.push_constant_buffers = &push_constants; + + ret = vkd3d_shader_compile(&info, spirv, &messages); + ok(!ret, "Failed to compile shader, error %d.\n", ret); + if (messages && vkd3d_test_state.debug_level) + trace("%s\n", messages); + vkd3d_shader_free_messages(messages); + if (ret) + return false; + + return true; +} + +static bool create_shader_stage(const struct vulkan_shader_runner *runner, + VkPipelineShaderStageCreateInfo *stage_info, const char *type, enum VkShaderStageFlagBits stage, + const char *source, struct vkd3d_shader_code *dxbc_ptr) +{ + VkShaderModuleCreateInfo module_info = {.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + struct vkd3d_shader_code spirv, dxbc; + + if (!dxbc_ptr) + dxbc_ptr = &dxbc; + + if (!compile_shader(runner, source, type, dxbc_ptr, &spirv)) + return false; + + if (dxbc_ptr == &dxbc) + vkd3d_shader_free_shader_code(&dxbc); + + memset(stage_info, 0, sizeof(*stage_info)); + stage_info->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage_info->stage = stage; + stage_info->pName = "main"; + + module_info.codeSize = spirv.size; + module_info.pCode = spirv.code; + + VK_CALL(vkCreateShaderModule(runner->device, &module_info, NULL, &stage_info->module)); + vkd3d_shader_free_shader_code(&spirv); + return true; +} + +static VkPrimitiveTopology vulkan_primitive_topology_from_d3d(D3D_PRIMITIVE_TOPOLOGY topology) +{ + switch (topology) + { + default: + fatal_error("Unhandled primitive topology %#x.\n", topology); + /* fall through */ + case D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + } +} + +static VkPipelineLayout create_pipeline_layout(const struct vulkan_shader_runner *runner, + VkDescriptorSetLayout set_layout) +{ + VkPipelineLayoutCreateInfo layout_desc = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + VkPushConstantRange push_constant_range; + VkPipelineLayout pipeline_layout; + + layout_desc.setLayoutCount = 1; + layout_desc.pSetLayouts = &set_layout; + + if (runner->r.uniform_count) + { + layout_desc.pushConstantRangeCount = 1; + layout_desc.pPushConstantRanges = &push_constant_range; + + push_constant_range.stageFlags = VK_SHADER_STAGE_ALL; + push_constant_range.offset = 0; + push_constant_range.size = runner->r.uniform_count * sizeof(*runner->r.uniforms); + } + + VK_CALL(vkCreatePipelineLayout(runner->device, &layout_desc, NULL, &pipeline_layout)); + + return pipeline_layout; +} + +static VkPipeline create_pipeline(const struct vulkan_shader_runner *runner, + VkPipelineLayout pipeline_layout, D3D_PRIMITIVE_TOPOLOGY primitive_topology) +{ + VkPipelineInputAssemblyStateCreateInfo ia_desc = {.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + VkPipelineRasterizationStateCreateInfo rs_desc = {.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + VkPipelineVertexInputStateCreateInfo input_desc = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineColorBlendStateCreateInfo blend_desc = {.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + VkPipelineMultisampleStateCreateInfo ms_desc = {.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + static const VkViewport viewport = {.width = RENDER_TARGET_WIDTH, .height = RENDER_TARGET_HEIGHT, .maxDepth = 1}; + VkPipelineViewportStateCreateInfo vp_desc = {.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + static const VkRect2D rt_rect = {.extent.width = RENDER_TARGET_WIDTH, .extent.height = RENDER_TARGET_HEIGHT}; + VkGraphicsPipelineCreateInfo pipeline_desc = {.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + VkPipelineColorBlendAttachmentState attachment_desc = {0}; + VkVertexInputAttributeDescription input_attributes[32]; + VkVertexInputBindingDescription input_bindings[32]; + struct vkd3d_shader_signature vs_input_signature; + VkPipelineShaderStageCreateInfo stage_desc[2]; + struct vkd3d_shader_code vs_dxbc; + VkDevice device = runner->device; + VkPipeline pipeline; + unsigned int i, j; + int ret; + + if (!create_shader_stage(runner, &stage_desc[0], "vs", VK_SHADER_STAGE_VERTEX_BIT, runner->r.vs_source, &vs_dxbc)) + return VK_NULL_HANDLE; + if (!create_shader_stage(runner, &stage_desc[1], "ps", VK_SHADER_STAGE_FRAGMENT_BIT, runner->r.ps_source, NULL)) + { + VK_CALL(vkDestroyShaderModule(device, stage_desc[0].module, NULL)); + return VK_NULL_HANDLE; + } + + ret = vkd3d_shader_parse_input_signature(&vs_dxbc, &vs_input_signature, NULL); + ok(!ret, "Failed to parse input signature, error %d.\n", ret); + + if (runner->r.input_element_count > ARRAY_SIZE(input_attributes)) + fatal_error("Input element count %zu is too high.\n", runner->r.input_element_count); + + for (i = 0; i < runner->r.input_element_count; ++i) + { + VkVertexInputAttributeDescription *attribute = &input_attributes[i]; + const struct input_element *element = &runner->r.input_elements[i]; + const struct vkd3d_shader_signature_element *signature_element; + + signature_element = vkd3d_shader_find_signature_element(&vs_input_signature, element->name, element->index, 0); + + attribute->location = signature_element->register_index; + attribute->binding = element->slot; + attribute->format = vkd3d_get_vk_format(element->format); + /* The offset will be filled below. */ + } + + input_desc.vertexAttributeDescriptionCount = runner->r.input_element_count; + input_desc.pVertexAttributeDescriptions = input_attributes; + input_desc.pVertexBindingDescriptions = input_bindings; + + for (i = 0; i < runner->r.resource_count; ++i) + { + const struct vulkan_resource *resource = vulkan_resource(runner->r.resources[i]); + + switch (resource->r.type) + { + case RESOURCE_TYPE_TEXTURE: + break; + + case RESOURCE_TYPE_VERTEX_BUFFER: + { + VkVertexInputBindingDescription *binding = &input_bindings[input_desc.vertexBindingDescriptionCount++]; + + binding->binding = resource->r.slot; + binding->stride = 0; + binding->inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + for (j = 0; j < runner->r.input_element_count; ++j) + { + if (runner->r.input_elements[j].slot == resource->r.slot) + { + input_attributes[j].offset = binding->stride; + binding->stride += runner->r.input_elements[j].texel_size; + } + } + break; + } + } + } + + ia_desc.topology = vulkan_primitive_topology_from_d3d(primitive_topology); + + vp_desc.viewportCount = 1; + vp_desc.pViewports = &viewport; + vp_desc.scissorCount = 1; + vp_desc.pScissors = &rt_rect; + + rs_desc.frontFace = VK_FRONT_FACE_CLOCKWISE; + rs_desc.lineWidth = 1.0f; + + ms_desc.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + blend_desc.attachmentCount = 1; + blend_desc.pAttachments = &attachment_desc; + + attachment_desc.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + pipeline_desc.stageCount = ARRAY_SIZE(stage_desc); + pipeline_desc.pStages = stage_desc; + pipeline_desc.pVertexInputState = &input_desc; + pipeline_desc.pInputAssemblyState = &ia_desc; + pipeline_desc.pViewportState = &vp_desc; + pipeline_desc.pRasterizationState = &rs_desc; + pipeline_desc.pMultisampleState = &ms_desc; + pipeline_desc.pColorBlendState = &blend_desc; + pipeline_desc.layout = pipeline_layout; + pipeline_desc.renderPass = runner->render_pass; + pipeline_desc.subpass = 0; + + VK_CALL(vkCreateGraphicsPipelines(runner->device, VK_NULL_HANDLE, 1, &pipeline_desc, NULL, &pipeline)); + + VK_CALL(vkDestroyShaderModule(device, stage_desc[0].module, NULL)); + VK_CALL(vkDestroyShaderModule(device, stage_desc[1].module, NULL)); + vkd3d_shader_free_shader_signature(&vs_input_signature); + vkd3d_shader_free_shader_code(&vs_dxbc); + + return pipeline; +} + +static VkSamplerAddressMode vk_address_mode_from_d3d12(D3D12_TEXTURE_ADDRESS_MODE mode) +{ + switch (mode) + { + case D3D12_TEXTURE_ADDRESS_MODE_WRAP: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case D3D12_TEXTURE_ADDRESS_MODE_MIRROR: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case D3D12_TEXTURE_ADDRESS_MODE_CLAMP: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case D3D12_TEXTURE_ADDRESS_MODE_BORDER: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + default: + fatal_error("Unhandled sampler address mode %#x.\n", mode); + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } +} + +static VkDescriptorSetLayout create_descriptor_set_layout(struct vulkan_shader_runner *runner) +{ + VkDescriptorSetLayoutCreateInfo set_desc = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + VkDescriptorSetLayoutBinding bindings[MAX_RESOURCES + MAX_SAMPLERS]; + VkDescriptorSetLayoutBinding *binding; + VkDescriptorSetLayout set_layout; + uint32_t binding_index = 0; + size_t i; + + if (runner->r.resource_count > ARRAY_SIZE(bindings)) + fatal_error("Resource count %zu is too high.\n", runner->r.resource_count); + + set_desc.pBindings = bindings; + + for (i = 0; i < runner->r.resource_count; ++i) + { + struct vulkan_resource *resource = vulkan_resource(runner->r.resources[i]); + + switch (resource->r.type) + { + case RESOURCE_TYPE_VERTEX_BUFFER: + break; + + case RESOURCE_TYPE_TEXTURE: + binding = &bindings[set_desc.bindingCount++]; + + resource->binding = binding_index++; + + binding->binding = resource->binding; + binding->descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + binding->descriptorCount = 1; + binding->stageFlags = VK_SHADER_STAGE_ALL; + binding->pImmutableSamplers = NULL; + break; + } + } + + for (i = 0; i < runner->r.sampler_count; ++i) + { + VkSamplerCreateInfo sampler_desc = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + struct vulkan_sampler *vulkan_sampler = &runner->samplers[i]; + const struct sampler *sampler = &runner->r.samplers[i]; + + sampler_desc.magFilter = (sampler->filter & 0x4) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + sampler_desc.minFilter = (sampler->filter & 0x1) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + sampler_desc.mipmapMode = (sampler->filter & 0x10) ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST; + sampler_desc.addressModeU = vk_address_mode_from_d3d12(sampler->u_address); + sampler_desc.addressModeV = vk_address_mode_from_d3d12(sampler->v_address); + sampler_desc.addressModeW = vk_address_mode_from_d3d12(sampler->w_address); + + VK_CALL(vkCreateSampler(runner->device, &sampler_desc, NULL, &vulkan_sampler->vk_sampler)); + vulkan_sampler->binding = binding_index++; + + binding = &bindings[set_desc.bindingCount++]; + + binding->binding = vulkan_sampler->binding; + binding->descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + binding->descriptorCount = 1; + binding->stageFlags = VK_SHADER_STAGE_ALL; + binding->pImmutableSamplers = &vulkan_sampler->vk_sampler; + } + + VK_CALL(vkCreateDescriptorSetLayout(runner->device, &set_desc, NULL, &set_layout)); + + return set_layout; +} + +static void bind_resources(struct vulkan_shader_runner *runner, VkPipelineBindPoint bind_point, + VkDescriptorSetLayout set_layout, VkPipelineLayout pipeline_layout) +{ + VkDescriptorSetAllocateInfo set_desc = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + VkCommandBuffer cmd_buffer = runner->cmd_buffer; + VkDescriptorSet descriptor_set; + unsigned int i; + + set_desc.descriptorPool = runner->descriptor_pool; + set_desc.descriptorSetCount = 1; + set_desc.pSetLayouts = &set_layout; + + VK_CALL(vkAllocateDescriptorSets(runner->device, &set_desc, &descriptor_set)); + + for (i = 0; i < runner->r.resource_count; ++i) + { + const struct vulkan_resource *resource = vulkan_resource(runner->r.resources[i]); + VkWriteDescriptorSet write = {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + static const VkDeviceSize zero_offset; + VkDescriptorImageInfo image_info; + + switch (resource->r.type) + { + case RESOURCE_TYPE_TEXTURE: + image_info.imageView = resource->view; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + write.dstSet = descriptor_set; + write.dstBinding = resource->binding; + write.dstArrayElement = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + write.pImageInfo = &image_info; + + VK_CALL(vkUpdateDescriptorSets(runner->device, 1, &write, 0, NULL)); + break; + + case RESOURCE_TYPE_VERTEX_BUFFER: + if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) + VK_CALL(vkCmdBindVertexBuffers(cmd_buffer, resource->r.slot, 1, &resource->buffer, &zero_offset)); + break; + } + } + + VK_CALL(vkCmdBindDescriptorSets(cmd_buffer, bind_point, pipeline_layout, 0, 1, &descriptor_set, 0, NULL)); + + if (runner->r.uniform_count) + VK_CALL(vkCmdPushConstants(cmd_buffer, pipeline_layout, VK_SHADER_STAGE_ALL, 0, + runner->r.uniform_count * sizeof(*runner->r.uniforms), runner->r.uniforms)); + + /* The descriptor set will be freed by resetting the descriptor pool. */ +} + +static void vulkan_runner_draw(struct shader_runner *r, + D3D_PRIMITIVE_TOPOLOGY primitive_topology, unsigned int vertex_count) +{ + struct vulkan_shader_runner *runner = vulkan_shader_runner(r); + + static const VkRect2D rt_rect = {.extent.width = RENDER_TARGET_WIDTH, .extent.height = RENDER_TARGET_HEIGHT}; + VkRenderPassBeginInfo pass_begin_desc = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + VkCommandBuffer cmd_buffer = runner->cmd_buffer; + VkClearAttachment clear_attachment; + VkDescriptorSetLayout set_layout; + VkPipelineLayout pipeline_layout; + VkDevice device = runner->device; + VkClearRect clear_rect; + VkPipeline pipeline; + unsigned int i; + + /* Create this before compiling shaders, it will assign resource bindings. */ + set_layout = create_descriptor_set_layout(runner); + + pipeline_layout = create_pipeline_layout(runner, set_layout); + if (!(pipeline = create_pipeline(runner, pipeline_layout, primitive_topology))) + goto out; + + begin_command_buffer(runner); + + pass_begin_desc.renderPass = runner->render_pass; + pass_begin_desc.framebuffer = runner->fb; + pass_begin_desc.renderArea = rt_rect; + + VK_CALL(vkCmdBeginRenderPass(cmd_buffer, &pass_begin_desc, VK_SUBPASS_CONTENTS_INLINE)); + + clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clear_attachment.colorAttachment = 0; + memset(&clear_attachment.clearValue.color, 0, sizeof(clear_attachment.clearValue.color)); + + clear_rect.rect = rt_rect; + clear_rect.baseArrayLayer = 0; + clear_rect.layerCount = 1; + + VK_CALL(vkCmdClearAttachments(cmd_buffer, 1, &clear_attachment, 1, &clear_rect)); + + VK_CALL(vkCmdBindPipeline(cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline)); + + bind_resources(runner, VK_PIPELINE_BIND_POINT_GRAPHICS, set_layout, pipeline_layout); + + VK_CALL(vkCmdDraw(cmd_buffer, vertex_count, 1, 0, 0)); + + VK_CALL(vkCmdEndRenderPass(cmd_buffer)); + end_command_buffer(runner); + + VK_CALL(vkDestroyPipeline(device, pipeline, NULL)); + VK_CALL(vkResetDescriptorPool(device, runner->descriptor_pool, 0)); + +out: + for (i = 0; i < runner->r.sampler_count; ++i) + VK_CALL(vkDestroySampler(device, runner->samplers[i].vk_sampler, NULL)); + + VK_CALL(vkDestroyPipelineLayout(device, pipeline_layout, NULL)); + VK_CALL(vkDestroyDescriptorSetLayout(device, set_layout, NULL)); +} + +static const struct vec4 *get_readback_vec4(const uint8_t *data, unsigned int row_pitch, unsigned int x, unsigned int y) +{ + return (struct vec4 *)(data + y * row_pitch + x * sizeof(struct vec4)); +} + +static void check_readback_data_vec4(const uint8_t *data, unsigned int row_pitch, + const RECT *rect, const struct vec4 *expected, unsigned int max_diff) +{ + unsigned int x = 0, y = 0; + struct vec4 got = {0}; + bool all_match = true; + + for (y = rect->top; y < rect->bottom; ++y) + { + for (x = rect->left; x < rect->right; ++x) + { + got = *get_readback_vec4(data, row_pitch, x, y); + if (!compare_vec4(&got, expected, max_diff)) + { + all_match = false; + break; + } + } + if (!all_match) + break; + } + ok(all_match, "Got {%.8e, %.8e, %.8e, %.8e}, expected {%.8e, %.8e, %.8e, %.8e} at (%u, %u).\n", + got.x, got.y, got.z, got.w, expected->x, expected->y, expected->z, expected->w, x, y); +} + +static void vulkan_runner_probe_vec4(struct shader_runner *r, const RECT *rect, const struct vec4 *v, unsigned int ulps) +{ + struct vulkan_shader_runner *runner = vulkan_shader_runner(r); + VkDevice device = runner->device; + VkBufferImageCopy region = {0}; + VkBuffer staging_buffer; + unsigned int row_pitch; + VkDeviceMemory memory; + void *data; + + row_pitch = RENDER_TARGET_WIDTH * sizeof(struct vec4); + + staging_buffer = create_buffer(runner, row_pitch * RENDER_TARGET_HEIGHT, + VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memory); + + begin_command_buffer(runner); + + transition_image_layout(runner, runner->render_target, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = RENDER_TARGET_WIDTH; + region.imageExtent.height = RENDER_TARGET_HEIGHT; + region.imageExtent.depth = 1; + + VK_CALL(vkCmdCopyImageToBuffer(runner->cmd_buffer, runner->render_target, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging_buffer, 1, ®ion)); + + transition_image_layout(runner, runner->render_target, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + end_command_buffer(runner); + + VK_CALL(vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &data)); + check_readback_data_vec4(data, row_pitch, rect, v, ulps); + VK_CALL(vkUnmapMemory(device, memory)); + + VK_CALL(vkFreeMemory(device, memory, NULL)); + VK_CALL(vkDestroyBuffer(device, staging_buffer, NULL)); +} + +static const struct shader_runner_ops vulkan_runner_ops = +{ + .create_resource = vulkan_runner_create_resource, + .destroy_resource = vulkan_runner_destroy_resource, + .draw = vulkan_runner_draw, + .probe_vec4 = vulkan_runner_probe_vec4, +}; + +static bool get_graphics_queue_index(const struct vulkan_shader_runner *runner, uint32_t *index) +{ + VkQueueFamilyProperties *queue_properties; + uint32_t count, i; + + count = 0; + VK_CALL(vkGetPhysicalDeviceQueueFamilyProperties(runner->phys_device, &count, NULL)); + queue_properties = malloc(count * sizeof(*queue_properties)); + VK_CALL(vkGetPhysicalDeviceQueueFamilyProperties(runner->phys_device, &count, queue_properties)); + + for (i = 0; i < count; ++i) + { + if (queue_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + free(queue_properties); + *index = i; + return true; + } + } + + free(queue_properties); + return false; +} + +static const char *const required_device_extensions[] = +{ + VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME, +}; + +static bool has_extension(const VkExtensionProperties *extensions, uint32_t count, const char *extension_name) +{ + uint32_t i; + + for (i = 0; i < count; ++i) + { + if (!strcmp(extensions[i].extensionName, extension_name)) + return true; + } + return false; +} + +static bool check_device_extensions(struct vulkan_shader_runner *runner) +{ + VkPhysicalDevice phys_device = runner->phys_device; + VkExtensionProperties *extensions; + uint32_t i, count; + + VK_CALL(vkEnumerateDeviceExtensionProperties(phys_device, NULL, &count, NULL)); + extensions = calloc(count, sizeof(*extensions)); + VK_CALL(vkEnumerateDeviceExtensionProperties(phys_device, NULL, &count, extensions)); + + for (i = 0; i < ARRAY_SIZE(required_device_extensions); ++i) + { + if (!has_extension(extensions, count, required_device_extensions[i])) + { + skip("The selected Vulkan device does not support %s.\n", required_device_extensions[i]); + return false; + } + } + + return true; +} + +static bool init_vulkan_runner(struct vulkan_shader_runner *runner) +{ + VkDescriptorPoolCreateInfo descriptor_pool_desc = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + VkCommandBufferAllocateInfo cmd_buffer_desc = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + VkCommandPoolCreateInfo command_pool_desc = {.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + VkRenderPassCreateInfo render_pass_desc = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + VkDeviceQueueCreateInfo queue_desc = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; + VkFramebufferCreateInfo fb_desc = {.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + VkInstanceCreateInfo instance_desc = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; + VkDeviceCreateInfo device_desc = {.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; + VkPhysicalDeviceFeatures ret_features, features; + VkDescriptorPoolSize descriptor_pool_sizes[2]; + VkAttachmentDescription attachment_desc = {0}; + static const float queue_priority = 1.0f; + VkSubpassDescription subpass_desc = {0}; + VkAttachmentReference color_ref = {0}; + VkFormatProperties format_props; + uint32_t count, graphics_index; + VkDevice device; + VkResult vr; + + runner->vkCreateInstance = (void *)vkGetInstanceProcAddr(NULL, "vkCreateInstance"); + + if ((vr = VK_CALL(vkCreateInstance(&instance_desc, NULL, &runner->instance))) < 0) + { + skip("Failed to create a Vulkan instance, vr %d.\n", vr); + return false; + } + +#define VK_INSTANCE_PFN(name) runner->name = (void *)vkGetInstanceProcAddr(runner->instance, #name); +#include "vulkan_procs.h" + + count = 1; + if ((vr = VK_CALL(vkEnumeratePhysicalDevices(runner->instance, &count, &runner->phys_device))) < 0) + { + skip("Failed to enumerate physical devices, vr %d.\n", vr); + goto out_destroy_instance; + } + + if (!count) + { + skip("No Vulkan devices are available.\n"); + goto out_destroy_instance; + } + + if (!get_graphics_queue_index(runner, &graphics_index)) + { + skip("The selected Vulkan device does not support graphics operations.\n"); + goto out_destroy_instance; + } + + device_desc.pQueueCreateInfos = &queue_desc; + device_desc.queueCreateInfoCount = 1; + device_desc.enabledExtensionCount = ARRAY_SIZE(required_device_extensions); + device_desc.ppEnabledExtensionNames = required_device_extensions; + + queue_desc.queueFamilyIndex = graphics_index; + queue_desc.queueCount = 1; + queue_desc.pQueuePriorities = &queue_priority; + + if (!check_device_extensions(runner)) + goto out_destroy_instance; + + VK_CALL(vkGetPhysicalDeviceFormatProperties(runner->phys_device, VK_FORMAT_R32G32B32A32_SFLOAT, &format_props)); + if (!(format_props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) + { + skip("The selected Vulkan device does not support R32G32B32A32_SFLOAT render targets.\n"); + goto out_destroy_instance; + } + + VK_CALL(vkGetPhysicalDeviceFeatures(runner->phys_device, &ret_features)); + + device_desc.pEnabledFeatures = &features; + memset(&features, 0, sizeof(features)); + + /* FIXME: Probably make these optional. */ + +#define ENABLE_FEATURE(x) \ + if (!ret_features.x) \ + { \ + skip("The selected Vulkan device does not support " #x ".\n"); \ + goto out_destroy_instance; \ + } \ + features.x = VK_TRUE + + ENABLE_FEATURE(shaderImageGatherExtended); + + if ((vr = VK_CALL(vkCreateDevice(runner->phys_device, &device_desc, NULL, &device)))) + { + skip("Failed to create device, vr %d.\n", vr); + goto out_destroy_instance; + } + runner->device = device; + +#define VK_DEVICE_PFN(name) runner->name = (void *)VK_CALL(vkGetDeviceProcAddr(device, #name)); +#include "vulkan_procs.h" + + VK_CALL(vkGetDeviceQueue(device, graphics_index, 0, &runner->queue)); + + command_pool_desc.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + command_pool_desc.queueFamilyIndex = graphics_index; + + VK_CALL(vkCreateCommandPool(device, &command_pool_desc, NULL, &runner->command_pool)); + + cmd_buffer_desc.commandPool = runner->command_pool; + cmd_buffer_desc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cmd_buffer_desc.commandBufferCount = 1; + + VK_CALL(vkAllocateCommandBuffers(device, &cmd_buffer_desc, &runner->cmd_buffer)); + + attachment_desc.format = VK_FORMAT_R32G32B32A32_SFLOAT; + attachment_desc.samples = VK_SAMPLE_COUNT_1_BIT; + attachment_desc.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachment_desc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment_desc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment_desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment_desc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachment_desc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + color_ref.attachment = 0; + color_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + subpass_desc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass_desc.colorAttachmentCount = 1; + subpass_desc.pColorAttachments = &color_ref; + + render_pass_desc.attachmentCount = 1; + render_pass_desc.pAttachments = &attachment_desc; + render_pass_desc.subpassCount = 1; + render_pass_desc.pSubpasses = &subpass_desc; + + VK_CALL(vkCreateRenderPass(device, &render_pass_desc, NULL, &runner->render_pass)); + + runner->render_target = create_2d_image(runner, RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_FORMAT_R32G32B32A32_SFLOAT, &runner->rt_memory); + runner->rtv = create_2d_image_view(runner, runner->render_target, VK_FORMAT_R32G32B32A32_SFLOAT); + + fb_desc.renderPass = runner->render_pass; + fb_desc.attachmentCount = 1; + fb_desc.pAttachments = &runner->rtv; + fb_desc.width = RENDER_TARGET_WIDTH; + fb_desc.height = RENDER_TARGET_HEIGHT; + fb_desc.layers = 1; + + VK_CALL(vkCreateFramebuffer(device, &fb_desc, NULL, &runner->fb)); + + descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + descriptor_pool_sizes[0].descriptorCount = MAX_RESOURCES; + descriptor_pool_sizes[1].type = VK_DESCRIPTOR_TYPE_SAMPLER; + descriptor_pool_sizes[1].descriptorCount = MAX_SAMPLERS; + + descriptor_pool_desc.maxSets = 1; + descriptor_pool_desc.poolSizeCount = ARRAY_SIZE(descriptor_pool_sizes); + descriptor_pool_desc.pPoolSizes = descriptor_pool_sizes; + + VK_CALL(vkCreateDescriptorPool(device, &descriptor_pool_desc, NULL, &runner->descriptor_pool)); + + begin_command_buffer(runner); + + transition_image_layout(runner, runner->render_target, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + end_command_buffer(runner); + + return true; + +out_destroy_instance: + VK_CALL(vkDestroyInstance(runner->instance, NULL)); + return false; +}; + +static void cleanup_vulkan_runner(struct vulkan_shader_runner *runner) +{ + VkDevice device = runner->device; + + VK_CALL(vkFreeMemory(device, runner->rt_memory, NULL)); + VK_CALL(vkDestroyFramebuffer(device, runner->fb, NULL)); + VK_CALL(vkDestroyImageView(device, runner->rtv, NULL)); + VK_CALL(vkDestroyImage(device, runner->render_target, NULL)); + + VK_CALL(vkDestroyDescriptorPool(device, runner->descriptor_pool, NULL)); + VK_CALL(vkDestroyRenderPass(device, runner->render_pass, NULL)); + VK_CALL(vkFreeCommandBuffers(device, runner->command_pool, 1, &runner->cmd_buffer)); + VK_CALL(vkDestroyCommandPool(device, runner->command_pool, NULL)); + VK_CALL(vkDestroyDevice(device, NULL)); + VK_CALL(vkDestroyInstance(runner->instance, NULL)); +} + +void run_shader_tests_vulkan(int argc, char **argv) +{ + struct vulkan_shader_runner runner = {0}; + + if (!init_vulkan_runner(&runner)) + return; + + run_shader_tests(&runner.r, argc, argv, &vulkan_runner_ops); + + cleanup_vulkan_runner(&runner); +} diff --git a/tests/vulkan_procs.h b/tests/vulkan_procs.h new file mode 100644 index 000000000..8e205c1be --- /dev/null +++ b/tests/vulkan_procs.h @@ -0,0 +1,106 @@ +/* + * Copyright 2016 Józef Kucia for CodeWeavers + * + * 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 + */ + +#ifndef VK_INSTANCE_PFN +# define VK_INSTANCE_PFN(x) +#endif + +#ifndef VK_INSTANCE_EXT_PFN +# define VK_INSTANCE_EXT_PFN(x) +#endif + +#ifndef VK_DEVICE_PFN +# define VK_DEVICE_PFN(x) +#endif + +#ifndef VK_DEVICE_EXT_PFN +# define VK_DEVICE_EXT_PFN(x) +#endif + +/* Instance functions (obtained by vkGetInstanceProcAddr). */ +VK_INSTANCE_PFN(vkDestroyInstance) /* Load vkDestroyInstance() first. */ +VK_INSTANCE_PFN(vkCreateDevice) +VK_INSTANCE_PFN(vkEnumerateDeviceExtensionProperties) +VK_INSTANCE_PFN(vkEnumeratePhysicalDevices) +VK_INSTANCE_PFN(vkGetDeviceProcAddr) +VK_INSTANCE_PFN(vkGetPhysicalDeviceFeatures) +VK_INSTANCE_PFN(vkGetPhysicalDeviceFormatProperties) +VK_INSTANCE_PFN(vkGetPhysicalDeviceMemoryProperties) +VK_INSTANCE_PFN(vkGetPhysicalDeviceQueueFamilyProperties) + +/* Device functions (obtained by vkGetDeviceProcAddr). */ +VK_DEVICE_PFN(vkDestroyDevice) /* Load vkDestroyDevice() first. */ +VK_DEVICE_PFN(vkAllocateCommandBuffers) +VK_DEVICE_PFN(vkAllocateDescriptorSets) +VK_DEVICE_PFN(vkAllocateMemory) +VK_DEVICE_PFN(vkBeginCommandBuffer) +VK_DEVICE_PFN(vkBindBufferMemory) +VK_DEVICE_PFN(vkBindImageMemory) +VK_DEVICE_PFN(vkCmdBeginRenderPass) +VK_DEVICE_PFN(vkCmdBindDescriptorSets) +VK_DEVICE_PFN(vkCmdBindPipeline) +VK_DEVICE_PFN(vkCmdBindVertexBuffers) +VK_DEVICE_PFN(vkCmdClearAttachments) +VK_DEVICE_PFN(vkCmdCopyBufferToImage) +VK_DEVICE_PFN(vkCmdCopyImageToBuffer) +VK_DEVICE_PFN(vkCmdDraw) +VK_DEVICE_PFN(vkCmdEndRenderPass) +VK_DEVICE_PFN(vkCmdFillBuffer) +VK_DEVICE_PFN(vkCmdPipelineBarrier) +VK_DEVICE_PFN(vkCmdPushConstants) +VK_DEVICE_PFN(vkCreateBuffer) +VK_DEVICE_PFN(vkCreateCommandPool) +VK_DEVICE_PFN(vkCreateDescriptorPool) +VK_DEVICE_PFN(vkCreateDescriptorSetLayout) +VK_DEVICE_PFN(vkCreateFramebuffer) +VK_DEVICE_PFN(vkCreateGraphicsPipelines) +VK_DEVICE_PFN(vkCreateImage) +VK_DEVICE_PFN(vkCreateImageView) +VK_DEVICE_PFN(vkCreatePipelineLayout) +VK_DEVICE_PFN(vkCreateRenderPass) +VK_DEVICE_PFN(vkCreateSampler) +VK_DEVICE_PFN(vkCreateShaderModule) +VK_DEVICE_PFN(vkDestroyBuffer) +VK_DEVICE_PFN(vkDestroyCommandPool) +VK_DEVICE_PFN(vkDestroyDescriptorPool) +VK_DEVICE_PFN(vkDestroyDescriptorSetLayout) +VK_DEVICE_PFN(vkDestroyFramebuffer) +VK_DEVICE_PFN(vkDestroyImage) +VK_DEVICE_PFN(vkDestroyImageView) +VK_DEVICE_PFN(vkDestroyPipeline) +VK_DEVICE_PFN(vkDestroyPipelineLayout) +VK_DEVICE_PFN(vkDestroyRenderPass) +VK_DEVICE_PFN(vkDestroySampler) +VK_DEVICE_PFN(vkDestroyShaderModule) +VK_DEVICE_PFN(vkEndCommandBuffer) +VK_DEVICE_PFN(vkFreeCommandBuffers) +VK_DEVICE_PFN(vkFreeMemory) +VK_DEVICE_PFN(vkGetBufferMemoryRequirements) +VK_DEVICE_PFN(vkGetDeviceQueue) +VK_DEVICE_PFN(vkGetImageMemoryRequirements) +VK_DEVICE_PFN(vkMapMemory) +VK_DEVICE_PFN(vkQueueSubmit) +VK_DEVICE_PFN(vkQueueWaitIdle) +VK_DEVICE_PFN(vkResetDescriptorPool) +VK_DEVICE_PFN(vkUnmapMemory) +VK_DEVICE_PFN(vkUpdateDescriptorSets) + +#undef VK_INSTANCE_PFN +#undef VK_INSTANCE_EXT_PFN +#undef VK_DEVICE_PFN +#undef VK_DEVICE_EXT_PFN
I only did a cursory review, with the idea that for something like this, unless there's something especially egregious, it's fine to fix things up after the fact. A couple of things did stand out though:
On Mon, 11 Apr 2022 at 19:35, Zebediah Figura zfigura@codeweavers.com wrote:
diff --git a/tests/shader_runner.c b/tests/shader_runner.c index bf3bc0d85..54b667fc7 100644 --- a/tests/shader_runner.c +++ b/tests/shader_runner.c @@ -856,6 +856,8 @@ START_TEST(shader_runner) #ifdef _WIN32 run_shader_tests_d3d9(argc, argv); run_shader_tests_d3d11(argc, argv); +#else
- run_shader_tests_vulkan(argc, argv);
#endif run_shader_tests_d3d12(argc, argv); }
I theory, Windows has Vulkan too.
Now that we have multiple shader runners, the logs can become less than clear; vkd3d_test_check_ok() uses "vkd3d_test_name" as prefix, but that's "shader_runner" for all of these. We may want to consider setting that to e.g. "shader_runner_d3d12" for the d3d12 runner, and so on.
+static void transition_image_layout(struct vulkan_shader_runner *runner,
VkImage image, VkImageLayout src_layout, VkImageLayout dst_layout)
+{
- VkImageMemoryBarrier barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
- barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.oldLayout = src_layout;
- barrier.newLayout = dst_layout;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.image = image;
- barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- barrier.subresourceRange.baseMipLevel = 0;
- barrier.subresourceRange.levelCount = 1;
- barrier.subresourceRange.baseArrayLayer = 0;
- barrier.subresourceRange.layerCount = 1;
- VK_CALL(vkCmdPipelineBarrier(runner->cmd_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &barrier));
+}
That function is a fair bit less generic than its name might suggest.
+static bool compile_shader(const struct vulkan_shader_runner *runner, const char *source, const char *type,
struct vkd3d_shader_code *dxbc, struct vkd3d_shader_code *spirv)
+{
[...]
- info.next = &hlsl_info;
- info.source.code = source;
- info.source.size = strlen(source);
- info.source_type = VKD3D_SHADER_SOURCE_HLSL;
- info.target_type = VKD3D_SHADER_TARGET_DXBC_TPF;
- info.log_level = VKD3D_SHADER_LOG_WARNING;
[...]
- info.next = &spirv_info;
- info.source = *dxbc;
- info.source_type = VKD3D_SHADER_SOURCE_DXBC_TPF;
- info.target_type = VKD3D_SHADER_TARGET_SPIRV_BINARY;
It should be relatively straightforward for vkd3d-shader to support compiling HLSL to SPIR-V. There may be a case for a more direct path, but for a start, compile_hlsl() could essentially just do what we're doing here.
+static VkDescriptorSetLayout create_descriptor_set_layout(struct vulkan_shader_runner *runner) +{
[...]
- for (i = 0; i < runner->r.sampler_count; ++i)
- {
VkSamplerCreateInfo sampler_desc = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
struct vulkan_sampler *vulkan_sampler = &runner->samplers[i];
const struct sampler *sampler = &runner->r.samplers[i];
sampler_desc.magFilter = (sampler->filter & 0x4) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
sampler_desc.minFilter = (sampler->filter & 0x1) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
sampler_desc.mipmapMode = (sampler->filter & 0x10) ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_desc.addressModeU = vk_address_mode_from_d3d12(sampler->u_address);
sampler_desc.addressModeV = vk_address_mode_from_d3d12(sampler->v_address);
sampler_desc.addressModeW = vk_address_mode_from_d3d12(sampler->w_address);
We may want to consider storing the filter modes separately to begin with, but if we're going to store these as D3D12_FILTER values, it would make sense to use the appropriate macros like D3D12_DECODE_MAG_FILTER to decode them as well.
+static void bind_resources(struct vulkan_shader_runner *runner, VkPipelineBindPoint bind_point,
VkDescriptorSetLayout set_layout, VkPipelineLayout pipeline_layout)
+{
[...]
- if (runner->r.uniform_count)
VK_CALL(vkCmdPushConstants(cmd_buffer, pipeline_layout, VK_SHADER_STAGE_ALL, 0,
runner->r.uniform_count * sizeof(*runner->r.uniforms), runner->r.uniforms));
Note that the number of available push constants is typically somewhat limited. A value of 128 (= 32 * float = 8 * vec4) for maxPushConstantsSize is not uncommon. That's probably sufficient for our purposes, but nevertheless something to be aware of. It's certainly a lot less than e.g. d3d9's 256 vec4 constants.
On 4/12/22 06:16, Henri Verbeet wrote:
I only did a cursory review, with the idea that for something like this, unless there's something especially egregious, it's fine to fix things up after the fact. A couple of things did stand out though:
On Mon, 11 Apr 2022 at 19:35, Zebediah Figura zfigura@codeweavers.com wrote:
diff --git a/tests/shader_runner.c b/tests/shader_runner.c index bf3bc0d85..54b667fc7 100644 --- a/tests/shader_runner.c +++ b/tests/shader_runner.c @@ -856,6 +856,8 @@ START_TEST(shader_runner) #ifdef _WIN32 run_shader_tests_d3d9(argc, argv); run_shader_tests_d3d11(argc, argv); +#else
- run_shader_tests_vulkan(argc, argv); #endif run_shader_tests_d3d12(argc, argv); }
I theory, Windows has Vulkan too.
Indeed. The intention here is to avoid building crosstests, but that's the wrong way to check for it...
Now that we have multiple shader runners, the logs can become less than clear; vkd3d_test_check_ok() uses "vkd3d_test_name" as prefix, but that's "shader_runner" for all of these. We may want to consider setting that to e.g. "shader_runner_d3d12" for the d3d12 runner, and so on.
Modifying vkd3d_test_name sounds like a good idea.
+static void transition_image_layout(struct vulkan_shader_runner *runner,
VkImage image, VkImageLayout src_layout, VkImageLayout dst_layout)
+{
- VkImageMemoryBarrier barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
- barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.oldLayout = src_layout;
- barrier.newLayout = dst_layout;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.image = image;
- barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- barrier.subresourceRange.baseMipLevel = 0;
- barrier.subresourceRange.levelCount = 1;
- barrier.subresourceRange.baseArrayLayer = 0;
- barrier.subresourceRange.layerCount = 1;
- VK_CALL(vkCmdPipelineBarrier(runner->cmd_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &barrier));
+}
That function is a fair bit less generic than its name might suggest.
I suppose so, although in what respect do you mean specifically?
+static bool compile_shader(const struct vulkan_shader_runner *runner, const char *source, const char *type,
struct vkd3d_shader_code *dxbc, struct vkd3d_shader_code *spirv)
+{
[...]
- info.next = &hlsl_info;
- info.source.code = source;
- info.source.size = strlen(source);
- info.source_type = VKD3D_SHADER_SOURCE_HLSL;
- info.target_type = VKD3D_SHADER_TARGET_DXBC_TPF;
- info.log_level = VKD3D_SHADER_LOG_WARNING;
[...]
- info.next = &spirv_info;
- info.source = *dxbc;
- info.source_type = VKD3D_SHADER_SOURCE_DXBC_TPF;
- info.target_type = VKD3D_SHADER_TARGET_SPIRV_BINARY;
It should be relatively straightforward for vkd3d-shader to support compiling HLSL to SPIR-V. There may be a case for a more direct path, but for a start, compile_hlsl() could essentially just do what we're doing here.
Yes, although in this case we need to reflect the DXBC anyway, in order to retrieve vertex attribute bindings.
+static VkDescriptorSetLayout create_descriptor_set_layout(struct vulkan_shader_runner *runner) +{
[...]
- for (i = 0; i < runner->r.sampler_count; ++i)
- {
VkSamplerCreateInfo sampler_desc = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
struct vulkan_sampler *vulkan_sampler = &runner->samplers[i];
const struct sampler *sampler = &runner->r.samplers[i];
sampler_desc.magFilter = (sampler->filter & 0x4) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
sampler_desc.minFilter = (sampler->filter & 0x1) ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
sampler_desc.mipmapMode = (sampler->filter & 0x10) ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler_desc.addressModeU = vk_address_mode_from_d3d12(sampler->u_address);
sampler_desc.addressModeV = vk_address_mode_from_d3d12(sampler->v_address);
sampler_desc.addressModeW = vk_address_mode_from_d3d12(sampler->w_address);
We may want to consider storing the filter modes separately to begin with, but if we're going to store these as D3D12_FILTER values, it would make sense to use the appropriate macros like D3D12_DECODE_MAG_FILTER to decode them as well.
Sure, that makes sense.
+static void bind_resources(struct vulkan_shader_runner *runner, VkPipelineBindPoint bind_point,
VkDescriptorSetLayout set_layout, VkPipelineLayout pipeline_layout)
+{
[...]
- if (runner->r.uniform_count)
VK_CALL(vkCmdPushConstants(cmd_buffer, pipeline_layout, VK_SHADER_STAGE_ALL, 0,
runner->r.uniform_count * sizeof(*runner->r.uniforms), runner->r.uniforms));
Note that the number of available push constants is typically somewhat limited. A value of 128 (= 32 * float = 8 * vec4) for maxPushConstantsSize is not uncommon. That's probably sufficient for our purposes, but nevertheless something to be aware of. It's certainly a lot less than e.g. d3d9's 256 vec4 constants.
I'll add a check to make sure we don't exceed the limits.
On Tue, 12 Apr 2022 at 18:42, Zebediah Figura zfigura@codeweavers.com wrote:
+static void transition_image_layout(struct vulkan_shader_runner *runner,
VkImage image, VkImageLayout src_layout, VkImageLayout dst_layout)
+{
- VkImageMemoryBarrier barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
- barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.oldLayout = src_layout;
- barrier.newLayout = dst_layout;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.image = image;
- barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- barrier.subresourceRange.baseMipLevel = 0;
- barrier.subresourceRange.levelCount = 1;
- barrier.subresourceRange.baseArrayLayer = 0;
- barrier.subresourceRange.layerCount = 1;
- VK_CALL(vkCmdPipelineBarrier(runner->cmd_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &barrier));
+}
That function is a fair bit less generic than its name might suggest.
I suppose so, although in what respect do you mean specifically?
levelCount and layerCount being 1, although those could easily be replaced with VK_REMAINING_MIP_LEVELS and VK_REMAINING_ARRAY_LAYERS. VK_IMAGE_ASPECT_COLOR_BIT wouldn't work for depth/stencil resources.
+static bool compile_shader(const struct vulkan_shader_runner *runner, const char *source, const char *type,
struct vkd3d_shader_code *dxbc, struct vkd3d_shader_code *spirv)
+{
[...]
- info.next = &hlsl_info;
- info.source.code = source;
- info.source.size = strlen(source);
- info.source_type = VKD3D_SHADER_SOURCE_HLSL;
- info.target_type = VKD3D_SHADER_TARGET_DXBC_TPF;
- info.log_level = VKD3D_SHADER_LOG_WARNING;
[...]
- info.next = &spirv_info;
- info.source = *dxbc;
- info.source_type = VKD3D_SHADER_SOURCE_DXBC_TPF;
- info.target_type = VKD3D_SHADER_TARGET_SPIRV_BINARY;
It should be relatively straightforward for vkd3d-shader to support compiling HLSL to SPIR-V. There may be a case for a more direct path, but for a start, compile_hlsl() could essentially just do what we're doing here.
Yes, although in this case we need to reflect the DXBC anyway, in order to retrieve vertex attribute bindings.
Well, we currently do, but I'm not sure that's a necessity. It shouldn't be too hard to get vkd3d_shader_scan()/vkd3d_shader_compile() to return input/output/patch signatures as well.
On 4/12/22 13:57, Henri Verbeet wrote:
On Tue, 12 Apr 2022 at 18:42, Zebediah Figura zfigura@codeweavers.com wrote:
+static void transition_image_layout(struct vulkan_shader_runner *runner,
VkImage image, VkImageLayout src_layout, VkImageLayout dst_layout)
+{
- VkImageMemoryBarrier barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
- barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
- barrier.oldLayout = src_layout;
- barrier.newLayout = dst_layout;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.image = image;
- barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- barrier.subresourceRange.baseMipLevel = 0;
- barrier.subresourceRange.levelCount = 1;
- barrier.subresourceRange.baseArrayLayer = 0;
- barrier.subresourceRange.layerCount = 1;
- VK_CALL(vkCmdPipelineBarrier(runner->cmd_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &barrier));
+}
That function is a fair bit less generic than its name might suggest.
I suppose so, although in what respect do you mean specifically?
levelCount and layerCount being 1, although those could easily be replaced with VK_REMAINING_MIP_LEVELS and VK_REMAINING_ARRAY_LAYERS. VK_IMAGE_ASPECT_COLOR_BIT wouldn't work for depth/stencil resources.
Okay, that seems sensible to adjust.
+static bool compile_shader(const struct vulkan_shader_runner *runner, const char *source, const char *type,
struct vkd3d_shader_code *dxbc, struct vkd3d_shader_code *spirv)
+{
[...]
- info.next = &hlsl_info;
- info.source.code = source;
- info.source.size = strlen(source);
- info.source_type = VKD3D_SHADER_SOURCE_HLSL;
- info.target_type = VKD3D_SHADER_TARGET_DXBC_TPF;
- info.log_level = VKD3D_SHADER_LOG_WARNING;
[...]
- info.next = &spirv_info;
- info.source = *dxbc;
- info.source_type = VKD3D_SHADER_SOURCE_DXBC_TPF;
- info.target_type = VKD3D_SHADER_TARGET_SPIRV_BINARY;
It should be relatively straightforward for vkd3d-shader to support compiling HLSL to SPIR-V. There may be a case for a more direct path, but for a start, compile_hlsl() could essentially just do what we're doing here.
Yes, although in this case we need to reflect the DXBC anyway, in order to retrieve vertex attribute bindings.
Well, we currently do, but I'm not sure that's a necessity. It shouldn't be too hard to get vkd3d_shader_scan()/vkd3d_shader_compile() to return input/output/patch signatures as well.
We do want a way to scan signatures from DXBC shaders regardless.
I'm not sure that helps us on the HLSL -> SPIRV front, though. I guess the solution there is "assume that inputs are allocated in order, one register per variable", which seems a bit tenuous but ends up working out, both for the reference compiler, and for native d3dcompiler (and for our DXBC -> SPIRV path.)
On Tue, 12 Apr 2022 at 22:48, Zebediah Figura zfigura@codeweavers.com wrote:
On 4/12/22 13:57, Henri Verbeet wrote:
On Tue, 12 Apr 2022 at 18:42, Zebediah Figura zfigura@codeweavers.com wrote:
Yes, although in this case we need to reflect the DXBC anyway, in order to retrieve vertex attribute bindings.
Well, we currently do, but I'm not sure that's a necessity. It shouldn't be too hard to get vkd3d_shader_scan()/vkd3d_shader_compile() to return input/output/patch signatures as well.
We do want a way to scan signatures from DXBC shaders regardless.
I'm not sure that helps us on the HLSL -> SPIRV front, though. I guess the solution there is "assume that inputs are allocated in order, one register per variable", which seems a bit tenuous but ends up working out, both for the reference compiler, and for native d3dcompiler (and for our DXBC -> SPIRV path.)
The trivial implementation could do something like the following:
struct vkd3d_shader_scan_signature_info { ... struct vkd3d_shader_signature *input; struct vkd3d_shader_signature *output; struct vkd3d_shader_signature *patch; };
and when present, fill that with information from the intermediate DXBC in vkd3d_shader_scan()/vkd3d_shader_compile(). A more direct variant could generate that same information directly from the HLSL.
On 4/13/22 09:34, Henri Verbeet wrote:
On Tue, 12 Apr 2022 at 22:48, Zebediah Figura zfigura@codeweavers.com wrote:
On 4/12/22 13:57, Henri Verbeet wrote:
On Tue, 12 Apr 2022 at 18:42, Zebediah Figura zfigura@codeweavers.com wrote:
Yes, although in this case we need to reflect the DXBC anyway, in order to retrieve vertex attribute bindings.
Well, we currently do, but I'm not sure that's a necessity. It shouldn't be too hard to get vkd3d_shader_scan()/vkd3d_shader_compile() to return input/output/patch signatures as well.
We do want a way to scan signatures from DXBC shaders regardless.
I'm not sure that helps us on the HLSL -> SPIRV front, though. I guess the solution there is "assume that inputs are allocated in order, one register per variable", which seems a bit tenuous but ends up working out, both for the reference compiler, and for native d3dcompiler (and for our DXBC -> SPIRV path.)
The trivial implementation could do something like the following:
struct vkd3d_shader_scan_signature_info { ... struct vkd3d_shader_signature *input; struct vkd3d_shader_signature *output; struct vkd3d_shader_signature *patch; };
and when present, fill that with information from the intermediate DXBC in vkd3d_shader_scan()/vkd3d_shader_compile(). A more direct variant could generate that same information directly from the HLSL.
It doesn't seem like the most intuitive API (e.g. it's kind of weird that you can scan DXBC but not SPIR-V, or that you can get information when compiling to SPIR-V but not when reflecting it), but I guess it makes sense regardless. I'll make sure to make the documentation clear...
On Wed, 13 Apr 2022 at 20:02, Zebediah Figura zfigura@codeweavers.com wrote:
It doesn't seem like the most intuitive API (e.g. it's kind of weird that you can scan DXBC but not SPIR-V, or that you can get information when compiling to SPIR-V but not when reflecting it), but I guess it makes sense regardless. I'll make sure to make the documentation clear...
The API doesn't prevent us from scanning/reflecting SPIR-V though; the issue is that (in principle) SPIR-V doesn't have semantic strings. Those originate from the HLSL source; DXBC retains those in the form of the input/output signature, while SPIR-V does not. Indeed, the whole reason we're scanning the HLSL/DXBC in the first place here is because matching inputs doesn't work that way in Vulkan.
It may be possible to add some extra information to the SPIR-V as an OpSource block, but it doesn't seem an especially attractive option. The other way to do this is to make use of knowledge about HLSL input assignment rules; in principle these are predictable, although I don't think HLSL explicitly guarantees that anywhere.
Taking a step back and looking at the bigger picture, there are some other ways to approach this as well. We could explicitly specify the mapping from HLSL input semantics to SPIR-V attribute locations at compile time, instead of using reflection to determine this after the fact. This would look similar to what we do for resource/descriptor bindings with the vkd3d_shader_interface_info structure. That seems like a useful feature regardless of what we end up doing for the shader runner.
Signed-off-by: Henri Verbeet hverbeet@codeweavers.com