From 1822f3bf90dc549b9c7f276077d83acfbe03fa2d Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 15:12:12 +0100 Subject: [PATCH] Vulkan: Begin hooking up SPIR-V reflection. --- Makefile.common | 1 + gfx/drivers_shader/shader_vulkan.cpp | 51 +++-- gfx/drivers_shader/slang_reflection.cpp | 242 ++++++++++++++++++++++++ gfx/drivers_shader/slang_reflection.hpp | 64 +++++++ 4 files changed, 338 insertions(+), 20 deletions(-) create mode 100644 gfx/drivers_shader/slang_reflection.cpp create mode 100644 gfx/drivers_shader/slang_reflection.hpp diff --git a/Makefile.common b/Makefile.common index ec9ac1dec8..ba14754393 100644 --- a/Makefile.common +++ b/Makefile.common @@ -771,6 +771,7 @@ ifeq ($(HAVE_VULKAN), 1) gfx/drivers_font/vulkan_raster_font.o \ gfx/drivers_shader/shader_vulkan.o \ gfx/drivers_shader/glslang_util.o \ + gfx/drivers_shader/slang_reflection.o \ $(GLSLANG_OBJ) \ $(SPIR2CROSS_OBJ) ifeq ($(HAVE_MENU_COMMON), 1) diff --git a/gfx/drivers_shader/shader_vulkan.cpp b/gfx/drivers_shader/shader_vulkan.cpp index df05461e08..178b11af00 100644 --- a/gfx/drivers_shader/shader_vulkan.cpp +++ b/gfx/drivers_shader/shader_vulkan.cpp @@ -25,10 +25,9 @@ #include "../drivers/vulkan_shaders/opaque.frag.inc" #include "../video_shader_driver.h" #include "../../verbosity.h" -#include "spir2cross.hpp" +#include "slang_reflection.hpp" using namespace std; -using namespace spir2cross; static uint32_t find_memory_type( const VkPhysicalDeviceMemoryProperties &mem_props, @@ -248,14 +247,6 @@ class Pass } private: - struct UBO - { - float MVP[16]; - float output_size[4]; - float original_size[4]; - float source_size[4]; - }; - VkDevice device; const VkPhysicalDeviceMemoryProperties &memory_properties; VkPipelineCache cache; @@ -306,6 +297,10 @@ class Pass VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range); + + slang_reflection reflection; + void build_semantic_vec4(uint8_t *data, slang_texture_semantic semantic, + unsigned width, unsigned height); }; // struct here since we're implementing the opaque typedef from C. @@ -952,7 +947,7 @@ bool Pass::init_buffers() ubos.clear(); for (unsigned i = 0; i < num_sync_indices; i++) ubos.emplace_back(new Buffer(device, - memory_properties, sizeof(UBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); + memory_properties, reflection.ubo_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); return true; } @@ -969,6 +964,10 @@ bool Pass::build() if (!init_pipeline()) return false; + memset(&reflection, 0, sizeof(reflection)); + if (!slang_reflect_spirv(vertex_shader, fragment_shader, &reflection)) + return false; + if (!init_buffers()) return false; @@ -1046,11 +1045,22 @@ void Pass::update_descriptor_set( const Texture &source) { set_uniform_buffer(sets[sync_index], 0, - ubos[sync_index]->get_buffer(), 0, sizeof(UBO)); + ubos[sync_index]->get_buffer(), 0, reflection.ubo_size); set_texture(sets[sync_index], 1, original); set_texture(sets[sync_index], 2, source); } +void Pass::build_semantic_vec4(uint8_t *data, slang_texture_semantic semantic, unsigned width, unsigned height) +{ + if (reflection.semantic_texture_ubo_mask & (1 << semantic)) + { + build_vec4( + reinterpret_cast(data + reflection.semantic_textures[semantic].ubo_offset), + width, + height); + } +} + void Pass::build_commands( DeferredDisposer &disposer, VkCommandBuffer cmd, @@ -1072,19 +1082,20 @@ void Pass::build_commands( current_framebuffer_size = size; } - UBO *u = static_cast(ubos[sync_index]->map()); + uint8_t *u = static_cast(ubos[sync_index]->map()); if (mvp) - memcpy(u->MVP, mvp, sizeof(float) * 16); + memcpy(u + reflection.mvp_offset, mvp, sizeof(float) * 16); else - build_identity_matrix(u->MVP); - build_vec4(u->output_size, - current_framebuffer_size.width, - current_framebuffer_size.height); - build_vec4(u->original_size, + build_identity_matrix(reinterpret_cast(u + reflection.mvp_offset)); + + build_semantic_vec4(u, SLANG_TEXTURE_SEMANTIC_OUTPUT, + current_framebuffer_size.width, current_framebuffer_size.height); + build_semantic_vec4(u, SLANG_TEXTURE_SEMANTIC_ORIGINAL, original.texture.width, original.texture.height); - build_vec4(u->source_size, + build_semantic_vec4(u, SLANG_TEXTURE_SEMANTIC_SOURCE, source.texture.width, source.texture.height); + ubos[sync_index]->unmap(); update_descriptor_set(original, source); diff --git a/gfx/drivers_shader/slang_reflection.cpp b/gfx/drivers_shader/slang_reflection.cpp new file mode 100644 index 0000000000..e4c43ffb27 --- /dev/null +++ b/gfx/drivers_shader/slang_reflection.cpp @@ -0,0 +1,242 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2016 - Hans-Kristian Arntzen + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "spir2cross.hpp" +#include "slang_reflection.hpp" +#include +#include +#include "../../verbosity.h" + +using namespace std; +using namespace spir2cross; + +static slang_texture_semantic slang_name_to_semantic(const string &name) +{ + if (name == "Original") + return SLANG_TEXTURE_SEMANTIC_ORIGINAL; + else if (name == "Source") + return SLANG_TEXTURE_SEMANTIC_SOURCE; + else + return SLANG_TEXTURE_INVALID_SEMANTIC; +} + +static bool find_uniform_offset(const Compiler &compiler, const Resource &resource, const char *name, + size_t *offset, unsigned *member_index) +{ + auto &type = compiler.get_type(resource.type_id); + size_t num_members = type.member_types.size(); + for (size_t i = 0; i < num_members; i++) + { + if (compiler.get_member_name(resource.type_id, i) == name) + { + *offset = compiler.get_member_decoration(resource.type_id, i, spv::DecorationOffset); + *member_index = i; + return true; + } + } + return false; +} + +static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragment_compiler, + const ShaderResources &vertex, const ShaderResources &fragment, + slang_reflection *reflection) +{ + unsigned member_index = 0; + + // Validate use of unexpected types. + if ( + !vertex.sampled_images.empty() || + !vertex.storage_buffers.empty() || + !vertex.subpass_inputs.empty() || + !vertex.storage_images.empty() || + !vertex.atomic_counters.empty() || + !vertex.push_constant_buffers.empty() || + !fragment.storage_buffers.empty() || + !fragment.subpass_inputs.empty() || + !fragment.storage_images.empty() || + !fragment.atomic_counters.empty() || + !fragment.push_constant_buffers.empty()) + { + RARCH_ERR("Invalid resource type detected.\n"); + return false; + } + + // Validate vertex input. + if (vertex.stage_inputs.size() != 2) + { + RARCH_ERR("Vertex must have two attributes.\n"); + return false; + } + + uint32_t location_mask = 0; + for (auto &input : vertex.stage_inputs) + location_mask |= 1 << vertex_compiler.get_decoration(input.id, spv::DecorationLocation); + + if (location_mask != 0x3) + { + RARCH_ERR("The two vertex attributes do not use location = 0 and location = 1.\n"); + return false; + } + + // Validate the single uniform buffer. + if (vertex.uniform_buffers.size() != 1) + { + RARCH_ERR("Vertex must use exactly one uniform buffer.\n"); + return false; + } + + if (fragment.uniform_buffers.size() > 1) + { + RARCH_ERR("Fragment must use zero or one uniform buffer.\n"); + return false; + } + + if (vertex_compiler.get_decoration(vertex.uniform_buffers[0].id, spv::DecorationDescriptorSet) != 0) + { + RARCH_ERR("Resources must use descriptor set #0.\n"); + return false; + } + + if (!fragment.uniform_buffers.empty() && + fragment_compiler.get_decoration(fragment.uniform_buffers[0].id, spv::DecorationDescriptorSet) != 0) + { + RARCH_ERR("Resources must use descriptor set #0.\n"); + return false; + } + + unsigned ubo_binding = vertex_compiler.get_decoration(vertex.uniform_buffers[0].id, + spv::DecorationBinding); + if (!fragment.uniform_buffers.empty() && + ubo_binding != fragment_compiler.get_decoration(fragment.uniform_buffers[0].id, spv::DecorationBinding)) + { + RARCH_ERR("Vertex and fragment uniform buffer must have same binding.\n"); + return false; + } + + if (ubo_binding >= SLANG_NUM_BINDINGS) + { + RARCH_ERR("Binding %u is out of range.\n", ubo_binding); + return false; + } + + reflection->ubo_stage_mask = SLANG_STAGE_VERTEX_MASK; + reflection->ubo_size = vertex_compiler.get_declared_struct_size( + vertex_compiler.get_type(vertex.uniform_buffers[0].type_id)); + + if (!fragment.uniform_buffers.empty()) + { + reflection->ubo_stage_mask |= SLANG_STAGE_FRAGMENT_MASK; + reflection->ubo_size = max(reflection->ubo_size, + fragment_compiler.get_declared_struct_size(fragment_compiler.get_type(fragment.uniform_buffers[0].type_id))); + } + + if (!find_uniform_offset(vertex_compiler, vertex.uniform_buffers[0], "MVP", &reflection->mvp_offset, &member_index)) + { + RARCH_ERR("Could not find offset for MVP matrix.\n"); + return false; + } + + uint32_t binding_mask = 1 << ubo_binding; + + // On to textures. + for (auto &texture : fragment.sampled_images) + { + unsigned set = fragment_compiler.get_decoration(texture.id, spv::DecorationDescriptorSet); + unsigned binding = fragment_compiler.get_decoration(texture.id, spv::DecorationBinding); + + if (set != 0) + { + RARCH_ERR("Resources must use descriptor set #0.\n"); + return false; + } + + if (binding >= SLANG_NUM_BINDINGS) + { + RARCH_ERR("Binding %u is out of range.\n", ubo_binding); + return false; + } + + if (binding_mask & (1 << binding)) + { + RARCH_ERR("Binding %u is already in use.\n", binding); + return false; + } + binding_mask |= 1 << binding; + + slang_texture_semantic index = slang_name_to_semantic(texture.name); + + if (index == SLANG_TEXTURE_INVALID_SEMANTIC) + { + RARCH_ERR("Non-semantic textures not supported.\n"); + return false; + } + + auto &semantic = reflection->semantic_textures[index]; + semantic.binding = binding; + semantic.stage_mask = SLANG_STAGE_FRAGMENT_MASK; + reflection->semantic_texture_mask |= 1 << index; + + // TODO: Do we want to expose Size uniforms if no stage is using the texture? + char uniform_name[128]; + snprintf(uniform_name, sizeof(uniform_name), "%sSize", texture.name.c_str()); + if (find_uniform_offset(fragment_compiler, fragment.uniform_buffers[0], uniform_name, + &semantic.ubo_offset, &member_index)) + { + auto &type = fragment_compiler.get_type( + fragment_compiler.get_type(fragment.uniform_buffers[0].type_id).member_types[member_index]); + + // Verify that the type is a vec4 to avoid any nasty surprises later. + bool is_vec4 = type.basetype == SPIRType::Float && type.array.empty() && type.vecsize == 4 && type.columns == 1; + if (!is_vec4) + { + RARCH_ERR("Semantic uniform is not vec4.\n"); + return false; + } + + reflection->semantic_texture_ubo_mask |= 1 << index; + } + } + + return true; +} + +bool slang_reflect_spirv(const std::vector &vertex, + const std::vector &fragment, + slang_reflection *reflection) +{ + try + { + Compiler vertex_compiler(vertex); + Compiler fragment_compiler(fragment); + auto vertex_resources = vertex_compiler.get_shader_resources(); + auto fragment_resources = fragment_compiler.get_shader_resources(); + + if (!slang_reflect(vertex_compiler, fragment_compiler, + vertex_resources, fragment_resources, + reflection)) + { + RARCH_ERR("Failed to reflect SPIR-V. Resource usage is inconsistent with expectations.\n"); + return false; + } + + return true; + } + catch (const std::exception &e) + { + RARCH_ERR("spir2cross threw exception: %s.\n", e.what()); + return false; + } +} + diff --git a/gfx/drivers_shader/slang_reflection.hpp b/gfx/drivers_shader/slang_reflection.hpp new file mode 100644 index 0000000000..95763e994c --- /dev/null +++ b/gfx/drivers_shader/slang_reflection.hpp @@ -0,0 +1,64 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2016 - Hans-Kristian Arntzen + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef SLANG_REFLECTION_HPP +#define SLANG_REFLECTION_HPP + +#include +#include + +// Textures with built-in meaning. +enum slang_texture_semantic +{ + SLANG_TEXTURE_SEMANTIC_ORIGINAL = 0, + SLANG_TEXTURE_SEMANTIC_SOURCE = 1, + SLANG_TEXTURE_SEMANTIC_OUTPUT = 2, + + SLANG_TEXTURE_NUM_SEMANTICS, + SLANG_TEXTURE_INVALID_SEMANTIC = -1 +}; + +enum slang_stage +{ + SLANG_STAGE_VERTEX_MASK = 1 << 0, + SLANG_STAGE_FRAGMENT_MASK = 1 << 1 +}; +#define SLANG_NUM_BINDINGS 16 + +struct slang_texture_semantic_meta +{ + size_t ubo_offset = 0; + unsigned binding = 0; + uint32_t stage_mask = 0; +}; + +struct slang_reflection +{ + size_t ubo_size = 0; + size_t mvp_offset = 0; + unsigned ubo_binding = 0; + uint32_t ubo_stage_mask = 0; + + slang_texture_semantic_meta semantic_textures[SLANG_TEXTURE_NUM_SEMANTICS]; + uint32_t semantic_texture_mask = 0; + uint32_t semantic_texture_ubo_mask = 0; +}; + +bool slang_reflect_spirv(const std::vector &vertex, + const std::vector &fragment, + slang_reflection *reflection); + +#endif +