mirror of
https://github.com/libretro/RetroArch
synced 2025-02-01 09:32:58 +00:00
2178b6d10f
Both swapchain recreation methods are proper andwithin the Vulkan specs. The differnece is retroarch follows method (apparently proposed in vulkan samples) that "hopes" the driver will reuse some of the old swapchain resources, while the other method destroys everything and recreates from scratch. At the moment on Nvidia drivers the second method is stable while the first method is unreliable in all cases today.
2638 lines
84 KiB
C
2638 lines
84 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2016-2017 - Hans-Kristian Arntzen
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <retro_assert.h>
|
|
#include <dynamic/dylib.h>
|
|
#include <string/stdstring.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "../../config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_X11
|
|
#ifdef HAVE_XCB
|
|
#include <X11/Xlib-xcb.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include "vulkan_common.h"
|
|
#include "../../libretro-common/include/retro_timers.h"
|
|
#include "../../configuration.h"
|
|
|
|
static dylib_t vulkan_library;
|
|
static VkInstance cached_instance;
|
|
static VkDevice cached_device;
|
|
static retro_vulkan_destroy_device_t cached_destroy_device;
|
|
|
|
#ifdef VULKAN_DEBUG
|
|
static VKAPI_ATTR VkBool32 VKAPI_CALL vulkan_debug_cb(
|
|
VkDebugReportFlagsEXT flags,
|
|
VkDebugReportObjectTypeEXT objectType,
|
|
uint64_t object,
|
|
size_t location,
|
|
int32_t messageCode,
|
|
const char *pLayerPrefix,
|
|
const char *pMessage,
|
|
void *pUserData)
|
|
{
|
|
(void)objectType;
|
|
(void)object;
|
|
(void)location;
|
|
(void)messageCode;
|
|
(void)pUserData;
|
|
|
|
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Error: %s: %s\n",
|
|
pLayerPrefix, pMessage);
|
|
}
|
|
else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT)
|
|
{
|
|
RARCH_WARN("[Vulkan]: Warning: %s: %s\n",
|
|
pLayerPrefix, pMessage);
|
|
}
|
|
else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)
|
|
{
|
|
RARCH_LOG("[Vulkan]: Performance warning: %s: %s\n",
|
|
pLayerPrefix, pMessage);
|
|
}
|
|
else
|
|
{
|
|
RARCH_LOG("[Vulkan]: Information: %s: %s\n",
|
|
pLayerPrefix, pMessage);
|
|
}
|
|
|
|
return VK_FALSE;
|
|
}
|
|
#endif
|
|
|
|
uint32_t vulkan_find_memory_type(
|
|
const VkPhysicalDeviceMemoryProperties *mem_props,
|
|
uint32_t device_reqs, uint32_t host_reqs)
|
|
{
|
|
uint32_t i;
|
|
for (i = 0; i < VK_MAX_MEMORY_TYPES; i++)
|
|
{
|
|
if ((device_reqs & (1u << i)) &&
|
|
(mem_props->memoryTypes[i].propertyFlags & host_reqs) == host_reqs)
|
|
return i;
|
|
}
|
|
|
|
RARCH_ERR("[Vulkan]: Failed to find valid memory type. This should never happen.");
|
|
abort();
|
|
}
|
|
|
|
uint32_t vulkan_find_memory_type_fallback(
|
|
const VkPhysicalDeviceMemoryProperties *mem_props,
|
|
uint32_t device_reqs, uint32_t host_reqs_first,
|
|
uint32_t host_reqs_second)
|
|
{
|
|
uint32_t i;
|
|
for (i = 0; i < VK_MAX_MEMORY_TYPES; i++)
|
|
{
|
|
if ((device_reqs & (1u << i)) &&
|
|
(mem_props->memoryTypes[i].propertyFlags & host_reqs_first) == host_reqs_first)
|
|
return i;
|
|
}
|
|
|
|
if (host_reqs_first == 0)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to find valid memory type. This should never happen.");
|
|
abort();
|
|
}
|
|
|
|
return vulkan_find_memory_type_fallback(mem_props,
|
|
device_reqs, host_reqs_second, 0);
|
|
}
|
|
|
|
void vulkan_transfer_image_ownership(VkCommandBuffer cmd,
|
|
VkImage image, VkImageLayout layout,
|
|
VkPipelineStageFlags src_stages,
|
|
VkPipelineStageFlags dst_stages,
|
|
uint32_t src_queue_family,
|
|
uint32_t dst_queue_family)
|
|
{
|
|
VkImageMemoryBarrier barrier =
|
|
{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
|
|
|
|
barrier.srcAccessMask = 0;
|
|
barrier.dstAccessMask = 0;
|
|
barrier.oldLayout = layout;
|
|
barrier.newLayout = layout;
|
|
barrier.srcQueueFamilyIndex = src_queue_family;
|
|
barrier.dstQueueFamilyIndex = dst_queue_family;
|
|
barrier.image = image;
|
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
|
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
|
|
|
|
vkCmdPipelineBarrier(cmd, src_stages, dst_stages,
|
|
false, 0, NULL, 0, NULL, 1, &barrier);
|
|
}
|
|
|
|
void vulkan_map_persistent_texture(
|
|
VkDevice device,
|
|
struct vk_texture *texture)
|
|
{
|
|
vkMapMemory(device, texture->memory, texture->offset,
|
|
texture->size, 0, &texture->mapped);
|
|
}
|
|
|
|
void vulkan_copy_staging_to_dynamic(vk_t *vk, VkCommandBuffer cmd,
|
|
struct vk_texture *dynamic,
|
|
struct vk_texture *staging)
|
|
{
|
|
VkImageCopy region;
|
|
|
|
retro_assert(dynamic->type == VULKAN_TEXTURE_DYNAMIC);
|
|
retro_assert(staging->type == VULKAN_TEXTURE_STAGING);
|
|
|
|
vulkan_sync_texture_to_gpu(vk, staging);
|
|
vulkan_transition_texture(vk, cmd, staging);
|
|
|
|
/* We don't have to sync against previous TRANSFER,
|
|
* since we observed the completion by fences.
|
|
*
|
|
* If we have a single texture_optimal, we would need to sync against
|
|
* previous transfers to avoid races.
|
|
*
|
|
* We would also need to optionally maintain extra textures due to
|
|
* changes in resolution, so this seems like the sanest and
|
|
* simplest solution. */
|
|
vulkan_image_layout_transition(vk, 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);
|
|
|
|
memset(®ion, 0, sizeof(region));
|
|
region.extent.width = dynamic->width;
|
|
region.extent.height = dynamic->height;
|
|
region.extent.depth = 1;
|
|
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.srcSubresource.layerCount = 1;
|
|
region.dstSubresource = region.srcSubresource;
|
|
|
|
vkCmdCopyImage(cmd,
|
|
staging->image, VK_IMAGE_LAYOUT_GENERAL,
|
|
dynamic->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
1, ®ion);
|
|
|
|
vulkan_image_layout_transition(vk, 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;
|
|
}
|
|
|
|
#ifdef VULKAN_DEBUG_TEXTURE_ALLOC
|
|
static VkImage vk_images[4 * 1024];
|
|
static unsigned vk_count;
|
|
|
|
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;
|
|
}
|
|
|
|
static unsigned track_seq;
|
|
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
|
|
|
|
void vulkan_sync_texture_to_gpu(vk_t *vk, const struct vk_texture *tex)
|
|
{
|
|
VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };
|
|
if (!tex || !tex->need_manual_cache_management || tex->memory == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
range.memory = tex->memory;
|
|
range.offset = 0;
|
|
range.size = VK_WHOLE_SIZE;
|
|
vkFlushMappedMemoryRanges(vk->context->device, 1, &range);
|
|
}
|
|
|
|
void vulkan_sync_texture_to_cpu(vk_t *vk, const struct vk_texture *tex)
|
|
{
|
|
VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };
|
|
if (!tex || !tex->need_manual_cache_management || tex->memory == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
range.memory = tex->memory;
|
|
range.offset = 0;
|
|
range.size = VK_WHOLE_SIZE;
|
|
vkInvalidateMappedMemoryRanges(vk->context->device, 1, &range);
|
|
}
|
|
|
|
static unsigned vulkan_num_miplevels(unsigned width, unsigned height)
|
|
{
|
|
unsigned size = MAX(width, height);
|
|
unsigned levels = 0;
|
|
while (size)
|
|
{
|
|
levels++;
|
|
size >>= 1;
|
|
}
|
|
return levels;
|
|
}
|
|
|
|
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;
|
|
struct vk_texture tex;
|
|
VkMemoryRequirements mem_reqs;
|
|
VkSubresourceLayout layout;
|
|
VkDevice device = vk->context->device;
|
|
VkImageCreateInfo info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
|
VkImageViewCreateInfo view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
|
|
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
|
VkImageSubresource subresource = { VK_IMAGE_ASPECT_COLOR_BIT };
|
|
VkCommandBufferAllocateInfo cmd_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
|
|
VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
|
|
VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
|
|
|
memset(&tex, 0, sizeof(tex));
|
|
|
|
info.imageType = VK_IMAGE_TYPE_2D;
|
|
info.format = format;
|
|
info.extent.width = width;
|
|
info.extent.height = height;
|
|
info.extent.depth = 1;
|
|
info.arrayLayers = 1;
|
|
|
|
/* For simplicity, always build mipmaps for
|
|
* static textures, samplers can be used to enable it dynamically.
|
|
*/
|
|
if (type == VULKAN_TEXTURE_STATIC)
|
|
{
|
|
info.mipLevels = vulkan_num_miplevels(width, height);
|
|
tex.mipmap = true;
|
|
}
|
|
else
|
|
info.mipLevels = 1;
|
|
|
|
info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
if (type == VULKAN_TEXTURE_STREAMED)
|
|
{
|
|
VkFormatProperties format_properties;
|
|
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)
|
|
{
|
|
RARCH_LOG("[Vulkan]: GPU does not support using linear images as textures. Falling back to copy path.\n");
|
|
type = VULKAN_TEXTURE_STAGING;
|
|
}
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case VULKAN_TEXTURE_STATIC:
|
|
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:
|
|
info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
info.tiling = VK_IMAGE_TILING_LINEAR;
|
|
info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
|
break;
|
|
|
|
case VULKAN_TEXTURE_READBACK:
|
|
info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
info.tiling = VK_IMAGE_TILING_LINEAR;
|
|
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
break;
|
|
}
|
|
|
|
vkCreateImage(device, &info, NULL, &tex.image);
|
|
#if 0
|
|
vulkan_track_alloc(tex.image);
|
|
#endif
|
|
vkGetImageMemoryRequirements(device, tex.image, &mem_reqs);
|
|
alloc.allocationSize = mem_reqs.size;
|
|
|
|
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);
|
|
|
|
tex.need_manual_cache_management =
|
|
(vk->context->memory_properties.memoryTypes[alloc.memoryTypeIndex].propertyFlags &
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0;
|
|
break;
|
|
}
|
|
|
|
/* 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 ... */
|
|
RARCH_LOG("[Vulkan]: GPU supports linear images as textures, but not DEVICE_LOCAL. Falling back to copy path.\n");
|
|
type = VULKAN_TEXTURE_STAGING;
|
|
vkDestroyImage(device, tex.image, NULL);
|
|
|
|
info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
vkCreateImage(device, &info, NULL, &tex.image);
|
|
|
|
vkGetImageMemoryRequirements(device, tex.image, &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);
|
|
}
|
|
|
|
/* We're not reusing the objects themselves. */
|
|
if (old && old->view != VK_NULL_HANDLE)
|
|
vkDestroyImageView(vk->context->device, old->view, NULL);
|
|
if (old && old->image != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyImage(vk->context->device, old->image, NULL);
|
|
#ifdef VULKAN_DEBUG_TEXTURE_ALLOC
|
|
vulkan_track_dealloc(old->image);
|
|
#endif
|
|
}
|
|
|
|
/* 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);
|
|
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));
|
|
}
|
|
|
|
vkBindImageMemory(device, tex.image, tex.memory, 0);
|
|
|
|
if (type != VULKAN_TEXTURE_STAGING && type != VULKAN_TEXTURE_READBACK)
|
|
{
|
|
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.levelCount = info.mipLevels;
|
|
view.subresourceRange.layerCount = 1;
|
|
|
|
vkCreateImageView(device, &view, NULL, &tex.view);
|
|
}
|
|
else
|
|
tex.view = VK_NULL_HANDLE;
|
|
|
|
if (info.tiling == VK_IMAGE_TILING_LINEAR)
|
|
vkGetImageSubresourceLayout(device, tex.image, &subresource, &layout);
|
|
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 && (type == VULKAN_TEXTURE_STREAMED || type == 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);
|
|
|
|
vulkan_sync_texture_to_gpu(vk, &tex);
|
|
vkUnmapMemory(device, tex.memory);
|
|
}
|
|
else if (initial && type == VULKAN_TEXTURE_STATIC)
|
|
{
|
|
VkImageCopy region;
|
|
VkCommandBuffer staging;
|
|
struct vk_texture tmp = vulkan_create_texture(vk, NULL,
|
|
width, height, format, initial, NULL, VULKAN_TEXTURE_STAGING);
|
|
|
|
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.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
|
|
vkBeginCommandBuffer(staging, &begin_info);
|
|
|
|
vulkan_image_layout_transition(vk, staging, tmp.image,
|
|
VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
|
VK_PIPELINE_STAGE_HOST_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
|
|
|
/* 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(vk,
|
|
staging,
|
|
tex.image,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
tex.mipmap ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
0, VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
|
|
|
memset(®ion, 0, sizeof(region));
|
|
region.extent.width = width;
|
|
region.extent.height = height;
|
|
region.extent.depth = 1;
|
|
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.srcSubresource.layerCount = 1;
|
|
region.dstSubresource = region.srcSubresource;
|
|
|
|
vkCmdCopyImage(staging,
|
|
tmp.image,
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
tex.image,
|
|
tex.mipmap ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
1, ®ion);
|
|
|
|
if (tex.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(vk, 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(vk, staging, tex.image,
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
vulkan_image_layout_transition(vk, staging, tex.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);
|
|
}
|
|
|
|
vkEndCommandBuffer(staging);
|
|
submit_info.commandBufferCount = 1;
|
|
submit_info.pCommandBuffers = &staging;
|
|
|
|
#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;
|
|
}
|
|
return tex;
|
|
}
|
|
|
|
void vulkan_destroy_texture(
|
|
VkDevice device,
|
|
struct vk_texture *tex)
|
|
{
|
|
if (tex->mapped)
|
|
vkUnmapMemory(device, tex->memory);
|
|
vkFreeMemory(device, tex->memory, NULL);
|
|
if (tex->view)
|
|
vkDestroyImageView(device, tex->view, NULL);
|
|
vkDestroyImage(device, tex->image, NULL);
|
|
#ifdef VULKAN_DEBUG_TEXTURE_ALLOC
|
|
vulkan_track_dealloc(tex->image);
|
|
#endif
|
|
memset(tex, 0, sizeof(*tex));
|
|
}
|
|
|
|
static void vulkan_write_quad_descriptors(
|
|
VkDevice device,
|
|
VkDescriptorSet set,
|
|
VkBuffer buffer,
|
|
VkDeviceSize offset,
|
|
VkDeviceSize range,
|
|
const struct vk_texture *texture,
|
|
VkSampler sampler)
|
|
{
|
|
VkDescriptorBufferInfo buffer_info;
|
|
VkWriteDescriptorSet write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
|
|
|
|
buffer_info.buffer = buffer;
|
|
buffer_info.offset = offset;
|
|
buffer_info.range = range;
|
|
|
|
write.dstSet = set;
|
|
write.dstBinding = 0;
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
write.pBufferInfo = &buffer_info;
|
|
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);
|
|
}
|
|
}
|
|
|
|
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(vk, cmd, texture->image,
|
|
texture->layout, VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
|
VK_PIPELINE_STAGE_HOST_BIT,
|
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
|
break;
|
|
|
|
case VULKAN_TEXTURE_STAGING:
|
|
vulkan_image_layout_transition(vk, cmd, texture->image,
|
|
texture->layout, VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
|
VK_PIPELINE_STAGE_HOST_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
|
break;
|
|
|
|
default:
|
|
retro_assert(0 && "Attempting to transition invalid texture type.\n");
|
|
break;
|
|
}
|
|
texture->layout = VK_IMAGE_LAYOUT_GENERAL;
|
|
}
|
|
|
|
static void vulkan_check_dynamic_state(
|
|
vk_t *vk)
|
|
{
|
|
|
|
if (vk->tracker.dirty & VULKAN_DIRTY_DYNAMIC_BIT)
|
|
{
|
|
VkRect2D sci;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void vulkan_draw_triangles(vk_t *vk, const struct vk_draw_triangles *call)
|
|
{
|
|
if (call->texture)
|
|
vulkan_transition_texture(vk, vk->cmd, call->texture);
|
|
|
|
if (call->pipeline != vk->tracker.pipeline)
|
|
{
|
|
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;
|
|
}
|
|
|
|
vulkan_check_dynamic_state(vk);
|
|
|
|
/* Upload descriptors */
|
|
{
|
|
VkDescriptorSet set;
|
|
|
|
/* Upload UBO */
|
|
struct vk_buffer_range range;
|
|
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;
|
|
memset(&vk->tracker.mvp, 0, sizeof(vk->tracker.mvp));
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
void vulkan_draw_quad(vk_t *vk, const struct vk_draw_quad *quad)
|
|
{
|
|
vulkan_transition_texture(vk, vk->cmd, quad->texture);
|
|
|
|
if (quad->pipeline != vk->tracker.pipeline)
|
|
{
|
|
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;
|
|
}
|
|
|
|
vulkan_check_dynamic_state(vk);
|
|
|
|
/* 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;
|
|
|
|
vulkan_write_quad_vbo((struct vk_vertex*)range.data,
|
|
0.0f, 0.0f, 1.0f, 1.0f,
|
|
0.0f, 0.0f, 1.0f, 1.0f,
|
|
&quad->color);
|
|
|
|
vkCmdBindVertexBuffers(vk->cmd, 0, 1,
|
|
&range.buffer, &range.offset);
|
|
}
|
|
|
|
/* Draw the quad */
|
|
vkCmdDraw(vk->cmd, 6, 1, 0, 0);
|
|
}
|
|
|
|
void vulkan_image_layout_transition(
|
|
vk_t *vk,
|
|
VkCommandBuffer cmd, VkImage image,
|
|
VkImageLayout old_layout,
|
|
VkImageLayout new_layout,
|
|
VkAccessFlags srcAccess,
|
|
VkAccessFlags dstAccess,
|
|
VkPipelineStageFlags srcStages,
|
|
VkPipelineStageFlags dstStages)
|
|
{
|
|
VkImageMemoryBarrier barrier =
|
|
{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
|
|
|
|
barrier.srcAccessMask = srcAccess;
|
|
barrier.dstAccessMask = dstAccess;
|
|
barrier.oldLayout = old_layout;
|
|
barrier.newLayout = new_layout;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.image = image;
|
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
barrier.subresourceRange.levelCount = 1;
|
|
barrier.subresourceRange.layerCount = 1;
|
|
|
|
vkCmdPipelineBarrier(cmd,
|
|
srcStages,
|
|
dstStages,
|
|
0,
|
|
0, NULL,
|
|
0, NULL,
|
|
1, &barrier);
|
|
}
|
|
|
|
struct vk_buffer vulkan_create_buffer(
|
|
const struct vulkan_context *context,
|
|
size_t size, VkBufferUsageFlags usage)
|
|
{
|
|
struct vk_buffer buffer;
|
|
VkMemoryRequirements mem_reqs;
|
|
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
|
VkBufferCreateInfo info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
|
|
|
info.size = size;
|
|
info.usage = usage;
|
|
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
vkCreateBuffer(context->device, &info, NULL, &buffer.buffer);
|
|
|
|
vkGetBufferMemoryRequirements(context->device, buffer.buffer, &mem_reqs);
|
|
|
|
alloc.allocationSize = mem_reqs.size;
|
|
alloc.memoryTypeIndex = vulkan_find_memory_type(
|
|
&context->memory_properties,
|
|
mem_reqs.memoryTypeBits,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
|
vkAllocateMemory(context->device, &alloc, NULL, &buffer.memory);
|
|
vkBindBufferMemory(context->device, buffer.buffer, buffer.memory, 0);
|
|
|
|
buffer.size = size;
|
|
|
|
vkMapMemory(context->device,
|
|
buffer.memory, 0, buffer.size, 0, &buffer.mapped);
|
|
return buffer;
|
|
}
|
|
|
|
void vulkan_destroy_buffer(
|
|
VkDevice device,
|
|
struct vk_buffer *buffer)
|
|
{
|
|
vkUnmapMemory(device, buffer->memory);
|
|
vkFreeMemory(device, buffer->memory, NULL);
|
|
|
|
vkDestroyBuffer(device, buffer->buffer, NULL);
|
|
|
|
memset(buffer, 0, sizeof(*buffer));
|
|
}
|
|
|
|
static struct vk_descriptor_pool *vulkan_alloc_descriptor_pool(
|
|
VkDevice device,
|
|
const struct vk_descriptor_manager *manager)
|
|
{
|
|
unsigned i;
|
|
VkDescriptorPoolCreateInfo pool_info = {
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
|
|
VkDescriptorSetAllocateInfo alloc_info = {
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
|
|
|
|
struct vk_descriptor_pool *pool =
|
|
(struct vk_descriptor_pool*)calloc(1, sizeof(*pool));
|
|
if (!pool)
|
|
return NULL;
|
|
|
|
pool_info.maxSets = VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS;
|
|
pool_info.poolSizeCount = manager->num_sizes;
|
|
pool_info.pPoolSizes = manager->sizes;
|
|
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
|
|
|
vkCreateDescriptorPool(device, &pool_info, NULL, &pool->pool);
|
|
|
|
/* Just allocate all descriptor sets up front. */
|
|
alloc_info.descriptorPool = pool->pool;
|
|
alloc_info.descriptorSetCount = 1;
|
|
alloc_info.pSetLayouts = &manager->set_layout;
|
|
|
|
for (i = 0; i < VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS; i++)
|
|
vkAllocateDescriptorSets(device, &alloc_info, &pool->sets[i]);
|
|
|
|
return pool;
|
|
}
|
|
|
|
VkDescriptorSet vulkan_descriptor_manager_alloc(
|
|
VkDevice device, struct vk_descriptor_manager *manager)
|
|
{
|
|
if (manager->count < VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS)
|
|
return manager->current->sets[manager->count++];
|
|
|
|
while (manager->current->next)
|
|
{
|
|
manager->current = manager->current->next;
|
|
manager->count = 0;
|
|
return manager->current->sets[manager->count++];
|
|
}
|
|
|
|
manager->current->next = vulkan_alloc_descriptor_pool(device, manager);
|
|
retro_assert(manager->current->next);
|
|
|
|
manager->current = manager->current->next;
|
|
manager->count = 0;
|
|
return manager->current->sets[manager->count++];
|
|
}
|
|
|
|
void vulkan_descriptor_manager_restart(struct vk_descriptor_manager *manager)
|
|
{
|
|
manager->current = manager->head;
|
|
manager->count = 0;
|
|
}
|
|
|
|
struct vk_descriptor_manager vulkan_create_descriptor_manager(
|
|
VkDevice device,
|
|
const VkDescriptorPoolSize *sizes,
|
|
unsigned num_sizes,
|
|
VkDescriptorSetLayout set_layout)
|
|
{
|
|
struct vk_descriptor_manager manager;
|
|
memset(&manager, 0, sizeof(manager));
|
|
retro_assert(num_sizes <= VULKAN_MAX_DESCRIPTOR_POOL_SIZES);
|
|
memcpy(manager.sizes, sizes, num_sizes * sizeof(*sizes));
|
|
manager.num_sizes = num_sizes;
|
|
manager.set_layout = set_layout;
|
|
|
|
manager.head = vulkan_alloc_descriptor_pool(device, &manager);
|
|
retro_assert(manager.head);
|
|
return manager;
|
|
}
|
|
|
|
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 void vulkan_buffer_chain_step(struct vk_buffer_chain *chain)
|
|
{
|
|
chain->current = chain->current->next;
|
|
chain->offset = 0;
|
|
}
|
|
|
|
static bool vulkan_buffer_chain_suballoc(struct vk_buffer_chain *chain,
|
|
size_t size, struct vk_buffer_range *range)
|
|
{
|
|
VkDeviceSize next_offset = chain->offset + size;
|
|
if (next_offset <= chain->current->buffer.size)
|
|
{
|
|
range->data = (uint8_t*)chain->current->buffer.mapped + chain->offset;
|
|
range->buffer = chain->current->buffer.buffer;
|
|
range->offset = chain->offset;
|
|
chain->offset = (next_offset + chain->alignment - 1)
|
|
& ~(chain->alignment - 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct vk_buffer_node *vulkan_buffer_chain_alloc_node(
|
|
const struct vulkan_context *context,
|
|
size_t size, VkBufferUsageFlags usage)
|
|
{
|
|
struct vk_buffer_node *node = (struct vk_buffer_node*)
|
|
calloc(1, sizeof(*node));
|
|
if (!node)
|
|
return NULL;
|
|
|
|
node->buffer = vulkan_create_buffer(
|
|
context, size, usage);
|
|
return node;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void vulkan_buffer_chain_discard(struct vk_buffer_chain *chain)
|
|
{
|
|
chain->current = chain->head;
|
|
chain->offset = 0;
|
|
}
|
|
|
|
bool vulkan_buffer_chain_alloc(const struct vulkan_context *context,
|
|
struct vk_buffer_chain *chain,
|
|
size_t size, struct vk_buffer_range *range)
|
|
{
|
|
if (!chain->head)
|
|
{
|
|
chain->head = vulkan_buffer_chain_alloc_node(context,
|
|
chain->block_size, chain->usage);
|
|
if (!chain->head)
|
|
return false;
|
|
|
|
chain->current = chain->head;
|
|
chain->offset = 0;
|
|
}
|
|
|
|
if (vulkan_buffer_chain_suballoc(chain, size, range))
|
|
return true;
|
|
|
|
/* We've exhausted the current chain, traverse list until we
|
|
* can find a block we can use. Usually, we just step once. */
|
|
while (chain->current->next)
|
|
{
|
|
vulkan_buffer_chain_step(chain);
|
|
if (vulkan_buffer_chain_suballoc(chain, size, range))
|
|
return true;
|
|
}
|
|
|
|
/* We have to allocate a new node, might allocate larger
|
|
* buffer here than block_size in case we have
|
|
* a very large allocation. */
|
|
if (size < chain->block_size)
|
|
size = chain->block_size;
|
|
|
|
chain->current->next = vulkan_buffer_chain_alloc_node(
|
|
context, size, chain->usage);
|
|
if (!chain->current->next)
|
|
return false;
|
|
|
|
vulkan_buffer_chain_step(chain);
|
|
/* This cannot possibly fail. */
|
|
retro_assert(vulkan_buffer_chain_suballoc(chain, size, range));
|
|
return true;
|
|
}
|
|
|
|
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 bool vulkan_load_instance_symbols(gfx_ctx_vulkan_data_t *vk)
|
|
{
|
|
if (!vulkan_symbol_wrapper_load_core_instance_symbols(vk->context.instance))
|
|
return false;
|
|
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance, vkDestroySurfaceKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance, vkGetPhysicalDeviceSurfaceSupportKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance, vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance, vkGetPhysicalDeviceSurfaceFormatsKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance, vkGetPhysicalDeviceSurfacePresentModesKHR);
|
|
return true;
|
|
}
|
|
|
|
static bool vulkan_load_device_symbols(gfx_ctx_vulkan_data_t *vk)
|
|
{
|
|
if (!vulkan_symbol_wrapper_load_core_device_symbols(vk->context.device))
|
|
return false;
|
|
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_EXTENSION_SYMBOL(vk->context.device, vkCreateSwapchainKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_EXTENSION_SYMBOL(vk->context.device, vkDestroySwapchainKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_EXTENSION_SYMBOL(vk->context.device, vkGetSwapchainImagesKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_EXTENSION_SYMBOL(vk->context.device, vkAcquireNextImageKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_EXTENSION_SYMBOL(vk->context.device, vkQueuePresentKHR);
|
|
return true;
|
|
}
|
|
|
|
static bool vulkan_find_extensions(const char **exts, unsigned num_exts,
|
|
const VkExtensionProperties *properties, unsigned property_count)
|
|
{
|
|
unsigned i, ext;
|
|
bool found;
|
|
for (ext = 0; ext < num_exts; ext++)
|
|
{
|
|
found = false;
|
|
for (i = 0; i < property_count; i++)
|
|
{
|
|
if (string_is_equal(exts[ext], properties[i].extensionName))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool vulkan_find_instance_extensions(const char **exts, unsigned num_exts)
|
|
{
|
|
uint32_t property_count;
|
|
bool ret = true;
|
|
VkExtensionProperties *properties = NULL;
|
|
|
|
if (vkEnumerateInstanceExtensionProperties(NULL, &property_count, NULL) != VK_SUCCESS)
|
|
return false;
|
|
|
|
properties = (VkExtensionProperties*)malloc(property_count * sizeof(*properties));
|
|
if (!properties)
|
|
{
|
|
ret = false;
|
|
goto end;
|
|
}
|
|
|
|
if (vkEnumerateInstanceExtensionProperties(NULL, &property_count, properties) != VK_SUCCESS)
|
|
{
|
|
ret = false;
|
|
goto end;
|
|
}
|
|
|
|
if (!vulkan_find_extensions(exts, num_exts, properties, property_count))
|
|
{
|
|
RARCH_ERR("[Vulkan]: Could not find instance extensions. Will attempt without them.\n");
|
|
ret = false;
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
free(properties);
|
|
return ret;
|
|
}
|
|
|
|
static bool vulkan_find_device_extensions(VkPhysicalDevice gpu,
|
|
const char **enabled, unsigned *enabled_count,
|
|
const char **exts, unsigned num_exts,
|
|
const char **optional_exts, unsigned num_optional_exts)
|
|
{
|
|
bool ret = true;
|
|
VkExtensionProperties *properties = NULL;
|
|
uint32_t property_count;
|
|
unsigned i;
|
|
|
|
if (vkEnumerateDeviceExtensionProperties(gpu, NULL, &property_count, NULL) != VK_SUCCESS)
|
|
return false;
|
|
|
|
properties = (VkExtensionProperties*)malloc(property_count * sizeof(*properties));
|
|
if (!properties)
|
|
{
|
|
ret = false;
|
|
goto end;
|
|
}
|
|
|
|
if (vkEnumerateDeviceExtensionProperties(gpu, NULL, &property_count, properties) != VK_SUCCESS)
|
|
{
|
|
ret = false;
|
|
goto end;
|
|
}
|
|
|
|
if (!vulkan_find_extensions(exts, num_exts, properties, property_count))
|
|
{
|
|
RARCH_ERR("[Vulkan]: Could not find device extension. Will attempt without it.\n");
|
|
ret = false;
|
|
goto end;
|
|
}
|
|
|
|
memcpy(enabled, exts, num_exts * sizeof(*exts));
|
|
*enabled_count = num_exts;
|
|
|
|
for (i = 0; i < num_optional_exts; i++)
|
|
if (vulkan_find_extensions(&optional_exts[i], 1, properties, property_count))
|
|
enabled[(*enabled_count)++] = optional_exts[i];
|
|
|
|
end:
|
|
free(properties);
|
|
return ret;
|
|
}
|
|
|
|
static bool vulkan_context_init_gpu(gfx_ctx_vulkan_data_t *vk)
|
|
{
|
|
uint32_t gpu_count = 0;
|
|
VkPhysicalDevice *gpus = NULL;
|
|
|
|
if (vk->context.gpu != VK_NULL_HANDLE)
|
|
return true;
|
|
|
|
if (vkEnumeratePhysicalDevices(vk->context.instance,
|
|
&gpu_count, NULL) != VK_SUCCESS)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to enumerate physical devices.\n");
|
|
return false;
|
|
}
|
|
|
|
gpus = (VkPhysicalDevice*)calloc(gpu_count, sizeof(*gpus));
|
|
if (!gpus)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to enumerate physical devices.\n");
|
|
return false;
|
|
}
|
|
|
|
if (vkEnumeratePhysicalDevices(vk->context.instance,
|
|
&gpu_count, gpus) != VK_SUCCESS)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to enumerate physical devices.\n");
|
|
return false;
|
|
}
|
|
|
|
if (gpu_count < 1)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to enumerate Vulkan physical device.\n");
|
|
free(gpus);
|
|
return false;
|
|
}
|
|
|
|
vk->context.gpu = gpus[0];
|
|
free(gpus);
|
|
return true;
|
|
}
|
|
|
|
static bool vulkan_context_init_device(gfx_ctx_vulkan_data_t *vk)
|
|
{
|
|
bool use_device_ext;
|
|
uint32_t queue_count;
|
|
unsigned i;
|
|
static const float one = 1.0f;
|
|
bool found_queue = false;
|
|
|
|
VkPhysicalDeviceFeatures features = { false };
|
|
VkDeviceQueueCreateInfo queue_info = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO };
|
|
VkDeviceCreateInfo device_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
|
|
|
|
const char *enabled_device_extensions[8];
|
|
unsigned enabled_device_extension_count = 0;
|
|
|
|
static const char *device_extensions[] = {
|
|
"VK_KHR_swapchain",
|
|
};
|
|
|
|
static const char *optional_device_extensions[] = {
|
|
"VK_KHR_sampler_mirror_clamp_to_edge",
|
|
};
|
|
|
|
#ifdef VULKAN_DEBUG
|
|
static const char *device_layers[] = { "VK_LAYER_LUNARG_standard_validation" };
|
|
#endif
|
|
|
|
struct retro_hw_render_context_negotiation_interface_vulkan *iface =
|
|
(struct retro_hw_render_context_negotiation_interface_vulkan*)video_driver_get_context_negotiation_interface();
|
|
|
|
if (iface && iface->interface_type != RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN)
|
|
{
|
|
RARCH_WARN("[Vulkan]: Got HW context negotiation interface, but it's the wrong API.\n");
|
|
iface = NULL;
|
|
}
|
|
|
|
if (iface && iface->interface_version != RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION)
|
|
{
|
|
RARCH_WARN("[Vulkan]: Got HW context negotiation interface, but it's the wrong interface version.\n");
|
|
iface = NULL;
|
|
}
|
|
|
|
if (!cached_device && iface && iface->create_device)
|
|
{
|
|
struct retro_vulkan_context context = { 0 };
|
|
const VkPhysicalDeviceFeatures features = { 0 };
|
|
|
|
bool ret = iface->create_device(&context, vk->context.instance,
|
|
vk->context.gpu,
|
|
vk->vk_surface,
|
|
vulkan_symbol_wrapper_instance_proc_addr(),
|
|
device_extensions,
|
|
ARRAY_SIZE(device_extensions),
|
|
#ifdef VULKAN_DEBUG
|
|
device_layers,
|
|
ARRAY_SIZE(device_layers),
|
|
#else
|
|
NULL,
|
|
0,
|
|
#endif
|
|
&features);
|
|
|
|
if (!ret)
|
|
{
|
|
RARCH_WARN("[Vulkan]: Failed to create device with negotiation interface. Falling back to default path.\n");
|
|
}
|
|
else
|
|
{
|
|
vk->context.destroy_device = iface->destroy_device;
|
|
|
|
vk->context.device = context.device;
|
|
vk->context.queue = context.queue;
|
|
vk->context.gpu = context.gpu;
|
|
vk->context.graphics_queue_index = context.queue_family_index;
|
|
|
|
if (context.presentation_queue != context.queue)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Present queue != graphics queue. This is currently not supported.\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cached_device && cached_destroy_device)
|
|
{
|
|
vk->context.destroy_device = cached_destroy_device;
|
|
cached_destroy_device = NULL;
|
|
}
|
|
|
|
if (!vulkan_context_init_gpu(vk))
|
|
return false;
|
|
|
|
vkGetPhysicalDeviceProperties(vk->context.gpu,
|
|
&vk->context.gpu_properties);
|
|
vkGetPhysicalDeviceMemoryProperties(vk->context.gpu,
|
|
&vk->context.memory_properties);
|
|
|
|
RARCH_LOG("[Vulkan]: Using GPU: %s\n", vk->context.gpu_properties.deviceName);
|
|
|
|
if (vk->context.device == VK_NULL_HANDLE)
|
|
{
|
|
VkQueueFamilyProperties *queue_properties = NULL;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(vk->context.gpu,
|
|
&queue_count, NULL);
|
|
|
|
if (queue_count < 1)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Invalid number of queues detected.\n");
|
|
return false;
|
|
}
|
|
|
|
queue_properties = (VkQueueFamilyProperties*)malloc(queue_count * sizeof(*queue_properties));
|
|
if (!queue_properties)
|
|
return false;
|
|
|
|
vkGetPhysicalDeviceQueueFamilyProperties(vk->context.gpu,
|
|
&queue_count, queue_properties);
|
|
|
|
for (i = 0; i < queue_count; i++)
|
|
{
|
|
VkQueueFlags required;
|
|
VkBool32 supported = VK_FALSE;
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(
|
|
vk->context.gpu, i,
|
|
vk->vk_surface, &supported);
|
|
|
|
required = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT;
|
|
if (supported && ((queue_properties[i].queueFlags & required) == required))
|
|
{
|
|
vk->context.graphics_queue_index = i;
|
|
RARCH_LOG("[Vulkan]: Queue family %u supports %u sub-queues.\n",
|
|
i, queue_properties[i].queueCount);
|
|
found_queue = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(queue_properties);
|
|
|
|
if (!found_queue)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Did not find suitable graphics queue.\n");
|
|
return false;
|
|
}
|
|
|
|
use_device_ext = vulkan_find_device_extensions(vk->context.gpu,
|
|
enabled_device_extensions, &enabled_device_extension_count,
|
|
device_extensions, ARRAY_SIZE(device_extensions),
|
|
optional_device_extensions, ARRAY_SIZE(optional_device_extensions));
|
|
|
|
if (!use_device_ext)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Could not find required device extensions.\n");
|
|
return false;
|
|
}
|
|
|
|
queue_info.queueFamilyIndex = vk->context.graphics_queue_index;
|
|
queue_info.queueCount = 1;
|
|
queue_info.pQueuePriorities = &one;
|
|
|
|
device_info.queueCreateInfoCount = 1;
|
|
device_info.pQueueCreateInfos = &queue_info;
|
|
device_info.enabledExtensionCount = enabled_device_extension_count;
|
|
device_info.ppEnabledExtensionNames = enabled_device_extension_count ? enabled_device_extensions : NULL;
|
|
device_info.pEnabledFeatures = &features;
|
|
#ifdef VULKAN_DEBUG
|
|
device_info.enabledLayerCount = ARRAY_SIZE(device_layers);
|
|
device_info.ppEnabledLayerNames = device_layers;
|
|
#endif
|
|
|
|
if (cached_device)
|
|
{
|
|
vk->context.device = cached_device;
|
|
cached_device = NULL;
|
|
|
|
video_driver_set_video_cache_context_ack();
|
|
RARCH_LOG("[Vulkan]: Using cached Vulkan context.\n");
|
|
}
|
|
else if (vkCreateDevice(vk->context.gpu, &device_info,
|
|
NULL, &vk->context.device) != VK_SUCCESS)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to create device.\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!vulkan_load_device_symbols(vk))
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to load device symbols.\n");
|
|
return false;
|
|
}
|
|
|
|
if (vk->context.queue == VK_NULL_HANDLE)
|
|
{
|
|
vkGetDeviceQueue(vk->context.device,
|
|
vk->context.graphics_queue_index, 0, &vk->context.queue);
|
|
}
|
|
|
|
#ifdef HAVE_THREADS
|
|
vk->context.queue_lock = slock_new();
|
|
if (!vk->context.queue_lock)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to create queue lock.\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool vulkan_context_init(gfx_ctx_vulkan_data_t *vk,
|
|
enum vulkan_wsi_type type)
|
|
{
|
|
unsigned i;
|
|
VkResult res;
|
|
PFN_vkGetInstanceProcAddr GetInstanceProcAddr;
|
|
VkInstanceCreateInfo info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
|
|
VkApplicationInfo app = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
|
|
|
|
const char *instance_extensions[4];
|
|
unsigned ext_count = 0;
|
|
|
|
#ifdef VULKAN_DEBUG
|
|
instance_extensions[ext_count++] = "VK_EXT_debug_report";
|
|
static const char *instance_layers[] = { "VK_LAYER_LUNARG_standard_validation" };
|
|
#endif
|
|
|
|
bool use_instance_ext;
|
|
struct retro_hw_render_context_negotiation_interface_vulkan *iface =
|
|
(struct retro_hw_render_context_negotiation_interface_vulkan*)video_driver_get_context_negotiation_interface();
|
|
|
|
if (iface && iface->interface_type != RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN)
|
|
{
|
|
RARCH_WARN("[Vulkan]: Got HW context negotiation interface, but it's the wrong API.\n");
|
|
iface = NULL;
|
|
}
|
|
|
|
if (iface && iface->interface_version != RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION)
|
|
{
|
|
RARCH_WARN("[Vulkan]: Got HW context negotiation interface, but it's the wrong interface version.\n");
|
|
iface = NULL;
|
|
}
|
|
|
|
instance_extensions[ext_count++] = "VK_KHR_surface";
|
|
|
|
switch (type)
|
|
{
|
|
case VULKAN_WSI_WAYLAND:
|
|
instance_extensions[ext_count++] = "VK_KHR_wayland_surface";
|
|
break;
|
|
case VULKAN_WSI_ANDROID:
|
|
instance_extensions[ext_count++] = "VK_KHR_android_surface";
|
|
break;
|
|
case VULKAN_WSI_WIN32:
|
|
instance_extensions[ext_count++] = "VK_KHR_win32_surface";
|
|
break;
|
|
case VULKAN_WSI_XLIB:
|
|
instance_extensions[ext_count++] = "VK_KHR_xlib_surface";
|
|
break;
|
|
case VULKAN_WSI_XCB:
|
|
instance_extensions[ext_count++] = "VK_KHR_xcb_surface";
|
|
break;
|
|
case VULKAN_WSI_MIR:
|
|
instance_extensions[ext_count++] = "VK_KHR_mir_surface";
|
|
break;
|
|
case VULKAN_WSI_DISPLAY:
|
|
instance_extensions[ext_count++] = "VK_KHR_display";
|
|
break;
|
|
case VULKAN_WSI_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!vulkan_library)
|
|
{
|
|
#ifdef _WIN32
|
|
vulkan_library = dylib_load("vulkan-1.dll");
|
|
#else
|
|
vulkan_library = dylib_load("libvulkan.so");
|
|
#endif
|
|
}
|
|
|
|
if (!vulkan_library)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to open Vulkan loader.\n");
|
|
return false;
|
|
}
|
|
|
|
RARCH_LOG("Vulkan dynamic library loaded.\n");
|
|
|
|
GetInstanceProcAddr =
|
|
(PFN_vkGetInstanceProcAddr)dylib_proc(vulkan_library, "vkGetInstanceProcAddr");
|
|
|
|
if (!GetInstanceProcAddr)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to load vkGetInstanceProcAddr symbol, broken loader?\n");
|
|
return false;
|
|
}
|
|
|
|
vulkan_symbol_wrapper_init(GetInstanceProcAddr);
|
|
|
|
if (!vulkan_symbol_wrapper_load_global_symbols())
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to load global Vulkan symbols, broken loader?\n");
|
|
return false;
|
|
}
|
|
|
|
use_instance_ext = vulkan_find_instance_extensions(instance_extensions, ext_count);
|
|
|
|
app.pApplicationName = "RetroArch";
|
|
app.applicationVersion = 0;
|
|
app.pEngineName = "RetroArch";
|
|
app.engineVersion = 0;
|
|
app.apiVersion = VK_MAKE_VERSION(1, 0, 18);
|
|
|
|
info.pApplicationInfo = &app;
|
|
info.enabledExtensionCount = use_instance_ext ? ext_count : 0;
|
|
info.ppEnabledExtensionNames = use_instance_ext ? instance_extensions : NULL;
|
|
#ifdef VULKAN_DEBUG
|
|
info.enabledLayerCount = ARRAY_SIZE(instance_layers);
|
|
info.ppEnabledLayerNames = instance_layers;
|
|
#endif
|
|
|
|
if (iface && iface->get_application_info)
|
|
{
|
|
info.pApplicationInfo = iface->get_application_info();
|
|
if (info.pApplicationInfo->pApplicationName)
|
|
{
|
|
RARCH_LOG("[Vulkan]: App: %s (version %u)\n",
|
|
info.pApplicationInfo->pApplicationName,
|
|
info.pApplicationInfo->applicationVersion);
|
|
}
|
|
|
|
if (info.pApplicationInfo->pEngineName)
|
|
{
|
|
RARCH_LOG("[Vulkan]: Engine: %s (version %u)\n",
|
|
info.pApplicationInfo->pEngineName,
|
|
info.pApplicationInfo->engineVersion);
|
|
}
|
|
}
|
|
|
|
if (cached_instance)
|
|
{
|
|
vk->context.instance = cached_instance;
|
|
cached_instance = NULL;
|
|
res = VK_SUCCESS;
|
|
}
|
|
else
|
|
res = vkCreateInstance(&info, NULL, &vk->context.instance);
|
|
|
|
#ifdef VULKAN_DEBUG
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkCreateDebugReportCallbackEXT);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkDebugReportMessageEXT);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkDestroyDebugReportCallbackEXT);
|
|
|
|
{
|
|
VkDebugReportCallbackCreateInfoEXT info =
|
|
{ VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT };
|
|
info.flags =
|
|
VK_DEBUG_REPORT_ERROR_BIT_EXT |
|
|
VK_DEBUG_REPORT_WARNING_BIT_EXT |
|
|
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
|
|
info.pfnCallback = vulkan_debug_cb;
|
|
vkCreateDebugReportCallbackEXT(vk->context.instance, &info, NULL, &vk->context.debug_callback);
|
|
}
|
|
RARCH_LOG("[Vulkan]: Enabling Vulkan debug layers.\n");
|
|
#endif
|
|
|
|
/* Try different API versions if driver has compatible
|
|
* but slightly different VK_API_VERSION. */
|
|
for (i = 1; i < 4 && res == VK_ERROR_INCOMPATIBLE_DRIVER; i++)
|
|
{
|
|
info.pApplicationInfo = &app;
|
|
app.apiVersion = VK_MAKE_VERSION(1, 0, i);
|
|
res = vkCreateInstance(&info, NULL, &vk->context.instance);
|
|
}
|
|
|
|
if (res == VK_ERROR_INCOMPATIBLE_DRIVER)
|
|
{
|
|
RARCH_ERR("Failed to create Vulkan instance.\n");
|
|
return false;
|
|
}
|
|
|
|
if (!vulkan_load_instance_symbols(vk))
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to load instance symbols.\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool vulkan_update_display_mode(
|
|
unsigned *width,
|
|
unsigned *height,
|
|
const VkDisplayModePropertiesKHR *mode,
|
|
const struct vulkan_display_surface_info *info)
|
|
{
|
|
unsigned visible_width = mode->parameters.visibleRegion.width;
|
|
unsigned visible_height = mode->parameters.visibleRegion.height;
|
|
|
|
if (!info->width || !info->height)
|
|
{
|
|
/* Strategy here is to pick something which is largest resolution. */
|
|
unsigned area = visible_width * visible_height;
|
|
if (area > (*width) * (*height))
|
|
{
|
|
*width = visible_width;
|
|
*height = visible_height;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* For particular resolutions, find the closest. */
|
|
int delta_x = (int)info->width - (int)visible_width;
|
|
int delta_y = (int)info->height - (int)visible_height;
|
|
int old_delta_x = (int)info->width - (int)*width;
|
|
int old_delta_y = (int)info->height - (int)*height;
|
|
|
|
int dist = delta_x * delta_x + delta_y * delta_y;
|
|
int old_dist = old_delta_x * old_delta_x + old_delta_y * old_delta_y;
|
|
if (dist < old_dist)
|
|
{
|
|
*width = visible_width;
|
|
*height = visible_height;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool vulkan_create_display_surface(gfx_ctx_vulkan_data_t *vk,
|
|
unsigned *width, unsigned *height,
|
|
const struct vulkan_display_surface_info *info)
|
|
{
|
|
bool ret = true;
|
|
uint32_t display_count = 0;
|
|
uint32_t plane_count = 0;
|
|
VkDisplayPropertiesKHR *displays = NULL;
|
|
VkDisplayPlanePropertiesKHR *planes = NULL;
|
|
uint32_t mode_count = 0;
|
|
VkDisplayModePropertiesKHR *modes = NULL;
|
|
unsigned dpy, i, j;
|
|
uint32_t best_plane = UINT32_MAX;
|
|
VkDisplayPlaneAlphaFlagBitsKHR alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
|
|
VkDisplaySurfaceCreateInfoKHR create_info = { VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR };
|
|
VkDisplayModeKHR best_mode = VK_NULL_HANDLE;
|
|
|
|
/* We need to decide on GPU here to be able to query support. */
|
|
if (!vulkan_context_init_gpu(vk))
|
|
return false;
|
|
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkGetPhysicalDeviceDisplayPropertiesKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkGetPhysicalDeviceDisplayPlanePropertiesKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkGetDisplayPlaneSupportedDisplaysKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkGetDisplayModePropertiesKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkCreateDisplayModeKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkGetDisplayPlaneCapabilitiesKHR);
|
|
VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(vk->context.instance,
|
|
vkCreateDisplayPlaneSurfaceKHR);
|
|
|
|
#define GOTO_FAIL() do { \
|
|
ret = false; \
|
|
goto end; \
|
|
} while(0)
|
|
|
|
if (vkGetPhysicalDeviceDisplayPropertiesKHR(vk->context.gpu, &display_count, NULL) != VK_SUCCESS)
|
|
GOTO_FAIL();
|
|
displays = (VkDisplayPropertiesKHR*)calloc(display_count, sizeof(*displays));
|
|
if (!displays)
|
|
GOTO_FAIL();
|
|
if (vkGetPhysicalDeviceDisplayPropertiesKHR(vk->context.gpu, &display_count, displays) != VK_SUCCESS)
|
|
GOTO_FAIL();
|
|
|
|
if (vkGetPhysicalDeviceDisplayPlanePropertiesKHR(vk->context.gpu, &plane_count, NULL) != VK_SUCCESS)
|
|
GOTO_FAIL();
|
|
planes = (VkDisplayPlanePropertiesKHR*)calloc(plane_count, sizeof(*planes));
|
|
if (!planes)
|
|
GOTO_FAIL();
|
|
if (vkGetPhysicalDeviceDisplayPlanePropertiesKHR(vk->context.gpu, &plane_count, planes) != VK_SUCCESS)
|
|
GOTO_FAIL();
|
|
|
|
for (dpy = 0; dpy < display_count; dpy++)
|
|
{
|
|
VkDisplayKHR display = displays[dpy].display;
|
|
best_mode = VK_NULL_HANDLE;
|
|
best_plane = UINT32_MAX;
|
|
|
|
if (vkGetDisplayModePropertiesKHR(vk->context.gpu,
|
|
display, &mode_count, NULL) != VK_SUCCESS)
|
|
GOTO_FAIL();
|
|
|
|
modes = (VkDisplayModePropertiesKHR*)calloc(mode_count, sizeof(*modes));
|
|
if (!modes)
|
|
GOTO_FAIL();
|
|
|
|
if (vkGetDisplayModePropertiesKHR(vk->context.gpu,
|
|
display, &mode_count, modes) != VK_SUCCESS)
|
|
GOTO_FAIL();
|
|
|
|
for (i = 0; i < mode_count; i++)
|
|
{
|
|
const VkDisplayModePropertiesKHR *mode = &modes[i];
|
|
if (vulkan_update_display_mode(width, height, mode, info))
|
|
best_mode = modes[i].displayMode;
|
|
}
|
|
|
|
free(modes);
|
|
modes = NULL;
|
|
mode_count = 0;
|
|
|
|
if (best_mode == VK_NULL_HANDLE)
|
|
continue;
|
|
|
|
for (i = 0; i < plane_count; i++)
|
|
{
|
|
uint32_t supported_count = 0;
|
|
VkDisplayKHR *supported = NULL;
|
|
VkDisplayPlaneCapabilitiesKHR plane_caps;
|
|
vkGetDisplayPlaneSupportedDisplaysKHR(vk->context.gpu, i, &supported_count, NULL);
|
|
if (!supported_count)
|
|
continue;
|
|
|
|
supported = (VkDisplayKHR*)calloc(supported_count, sizeof(*supported));
|
|
if (!supported)
|
|
GOTO_FAIL();
|
|
|
|
vkGetDisplayPlaneSupportedDisplaysKHR(vk->context.gpu, i, &supported_count,
|
|
supported);
|
|
|
|
for (j = 0; j < supported_count; j++)
|
|
{
|
|
if (supported[j] == display)
|
|
{
|
|
if (best_plane == UINT32_MAX)
|
|
best_plane = j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(supported);
|
|
supported = NULL;
|
|
|
|
if (j == supported_count)
|
|
continue;
|
|
|
|
if (planes[i].currentDisplay == VK_NULL_HANDLE ||
|
|
planes[i].currentDisplay == display)
|
|
best_plane = j;
|
|
else
|
|
continue;
|
|
|
|
vkGetDisplayPlaneCapabilitiesKHR(vk->context.gpu,
|
|
best_mode, i, &plane_caps);
|
|
|
|
if (plane_caps.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR)
|
|
{
|
|
best_plane = j;
|
|
alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
|
|
if (best_mode == VK_NULL_HANDLE)
|
|
GOTO_FAIL();
|
|
if (best_plane == UINT32_MAX)
|
|
GOTO_FAIL();
|
|
|
|
create_info.displayMode = best_mode;
|
|
create_info.planeIndex = best_plane;
|
|
create_info.planeStackIndex = planes[best_plane].currentStackIndex;
|
|
create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
|
create_info.globalAlpha = 1.0f;
|
|
create_info.alphaMode = alpha_mode;
|
|
create_info.imageExtent.width = *width;
|
|
create_info.imageExtent.height = *height;
|
|
|
|
if (vkCreateDisplayPlaneSurfaceKHR(vk->context.instance,
|
|
&create_info, NULL, &vk->vk_surface) != VK_SUCCESS)
|
|
GOTO_FAIL();
|
|
|
|
end:
|
|
free(displays);
|
|
free(planes);
|
|
free(modes);
|
|
return ret;
|
|
}
|
|
|
|
bool vulkan_surface_create(gfx_ctx_vulkan_data_t *vk,
|
|
enum vulkan_wsi_type type,
|
|
void *display, void *surface,
|
|
unsigned width, unsigned height,
|
|
unsigned swap_interval)
|
|
{
|
|
switch (type)
|
|
{
|
|
case VULKAN_WSI_WAYLAND:
|
|
#ifdef HAVE_WAYLAND
|
|
{
|
|
PFN_vkCreateWaylandSurfaceKHR create;
|
|
if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(vk->context.instance, "vkCreateWaylandSurfaceKHR", create))
|
|
return false;
|
|
VkWaylandSurfaceCreateInfoKHR surf_info;
|
|
|
|
memset(&surf_info, 0, sizeof(surf_info));
|
|
|
|
surf_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
|
|
surf_info.pNext = NULL;
|
|
surf_info.flags = 0;
|
|
surf_info.display = (struct wl_display*)display;
|
|
surf_info.surface = (struct wl_surface*)surface;
|
|
|
|
if (create(vk->context.instance,
|
|
&surf_info, NULL, &vk->vk_surface) != VK_SUCCESS)
|
|
return false;
|
|
}
|
|
#endif
|
|
break;
|
|
case VULKAN_WSI_ANDROID:
|
|
#ifdef ANDROID
|
|
{
|
|
PFN_vkCreateAndroidSurfaceKHR create;
|
|
if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(vk->context.instance, "vkCreateAndroidSurfaceKHR", create))
|
|
return false;
|
|
VkAndroidSurfaceCreateInfoKHR surf_info;
|
|
|
|
memset(&surf_info, 0, sizeof(surf_info));
|
|
|
|
surf_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
|
|
surf_info.flags = 0;
|
|
surf_info.window = (ANativeWindow*)surface;
|
|
|
|
if (create(vk->context.instance,
|
|
&surf_info, NULL, &vk->vk_surface) != VK_SUCCESS)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to create Android surface.\n");
|
|
return false;
|
|
}
|
|
RARCH_LOG("[Vulkan]: Created Android surface: %llu\n",
|
|
(unsigned long long)vk->vk_surface);
|
|
}
|
|
#endif
|
|
break;
|
|
case VULKAN_WSI_WIN32:
|
|
#ifdef _WIN32
|
|
{
|
|
VkWin32SurfaceCreateInfoKHR surf_info;
|
|
PFN_vkCreateWin32SurfaceKHR create;
|
|
|
|
if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(vk->context.instance, "vkCreateWin32SurfaceKHR", create))
|
|
return false;
|
|
|
|
memset(&surf_info, 0, sizeof(surf_info));
|
|
|
|
surf_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
|
surf_info.flags = 0;
|
|
surf_info.hinstance = *(const HINSTANCE*)display;
|
|
surf_info.hwnd = *(const HWND*)surface;
|
|
|
|
if (create(vk->context.instance,
|
|
&surf_info, NULL, &vk->vk_surface) != VK_SUCCESS)
|
|
return false;
|
|
}
|
|
#endif
|
|
break;
|
|
case VULKAN_WSI_XLIB:
|
|
#ifdef HAVE_XLIB
|
|
{
|
|
PFN_vkCreateXlibSurfaceKHR create;
|
|
if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(vk->context.instance, "vkCreateXlibSurfaceKHR", create))
|
|
return false;
|
|
VkXlibSurfaceCreateInfoKHR surf_info;
|
|
|
|
memset(&surf_info, 0, sizeof(surf_info));
|
|
|
|
surf_info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
|
|
surf_info.flags = 0;
|
|
surf_info.dpy = (Display*)display;
|
|
surf_info.window = *(const Window*)surface;
|
|
|
|
if (create(vk->context.instance,
|
|
&surf_info, NULL, &vk->vk_surface)
|
|
!= VK_SUCCESS)
|
|
return false;
|
|
}
|
|
#endif
|
|
break;
|
|
case VULKAN_WSI_XCB:
|
|
#ifdef HAVE_X11
|
|
#ifdef HAVE_XCB
|
|
{
|
|
PFN_vkCreateXcbSurfaceKHR create;
|
|
if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(vk->context.instance, "vkCreateXcbSurfaceKHR", create))
|
|
return false;
|
|
VkXcbSurfaceCreateInfoKHR surf_info;
|
|
|
|
memset(&surf_info, 0, sizeof(surf_info));
|
|
|
|
surf_info.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
|
|
surf_info.flags = 0;
|
|
surf_info.connection = XGetXCBConnection((Display*)display);
|
|
surf_info.window = *(const xcb_window_t*)surface;
|
|
|
|
if (create(vk->context.instance,
|
|
&surf_info, NULL, &vk->vk_surface)
|
|
!= VK_SUCCESS)
|
|
return false;
|
|
}
|
|
#endif
|
|
#endif
|
|
break;
|
|
case VULKAN_WSI_MIR:
|
|
#ifdef HAVE_MIR
|
|
{
|
|
PFN_vkCreateMirSurfaceKHR create;
|
|
if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(vk->context.instance, "vkCreateMirSurfaceKHR", create))
|
|
return false;
|
|
VkMirSurfaceCreateInfoKHR surf_info;
|
|
|
|
memset(&surf_info, 0, sizeof(surf_info));
|
|
|
|
surf_info.sType = VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR;
|
|
surf_info.connection = display;
|
|
surf_info.mirSurface = surface;
|
|
|
|
if (create(vk->context.instance,
|
|
&surf_info, NULL, &vk->vk_surface)
|
|
!= VK_SUCCESS)
|
|
return false;
|
|
}
|
|
#endif
|
|
break;
|
|
case VULKAN_WSI_DISPLAY:
|
|
{
|
|
if (!vulkan_create_display_surface(vk,
|
|
&width, &height,
|
|
(const struct vulkan_display_surface_info*)display))
|
|
return false;
|
|
}
|
|
break;
|
|
case VULKAN_WSI_NONE:
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
/* Must create device after surface since we need to be able to query queues to use for presentation. */
|
|
if (!vulkan_context_init_device(vk))
|
|
return false;
|
|
|
|
if (!vulkan_create_swapchain(
|
|
vk, width, height, swap_interval))
|
|
return false;
|
|
|
|
vulkan_acquire_next_image(vk);
|
|
return true;
|
|
}
|
|
|
|
void vulkan_present(gfx_ctx_vulkan_data_t *vk, unsigned index)
|
|
{
|
|
VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
|
|
VkResult result = VK_SUCCESS;
|
|
VkResult err = VK_SUCCESS;
|
|
|
|
/* We're still waiting for a proper swapchain, so just fake it. */
|
|
if (vk->swapchain == VK_NULL_HANDLE)
|
|
{
|
|
retro_sleep(10);
|
|
return;
|
|
}
|
|
|
|
present.swapchainCount = 1;
|
|
present.pSwapchains = &vk->swapchain;
|
|
present.pImageIndices = &index;
|
|
present.pResults = &result;
|
|
present.waitSemaphoreCount = 1;
|
|
present.pWaitSemaphores = &vk->context.swapchain_semaphores[index];
|
|
|
|
/* Better hope QueuePresent doesn't block D: */
|
|
#ifdef HAVE_THREADS
|
|
slock_lock(vk->context.queue_lock);
|
|
#endif
|
|
err = vkQueuePresentKHR(vk->context.queue, &present);
|
|
|
|
if (err != VK_SUCCESS || result != VK_SUCCESS)
|
|
{
|
|
RARCH_LOG("[Vulkan]: QueuePresent failed, invalidating swapchain.\n");
|
|
vk->context.invalid_swapchain = true;
|
|
}
|
|
|
|
#ifdef HAVE_THREADS
|
|
slock_unlock(vk->context.queue_lock);
|
|
#endif
|
|
}
|
|
|
|
void vulkan_context_destroy(gfx_ctx_vulkan_data_t *vk,
|
|
bool destroy_surface)
|
|
{
|
|
unsigned i;
|
|
|
|
if (!vk->context.instance)
|
|
return;
|
|
|
|
if (vk->context.device)
|
|
vkDeviceWaitIdle(vk->context.device);
|
|
if (vk->swapchain)
|
|
vkDestroySwapchainKHR(vk->context.device,
|
|
vk->swapchain, NULL);
|
|
|
|
if (destroy_surface && vk->vk_surface != VK_NULL_HANDLE)
|
|
vkDestroySurfaceKHR(vk->context.instance,
|
|
vk->vk_surface, NULL);
|
|
|
|
for (i = 0; i < VULKAN_MAX_SWAPCHAIN_IMAGES; i++)
|
|
{
|
|
if (vk->context.swapchain_semaphores[i] != VK_NULL_HANDLE)
|
|
vkDestroySemaphore(vk->context.device,
|
|
vk->context.swapchain_semaphores[i], NULL);
|
|
if (vk->context.swapchain_fences[i] != VK_NULL_HANDLE)
|
|
vkDestroyFence(vk->context.device,
|
|
vk->context.swapchain_fences[i], NULL);
|
|
}
|
|
|
|
#ifdef VULKAN_DEBUG
|
|
if (vk->context.debug_callback)
|
|
vkDestroyDebugReportCallbackEXT(vk->context.instance, vk->context.debug_callback, NULL);
|
|
#endif
|
|
|
|
if (video_driver_is_video_cache_context())
|
|
{
|
|
cached_device = vk->context.device;
|
|
cached_instance = vk->context.instance;
|
|
cached_destroy_device = vk->context.destroy_device;
|
|
}
|
|
else
|
|
{
|
|
if (vk->context.device)
|
|
vkDestroyDevice(vk->context.device, NULL);
|
|
if (vk->context.instance)
|
|
{
|
|
if (vk->context.destroy_device)
|
|
vk->context.destroy_device();
|
|
|
|
vkDestroyInstance(vk->context.instance, NULL);
|
|
if (vulkan_library)
|
|
{
|
|
dylib_close(vulkan_library);
|
|
vulkan_library = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void vulkan_acquire_clear_fences(gfx_ctx_vulkan_data_t *vk)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < vk->context.num_swapchain_images; i++)
|
|
{
|
|
if (vk->context.swapchain_fences[i])
|
|
{
|
|
vkDestroyFence(vk->context.device,
|
|
vk->context.swapchain_fences[i], NULL);
|
|
vk->context.swapchain_fences[i] = VK_NULL_HANDLE;
|
|
vk->context.swapchain_fences_signalled[i] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void vulkan_acquire_wait_fences(gfx_ctx_vulkan_data_t *vk)
|
|
{
|
|
VkFenceCreateInfo fence_info =
|
|
{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
|
|
|
unsigned index = vk->context.current_swapchain_index;
|
|
VkFence *next_fence = &vk->context.swapchain_fences[index];
|
|
|
|
if (*next_fence != VK_NULL_HANDLE)
|
|
{
|
|
if (vk->context.swapchain_fences_signalled[index])
|
|
vkWaitForFences(vk->context.device, 1, next_fence, true, UINT64_MAX);
|
|
vkResetFences(vk->context.device, 1, next_fence);
|
|
vk->context.swapchain_fences_signalled[index] = false;
|
|
}
|
|
else
|
|
vkCreateFence(vk->context.device, &fence_info, NULL, next_fence);
|
|
}
|
|
|
|
void vulkan_acquire_next_image(gfx_ctx_vulkan_data_t *vk)
|
|
{
|
|
unsigned index;
|
|
VkResult err;
|
|
VkFence fence;
|
|
VkFenceCreateInfo fence_info =
|
|
{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
|
VkSemaphoreCreateInfo sem_info =
|
|
{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
|
|
bool is_retrying = false;
|
|
|
|
if (vk->swapchain == VK_NULL_HANDLE)
|
|
{
|
|
/* We don't have a swapchain, try to create one now. */
|
|
if (!vulkan_create_swapchain(vk, vk->context.swapchain_width,
|
|
vk->context.swapchain_height, vk->context.swap_interval))
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to create new swapchain.\n");
|
|
return;
|
|
}
|
|
|
|
if (vk->swapchain == VK_NULL_HANDLE)
|
|
{
|
|
/* We still don't have a swapchain, so just fake it ... */
|
|
vk->context.current_swapchain_index = 0;
|
|
vulkan_acquire_clear_fences(vk);
|
|
vulkan_acquire_wait_fences(vk);
|
|
return;
|
|
}
|
|
}
|
|
|
|
retry:
|
|
vkCreateFence(vk->context.device, &fence_info, NULL, &fence);
|
|
|
|
err = vkAcquireNextImageKHR(vk->context.device,
|
|
vk->swapchain, UINT64_MAX,
|
|
VK_NULL_HANDLE, fence, &vk->context.current_swapchain_index);
|
|
|
|
index = vk->context.current_swapchain_index;
|
|
if (vk->context.swapchain_semaphores[index] == VK_NULL_HANDLE)
|
|
vkCreateSemaphore(vk->context.device, &sem_info,
|
|
NULL, &vk->context.swapchain_semaphores[index]);
|
|
|
|
if (err == VK_SUCCESS)
|
|
vkWaitForFences(vk->context.device, 1, &fence, true, UINT64_MAX);
|
|
vkDestroyFence(vk->context.device, fence, NULL);
|
|
|
|
vulkan_acquire_wait_fences(vk);
|
|
|
|
if (err != VK_SUCCESS)
|
|
{
|
|
if (is_retrying)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Tried acquring next swapchain image after creating new one, but failed ...\n");
|
|
}
|
|
else
|
|
{
|
|
RARCH_LOG("[Vulkan]: AcquireNextImage failed, invalidating swapchain.\n");
|
|
vk->context.invalid_swapchain = true;
|
|
|
|
RARCH_LOG("[Vulkan]: AcquireNextImage failed, so trying to recreate swapchain.\n");
|
|
if (!vulkan_create_swapchain(vk, vk->context.swapchain_width,
|
|
vk->context.swapchain_height, vk->context.swap_interval))
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to create new swapchain.\n");
|
|
}
|
|
else
|
|
{
|
|
is_retrying = true;
|
|
goto retry;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
|
|
unsigned width, unsigned height,
|
|
unsigned swap_interval)
|
|
{
|
|
unsigned i;
|
|
uint32_t format_count;
|
|
uint32_t present_mode_count;
|
|
uint32_t desired_swapchain_images;
|
|
VkSurfaceCapabilitiesKHR surface_properties;
|
|
VkSurfaceFormatKHR formats[256];
|
|
VkPresentModeKHR present_modes[16];
|
|
VkSurfaceFormatKHR format;
|
|
VkExtent2D swapchain_size;
|
|
VkSwapchainKHR old_swapchain;
|
|
VkSurfaceTransformFlagBitsKHR pre_transform;
|
|
VkSwapchainCreateInfoKHR info = {
|
|
VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR };
|
|
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
|
settings_t *settings = config_get_ptr();
|
|
VkCompositeAlphaFlagBitsKHR composite = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
|
|
vkDeviceWaitIdle(vk->context.device);
|
|
|
|
present_mode_count = 0;
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(
|
|
vk->context.gpu, vk->vk_surface,
|
|
&present_mode_count, NULL);
|
|
if (present_mode_count < 1 || present_mode_count > 16)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Bogus present modes found.\n");
|
|
return false;
|
|
}
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(
|
|
vk->context.gpu, vk->vk_surface,
|
|
&present_mode_count, present_modes);
|
|
|
|
for (i = 0; i < present_mode_count; i++)
|
|
{
|
|
RARCH_LOG("[Vulkan]: Swapchain supports present mode: %u.\n",
|
|
present_modes[i]);
|
|
}
|
|
|
|
vk->context.swap_interval = swap_interval;
|
|
for (i = 0; i < present_mode_count; i++)
|
|
{
|
|
if (!swap_interval && present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
|
|
{
|
|
swapchain_present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
|
|
break;
|
|
}
|
|
else if (!swap_interval && present_modes[i]
|
|
== VK_PRESENT_MODE_IMMEDIATE_KHR)
|
|
{
|
|
swapchain_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
|
break;
|
|
}
|
|
else if (swap_interval && present_modes[i] == VK_PRESENT_MODE_FIFO_KHR)
|
|
{
|
|
/* Kind of tautological since FIFO must always be present. */
|
|
swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RARCH_LOG("[Vulkan]: Creating swapchain with present mode: %u\n",
|
|
(unsigned)swapchain_present_mode);
|
|
|
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk->context.gpu,
|
|
vk->vk_surface, &surface_properties);
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(vk->context.gpu,
|
|
vk->vk_surface, &format_count, NULL);
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(vk->context.gpu,
|
|
vk->vk_surface, &format_count, formats);
|
|
|
|
format.format = VK_FORMAT_UNDEFINED;
|
|
if (format_count == 1 && formats[0].format == VK_FORMAT_UNDEFINED)
|
|
{
|
|
format = formats[0];
|
|
format.format = VK_FORMAT_B8G8R8A8_UNORM;
|
|
}
|
|
else
|
|
{
|
|
if (format_count == 0)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Surface has no formats.\n");
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < format_count; i++)
|
|
{
|
|
if (
|
|
formats[i].format == VK_FORMAT_R8G8B8A8_UNORM ||
|
|
formats[i].format == VK_FORMAT_B8G8R8A8_UNORM ||
|
|
formats[i].format == VK_FORMAT_A8B8G8R8_UNORM_PACK32)
|
|
{
|
|
format = formats[i];
|
|
}
|
|
}
|
|
|
|
if (format.format == VK_FORMAT_UNDEFINED)
|
|
format = formats[0];
|
|
}
|
|
|
|
if (surface_properties.currentExtent.width == -1)
|
|
{
|
|
swapchain_size.width = width;
|
|
swapchain_size.height = height;
|
|
}
|
|
else
|
|
swapchain_size = surface_properties.currentExtent;
|
|
|
|
#if 0
|
|
/* Tests for deferred creation. */
|
|
static unsigned retry_count = 0;
|
|
if (++retry_count < 50)
|
|
{
|
|
surface_properties.maxImageExtent.width = 0;
|
|
surface_properties.maxImageExtent.height = 0;
|
|
surface_properties.minImageExtent.width = 0;
|
|
surface_properties.minImageExtent.height = 0;
|
|
}
|
|
#endif
|
|
|
|
/* Clamp swapchain size to boundaries. */
|
|
if (swapchain_size.width > surface_properties.maxImageExtent.width)
|
|
swapchain_size.width = surface_properties.maxImageExtent.width;
|
|
if (swapchain_size.width < surface_properties.minImageExtent.width)
|
|
swapchain_size.width = surface_properties.minImageExtent.width;
|
|
if (swapchain_size.height > surface_properties.maxImageExtent.height)
|
|
swapchain_size.height = surface_properties.maxImageExtent.height;
|
|
if (swapchain_size.height < surface_properties.minImageExtent.height)
|
|
swapchain_size.height = surface_properties.minImageExtent.height;
|
|
|
|
if (swapchain_size.width == 0 && swapchain_size.height == 0)
|
|
{
|
|
/* Cannot create swapchain yet, try again later. */
|
|
if (vk->swapchain != VK_NULL_HANDLE)
|
|
vkDestroySwapchainKHR(vk->context.device, vk->swapchain, NULL);
|
|
vk->swapchain = VK_NULL_HANDLE;
|
|
vk->context.swapchain_width = width;
|
|
vk->context.swapchain_height = height;
|
|
vk->context.num_swapchain_images = 1;
|
|
|
|
memset(vk->context.swapchain_images, 0, sizeof(vk->context.swapchain_images));
|
|
RARCH_LOG("[Vulkan]: Cannot create a swapchain yet. Will try again later ...\n");
|
|
return true;
|
|
}
|
|
|
|
RARCH_LOG("[Vulkan]: Using swapchain size %u x %u.\n",
|
|
swapchain_size.width, swapchain_size.height);
|
|
|
|
desired_swapchain_images = surface_properties.minImageCount + 1;
|
|
|
|
/* Limit latency. */
|
|
if (desired_swapchain_images > settings->uints.video_max_swapchain_images)
|
|
desired_swapchain_images = settings->uints.video_max_swapchain_images;
|
|
|
|
if (desired_swapchain_images < surface_properties.minImageCount)
|
|
desired_swapchain_images = surface_properties.minImageCount;
|
|
|
|
if ((surface_properties.maxImageCount > 0)
|
|
&& (desired_swapchain_images > surface_properties.maxImageCount))
|
|
desired_swapchain_images = surface_properties.maxImageCount;
|
|
|
|
if (surface_properties.supportedTransforms
|
|
& VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
|
|
pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
|
else
|
|
pre_transform = surface_properties.currentTransform;
|
|
|
|
if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
|
|
composite = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
|
|
composite = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
|
|
else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
|
|
composite = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
|
|
else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
|
|
composite = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
|
|
|
|
old_swapchain = vk->swapchain;
|
|
|
|
if (/* NVIDIA WAR */
|
|
vk->context.gpu_properties.vendorID == 0x10DE &&
|
|
old_swapchain != VK_NULL_HANDLE)
|
|
{
|
|
RARCH_LOG("[Vulkan]: Old swapchain destroyed.\n");
|
|
vkDestroySwapchainKHR(vk->context.device, old_swapchain, NULL);
|
|
old_swapchain = VK_NULL_HANDLE;
|
|
}
|
|
|
|
info.surface = vk->vk_surface;
|
|
info.minImageCount = desired_swapchain_images;
|
|
info.imageFormat = format.format;
|
|
info.imageColorSpace = format.colorSpace;
|
|
info.imageExtent.width = swapchain_size.width;
|
|
info.imageExtent.height = swapchain_size.height;
|
|
info.imageArrayLayers = 1;
|
|
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
info.preTransform = pre_transform;
|
|
info.compositeAlpha = composite;
|
|
info.presentMode = swapchain_present_mode;
|
|
info.clipped = true;
|
|
info.oldSwapchain = old_swapchain;
|
|
info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
|
|
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
|
|
if (vkCreateSwapchainKHR(vk->context.device,
|
|
&info, NULL, &vk->swapchain) != VK_SUCCESS)
|
|
{
|
|
RARCH_ERR("[Vulkan]: Failed to create swapchain.\n");
|
|
return false;
|
|
}
|
|
|
|
if ( vk->context.gpu_properties.vendorID != 0x10DE &&
|
|
old_swapchain != VK_NULL_HANDLE)
|
|
{
|
|
RARCH_LOG("[Vulkan]: Recycled old swapchain.\n");
|
|
vkDestroySwapchainKHR(vk->context.device, old_swapchain, NULL);
|
|
}
|
|
|
|
vk->context.swapchain_width = swapchain_size.width;
|
|
vk->context.swapchain_height = swapchain_size.height;
|
|
|
|
/* Make sure we create a backbuffer format that is as we expect. */
|
|
switch (format.format)
|
|
{
|
|
case VK_FORMAT_B8G8R8A8_SRGB:
|
|
vk->context.swapchain_format = VK_FORMAT_B8G8R8A8_UNORM;
|
|
vk->context.swapchain_is_srgb = true;
|
|
break;
|
|
|
|
case VK_FORMAT_R8G8B8A8_SRGB:
|
|
vk->context.swapchain_format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
vk->context.swapchain_is_srgb = true;
|
|
break;
|
|
|
|
case VK_FORMAT_R8G8B8_SRGB:
|
|
vk->context.swapchain_format = VK_FORMAT_R8G8B8_UNORM;
|
|
vk->context.swapchain_is_srgb = true;
|
|
break;
|
|
|
|
case VK_FORMAT_B8G8R8_SRGB:
|
|
vk->context.swapchain_format = VK_FORMAT_B8G8R8_UNORM;
|
|
vk->context.swapchain_is_srgb = true;
|
|
break;
|
|
|
|
default:
|
|
vk->context.swapchain_format = format.format;
|
|
break;
|
|
}
|
|
|
|
vkGetSwapchainImagesKHR(vk->context.device, vk->swapchain,
|
|
&vk->context.num_swapchain_images, NULL);
|
|
vkGetSwapchainImagesKHR(vk->context.device, vk->swapchain,
|
|
&vk->context.num_swapchain_images, vk->context.swapchain_images);
|
|
|
|
RARCH_LOG("[Vulkan]: Got %u swapchain images.\n",
|
|
vk->context.num_swapchain_images);
|
|
|
|
vulkan_acquire_clear_fences(vk);
|
|
vk->context.invalid_swapchain = true;
|
|
|
|
return true;
|
|
}
|