From 07524fe433b8660ce3b97398b587ca6488724dcc Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 09:29:41 +0100 Subject: [PATCH 01/13] Vulkan: Log about format use. --- gfx/drivers/vulkan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index a6f0e758dd..3a1e19ac7e 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -978,6 +978,7 @@ static void *vulkan_init(const video_info_t *video, vk->tex_fmt = video->rgb32 ? VK_FORMAT_B8G8R8A8_UNORM : VK_FORMAT_R5G6B5_UNORM_PACK16; vk->keep_aspect = video->force_aspect; + RARCH_LOG("[Vulkan]: Using %s format.\n", video->rgb32 ? "BGRA8888" : "RGB565"); /* Set the viewport to fix recording, since it needs to know * the viewport sizes before we start running. */ From 84923a04651b7e8667009028a5339c96e3749562 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 10:02:00 +0100 Subject: [PATCH 02/13] Vulkan: Tighten up pipeline barrier usage. Lots of memory barriers used with TOP_OF_PIPE which is not valid. --- cores/libretro-test-vulkan/libretro-test.c | 4 +-- gfx/common/vulkan_common.c | 42 ++++++++++++++++------ gfx/drivers/vulkan.c | 18 +++++----- gfx/drivers_shader/shader_vulkan.cpp | 6 ++-- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/cores/libretro-test-vulkan/libretro-test.c b/cores/libretro-test-vulkan/libretro-test.c index 898e0dcf21..64da2eea93 100644 --- a/cores/libretro-test-vulkan/libretro-test.c +++ b/cores/libretro-test-vulkan/libretro-test.c @@ -186,7 +186,7 @@ static void vulkan_test_render(void) VkImageMemoryBarrier prepare_rendering = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; prepare_rendering.srcAccessMask = 0; - prepare_rendering.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + prepare_rendering.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; prepare_rendering.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; prepare_rendering.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; prepare_rendering.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; @@ -704,7 +704,7 @@ static void context_destroy(void) static bool retro_init_hw_context(void) { hw_render.context_type = RETRO_HW_CONTEXT_VULKAN; - hw_render.version_major = VK_API_VERSION; + hw_render.version_major = VK_MAKE_VERSION(1, 0, 6); hw_render.version_minor = 0; hw_render.context_reset = context_reset; hw_render.context_destroy = context_destroy; diff --git a/gfx/common/vulkan_common.c b/gfx/common/vulkan_common.c index babe5d312d..d9bbc9ccea 100644 --- a/gfx/common/vulkan_common.c +++ b/gfx/common/vulkan_common.c @@ -135,7 +135,7 @@ void vulkan_copy_staging_to_dynamic(vk_t *vk, VkCommandBuffer cmd, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VK_PIPELINE_STAGE_TRANSFER_BIT); memset(®ion, 0, sizeof(region)); region.extent.width = dynamic->width; @@ -445,14 +445,14 @@ struct vk_texture vulkan_create_texture(vk_t *vk, vulkan_image_layout_transition(vk, staging, tmp.image, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); vulkan_image_layout_transition(vk, staging, tex.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VK_PIPELINE_STAGE_TRANSFER_BIT); memset(®ion, 0, sizeof(region)); region.extent.width = width; @@ -473,7 +473,7 @@ struct vk_texture vulkan_create_texture(vk_t *vk, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); VKFUNC(vkEndCommandBuffer)(staging); submit_info.commandBufferCount = 1; @@ -553,15 +553,35 @@ void vulkan_transition_texture(vk_t *vk, struct vk_texture *texture) /* Transition to GENERAL layout for linear streamed textures. * We're using linear textures here, so only * GENERAL layout is supported. + * If we're already in GENERAL, add a host -> shader read memory barrier + * to invalidate texture caches. */ - if (texture->layout != VK_IMAGE_LAYOUT_PREINITIALIZED) + if (texture->layout != VK_IMAGE_LAYOUT_PREINITIALIZED && + texture->layout != VK_IMAGE_LAYOUT_GENERAL) return; - vulkan_image_layout_transition(vk, vk->cmd, texture->image, - texture->layout, VK_IMAGE_LAYOUT_GENERAL, - VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + switch (texture->type) + { + case VULKAN_TEXTURE_STREAMED: + vulkan_image_layout_transition(vk, vk->cmd, texture->image, + texture->layout, VK_IMAGE_LAYOUT_GENERAL, + VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + break; + + case VULKAN_TEXTURE_STAGING: + vulkan_image_layout_transition(vk, vk->cmd, texture->image, + texture->layout, VK_IMAGE_LAYOUT_GENERAL, + VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + break; + + default: + retro_assert(0 && "Attempting to transition invalid texture type.\n"); + break; + } texture->layout = VK_IMAGE_LAYOUT_GENERAL; } diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index 3a1e19ac7e..6c028f2c1c 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -1335,7 +1335,7 @@ static void vulkan_readback(vk_t *vk) VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VK_PIPELINE_STAGE_TRANSFER_BIT); VKFUNC(vkCmdCopyImage)(vk->cmd, vk->chain->backbuffer.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, @@ -1527,12 +1527,12 @@ static bool vulkan_frame(void *data, const void *frame, rp_info.clearValueCount = 1; rp_info.pClearValues = &clear_value; - /* Prepare backbuffer for rendering */ + /* Prepare backbuffer for rendering. We don't use WSI semaphores here. */ vulkan_image_layout_transition(vk, vk->cmd, chain->backbuffer.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); /* Begin render pass and set up viewport */ VKFUNC(vkCmdBeginRenderPass)(vk->cmd, &rp_info, VK_SUBPASS_CONTENTS_INLINE); @@ -1613,10 +1613,10 @@ static bool vulkan_frame(void *data, const void *frame, chain->backbuffer.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - VK_ACCESS_TRANSFER_READ_BIT, - VK_ACCESS_MEMORY_READ_BIT, + 0, + 0, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); vk->readback.pending = false; } @@ -1628,9 +1628,9 @@ static bool vulkan_frame(void *data, const void *frame, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_ACCESS_MEMORY_READ_BIT, + 0, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); } retro_perf_start(&end_cmd); diff --git a/gfx/drivers_shader/shader_vulkan.cpp b/gfx/drivers_shader/shader_vulkan.cpp index ccd84d3822..129ac29be7 100644 --- a/gfx/drivers_shader/shader_vulkan.cpp +++ b/gfx/drivers_shader/shader_vulkan.cpp @@ -1074,9 +1074,9 @@ void Pass::build_commands( framebuffer->get_image(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_ACCESS_SHADER_READ_BIT, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); VkRenderPassBeginInfo rp_info = { From fa7eeb57b83936cf43c7b9df0f7f74b13354ec6a Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 10:03:15 +0100 Subject: [PATCH 03/13] Vulkan: Use 1.0.6. 1.0.2 is pre public release version I think ... --- gfx/common/vulkan_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gfx/common/vulkan_common.c b/gfx/common/vulkan_common.c index d9bbc9ccea..83e8fd530d 100644 --- a/gfx/common/vulkan_common.c +++ b/gfx/common/vulkan_common.c @@ -1262,7 +1262,7 @@ bool vulkan_context_init(gfx_ctx_vulkan_data_t *vk, app.applicationVersion = 0; app.pEngineName = "RetroArch"; app.engineVersion = 0; - app.apiVersion = VK_MAKE_VERSION(1, 0, 2); + app.apiVersion = VK_MAKE_VERSION(1, 0, 6); info.pApplicationInfo = &app; info.enabledExtensionCount = ARRAY_SIZE(instance_extensions); From a21687b5d7bb5763e5c8c6089d929ad45ffa1e7a Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 10:10:29 +0100 Subject: [PATCH 04/13] Vulkan: Ensure vertex caches are invalidated. --- gfx/drivers/vulkan.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index 6c028f2c1c..4e90400ff7 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -1351,6 +1351,20 @@ static void vulkan_readback(vk_t *vk) VK_PIPELINE_STAGE_HOST_BIT); } +static void vulkan_flush_vertex_caches(vk_t *vk) +{ + VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; + barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT; + + VKFUNC(vkCmdPipelineBarrier)(vk->cmd, + VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + false, + 1, &barrier, + 0, NULL, 0, NULL); +} + static bool vulkan_frame(void *data, const void *frame, unsigned frame_width, unsigned frame_height, uint64_t frame_count, @@ -1407,6 +1421,8 @@ static bool vulkan_frame(void *data, const void *frame, memset(&vk->tracker, 0, sizeof(vk->tracker)); + vulkan_flush_vertex_caches(vk); + /* Upload texture */ retro_perf_start(©_frame); if (frame && !vk->hw.enable) From 6f01b8dc2c7367f1bfce04ba09331ac58c2d64aa Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 11:22:31 +0100 Subject: [PATCH 05/13] Add spir2cross submodule. --- .gitmodules | 3 +++ Makefile.common | 9 +++++++-- deps/spir2cross | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) create mode 160000 deps/spir2cross diff --git a/.gitmodules b/.gitmodules index 9a7de532c9..49f252a8ef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "deps/glslang/glslang"] path = deps/glslang/glslang url = git://github.com/KhronosGroup/glslang.git +[submodule "deps/spir2cross"] + path = deps/spir2cross + url = git://github.com/ARM-software/spir2cross diff --git a/Makefile.common b/Makefile.common index 232ecbcb33..ec9ac1dec8 100644 --- a/Makefile.common +++ b/Makefile.common @@ -749,25 +749,30 @@ ifeq ($(HAVE_VULKAN), 1) $(wildcard deps/glslang/glslang/glslang/MachineIndependent/preprocessor/*.cpp) \ $(wildcard deps/glslang/glslang/glslang/OSDependent/$(GLSLANG_PLATFORM)/*.cpp) + SPIR2CROSS_SOURCES := deps/spir2cross/spir2cross.cpp + DEFINES += \ -Ideps/glslang/glslang/glslang/OSDependent/$(GLSLANG_PLATFORM) \ -Ideps/glslang/glslang \ -Ideps/glslang/glslang/glslang/MachineIndependent \ -Ideps/glslang/glslang/glslang/Public \ -Ideps/glslang/glslang/SPIRV \ - -Ideps/glslang + -Ideps/glslang \ + -Ideps/spir2cross CXXFLAGS += -Wno-switch -Wno-sign-compare -fno-strict-aliasing -Wno-maybe-uninitialized -Wno-reorder -I./gfx/include/vulkan CFLAGS += -I./gfx/include/vulkan GLSLANG_OBJ := $(GLSLANG_SOURCES:.cpp=.o) + SPIR2CROSS_OBJ := $(SPIR2CROSS_SOURCES:.cpp=.o) OBJ += gfx/drivers/vulkan.o \ gfx/common/vulkan_common.o \ gfx/drivers_font/vulkan_raster_font.o \ gfx/drivers_shader/shader_vulkan.o \ gfx/drivers_shader/glslang_util.o \ - $(GLSLANG_OBJ) + $(GLSLANG_OBJ) \ + $(SPIR2CROSS_OBJ) ifeq ($(HAVE_MENU_COMMON), 1) OBJ += menu/drivers_display/menu_display_vulkan.o endif diff --git a/deps/spir2cross b/deps/spir2cross new file mode 160000 index 0000000000..0ae2bcc3d0 --- /dev/null +++ b/deps/spir2cross @@ -0,0 +1 @@ +Subproject commit 0ae2bcc3d0edc60e03180f6080a168f78edc82ca From 840278796e0edfa035c1d8104862da52a5673385 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 11:45:59 +0100 Subject: [PATCH 06/13] Vulkan: Refactor out common resources in filter chain. --- gfx/drivers_shader/shader_vulkan.cpp | 114 ++++++++++++++++----------- 1 file changed, 70 insertions(+), 44 deletions(-) diff --git a/gfx/drivers_shader/shader_vulkan.cpp b/gfx/drivers_shader/shader_vulkan.cpp index 129ac29be7..9336a4b183 100644 --- a/gfx/drivers_shader/shader_vulkan.cpp +++ b/gfx/drivers_shader/shader_vulkan.cpp @@ -175,6 +175,18 @@ class Framebuffer void init_render_pass(); }; +struct CommonResources +{ + CommonResources(VkDevice device, + const VkPhysicalDeviceMemoryProperties &memory_properties); + ~CommonResources(); + + unique_ptr vbo_offscreen, vbo_final; + VkSampler samplers[2]; + + VkDevice device; +}; + class Pass { public: @@ -228,6 +240,11 @@ class Pass return pass_info.source_filter; } + void set_common_resources(const CommonResources &common) + { + this->common = &common; + } + private: struct UBO { @@ -251,11 +268,10 @@ class Pass VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; VkDescriptorSetLayout set_layout = VK_NULL_HANDLE; VkDescriptorPool pool = VK_NULL_HANDLE; - VkSampler samplers[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; vector> ubos; - unique_ptr vbo; vector sets; + const CommonResources *common = nullptr; Size2D current_framebuffer_size; VkViewport current_viewport; @@ -270,7 +286,6 @@ class Pass bool init_pipeline(); bool init_pipeline_layout(); bool init_buffers(); - bool init_samplers(); void image_layout_transition(VkDevice device, VkCommandBuffer cmd, VkImage image, @@ -330,6 +345,7 @@ struct vulkan_filter_chain vector> passes; vector pass_info; vector>> deferred_calls; + CommonResources common; vulkan_filter_chain_texture input_texture; @@ -351,7 +367,8 @@ vulkan_filter_chain::vulkan_filter_chain( const vulkan_filter_chain_create_info &info) : device(info.device), memory_properties(*info.memory_properties), - cache(info.pipeline_cache) + cache(info.pipeline_cache), + common(info.device, *info.memory_properties) { max_input_size = { info.max_input_size.width, info.max_input_size.height }; set_swapchain_info(info.swapchain); @@ -397,6 +414,7 @@ void vulkan_filter_chain::set_num_passes(unsigned num_passes) { passes.emplace_back(new Pass(device, memory_properties, cache, deferred_calls.size(), i + 1 == num_passes)); + passes.back()->set_common_resources(common); } } @@ -660,18 +678,10 @@ void Pass::clear_vk() if (pipeline_layout != VK_NULL_HANDLE) VKFUNC(vkDestroyPipelineLayout)(device, pipeline_layout, nullptr); - for (auto &samp : samplers) - { - if (samp != VK_NULL_HANDLE) - VKFUNC(vkDestroySampler)(device, samp, nullptr); - samp = VK_NULL_HANDLE; - } - pool = VK_NULL_HANDLE; pipeline = VK_NULL_HANDLE; set_layout = VK_NULL_HANDLE; ubos.clear(); - vbo.reset(); } bool Pass::init_pipeline_layout() @@ -869,8 +879,40 @@ bool Pass::init_pipeline() return true; } -bool Pass::init_samplers() +CommonResources::CommonResources(VkDevice device, + const VkPhysicalDeviceMemoryProperties &memory_properties) + : device(device) { + // The final pass uses an MVP designed for [0, 1] range VBO. + // For in-between passes, we just go with identity matrices, so keep it simple. + const float vbo_data_offscreen[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + -1.0f, +1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + 1.0f, +1.0f, 1.0f, 1.0f, + }; + + const float vbo_data_final[] = { + 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, +1.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, +1.0f, 1.0f, 1.0f, + }; + + vbo_offscreen = unique_ptr(new Buffer(device, + memory_properties, sizeof(vbo_data_offscreen), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)); + + void *ptr = vbo_offscreen->map(); + memcpy(ptr, vbo_data_offscreen, sizeof(vbo_data_offscreen)); + vbo_offscreen->unmap(); + + vbo_final = unique_ptr(new Buffer(device, + memory_properties, sizeof(vbo_data_final), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)); + + ptr = vbo_final->map(); + memcpy(ptr, vbo_data_final, sizeof(vbo_data_final)); + vbo_final->unmap(); + VkSamplerCreateInfo info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; info.magFilter = VK_FILTER_NEAREST; info.minFilter = VK_FILTER_NEAREST; @@ -886,44 +928,29 @@ bool Pass::init_samplers() info.unnormalizedCoordinates = false; info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - if (VKFUNC(vkCreateSampler)(device, - &info, NULL, &samplers[VULKAN_FILTER_CHAIN_NEAREST]) != VK_SUCCESS) - return false; + VKFUNC(vkCreateSampler)(device, + &info, nullptr, &samplers[VULKAN_FILTER_CHAIN_NEAREST]); info.magFilter = VK_FILTER_LINEAR; info.minFilter = VK_FILTER_LINEAR; - if (VKFUNC(vkCreateSampler)(device, - &info, NULL, &samplers[VULKAN_FILTER_CHAIN_LINEAR]) != VK_SUCCESS) - return false; + VKFUNC(vkCreateSampler)(device, + &info, nullptr, &samplers[VULKAN_FILTER_CHAIN_LINEAR]); +} - return true; +CommonResources::~CommonResources() +{ + for (auto &samp : samplers) + if (samp != VK_NULL_HANDLE) + VKFUNC(vkDestroySampler)(device, samp, nullptr); } bool Pass::init_buffers() { - unsigned i; - // The final pass uses an MVP designed for [0, 1] range VBO. - // For in-between passes, we just go with identity matrices, so keep it simple. - float pos_min = final_pass ? 0.0f : -1.0f; - - const float vbo_data[] = { - pos_min, pos_min, 0.0f, 0.0f, - pos_min, +1.0f, 0.0f, 1.0f, - 1.0f, pos_min, 1.0f, 0.0f, - 1.0f, +1.0f, 1.0f, 1.0f, - }; - ubos.clear(); - for (i = 0; i < num_sync_indices; i++) + 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)); - vbo = unique_ptr(new Buffer(device, - memory_properties, sizeof(vbo_data), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)); - - void *ptr = vbo->map(); - memcpy(ptr, vbo_data, sizeof(vbo_data)); - vbo->unmap(); return true; } @@ -943,9 +970,6 @@ bool Pass::build() if (!init_buffers()) return false; - if (!init_samplers()) - return false; - return true; } @@ -1001,7 +1025,7 @@ void Pass::set_texture(VkDescriptorSet set, unsigned binding, const Texture &texture) { VkDescriptorImageInfo image_info; - image_info.sampler = samplers[texture.filter]; + image_info.sampler = common->samplers[texture.filter]; image_info.imageView = texture.texture.view; image_info.imageLayout = texture.texture.layout; @@ -1101,7 +1125,9 @@ void Pass::build_commands( 0, 1, &sets[sync_index], 0, nullptr); VkDeviceSize offset = 0; - VKFUNC(vkCmdBindVertexBuffers)(cmd, 0, 1, &vbo->get_buffer(), &offset); + VKFUNC(vkCmdBindVertexBuffers)(cmd, 0, 1, + final_pass ? &common->vbo_final->get_buffer() : &common->vbo_offscreen->get_buffer(), + &offset); if (final_pass) { From 7899d449e6526ca0947bece677894f0617eb82c3 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 11:49:27 +0100 Subject: [PATCH 07/13] Vulkan: Also invalidate fragment uniform buffer --- gfx/drivers/vulkan.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index 4e90400ff7..d5e4e786a6 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -1351,7 +1351,7 @@ static void vulkan_readback(vk_t *vk) VK_PIPELINE_STAGE_HOST_BIT); } -static void vulkan_flush_vertex_caches(vk_t *vk) +static void vulkan_flush_caches(vk_t *vk) { VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; @@ -1359,7 +1359,9 @@ static void vulkan_flush_vertex_caches(vk_t *vk) VKFUNC(vkCmdPipelineBarrier)(vk->cmd, VK_PIPELINE_STAGE_HOST_BIT, - VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, false, 1, &barrier, 0, NULL, 0, NULL); @@ -1421,7 +1423,7 @@ static bool vulkan_frame(void *data, const void *frame, memset(&vk->tracker, 0, sizeof(vk->tracker)); - vulkan_flush_vertex_caches(vk); + vulkan_flush_caches(vk); /* Upload texture */ retro_perf_start(©_frame); From 5dcb78ea212a35cade91ff6446095c8a44555f3d Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 11:56:09 +0100 Subject: [PATCH 08/13] Vulkan: Include spir2cross in shader_vulkan.cpp. --- gfx/drivers_shader/shader_vulkan.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gfx/drivers_shader/shader_vulkan.cpp b/gfx/drivers_shader/shader_vulkan.cpp index 9336a4b183..df05461e08 100644 --- a/gfx/drivers_shader/shader_vulkan.cpp +++ b/gfx/drivers_shader/shader_vulkan.cpp @@ -25,8 +25,10 @@ #include "../drivers/vulkan_shaders/opaque.frag.inc" #include "../video_shader_driver.h" #include "../../verbosity.h" +#include "spir2cross.hpp" using namespace std; +using namespace spir2cross; static uint32_t find_memory_type( const VkPhysicalDeviceMemoryProperties &mem_props, From 72cdd37ad295c9bdc5021228d44eb37ae265b7a6 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 12:14:11 +0100 Subject: [PATCH 09/13] Vulkan: Add #include support to slang shaders. --- gfx/drivers_shader/glslang_util.cpp | 38 ++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/gfx/drivers_shader/glslang_util.cpp b/gfx/drivers_shader/glslang_util.cpp index 5a1103cdee..3e83336578 100644 --- a/gfx/drivers_shader/glslang_util.cpp +++ b/gfx/drivers_shader/glslang_util.cpp @@ -24,14 +24,16 @@ #include "glslang.hpp" #include "../../general.h" +#include "../../libretro-common/include/file/file_path.h" using namespace std; -bool read_file(const char *path, vector *output) +static bool read_shader_file(const char *path, vector *output) { - char *buf = nullptr; + char *buf = nullptr; ssize_t len = 0; struct string_list *list = NULL; + char include_path[PATH_MAX]; if (retro_read_file(path, (void**)&buf, &len) < 0) { @@ -54,15 +56,39 @@ bool read_file(const char *path, vector *output) return false; } - output->clear(); for (size_t i = 0; i < list->size; i++) - output->push_back(list->elems[i].data); + { + const char *line = list->elems[i].data; + if (strstr(line, "#include ") == line) + { + char *c = (char*)strchr(line, '"'); + if (!c) + { + RARCH_ERR("Invalid include statement \"%s\".\n", line); + return false; + } + c++; + char *closing = (char*)strchr(c, '"'); + if (!closing) + { + RARCH_ERR("Invalid include statement \"%s\".\n", line); + return false; + } + *closing = '\0'; + fill_pathname_resolve_relative(include_path, path, c, sizeof(include_path)); + + if (!read_shader_file(include_path, output)) + return false; + } + else + output->push_back(line); + } string_list_free(list); return true; } -string build_stage_source(const vector &lines, const char *stage) +static string build_stage_source(const vector &lines, const char *stage) { ostringstream str; bool active = true; @@ -99,7 +125,7 @@ bool glslang_compile_shader(const char *shader_path, glslang_output *output) vector lines; RARCH_LOG("Compiling shader \"%s\".\n", shader_path); - if (!read_file(shader_path, &lines)) + if (!read_shader_file(shader_path, &lines)) return false; auto &header = lines.front(); From 1822f3bf90dc549b9c7f276077d83acfbe03fa2d Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 15:12:12 +0100 Subject: [PATCH 10/13] 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 + From dce96d08698f666c246913ff4faa821ce11213ac Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 15:31:01 +0100 Subject: [PATCH 11/13] Vulkan: Find OutputSize semantic uniform as well. --- gfx/drivers_shader/slang_reflection.cpp | 72 +++++++++++++++++-------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/gfx/drivers_shader/slang_reflection.cpp b/gfx/drivers_shader/slang_reflection.cpp index e4c43ffb27..b10db60ea9 100644 --- a/gfx/drivers_shader/slang_reflection.cpp +++ b/gfx/drivers_shader/slang_reflection.cpp @@ -49,12 +49,41 @@ static bool find_uniform_offset(const Compiler &compiler, const Resource &resour return false; } +static bool find_semantic_uniform(slang_reflection *reflection, slang_texture_semantic index, + const Compiler &compiler, const Resource &resource, const char *name) +{ + unsigned member_index = 0; + auto &semantic = reflection->semantic_textures[index]; + + // TODO: Do we want to expose Size uniforms if no stage is using the texture? + if (find_uniform_offset(compiler, resource, name, + &semantic.ubo_offset, &member_index)) + { + auto &type = compiler.get_type( + compiler.get_type(resource.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; +} + 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() || @@ -142,19 +171,34 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm fragment_compiler.get_declared_struct_size(fragment_compiler.get_type(fragment.uniform_buffers[0].type_id))); } + // Find two magic uniforms, MVP and OutputSize. + unsigned member_index = 0; 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; } + if (!find_semantic_uniform(reflection, SLANG_TEXTURE_SEMANTIC_OUTPUT, + fragment_compiler, fragment.uniform_buffers[0], + "OutputSize")) + { + // OutputSize isn't backed by an actual texture. + find_semantic_uniform(reflection, SLANG_TEXTURE_SEMANTIC_OUTPUT, + vertex_compiler, vertex.uniform_buffers[0], + "OutputSize"); + } + //// + 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); + unsigned set = fragment_compiler.get_decoration(texture.id, + spv::DecorationDescriptorSet); + unsigned binding = fragment_compiler.get_decoration(texture.id, + spv::DecorationBinding); if (set != 0) { @@ -188,25 +232,11 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm 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; - } + find_semantic_uniform(reflection, index, + fragment_compiler, fragment.uniform_buffers[0], + uniform_name); } return true; From 6add83e3ec790cb7a004f4bf845191c62430f6ed Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 16:18:28 +0100 Subject: [PATCH 12/13] Vulkan: Use reflection output for source/original. Fix some logging messages in reflection. --- gfx/drivers_shader/shader_vulkan.cpp | 67 ++++++++++++------ gfx/drivers_shader/slang_reflection.cpp | 94 +++++++++++++++++-------- 2 files changed, 112 insertions(+), 49 deletions(-) diff --git a/gfx/drivers_shader/shader_vulkan.cpp b/gfx/drivers_shader/shader_vulkan.cpp index 178b11af00..2b094cb822 100644 --- a/gfx/drivers_shader/shader_vulkan.cpp +++ b/gfx/drivers_shader/shader_vulkan.cpp @@ -293,6 +293,9 @@ class Pass void set_texture(VkDescriptorSet set, unsigned binding, const Texture &texture); + void set_semantic_texture(VkDescriptorSet set, slang_texture_semantic semantic, + const Texture &texture); + void set_uniform_buffer(VkDescriptorSet set, unsigned binding, VkBuffer buffer, VkDeviceSize offset, @@ -683,26 +686,40 @@ void Pass::clear_vk() bool Pass::init_pipeline_layout() { - unsigned i; vector bindings; vector desc_counts; - // TODO: Expand this a lot, need to reflect shaders to figure this out. - bindings.push_back({ 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, - VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, - nullptr }); - - bindings.push_back({ 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, - VK_SHADER_STAGE_FRAGMENT_BIT, - nullptr }); - - bindings.push_back({ 2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, - VK_SHADER_STAGE_FRAGMENT_BIT, - nullptr }); + // Main UBO. + VkShaderStageFlags ubo_mask = 0; + if (reflection.ubo_stage_mask & SLANG_STAGE_VERTEX_MASK) + ubo_mask |= VK_SHADER_STAGE_VERTEX_BIT; + if (reflection.ubo_stage_mask & SLANG_STAGE_FRAGMENT_MASK) + ubo_mask |= VK_SHADER_STAGE_FRAGMENT_BIT; + bindings.push_back({ reflection.ubo_binding, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + ubo_mask, nullptr }); desc_counts.push_back({ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, num_sync_indices }); - desc_counts.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, num_sync_indices }); - desc_counts.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, num_sync_indices }); + + // Semantic textures. + for (unsigned i = 0; i < 32; i++) + { + if (reflection.semantic_texture_mask & (1u << i)) + { + auto &texture = reflection.semantic_textures[i]; + + VkShaderStageFlags stages = 0; + if (texture.stage_mask & SLANG_STAGE_VERTEX_MASK) + stages |= VK_SHADER_STAGE_VERTEX_BIT; + if (texture.stage_mask & SLANG_STAGE_FRAGMENT_MASK) + stages |= VK_SHADER_STAGE_FRAGMENT_BIT; + + bindings.push_back({ texture.binding, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, + stages, nullptr }); + desc_counts.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, num_sync_indices }); + } + } VkDescriptorSetLayoutCreateInfo set_layout_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; @@ -738,7 +755,7 @@ bool Pass::init_pipeline_layout() sets.resize(num_sync_indices); - for (i = 0; i < num_sync_indices; i++) + for (unsigned i = 0; i < num_sync_indices; i++) VKFUNC(vkAllocateDescriptorSets)(device, &alloc_info, &sets[i]); return true; @@ -961,13 +978,13 @@ bool Pass::build() pass_info.rt_format)); } - if (!init_pipeline()) - return false; - memset(&reflection, 0, sizeof(reflection)); if (!slang_reflect_spirv(vertex_shader, fragment_shader, &reflection)) return false; + if (!init_pipeline()) + return false; + if (!init_buffers()) return false; @@ -1040,14 +1057,22 @@ void Pass::set_texture(VkDescriptorSet set, unsigned binding, VKFUNC(vkUpdateDescriptorSets)(device, 1, &write, 0, nullptr); } +void Pass::set_semantic_texture(VkDescriptorSet set, + slang_texture_semantic semantic, const Texture &texture) +{ + if (reflection.semantic_texture_mask & (1u << semantic)) + set_texture(set, reflection.semantic_textures[semantic].binding, texture); +} + void Pass::update_descriptor_set( const Texture &original, const Texture &source) { set_uniform_buffer(sets[sync_index], 0, ubos[sync_index]->get_buffer(), 0, reflection.ubo_size); - set_texture(sets[sync_index], 1, original); - set_texture(sets[sync_index], 2, source); + + set_semantic_texture(sets[sync_index], SLANG_TEXTURE_SEMANTIC_ORIGINAL, original); + set_semantic_texture(sets[sync_index], SLANG_TEXTURE_SEMANTIC_SOURCE, source); } void Pass::build_semantic_vec4(uint8_t *data, slang_texture_semantic semantic, unsigned width, unsigned height) diff --git a/gfx/drivers_shader/slang_reflection.cpp b/gfx/drivers_shader/slang_reflection.cpp index b10db60ea9..c37d616fb0 100644 --- a/gfx/drivers_shader/slang_reflection.cpp +++ b/gfx/drivers_shader/slang_reflection.cpp @@ -50,17 +50,22 @@ static bool find_uniform_offset(const Compiler &compiler, const Resource &resour } static bool find_semantic_uniform(slang_reflection *reflection, slang_texture_semantic index, - const Compiler &compiler, const Resource &resource, const char *name) + const Compiler &vertex_compiler, const vector &vertex_resources, + const Compiler &fragment_compiler, const vector &fragment_resources, + const char *name) { unsigned member_index = 0; auto &semantic = reflection->semantic_textures[index]; + size_t vertex_ubo_offset = size_t(-1); + size_t fragment_ubo_offset = size_t(-1); + // TODO: Do we want to expose Size uniforms if no stage is using the texture? - if (find_uniform_offset(compiler, resource, name, - &semantic.ubo_offset, &member_index)) + if (find_uniform_offset(vertex_compiler, vertex_resources[0], name, + &vertex_ubo_offset, &member_index)) { - auto &type = compiler.get_type( - compiler.get_type(resource.type_id).member_types[member_index]); + auto &type = vertex_compiler.get_type( + vertex_compiler.get_type(vertex_resources[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 && @@ -70,13 +75,46 @@ static bool find_semantic_uniform(slang_reflection *reflection, slang_texture_se if (!is_vec4) { - RARCH_ERR("Semantic uniform is not vec4.\n"); + RARCH_ERR("[slang]: Semantic uniform is not vec4.\n"); return false; } reflection->semantic_texture_ubo_mask |= 1 << index; } + if (!fragment_resources.empty() && + find_uniform_offset(fragment_compiler, fragment_resources[0], name, + &fragment_ubo_offset, &member_index)) + { + auto &type = fragment_compiler.get_type( + fragment_compiler.get_type(fragment_resources[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("[slang]: Semantic uniform is not vec4.\n"); + return false; + } + + reflection->semantic_texture_ubo_mask |= 1 << index; + } + + // Check for UBO offset mismatch between stages. + if (vertex_ubo_offset != size_t(-1) && + fragment_ubo_offset != size_t(-1) && + vertex_ubo_offset != fragment_ubo_offset) + { + RARCH_ERR("[slang]: Vertex (%u) and fragment (%u) UBO offset for %s mismatches.\n", + unsigned(vertex_ubo_offset), unsigned(fragment_ubo_offset), name); + return false; + } + + semantic.ubo_offset = vertex_ubo_offset; return true; } @@ -98,14 +136,14 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm !fragment.atomic_counters.empty() || !fragment.push_constant_buffers.empty()) { - RARCH_ERR("Invalid resource type detected.\n"); + RARCH_ERR("[slang]: Invalid resource type detected.\n"); return false; } // Validate vertex input. if (vertex.stage_inputs.size() != 2) { - RARCH_ERR("Vertex must have two attributes.\n"); + RARCH_ERR("[slang]: Vertex must have two attributes.\n"); return false; } @@ -115,33 +153,33 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm if (location_mask != 0x3) { - RARCH_ERR("The two vertex attributes do not use location = 0 and location = 1.\n"); + RARCH_ERR("[slang]: 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"); + RARCH_ERR("[slang]: 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"); + RARCH_ERR("[slang]: 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"); + RARCH_ERR("[slang]: 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"); + RARCH_ERR("[slang]: Resources must use descriptor set #0.\n"); return false; } @@ -150,16 +188,17 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm 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"); + RARCH_ERR("[slang]: 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); + RARCH_ERR("[slang]: Binding %u is out of range.\n", ubo_binding); return false; } + reflection->ubo_binding = ubo_binding; 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)); @@ -175,18 +214,16 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm unsigned member_index = 0; 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"); + RARCH_ERR("[slang]: Could not find offset for MVP matrix.\n"); return false; } if (!find_semantic_uniform(reflection, SLANG_TEXTURE_SEMANTIC_OUTPUT, - fragment_compiler, fragment.uniform_buffers[0], + vertex_compiler, vertex.uniform_buffers, + fragment_compiler, fragment.uniform_buffers, "OutputSize")) { - // OutputSize isn't backed by an actual texture. - find_semantic_uniform(reflection, SLANG_TEXTURE_SEMANTIC_OUTPUT, - vertex_compiler, vertex.uniform_buffers[0], - "OutputSize"); + return false; } //// @@ -202,19 +239,19 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm if (set != 0) { - RARCH_ERR("Resources must use descriptor set #0.\n"); + RARCH_ERR("[slang]: 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); + RARCH_ERR("[slang]: 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); + RARCH_ERR("[slang]: Binding %u is already in use.\n", binding); return false; } binding_mask |= 1 << binding; @@ -223,7 +260,7 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm if (index == SLANG_TEXTURE_INVALID_SEMANTIC) { - RARCH_ERR("Non-semantic textures not supported.\n"); + RARCH_ERR("[slang]: Non-semantic textures not supported.\n"); return false; } @@ -235,7 +272,8 @@ static bool slang_reflect(const Compiler &vertex_compiler, const Compiler &fragm char uniform_name[128]; snprintf(uniform_name, sizeof(uniform_name), "%sSize", texture.name.c_str()); find_semantic_uniform(reflection, index, - fragment_compiler, fragment.uniform_buffers[0], + vertex_compiler, vertex.uniform_buffers, + fragment_compiler, fragment.uniform_buffers, uniform_name); } @@ -257,7 +295,7 @@ bool slang_reflect_spirv(const std::vector &vertex, vertex_resources, fragment_resources, reflection)) { - RARCH_ERR("Failed to reflect SPIR-V. Resource usage is inconsistent with expectations.\n"); + RARCH_ERR("[slang]: Failed to reflect SPIR-V. Resource usage is inconsistent with expectations.\n"); return false; } @@ -265,7 +303,7 @@ bool slang_reflect_spirv(const std::vector &vertex, } catch (const std::exception &e) { - RARCH_ERR("spir2cross threw exception: %s.\n", e.what()); + RARCH_ERR("[slang]: spir2cross threw exception: %s.\n", e.what()); return false; } } From f1a5725f6688e868e827e475fe6052e47f8e4bf9 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Mon, 21 Mar 2016 16:57:04 +0100 Subject: [PATCH 13/13] Vulkan: Host writes are implicitly flushed on submit. Use TOP_OF_PIPE/0 access instead to work around spammy asserts on Anvil. We still have to invalidate the caches however. --- gfx/common/vulkan_common.c | 12 ++++++------ gfx/drivers/vulkan.c | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gfx/common/vulkan_common.c b/gfx/common/vulkan_common.c index 83e8fd530d..fd3ff0af55 100644 --- a/gfx/common/vulkan_common.c +++ b/gfx/common/vulkan_common.c @@ -444,8 +444,8 @@ struct vk_texture vulkan_create_texture(vk_t *vk, vulkan_image_layout_transition(vk, staging, tmp.image, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_GENERAL, - VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, - VK_PIPELINE_STAGE_HOST_BIT, + 0, VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); vulkan_image_layout_transition(vk, staging, tex.image, @@ -565,16 +565,16 @@ void vulkan_transition_texture(vk_t *vk, struct vk_texture *texture) case VULKAN_TEXTURE_STREAMED: vulkan_image_layout_transition(vk, vk->cmd, texture->image, texture->layout, VK_IMAGE_LAYOUT_GENERAL, - VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - VK_PIPELINE_STAGE_HOST_BIT, + 0, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); break; case VULKAN_TEXTURE_STAGING: vulkan_image_layout_transition(vk, vk->cmd, texture->image, texture->layout, VK_IMAGE_LAYOUT_GENERAL, - VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, - VK_PIPELINE_STAGE_HOST_BIT, + 0, VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); break; diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index d5e4e786a6..013967a7e3 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -1354,11 +1354,11 @@ static void vulkan_readback(vk_t *vk) static void vulkan_flush_caches(vk_t *vk) { VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER }; - barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT; VKFUNC(vkCmdPipelineBarrier)(vk->cmd, - VK_PIPELINE_STAGE_HOST_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,