RetroArch/gfx/drivers/vulkan.c
Hans-Kristian Arntzen 38a305bb6e Vulkan: Add proc address query functions to HW interface.
Allows cores to avoid linking directly against any extra library.
Update HW interface version to 2 to signal ABI change.

Note that the interface is still experimental and can change at any time.
2016-03-29 16:35:31 +02:00

2298 lines
70 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2016 - Hans-Kristian Arntzen
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "../common/vulkan_common.h"
#include "vulkan_shaders/alpha_blend.vert.inc"
#include "vulkan_shaders/alpha_blend.frag.inc"
#include "vulkan_shaders/font.frag.inc"
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include <string.h>
#include <compat/strl.h>
#include <gfx/scaler/scaler.h>
#include <formats/image.h>
#include <retro_inline.h>
#include <retro_miscellaneous.h>
#include <retro_assert.h>
#include "../../driver.h"
#include "../../record/record_driver.h"
#include "../../performance.h"
#include "../../libretro.h"
#include "../../general.h"
#include "../../retroarch.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#include "../font_driver.h"
#include "../video_context_driver.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);
#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 gfx_ctx_init_first(vk, settings->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.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
rp_info.attachmentCount = 1;
rp_info.pAttachments = &attachment;
rp_info.subpassCount = 1;
rp_info.pSubpasses = &subpass;
VKFUNC(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;
VkFramebufferCreateInfo info;
vk->swapchain[i].backbuffer.image = vk->context->swapchain_images[i];
/* Create an image view which we can render into. */
view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
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;
VKFUNC(vkCreateImageView)(vk->context->device,
&view, NULL, &vk->swapchain[i].backbuffer.view);
/* Create the framebuffer */
info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
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;
VKFUNC(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;
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;
VKFUNC(vkCreateDescriptorSetLayout)(vk->context->device,
&set_layout_info, NULL, &vk->pipelines.set_layout);
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &vk->pipelines.set_layout;
VKFUNC(vkCreatePipelineLayout)(vk->context->device,
&layout_info, NULL, &vk->pipelines.layout);
}
static void vulkan_init_pipelines(
vk_t *vk)
{
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 = alpha_blend_vert_spv_len;
module_info.pCode = (const uint32_t*)alpha_blend_vert_spv;
shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
shader_stages[0].pName = "main";
VKFUNC(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 = font_frag_spv_len;
module_info.pCode = (const uint32_t*)font_frag_spv;
shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shader_stages[1].pName = "main";
VKFUNC(vkCreateShaderModule)(vk->context->device,
&module_info, NULL, &shader_stages[1].module);
VKFUNC(vkCreateGraphicsPipelines)(vk->context->device, vk->pipelines.cache,
1, &pipe, NULL, &vk->pipelines.font);
VKFUNC(vkDestroyShaderModule)(vk->context->device, shader_stages[1].module, NULL);
/* Alpha-blended pipeline. */
module_info.codeSize = alpha_blend_frag_spv_len;
module_info.pCode = (const uint32_t*)alpha_blend_frag_spv;
shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shader_stages[1].pName = "main";
VKFUNC(vkCreateShaderModule)(vk->context->device,
&module_info, NULL, &shader_stages[1].module);
VKFUNC(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;
VKFUNC(vkCreateGraphicsPipelines)(vk->context->device, vk->pipelines.cache,
1, &pipe, NULL, &vk->display.pipelines[i]);
}
VKFUNC(vkDestroyShaderModule)(vk->context->device, shader_stages[0].module, NULL);
VKFUNC(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;
VKFUNC(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;
VKFUNC(vkAllocateCommandBuffers)(vk->context->device,
&info, &vk->swapchain[i].cmd);
}
}
static void vulkan_init_samplers(vk_t *vk)
{
VkSamplerCreateInfo info;
info.sType = 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;
VKFUNC(vkCreateSampler)(vk->context->device,
&info, NULL, &vk->samplers.nearest);
info.magFilter = VK_FILTER_LINEAR;
info.minFilter = VK_FILTER_LINEAR;
VKFUNC(vkCreateSampler)(vk->context->device,
&info, NULL, &vk->samplers.linear);
}
static void vulkan_deinit_samplers(vk_t *vk)
{
VKFUNC(vkDestroySampler)(vk->context->device, vk->samplers.nearest, NULL);
VKFUNC(vkDestroySampler)(vk->context->device, vk->samplers.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)
VKFUNC(vkFreeCommandBuffers)(vk->context->device,
vk->swapchain[i].cmd_pool, 1, &vk->swapchain[i].cmd);
VKFUNC(vkDestroyCommandPool)(vk->context->device,
vk->swapchain[i].cmd_pool, NULL);
}
}
static void vulkan_deinit_pipeline_layout(vk_t *vk)
{
VKFUNC(vkDestroyPipelineLayout)(vk->context->device,
vk->pipelines.layout, NULL);
VKFUNC(vkDestroyDescriptorSetLayout)(vk->context->device,
vk->pipelines.set_layout, NULL);
}
static void vulkan_deinit_pipelines(vk_t *vk)
{
unsigned i;
vulkan_deinit_pipeline_layout(vk);
VKFUNC(vkDestroyPipeline)(vk->context->device,
vk->pipelines.alpha_blend, NULL);
VKFUNC(vkDestroyPipeline)(vk->context->device,
vk->pipelines.font, NULL);
for (i = 0; i < 4; i++)
VKFUNC(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++)
{
VKFUNC(vkDestroyFramebuffer)(vk->context->device,
vk->swapchain[i].backbuffer.framebuffer, NULL);
VKFUNC(vkDestroyImageView)(vk->context->device,
vk->swapchain[i].backbuffer.view, NULL);
}
VKFUNC(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));
info.device = vk->context->device;
info.gpu = vk->context->gpu;
info.memory_properties = &vk->context->memory_properties;
info.pipeline_cache = vk->pipelines.cache;
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.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)
{
settings_t *settings = config_get_ptr();
const char *shader_path = (settings->video.shader_enable && *settings->video.shader_path) ?
settings->video.shader_path : NULL;
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)
{
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 };
VKFUNC(vkCreatePipelineCache)(vk->context->device,
&cache, NULL, &vk->pipelines.cache);
pool_info.queueFamilyIndex = vk->context->graphics_queue_index;
VKFUNC(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;
VKFUNC(vkDestroyPipelineCache)(vk->context->device,
vk->pipelines.cache, NULL);
vulkan_destroy_texture(
vk->context->device,
&vk->display.blank_texture);
VKFUNC(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)
{
VKFUNC(vkQueueWaitIdle)(vk->context->queue);
vulkan_deinit_resources(vk);
/* No need to init this since textures are create on-demand. */
vulkan_deinit_menu(vk);
font_driver_free(NULL);
vulkan_deinit_static_resources(vk);
vulkan_overlay_free(vk);
if (vk->filter_chain)
vulkan_filter_chain_free(vk->filter_chain);
gfx_ctx_ctl(GFX_CTL_FREE, NULL);
}
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)
{
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;
}
}
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;
slock_lock(vk->context->queue_lock);
}
static void vulkan_unlock_queue(void *handle)
{
vk_t *vk = (vk_t*)handle;
slock_unlock(vk->context->queue_lock);
}
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 = NULL;
video_driver_ctl(RARCH_DISPLAY_CTL_HW_CONTEXT_GET, &hwr);
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->get_device_proc_addr = VKFUNC(vkGetDeviceProcAddr);
iface->get_instance_proc_addr = VKFUNC(vkGetInstanceProcAddr);
}
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->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;
settings_t *settings = config_get_ptr();
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)
goto error;
gfx_ctx_ctl(GFX_CTL_SET, (void*)ctx_driver);
gfx_ctx_ctl(GFX_CTL_GET_VIDEO_SIZE, &mode);
full_x = mode.width;
full_y = mode.height;
mode.width = 0;
mode.height = 0;
RARCH_LOG("Detecting screen resolution %ux%u.\n", full_x, full_y);
interval = video->vsync ? settings->video.swap_interval : 0;
gfx_ctx_ctl(GFX_CTL_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 (!gfx_ctx_ctl(GFX_CTL_SET_VIDEO_MODE, &mode))
goto error;
gfx_ctx_ctl(GFX_CTL_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);
gfx_ctx_ctl(GFX_CTL_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))
goto error;
inp.input = input;
inp.input_data = input_data;
gfx_ctx_ctl(GFX_CTL_INPUT_DRIVER, &inp);
if (settings->video.font_enable)
{
if (!font_driver_init_first(NULL, NULL, vk, *settings->video.font_path
? settings->video.font_path : NULL, settings->video.font_size, false,
FONT_DRIVER_RENDER_VULKAN_API))
RARCH_ERR("[Vulkan]: Failed to initialize font renderer.\n");
}
vulkan_init_readback(vk);
return vk;
error:
vulkan_free(vk);
return NULL;
}
static void vulkan_update_filter_chain(vk_t *vk)
{
const struct vulkan_filter_chain_swapchain_info info = {
vk->vk_vp,
vk->context->swapchain_format,
vk->render_pass,
vk->context->num_swapchain_images,
};
if (!vulkan_filter_chain_update_swapchain_info(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)
{
VKFUNC(vkQueueWaitIdle)(vk->context->queue);
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->video.swap_interval;
gfx_ctx_ctl(GFX_CTL_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 (gfx_ctx_ctl(GFX_CTL_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_focus(void *data)
{
(void)data;
return gfx_ctx_ctl(GFX_CTL_FOCUS, NULL);
}
static bool vulkan_suppress_screensaver(void *data, bool enable)
{
(void)data;
bool enabled = enable;
return gfx_ctx_ctl(GFX_CTL_SUPPRESS_SCREENSAVER, &enabled);
}
static bool vulkan_has_windowed(void *data)
{
(void)data;
return gfx_ctx_ctl(GFX_CTL_HAS_WINDOWED, NULL);
}
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(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 gfx_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 gfx_ortho ortho = {0, 1, 0, 1, -1, 1};
if (!vk)
return;
vk->rotation = 90 * rotation;
vulkan_set_projection(vk, &ortho, true);
}
static void vulkan_set_video_mode(void *data,
unsigned width, unsigned height,
bool fullscreen)
{
(void)data;
gfx_ctx_mode_t mode;
mode.width = width;
mode.height = height;
mode.fullscreen = fullscreen;
gfx_ctx_ctl(GFX_CTL_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 gfx_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;
gfx_ctx_ctl(GFX_CTL_TRANSLATE_ASPECT, &aspect_data);
if (settings->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->video.aspect_ratio_idx == ASPECT_RATIO_CUSTOM)
{
const struct video_viewport *custom = video_viewport_get_custom();
/* Vukan 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);
VKFUNC(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_flush_caches(vk_t *vk)
{
VkMemoryBarrier barrier = { VK_STRUCTURE_TYPE_MEMORY_BARRIER };
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT;
VKFUNC(vkCmdPipelineBarrier)(vk->cmd,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
false,
1, &barrier,
0, NULL, 0, NULL);
}
static bool vulkan_frame(void *data, const void *frame,
unsigned frame_width, unsigned frame_height,
uint64_t frame_count,
unsigned pitch, const char *msg)
{
struct vk_per_frame *chain;
unsigned width, height;
VkClearValue clear_value;
vk_t *vk = (vk_t*)data;
static struct retro_perf_counter frame_run = {0};
static struct retro_perf_counter begin_cmd = {0};
static struct retro_perf_counter build_cmd = {0};
static struct retro_perf_counter end_cmd = {0};
static struct retro_perf_counter copy_frame = {0};
static struct retro_perf_counter swapbuffers = {0};
static struct retro_perf_counter queue_submit = {0};
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;
rarch_perf_init(&frame_run, "frame_run");
rarch_perf_init(&copy_frame, "copy_frame");
rarch_perf_init(&swapbuffers, "swapbuffers");
rarch_perf_init(&queue_submit, "queue_submit");
rarch_perf_init(&begin_cmd, "begin_command");
rarch_perf_init(&build_cmd, "build_command");
rarch_perf_init(&end_cmd, "end_command");
retro_perf_start(&frame_run);
video_driver_get_size(&width, &height);
/* 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);
retro_perf_start(&begin_cmd);
/* Start recording the command buffer. */
vk->cmd = chain->cmd;
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VKFUNC(vkResetCommandBuffer)(vk->cmd, 0);
VKFUNC(vkBeginCommandBuffer)(vk->cmd, &begin_info);
retro_perf_stop(&begin_cmd);
memset(&vk->tracker, 0, sizeof(vk->tracker));
vulkan_flush_caches(vk);
/* Upload texture */
retro_perf_start(&copy_frame);
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);
}
vk->last_valid_index = frame_index;
}
retro_perf_stop(&copy_frame);
/* Notify filter chain about the new sync index. */
vulkan_filter_chain_notify_sync_index(vk->filter_chain, frame_index);
vulkan_filter_chain_set_frame_count(vk->filter_chain, frame_count);
retro_perf_start(&build_cmd);
/* 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, 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(vk->filter_chain, &input);
}
vulkan_set_viewport(vk, width, height, false, true);
vulkan_filter_chain_build_offscreen_passes(
vk->filter_chain, vk->cmd, &vk->vk_vp);
/* Render to backbuffer. */
clear_value.color.float32[0] = 0.0f;
clear_value.color.float32[1] = 0.0f;
clear_value.color.float32[2] = 0.0f;
clear_value.color.float32[3] = 1.0f;
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_value;
/* 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 */
VKFUNC(vkCmdBeginRenderPass)(vk->cmd, &rp_info, VK_SUBPASS_CONTENTS_INLINE);
vulkan_filter_chain_build_viewport_pass(vk->filter_chain, vk->cmd,
&vk->vk_vp, vk->mvp.data);
#if defined(HAVE_MENU)
if (vk->menu.enable)
{
menu_driver_ctl(RARCH_MENU_CTL_FRAME, NULL);
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 = 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);
}
}
#endif
if (msg)
font_driver_render_msg(NULL, msg, NULL);
#ifdef HAVE_OVERLAY
if (vk->overlay.enable)
vulkan_render_overlay(vk);
#endif
retro_perf_stop(&build_cmd);
/* End the render pass. We're done rendering to backbuffer now. */
VKFUNC(vkCmdEndRenderPass)(vk->cmd);
if (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,
0,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
vk->readback.pending = false;
}
else
{
/* 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,
0,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
retro_perf_start(&end_cmd);
VKFUNC(vkEndCommandBuffer)(vk->cmd);
retro_perf_stop(&end_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 (vk->hw.enable && frame && !vk->hw.num_cmd)
{
submit_info.waitSemaphoreCount = vk->hw.num_semaphores;
submit_info.pWaitSemaphores = vk->hw.semaphores;
submit_info.pWaitDstStageMask = vk->hw.wait_dst_stages;
}
submit_info.signalSemaphoreCount =
vk->context->swapchain_semaphores[frame_index] != VK_NULL_HANDLE ? 1 : 0;
submit_info.pSignalSemaphores =
&vk->context->swapchain_semaphores[frame_index];
retro_perf_stop(&frame_run);
retro_perf_start(&queue_submit);
slock_lock(vk->context->queue_lock);
VKFUNC(vkQueueSubmit)(vk->context->queue, 1,
&submit_info, vk->context->swapchain_fences[frame_index]);
slock_unlock(vk->context->queue_lock);
retro_perf_stop(&queue_submit);
retro_perf_start(&swapbuffers);
gfx_ctx_ctl(GFX_CTL_SWAP_BUFFERS, NULL);
retro_perf_stop(&swapbuffers);
gfx_ctx_ctl(GFX_CTL_UPDATE_WINDOW_TITLE, NULL);
/* 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;
gfx_ctx_ctl(GFX_CTL_SET_RESIZE, &mode);
vk->should_resize = false;
}
vulkan_check_swapchain(vk);
return true;
}
static void vulkan_set_aspect_ratio(void *data, unsigned aspect_ratio_idx)
{
vk_t *vk = (vk_t*)data;
enum rarch_display_ctl_state cmd = RARCH_DISPLAY_CTL_NONE;
switch (aspect_ratio_idx)
{
case ASPECT_RATIO_SQUARE:
cmd = RARCH_DISPLAY_CTL_SET_VIEWPORT_SQUARE_PIXEL;
break;
case ASPECT_RATIO_CORE:
cmd = RARCH_DISPLAY_CTL_SET_VIEWPORT_CORE;
break;
case ASPECT_RATIO_CONFIG:
cmd = RARCH_DISPLAY_CTL_SET_VIEWPORT_CONFIG;
break;
default:
break;
}
if (cmd != RARCH_DISPLAY_CTL_NONE)
video_driver_ctl(cmd, NULL);
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;
gfx_ctx_ctl(GFX_CTL_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(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;
}
#if defined(HAVE_MENU)
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 = vk->context->current_swapchain_index;
struct vk_texture *texture = &vk->menu.textures[index];
struct vk_texture *texture_optimal = &vk->menu.textures_optimal[index];
const VkComponentMapping br_swizzle = {
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_A,
};
if (!vk)
return;
/* 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);
VKFUNC(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);
VKFUNC(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, const char *msg,
const struct font_params *params, void *font)
{
(void)data;
font_driver_render_msg(font, msg, params);
}
#endif
static uintptr_t vulkan_load_texture(void *video_data, void *data,
bool threaded, enum texture_filter_type filter_type)
{
vk_t *vk = (vk_t*)video_data;
struct texture_image *image = (struct texture_image*)data;
struct vk_texture *texture = (struct vk_texture*)calloc(1, sizeof(*texture));
if (!texture)
return 0;
*texture = vulkan_create_texture(vk, NULL,
image->width, image->height, VK_FORMAT_B8G8R8A8_UNORM,
image->pixels, NULL, VULKAN_TEXTURE_STATIC);
/* TODO: Actually add mipmapping support.
* Optimal tiling would make sense here as well ... */
texture->default_smooth =
filter_type == TEXTURE_FILTER_MIPMAP_LINEAR || filter_type == TEXTURE_FILTER_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. */
VKFUNC(vkQueueWaitIdle)(vk->context->queue);
vulkan_destroy_texture(
vk->context->device, texture);
free(texture);
}
static const video_poke_interface_t vulkan_poke_interface = {
vulkan_load_texture,
vulkan_unload_texture,
vulkan_set_video_mode,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
vulkan_set_aspect_ratio,
vulkan_apply_state_changes,
#if defined(HAVE_MENU)
vulkan_set_texture_frame,
vulkan_set_texture_enable,
#else
NULL,
NULL,
#endif
vulkan_set_osd_msg,
vulkan_show_mouse,
NULL,
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);
/* 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)
{
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;
static struct retro_perf_counter stream_readback = {0};
if (staging->memory == VK_NULL_HANDLE)
return false;
rarch_perf_init(&stream_readback, "stream_readback");
retro_perf_start(&stream_readback);
buffer += 3 * (vk->vp.height - 1) * vk->vp.width;
VKFUNC(vkMapMemory)(vk->context->device, staging->memory,
staging->offset, staging->size, 0, (void**)&src);
vk->readback.scaler.in_stride = staging->stride;
vk->readback.scaler.out_stride = -(int)vk->vp.width * 3;
scaler_ctx_scale(&vk->readback.scaler, buffer, src);
VKFUNC(vkUnmapMemory)(vk->context->device, staging->memory);
retro_perf_stop(&stream_readback);
}
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;
video_driver_ctl(RARCH_DISPLAY_CTL_CACHED_FRAME_RENDER, NULL);
VKFUNC(vkQueueWaitIdle)(vk->context->queue);
if (!staging->mapped)
vulkan_map_persistent_texture(
vk->context->device, 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)
gfx_ctx_ctl(GFX_CTL_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)
{
unsigned width, height;
unsigned i;
struct video_viewport vp;
if (!vk)
return;
video_driver_get_size(&width, &height);
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 = vk->samplers.linear;
call.mvp = &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;
slock_lock(vk->context->queue_lock);
VKFUNC(vkQueueWaitIdle)(vk->context->queue);
slock_unlock(vk->context->queue_lock);
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,
vulkan_focus,
vulkan_suppress_screensaver,
vulkan_has_windowed,
vulkan_set_shader,
vulkan_free,
"vulkan",
vulkan_set_viewport,
vulkan_set_rotation,
vulkan_viewport_info,
vulkan_read_viewport,
NULL, /* vulkan_read_frame_raw */
#ifdef HAVE_OVERLAY
vulkan_get_overlay_interface,
#else
NULL,
#endif
vulkan_get_poke_interface,
NULL, /* vulkan_wrap_type_to_enum */
};