RetroArch/gfx/drivers/vulkan.c
orbea 260ce526c2 Fix segfaults when starting vulkan without a working vulkan driver.
RetroArch will crash in several places when running vulkan in an
environment that does not have working vulkan drivers.

This should guard against those crashes and allow RetroArch to fail
safely in those cases.
2018-08-24 19:36:03 -07:00

2694 lines
83 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <string.h>
#include <compat/strl.h>
#include <gfx/scaler/scaler.h>
#include <gfx/video_frame.h>
#include <formats/image.h>
#include <retro_inline.h>
#include <retro_miscellaneous.h>
#include <retro_math.h>
#include <retro_assert.h>
#include <libretro.h>
#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif
#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#include "../font_driver.h"
#include "../common/vulkan_common.h"
#include "../../driver.h"
#include "../../configuration.h"
#include "../../record/record_driver.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
#include "../video_coord_array.h"
static void vulkan_set_viewport(void *data, unsigned viewport_width,
unsigned viewport_height, bool force_full, bool allow_rotate);
#ifdef HAVE_OVERLAY
static void vulkan_overlay_free(vk_t *vk);
static void vulkan_render_overlay(vk_t *vk, video_frame_info_t *video_info);
#endif
static void vulkan_viewport_info(void *data, struct video_viewport *vp);
static const gfx_ctx_driver_t *vulkan_get_context(vk_t *vk)
{
unsigned major = 1;
unsigned minor = 0;
settings_t *settings = config_get_ptr();
enum gfx_ctx_api api = GFX_CTX_VULKAN_API;
return video_context_driver_init_first(
vk, settings->arrays.video_context_driver,
api, major, minor, false);
}
static void vulkan_init_render_pass(
vk_t *vk)
{
VkRenderPassCreateInfo rp_info = {
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
VkAttachmentDescription attachment = {0};
VkSubpassDescription subpass = {0};
VkAttachmentReference color_ref = { 0,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
/* 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;
/* We have one subpass.
* This subpass has 1 color attachment. */
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_ref;
/* Finally, create the renderpass. */
rp_info.attachmentCount = 1;
rp_info.pAttachments = &attachment;
rp_info.subpassCount = 1;
rp_info.pSubpasses = &subpass;
vkCreateRenderPass(vk->context->device,
&rp_info, NULL, &vk->render_pass);
}
static void vulkan_init_framebuffers(
vk_t *vk)
{
unsigned i;
vulkan_init_render_pass(vk);
for (i = 0; i < vk->num_swapchain_images; i++)
{
VkImageViewCreateInfo view =
{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
VkFramebufferCreateInfo info =
{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
vk->swapchain[i].backbuffer.image = vk->context->swapchain_images[i];
if (vk->context->swapchain_images[i] == VK_NULL_HANDLE)
{
vk->swapchain[i].backbuffer.view = VK_NULL_HANDLE;
vk->swapchain[i].backbuffer.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->swapchain[i].backbuffer.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->swapchain[i].backbuffer.view);
/* Create the framebuffer */
info.renderPass = vk->render_pass;
info.attachmentCount = 1;
info.pAttachments = &vk->swapchain[i].backbuffer.view;
info.width = vk->context->swapchain_width;
info.height = vk->context->swapchain_height;
info.layers = 1;
vkCreateFramebuffer(vk->context->device,
&info, NULL, &vk->swapchain[i].backbuffer.framebuffer);
}
}
static void vulkan_init_pipeline_layout(
vk_t *vk)
{
VkDescriptorSetLayoutCreateInfo set_layout_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
VkPipelineLayoutCreateInfo layout_info = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
VkDescriptorSetLayoutBinding bindings[2] = {{0}};
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;
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;
set_layout_info.bindingCount = 2;
set_layout_info.pBindings = bindings;
vkCreateDescriptorSetLayout(vk->context->device,
&set_layout_info, NULL, &vk->pipelines.set_layout);
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &vk->pipelines.set_layout;
vkCreatePipelineLayout(vk->context->device,
&layout_info, NULL, &vk->pipelines.layout);
}
static void vulkan_init_pipelines(vk_t *vk)
{
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 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"
;
unsigned i;
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 };
VkPipelineMultisampleStateCreateInfo multisample = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_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 };
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 = false;
raster.rasterizerDiscardEnable = false;
raster.depthBiasEnable = false;
raster.lineWidth = 1.0f;
/* Blend state */
blend_attachment.blendEnable = 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 = false;
depth_stencil.depthWriteEnable = false;
depth_stencil.depthBoundsTestEnable = false;
depth_stencil.stencilTestEnable = false;
depth_stencil.minDepthBounds = 0.0f;
depth_stencil.maxDepthBounds = 1.0f;
/* Multisample state */
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
/* 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 = 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[0].module, NULL);
vkDestroyShaderModule(vk->context->device, shader_stages[1].module, NULL);
/* Other menu pipelines. */
for (i = 0; i < ARRAY_SIZE(vk->display.pipelines) - 4; 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:
retro_assert(0 && "No shader for menu pipeline.");
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:
retro_assert(0 && "No shader for menu pipeline.");
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_ONE;
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[4 + i]);
vkDestroyShaderModule(vk->context->device, shader_stages[0].module, NULL);
vkDestroyShaderModule(vk->context->device, shader_stages[1].module, NULL);
}
}
static void vulkan_init_command_buffers(vk_t *vk)
{
/* RESET_COMMAND_BUFFER_BIT allows command buffer to be reset. */
unsigned i;
for (i = 0; i < vk->num_swapchain_images; i++)
{
VkCommandPoolCreateInfo pool_info = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
VkCommandBufferAllocateInfo info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
pool_info.queueFamilyIndex = vk->context->graphics_queue_index;
pool_info.flags =
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
vkCreateCommandPool(vk->context->device,
&pool_info, NULL, &vk->swapchain[i].cmd_pool);
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);
}
}
static void vulkan_init_samplers(vk_t *vk)
{
VkSamplerCreateInfo info =
{ VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
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.maxAnisotropy = 1.0f;
info.compareEnable = false;
info.minLod = 0.0f;
info.maxLod = 0.0f;
info.unnormalizedCoordinates = false;
info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
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_deinit_samplers(vk_t *vk)
{
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);
}
static void vulkan_init_buffers(vk_t *vk)
{
unsigned i;
for (i = 0; i < vk->num_swapchain_images; i++)
{
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);
}
}
static void vulkan_deinit_buffers(vk_t *vk)
{
unsigned i;
for (i = 0; i < 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_init_descriptor_pool(vk_t *vk)
{
unsigned i;
static const VkDescriptorPoolSize pool_sizes[2] = {
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS },
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VULKAN_DESCRIPTOR_MANAGER_BLOCK_SETS },
};
for (i = 0; i < vk->num_swapchain_images; i++)
{
vk->swapchain[i].descriptor_manager =
vulkan_create_descriptor_manager(
vk->context->device,
pool_sizes, 2, vk->pipelines.set_layout);
}
}
static void vulkan_deinit_descriptor_pool(vk_t *vk)
{
unsigned i;
for (i = 0; i < 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)
{
unsigned i;
vulkan_init_samplers(vk);
if (!vk->hw.enable)
{
for (i = 0; i < 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);
vulkan_map_persistent_texture(
vk->context->device,
&vk->swapchain[i].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);
}
}
}
static void vulkan_deinit_textures(vk_t *vk)
{
unsigned i;
vulkan_deinit_samplers(vk);
for (i = 0; i < 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);
}
}
static void vulkan_deinit_command_buffers(vk_t *vk)
{
unsigned i;
for (i = 0; i < 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_pipeline_layout(vk_t *vk)
{
vkDestroyPipelineLayout(vk->context->device,
vk->pipelines.layout, NULL);
vkDestroyDescriptorSetLayout(vk->context->device,
vk->pipelines.set_layout, NULL);
}
static void vulkan_deinit_pipelines(vk_t *vk)
{
unsigned i;
vulkan_deinit_pipeline_layout(vk);
vkDestroyPipeline(vk->context->device,
vk->pipelines.alpha_blend, NULL);
vkDestroyPipeline(vk->context->device,
vk->pipelines.font, NULL);
for (i = 0; i < ARRAY_SIZE(vk->display.pipelines); i++)
vkDestroyPipeline(vk->context->device,
vk->display.pipelines[i], NULL);
}
static void vulkan_deinit_framebuffers(vk_t *vk)
{
unsigned i;
for (i = 0; i < vk->num_swapchain_images; i++)
{
if (vk->swapchain[i].backbuffer.framebuffer)
{
vkDestroyFramebuffer(vk->context->device,
vk->swapchain[i].backbuffer.framebuffer, NULL);
}
if (vk->swapchain[i].backbuffer.view)
{
vkDestroyImageView(vk->context->device,
vk->swapchain[i].backbuffer.view, NULL);
}
}
vkDestroyRenderPass(vk->context->device, vk->render_pass, NULL);
}
static bool vulkan_init_default_filter_chain(vk_t *vk)
{
struct vulkan_filter_chain_create_info info;
memset(&info, 0, sizeof(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_swapchain_index].cmd_pool;
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;
info.original_format = vk->tex_fmt;
vk->filter_chain = vulkan_filter_chain_create_default(
&info,
vk->video.smooth ?
VULKAN_FILTER_CHAIN_LINEAR : VULKAN_FILTER_CHAIN_NEAREST);
if (!vk->filter_chain)
{
RARCH_ERR("Failed to create filter chain.\n");
return false;
}
return true;
}
static bool vulkan_init_filter_chain_preset(vk_t *vk, const char *shader_path)
{
struct vulkan_filter_chain_create_info info;
memset(&info, 0, sizeof(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_swapchain_index].cmd_pool;
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;
info.original_format = vk->tex_fmt;
vk->filter_chain = vulkan_filter_chain_create_from_preset(
&info, shader_path,
vk->video.smooth ?
VULKAN_FILTER_CHAIN_LINEAR : VULKAN_FILTER_CHAIN_NEAREST);
if (!vk->filter_chain)
{
RARCH_ERR("[Vulkan]: Failed to create preset: \"%s\".\n", shader_path);
return false;
}
return true;
}
static bool vulkan_init_filter_chain(vk_t *vk)
{
const char *shader_path = retroarch_get_shader_preset();
enum rarch_shader_type type = video_shader_parse_type(shader_path, RARCH_SHADER_NONE);
if (type == RARCH_SHADER_NONE)
{
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_resources(vk_t *vk)
{
if (!vk->context)
return;
vk->num_swapchain_images = vk->context->num_swapchain_images;
vulkan_init_framebuffers(vk);
vulkan_init_pipelines(vk);
vulkan_init_descriptor_pool(vk);
vulkan_init_textures(vk);
vulkan_init_buffers(vk);
vulkan_init_command_buffers(vk);
}
static void vulkan_init_static_resources(vk_t *vk)
{
unsigned i;
uint32_t blank[4 * 4];
VkCommandPoolCreateInfo pool_info = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
/* Create the pipeline cache. */
VkPipelineCacheCreateInfo cache = {
VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
if (!vk->context)
return;
vkCreatePipelineCache(vk->context->device,
&cache, NULL, &vk->pipelines.cache);
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)
{
unsigned 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);
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_resources(vk_t *vk)
{
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);
}
static void vulkan_deinit_menu(vk_t *vk)
{
unsigned 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]);
}
}
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_resources(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);
video_context_driver_free();
}
scaler_ctx_gen_reset(&vk->readback.scaler);
free(vk);
}
static uint32_t vulkan_get_sync_index(void *handle)
{
vk_t *vk = (vk_t*)handle;
return vk->context->current_swapchain_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)
{
unsigned i;
vk_t *vk = (vk_t*)handle;
vk->hw.image = image;
vk->hw.num_semaphores = num_semaphores;
vk->hw.semaphores = semaphores;
if (num_semaphores > 0)
{
vk->hw.wait_dst_stages = (VkPipelineStageFlags*)
realloc(vk->hw.wait_dst_stages,
sizeof(VkPipelineStageFlags) * vk->hw.num_semaphores);
/* If this fails, we're screwed anyways. */
retro_assert(vk->hw.wait_dst_stages);
for (i = 0; i < vk->hw.num_semaphores; i++)
vk->hw.wait_dst_stages[i] = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
vk->hw.valid_semaphore = true;
vk->hw.src_queue_family = src_queue_family;
}
}
static void vulkan_wait_sync_index(void *handle)
{
(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)
{
vk->hw.cmd = (VkCommandBuffer*)realloc(vk->hw.cmd,
sizeof(VkCommandBuffer) * required_capacity);
/* If this fails, we're just screwed. */
retro_assert(vk->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)
{
vk_t *vk = (vk_t*)handle;
#ifdef HAVE_THREADS
slock_lock(vk->context->queue_lock);
#endif
}
static void vulkan_unlock_queue(void *handle)
{
vk_t *vk = (vk_t*)handle;
#ifdef HAVE_THREADS
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->hw.enable = true;
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)
{
/* Only bother with this if we're doing GPU recording.
* Check recording_is_enabled() and not
* driver.recording_data, because recording is
* not initialized yet.
*/
settings_t *settings = config_get_ptr();
bool *recording_enabled = recording_is_enabled();
vk->readback.streamed = settings->bools.video_gpu_record && *recording_enabled;
if (!vk->readback.streamed)
return;
vk->readback.scaler.in_width = vk->vp.width;
vk->readback.scaler.in_height = vk->vp.height;
vk->readback.scaler.out_width = vk->vp.width;
vk->readback.scaler.out_height = vk->vp.height;
vk->readback.scaler.in_fmt = SCALER_FMT_ARGB8888;
vk->readback.scaler.out_fmt = SCALER_FMT_BGR24;
vk->readback.scaler.scaler_type = SCALER_TYPE_POINT;
if (!scaler_ctx_gen_filter(&vk->readback.scaler))
{
vk->readback.streamed = false;
RARCH_ERR("[Vulkan]: Failed to initialize scaler context.\n");
}
}
static void *vulkan_init(const video_info_t *video,
const input_driver_t **input,
void **input_data)
{
gfx_ctx_mode_t mode;
gfx_ctx_input_t inp;
unsigned interval;
unsigned full_x, full_y;
unsigned win_width;
unsigned win_height;
unsigned temp_width = 0;
unsigned temp_height = 0;
const gfx_ctx_driver_t *ctx_driver = NULL;
vk_t *vk = (vk_t*)calloc(1, sizeof(*vk));
if (!vk)
return NULL;
vk->video = *video;
ctx_driver = vulkan_get_context(vk);
if (!ctx_driver)
{
RARCH_ERR("[Vulkan]: Failed to get Vulkan context.\n");
goto error;
}
video_context_driver_set((const gfx_ctx_driver_t*)ctx_driver);
video_context_driver_get_video_size(&mode);
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;
video_context_driver_swap_interval(&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;
}
mode.width = win_width;
mode.height = win_height;
mode.fullscreen = video->fullscreen;
if (!video_context_driver_set_video_mode(&mode))
{
RARCH_ERR("[Vulkan]: Failed to set video mode.\n");
goto error;
}
video_context_driver_get_video_size(&mode);
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);
RARCH_LOG("[Vulkan]: Using resolution %ux%u\n", temp_width, temp_height);
video_context_driver_get_context_data(&vk->context);
vk->vsync = video->vsync;
vk->fullscreen = video->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;
vk->keep_aspect = video->force_aspect;
RARCH_LOG("[Vulkan]: Using %s format.\n", video->rgb32 ? "BGRA8888" : "RGB565");
/* Set the viewport to fix recording, since it needs to know
* the viewport sizes before we start running. */
vulkan_set_viewport(vk, temp_width, temp_height, false, true);
vulkan_init_hw_render(vk);
vulkan_init_static_resources(vk);
vulkan_init_resources(vk);
if (!vulkan_init_filter_chain(vk))
{
RARCH_ERR("[Vulkan]: Failed to init filter chain.\n");
goto error;
}
inp.input = input;
inp.input_data = input_data;
video_context_driver_input_driver(&inp);
if (video->font_enable)
font_driver_init_osd(vk, false,
video->is_threaded,
FONT_DRIVER_RENDER_VULKAN_API);
vulkan_init_readback(vk);
return vk;
error:
vulkan_free(vk);
return NULL;
}
static void vulkan_update_filter_chain(vk_t *vk)
{
struct vulkan_filter_chain_swapchain_info info;
info.viewport = vk->vk_vp;
info.format = vk->context->swapchain_format;
info.render_pass = vk->render_pass;
info.num_indices = vk->context->num_swapchain_images;
if (!vulkan_filter_chain_update_swapchain_info((vulkan_filter_chain_t*)vk->filter_chain, &info))
RARCH_ERR("Failed to update filter chain info. This will probably lead to a crash ...\n");
}
static void vulkan_check_swapchain(vk_t *vk)
{
if (vk->context->invalid_swapchain)
{
#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_resources(vk);
vulkan_init_resources(vk);
vk->context->invalid_swapchain = false;
vulkan_update_filter_chain(vk);
}
}
static void vulkan_set_nonblock_state(void *data, bool state)
{
unsigned interval;
vk_t *vk = (vk_t*)data;
settings_t *settings = config_get_ptr();
if (!vk)
return;
RARCH_LOG("[Vulkan]: VSync => %s\n", state ? "off" : "on");
interval = state ? 0 : settings->uints.video_swap_interval;
video_context_driver_swap_interval(&interval);
/* Changing vsync might require recreating the swapchain, which means new VkImages
* to render into. */
vulkan_check_swapchain(vk);
}
static bool vulkan_alive(void *data)
{
gfx_ctx_size_t size_data;
unsigned temp_width = 0;
unsigned temp_height = 0;
bool ret = false;
bool quit = false;
bool resize = false;
vk_t *vk = (vk_t*)data;
video_driver_get_size(&temp_width, &temp_height);
size_data.quit = &quit;
size_data.resize = &resize;
size_data.width = &temp_width;
size_data.height = &temp_height;
if (video_context_driver_check_window(&size_data))
{
if (quit)
vk->quitting = true;
else if (resize)
vk->should_resize = true;
ret = !vk->quitting;
}
if (temp_width != 0 && temp_height != 0)
video_driver_set_size(&temp_width, &temp_height);
return ret;
}
static bool vulkan_suppress_screensaver(void *data, bool enable)
{
bool enabled = enable;
(void)data;
return video_context_driver_suppress_screensaver(&enabled);
}
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 (type != RARCH_SHADER_SLANG && path)
{
RARCH_WARN("[Vulkan]: Only .slang or .slangp shaders are supported. Falling back to stock.\n");
path = NULL;
}
if (vk->filter_chain)
vulkan_filter_chain_free((vulkan_filter_chain_t*)vk->filter_chain);
vk->filter_chain = NULL;
if (!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)
{
math_matrix_4x4 rot;
/* 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;
}
matrix_4x4_rotate_z(rot, M_PI * vk->rotation / 180.0f);
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)
{
gfx_ctx_mode_t mode;
(void)data;
mode.width = width;
mode.height = height;
mode.fullscreen = fullscreen;
video_context_driver_set_video_mode(&mode);
}
static void vulkan_set_viewport(void *data, unsigned viewport_width,
unsigned viewport_height, bool force_full, bool allow_rotate)
{
gfx_ctx_aspect_t aspect_data;
unsigned width, height;
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();
vk_t *vk = (vk_t*)data;
video_driver_get_size(&width, &height);
aspect_data.aspect = &device_aspect;
aspect_data.width = viewport_width;
aspect_data.height = viewport_height;
video_context_driver_translate_aspect(&aspect_data);
if (settings->bools.video_scale_integer && !force_full)
{
video_viewport_get_scaled_integer(&vk->vp,
viewport_width, viewport_height,
video_driver_get_aspect_ratio(), vk->keep_aspect);
viewport_width = vk->vp.width;
viewport_height = vk->vp.height;
}
else if (vk->keep_aspect && !force_full)
{
float desired_aspect = video_driver_get_aspect_ratio();
#if defined(HAVE_MENU)
if (settings->uints.video_aspect_ratio_idx == ASPECT_RATIO_CUSTOM)
{
const struct video_viewport *custom = video_viewport_get_custom();
/* Vulkan has top-left origin viewport. */
x = custom->x;
y = custom->y;
viewport_width = custom->width;
viewport_height = custom->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;
#if 0
RARCH_LOG("Setting viewport @ %ux%u\n", viewport_width, viewport_height);
#endif
}
static void vulkan_readback(vk_t *vk)
{
VkImageCopy region;
struct vk_texture *staging;
struct video_viewport vp;
vulkan_viewport_info(vk, &vp);
memset(&region, 0, sizeof(region));
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.layerCount = 1;
region.dstSubresource = region.srcSubresource;
region.srcOffset.x = vp.x;
region.srcOffset.y = vp.y;
region.extent.width = vp.width;
region.extent.height = vp.height;
region.extent.depth = 1;
/* FIXME: We won't actually get format conversion with vkCmdCopyImage, so have to check
* properly for this. BGRA seems to be the default for all swapchains. */
if (vk->context->swapchain_format != VK_FORMAT_B8G8R8A8_UNORM)
RARCH_WARN("[Vulkan]: Backbuffer is not BGRA8888, readbacks might not work properly.\n");
staging = &vk->readback.staging[vk->context->current_swapchain_index];
*staging = vulkan_create_texture(vk,
staging->memory != VK_NULL_HANDLE ? staging : NULL,
vk->vp.width, vk->vp.height,
VK_FORMAT_B8G8R8A8_UNORM,
NULL, NULL, VULKAN_TEXTURE_READBACK);
/* Go through the long-winded dance of remapping image layouts. */
vulkan_image_layout_transition(vk, vk->cmd, staging->image,
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL,
0, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
vkCmdCopyImage(vk->cmd, vk->chain->backbuffer.image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
staging->image,
VK_IMAGE_LAYOUT_GENERAL,
1, &region);
/* Make the data visible to host. */
vulkan_image_layout_transition(vk, vk->cmd, staging->image,
VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_HOST_BIT);
}
static void vulkan_inject_black_frame(vk_t *vk, video_frame_info_t *video_info)
{
VkCommandBufferBeginInfo begin_info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
VkSubmitInfo submit_info = {
VK_STRUCTURE_TYPE_SUBMIT_INFO };
const VkClearColorValue clear_color = {{ 0.0f, 0.0f, 0.0f, 1.0f }};
const VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
unsigned frame_index = vk->context->current_swapchain_index;
struct vk_per_frame *chain = &vk->swapchain[frame_index];
vk->chain = chain;
vk->cmd = chain->cmd;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkResetCommandBuffer(vk->cmd, 0);
vkBeginCommandBuffer(vk->cmd, &begin_info);
vulkan_image_layout_transition(vk, vk->cmd, chain->backbuffer.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);
vkCmdClearColorImage(vk->cmd, chain->backbuffer.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
&clear_color, 1, &range);
vulkan_image_layout_transition(vk, vk->cmd, chain->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.commandBufferCount = 1;
submit_info.pCommandBuffers = &vk->cmd;
if (vk->context->swapchain_semaphores[frame_index] != VK_NULL_HANDLE)
{
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &vk->context->swapchain_semaphores[frame_index];
}
#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
video_info->cb_swap_buffers(video_info->context_data, video_info);
}
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)
{
VkSemaphore signal_semaphores[2];
vk_t *vk = (vk_t*)data;
struct vk_per_frame *chain = NULL;
bool waits_for_semaphores = false;
unsigned width = video_info->width;
unsigned height = video_info->height;
VkClearValue clear_color;
VkCommandBufferBeginInfo begin_info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
VkRenderPassBeginInfo rp_info = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
VkSubmitInfo submit_info = {
VK_STRUCTURE_TYPE_SUBMIT_INFO };
unsigned frame_index =
vk->context->current_swapchain_index;
/* Bookkeeping on start of frame. */
chain = &vk->swapchain[frame_index];
vk->chain = chain;
vulkan_descriptor_manager_restart(&chain->descriptor_manager);
vulkan_buffer_chain_discard(&chain->vbo);
vulkan_buffer_chain_discard(&chain->ubo);
/* Start recording the command buffer. */
vk->cmd = chain->cmd;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkResetCommandBuffer(vk->cmd, 0);
vkBeginCommandBuffer(vk->cmd, &begin_info);
memset(&vk->tracker, 0, sizeof(vk->tracker));
waits_for_semaphores = vk->hw.enable && frame &&
!vk->hw.num_cmd && vk->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)
{
retro_assert(vk->hw.image);
/* 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->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);
vulkan_map_persistent_texture(
vk->context->device, &chain->texture);
if (chain->texture.type == VULKAN_TEXTURE_STAGING)
{
chain->texture_optimal = vulkan_create_texture(
vk,
&chain->texture_optimal,
frame_width, frame_height, chain->texture_optimal.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);
}
/* If we have an optimal texture, copy to that now. */
if (chain->texture_optimal.memory != VK_NULL_HANDLE)
{
vulkan_copy_staging_to_dynamic(vk, vk->cmd,
&chain->texture_optimal, &chain->texture);
}
else
vulkan_sync_texture_to_gpu(vk, &chain->texture);
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);
/* Render offscreen filter chain passes. */
{
/* Set the source texture in the filter chain */
struct vulkan_filter_chain_texture input;
if (vk->hw.enable)
{
/* Does this make that this can happen at all? */
if (!vk->hw.image)
{
RARCH_ERR("[Vulkan]: HW image is not set. Buggy core?\n");
return false;
}
input.image = vk->hw.image->create_info.image;
input.view = vk->hw.image->image_view;
input.layout = vk->hw.image->image_layout;
if (frame)
{
input.width = frame_width;
input.height = frame_height;
}
else
{
input.width = vk->hw.last_width;
input.height = vk->hw.last_height;
}
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
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;
}
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);
/* Render to backbuffer. */
if (chain->backbuffer.image != VK_NULL_HANDLE)
{
rp_info.renderPass = vk->render_pass;
rp_info.framebuffer = chain->backbuffer.framebuffer;
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. We don't use WSI semaphores here. */
vulkan_image_layout_transition(vk, vk->cmd, chain->backbuffer.image,
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_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);
#if defined(HAVE_MENU)
if (vk->menu.enable)
{
menu_driver_frame(video_info);
if (vk->menu.textures[vk->menu.last_index].image != VK_NULL_HANDLE)
{
struct vk_draw_quad quad;
struct vk_texture *optimal = &vk->menu.textures_optimal[vk->menu.last_index];
vulkan_set_viewport(vk, width, height, vk->menu.full_screen, false);
quad.pipeline = vk->pipelines.alpha_blend;
quad.texture = &vk->menu.textures[vk->menu.last_index];
if (optimal->memory != VK_NULL_HANDLE)
{
if (vk->menu.dirty[vk->menu.last_index])
{
vulkan_copy_staging_to_dynamic(vk, vk->cmd,
optimal,
quad.texture);
vk->menu.dirty[vk->menu.last_index] = false;
}
quad.texture = optimal;
}
quad.sampler = optimal->mipmap ?
vk->samplers.mipmap_linear : vk->samplers.linear;
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 (video_info->statistics_show)
{
struct font_params *osd_params = (struct font_params*)
&video_info->osd_stat_params;
if (osd_params)
{
font_driver_render_msg(video_info, NULL, video_info->stat_text,
(const struct font_params*)&video_info->osd_stat_params);
}
}
#endif
if (msg)
font_driver_render_msg(video_info, NULL, msg, NULL);
#ifdef HAVE_OVERLAY
if (vk->overlay.enable)
vulkan_render_overlay(vk, video_info);
#endif
/* End the render pass. We're done rendering to backbuffer now. */
vkCmdEndRenderPass(vk->cmd);
}
/* 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 (chain->backbuffer.image != VK_NULL_HANDLE &&
(vk->readback.pending || vk->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,
vk->cmd, chain->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_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
vulkan_readback(vk);
/* Prepare for presentation after transfers are complete. */
vulkan_image_layout_transition(vk, vk->cmd,
chain->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->readback.pending = false;
}
else if (chain->backbuffer.image != VK_NULL_HANDLE)
{
/* Prepare backbuffer for presentation. */
vulkan_image_layout_transition(vk, vk->cmd,
chain->backbuffer.image,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_ALL_GRAPHICS_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)
{
retro_assert(vk->hw.image);
/* 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. */
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->hw.valid_semaphore = false;
}
submit_info.signalSemaphoreCount = 0;
if (vk->context->swapchain_semaphores[frame_index] != VK_NULL_HANDLE)
signal_semaphores[submit_info.signalSemaphoreCount++] = vk->context->swapchain_semaphores[frame_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
video_info->cb_swap_buffers(video_info->context_data, video_info);
if (!vk->context->swap_interval_emulation_lock)
video_info->cb_update_window_title(
video_info->context_data, video_info);
/* Handle spurious swapchain invalidations as soon as we can,
* i.e. right after swap buffers. */
if (vk->should_resize)
{
gfx_ctx_mode_t mode;
mode.width = width;
mode.height = height;
video_info->cb_set_resize(video_info->context_data, mode.width, mode.height);
vk->should_resize = false;
}
vulkan_check_swapchain(vk);
/* Disable BFI during fast forward, slow-motion,
* and pause to prevent flicker. */
if (
video_info->black_frame_insertion
&& !video_info->input_driver_nonblock_state
&& !video_info->runloop_is_slowmotion
&& !video_info->runloop_is_paused)
{
vulkan_inject_black_frame(vk, video_info);
}
/* 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->swap_interval_emulation_lock)
{
unsigned i;
vk->context->swap_interval_emulation_lock = true;
for (i = 1; i < vk->context->swap_interval; i++)
{
if (!vulkan_frame(vk, NULL, 0, 0, frame_count, 0, msg,
video_info))
{
vk->context->swap_interval_emulation_lock = false;
return false;
}
}
vk->context->swap_interval_emulation_lock = false;
}
return true;
}
static void vulkan_set_aspect_ratio(void *data, unsigned aspect_ratio_idx)
{
vk_t *vk = (vk_t*)data;
switch (aspect_ratio_idx)
{
case ASPECT_RATIO_SQUARE:
video_driver_set_viewport_square_pixel();
break;
case ASPECT_RATIO_CORE:
video_driver_set_viewport_core();
break;
case ASPECT_RATIO_CONFIG:
video_driver_set_viewport_config();
break;
default:
break;
}
video_driver_set_aspect_ratio_value(
aspectratio_lut[aspect_ratio_idx].value);
if (!vk)
return;
vk->keep_aspect = true;
vk->should_resize = true;
}
static void vulkan_apply_state_changes(void *data)
{
vk_t *vk = (vk_t*)data;
if (vk)
vk->should_resize = true;
}
static void vulkan_show_mouse(void *data, bool state)
{
(void)data;
video_context_driver_show_mouse(&state);
}
static struct video_shader *vulkan_get_current_shader(void *data)
{
vk_t *vk = (vk_t*)data;
if (!vk || !vk->filter_chain)
return NULL;
return vulkan_filter_chain_get_preset((vulkan_filter_chain_t*)vk->filter_chain);
}
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_swapchain_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);
vulkan_map_persistent_texture(
vk->context->device, &chain->texture);
if (chain->texture.type == VULKAN_TEXTURE_STAGING)
{
chain->texture_optimal = vulkan_create_texture(
vk,
&chain->texture_optimal,
framebuffer->width,
framebuffer->height,
chain->texture_optimal.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_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->hw.enable;
}
static void vulkan_set_texture_frame(void *data,
const void *frame, bool rgb32, unsigned width, unsigned height,
float alpha)
{
unsigned y, stride;
uint8_t *ptr = NULL;
uint8_t *dst = NULL;
const uint8_t *src = NULL;
vk_t *vk = (vk_t*)data;
unsigned index = 0;
struct vk_texture *texture = NULL;
struct vk_texture *texture_optimal = NULL;
const VkComponentMapping br_swizzle = {
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_A,
};
if (!vk)
return;
index = vk->context->current_swapchain_index;
texture = &vk->menu.textures[index];
texture_optimal = &vk->menu.textures_optimal[index];
/* B4G4R4A4 must be supported, but R4G4B4A4 is optional,
* just apply the swizzle in the image view instead. */
*texture = vulkan_create_texture(vk,
texture->memory ? texture : NULL,
width, height,
rgb32 ? VK_FORMAT_B8G8R8A8_UNORM : VK_FORMAT_B4G4R4A4_UNORM_PACK16,
NULL, rgb32 ? NULL : &br_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;
for (y = 0; y < height; y++, dst += texture->stride, src += stride)
memcpy(dst, src, stride);
vulkan_sync_texture_to_gpu(vk, texture);
vkUnmapMemory(vk->context->device, texture->memory);
vk->menu.alpha = alpha;
vk->menu.last_index = index;
if (texture->type == VULKAN_TEXTURE_STAGING)
{
*texture_optimal = vulkan_create_texture(vk,
texture_optimal->memory ? texture_optimal : NULL,
width, height,
rgb32 ? VK_FORMAT_B8G8R8A8_UNORM : VK_FORMAT_B4G4R4A4_UNORM_PACK16,
NULL, rgb32 ? NULL : &br_swizzle,
VULKAN_TEXTURE_DYNAMIC);
}
vk->menu.dirty[index] = true;
}
static void vulkan_set_texture_enable(void *data, bool state, bool full_screen)
{
vk_t *vk = (vk_t*)data;
if (!vk)
return;
vk->menu.enable = state;
vk->menu.full_screen = full_screen;
}
static void vulkan_set_osd_msg(void *data,
video_frame_info_t *video_info,
const char *msg,
const void *params, void *font)
{
font_driver_render_msg(video_info, font, msg, (const struct font_params *)params);
}
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;
texture = (struct vk_texture*)
calloc(1, sizeof(*texture));
if (!texture)
return 0;
if (!image->pixels || !image->width || !image->height)
{
/* Create a dummy texture instead. */
#define T0 0xff000000u
#define T1 0xffffffffu
static const uint32_t checkerboard[] = {
T0, T1, T0, T1, T0, T1, T0, T1,
T1, T0, T1, T0, T1, T0, T1, T0,
T0, T1, T0, T1, T0, T1, T0, T1,
T1, T0, T1, T0, T1, T0, T1, T0,
T0, T1, T0, T1, T0, T1, T0, T1,
T1, T0, T1, T0, T1, T0, T1, T0,
T0, T1, T0, T1, T0, T1, T0, T1,
T1, T0, T1, T0, T1, T0, T1, T0,
};
#undef T0
#undef T1
*texture = vulkan_create_texture(vk, NULL,
8, 8, VK_FORMAT_B8G8R8A8_UNORM,
checkerboard, NULL, VULKAN_TEXTURE_STATIC);
texture->default_smooth = false;
texture->mipmap = false;
}
else
{
*texture = vulkan_create_texture(vk, NULL,
image->width, image->height, VK_FORMAT_B8G8R8A8_UNORM,
image->pixels, NULL, VULKAN_TEXTURE_STATIC);
texture->default_smooth =
filter_type == TEXTURE_FILTER_MIPMAP_LINEAR || filter_type == TEXTURE_FILTER_LINEAR;
texture->mipmap = filter_type == TEXTURE_FILTER_MIPMAP_LINEAR;
}
return (uintptr_t)texture;
}
static void vulkan_unload_texture(void *data, uintptr_t handle)
{
vk_t *vk = (vk_t*)data;
struct vk_texture *texture = (struct vk_texture*)handle;
if (!texture)
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);
return flags;
}
static const video_poke_interface_t vulkan_poke_interface = {
vulkan_get_flags,
NULL, /* set_coords */
NULL, /* set_mvp */
vulkan_load_texture,
vulkan_unload_texture,
vulkan_set_video_mode,
vulkan_get_refresh_rate, /* get_refresh_rate */
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
vulkan_set_aspect_ratio,
vulkan_apply_state_changes,
vulkan_set_texture_frame,
vulkan_set_texture_enable,
vulkan_set_osd_msg,
vulkan_show_mouse,
NULL, /* grab_mouse_toggle */
vulkan_get_current_shader,
vulkan_get_current_sw_framebuffer,
vulkan_get_hw_render_interface,
};
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;
video_driver_get_size(&width, &height);
if (!vk)
return;
/* 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_swapchain_index];
if (vk->readback.streamed)
{
const uint8_t *src = NULL;
struct scaler_ctx *ctx = NULL;
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);
vulkan_sync_texture_to_cpu(vk, staging);
vk->readback.scaler.in_stride = staging->stride;
vk->readback.scaler.out_stride = -(int)vk->vp.width * 3;
ctx = &vk->readback.scaler;
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->readback.pending = true;
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->mapped)
vulkan_map_persistent_texture(
vk->context->device, staging);
vulkan_sync_texture_to_cpu(vk, staging);
{
unsigned x, y;
const uint8_t *src = (const uint8_t*)staging->mapped;
buffer += 3 * (vk->vp.height - 1)
* vk->vp.width;
for (y = 0; y < vk->vp.height; y++,
src += staging->stride, buffer -= 3 * vk->vp.width)
{
for (x = 0; x < 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];
}
}
}
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;
vk->overlay.enable = enable;
if (vk->fullscreen)
video_context_driver_show_mouse(&enable);
}
static void vulkan_overlay_full_screen(void *data, bool enable)
{
vk_t *vk = (vk_t*)data;
if (!vk)
return;
vk->overlay.full_screen = enable;
}
static void vulkan_overlay_free(vk_t *vk)
{
unsigned i;
if (!vk)
return;
free(vk->overlay.vertex);
for (i = 0; i < vk->overlay.count; i++)
if (vk->overlay.images[i].memory != VK_NULL_HANDLE)
vulkan_destroy_texture(
vk->context->device,
&vk->overlay.images[i]);
memset(&vk->overlay, 0, sizeof(vk->overlay));
}
static void vulkan_overlay_set_alpha(void *data,
unsigned image, float mod)
{
unsigned 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, video_frame_info_t *video_info)
{
unsigned i;
struct video_viewport vp;
unsigned width = video_info->width;
unsigned height = video_info->height;
if (!vk)
return;
vp = vk->vp;
vulkan_set_viewport(vk, width, height, vk->overlay.full_screen, false);
for (i = 0; i < 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));
memset(&call, 0, sizeof(call));
call.pipeline = vk->display.pipelines[3]; /* Strip with blend */
call.texture = &vk->overlay.images[i];
call.sampler = call.texture->mipmap ?
vk->samplers.mipmap_linear : vk->samplers.linear;
call.uniform = &vk->mvp;
call.uniform_size = sizeof(vk->mvp);
call.vbo = &range;
call.vertices = 4;
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)
{
unsigned i, j;
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
vulkan_overlay_free(vk);
vk->overlay.images = (struct vk_texture*)
calloc(num_images, sizeof(*vk->overlay.images));
if (!vk->overlay.images)
goto error;
vk->overlay.count = num_images;
vk->overlay.vertex = (struct vk_vertex*)
calloc(4 * num_images, sizeof(*vk->overlay.vertex));
if (!vk->overlay.vertex)
goto error;
for (i = 0; i < num_images; i++)
{
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;
}
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)
{
(void)data;
*iface = &vulkan_overlay_interface;
}
#endif
video_driver_t video_vulkan = {
vulkan_init,
vulkan_frame,
vulkan_set_nonblock_state,
vulkan_alive,
NULL, /* focus */
vulkan_suppress_screensaver,
NULL, /* has_windowed */
vulkan_set_shader,
vulkan_free,
"vulkan",
vulkan_set_viewport,
vulkan_set_rotation,
vulkan_viewport_info,
vulkan_read_viewport,
NULL, /* vulkan_read_frame_raw */
#ifdef HAVE_OVERLAY
vulkan_get_overlay_interface,
#endif
vulkan_get_poke_interface,
NULL, /* vulkan_wrap_type_to_enum */
};