/* RetroArch - A frontend for libretro. * Copyright (C) 2016-2017 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #ifdef HAVE_MENU #include "../../menu/menu_driver.h" #endif #ifdef HAVE_GFX_WIDGETS #include "../gfx_widgets.h" #endif #include "../font_driver.h" #include "../video_driver.h" #include "../common/vulkan_common.h" #include "../../configuration.h" #ifdef HAVE_REWIND #include "../../state_manager.h" #endif #include "../../record/record_driver.h" #include "../../retroarch.h" #include "../../verbosity.h" #define VK_REMAP_TO_TEXFMT(fmt) ((fmt == VK_FORMAT_R5G6B5_UNORM_PACK16) ? VK_FORMAT_R8G8B8A8_UNORM : fmt) typedef struct { vk_t *vk; void *font_data; struct font_atlas *atlas; const font_renderer_driver_t *font_driver; struct vk_vertex *pv; struct vk_texture texture; struct vk_texture texture_optimal; struct vk_buffer_range range; unsigned vertices; bool needs_update; } vulkan_raster_t; #ifdef VULKAN_DEBUG_TEXTURE_ALLOC static VkImage vk_images[4 * 1024]; static unsigned vk_count; static unsigned track_seq; #endif /* * VULKAN COMMON */ #ifdef VULKAN_DEBUG_TEXTURE_ALLOC #if 0 void vulkan_log_textures(void) { unsigned i; for (i = 0; i < vk_count; i++) { RARCH_WARN("[Vulkan]: Found leaked texture %llu.\n", (unsigned long long)vk_images[i]); } vk_count = 0; } #endif static void vulkan_track_alloc(VkImage image) { vk_images[vk_count++] = image; RARCH_LOG("[Vulkan]: Alloc %llu (%u).\n", (unsigned long long)image, track_seq); track_seq++; } static void vulkan_track_dealloc(VkImage image) { unsigned i; for (i = 0; i < vk_count; i++) { if (image == vk_images[i]) { vk_count--; memmove(vk_images + i, vk_images + 1 + i, sizeof(VkImage) * (vk_count - i)); return; } } retro_assert(0 && "Couldn't find VkImage in dealloc!"); } #endif static INLINE unsigned vulkan_format_to_bpp(VkFormat format) { switch (format) { case VK_FORMAT_B8G8R8A8_UNORM: return 4; case VK_FORMAT_R4G4B4A4_UNORM_PACK16: case VK_FORMAT_B4G4R4A4_UNORM_PACK16: case VK_FORMAT_R5G6B5_UNORM_PACK16: return 2; case VK_FORMAT_R8_UNORM: return 1; default: /* Unknown format */ break; } return 0; } static unsigned vulkan_num_miplevels(unsigned width, unsigned height) { unsigned size = MAX(width, height); unsigned levels = 0; while (size) { levels++; size >>= 1; } return levels; } static void vulkan_write_quad_descriptors( VkDevice device, VkDescriptorSet set, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, const struct vk_texture *texture, VkSampler sampler) { VkWriteDescriptorSet write; VkDescriptorBufferInfo buffer_info; buffer_info.buffer = buffer; buffer_info.offset = offset; buffer_info.range = range; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write.pNext = NULL; write.dstSet = set; write.dstBinding = 0; write.dstArrayElement = 0; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; write.pImageInfo = NULL; write.pBufferInfo = &buffer_info; write.pTexelBufferView = NULL; vkUpdateDescriptorSets(device, 1, &write, 0, NULL); if (texture) { VkDescriptorImageInfo image_info; image_info.sampler = sampler; image_info.imageView = texture->view; image_info.imageLayout = texture->layout; write.dstSet = set; write.dstBinding = 1; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &image_info; vkUpdateDescriptorSets(device, 1, &write, 0, NULL); } } static void vulkan_transition_texture(vk_t *vk, VkCommandBuffer cmd, 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) && (texture->layout != VK_IMAGE_LAYOUT_GENERAL)) return; switch (texture->type) { case VULKAN_TEXTURE_STREAMED: VULKAN_IMAGE_LAYOUT_TRANSITION(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; default: retro_assert(0 && "Attempting to transition invalid texture type.\n"); break; } texture->layout = VK_IMAGE_LAYOUT_GENERAL; } /* The VBO needs to be written to before calling this. * Use vulkan_buffer_chain_alloc. */ static void vulkan_draw_triangles(vk_t *vk, const struct vk_draw_triangles *call) { if (call->texture && call->texture->image) vulkan_transition_texture(vk, vk->cmd, call->texture); if (call->pipeline != vk->tracker.pipeline) { VkRect2D sci; vkCmdBindPipeline(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, call->pipeline); vk->tracker.pipeline = call->pipeline; /* Changing pipeline invalidates dynamic state. */ vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; if (vk->flags & VK_FLAG_TRACKER_USE_SCISSOR) sci = vk->tracker.scissor; else { /* No scissor -> viewport */ sci.offset.x = vk->vp.x; sci.offset.y = vk->vp.y; sci.extent.width = vk->vp.width; sci.extent.height = vk->vp.height; } vkCmdSetViewport(vk->cmd, 0, 1, &vk->vk_vp); vkCmdSetScissor (vk->cmd, 0, 1, &sci); vk->tracker.dirty &= ~VULKAN_DIRTY_DYNAMIC_BIT; } else if (vk->tracker.dirty & VULKAN_DIRTY_DYNAMIC_BIT) { VkRect2D sci; if (vk->flags & VK_FLAG_TRACKER_USE_SCISSOR) sci = vk->tracker.scissor; else { /* No scissor -> viewport */ sci.offset.x = vk->vp.x; sci.offset.y = vk->vp.y; sci.extent.width = vk->vp.width; sci.extent.height = vk->vp.height; } vkCmdSetViewport(vk->cmd, 0, 1, &vk->vk_vp); vkCmdSetScissor (vk->cmd, 0, 1, &sci); vk->tracker.dirty &= ~VULKAN_DIRTY_DYNAMIC_BIT; } /* Upload descriptors */ { VkDescriptorSet set; /* Upload UBO */ struct vk_buffer_range range; float *mvp_data_ptr = NULL; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->ubo, call->uniform_size, &range)) return; memcpy(range.data, call->uniform, call->uniform_size); set = vulkan_descriptor_manager_alloc( vk->context->device, &vk->chain->descriptor_manager); vulkan_write_quad_descriptors( vk->context->device, set, range.buffer, range.offset, call->uniform_size, call->texture, call->sampler); vkCmdBindDescriptorSets(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vk->pipelines.layout, 0, 1, &set, 0, NULL); vk->tracker.view = VK_NULL_HANDLE; vk->tracker.sampler = VK_NULL_HANDLE; for ( mvp_data_ptr = &vk->tracker.mvp.data[0] ; mvp_data_ptr < vk->tracker.mvp.data + 16 ; mvp_data_ptr++) *mvp_data_ptr = 0.0f; } /* VBO is already uploaded. */ vkCmdBindVertexBuffers(vk->cmd, 0, 1, &call->vbo->buffer, &call->vbo->offset); /* Draw the quad */ vkCmdDraw(vk->cmd, call->vertices, 1, 0, 0); } static void vulkan_destroy_texture( VkDevice device, struct vk_texture *tex) { if (tex->mapped) vkUnmapMemory(device, tex->memory); if (tex->view) vkDestroyImageView(device, tex->view, NULL); if (tex->image) vkDestroyImage(device, tex->image, NULL); if (tex->buffer) vkDestroyBuffer(device, tex->buffer, NULL); if (tex->memory) vkFreeMemory(device, tex->memory, NULL); #ifdef VULKAN_DEBUG_TEXTURE_ALLOC if (tex->image) vulkan_track_dealloc(tex->image); #endif tex->type = VULKAN_TEXTURE_STREAMED; tex->flags = 0; tex->memory_type = 0; tex->width = 0; tex->height = 0; tex->offset = 0; tex->stride = 0; tex->size = 0; tex->mapped = NULL; tex->image = VK_NULL_HANDLE; tex->view = VK_NULL_HANDLE; tex->memory = VK_NULL_HANDLE; tex->buffer = VK_NULL_HANDLE; tex->format = VK_FORMAT_UNDEFINED; tex->memory_size = 0; tex->layout = VK_IMAGE_LAYOUT_UNDEFINED; } static struct vk_texture vulkan_create_texture(vk_t *vk, struct vk_texture *old, unsigned width, unsigned height, VkFormat format, const void *initial, const VkComponentMapping *swizzle, enum vk_texture_type type) { unsigned i; uint32_t buffer_width; struct vk_texture tex; VkImageCreateInfo info; VkFormat remap_tex_fmt; VkMemoryRequirements mem_reqs; VkSubresourceLayout layout; VkMemoryAllocateInfo alloc; VkBufferCreateInfo buffer_info; VkDevice device = vk->context->device; VkImageSubresource subresource = { VK_IMAGE_ASPECT_COLOR_BIT }; memset(&tex, 0, sizeof(tex)); info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.pNext = NULL; info.flags = 0; info.imageType = VK_IMAGE_TYPE_2D; info.format = format; info.extent.width = width; info.extent.height = height; info.extent.depth = 1; info.mipLevels = 1; info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = 0; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.queueFamilyIndexCount = 0; info.pQueueFamilyIndices = NULL; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; /* Align stride to 4 bytes to make sure we can use compute shader uploads without too many problems. */ buffer_width = width * vulkan_format_to_bpp(format); buffer_width = (buffer_width + 3u) & ~3u; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.pNext = NULL; buffer_info.flags = 0; buffer_info.size = buffer_width * height; buffer_info.usage = 0; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; buffer_info.queueFamilyIndexCount = 0; buffer_info.pQueueFamilyIndices = NULL; remap_tex_fmt = VK_REMAP_TO_TEXFMT(format); /* Compatibility concern. Some Apple hardware does not support rgb565. * Use compute shader uploads instead. * If we attempt to use streamed texture, force staging path. * If we're creating fallback dynamic texture, force RGBA8888. */ if (remap_tex_fmt != format) { if (type == VULKAN_TEXTURE_STREAMED) type = VULKAN_TEXTURE_STAGING; else if (type == VULKAN_TEXTURE_DYNAMIC) { format = remap_tex_fmt; info.format = format; info.usage |= VK_IMAGE_USAGE_STORAGE_BIT; } } if (type == VULKAN_TEXTURE_STREAMED) { VkFormatProperties format_properties; const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; vkGetPhysicalDeviceFormatProperties( vk->context->gpu, format, &format_properties); if ((format_properties.linearTilingFeatures & required) != required) { #ifdef VULKAN_DEBUG RARCH_DBG("[Vulkan]: GPU does not support using linear images as textures. Falling back to copy path.\n"); #endif type = VULKAN_TEXTURE_STAGING; } } switch (type) { case VULKAN_TEXTURE_STATIC: /* For simplicity, always build mipmaps for * static textures, samplers can be used to enable it dynamically. */ info.mipLevels = vulkan_num_miplevels(width, height); tex.flags |= VK_TEX_FLAG_MIPMAP; retro_assert(initial && "Static textures must have initial data.\n"); info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; break; case VULKAN_TEXTURE_DYNAMIC: retro_assert(!initial && "Dynamic textures must not have initial data.\n"); info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; break; case VULKAN_TEXTURE_STREAMED: info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; info.tiling = VK_IMAGE_TILING_LINEAR; info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; break; case VULKAN_TEXTURE_STAGING: buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; info.initialLayout = VK_IMAGE_LAYOUT_GENERAL; info.tiling = VK_IMAGE_TILING_LINEAR; break; case VULKAN_TEXTURE_READBACK: buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; info.initialLayout = VK_IMAGE_LAYOUT_GENERAL; info.tiling = VK_IMAGE_TILING_LINEAR; break; } if ( (type != VULKAN_TEXTURE_STAGING) && (type != VULKAN_TEXTURE_READBACK)) { vkCreateImage(device, &info, NULL, &tex.image); vulkan_debug_mark_image(device, tex.image); #if 0 vulkan_track_alloc(tex.image); #endif vkGetImageMemoryRequirements(device, tex.image, &mem_reqs); } else { /* Linear staging textures are not guaranteed to be supported, * use buffers instead. */ vkCreateBuffer(device, &buffer_info, NULL, &tex.buffer); vulkan_debug_mark_buffer(device, tex.buffer); vkGetBufferMemoryRequirements(device, tex.buffer, &mem_reqs); } alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc.pNext = NULL; alloc.allocationSize = mem_reqs.size; alloc.memoryTypeIndex = 0; switch (type) { case VULKAN_TEXTURE_STATIC: case VULKAN_TEXTURE_DYNAMIC: alloc.memoryTypeIndex = vulkan_find_memory_type_fallback( &vk->context->memory_properties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0); break; default: /* Try to find a memory type which is cached, * even if it means manual cache management. */ alloc.memoryTypeIndex = vulkan_find_memory_type_fallback( &vk->context->memory_properties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); if ((vk->context->memory_properties.memoryTypes [ alloc.memoryTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) tex.flags |= VK_TEX_FLAG_NEED_MANUAL_CACHE_MANAGEMENT; /* If the texture is STREAMED and it's not DEVICE_LOCAL, we expect to hit a slower path, * so fallback to copy path. */ if ( type == VULKAN_TEXTURE_STREAMED && (vk->context->memory_properties.memoryTypes[ alloc.memoryTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == 0) { /* Recreate texture but for STAGING this time ... */ #ifdef VULKAN_DEBUG RARCH_DBG("[Vulkan]: GPU supports linear images as textures, but not DEVICE_LOCAL. Falling back to copy path.\n"); #endif type = VULKAN_TEXTURE_STAGING; vkDestroyImage(device, tex.image, NULL); tex.image = VK_NULL_HANDLE; info.initialLayout = VK_IMAGE_LAYOUT_GENERAL; buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; vkCreateBuffer(device, &buffer_info, NULL, &tex.buffer); vulkan_debug_mark_buffer(device, tex.buffer); vkGetBufferMemoryRequirements(device, tex.buffer, &mem_reqs); alloc.allocationSize = mem_reqs.size; alloc.memoryTypeIndex = vulkan_find_memory_type_fallback( &vk->context->memory_properties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); } break; } /* We're not reusing the objects themselves. */ if (old) { if (old->view != VK_NULL_HANDLE) vkDestroyImageView(vk->context->device, old->view, NULL); if (old->image != VK_NULL_HANDLE) { vkDestroyImage(vk->context->device, old->image, NULL); #ifdef VULKAN_DEBUG_TEXTURE_ALLOC vulkan_track_dealloc(old->image); #endif } if (old->buffer != VK_NULL_HANDLE) vkDestroyBuffer(vk->context->device, old->buffer, NULL); } /* We can pilfer the old memory and move it over to the new texture. */ if ( old && old->memory_size >= mem_reqs.size && old->memory_type == alloc.memoryTypeIndex) { tex.memory = old->memory; tex.memory_size = old->memory_size; tex.memory_type = old->memory_type; if (old->mapped) vkUnmapMemory(device, old->memory); old->memory = VK_NULL_HANDLE; } else { vkAllocateMemory(device, &alloc, NULL, &tex.memory); vulkan_debug_mark_memory(device, tex.memory); tex.memory_size = alloc.allocationSize; tex.memory_type = alloc.memoryTypeIndex; } if (old) { if (old->memory != VK_NULL_HANDLE) vkFreeMemory(device, old->memory, NULL); memset(old, 0, sizeof(*old)); } if (tex.image) vkBindImageMemory(device, tex.image, tex.memory, 0); if (tex.buffer) vkBindBufferMemory(device, tex.buffer, tex.memory, 0); if ( type != VULKAN_TEXTURE_STAGING && type != VULKAN_TEXTURE_READBACK) { VkImageViewCreateInfo view; view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; view.pNext = NULL; view.flags = 0; view.image = tex.image; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = format; if (swizzle) view.components = *swizzle; else { view.components.r = VK_COMPONENT_SWIZZLE_R; view.components.g = VK_COMPONENT_SWIZZLE_G; view.components.b = VK_COMPONENT_SWIZZLE_B; view.components.a = VK_COMPONENT_SWIZZLE_A; } view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view.subresourceRange.baseMipLevel = 0; view.subresourceRange.levelCount = info.mipLevels; view.subresourceRange.baseArrayLayer = 0; view.subresourceRange.layerCount = 1; vkCreateImageView(device, &view, NULL, &tex.view); } else tex.view = VK_NULL_HANDLE; if ( tex.image && info.tiling == VK_IMAGE_TILING_LINEAR) vkGetImageSubresourceLayout(device, tex.image, &subresource, &layout); else if (tex.buffer) { layout.offset = 0; layout.size = buffer_info.size; layout.rowPitch = buffer_width; } else memset(&layout, 0, sizeof(layout)); tex.stride = layout.rowPitch; tex.offset = layout.offset; tex.size = layout.size; tex.layout = info.initialLayout; tex.width = width; tex.height = height; tex.format = format; tex.type = type; if (initial) { switch (type) { case VULKAN_TEXTURE_STREAMED: case VULKAN_TEXTURE_STAGING: { unsigned y; uint8_t *dst = NULL; const uint8_t *src = NULL; void *ptr = NULL; unsigned bpp = vulkan_format_to_bpp(tex.format); unsigned stride = tex.width * bpp; vkMapMemory(device, tex.memory, tex.offset, tex.size, 0, &ptr); dst = (uint8_t*)ptr; src = (const uint8_t*)initial; for (y = 0; y < tex.height; y++, dst += tex.stride, src += stride) memcpy(dst, src, width * bpp); if ( (tex.flags & VK_TEX_FLAG_NEED_MANUAL_CACHE_MANAGEMENT) && (tex.memory != VK_NULL_HANDLE)) VULKAN_SYNC_TEXTURE_TO_GPU(vk->context->device, tex.memory); vkUnmapMemory(device, tex.memory); } break; case VULKAN_TEXTURE_STATIC: { VkBufferImageCopy region; VkCommandBuffer staging; VkSubmitInfo submit_info; VkCommandBufferBeginInfo begin_info; VkCommandBufferAllocateInfo cmd_info; enum VkImageLayout layout_fmt = (tex.flags & VK_TEX_FLAG_MIPMAP) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; struct vk_texture tmp = vulkan_create_texture(vk, NULL, width, height, format, initial, NULL, VULKAN_TEXTURE_STAGING); cmd_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmd_info.pNext = NULL; cmd_info.commandPool = vk->staging_pool; cmd_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmd_info.commandBufferCount = 1; vkAllocateCommandBuffers(vk->context->device, &cmd_info, &staging); begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.pNext = NULL; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.pInheritanceInfo = NULL; vkBeginCommandBuffer(staging, &begin_info); /* If doing mipmapping on upload, keep in general * so we can easily do transfers to * and transfers from the images without having to * mess around with lots of extra transitions at * per-level granularity. */ VULKAN_IMAGE_LAYOUT_TRANSITION( staging, tex.image, VK_IMAGE_LAYOUT_UNDEFINED, layout_fmt, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); memset(®ion, 0, sizeof(region)); region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.layerCount = 1; region.imageExtent.width = width; region.imageExtent.height = height; region.imageExtent.depth = 1; vkCmdCopyBufferToImage(staging, tmp.buffer, tex.image, layout_fmt, 1, ®ion); if (tex.flags & VK_TEX_FLAG_MIPMAP) { for (i = 1; i < info.mipLevels; i++) { VkImageBlit blit_region; unsigned src_width = MAX(width >> (i - 1), 1); unsigned src_height = MAX(height >> (i - 1), 1); unsigned target_width = MAX(width >> i, 1); unsigned target_height = MAX(height >> i, 1); memset(&blit_region, 0, sizeof(blit_region)); blit_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; blit_region.srcSubresource.mipLevel = i - 1; blit_region.srcSubresource.baseArrayLayer = 0; blit_region.srcSubresource.layerCount = 1; blit_region.dstSubresource = blit_region.srcSubresource; blit_region.dstSubresource.mipLevel = i; blit_region.srcOffsets[1].x = src_width; blit_region.srcOffsets[1].y = src_height; blit_region.srcOffsets[1].z = 1; blit_region.dstOffsets[1].x = target_width; blit_region.dstOffsets[1].y = target_height; blit_region.dstOffsets[1].z = 1; /* Only injects execution and memory barriers, * not actual transition. */ VULKAN_IMAGE_LAYOUT_TRANSITION( staging, tex.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); vkCmdBlitImage( staging, tex.image, VK_IMAGE_LAYOUT_GENERAL, tex.image, VK_IMAGE_LAYOUT_GENERAL, 1, &blit_region, VK_FILTER_LINEAR); } } /* Complete our texture. */ VULKAN_IMAGE_LAYOUT_TRANSITION( staging, tex.image, layout_fmt, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); vkEndCommandBuffer(staging); submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = NULL; submit_info.waitSemaphoreCount = 0; submit_info.pWaitSemaphores = NULL; submit_info.pWaitDstStageMask = NULL; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &staging; submit_info.signalSemaphoreCount = 0; submit_info.pSignalSemaphores = NULL; #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueSubmit(vk->context->queue, 1, &submit_info, VK_NULL_HANDLE); /* TODO: Very crude, but texture uploads only happen * during init, so waiting for GPU to complete transfer * and blocking isn't a big deal. */ vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif vkFreeCommandBuffers(vk->context->device, vk->staging_pool, 1, &staging); vulkan_destroy_texture( vk->context->device, &tmp); tex.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } break; case VULKAN_TEXTURE_DYNAMIC: case VULKAN_TEXTURE_READBACK: /* TODO/FIXME - stubs */ break; } } return tex; } /* Dynamic texture type should be set to : VULKAN_TEXTURE_DYNAMIC * Staging texture type should be set to : VULKAN_TEXTURE_STAGING */ static void vulkan_copy_staging_to_dynamic(vk_t *vk, VkCommandBuffer cmd, struct vk_texture *dynamic, struct vk_texture *staging) { bool compute_upload = dynamic->format != staging->format; if (compute_upload) { const uint32_t ubo[3] = { dynamic->width, dynamic->height, (uint32_t)(staging->stride / 4) /* in terms of u32 words */ }; VkWriteDescriptorSet write; VkDescriptorBufferInfo buffer_info; VkDescriptorImageInfo image_info; struct vk_buffer_range range; VkDescriptorSet set; VULKAN_IMAGE_LAYOUT_TRANSITION( cmd, dynamic->image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, 0, VK_ACCESS_SHADER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); /* staging->format is always RGB565 here. * Can be expanded as needed if more cases are added to VK_REMAP_TO_TEXFMT. */ retro_assert(staging->format == VK_FORMAT_R5G6B5_UNORM_PACK16); set = vulkan_descriptor_manager_alloc( vk->context->device, &vk->chain->descriptor_manager); if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->ubo, sizeof(ubo), &range)) return; memcpy(range.data, ubo, sizeof(ubo)); VULKAN_SET_UNIFORM_BUFFER(vk->context->device, set, 0, range.buffer, range.offset, sizeof(ubo)); image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; image_info.imageView = dynamic->view; image_info.sampler = VK_NULL_HANDLE; buffer_info.buffer = staging->buffer; buffer_info.offset = 0; buffer_info.range = VK_WHOLE_SIZE; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write.pNext = NULL; write.dstSet = set; write.dstBinding = 3; write.dstArrayElement = 0; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; write.pImageInfo = &image_info; write.pBufferInfo = NULL; write.pTexelBufferView = NULL; vkUpdateDescriptorSets(vk->context->device, 1, &write, 0, NULL); write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; write.dstBinding = 4; write.pImageInfo = NULL; write.pBufferInfo = &buffer_info; vkUpdateDescriptorSets(vk->context->device, 1, &write, 0, NULL); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, vk->pipelines.rgb565_to_rgba8888); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, vk->pipelines.layout, 0, 1, &set, 0, NULL); vkCmdDispatch(cmd, (dynamic->width + 15) / 16, (dynamic->height + 7) / 8, 1); VULKAN_IMAGE_LAYOUT_TRANSITION( cmd, dynamic->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); } else { VkBufferImageCopy region; VULKAN_IMAGE_LAYOUT_TRANSITION( cmd, dynamic->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_TRANSFER_BIT); region.bufferOffset = 0; region.bufferRowLength = 0; region.bufferImageHeight = 0; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.mipLevel = 0; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset.x = 0; region.imageOffset.y = 0; region.imageOffset.z = 0; region.imageExtent.width = dynamic->width; region.imageExtent.height = dynamic->height; region.imageExtent.depth = 1; vkCmdCopyBufferToImage( cmd, staging->buffer, dynamic->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); VULKAN_IMAGE_LAYOUT_TRANSITION( cmd, dynamic->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); } dynamic->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } /** * FORWARD DECLARATIONS */ static void vulkan_set_viewport(void *data, unsigned viewport_width, unsigned viewport_height, bool force_full, bool allow_rotate); static bool vulkan_is_mapped_swapchain_texture_ptr(const vk_t* vk, const void* ptr); #ifdef HAVE_OVERLAY static void vulkan_overlay_free(vk_t *vk); static void vulkan_render_overlay(vk_t *vk, unsigned width, unsigned height); #endif static void vulkan_viewport_info(void *data, struct video_viewport *vp); /** * DISPLAY DRIVER */ /* Will do Y-flip later, but try to make it similar to GL. */ static const float vk_vertexes[8] = { 0, 0, 1, 0, 0, 1, 1, 1 }; static const float vk_tex_coords[8] = { 0, 1, 1, 1, 0, 0, 1, 0 }; static const float vk_colors[16] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; static void *gfx_display_vk_get_default_mvp(void *data) { vk_t *vk = (vk_t*)data; if (!vk) return NULL; return &vk->mvp_no_rot; } static const float *gfx_display_vk_get_default_vertices(void) { return &vk_vertexes[0]; } static const float *gfx_display_vk_get_default_tex_coords(void) { return &vk_tex_coords[0]; } #ifdef HAVE_SHADERPIPELINE static unsigned to_menu_pipeline( enum gfx_display_prim_type type, unsigned pipeline) { switch (pipeline) { case VIDEO_SHADER_MENU: return 6 + (type == GFX_DISPLAY_PRIM_TRIANGLESTRIP); case VIDEO_SHADER_MENU_2: return 8 + (type == GFX_DISPLAY_PRIM_TRIANGLESTRIP); case VIDEO_SHADER_MENU_3: return 10 + (type == GFX_DISPLAY_PRIM_TRIANGLESTRIP); case VIDEO_SHADER_MENU_4: return 12 + (type == GFX_DISPLAY_PRIM_TRIANGLESTRIP); case VIDEO_SHADER_MENU_5: return 14 + (type == GFX_DISPLAY_PRIM_TRIANGLESTRIP); default: break; } return 0; } static void gfx_display_vk_draw_pipeline( gfx_display_ctx_draw_t *draw, gfx_display_t *p_disp, void *data, unsigned video_width, unsigned video_height) { static uint8_t ubo_scratch_data[768]; static struct video_coords blank_coords; static float t = 0.0f; float output_size[2]; float yflip = 1.0f; video_coord_array_t *ca = NULL; vk_t *vk = (vk_t*)data; if (!vk || !draw) return; draw->x = 0; draw->y = 0; draw->matrix_data = NULL; output_size[0] = (float)vk->context->swapchain_width; output_size[1] = (float)vk->context->swapchain_height; switch (draw->pipeline_id) { /* Ribbon */ default: case VIDEO_SHADER_MENU: case VIDEO_SHADER_MENU_2: ca = &p_disp->dispca; draw->coords = (struct video_coords*)&ca->coords; draw->backend_data = ubo_scratch_data; draw->backend_data_size = 2 * sizeof(float); /* Match UBO layout in shader. */ memcpy(ubo_scratch_data, &t, sizeof(t)); memcpy(ubo_scratch_data + sizeof(float), &yflip, sizeof(yflip)); break; /* Snow simple */ case VIDEO_SHADER_MENU_3: case VIDEO_SHADER_MENU_4: case VIDEO_SHADER_MENU_5: draw->backend_data = ubo_scratch_data; draw->backend_data_size = sizeof(math_matrix_4x4) + 4 * sizeof(float); /* Match UBO layout in shader. */ memcpy(ubo_scratch_data, &vk->mvp_no_rot, sizeof(math_matrix_4x4)); memcpy(ubo_scratch_data + sizeof(math_matrix_4x4), output_size, sizeof(output_size)); /* Shader uses FragCoord, need to fix up. */ if (draw->pipeline_id == VIDEO_SHADER_MENU_5) yflip = -1.0f; memcpy(ubo_scratch_data + sizeof(math_matrix_4x4) + 2 * sizeof(float), &t, sizeof(t)); memcpy(ubo_scratch_data + sizeof(math_matrix_4x4) + 3 * sizeof(float), &yflip, sizeof(yflip)); draw->coords = &blank_coords; blank_coords.vertices = 4; draw->prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; break; } t += 0.01; } #endif static void gfx_display_vk_draw(gfx_display_ctx_draw_t *draw, void *data, unsigned video_width, unsigned video_height) { unsigned i; struct vk_buffer_range range; struct vk_texture *texture = NULL; const float *vertex = NULL; const float *tex_coord = NULL; const float *color = NULL; struct vk_vertex *pv = NULL; vk_t *vk = (vk_t*)data; if (!vk || !draw) return; texture = (struct vk_texture*)draw->texture; vertex = draw->coords->vertex; tex_coord = draw->coords->tex_coord; color = draw->coords->color; if (!vertex) vertex = &vk_vertexes[0]; if (!tex_coord) tex_coord = &vk_tex_coords[0]; if (!draw->coords->lut_tex_coord) draw->coords->lut_tex_coord = &vk_tex_coords[0]; if (!texture) texture = &vk->display.blank_texture; if (!color) color = &vk_colors[0]; vk->vk_vp.x = draw->x; vk->vk_vp.y = vk->context->swapchain_height - draw->y - draw->height; vk->vk_vp.width = draw->width; vk->vk_vp.height = draw->height; vk->vk_vp.minDepth = 0.0f; vk->vk_vp.maxDepth = 1.0f; vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; /* Bake interleaved VBO. Kinda ugly, we should probably try to move to * an interleaved model to begin with ... */ if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->vbo, draw->coords->vertices * sizeof(struct vk_vertex), &range)) return; pv = (struct vk_vertex*)range.data; for (i = 0; i < draw->coords->vertices; i++, pv++) { pv->x = *vertex++; /* Y-flip. Vulkan is top-left clip space */ pv->y = 1.0f - (*vertex++); pv->tex_x = *tex_coord++; pv->tex_y = *tex_coord++; pv->color.r = *color++; pv->color.g = *color++; pv->color.b = *color++; pv->color.a = *color++; } switch (draw->pipeline_id) { #ifdef HAVE_SHADERPIPELINE case VIDEO_SHADER_MENU: case VIDEO_SHADER_MENU_2: case VIDEO_SHADER_MENU_3: case VIDEO_SHADER_MENU_4: case VIDEO_SHADER_MENU_5: { struct vk_draw_triangles call; call.pipeline = vk->display.pipelines[ to_menu_pipeline(draw->prim_type, draw->pipeline_id)]; call.texture = NULL; call.sampler = VK_NULL_HANDLE; call.uniform = draw->backend_data; call.uniform_size = draw->backend_data_size; call.vbo = ⦥ call.vertices = draw->coords->vertices; vulkan_draw_triangles(vk, &call); } break; #endif default: { struct vk_draw_triangles call; unsigned disp_pipeline = ((draw->prim_type == GFX_DISPLAY_PRIM_TRIANGLESTRIP) << 1) | (((vk->flags & VK_FLAG_DISPLAY_BLEND) > 0) << 0); call.pipeline = vk->display.pipelines[disp_pipeline]; call.texture = texture; call.sampler = (texture->flags & VK_TEX_FLAG_MIPMAP) ? vk->samplers.mipmap_linear : ((texture->flags & VK_TEX_FLAG_DEFAULT_SMOOTH) ? vk->samplers.linear : vk->samplers.nearest); call.uniform = draw->matrix_data ? draw->matrix_data : &vk->mvp_no_rot; call.uniform_size = sizeof(math_matrix_4x4); call.vbo = ⦥ call.vertices = draw->coords->vertices; vulkan_draw_triangles(vk, &call); } break; } } static void gfx_display_vk_blend_begin(void *data) { vk_t *vk = (vk_t*)data; if (vk) vk->flags |= VK_FLAG_DISPLAY_BLEND; } static void gfx_display_vk_blend_end(void *data) { vk_t *vk = (vk_t*)data; if (vk) vk->flags &= ~VK_FLAG_DISPLAY_BLEND; } static void gfx_display_vk_scissor_begin( void *data, unsigned video_width, unsigned video_height, int x, int y, unsigned width, unsigned height) { vk_t *vk = (vk_t*)data; vk->tracker.scissor.offset.x = x; vk->tracker.scissor.offset.y = y; vk->tracker.scissor.extent.width = width; vk->tracker.scissor.extent.height = height; vk->flags |= VK_FLAG_TRACKER_USE_SCISSOR; vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; } static void gfx_display_vk_scissor_end(void *data, unsigned video_width, unsigned video_height) { vk_t *vk = (vk_t*)data; vk->flags &= ~VK_FLAG_TRACKER_USE_SCISSOR; vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; } gfx_display_ctx_driver_t gfx_display_ctx_vulkan = { gfx_display_vk_draw, #ifdef HAVE_SHADERPIPELINE gfx_display_vk_draw_pipeline, #else NULL, /* draw_pipeline */ #endif gfx_display_vk_blend_begin, gfx_display_vk_blend_end, gfx_display_vk_get_default_mvp, gfx_display_vk_get_default_vertices, gfx_display_vk_get_default_tex_coords, FONT_DRIVER_RENDER_VULKAN_API, GFX_VIDEO_DRIVER_VULKAN, "vulkan", false, gfx_display_vk_scissor_begin, gfx_display_vk_scissor_end }; /** * FONT DRIVER */ static INLINE void vulkan_font_update_glyph( vulkan_raster_t *font, const struct font_glyph *glyph) { unsigned row; for (row = glyph->atlas_offset_y; row < (glyph->atlas_offset_y + glyph->height); row++) { uint8_t *src = font->atlas->buffer + row * font->atlas->width + glyph->atlas_offset_x; uint8_t *dst = (uint8_t*)font->texture.mapped + row * font->texture.stride + glyph->atlas_offset_x; memcpy(dst, src, glyph->width); } } static void vulkan_font_free(void *data, bool is_threaded) { vulkan_raster_t *font = (vulkan_raster_t*)data; if (!font) return; if (font->font_driver && font->font_data) font->font_driver->free(font->font_data); vkQueueWaitIdle(font->vk->context->queue); vulkan_destroy_texture( font->vk->context->device, &font->texture); vulkan_destroy_texture( font->vk->context->device, &font->texture_optimal); free(font); } static void *vulkan_font_init(void *data, const char *font_path, float font_size, bool is_threaded) { vulkan_raster_t *font = (vulkan_raster_t*)calloc(1, sizeof(*font)); if (!font) return NULL; font->vk = (vk_t*)data; if (!font_renderer_create_default( &font->font_driver, &font->font_data, font_path, font_size)) { free(font); return NULL; } font->atlas = font->font_driver->get_atlas(font->font_data); font->texture = vulkan_create_texture(font->vk, NULL, font->atlas->width, font->atlas->height, VK_FORMAT_R8_UNORM, font->atlas->buffer, NULL, VULKAN_TEXTURE_STAGING); { struct vk_texture *texture = &font->texture; VK_MAP_PERSISTENT_TEXTURE(font->vk->context->device, texture); } font->texture_optimal = vulkan_create_texture(font->vk, NULL, font->atlas->width, font->atlas->height, VK_FORMAT_R8_UNORM, NULL, NULL, VULKAN_TEXTURE_DYNAMIC); font->needs_update = true; return font; } static int vulkan_get_message_width(void *data, const char *msg, size_t msg_len, float scale) { const struct font_glyph* glyph_q = NULL; vulkan_raster_t *font = (vulkan_raster_t*)data; const char* msg_end = msg + msg_len; int delta_x = 0; if ( !font || !font->font_driver || !font->font_data ) return 0; glyph_q = font->font_driver->get_glyph(font->font_data, '?'); while (msg < msg_end) { const struct font_glyph *glyph; uint32_t code = utf8_walk(&msg); /* Do something smarter here ... */ if (!(glyph = font->font_driver->get_glyph( font->font_data, code))) if (!(glyph = glyph_q)) continue; if (font->atlas->dirty) { vulkan_font_update_glyph(font, glyph); font->atlas->dirty = false; font->needs_update = true; } delta_x += glyph->advance_x; } return delta_x * scale; } static void vulkan_font_render_line(vk_t *vk, vulkan_raster_t *font, const struct font_glyph* glyph_q, const char *msg, size_t msg_len, float scale, const float color[4], float pos_x, float pos_y, int pre_x, float inv_tex_size_x, float inv_tex_size_y, float inv_win_width, float inv_win_height, unsigned text_align) { struct vk_color vk_color; const char* msg_end = msg + msg_len; int x = pre_x; int y = roundf((1.0f - pos_y) * vk->vp.height); int delta_x = 0; int delta_y = 0; vk_color.r = color[0]; vk_color.g = color[1]; vk_color.b = color[2]; vk_color.a = color[3]; switch (text_align) { case TEXT_ALIGN_RIGHT: x -= vulkan_get_message_width(font, msg, msg_len, scale); break; case TEXT_ALIGN_CENTER: x -= vulkan_get_message_width(font, msg, msg_len, scale) / 2; break; } while (msg < msg_end) { const struct font_glyph *glyph; int off_x, off_y, tex_x, tex_y, width, height; unsigned code = utf8_walk(&msg); /* Do something smarter here ... */ if (!(glyph = font->font_driver->get_glyph(font->font_data, code))) if (!(glyph = glyph_q)) continue; if (font->atlas->dirty) { vulkan_font_update_glyph(font, glyph); font->atlas->dirty = false; font->needs_update = true; } off_x = glyph->draw_offset_x; off_y = glyph->draw_offset_y; tex_x = glyph->atlas_offset_x; tex_y = glyph->atlas_offset_y; width = glyph->width; height = glyph->height; { struct vk_vertex *pv = font->pv + font->vertices; float _x = (x + (off_x + delta_x) * scale) * inv_win_width; float _y = (y + (off_y + delta_y) * scale) * inv_win_height; float _width = width * scale * inv_win_width; float _height = height * scale * inv_win_height; float _tex_x = tex_x * inv_tex_size_x; float _tex_y = tex_y * inv_tex_size_y; float _tex_width = width * inv_tex_size_x; float _tex_height = height * inv_tex_size_y; const struct vk_color *_color = &vk_color; VULKAN_WRITE_QUAD_VBO(pv, _x, _y, _width, _height, _tex_x, _tex_y, _tex_width, _tex_height, _color); } font->vertices += 6; delta_x += glyph->advance_x; delta_y += glyph->advance_y; } } static void vulkan_font_render_message(vk_t *vk, vulkan_raster_t *font, const char *msg, float scale, const float color[4], float pos_x, float pos_y, unsigned text_align) { float line_height; struct font_line_metrics *line_metrics = NULL; const struct font_glyph* glyph_q = font->font_driver->get_glyph(font->font_data, '?'); int x = roundf(pos_x * vk->vp.width); int lines = 0; float inv_tex_size_x = 1.0f / font->texture.width; float inv_tex_size_y = 1.0f / font->texture.height; float inv_win_width = 1.0f / vk->vp.width; float inv_win_height = 1.0f / vk->vp.height; font->font_driver->get_line_metrics(font->font_data, &line_metrics); line_height = line_metrics->height * scale / vk->vp.height; for (;;) { const char *delim = strchr(msg, '\n'); size_t msg_len = delim ? (size_t)(delim - msg) : strlen(msg); /* Draw the line */ vulkan_font_render_line(vk, font, glyph_q, msg, msg_len, scale, color, pos_x, pos_y - (float)lines * line_height, x, inv_tex_size_x, inv_tex_size_y, inv_win_width, inv_win_height, text_align); if (!delim) break; msg += msg_len + 1; lines++; } } static void vulkan_font_flush(vk_t *vk, vulkan_raster_t *font) { struct vk_draw_triangles call; call.pipeline = vk->pipelines.font; call.texture = &font->texture_optimal; call.sampler = vk->samplers.mipmap_linear; call.uniform = &vk->mvp; call.uniform_size = sizeof(vk->mvp); call.vbo = &font->range; call.vertices = font->vertices; if (font->needs_update) { VkCommandBuffer staging; VkSubmitInfo submit_info; VkCommandBufferAllocateInfo cmd_info; VkCommandBufferBeginInfo begin_info; struct vk_texture *dynamic_tex = NULL; struct vk_texture *staging_tex = NULL; cmd_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmd_info.pNext = NULL; cmd_info.commandPool = vk->staging_pool; cmd_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmd_info.commandBufferCount = 1; vkAllocateCommandBuffers(vk->context->device, &cmd_info, &staging); begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.pNext = NULL; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.pInheritanceInfo = NULL; vkBeginCommandBuffer(staging, &begin_info); VULKAN_SYNC_TEXTURE_TO_GPU_COND_OBJ(vk, font->texture); dynamic_tex = &font->texture_optimal; staging_tex = &font->texture; vulkan_copy_staging_to_dynamic(vk, staging, dynamic_tex, staging_tex); vkEndCommandBuffer(staging); #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = NULL; submit_info.waitSemaphoreCount = 0; submit_info.pWaitSemaphores = NULL; submit_info.pWaitDstStageMask = NULL; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &staging; submit_info.signalSemaphoreCount = 0; submit_info.pSignalSemaphores = NULL; vkQueueSubmit(vk->context->queue, 1, &submit_info, VK_NULL_HANDLE); vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif vkFreeCommandBuffers(vk->context->device, vk->staging_pool, 1, &staging); font->needs_update = false; } vulkan_draw_triangles(vk, &call); } static void vulkan_font_render_msg( void *userdata, void *data, const char *msg, const struct font_params *params) { float color[4]; int drop_x, drop_y; bool full_screen; size_t max_glyphs; unsigned width, height; enum text_alignment text_align; float x, y, scale, drop_mod, drop_alpha; vulkan_raster_t *font = (vulkan_raster_t*)data; settings_t *settings = config_get_ptr(); float video_msg_pos_x = settings->floats.video_msg_pos_x; float video_msg_pos_y = settings->floats.video_msg_pos_y; float video_msg_color_r = settings->floats.video_msg_color_r; float video_msg_color_g = settings->floats.video_msg_color_g; float video_msg_color_b = settings->floats.video_msg_color_b; vk_t *vk = (vk_t*)userdata; if (!font || !msg || !*msg || !vk) return; width = vk->video_width; height = vk->video_height; if (params) { x = params->x; y = params->y; scale = params->scale; full_screen = params->full_screen; text_align = params->text_align; drop_x = params->drop_x; drop_y = params->drop_y; drop_mod = params->drop_mod; drop_alpha = params->drop_alpha; color[0] = FONT_COLOR_GET_RED(params->color) / 255.0f; color[1] = FONT_COLOR_GET_GREEN(params->color) / 255.0f; color[2] = FONT_COLOR_GET_BLUE(params->color) / 255.0f; color[3] = FONT_COLOR_GET_ALPHA(params->color) / 255.0f; /* If alpha is 0.0f, turn it into default 1.0f */ if (color[3] <= 0.0f) color[3] = 1.0f; } else { x = video_msg_pos_x; y = video_msg_pos_y; scale = 1.0f; full_screen = true; text_align = TEXT_ALIGN_LEFT; drop_x = -2; drop_y = -2; drop_mod = 0.3f; drop_alpha = 1.0f; color[0] = video_msg_color_r; color[1] = video_msg_color_g; color[2] = video_msg_color_b; color[3] = 1.0f; } vulkan_set_viewport(vk, width, height, full_screen, false); max_glyphs = strlen(msg); if (drop_x || drop_y) max_glyphs *= 2; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->vbo, 6 * sizeof(struct vk_vertex) * max_glyphs, &font->range)) return; font->vertices = 0; font->pv = (struct vk_vertex*)font->range.data; if (drop_x || drop_y) { float color_dark[4]; color_dark[0] = color[0] * drop_mod; color_dark[1] = color[1] * drop_mod; color_dark[2] = color[2] * drop_mod; color_dark[3] = color[3] * drop_alpha; vulkan_font_render_message(vk, font, msg, scale, color_dark, x + scale * drop_x / vk->vp.width, y + scale * drop_y / vk->vp.height, text_align); } vulkan_font_render_message(vk, font, msg, scale, color, x, y, text_align); vulkan_font_flush(vk, font); } static const struct font_glyph *vulkan_font_get_glyph( void *data, uint32_t code) { const struct font_glyph* glyph; vulkan_raster_t *font = (vulkan_raster_t*)data; if (!font || !font->font_driver) return NULL; glyph = font->font_driver->get_glyph((void*)font->font_driver, code); if (glyph && font->atlas->dirty) { vulkan_font_update_glyph(font, glyph); font->atlas->dirty = false; font->needs_update = true; } return glyph; } static bool vulkan_get_line_metrics(void* data, struct font_line_metrics **metrics) { vulkan_raster_t *font = (vulkan_raster_t*)data; if (font && font->font_driver && font->font_data) { font->font_driver->get_line_metrics(font->font_data, metrics); return true; } return false; } font_renderer_t vulkan_raster_font = { vulkan_font_init, vulkan_font_free, vulkan_font_render_msg, "vulkan", vulkan_font_get_glyph, NULL, /* bind_block */ NULL, /* flush_block */ vulkan_get_message_width, vulkan_get_line_metrics }; /* * VIDEO DRIVER */ static struct vk_descriptor_manager vulkan_create_descriptor_manager( VkDevice device, const VkDescriptorPoolSize *sizes, unsigned num_sizes, VkDescriptorSetLayout set_layout) { int i; struct vk_descriptor_manager manager; manager.current = NULL; manager.count = 0; for (i = 0; i < VULKAN_MAX_DESCRIPTOR_POOL_SIZES; i++) { manager.sizes[i].type = VK_DESCRIPTOR_TYPE_SAMPLER; manager.sizes[i].descriptorCount = 0; } memcpy(manager.sizes, sizes, num_sizes * sizeof(*sizes)); manager.set_layout = set_layout; manager.num_sizes = num_sizes; manager.head = vulkan_alloc_descriptor_pool(device, &manager); return manager; } static void vulkan_destroy_descriptor_manager( VkDevice device, struct vk_descriptor_manager *manager) { struct vk_descriptor_pool *node = manager->head; while (node) { struct vk_descriptor_pool *next = node->next; vkFreeDescriptorSets(device, node->pool, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS, node->sets); vkDestroyDescriptorPool(device, node->pool, NULL); free(node); node = next; } memset(manager, 0, sizeof(*manager)); } static struct vk_buffer_chain vulkan_buffer_chain_init( VkDeviceSize block_size, VkDeviceSize alignment, VkBufferUsageFlags usage) { struct vk_buffer_chain chain; chain.block_size = block_size; chain.alignment = alignment; chain.offset = 0; chain.usage = usage; chain.head = NULL; chain.current = NULL; return chain; } static const gfx_ctx_driver_t *gfx_ctx_vk_drivers[] = { #if defined(__APPLE__) &gfx_ctx_cocoavk, #endif #if defined(_WIN32) && !defined(__WINRT__) &gfx_ctx_w_vk, #endif #if defined(ANDROID) &gfx_ctx_vk_android, #endif #if defined(HAVE_WAYLAND) &gfx_ctx_vk_wayland, #endif #if defined(HAVE_X11) &gfx_ctx_vk_x, #endif #if defined(HAVE_VULKAN_DISPLAY) &gfx_ctx_khr_display, #endif &gfx_ctx_null, NULL }; static const gfx_ctx_driver_t *vk_context_driver_init_first( uint32_t runloop_flags, settings_t *settings, void *data, const char *ident, enum gfx_ctx_api api, unsigned major, unsigned minor, bool hw_render_ctx, void **ctx_data) { unsigned j; int i = -1; video_driver_state_t *video_st = video_state_get_ptr(); for (j = 0; gfx_ctx_vk_drivers[j]; j++) { if (string_is_equal_noncase(ident, gfx_ctx_vk_drivers[j]->ident)) { i = j; break; } } if (i >= 0) { const gfx_ctx_driver_t *ctx = video_context_driver_init( (runloop_flags & RUNLOOP_FLAG_CORE_SET_SHARED_CONTEXT) ? true : false, settings, data, gfx_ctx_vk_drivers[i], ident, api, major, minor, hw_render_ctx, ctx_data); if (ctx) { video_st->context_data = *ctx_data; return ctx; } } for (i = 0; gfx_ctx_vk_drivers[i]; i++) { const gfx_ctx_driver_t *ctx = video_context_driver_init( (runloop_flags & RUNLOOP_FLAG_CORE_SET_SHARED_CONTEXT) ? true : false, settings, data, gfx_ctx_vk_drivers[i], ident, api, major, minor, hw_render_ctx, ctx_data); if (ctx) { video_st->context_data = *ctx_data; return ctx; } } return NULL; } static const gfx_ctx_driver_t *vulkan_get_context(vk_t *vk, settings_t *settings) { void *ctx_data = NULL; unsigned major = 1; unsigned minor = 0; enum gfx_ctx_api api = GFX_CTX_VULKAN_API; uint32_t runloop_flags = runloop_get_flags(); const gfx_ctx_driver_t *gfx_ctx = vk_context_driver_init_first( runloop_flags, settings, vk, settings->arrays.video_context_driver, api, major, minor, false, &ctx_data); if (ctx_data) vk->ctx_data = ctx_data; return gfx_ctx; } static void vulkan_init_render_pass( vk_t *vk) { VkRenderPassCreateInfo rp_info; VkAttachmentReference color_ref; VkAttachmentDescription attachment; VkSubpassDescription subpass; attachment.flags = 0; /* Backbuffer format. */ attachment.format = vk->context->swapchain_format; /* Not multisampled. */ attachment.samples = VK_SAMPLE_COUNT_1_BIT; /* When starting the frame, we want tiles to be cleared. */ attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; /* When end the frame, we want tiles to be written out. */ attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; /* Don't care about stencil since we're not using it. */ attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; /* The image layout will be attachment_optimal * when we're executing the renderpass. */ attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; /* Color attachment reference */ color_ref.attachment = 0; color_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; /* We have one subpass. * This subpass has 1 color attachment. */ subpass.flags = 0; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.inputAttachmentCount = 0; subpass.pInputAttachments = NULL; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_ref; subpass.pResolveAttachments = NULL; subpass.pDepthStencilAttachment = NULL; subpass.preserveAttachmentCount = 0; subpass.pPreserveAttachments = NULL; /* Finally, create the renderpass. */ rp_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; rp_info.pNext = NULL; rp_info.flags = 0; rp_info.attachmentCount = 1; rp_info.pAttachments = &attachment; rp_info.subpassCount = 1; rp_info.pSubpasses = &subpass; rp_info.dependencyCount = 0; rp_info.pDependencies = NULL; vkCreateRenderPass(vk->context->device, &rp_info, NULL, &vk->render_pass); } static void vulkan_init_framebuffers( vk_t *vk) { int i; for (i = 0; i < (int) vk->num_swapchain_images; i++) { VkImageViewCreateInfo view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; VkFramebufferCreateInfo info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO }; vk->backbuffers[i].image = vk->context->swapchain_images[i]; if (vk->context->swapchain_images[i] == VK_NULL_HANDLE) { vk->backbuffers[i].view = VK_NULL_HANDLE; vk->backbuffers[i].framebuffer = VK_NULL_HANDLE; continue; } /* Create an image view which we can render into. */ view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = vk->context->swapchain_format; view.image = vk->backbuffers[i].image; view.subresourceRange.baseMipLevel = 0; view.subresourceRange.baseArrayLayer = 0; view.subresourceRange.levelCount = 1; view.subresourceRange.layerCount = 1; view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view.components.r = VK_COMPONENT_SWIZZLE_R; view.components.g = VK_COMPONENT_SWIZZLE_G; view.components.b = VK_COMPONENT_SWIZZLE_B; view.components.a = VK_COMPONENT_SWIZZLE_A; vkCreateImageView(vk->context->device, &view, NULL, &vk->backbuffers[i].view); /* Create the framebuffer */ info.renderPass = vk->render_pass; info.attachmentCount = 1; info.pAttachments = &vk->backbuffers[i].view; info.width = vk->context->swapchain_width; info.height = vk->context->swapchain_height; info.layers = 1; vkCreateFramebuffer(vk->context->device, &info, NULL, &vk->backbuffers[i].framebuffer); } } static void vulkan_init_pipeline_layout( vk_t *vk) { VkPipelineLayoutCreateInfo layout_info; VkDescriptorSetLayoutCreateInfo set_layout_info; VkDescriptorSetLayoutBinding bindings[5]; bindings[0].binding = 0; bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; bindings[0].descriptorCount = 1; bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT; bindings[0].pImmutableSamplers = NULL; bindings[1].binding = 1; bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[1].descriptorCount = 1; bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; bindings[1].pImmutableSamplers = NULL; bindings[2].binding = 2; bindings[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[2].descriptorCount = 1; bindings[2].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; bindings[2].pImmutableSamplers = NULL; bindings[3].binding = 3; bindings[3].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; bindings[3].descriptorCount = 1; bindings[3].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; bindings[3].pImmutableSamplers = NULL; bindings[4].binding = 4; bindings[4].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; bindings[4].descriptorCount = 1; bindings[4].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; bindings[4].pImmutableSamplers = NULL; set_layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; set_layout_info.pNext = NULL; set_layout_info.flags = 0; set_layout_info.bindingCount = 5; set_layout_info.pBindings = bindings; vkCreateDescriptorSetLayout(vk->context->device, &set_layout_info, NULL, &vk->pipelines.set_layout); layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; layout_info.pNext = NULL; layout_info.flags = 0; layout_info.setLayoutCount = 1; layout_info.pSetLayouts = &vk->pipelines.set_layout; layout_info.pushConstantRangeCount = 0; layout_info.pPushConstantRanges = NULL; vkCreatePipelineLayout(vk->context->device, &layout_info, NULL, &vk->pipelines.layout); } static void vulkan_init_pipelines(vk_t *vk) { #ifdef VULKAN_HDR_SWAPCHAIN static const uint32_t hdr_frag[] = #include "vulkan_shaders/hdr.frag.inc" ; #endif /* VULKAN_HDR_SWAPCHAIN */ static const uint32_t alpha_blend_vert[] = #include "vulkan_shaders/alpha_blend.vert.inc" ; static const uint32_t alpha_blend_frag[] = #include "vulkan_shaders/alpha_blend.frag.inc" ; static const uint32_t font_frag[] = #include "vulkan_shaders/font.frag.inc" ; static const uint32_t rgb565_to_rgba8888_comp[] = #include "vulkan_shaders/rgb565_to_rgba8888.comp.inc" ; static const uint32_t pipeline_ribbon_vert[] = #include "vulkan_shaders/pipeline_ribbon.vert.inc" ; static const uint32_t pipeline_ribbon_frag[] = #include "vulkan_shaders/pipeline_ribbon.frag.inc" ; static const uint32_t pipeline_ribbon_simple_vert[] = #include "vulkan_shaders/pipeline_ribbon_simple.vert.inc" ; static const uint32_t pipeline_ribbon_simple_frag[] = #include "vulkan_shaders/pipeline_ribbon_simple.frag.inc" ; static const uint32_t pipeline_snow_simple_frag[] = #include "vulkan_shaders/pipeline_snow_simple.frag.inc" ; static const uint32_t pipeline_snow_frag[] = #include "vulkan_shaders/pipeline_snow.frag.inc" ; static const uint32_t pipeline_bokeh_frag[] = #include "vulkan_shaders/pipeline_bokeh.frag.inc" ; int i; VkPipelineMultisampleStateCreateInfo multisample; VkPipelineInputAssemblyStateCreateInfo input_assembly = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO }; VkPipelineVertexInputStateCreateInfo vertex_input = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; VkPipelineRasterizationStateCreateInfo raster = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; VkPipelineColorBlendAttachmentState blend_attachment = {0}; VkPipelineColorBlendStateCreateInfo blend = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; VkPipelineViewportStateCreateInfo viewport = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; VkPipelineDepthStencilStateCreateInfo depth_stencil = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; VkPipelineDynamicStateCreateInfo dynamic = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; VkPipelineShaderStageCreateInfo shader_stages[2] = { { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }, { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }, }; VkGraphicsPipelineCreateInfo pipe = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; VkComputePipelineCreateInfo cpipe = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO }; VkShaderModuleCreateInfo module_info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; VkVertexInputAttributeDescription attributes[3] = {{0}}; VkVertexInputBindingDescription binding = {0}; static const VkDynamicState dynamics[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; vulkan_init_pipeline_layout(vk); /* Input assembly */ input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; /* VAO state */ attributes[0].location = 0; attributes[0].binding = 0; attributes[0].format = VK_FORMAT_R32G32_SFLOAT; attributes[0].offset = 0; attributes[1].location = 1; attributes[1].binding = 0; attributes[1].format = VK_FORMAT_R32G32_SFLOAT; attributes[1].offset = 2 * sizeof(float); attributes[2].location = 2; attributes[2].binding = 0; attributes[2].format = VK_FORMAT_R32G32B32A32_SFLOAT; attributes[2].offset = 4 * sizeof(float); binding.binding = 0; binding.stride = sizeof(struct vk_vertex); binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; vertex_input.vertexBindingDescriptionCount = 1; vertex_input.pVertexBindingDescriptions = &binding; vertex_input.vertexAttributeDescriptionCount = 3; vertex_input.pVertexAttributeDescriptions = attributes; /* Raster state */ raster.polygonMode = VK_POLYGON_MODE_FILL; raster.cullMode = VK_CULL_MODE_NONE; raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; raster.depthClampEnable = VK_FALSE; raster.rasterizerDiscardEnable = VK_FALSE; raster.depthBiasEnable = VK_FALSE; raster.lineWidth = 1.0f; /* Blend state */ blend_attachment.blendEnable = VK_FALSE; blend_attachment.colorWriteMask = 0xf; blend.attachmentCount = 1; blend.pAttachments = &blend_attachment; /* Viewport state */ viewport.viewportCount = 1; viewport.scissorCount = 1; /* Depth-stencil state */ depth_stencil.depthTestEnable = VK_FALSE; depth_stencil.depthWriteEnable = VK_FALSE; depth_stencil.depthBoundsTestEnable = VK_FALSE; depth_stencil.stencilTestEnable = VK_FALSE; depth_stencil.minDepthBounds = 0.0f; depth_stencil.maxDepthBounds = 1.0f; /* Multisample state */ multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisample.pNext = NULL; multisample.flags = 0; multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisample.sampleShadingEnable = VK_FALSE; multisample.minSampleShading = 0.0f; multisample.pSampleMask = NULL; multisample.alphaToCoverageEnable = VK_FALSE; multisample.alphaToOneEnable = VK_FALSE; /* Dynamic state */ dynamic.pDynamicStates = dynamics; dynamic.dynamicStateCount = ARRAY_SIZE(dynamics); pipe.stageCount = 2; pipe.pStages = shader_stages; pipe.pVertexInputState = &vertex_input; pipe.pInputAssemblyState = &input_assembly; pipe.pRasterizationState = &raster; pipe.pColorBlendState = &blend; pipe.pMultisampleState = &multisample; pipe.pViewportState = &viewport; pipe.pDepthStencilState = &depth_stencil; pipe.pDynamicState = &dynamic; pipe.renderPass = vk->render_pass; pipe.layout = vk->pipelines.layout; module_info.codeSize = sizeof(alpha_blend_vert); module_info.pCode = alpha_blend_vert; shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shader_stages[0].pName = "main"; vkCreateShaderModule(vk->context->device, &module_info, NULL, &shader_stages[0].module); blend_attachment.blendEnable = VK_TRUE; blend_attachment.colorWriteMask = 0xf; blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; /* Glyph pipeline */ module_info.codeSize = sizeof(font_frag); module_info.pCode = font_frag; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stages[1].pName = "main"; vkCreateShaderModule(vk->context->device, &module_info, NULL, &shader_stages[1].module); vkCreateGraphicsPipelines(vk->context->device, vk->pipelines.cache, 1, &pipe, NULL, &vk->pipelines.font); vkDestroyShaderModule(vk->context->device, shader_stages[1].module, NULL); /* Alpha-blended pipeline. */ module_info.codeSize = sizeof(alpha_blend_frag); module_info.pCode = alpha_blend_frag; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stages[1].pName = "main"; vkCreateShaderModule(vk->context->device, &module_info, NULL, &shader_stages[1].module); vkCreateGraphicsPipelines(vk->context->device, vk->pipelines.cache, 1, &pipe, NULL, &vk->pipelines.alpha_blend); /* Build display pipelines. */ for (i = 0; i < 4; i++) { input_assembly.topology = i & 2 ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; blend_attachment.blendEnable = i & 1; vkCreateGraphicsPipelines(vk->context->device, vk->pipelines.cache, 1, &pipe, NULL, &vk->display.pipelines[i]); } vkDestroyShaderModule(vk->context->device, shader_stages[1].module, NULL); #ifdef VULKAN_HDR_SWAPCHAIN blend_attachment.blendEnable = VK_FALSE; /* HDR pipeline. */ module_info.codeSize = sizeof(hdr_frag); module_info.pCode = hdr_frag; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stages[1].pName = "main"; vkCreateShaderModule(vk->context->device, &module_info, NULL, &shader_stages[1].module); vkCreateGraphicsPipelines(vk->context->device, vk->pipelines.cache, 1, &pipe, NULL, &vk->pipelines.hdr); /* Build display hdr pipelines. */ for (i = 4; i < 6; i++) { input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; vkCreateGraphicsPipelines(vk->context->device, vk->pipelines.cache, 1, &pipe, NULL, &vk->display.pipelines[i]); } vkDestroyShaderModule(vk->context->device, shader_stages[1].module, NULL); blend_attachment.blendEnable = VK_TRUE; #endif /* VULKAN_HDR_SWAPCHAIN */ vkDestroyShaderModule(vk->context->device, shader_stages[0].module, NULL); /* Other menu pipelines. */ for (i = 0; i < (int)ARRAY_SIZE(vk->display.pipelines) - 6; i++) { switch (i >> 1) { case 0: module_info.codeSize = sizeof(pipeline_ribbon_vert); module_info.pCode = pipeline_ribbon_vert; break; case 1: module_info.codeSize = sizeof(pipeline_ribbon_simple_vert); module_info.pCode = pipeline_ribbon_simple_vert; break; case 2: module_info.codeSize = sizeof(alpha_blend_vert); module_info.pCode = alpha_blend_vert; break; case 3: module_info.codeSize = sizeof(alpha_blend_vert); module_info.pCode = alpha_blend_vert; break; case 4: module_info.codeSize = sizeof(alpha_blend_vert); module_info.pCode = alpha_blend_vert; break; default: break; } shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shader_stages[0].pName = "main"; vkCreateShaderModule(vk->context->device, &module_info, NULL, &shader_stages[0].module); switch (i >> 1) { case 0: module_info.codeSize = sizeof(pipeline_ribbon_frag); module_info.pCode = pipeline_ribbon_frag; break; case 1: module_info.codeSize = sizeof(pipeline_ribbon_simple_frag); module_info.pCode = pipeline_ribbon_simple_frag; break; case 2: module_info.codeSize = sizeof(pipeline_snow_simple_frag); module_info.pCode = pipeline_snow_simple_frag; break; case 3: module_info.codeSize = sizeof(pipeline_snow_frag); module_info.pCode = pipeline_snow_frag; break; case 4: module_info.codeSize = sizeof(pipeline_bokeh_frag); module_info.pCode = pipeline_bokeh_frag; break; default: break; } shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stages[1].pName = "main"; vkCreateShaderModule(vk->context->device, &module_info, NULL, &shader_stages[1].module); switch (i >> 1) { case 0: case 1: blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR; blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; break; default: blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; break; } input_assembly.topology = i & 1 ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; vkCreateGraphicsPipelines(vk->context->device, vk->pipelines.cache, 1, &pipe, NULL, &vk->display.pipelines[6 + i]); vkDestroyShaderModule(vk->context->device, shader_stages[0].module, NULL); vkDestroyShaderModule(vk->context->device, shader_stages[1].module, NULL); } cpipe.layout = vk->pipelines.layout; cpipe.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; cpipe.stage.pName = "main"; cpipe.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; module_info.codeSize = sizeof(rgb565_to_rgba8888_comp); module_info.pCode = rgb565_to_rgba8888_comp; vkCreateShaderModule(vk->context->device, &module_info, NULL, &cpipe.stage.module); vkCreateComputePipelines(vk->context->device, vk->pipelines.cache, 1, &cpipe, NULL, &vk->pipelines.rgb565_to_rgba8888); vkDestroyShaderModule(vk->context->device, cpipe.stage.module, NULL); } static void vulkan_init_samplers(vk_t *vk) { VkSamplerCreateInfo info; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; info.pNext = NULL; info.flags = 0; info.magFilter = VK_FILTER_NEAREST; info.minFilter = VK_FILTER_NEAREST; info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; info.mipLodBias = 0.0f; info.anisotropyEnable = VK_FALSE; info.maxAnisotropy = 1.0f; info.compareEnable = VK_FALSE; info.minLod = 0.0f; info.maxLod = 0.0f; info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; info.unnormalizedCoordinates = VK_FALSE; vkCreateSampler(vk->context->device, &info, NULL, &vk->samplers.nearest); info.magFilter = VK_FILTER_LINEAR; info.minFilter = VK_FILTER_LINEAR; vkCreateSampler(vk->context->device, &info, NULL, &vk->samplers.linear); info.maxLod = VK_LOD_CLAMP_NONE; info.magFilter = VK_FILTER_NEAREST; info.minFilter = VK_FILTER_NEAREST; info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; vkCreateSampler(vk->context->device, &info, NULL, &vk->samplers.mipmap_nearest); info.magFilter = VK_FILTER_LINEAR; info.minFilter = VK_FILTER_LINEAR; info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; vkCreateSampler(vk->context->device, &info, NULL, &vk->samplers.mipmap_linear); } static void vulkan_buffer_chain_free( VkDevice device, struct vk_buffer_chain *chain) { struct vk_buffer_node *node = chain->head; while (node) { struct vk_buffer_node *next = node->next; vulkan_destroy_buffer(device, &node->buffer); free(node); node = next; } memset(chain, 0, sizeof(*chain)); } static void vulkan_deinit_buffers(vk_t *vk) { int i; for (i = 0; i < (int) vk->num_swapchain_images; i++) { vulkan_buffer_chain_free( vk->context->device, &vk->swapchain[i].vbo); vulkan_buffer_chain_free( vk->context->device, &vk->swapchain[i].ubo); } } static void vulkan_deinit_descriptor_pool(vk_t *vk) { int i; for (i = 0; i < (int) vk->num_swapchain_images; i++) vulkan_destroy_descriptor_manager( vk->context->device, &vk->swapchain[i].descriptor_manager); } static void vulkan_init_textures(vk_t *vk) { const uint32_t zero = 0; if (!(vk->flags & VK_FLAG_HW_ENABLE)) { int i; for (i = 0; i < (int) vk->num_swapchain_images; i++) { vk->swapchain[i].texture = vulkan_create_texture( vk, NULL, vk->tex_w, vk->tex_h, vk->tex_fmt, NULL, NULL, VULKAN_TEXTURE_STREAMED); { struct vk_texture *texture = &vk->swapchain[i].texture; VK_MAP_PERSISTENT_TEXTURE(vk->context->device, texture); } if (vk->swapchain[i].texture.type == VULKAN_TEXTURE_STAGING) vk->swapchain[i].texture_optimal = vulkan_create_texture( vk, NULL, vk->tex_w, vk->tex_h, vk->tex_fmt, NULL, NULL, VULKAN_TEXTURE_DYNAMIC); } } vk->default_texture = vulkan_create_texture(vk, NULL, 1, 1, VK_FORMAT_B8G8R8A8_UNORM, &zero, NULL, VULKAN_TEXTURE_STATIC); } static void vulkan_deinit_textures(vk_t *vk) { int i; video_driver_state_t *video_st = video_state_get_ptr(); /* Avoid memcpying from a destroyed/unmapped texture later on. */ const void *cached_frame = video_st->frame_cache_data; if (vulkan_is_mapped_swapchain_texture_ptr(vk, cached_frame)) video_st->frame_cache_data = NULL; vkDestroySampler(vk->context->device, vk->samplers.nearest, NULL); vkDestroySampler(vk->context->device, vk->samplers.linear, NULL); vkDestroySampler(vk->context->device, vk->samplers.mipmap_nearest, NULL); vkDestroySampler(vk->context->device, vk->samplers.mipmap_linear, NULL); for (i = 0; i < (int) vk->num_swapchain_images; i++) { if (vk->swapchain[i].texture.memory != VK_NULL_HANDLE) vulkan_destroy_texture( vk->context->device, &vk->swapchain[i].texture); if (vk->swapchain[i].texture_optimal.memory != VK_NULL_HANDLE) vulkan_destroy_texture( vk->context->device, &vk->swapchain[i].texture_optimal); } if (vk->default_texture.memory != VK_NULL_HANDLE) vulkan_destroy_texture(vk->context->device, &vk->default_texture); } static void vulkan_deinit_command_buffers(vk_t *vk) { int i; for (i = 0; i < (int) vk->num_swapchain_images; i++) { if (vk->swapchain[i].cmd) vkFreeCommandBuffers(vk->context->device, vk->swapchain[i].cmd_pool, 1, &vk->swapchain[i].cmd); vkDestroyCommandPool(vk->context->device, vk->swapchain[i].cmd_pool, NULL); } } static void vulkan_deinit_pipelines(vk_t *vk) { int i; vkDestroyPipelineLayout(vk->context->device, vk->pipelines.layout, NULL); vkDestroyDescriptorSetLayout(vk->context->device, vk->pipelines.set_layout, NULL); vkDestroyPipeline(vk->context->device, vk->pipelines.alpha_blend, NULL); vkDestroyPipeline(vk->context->device, vk->pipelines.font, NULL); vkDestroyPipeline(vk->context->device, vk->pipelines.rgb565_to_rgba8888, NULL); #ifdef VULKAN_HDR_SWAPCHAIN vkDestroyPipeline(vk->context->device, vk->pipelines.hdr, NULL); #endif /* VULKAN_HDR_SWAPCHAIN */ for (i = 0; i < (int)ARRAY_SIZE(vk->display.pipelines); i++) vkDestroyPipeline(vk->context->device, vk->display.pipelines[i], NULL); } static void vulkan_deinit_framebuffers(vk_t *vk) { int i; for (i = 0; i < (int) vk->num_swapchain_images; i++) { if (vk->backbuffers[i].framebuffer) vkDestroyFramebuffer(vk->context->device, vk->backbuffers[i].framebuffer, NULL); if (vk->backbuffers[i].view) vkDestroyImageView(vk->context->device, vk->backbuffers[i].view, NULL); } vkDestroyRenderPass(vk->context->device, vk->render_pass, NULL); } #ifdef VULKAN_HDR_SWAPCHAIN static void vulkan_set_hdr_max_nits(void* data, float max_nits) { vk_t *vk = (vk_t*)data; vulkan_hdr_uniform_t* mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; vk->hdr.max_output_nits = max_nits; mapped_ubo->max_nits = max_nits; } static void vulkan_set_hdr_paper_white_nits(void* data, float paper_white_nits) { vk_t *vk = (vk_t*)data; vulkan_hdr_uniform_t* mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; mapped_ubo->paper_white_nits = paper_white_nits; } static void vulkan_set_hdr_contrast(void* data, float contrast) { vk_t *vk = (vk_t*)data; vulkan_hdr_uniform_t* mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; mapped_ubo->contrast = contrast; } static void vulkan_set_hdr_expand_gamut(void* data, bool expand_gamut) { vk_t *vk = (vk_t*)data; vulkan_hdr_uniform_t* mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; mapped_ubo->expand_gamut = expand_gamut ? 1.0f : 0.0f; } static void vulkan_set_hdr_inverse_tonemap(vk_t* vk, bool inverse_tonemap) { vulkan_hdr_uniform_t* mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; mapped_ubo->inverse_tonemap = inverse_tonemap ? 1.0f : 0.0f; } static void vulkan_set_hdr10(vk_t* vk, bool hdr10) { vulkan_hdr_uniform_t* mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; mapped_ubo->hdr10 = hdr10 ? 1.0f : 0.0f; } #endif /* VULKAN_HDR_SWAPCHAIN */ static bool vulkan_init_default_filter_chain(vk_t *vk) { struct vulkan_filter_chain_create_info info; if (!vk->context) return false; info.device = vk->context->device; info.gpu = vk->context->gpu; info.memory_properties = &vk->context->memory_properties; info.pipeline_cache = vk->pipelines.cache; info.queue = vk->context->queue; info.command_pool = vk->swapchain[vk->context->current_frame_index].cmd_pool; info.num_passes = 0; info.original_format = VK_REMAP_TO_TEXFMT(vk->tex_fmt); info.max_input_size.width = vk->tex_w; info.max_input_size.height = vk->tex_h; info.swapchain.viewport = vk->vk_vp; info.swapchain.format = vk->context->swapchain_format; info.swapchain.render_pass = vk->render_pass; info.swapchain.num_indices = vk->context->num_swapchain_images; vk->filter_chain = vulkan_filter_chain_create_default( &info, vk->video.smooth ? GLSLANG_FILTER_CHAIN_LINEAR : GLSLANG_FILTER_CHAIN_NEAREST); if (!vk->filter_chain) { RARCH_ERR("Failed to create filter chain.\n"); return false; } #ifdef VULKAN_HDR_SWAPCHAIN if (vk->context->flags & VK_CTX_FLAG_HDR_ENABLE) { struct video_shader* shader_preset = vulkan_filter_chain_get_preset( vk->filter_chain); VkFormat rt_format = (shader_preset && shader_preset->passes) ? vulkan_filter_chain_get_pass_rt_format(vk->filter_chain, shader_preset->passes - 1) : VK_FORMAT_UNDEFINED; bool emits_hdr10 = shader_preset && shader_preset->passes && vulkan_filter_chain_emits_hdr10(vk->filter_chain); switch (rt_format) { case VK_FORMAT_A2B10G10R10_UNORM_PACK32: /* If the last shader pass uses a RGB10A2 back buffer * and HDR has been enabled, assume we want to skip * the inverse tonemapper and HDR10 conversion. * If we just inherited HDR10 format based on backbuffer, * we would have used RGBA8, and thus we should do inverse tonemap as expected. */ vulkan_set_hdr_inverse_tonemap(vk, !emits_hdr10); vulkan_set_hdr10(vk, !emits_hdr10); vk->flags |= VK_FLAG_SHOULD_RESIZE; break; case VK_FORMAT_R16G16B16A16_SFLOAT: /* If the last shader pass uses a RGBA16 backbuffer * and HDR has been enabled, assume we want to * skip the inverse tonemapper */ vulkan_set_hdr_inverse_tonemap(vk, false); vulkan_set_hdr10(vk, true); vk->flags |= VK_FLAG_SHOULD_RESIZE; break; case VK_FORMAT_UNDEFINED: default: vulkan_set_hdr_inverse_tonemap(vk, true); vulkan_set_hdr10(vk, true); break; } } #endif /* VULKAN_HDR_SWAPCHAIN */ return true; } static bool vulkan_init_filter_chain_preset(vk_t *vk, const char *shader_path) { struct vulkan_filter_chain_create_info info; info.device = vk->context->device; info.gpu = vk->context->gpu; info.memory_properties = &vk->context->memory_properties; info.pipeline_cache = vk->pipelines.cache; info.queue = vk->context->queue; info.command_pool = vk->swapchain[vk->context->current_frame_index].cmd_pool; info.num_passes = 0; info.original_format = VK_REMAP_TO_TEXFMT(vk->tex_fmt); info.max_input_size.width = vk->tex_w; info.max_input_size.height = vk->tex_h; info.swapchain.viewport = vk->vk_vp; info.swapchain.format = vk->context->swapchain_format; info.swapchain.render_pass = vk->render_pass; info.swapchain.num_indices = vk->context->num_swapchain_images; vk->filter_chain = vulkan_filter_chain_create_from_preset( &info, shader_path, vk->video.smooth ? GLSLANG_FILTER_CHAIN_LINEAR : GLSLANG_FILTER_CHAIN_NEAREST); if (!vk->filter_chain) { RARCH_ERR("[Vulkan]: Failed to create preset: \"%s\".\n", shader_path); return false; } #ifdef VULKAN_HDR_SWAPCHAIN if (vk->context->flags & VK_CTX_FLAG_HDR_ENABLE) { struct video_shader* shader_preset = vulkan_filter_chain_get_preset(vk->filter_chain); VkFormat rt_format = (shader_preset && shader_preset->passes) ? vulkan_filter_chain_get_pass_rt_format(vk->filter_chain, shader_preset->passes - 1) : VK_FORMAT_UNDEFINED; bool emits_hdr10 = shader_preset && shader_preset->passes && vulkan_filter_chain_emits_hdr10(vk->filter_chain); switch (rt_format) { case VK_FORMAT_A2B10G10R10_UNORM_PACK32: /* If the last shader pass uses a RGB10A2 backbuffer * and HDR has been enabled, assume we want to * skip the inverse tonemapper and HDR10 conversion * If we just inherited HDR10 format based on backbuffer, * we would have used RGBA8, and thus we should do inverse tonemap as expected. */ vulkan_set_hdr_inverse_tonemap(vk, !emits_hdr10); vulkan_set_hdr10(vk, !emits_hdr10); vk->flags |= VK_FLAG_SHOULD_RESIZE; break; case VK_FORMAT_R16G16B16A16_SFLOAT: /* If the last shader pass uses a RGBA16 backbuffer * and HDR has been enabled, assume we want to * skip the inverse tonemapper */ vulkan_set_hdr_inverse_tonemap(vk, false); vulkan_set_hdr10(vk, true); vk->flags |= VK_FLAG_SHOULD_RESIZE; break; case VK_FORMAT_UNDEFINED: default: vulkan_set_hdr_inverse_tonemap(vk, true); vulkan_set_hdr10(vk, true); break; } } #endif /* VULKAN_HDR_SWAPCHAIN */ return true; } static bool vulkan_init_filter_chain(vk_t *vk) { const char *shader_path = video_shader_get_current_shader_preset(); enum rarch_shader_type type = video_shader_parse_type(shader_path); if (string_is_empty(shader_path)) { RARCH_LOG("[Vulkan]: Loading stock shader.\n"); return vulkan_init_default_filter_chain(vk); } if (type != RARCH_SHADER_SLANG) { RARCH_LOG("[Vulkan]: Only Slang shaders are supported, falling back to stock.\n"); return vulkan_init_default_filter_chain(vk); } if (!shader_path || !vulkan_init_filter_chain_preset(vk, shader_path)) vulkan_init_default_filter_chain(vk); return true; } static void vulkan_init_static_resources(vk_t *vk) { int i; uint32_t blank[4 * 4]; VkCommandPoolCreateInfo pool_info; VkPipelineCacheCreateInfo cache; /* Create the pipeline cache. */ cache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; cache.pNext = NULL; cache.flags = 0; cache.initialDataSize = 0; cache.pInitialData = NULL; vkCreatePipelineCache(vk->context->device, &cache, NULL, &vk->pipelines.cache); pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; pool_info.pNext = NULL; pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; pool_info.queueFamilyIndex = vk->context->graphics_queue_index; vkCreateCommandPool(vk->context->device, &pool_info, NULL, &vk->staging_pool); for (i = 0; i < 4 * 4; i++) blank[i] = -1u; vk->display.blank_texture = vulkan_create_texture(vk, NULL, 4, 4, VK_FORMAT_B8G8R8A8_UNORM, blank, NULL, VULKAN_TEXTURE_STATIC); } static void vulkan_deinit_static_resources(vk_t *vk) { int i; vkDestroyPipelineCache(vk->context->device, vk->pipelines.cache, NULL); vulkan_destroy_texture( vk->context->device, &vk->display.blank_texture); vkDestroyCommandPool(vk->context->device, vk->staging_pool, NULL); free(vk->hw.cmd); free(vk->hw.wait_dst_stages); free(vk->hw.semaphores); for (i = 0; i < VULKAN_MAX_SWAPCHAIN_IMAGES; i++) if (vk->readback.staging[i].memory != VK_NULL_HANDLE) vulkan_destroy_texture( vk->context->device, &vk->readback.staging[i]); } static void vulkan_deinit_menu(vk_t *vk) { int i; for (i = 0; i < VULKAN_MAX_SWAPCHAIN_IMAGES; i++) { if (vk->menu.textures[i].memory) vulkan_destroy_texture( vk->context->device, &vk->menu.textures[i]); if (vk->menu.textures_optimal[i].memory) vulkan_destroy_texture( vk->context->device, &vk->menu.textures_optimal[i]); } } #ifdef VULKAN_HDR_SWAPCHAIN static void vulkan_destroy_hdr_buffer(VkDevice device, struct vk_image *img) { vkDestroyImageView(device, img->view, NULL); vkDestroyImage(device, img->image, NULL); vkDestroyFramebuffer(device, img->framebuffer, NULL); vkFreeMemory(device, img->memory, NULL); memset(img, 0, sizeof(*img)); } #endif static void vulkan_free(void *data) { vk_t *vk = (vk_t*)data; if (!vk) return; if (vk->context && vk->context->device) { #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif vulkan_deinit_pipelines(vk); vulkan_deinit_framebuffers(vk); vulkan_deinit_descriptor_pool(vk); vulkan_deinit_textures(vk); vulkan_deinit_buffers(vk); vulkan_deinit_command_buffers(vk); /* No need to init this since textures are create on-demand. */ vulkan_deinit_menu(vk); font_driver_free_osd(); vulkan_deinit_static_resources(vk); #ifdef HAVE_OVERLAY vulkan_overlay_free(vk); #endif if (vk->filter_chain) vulkan_filter_chain_free((vulkan_filter_chain_t*)vk->filter_chain); #ifdef VULKAN_HDR_SWAPCHAIN vulkan_destroy_buffer(vk->context->device, &vk->hdr.ubo); vulkan_destroy_hdr_buffer(vk->context->device, &vk->main_buffer); video_driver_unset_hdr_support(); #endif /* VULKAN_HDR_SWAPCHAIN */ if (vk->ctx_driver && vk->ctx_driver->destroy) vk->ctx_driver->destroy(vk->ctx_data); video_context_driver_free(); } scaler_ctx_gen_reset(&vk->readback.scaler_bgr); scaler_ctx_gen_reset(&vk->readback.scaler_rgb); free(vk); } static uint32_t vulkan_get_sync_index(void *handle) { vk_t *vk = (vk_t*)handle; return vk->context->current_frame_index; } static uint32_t vulkan_get_sync_index_mask(void *handle) { vk_t *vk = (vk_t*)handle; return (1 << vk->context->num_swapchain_images) - 1; } static void vulkan_set_image(void *handle, const struct retro_vulkan_image *image, uint32_t num_semaphores, const VkSemaphore *semaphores, uint32_t src_queue_family) { vk_t *vk = (vk_t*)handle; vk->hw.image = image; vk->hw.num_semaphores = num_semaphores; if (num_semaphores > 0) { int i; /* Allocate one extra in case we need to use WSI acquire semaphores. */ VkPipelineStageFlags *stage_flags = (VkPipelineStageFlags*)realloc(vk->hw.wait_dst_stages, sizeof(VkPipelineStageFlags) * (vk->hw.num_semaphores + 1)); VkSemaphore *new_semaphores = (VkSemaphore*)realloc(vk->hw.semaphores, sizeof(VkSemaphore) * (vk->hw.num_semaphores + 1)); vk->hw.wait_dst_stages = stage_flags; vk->hw.semaphores = new_semaphores; for (i = 0; i < (int) vk->hw.num_semaphores; i++) { vk->hw.wait_dst_stages[i] = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; vk->hw.semaphores[i] = semaphores[i]; } vk->flags |= VK_FLAG_HW_VALID_SEMAPHORE; vk->hw.src_queue_family = src_queue_family; } } static void vulkan_wait_sync_index(void *handle) { /* no-op. RetroArch already waits for this * in gfx_ctx_swap_buffers(). */ } static void vulkan_set_command_buffers(void *handle, uint32_t num_cmd, const VkCommandBuffer *cmd) { vk_t *vk = (vk_t*)handle; unsigned required_capacity = num_cmd + 1; if (required_capacity > vk->hw.capacity_cmd) { VkCommandBuffer *hw_cmd = (VkCommandBuffer*) realloc(vk->hw.cmd, sizeof(VkCommandBuffer) * required_capacity); vk->hw.cmd = hw_cmd; vk->hw.capacity_cmd = required_capacity; } vk->hw.num_cmd = num_cmd; memcpy(vk->hw.cmd, cmd, sizeof(VkCommandBuffer) * num_cmd); } static void vulkan_lock_queue(void *handle) { #ifdef HAVE_THREADS vk_t *vk = (vk_t*)handle; slock_lock(vk->context->queue_lock); #endif } static void vulkan_unlock_queue(void *handle) { #ifdef HAVE_THREADS vk_t *vk = (vk_t*)handle; slock_unlock(vk->context->queue_lock); #endif } static void vulkan_set_signal_semaphore(void *handle, VkSemaphore semaphore) { vk_t *vk = (vk_t*)handle; vk->hw.signal_semaphore = semaphore; } static void vulkan_init_hw_render(vk_t *vk) { struct retro_hw_render_interface_vulkan *iface = &vk->hw.iface; struct retro_hw_render_callback *hwr = video_driver_get_hw_context(); if (hwr->context_type != RETRO_HW_CONTEXT_VULKAN) return; vk->flags |= VK_FLAG_HW_ENABLE; iface->interface_type = RETRO_HW_RENDER_INTERFACE_VULKAN; iface->interface_version = RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION; iface->instance = vk->context->instance; iface->gpu = vk->context->gpu; iface->device = vk->context->device; iface->queue = vk->context->queue; iface->queue_index = vk->context->graphics_queue_index; iface->handle = vk; iface->set_image = vulkan_set_image; iface->get_sync_index = vulkan_get_sync_index; iface->get_sync_index_mask = vulkan_get_sync_index_mask; iface->wait_sync_index = vulkan_wait_sync_index; iface->set_command_buffers = vulkan_set_command_buffers; iface->lock_queue = vulkan_lock_queue; iface->unlock_queue = vulkan_unlock_queue; iface->set_signal_semaphore = vulkan_set_signal_semaphore; iface->get_device_proc_addr = vkGetDeviceProcAddr; iface->get_instance_proc_addr = vulkan_symbol_wrapper_instance_proc_addr(); } static void vulkan_init_readback(vk_t *vk, settings_t *settings) { /* Only bother with this if we're doing GPU recording. * Check recording_st->enable and not * driver.recording_data, because recording is * not initialized yet. */ recording_state_t *recording_st = recording_state_get_ptr(); bool recording_enabled = recording_st->enable; bool video_gpu_record = settings->bools.video_gpu_record; if (!(video_gpu_record && recording_enabled)) { vk->flags &= ~VK_FLAG_READBACK_STREAMED; return; } vk->flags |= VK_FLAG_READBACK_STREAMED; vk->readback.scaler_bgr.in_width = vk->vp.width; vk->readback.scaler_bgr.in_height = vk->vp.height; vk->readback.scaler_bgr.out_width = vk->vp.width; vk->readback.scaler_bgr.out_height = vk->vp.height; vk->readback.scaler_bgr.in_fmt = SCALER_FMT_ARGB8888; vk->readback.scaler_bgr.out_fmt = SCALER_FMT_BGR24; vk->readback.scaler_bgr.scaler_type = SCALER_TYPE_POINT; vk->readback.scaler_rgb.in_width = vk->vp.width; vk->readback.scaler_rgb.in_height = vk->vp.height; vk->readback.scaler_rgb.out_width = vk->vp.width; vk->readback.scaler_rgb.out_height = vk->vp.height; vk->readback.scaler_rgb.in_fmt = SCALER_FMT_ABGR8888; vk->readback.scaler_rgb.out_fmt = SCALER_FMT_BGR24; vk->readback.scaler_rgb.scaler_type = SCALER_TYPE_POINT; if (!scaler_ctx_gen_filter(&vk->readback.scaler_bgr)) { vk->flags &= ~VK_FLAG_READBACK_STREAMED; RARCH_ERR("[Vulkan]: Failed to initialize scaler context.\n"); } if (!scaler_ctx_gen_filter(&vk->readback.scaler_rgb)) { vk->flags &= ~VK_FLAG_READBACK_STREAMED; RARCH_ERR("[Vulkan]: Failed to initialize scaler context.\n"); } } static void *vulkan_init(const video_info_t *video, input_driver_t **input, void **input_data) { unsigned full_x, full_y; unsigned win_width; unsigned win_height; unsigned mode_width = 0; unsigned mode_height = 0; int interval = 0; unsigned temp_width = 0; unsigned temp_height = 0; const gfx_ctx_driver_t *ctx_driver = NULL; settings_t *settings = config_get_ptr(); #ifdef VULKAN_HDR_SWAPCHAIN vulkan_hdr_uniform_t* mapped_ubo = NULL; #endif vk_t *vk = (vk_t*)calloc(1, sizeof(*vk)); if (!vk) return NULL; ctx_driver = vulkan_get_context(vk, settings); if (!ctx_driver) { RARCH_ERR("[Vulkan]: Failed to get Vulkan context.\n"); goto error; } #ifdef VULKAN_HDR_SWAPCHAIN vk->hdr.max_output_nits = settings->floats.video_hdr_max_nits; vk->hdr.min_output_nits = 0.001f; vk->hdr.max_cll = 0.0f; vk->hdr.max_fall = 0.0f; #endif /* VULKAN_HDR_SWAPCHAIN */ vk->video = *video; vk->ctx_driver = ctx_driver; video_context_driver_set((const gfx_ctx_driver_t*)ctx_driver); RARCH_LOG("[Vulkan]: Found vulkan context: \"%s\".\n", ctx_driver->ident); if (vk->ctx_driver->get_video_size) vk->ctx_driver->get_video_size(vk->ctx_data, &mode_width, &mode_height); full_x = mode_width; full_y = mode_height; mode_width = 0; mode_height = 0; RARCH_LOG("[Vulkan]: Detecting screen resolution: %ux%u.\n", full_x, full_y); interval = video->vsync ? video->swap_interval : 0; if (ctx_driver->swap_interval) { bool adaptive_vsync_enabled = video_driver_test_all_flags( GFX_CTX_FLAGS_ADAPTIVE_VSYNC) && video->adaptive_vsync; if (adaptive_vsync_enabled && interval == 1) interval = -1; ctx_driver->swap_interval(vk->ctx_data, interval); } win_width = video->width; win_height = video->height; if (video->fullscreen && (win_width == 0) && (win_height == 0)) { win_width = full_x; win_height = full_y; } if ( !vk->ctx_driver->set_video_mode || !vk->ctx_driver->set_video_mode(vk->ctx_data, win_width, win_height, video->fullscreen)) { RARCH_ERR("[Vulkan]: Failed to set video mode.\n"); goto error; } if (vk->ctx_driver->get_video_size) vk->ctx_driver->get_video_size(vk->ctx_data, &mode_width, &mode_height); temp_width = mode_width; temp_height = mode_height; if (temp_width != 0 && temp_height != 0) video_driver_set_size(temp_width, temp_height); video_driver_get_size(&temp_width, &temp_height); vk->video_width = temp_width; vk->video_height = temp_height; RARCH_LOG("[Vulkan]: Using resolution %ux%u.\n", temp_width, temp_height); if (!vk->ctx_driver || !vk->ctx_driver->get_context_data) { RARCH_ERR("[Vulkan]: Failed to get context data.\n"); goto error; } *(void**)&vk->context = vk->ctx_driver->get_context_data(vk->ctx_data); if (video->vsync) vk->flags |= VK_FLAG_VSYNC; else vk->flags &= ~VK_FLAG_VSYNC; if (video->fullscreen) vk->flags |= VK_FLAG_FULLSCREEN; else vk->flags &= ~VK_FLAG_FULLSCREEN; vk->tex_w = RARCH_SCALE_BASE * video->input_scale; vk->tex_h = RARCH_SCALE_BASE * video->input_scale; vk->tex_fmt = video->rgb32 ? VK_FORMAT_B8G8R8A8_UNORM : VK_FORMAT_R5G6B5_UNORM_PACK16; if (video->force_aspect) vk->flags |= VK_FLAG_KEEP_ASPECT; else vk->flags &= ~VK_FLAG_KEEP_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. */ vulkan_set_viewport(vk, temp_width, temp_height, false, true); #ifdef VULKAN_HDR_SWAPCHAIN vk->hdr.ubo = vulkan_create_buffer(vk->context, sizeof(vulkan_hdr_uniform_t), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; mapped_ubo->mvp = vk->mvp_no_rot; mapped_ubo->max_nits = settings->floats.video_hdr_max_nits; mapped_ubo->paper_white_nits = settings->floats.video_hdr_paper_white_nits; mapped_ubo->contrast = VIDEO_HDR_MAX_CONTRAST - settings->floats.video_hdr_display_contrast; mapped_ubo->expand_gamut = settings->bools.video_hdr_expand_gamut; mapped_ubo->inverse_tonemap = 1.0f; /* Use this to turn on/off the inverse tonemap */ mapped_ubo->hdr10 = 1.0f; /* Use this to turn on/off the hdr10 */ #endif /* VULKAN_HDR_SWAPCHAIN */ vulkan_init_hw_render(vk); if (vk->context) { int i; static const VkDescriptorPoolSize pool_sizes[4] = { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS * 2 }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS }, }; vulkan_init_static_resources(vk); vk->num_swapchain_images = vk->context->num_swapchain_images; vulkan_init_render_pass(vk); vulkan_init_framebuffers(vk); vulkan_init_pipelines(vk); vulkan_init_samplers(vk); vulkan_init_textures(vk); for (i = 0; i < (int) vk->num_swapchain_images; i++) { VkCommandPoolCreateInfo pool_info; VkCommandBufferAllocateInfo info; vk->swapchain[i].descriptor_manager = vulkan_create_descriptor_manager( vk->context->device, pool_sizes, 4, vk->pipelines.set_layout); vk->swapchain[i].vbo = vulkan_buffer_chain_init( VULKAN_BUFFER_BLOCK_SIZE, 16, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); vk->swapchain[i].ubo = vulkan_buffer_chain_init( VULKAN_BUFFER_BLOCK_SIZE, vk->context->gpu_properties.limits.minUniformBufferOffsetAlignment, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; pool_info.pNext = NULL; /* RESET_COMMAND_BUFFER_BIT allows command buffer to be reset. */ pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; pool_info.queueFamilyIndex = vk->context->graphics_queue_index; vkCreateCommandPool(vk->context->device, &pool_info, NULL, &vk->swapchain[i].cmd_pool); info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; info.pNext = NULL; info.commandPool = vk->swapchain[i].cmd_pool; info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; info.commandBufferCount = 1; vkAllocateCommandBuffers(vk->context->device, &info, &vk->swapchain[i].cmd); } } if (!vulkan_init_filter_chain(vk)) { RARCH_ERR("[Vulkan]: Failed to init filter chain.\n"); goto error; } if (vk->ctx_driver->input_driver) { const char *joypad_name = settings->arrays.input_joypad_driver; vk->ctx_driver->input_driver( vk->ctx_data, joypad_name, input, input_data); } if (video->font_enable) font_driver_init_osd(vk, video, false, video->is_threaded, FONT_DRIVER_RENDER_VULKAN_API); #if OSX // The MoltenVK driver needs this, particularly after driver reinit vk->flags |= VK_FLAG_SHOULD_RESIZE; #endif vulkan_init_readback(vk, settings); return vk; error: vulkan_free(vk); return NULL; } static void vulkan_check_swapchain(vk_t *vk) { struct vulkan_filter_chain_swapchain_info filter_info; #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif vulkan_deinit_pipelines(vk); vulkan_deinit_framebuffers(vk); vulkan_deinit_descriptor_pool(vk); vulkan_deinit_textures(vk); vulkan_deinit_buffers(vk); vulkan_deinit_command_buffers(vk); if (vk->context) { int i; static const VkDescriptorPoolSize pool_sizes[4] = { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS * 2 }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS }, }; vk->num_swapchain_images = vk->context->num_swapchain_images; vulkan_init_render_pass(vk); vulkan_init_framebuffers(vk); vulkan_init_pipelines(vk); vulkan_init_samplers(vk); vulkan_init_textures(vk); for (i = 0; i < (int) vk->num_swapchain_images; i++) { VkCommandPoolCreateInfo pool_info; VkCommandBufferAllocateInfo info; vk->swapchain[i].descriptor_manager = vulkan_create_descriptor_manager( vk->context->device, pool_sizes, 4, vk->pipelines.set_layout); vk->swapchain[i].vbo = vulkan_buffer_chain_init( VULKAN_BUFFER_BLOCK_SIZE, 16, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); vk->swapchain[i].ubo = vulkan_buffer_chain_init( VULKAN_BUFFER_BLOCK_SIZE, vk->context->gpu_properties.limits.minUniformBufferOffsetAlignment, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; pool_info.pNext = NULL; /* RESET_COMMAND_BUFFER_BIT allows command buffer to be reset. */ pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; pool_info.queueFamilyIndex = vk->context->graphics_queue_index; vkCreateCommandPool(vk->context->device, &pool_info, NULL, &vk->swapchain[i].cmd_pool); info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; info.pNext = NULL; info.commandPool = vk->swapchain[i].cmd_pool; info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; info.commandBufferCount = 1; vkAllocateCommandBuffers(vk->context->device, &info, &vk->swapchain[i].cmd); } } vk->context->flags &= ~VK_CTX_FLAG_INVALID_SWAPCHAIN; filter_info.viewport = vk->vk_vp; filter_info.format = vk->context->swapchain_format; filter_info.render_pass = vk->render_pass; filter_info.num_indices = vk->context->num_swapchain_images; if ( !vulkan_filter_chain_update_swapchain_info( (vulkan_filter_chain_t*)vk->filter_chain, &filter_info) ) RARCH_ERR("Failed to update filter chain info. This will probably lead to a crash ...\n"); } static void vulkan_set_nonblock_state(void *data, bool state, bool adaptive_vsync_enabled, unsigned swap_interval) { vk_t *vk = (vk_t*)data; if (!vk) return; if (vk->ctx_driver->swap_interval) { int interval = 0; if (!state) interval = swap_interval; if (adaptive_vsync_enabled && interval == 1) interval = -1; vk->ctx_driver->swap_interval(vk->ctx_data, interval); } /* Changing vsync might require recreating the swapchain, * which means new VkImages to render into. */ if (vk->context->flags & VK_CTX_FLAG_INVALID_SWAPCHAIN) vulkan_check_swapchain(vk); } static bool vulkan_alive(void *data) { bool ret = false; bool quit = false; bool resize = false; vk_t *vk = (vk_t*)data; unsigned temp_width = vk->video_width; unsigned temp_height = vk->video_height; vk->ctx_driver->check_window(vk->ctx_data, &quit, &resize, &temp_width, &temp_height); if (quit) vk->flags |= VK_FLAG_QUITTING; else if (resize) vk->flags |= VK_FLAG_SHOULD_RESIZE; ret = (!(vk->flags & VK_FLAG_QUITTING)); if (temp_width != 0 && temp_height != 0) { video_driver_set_size(temp_width, temp_height); vk->video_width = temp_width; vk->video_height = temp_height; } return ret; } static bool vulkan_suppress_screensaver(void *data, bool enable) { bool enabled = enable; vk_t *vk = (vk_t*)data; if (vk->ctx_data && vk->ctx_driver->suppress_screensaver) return vk->ctx_driver->suppress_screensaver(vk->ctx_data, enabled); return false; } static bool vulkan_set_shader(void *data, enum rarch_shader_type type, const char *path) { vk_t *vk = (vk_t*)data; if (!vk) return false; if (vk->filter_chain) vulkan_filter_chain_free((vulkan_filter_chain_t*)vk->filter_chain); vk->filter_chain = NULL; if (!string_is_empty(path) && type != RARCH_SHADER_SLANG) { RARCH_WARN("[Vulkan]: Only Slang shaders are supported. Falling back to stock.\n"); path = NULL; } if (string_is_empty(path)) { vulkan_init_default_filter_chain(vk); return true; } if (!vulkan_init_filter_chain_preset(vk, path)) { RARCH_ERR("[Vulkan]: Failed to create filter chain: \"%s\". Falling back to stock.\n", path); vulkan_init_default_filter_chain(vk); return false; } return true; } static void vulkan_set_projection(vk_t *vk, struct video_ortho *ortho, bool allow_rotate) { float radians, cosine, sine; static math_matrix_4x4 rot = { { 0.0f, 0.0f, 0.0f, 0.0f , 0.0f, 0.0f, 0.0f, 0.0f , 0.0f, 0.0f, 0.0f, 0.0f , 0.0f, 0.0f, 0.0f, 1.0f } }; /* Calculate projection. */ matrix_4x4_ortho(vk->mvp_no_rot, ortho->left, ortho->right, ortho->bottom, ortho->top, ortho->znear, ortho->zfar); if (!allow_rotate) { vk->mvp = vk->mvp_no_rot; return; } radians = M_PI * vk->rotation / 180.0f; cosine = cosf(radians); sine = sinf(radians); MAT_ELEM_4X4(rot, 0, 0) = cosine; MAT_ELEM_4X4(rot, 0, 1) = -sine; MAT_ELEM_4X4(rot, 1, 0) = sine; MAT_ELEM_4X4(rot, 1, 1) = cosine; matrix_4x4_multiply(vk->mvp, rot, vk->mvp_no_rot); } static void vulkan_set_rotation(void *data, unsigned rotation) { vk_t *vk = (vk_t*)data; struct video_ortho ortho = {0, 1, 0, 1, -1, 1}; if (!vk) return; vk->rotation = 270 * rotation; vulkan_set_projection(vk, &ortho, true); } static void vulkan_set_video_mode(void *data, unsigned width, unsigned height, bool fullscreen) { vk_t *vk = (vk_t*)data; if (vk->ctx_driver->set_video_mode) vk->ctx_driver->set_video_mode(vk->ctx_data, width, height, fullscreen); } static void vulkan_set_viewport(void *data, unsigned viewport_width, unsigned viewport_height, bool force_full, bool allow_rotate) { int x = 0; int y = 0; float device_aspect = (float)viewport_width / viewport_height; struct video_ortho ortho = {0, 1, 0, 1, -1, 1}; settings_t *settings = config_get_ptr(); bool video_scale_integer = settings->bools.video_scale_integer; unsigned aspect_ratio_idx = settings->uints.video_aspect_ratio_idx; vk_t *vk = (vk_t*)data; if (vk->ctx_driver->translate_aspect) device_aspect = vk->ctx_driver->translate_aspect( vk->ctx_data, viewport_width, viewport_height); if (video_scale_integer && !force_full) { video_viewport_get_scaled_integer(&vk->vp, viewport_width, viewport_height, video_driver_get_aspect_ratio(), vk->flags & VK_FLAG_KEEP_ASPECT); viewport_width = vk->vp.width; viewport_height = vk->vp.height; } else if ((vk->flags & VK_FLAG_KEEP_ASPECT) && !force_full) { float desired_aspect = video_driver_get_aspect_ratio(); #if defined(HAVE_MENU) if (aspect_ratio_idx == ASPECT_RATIO_CUSTOM) { video_viewport_t *custom_vp = &settings->video_viewport_custom; /* Vulkan has top-left origin viewport. */ x = custom_vp->x; y = custom_vp->y; viewport_width = custom_vp->width; viewport_height = custom_vp->height; } else #endif { float delta; if (fabsf(device_aspect - desired_aspect) < 0.0001f) { /* If the aspect ratios of screen and desired aspect * ratio are sufficiently equal (floating point stuff), * assume they are actually equal. */ } else if (device_aspect > desired_aspect) { delta = (desired_aspect / device_aspect - 1.0f) / 2.0f + 0.5f; x = (int)roundf(viewport_width * (0.5f - delta)); viewport_width = (unsigned)roundf(2.0f * viewport_width * delta); } else { delta = (device_aspect / desired_aspect - 1.0f) / 2.0f + 0.5f; y = (int)roundf(viewport_height * (0.5f - delta)); viewport_height = (unsigned)roundf(2.0f * viewport_height * delta); } } vk->vp.x = x; vk->vp.y = y; vk->vp.width = viewport_width; vk->vp.height = viewport_height; } else { vk->vp.x = 0; vk->vp.y = 0; vk->vp.width = viewport_width; vk->vp.height = viewport_height; } #if defined(RARCH_MOBILE) /* In portrait mode, we want viewport to gravitate to top of screen. */ if (device_aspect < 1.0f) vk->vp.y = 0; #endif vulkan_set_projection(vk, &ortho, allow_rotate); /* Set last backbuffer viewport. */ if (!force_full) { vk->vp_out_width = viewport_width; vk->vp_out_height = viewport_height; } vk->vk_vp.x = (float)vk->vp.x; vk->vk_vp.y = (float)vk->vp.y; vk->vk_vp.width = (float)vk->vp.width; vk->vk_vp.height = (float)vk->vp.height; vk->vk_vp.minDepth = 0.0f; vk->vk_vp.maxDepth = 1.0f; vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; } static void vulkan_readback(vk_t *vk) { VkBufferImageCopy region; struct vk_texture *staging; struct video_viewport vp; VkMemoryBarrier barrier; vp.x = 0; vp.y = 0; vp.width = 0; vp.height = 0; vp.full_width = 0; vp.full_height = 0; vulkan_viewport_info(vk, &vp); region.bufferOffset = 0; region.bufferRowLength = 0; region.bufferImageHeight = 0; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.mipLevel = 0; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset.x = vp.x; region.imageOffset.y = vp.y; region.imageOffset.z = 0; region.imageExtent.width = vp.width; region.imageExtent.height = vp.height; region.imageExtent.depth = 1; staging = &vk->readback.staging[vk->context->current_frame_index]; *staging = vulkan_create_texture(vk, staging->memory != VK_NULL_HANDLE ? staging : NULL, vk->vp.width, vk->vp.height, VK_FORMAT_B8G8R8A8_UNORM, /* Formats don't matter for readback since it's a raw copy. */ NULL, NULL, VULKAN_TEXTURE_READBACK); vkCmdCopyImageToBuffer(vk->cmd, vk->backbuffer->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staging->buffer, 1, ®ion); /* Make the data visible to host. */ barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; barrier.pNext = NULL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT; vkCmdPipelineBarrier(vk->cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &barrier, 0, NULL, 0, NULL); } static void vulkan_inject_black_frame(vk_t *vk, video_frame_info_t *video_info) { VkSubmitInfo submit_info; VkCommandBufferBeginInfo begin_info; const VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; const VkClearColorValue clear_color = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; unsigned frame_index = vk->context->current_frame_index; unsigned swapchain_index = vk->context->current_swapchain_index; struct vk_per_frame *chain = &vk->swapchain[frame_index]; struct vk_image *backbuffer = &vk->backbuffers[swapchain_index]; vk->chain = chain; vk->cmd = chain->cmd; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.pNext = NULL; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.pInheritanceInfo = NULL; vkResetCommandBuffer(vk->cmd, 0); vkBeginCommandBuffer(vk->cmd, &begin_info); VULKAN_IMAGE_LAYOUT_TRANSITION(vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); vkCmdClearColorImage(vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, &range); VULKAN_IMAGE_LAYOUT_TRANSITION(vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); vkEndCommandBuffer(vk->cmd); submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = NULL; submit_info.waitSemaphoreCount = 0; submit_info.pWaitSemaphores = NULL; submit_info.pWaitDstStageMask = NULL; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &vk->cmd; submit_info.signalSemaphoreCount = 0; submit_info.pSignalSemaphores = NULL; if ( (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) && (vk->context->swapchain_semaphores[swapchain_index] != VK_NULL_HANDLE)) { submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &vk->context->swapchain_semaphores[swapchain_index]; } if ( (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) && (vk->context->swapchain_acquire_semaphore != VK_NULL_HANDLE)) { static const VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; vk->context->swapchain_wait_semaphores[frame_index] = vk->context->swapchain_acquire_semaphore; vk->context->swapchain_acquire_semaphore = VK_NULL_HANDLE; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &vk->context->swapchain_wait_semaphores[frame_index]; submit_info.pWaitDstStageMask = &wait_stage; } #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueSubmit(vk->context->queue, 1, &submit_info, vk->context->swapchain_fences[frame_index]); vk->context->swapchain_fences_signalled[frame_index] = true; #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif } #if defined(HAVE_MENU) /* VBO will be written to here. */ static void vulkan_draw_quad(vk_t *vk, const struct vk_draw_quad *quad) { if (quad->texture && quad->texture->image) vulkan_transition_texture(vk, vk->cmd, quad->texture); if (quad->pipeline != vk->tracker.pipeline) { VkRect2D sci; vkCmdBindPipeline(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, quad->pipeline); vk->tracker.pipeline = quad->pipeline; /* Changing pipeline invalidates dynamic state. */ vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; if (vk->flags & VK_FLAG_TRACKER_USE_SCISSOR) sci = vk->tracker.scissor; else { /* No scissor -> viewport */ sci.offset.x = vk->vp.x; sci.offset.y = vk->vp.y; sci.extent.width = vk->vp.width; sci.extent.height = vk->vp.height; } vkCmdSetViewport(vk->cmd, 0, 1, &vk->vk_vp); vkCmdSetScissor (vk->cmd, 0, 1, &sci); vk->tracker.dirty &= ~VULKAN_DIRTY_DYNAMIC_BIT; } else if (vk->tracker.dirty & VULKAN_DIRTY_DYNAMIC_BIT) { VkRect2D sci; if (vk->flags & VK_FLAG_TRACKER_USE_SCISSOR) sci = vk->tracker.scissor; else { /* No scissor -> viewport */ sci.offset.x = vk->vp.x; sci.offset.y = vk->vp.y; sci.extent.width = vk->vp.width; sci.extent.height = vk->vp.height; } vkCmdSetViewport(vk->cmd, 0, 1, &vk->vk_vp); vkCmdSetScissor (vk->cmd, 0, 1, &sci); vk->tracker.dirty &= ~VULKAN_DIRTY_DYNAMIC_BIT; } /* Upload descriptors */ { VkDescriptorSet set; struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->ubo, sizeof(*quad->mvp), &range)) return; if ( string_is_equal_fast(quad->mvp, &vk->tracker.mvp, sizeof(*quad->mvp)) || quad->texture->view != vk->tracker.view || quad->sampler != vk->tracker.sampler) { /* Upload UBO */ struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->ubo, sizeof(*quad->mvp), &range)) return; memcpy(range.data, quad->mvp, sizeof(*quad->mvp)); set = vulkan_descriptor_manager_alloc( vk->context->device, &vk->chain->descriptor_manager); vulkan_write_quad_descriptors( vk->context->device, set, range.buffer, range.offset, sizeof(*quad->mvp), quad->texture, quad->sampler); vkCmdBindDescriptorSets(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vk->pipelines.layout, 0, 1, &set, 0, NULL); vk->tracker.view = quad->texture->view; vk->tracker.sampler = quad->sampler; vk->tracker.mvp = *quad->mvp; } } /* Upload VBO */ { struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->vbo, 6 * sizeof(struct vk_vertex), &range)) return; { struct vk_vertex *pv = (struct vk_vertex*)range.data; const struct vk_color *color = &quad->color; VULKAN_WRITE_QUAD_VBO(pv, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, color); } vkCmdBindVertexBuffers(vk->cmd, 0, 1, &range.buffer, &range.offset); } /* Draw the quad */ vkCmdDraw(vk->cmd, 6, 1, 0, 0); } #endif static bool vulkan_frame(void *data, const void *frame, unsigned frame_width, unsigned frame_height, uint64_t frame_count, unsigned pitch, const char *msg, video_frame_info_t *video_info) { int i; VkSubmitInfo submit_info; VkClearValue clear_color; VkRenderPassBeginInfo rp_info; VkCommandBufferBeginInfo begin_info; VkSemaphore signal_semaphores[2]; vk_t *vk = (vk_t*)data; bool waits_for_semaphores = false; unsigned width = video_info->width; unsigned height = video_info->height; bool statistics_show = video_info->statistics_show; const char *stat_text = video_info->stat_text; unsigned black_frame_insertion = video_info->black_frame_insertion; bool input_driver_nonblock_state = video_info->input_driver_nonblock_state; bool runloop_is_slowmotion = video_info->runloop_is_slowmotion; bool runloop_is_paused = video_info->runloop_is_paused; unsigned video_width = video_info->width; unsigned video_height = video_info->height; struct font_params *osd_params = (struct font_params*) &video_info->osd_stat_params; #ifdef HAVE_MENU bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false; #endif #ifdef HAVE_GFX_WIDGETS bool widgets_active = video_info->widgets_active; #endif unsigned frame_index = vk->context->current_frame_index; unsigned swapchain_index = vk->context->current_swapchain_index; bool overlay_behind_menu = video_info->overlay_behind_menu; #ifdef VULKAN_HDR_SWAPCHAIN bool use_main_buffer = (vk->context->flags & VK_CTX_FLAG_HDR_ENABLE) && (!vk->filter_chain || !vulkan_filter_chain_emits_hdr10(vk->filter_chain)); #endif /* VULKAN_HDR_SWAPCHAIN */ /* Bookkeeping on start of frame. */ struct vk_per_frame *chain = &vk->swapchain[frame_index]; struct vk_image *backbuffer = &vk->backbuffers[swapchain_index]; struct vk_descriptor_manager *manager = &chain->descriptor_manager; struct vk_buffer_chain *buff_chain_vbo = &chain->vbo; struct vk_buffer_chain *buff_chain_ubo = &chain->ubo; vk->chain = chain; vk->backbuffer = backbuffer; VK_DESCRIPTOR_MANAGER_RESTART(manager); VK_BUFFER_CHAIN_DISCARD(buff_chain_vbo); VK_BUFFER_CHAIN_DISCARD(buff_chain_ubo); /* Start recording the command buffer. */ vk->cmd = chain->cmd; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.pNext = NULL; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.pInheritanceInfo = NULL; vkResetCommandBuffer(vk->cmd, 0); vkBeginCommandBuffer(vk->cmd, &begin_info); vk->tracker.dirty = 0; vk->tracker.scissor.offset.x = 0; vk->tracker.scissor.offset.y = 0; vk->tracker.scissor.extent.width = 0; vk->tracker.scissor.extent.height = 0; vk->flags &= ~VK_FLAG_TRACKER_USE_SCISSOR; vk->tracker.pipeline = VK_NULL_HANDLE; vk->tracker.view = VK_NULL_HANDLE; vk->tracker.sampler = VK_NULL_HANDLE; for (i = 0; i < 16; i++) vk->tracker.mvp.data[i] = 0.0f; waits_for_semaphores = (vk->flags & VK_FLAG_HW_ENABLE) && frame && !vk->hw.num_cmd && (vk->flags & VK_FLAG_HW_VALID_SEMAPHORE); if ( waits_for_semaphores && (vk->hw.src_queue_family != VK_QUEUE_FAMILY_IGNORED) && (vk->hw.src_queue_family != vk->context->graphics_queue_index)) { /* Acquire ownership of image from other queue family. */ VULKAN_TRANSFER_IMAGE_OWNERSHIP(vk->cmd, vk->hw.image->create_info.image, vk->hw.image->image_layout, /* Create a dependency chain from semaphore wait. */ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, vk->hw.src_queue_family, vk->context->graphics_queue_index); } /* Upload texture */ if (frame && (!(vk->flags & VK_FLAG_HW_ENABLE))) { unsigned y; uint8_t *dst = NULL; const uint8_t *src = (const uint8_t*)frame; unsigned bpp = vk->video.rgb32 ? 4 : 2; if ( chain->texture.width != frame_width || chain->texture.height != frame_height) { chain->texture = vulkan_create_texture(vk, &chain->texture, frame_width, frame_height, chain->texture.format, NULL, NULL, chain->texture_optimal.memory ? VULKAN_TEXTURE_STAGING : VULKAN_TEXTURE_STREAMED); { struct vk_texture *texture = &chain->texture; VK_MAP_PERSISTENT_TEXTURE(vk->context->device, texture); } if (chain->texture.type == VULKAN_TEXTURE_STAGING) chain->texture_optimal = vulkan_create_texture( vk, &chain->texture_optimal, frame_width, frame_height, chain->texture.format, /* Ensure we use the original format and not any remapped format. */ NULL, NULL, VULKAN_TEXTURE_DYNAMIC); } if (frame != chain->texture.mapped) { dst = (uint8_t*)chain->texture.mapped; if ( (chain->texture.stride == pitch ) && pitch == frame_width * bpp) memcpy(dst, src, frame_width * frame_height * bpp); else for (y = 0; y < frame_height; y++, dst += chain->texture.stride, src += pitch) memcpy(dst, src, frame_width * bpp); } VULKAN_SYNC_TEXTURE_TO_GPU_COND_OBJ(vk, chain->texture); /* If we have an optimal texture, copy to that now. */ if (chain->texture_optimal.memory != VK_NULL_HANDLE) { struct vk_texture *dynamic = &chain->texture_optimal; struct vk_texture *staging = &chain->texture; vulkan_copy_staging_to_dynamic(vk, vk->cmd, dynamic, staging); } vk->last_valid_index = frame_index; } /* Notify filter chain about the new sync index. */ vulkan_filter_chain_notify_sync_index( (vulkan_filter_chain_t*)vk->filter_chain, frame_index); vulkan_filter_chain_set_frame_count( (vulkan_filter_chain_t*)vk->filter_chain, frame_count); #ifdef HAVE_REWIND vulkan_filter_chain_set_frame_direction( (vulkan_filter_chain_t*)vk->filter_chain, state_manager_frame_is_reversed() ? -1 : 1); #else vulkan_filter_chain_set_frame_direction( (vulkan_filter_chain_t*)vk->filter_chain, 1); #endif vulkan_filter_chain_set_rotation( (vulkan_filter_chain_t*)vk->filter_chain, retroarch_get_rotation()); /* Render offscreen filter chain passes. */ { /* Set the source texture in the filter chain */ struct vulkan_filter_chain_texture input; if (vk->flags & VK_FLAG_HW_ENABLE) { /* Does this make that this can happen at all? */ if (vk->hw.image && vk->hw.image->create_info.image) { if (frame) { input.width = frame_width; input.height = frame_height; } else { input.width = vk->hw.last_width; input.height = vk->hw.last_height; } input.image = vk->hw.image->create_info.image; input.view = vk->hw.image->image_view; input.layout = vk->hw.image->image_layout; /* The format can change on a whim. */ input.format = vk->hw.image->create_info.format; } else { /* Fall back to the default, black texture. * This can happen if we restart the video * driver while in the menu. */ input.width = vk->default_texture.width; input.height = vk->default_texture.height; input.image = vk->default_texture.image; input.view = vk->default_texture.view; input.layout = vk->default_texture.layout; input.format = vk->default_texture.format; } vk->hw.last_width = input.width; vk->hw.last_height = input.height; } else { struct vk_texture *tex = &vk->swapchain[vk->last_valid_index].texture; if (vk->swapchain[vk->last_valid_index].texture_optimal.memory != VK_NULL_HANDLE) tex = &vk->swapchain[vk->last_valid_index].texture_optimal; else if (tex->image) vulkan_transition_texture(vk, vk->cmd, tex); input.image = tex->image; input.view = tex->view; input.layout = tex->layout; input.width = tex->width; input.height = tex->height; input.format = VK_FORMAT_UNDEFINED; /* It's already configured. */ } vulkan_filter_chain_set_input_texture((vulkan_filter_chain_t*) vk->filter_chain, &input); } vulkan_set_viewport(vk, width, height, false, true); vulkan_filter_chain_build_offscreen_passes( (vulkan_filter_chain_t*)vk->filter_chain, vk->cmd, &vk->vk_vp); #if defined(HAVE_MENU) /* Upload menu texture. */ if (vk->flags & VK_FLAG_MENU_ENABLE) { if ( vk->menu.textures[vk->menu.last_index].image != VK_NULL_HANDLE || vk->menu.textures[vk->menu.last_index].buffer != VK_NULL_HANDLE) { struct vk_texture *optimal = &vk->menu.textures_optimal[vk->menu.last_index]; struct vk_texture *texture = &vk->menu.textures[vk->menu.last_index]; if (optimal->memory != VK_NULL_HANDLE) { if (vk->menu.dirty[vk->menu.last_index]) { struct vk_texture *dynamic = optimal; struct vk_texture *staging = texture; VULKAN_SYNC_TEXTURE_TO_GPU_COND_PTR(vk, staging); vulkan_copy_staging_to_dynamic(vk, vk->cmd, dynamic, staging); vk->menu.dirty[vk->menu.last_index] = false; } } } } #endif #ifdef VULKAN_HDR_SWAPCHAIN if (use_main_buffer) backbuffer = &vk->main_buffer; #endif /* VULKAN_HDR_SWAPCHAIN */ /* Render to backbuffer. */ if ( (backbuffer->image != VK_NULL_HANDLE) && (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN)) { rp_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; rp_info.pNext = NULL; rp_info.renderPass = vk->render_pass; rp_info.framebuffer = backbuffer->framebuffer; rp_info.renderArea.offset.x = 0; rp_info.renderArea.offset.y = 0; rp_info.renderArea.extent.width = vk->context->swapchain_width; rp_info.renderArea.extent.height = vk->context->swapchain_height; rp_info.clearValueCount = 1; rp_info.pClearValues = &clear_color; clear_color.color.float32[0] = 0.0f; clear_color.color.float32[1] = 0.0f; clear_color.color.float32[2] = 0.0f; clear_color.color.float32[3] = 0.0f; /* Prepare backbuffer for rendering. */ VULKAN_IMAGE_LAYOUT_TRANSITION(vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); /* Begin render pass and set up viewport */ vkCmdBeginRenderPass(vk->cmd, &rp_info, VK_SUBPASS_CONTENTS_INLINE); vulkan_filter_chain_build_viewport_pass( (vulkan_filter_chain_t*)vk->filter_chain, vk->cmd, &vk->vk_vp, vk->mvp.data); #ifdef HAVE_OVERLAY if ((vk->flags & VK_FLAG_OVERLAY_ENABLE) && overlay_behind_menu) vulkan_render_overlay(vk, video_width, video_height); #endif #if defined(HAVE_MENU) if (vk->flags & VK_FLAG_MENU_ENABLE) { menu_driver_frame(menu_is_alive, video_info); if (vk->menu.textures[vk->menu.last_index].image != VK_NULL_HANDLE || vk->menu.textures[vk->menu.last_index].buffer != VK_NULL_HANDLE) { struct vk_draw_quad quad; struct vk_texture *optimal = &vk->menu.textures_optimal[vk->menu.last_index]; settings_t *settings = config_get_ptr(); bool menu_linear_filter = settings->bools.menu_linear_filter; vulkan_set_viewport(vk, width, height, ((vk->flags & VK_FLAG_MENU_FULLSCREEN) > 0), false); quad.pipeline = vk->pipelines.alpha_blend; quad.texture = &vk->menu.textures[vk->menu.last_index]; if (optimal->memory != VK_NULL_HANDLE) quad.texture = optimal; if (menu_linear_filter) quad.sampler = (optimal->flags & VK_TEX_FLAG_MIPMAP) ? vk->samplers.mipmap_linear : vk->samplers.linear; else quad.sampler = (optimal->flags & VK_TEX_FLAG_MIPMAP) ? vk->samplers.mipmap_nearest : vk->samplers.nearest; quad.mvp = &vk->mvp_no_rot; quad.color.r = 1.0f; quad.color.g = 1.0f; quad.color.b = 1.0f; quad.color.a = vk->menu.alpha; vulkan_draw_quad(vk, &quad); } } else if (statistics_show) { if (osd_params) font_driver_render_msg(vk, stat_text, osd_params, NULL); } #endif #ifdef HAVE_OVERLAY if ((vk->flags & VK_FLAG_OVERLAY_ENABLE) && !overlay_behind_menu) vulkan_render_overlay(vk, video_width, video_height); #endif if (!string_is_empty(msg)) font_driver_render_msg(vk, msg, NULL, NULL); #ifdef HAVE_GFX_WIDGETS if (widgets_active) gfx_widgets_frame(video_info); #endif /* End the render pass. We're done rendering to backbuffer now. */ vkCmdEndRenderPass(vk->cmd); #ifdef VULKAN_HDR_SWAPCHAIN /* Copy over back buffer to swap chain render targets */ if (use_main_buffer) { backbuffer = &vk->backbuffers[swapchain_index]; vulkan_hdr_uniform_t* mapped_ubo = (vulkan_hdr_uniform_t*)vk->hdr.ubo.mapped; mapped_ubo->mvp = vk->mvp_no_rot; rp_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; rp_info.pNext = NULL; rp_info.renderPass = vk->render_pass; rp_info.framebuffer = backbuffer->framebuffer; rp_info.renderArea.offset.x = 0; rp_info.renderArea.offset.y = 0; rp_info.renderArea.extent.width = vk->context->swapchain_width; rp_info.renderArea.extent.height = vk->context->swapchain_height; rp_info.clearValueCount = 1; rp_info.pClearValues = &clear_color; clear_color.color.float32[0] = 0.0f; clear_color.color.float32[1] = 0.0f; clear_color.color.float32[2] = 0.0f; clear_color.color.float32[3] = 0.0f; /* Prepare backbuffer for rendering. */ VULKAN_IMAGE_LAYOUT_TRANSITION(vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); VULKAN_IMAGE_LAYOUT_TRANSITION(vk->cmd, vk->main_buffer.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); /* Begin render pass and set up viewport */ vkCmdBeginRenderPass(vk->cmd, &rp_info, VK_SUBPASS_CONTENTS_INLINE); { if (vk->pipelines.hdr != vk->tracker.pipeline) { vkCmdBindPipeline(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vk->pipelines.hdr); vk->tracker.pipeline = vk->pipelines.hdr; /* Changing pipeline invalidates dynamic state. */ vk->tracker.dirty |= VULKAN_DIRTY_DYNAMIC_BIT; } } { VkWriteDescriptorSet write; VkDescriptorImageInfo image_info; VkDescriptorSet set = vulkan_descriptor_manager_alloc( vk->context->device, &vk->chain->descriptor_manager); VULKAN_SET_UNIFORM_BUFFER(vk->context->device, set, 0, vk->hdr.ubo.buffer, 0, vk->hdr.ubo.size); image_info.sampler = vk->samplers.nearest; image_info.imageView = vk->main_buffer.view; image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write.pNext = NULL; write.dstSet = set; write.dstBinding = 2; write.dstArrayElement = 0; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &image_info; write.pBufferInfo = NULL; write.pTexelBufferView = NULL; vkUpdateDescriptorSets(vk->context->device, 1, &write, 0, NULL); vkCmdBindDescriptorSets(vk->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vk->pipelines.layout, 0, 1, &set, 0, NULL); vk->tracker.view = vk->main_buffer.view; vk->tracker.sampler = vk->samplers.nearest; } { VkViewport viewport; VkRect2D sci; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = vk->context->swapchain_width; viewport.height = vk->context->swapchain_height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; sci.offset.x = (int32_t)viewport.x; sci.offset.y = (int32_t)viewport.y; sci.extent.width = (uint32_t)viewport.width; sci.extent.height = (uint32_t)viewport.height; vkCmdSetViewport(vk->cmd, 0, 1, &viewport); vkCmdSetScissor(vk->cmd, 0, 1, &sci); } /* Upload VBO */ { struct vk_buffer_range range; vulkan_buffer_chain_alloc(vk->context, &vk->chain->vbo, 6 * sizeof(struct vk_vertex), &range); { struct vk_vertex *pv = (struct vk_vertex*)range.data; struct vk_color color; color.r = 1.0f; color.g = 1.0f; color.b = 1.0f; color.a = 1.0f; VULKAN_WRITE_QUAD_VBO(pv, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, &color); } vkCmdBindVertexBuffers(vk->cmd, 0, 1, &range.buffer, &range.offset); } vkCmdDraw(vk->cmd, 6, 1, 0, 0); vkCmdEndRenderPass(vk->cmd); } #endif /* VULKAN_HDR_SWAPCHAIN */ } /* End the filter chain frame. * This must happen outside a render pass. */ vulkan_filter_chain_end_frame((vulkan_filter_chain_t*)vk->filter_chain, vk->cmd); if ( (backbuffer->image != VK_NULL_HANDLE) && (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) ) { if ( (vk->flags & VK_FLAG_READBACK_PENDING) || (vk->flags & VK_FLAG_READBACK_STREAMED)) { /* We cannot safely read back from an image which * has already been presented as we need to * maintain the PRESENT_SRC_KHR layout. * * If we're reading back, * perform the readback before presenting. */ VULKAN_IMAGE_LAYOUT_TRANSITION( vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); vulkan_readback(vk); /* Prepare for presentation after transfers are complete. */ VULKAN_IMAGE_LAYOUT_TRANSITION( vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 0, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); vk->flags &= ~VK_FLAG_READBACK_PENDING; } else { /* Prepare backbuffer for presentation. */ VULKAN_IMAGE_LAYOUT_TRANSITION( vk->cmd, backbuffer->image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); } } if ( waits_for_semaphores && (vk->hw.src_queue_family != VK_QUEUE_FAMILY_IGNORED) && (vk->hw.src_queue_family != vk->context->graphics_queue_index)) { /* Release ownership of image back to other queue family. */ VULKAN_TRANSFER_IMAGE_OWNERSHIP(vk->cmd, vk->hw.image->create_info.image, vk->hw.image->image_layout, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, vk->context->graphics_queue_index, vk->hw.src_queue_family); } vkEndCommandBuffer(vk->cmd); /* Submit command buffers to GPU. */ submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = NULL; if (vk->hw.num_cmd) { /* vk->hw.cmd has already been allocated for this. */ vk->hw.cmd[vk->hw.num_cmd] = vk->cmd; submit_info.commandBufferCount = vk->hw.num_cmd + 1; submit_info.pCommandBuffers = vk->hw.cmd; vk->hw.num_cmd = 0; } else { submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &vk->cmd; } if (waits_for_semaphores) { submit_info.waitSemaphoreCount = vk->hw.num_semaphores; submit_info.pWaitSemaphores = vk->hw.semaphores; submit_info.pWaitDstStageMask = vk->hw.wait_dst_stages; /* Consume the semaphores. */ vk->flags &= ~VK_FLAG_HW_VALID_SEMAPHORE; /* We allocated space for this. */ if ( (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) && (vk->context->swapchain_acquire_semaphore != VK_NULL_HANDLE)) { vk->context->swapchain_wait_semaphores[frame_index] = vk->context->swapchain_acquire_semaphore; vk->context->swapchain_acquire_semaphore = VK_NULL_HANDLE; vk->hw.semaphores[submit_info.waitSemaphoreCount] = vk->context->swapchain_wait_semaphores[frame_index]; vk->hw.wait_dst_stages[submit_info.waitSemaphoreCount] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; submit_info.waitSemaphoreCount++; } } else if ((vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) && (vk->context->swapchain_acquire_semaphore != VK_NULL_HANDLE)) { static const VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; vk->context->swapchain_wait_semaphores[frame_index] = vk->context->swapchain_acquire_semaphore; vk->context->swapchain_acquire_semaphore = VK_NULL_HANDLE; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &vk->context->swapchain_wait_semaphores[frame_index]; submit_info.pWaitDstStageMask = &wait_stage; } else { submit_info.waitSemaphoreCount = 0; submit_info.pWaitSemaphores = NULL; submit_info.pWaitDstStageMask = NULL; } submit_info.signalSemaphoreCount = 0; if ((vk->context->swapchain_semaphores[swapchain_index] != VK_NULL_HANDLE) && (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN)) signal_semaphores[submit_info.signalSemaphoreCount++] = vk->context->swapchain_semaphores[swapchain_index]; if (vk->hw.signal_semaphore != VK_NULL_HANDLE) { signal_semaphores[submit_info.signalSemaphoreCount++] = vk->hw.signal_semaphore; vk->hw.signal_semaphore = VK_NULL_HANDLE; } submit_info.pSignalSemaphores = submit_info.signalSemaphoreCount ? signal_semaphores : NULL; #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueSubmit(vk->context->queue, 1, &submit_info, vk->context->swapchain_fences[frame_index]); vk->context->swapchain_fences_signalled[frame_index] = true; #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif if (vk->ctx_driver->swap_buffers) vk->ctx_driver->swap_buffers(vk->ctx_data); if (!(vk->context->flags & VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK)) { if (vk->ctx_driver->update_window_title) vk->ctx_driver->update_window_title(vk->ctx_data); } /* Handle spurious swapchain invalidations as soon as we can, * i.e. right after swap buffers. */ #ifdef VULKAN_HDR_SWAPCHAIN bool video_hdr_enable = video_info->hdr_enable; if ( (vk->flags & VK_FLAG_SHOULD_RESIZE) || (((vk->context->flags & VK_CTX_FLAG_HDR_ENABLE) > 0) != video_hdr_enable)) #else if (vk->flags & VK_FLAG_SHOULD_RESIZE) #endif /* VULKAN_HDR_SWAPCHAIN */ { #ifdef VULKAN_HDR_SWAPCHAIN if (video_hdr_enable) { vk->context->flags |= VK_CTX_FLAG_HDR_ENABLE; #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif vulkan_destroy_hdr_buffer(vk->context->device, &vk->main_buffer); } else vk->context->flags &= ~VK_CTX_FLAG_HDR_ENABLE; #endif /* VULKAN_HDR_SWAPCHAIN */ gfx_ctx_mode_t mode; mode.width = width; mode.height = height; if (vk->ctx_driver->set_resize) vk->ctx_driver->set_resize(vk->ctx_data, mode.width, mode.height); #ifdef VULKAN_HDR_SWAPCHAIN if ( (vk->context->swapchain_colour_space) == VK_COLOR_SPACE_HDR10_ST2084_EXT) vk->flags |= VK_FLAG_HDR_SUPPORT; else { vk->flags &= ~VK_FLAG_HDR_SUPPORT; vk->context->flags &= ~VK_CTX_FLAG_HDR_ENABLE; } if (vk->context->flags & VK_CTX_FLAG_HDR_ENABLE) { VkMemoryRequirements mem_reqs; VkImageCreateInfo image_info; VkMemoryAllocateInfo alloc; VkImageViewCreateInfo view; VkFramebufferCreateInfo info; memset(&vk->main_buffer, 0, sizeof(vk->main_buffer)); /* Create the image */ image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; image_info.pNext = NULL; image_info.flags = 0; image_info.imageType = VK_IMAGE_TYPE_2D; image_info.format = vk->context->swapchain_format; image_info.extent.width = video_width; image_info.extent.height = video_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 = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; image_info.queueFamilyIndexCount= 0; image_info.pQueueFamilyIndices = NULL; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; vkCreateImage(vk->context->device, &image_info, NULL, &vk->main_buffer.image); vulkan_debug_mark_image(vk->context->device, vk->main_buffer.image); vkGetImageMemoryRequirements(vk->context->device, vk->main_buffer.image, &mem_reqs); alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc.pNext = NULL; alloc.allocationSize = mem_reqs.size; alloc.memoryTypeIndex = vulkan_find_memory_type( &vk->context->memory_properties, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); vkAllocateMemory(vk->context->device, &alloc, NULL, &vk->main_buffer.memory); vulkan_debug_mark_memory(vk->context->device, vk->main_buffer.memory); vkBindImageMemory(vk->context->device, vk->main_buffer.image, vk->main_buffer.memory, 0); /* Create an image view which we can render into. */ view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; view.pNext = NULL; view.flags = 0; view.image = vk->main_buffer.image; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = image_info.format; view.components.r = VK_COMPONENT_SWIZZLE_R; view.components.g = VK_COMPONENT_SWIZZLE_G; view.components.b = VK_COMPONENT_SWIZZLE_B; view.components.a = VK_COMPONENT_SWIZZLE_A; view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view.subresourceRange.baseMipLevel = 0; view.subresourceRange.levelCount = 1; view.subresourceRange.baseArrayLayer = 0; view.subresourceRange.layerCount = 1; vkCreateImageView(vk->context->device, &view, NULL, &vk->main_buffer.view); /* Create the framebuffer */ info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; info.pNext = NULL; info.flags = 0; info.renderPass = vk->render_pass; info.attachmentCount = 1; info.pAttachments = &vk->main_buffer.view; info.width = vk->context->swapchain_width; info.height = vk->context->swapchain_height; info.layers = 1; vkCreateFramebuffer(vk->context->device, &info, NULL, &vk->main_buffer.framebuffer); } #endif /* VULKAN_HDR_SWAPCHAIN */ vk->flags &= ~VK_FLAG_SHOULD_RESIZE; } if (vk->context->flags & VK_CTX_FLAG_INVALID_SWAPCHAIN) vulkan_check_swapchain(vk); /* Disable BFI during fast forward, slow-motion, * and pause to prevent flicker. */ if ( (backbuffer->image != VK_NULL_HANDLE) && (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) && black_frame_insertion && !input_driver_nonblock_state && !runloop_is_slowmotion && !runloop_is_paused && (!(vk->flags & VK_FLAG_MENU_ENABLE))) { int n; for (n = 0; n < (int) black_frame_insertion; ++n) { vulkan_inject_black_frame(vk, video_info); if (vk->ctx_driver->swap_buffers) vk->ctx_driver->swap_buffers(vk->ctx_data); } } /* Vulkan doesn't directly support swap_interval > 1, * so we fake it by duping out more frames. */ if ( (vk->context->swap_interval > 1) && (!(vk->context->flags & VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK))) { int i; vk->context->flags |= VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK; for (i = 1; i < (int) vk->context->swap_interval; i++) { if (!vulkan_frame(vk, NULL, 0, 0, frame_count, 0, msg, video_info)) { vk->context->flags &= ~VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK; return false; } } vk->context->flags &= ~VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK; } return true; } static void vulkan_set_aspect_ratio(void *data, unsigned aspect_ratio_idx) { vk_t *vk = (vk_t*)data; if (vk) vk->flags |= VK_FLAG_KEEP_ASPECT | VK_FLAG_SHOULD_RESIZE; } static void vulkan_apply_state_changes(void *data) { vk_t *vk = (vk_t*)data; if (vk) vk->flags |= VK_FLAG_SHOULD_RESIZE; } static void vulkan_show_mouse(void *data, bool state) { vk_t *vk = (vk_t*)data; if (vk && vk->ctx_driver->show_mouse) vk->ctx_driver->show_mouse(vk->ctx_data, state); } static struct video_shader *vulkan_get_current_shader(void *data) { vk_t *vk = (vk_t*)data; if (vk && vk->filter_chain) return vulkan_filter_chain_get_preset((vulkan_filter_chain_t*)vk->filter_chain); return NULL; } static bool vulkan_get_current_sw_framebuffer(void *data, struct retro_framebuffer *framebuffer) { struct vk_per_frame *chain = NULL; vk_t *vk = (vk_t*)data; vk->chain = &vk->swapchain[vk->context->current_frame_index]; chain = vk->chain; if (chain->texture.width != framebuffer->width || chain->texture.height != framebuffer->height) { chain->texture = vulkan_create_texture(vk, &chain->texture, framebuffer->width, framebuffer->height, chain->texture.format, NULL, NULL, VULKAN_TEXTURE_STREAMED); { struct vk_texture *texture = &chain->texture; VK_MAP_PERSISTENT_TEXTURE(vk->context->device, texture); } if (chain->texture.type == VULKAN_TEXTURE_STAGING) { chain->texture_optimal = vulkan_create_texture( vk, &chain->texture_optimal, framebuffer->width, framebuffer->height, chain->texture.format, /* Ensure we use the non-remapped format. */ NULL, NULL, VULKAN_TEXTURE_DYNAMIC); } } framebuffer->data = chain->texture.mapped; framebuffer->pitch = chain->texture.stride; framebuffer->format = vk->video.rgb32 ? RETRO_PIXEL_FORMAT_XRGB8888 : RETRO_PIXEL_FORMAT_RGB565; framebuffer->memory_flags = 0; if (vk->context->memory_properties.memoryTypes[ chain->texture.memory_type].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) framebuffer->memory_flags |= RETRO_MEMORY_TYPE_CACHED; return true; } static bool vulkan_is_mapped_swapchain_texture_ptr(const vk_t* vk, const void* ptr) { int i; for (i = 0; i < (int) vk->num_swapchain_images; i++) { if (ptr == vk->swapchain[i].texture.mapped) return true; } return false; } static bool vulkan_get_hw_render_interface(void *data, const struct retro_hw_render_interface **iface) { vk_t *vk = (vk_t*)data; *iface = (const struct retro_hw_render_interface*)&vk->hw.iface; return ((vk->flags & VK_FLAG_HW_ENABLE) > 0); } static void vulkan_set_texture_frame(void *data, const void *frame, bool rgb32, unsigned width, unsigned height, float alpha) { size_t y; unsigned stride; uint8_t *ptr = NULL; uint8_t *dst = NULL; const uint8_t *src = NULL; vk_t *vk = (vk_t*)data; unsigned idx = 0; struct vk_texture *texture = NULL; struct vk_texture *texture_optimal = NULL; VkFormat fmt = VK_FORMAT_B8G8R8A8_UNORM; bool do_memcpy = true; const VkComponentMapping *ptr_swizzle = NULL; if (!vk) return; if (!rgb32) { VkFormatProperties formatProperties; vkGetPhysicalDeviceFormatProperties(vk->context->gpu, VK_FORMAT_B4G4R4A4_UNORM_PACK16, &formatProperties); if (formatProperties.optimalTilingFeatures != 0) { static const VkComponentMapping br_swizzle = {VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A}; /* B4G4R4A4 must be supported, but R4G4B4A4 is optional, * just apply the swizzle in the image view instead. */ fmt = VK_FORMAT_B4G4R4A4_UNORM_PACK16; ptr_swizzle = &br_swizzle; } else do_memcpy = false; } idx = vk->context->current_frame_index; texture = &vk->menu.textures[idx]; texture_optimal = &vk->menu.textures_optimal[idx]; *texture = vulkan_create_texture(vk, texture->memory ? texture : NULL, width, height, fmt, NULL, ptr_swizzle, texture_optimal->memory ? VULKAN_TEXTURE_STAGING : VULKAN_TEXTURE_STREAMED); vkMapMemory(vk->context->device, texture->memory, texture->offset, texture->size, 0, (void**)&ptr); dst = ptr; src = (const uint8_t*)frame; stride = (rgb32 ? sizeof(uint32_t) : sizeof(uint16_t)) * width; if (do_memcpy) { for (y = 0; y < height; y++, dst += texture->stride, src += stride) memcpy(dst, src, stride); } else { for (y = 0; y < height; y++, dst += texture->stride, src += stride) { size_t x; uint16_t *srcpix = (uint16_t*)src; uint32_t *dstpix = (uint32_t*)dst; for (x = 0; x < width; x++, srcpix++, dstpix++) { uint32_t pix = *srcpix; *dstpix = ( (pix & 0xf000) >> 8) | ((pix & 0x0f00) << 4) | ((pix & 0x00f0) << 16) | ((pix & 0x000f) << 28); } } } vk->menu.alpha = alpha; vk->menu.last_index = idx; if (texture->type == VULKAN_TEXTURE_STAGING) *texture_optimal = vulkan_create_texture(vk, texture_optimal->memory ? texture_optimal : NULL, width, height, fmt, NULL, ptr_swizzle, VULKAN_TEXTURE_DYNAMIC); else { VULKAN_SYNC_TEXTURE_TO_GPU_COND_PTR(vk, texture); } vkUnmapMemory(vk->context->device, texture->memory); vk->menu.dirty[idx] = true; } static void vulkan_set_texture_enable(void *data, bool state, bool fullscreen) { vk_t *vk = (vk_t*)data; if (!vk) return; if (state) vk->flags |= VK_FLAG_MENU_ENABLE; else vk->flags &= ~VK_FLAG_MENU_ENABLE; if (fullscreen) vk->flags |= VK_FLAG_MENU_FULLSCREEN; else vk->flags &= ~VK_FLAG_MENU_FULLSCREEN; } #define VK_T0 0xff000000u #define VK_T1 0xffffffffu static uintptr_t vulkan_load_texture(void *video_data, void *data, bool threaded, enum texture_filter_type filter_type) { struct vk_texture *texture = NULL; vk_t *vk = (vk_t*)video_data; struct texture_image *image = (struct texture_image*)data; if (!image) return 0; if (!(texture = (struct vk_texture*)calloc(1, sizeof(*texture)))) return 0; if (!image->pixels || !image->width || !image->height) { /* Create a dummy texture instead. */ static const uint32_t checkerboard[] = { VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, VK_T1, VK_T0, }; *texture = vulkan_create_texture(vk, NULL, 8, 8, VK_FORMAT_B8G8R8A8_UNORM, checkerboard, NULL, VULKAN_TEXTURE_STATIC); texture->flags &= ~(VK_TEX_FLAG_DEFAULT_SMOOTH | VK_TEX_FLAG_MIPMAP); } else { *texture = vulkan_create_texture(vk, NULL, image->width, image->height, VK_FORMAT_B8G8R8A8_UNORM, image->pixels, NULL, VULKAN_TEXTURE_STATIC); if (filter_type == TEXTURE_FILTER_MIPMAP_LINEAR || filter_type == TEXTURE_FILTER_LINEAR) texture->flags |= VK_TEX_FLAG_DEFAULT_SMOOTH; if (filter_type == TEXTURE_FILTER_MIPMAP_LINEAR) texture->flags |= VK_TEX_FLAG_MIPMAP; } return (uintptr_t)texture; } static void vulkan_unload_texture(void *data, bool threaded, uintptr_t handle) { vk_t *vk = (vk_t*)data; struct vk_texture *texture = (struct vk_texture*)handle; if (!texture || !vk) return; /* TODO: We really want to defer this deletion instead, * but this will do for now. */ #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif vulkan_destroy_texture( vk->context->device, texture); free(texture); } static float vulkan_get_refresh_rate(void *data) { float refresh_rate; if (video_context_driver_get_refresh_rate(&refresh_rate)) return refresh_rate; return 0.0f; } static uint32_t vulkan_get_flags(void *data) { uint32_t flags = 0; BIT32_SET(flags, GFX_CTX_FLAGS_CUSTOMIZABLE_SWAPCHAIN_IMAGES); BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION); BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING); BIT32_SET(flags, GFX_CTX_FLAGS_SCREENSHOTS_SUPPORTED); BIT32_SET(flags, GFX_CTX_FLAGS_OVERLAY_BEHIND_MENU_SUPPORTED); return flags; } static void vulkan_get_video_output_size(void *data, unsigned *width, unsigned *height, char *desc, size_t desc_len) { vk_t *vk = (vk_t*)data; if (vk && vk->ctx_driver && vk->ctx_driver->get_video_output_size) vk->ctx_driver->get_video_output_size( vk->ctx_data, width, height, desc, desc_len); } static void vulkan_get_video_output_prev(void *data) { vk_t *vk = (vk_t*)data; if (vk && vk->ctx_driver && vk->ctx_driver->get_video_output_prev) vk->ctx_driver->get_video_output_prev(vk->ctx_data); } static void vulkan_get_video_output_next(void *data) { vk_t *vk = (vk_t*)data; if (vk && vk->ctx_driver && vk->ctx_driver->get_video_output_next) vk->ctx_driver->get_video_output_next(vk->ctx_data); } static const video_poke_interface_t vulkan_poke_interface = { vulkan_get_flags, vulkan_load_texture, vulkan_unload_texture, vulkan_set_video_mode, vulkan_get_refresh_rate, NULL, /* set_filtering */ vulkan_get_video_output_size, vulkan_get_video_output_prev, vulkan_get_video_output_next, NULL, /* get_current_framebuffer */ NULL, /* get_proc_address */ vulkan_set_aspect_ratio, vulkan_apply_state_changes, vulkan_set_texture_frame, vulkan_set_texture_enable, font_driver_render_msg, vulkan_show_mouse, NULL, /* grab_mouse_toggle */ vulkan_get_current_shader, vulkan_get_current_sw_framebuffer, vulkan_get_hw_render_interface, #ifdef VULKAN_HDR_SWAPCHAIN vulkan_set_hdr_max_nits, vulkan_set_hdr_paper_white_nits, vulkan_set_hdr_contrast, vulkan_set_hdr_expand_gamut #else NULL, /* set_hdr_max_nits */ NULL, /* set_hdr_paper_white_nits */ NULL, /* set_hdr_contrast */ NULL /* set_hdr_expand_gamut */ #endif /* VULKAN_HDR_SWAPCHAIN */ }; static void vulkan_get_poke_interface(void *data, const video_poke_interface_t **iface) { (void)data; *iface = &vulkan_poke_interface; } static void vulkan_viewport_info(void *data, struct video_viewport *vp) { unsigned width, height; vk_t *vk = (vk_t*)data; if (!vk) return; width = vk->video_width; height = vk->video_height; /* Make sure we get the correct viewport. */ vulkan_set_viewport(vk, width, height, false, true); *vp = vk->vp; vp->full_width = width; vp->full_height = height; } static bool vulkan_read_viewport(void *data, uint8_t *buffer, bool is_idle) { struct vk_texture *staging = NULL; vk_t *vk = (vk_t*)data; if (!vk) return false; staging = &vk->readback.staging[vk->context->current_frame_index]; if (vk->flags & VK_FLAG_READBACK_STREAMED) { const uint8_t *src = NULL; struct scaler_ctx *ctx = NULL; switch (vk->context->swapchain_format) { case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_A8B8G8R8_UNORM_PACK32: ctx = &vk->readback.scaler_rgb; break; case VK_FORMAT_B8G8R8A8_UNORM: ctx = &vk->readback.scaler_bgr; break; default: RARCH_ERR("[Vulkan]: Unexpected swapchain format. Cannot readback.\n"); break; } if (ctx) { if (staging->memory == VK_NULL_HANDLE) return false; buffer += 3 * (vk->vp.height - 1) * vk->vp.width; vkMapMemory(vk->context->device, staging->memory, staging->offset, staging->size, 0, (void**)&src); if ( (staging->flags & VK_TEX_FLAG_NEED_MANUAL_CACHE_MANAGEMENT) && (staging->memory != VK_NULL_HANDLE)) VULKAN_SYNC_TEXTURE_TO_CPU(vk->context->device, staging->memory); ctx->in_stride = (int)staging->stride; ctx->out_stride = -(int)vk->vp.width * 3; scaler_ctx_scale_direct(ctx, buffer, src); vkUnmapMemory(vk->context->device, staging->memory); } } else { /* Synchronous path only for now. */ /* TODO: How will we deal with format conversion? * For now, take the simplest route and use image blitting * with conversion. */ vk->flags |= VK_FLAG_READBACK_PENDING; if (!is_idle) video_driver_cached_frame(); #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif if (!staging->memory) { RARCH_ERR("[Vulkan]: Attempted to readback synchronously, but no image is present.\nThis can happen if vsync is disabled on Windows systems due to mailbox emulation.\n"); return false; } if (!staging->mapped) { VK_MAP_PERSISTENT_TEXTURE(vk->context->device, staging); } if ( (staging->flags & VK_TEX_FLAG_NEED_MANUAL_CACHE_MANAGEMENT) && (staging->memory != VK_NULL_HANDLE)) VULKAN_SYNC_TEXTURE_TO_CPU(vk->context->device, staging->memory); { int y; const uint8_t *src = (const uint8_t*)staging->mapped; buffer += 3 * (vk->vp.height - 1) * vk->vp.width; switch (vk->context->swapchain_format) { case VK_FORMAT_B8G8R8A8_UNORM: for (y = 0; y < (int) vk->vp.height; y++, src += staging->stride, buffer -= 3 * vk->vp.width) { int x; for (x = 0; x < (int) vk->vp.width; x++) { buffer[3 * x + 0] = src[4 * x + 0]; buffer[3 * x + 1] = src[4 * x + 1]; buffer[3 * x + 2] = src[4 * x + 2]; } } break; case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_A8B8G8R8_UNORM_PACK32: for (y = 0; y < (int) vk->vp.height; y++, src += staging->stride, buffer -= 3 * vk->vp.width) { int x; for (x = 0; x < (int) vk->vp.width; x++) { buffer[3 * x + 2] = src[4 * x + 0]; buffer[3 * x + 1] = src[4 * x + 1]; buffer[3 * x + 0] = src[4 * x + 2]; } } break; default: RARCH_ERR("[Vulkan]: Unexpected swapchain format.\n"); break; } } vulkan_destroy_texture( vk->context->device, staging); } return true; } #ifdef HAVE_OVERLAY static void vulkan_overlay_enable(void *data, bool enable) { vk_t *vk = (vk_t*)data; if (!vk) return; if (enable) vk->flags |= VK_FLAG_OVERLAY_ENABLE; else vk->flags &= ~VK_FLAG_OVERLAY_ENABLE; if (vk->ctx_driver->show_mouse) vk->ctx_driver->show_mouse(vk->ctx_data, enable); } static void vulkan_overlay_full_screen(void *data, bool enable) { vk_t *vk = (vk_t*)data; if (!vk) return; if (enable) vk->flags |= VK_FLAG_OVERLAY_FULLSCREEN; else vk->flags &= ~VK_FLAG_OVERLAY_FULLSCREEN; } static void vulkan_overlay_free(vk_t *vk) { int i; if (!vk) return; free(vk->overlay.vertex); for (i = 0; i < (int) vk->overlay.count; i++) if (vk->overlay.images[i].memory != VK_NULL_HANDLE) vulkan_destroy_texture( vk->context->device, &vk->overlay.images[i]); if (vk->overlay.images) free(vk->overlay.images); memset(&vk->overlay, 0, sizeof(vk->overlay)); } static void vulkan_overlay_set_alpha(void *data, unsigned image, float mod) { int i; struct vk_vertex *pv; vk_t *vk = (vk_t*)data; if (!vk) return; pv = &vk->overlay.vertex[image * 4]; for (i = 0; i < 4; i++) { pv[i].color.r = 1.0f; pv[i].color.g = 1.0f; pv[i].color.b = 1.0f; pv[i].color.a = mod; } } static void vulkan_render_overlay(vk_t *vk, unsigned width, unsigned height) { int i; struct video_viewport vp; if (!vk) return; vp = vk->vp; vulkan_set_viewport(vk, width, height, ((vk->flags & VK_FLAG_OVERLAY_FULLSCREEN) > 0), false); for (i = 0; i < (int) vk->overlay.count; i++) { struct vk_draw_triangles call; struct vk_buffer_range range; if (!vulkan_buffer_chain_alloc(vk->context, &vk->chain->vbo, 4 * sizeof(struct vk_vertex), &range)) break; memcpy(range.data, &vk->overlay.vertex[i * 4], 4 * sizeof(struct vk_vertex)); call.vertices = 4; call.uniform_size = sizeof(vk->mvp); call.uniform = &vk->mvp; call.vbo = ⦥ call.texture = &vk->overlay.images[i]; call.pipeline = vk->display.pipelines[3]; /* Strip with blend */ call.sampler = (call.texture->flags & VK_TEX_FLAG_MIPMAP) ? vk->samplers.mipmap_linear : vk->samplers.linear; vulkan_draw_triangles(vk, &call); } /* Restore the viewport so we don't mess with recording. */ vk->vp = vp; } static void vulkan_overlay_vertex_geom(void *data, unsigned image, float x, float y, float w, float h) { struct vk_vertex *pv = NULL; vk_t *vk = (vk_t*)data; if (!vk) return; pv = &vk->overlay.vertex[4 * image]; pv[0].x = x; pv[0].y = y; pv[1].x = x; pv[1].y = y + h; pv[2].x = x + w; pv[2].y = y; pv[3].x = x + w; pv[3].y = y + h; } static void vulkan_overlay_tex_geom(void *data, unsigned image, float x, float y, float w, float h) { struct vk_vertex *pv = NULL; vk_t *vk = (vk_t*)data; if (!vk) return; pv = &vk->overlay.vertex[4 * image]; pv[0].tex_x = x; pv[0].tex_y = y; pv[1].tex_x = x; pv[1].tex_y = y + h; pv[2].tex_x = x + w; pv[2].tex_y = y; pv[3].tex_x = x + w; pv[3].tex_y = y + h; } static bool vulkan_overlay_load(void *data, const void *image_data, unsigned num_images) { int i; bool old_enabled = false; const struct texture_image *images = (const struct texture_image*)image_data; vk_t *vk = (vk_t*)data; static const struct vk_color white = { 1.0f, 1.0f, 1.0f, 1.0f, }; if (!vk) return false; #ifdef HAVE_THREADS slock_lock(vk->context->queue_lock); #endif vkQueueWaitIdle(vk->context->queue); #ifdef HAVE_THREADS slock_unlock(vk->context->queue_lock); #endif if (vk->flags & VK_FLAG_OVERLAY_ENABLE) old_enabled = true; vulkan_overlay_free(vk); if (!(vk->overlay.images = (struct vk_texture*) calloc(num_images, sizeof(*vk->overlay.images)))) goto error; vk->overlay.count = num_images; if (!(vk->overlay.vertex = (struct vk_vertex*) calloc(4 * num_images, sizeof(*vk->overlay.vertex)))) goto error; for (i = 0; i < (int) num_images; i++) { int j; vk->overlay.images[i] = vulkan_create_texture(vk, NULL, images[i].width, images[i].height, VK_FORMAT_B8G8R8A8_UNORM, images[i].pixels, NULL, VULKAN_TEXTURE_STATIC); vulkan_overlay_tex_geom(vk, i, 0, 0, 1, 1); vulkan_overlay_vertex_geom(vk, i, 0, 0, 1, 1); for (j = 0; j < 4; j++) vk->overlay.vertex[4 * i + j].color = white; } if (old_enabled) vk->flags |= VK_FLAG_OVERLAY_ENABLE; else vk->flags &= ~VK_FLAG_OVERLAY_ENABLE; return true; error: vulkan_overlay_free(vk); return false; } static const video_overlay_interface_t vulkan_overlay_interface = { vulkan_overlay_enable, vulkan_overlay_load, vulkan_overlay_tex_geom, vulkan_overlay_vertex_geom, vulkan_overlay_full_screen, vulkan_overlay_set_alpha, }; static void vulkan_get_overlay_interface(void *data, const video_overlay_interface_t **iface) { *iface = &vulkan_overlay_interface; } #endif #ifdef HAVE_GFX_WIDGETS static bool vulkan_gfx_widgets_enabled(void *data) { return true; } #endif static bool vulkan_has_windowed(void *data) { vk_t *vk = (vk_t*)data; if (vk && vk->ctx_driver) return vk->ctx_driver->has_windowed; return false; } static bool vulkan_focus(void *data) { vk_t *vk = (vk_t*)data; if (vk && vk->ctx_driver && vk->ctx_driver->has_focus) return vk->ctx_driver->has_focus(vk->ctx_data); return true; } video_driver_t video_vulkan = { vulkan_init, vulkan_frame, vulkan_set_nonblock_state, vulkan_alive, vulkan_focus, vulkan_suppress_screensaver, vulkan_has_windowed, vulkan_set_shader, vulkan_free, "vulkan", vulkan_set_viewport, vulkan_set_rotation, vulkan_viewport_info, vulkan_read_viewport, NULL, /* read_frame_raw */ #ifdef HAVE_OVERLAY vulkan_get_overlay_interface, #endif vulkan_get_poke_interface, NULL, /* wrap_type_to_enum */ #ifdef HAVE_GFX_WIDGETS vulkan_gfx_widgets_enabled #endif };