mirror of
https://github.com/libretro/libretro-3dengine.git
synced 2026-11-07 11:06:04 -10:00
1490 lines
55 KiB
C++
1490 lines
55 KiB
C++
/*
|
|
* Libretro 3DEngine
|
|
* Copyright (C) 2013-2014 - Hans-Kristian Arntzen
|
|
* Copyright (C) 2013-2014 - Daniel De Matteis
|
|
*
|
|
* Libretro 3DEngine 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.
|
|
*
|
|
* Libretro 3DEngine 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 Libretro 3DEngine.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifdef HAVE_VULKAN
|
|
|
|
#include <vulkan/vulkan_symbol_wrapper.h>
|
|
#include <libretro_vulkan.h>
|
|
|
|
#include "vk_renderer.hpp"
|
|
#include "vk_shaders.inc"
|
|
#include "rpng.h"
|
|
#include "rtga.h"
|
|
#include "util.hpp"
|
|
#include "shared.hpp"
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <map>
|
|
#include <string>
|
|
#include <algorithm>
|
|
|
|
#define VKR_LOG(level, ...) do { if (log_cb) log_cb(level, __VA_ARGS__); } while (0)
|
|
#define VKR_MAX_SLOTS 16u
|
|
#define VKR_UNDEF32 ((uint32_t)-1)
|
|
|
|
namespace VKR
|
|
{
|
|
namespace
|
|
{
|
|
struct Buffer
|
|
{
|
|
Buffer() : buf(VK_NULL_HANDLE), mem(VK_NULL_HANDLE), map(NULL), size(0) {}
|
|
VkBuffer buf;
|
|
VkDeviceMemory mem;
|
|
uint8_t *map;
|
|
VkDeviceSize size;
|
|
};
|
|
|
|
struct Tex
|
|
{
|
|
Tex() : image(VK_NULL_HANDLE), mem(VK_NULL_HANDLE), view(VK_NULL_HANDLE) {}
|
|
VkImage image;
|
|
VkDeviceMemory mem;
|
|
VkImageView view;
|
|
};
|
|
|
|
struct MeshGPU
|
|
{
|
|
MeshGPU() : count(0), tex_set(VK_NULL_HANDLE), spec_power(60.0f), alpha(1.0f) {}
|
|
Buffer vbo;
|
|
uint32_t count;
|
|
VkDescriptorSet tex_set;
|
|
glm::vec3 ambient, diffuse, specular;
|
|
float spec_power;
|
|
float alpha;
|
|
};
|
|
|
|
/* std140: all vec4 so no padding surprises. Matches mesh.frag. */
|
|
struct DrawUBO
|
|
{
|
|
float light[4];
|
|
float light_ambient[4];
|
|
float eye[4];
|
|
float mtl_ambient[4];
|
|
float mtl_diffuse[4];
|
|
float mtl_specular[4];
|
|
float mtl_params[4]; /* x = specular power, y = alpha mod */
|
|
};
|
|
|
|
struct PushConstants
|
|
{
|
|
float model[16];
|
|
float mvp[16];
|
|
};
|
|
|
|
/* Color image that is handed to the frontend + its depth buffer. */
|
|
struct Target
|
|
{
|
|
Target() : color(VK_NULL_HANDLE), depth(VK_NULL_HANDLE),
|
|
color_mem(VK_NULL_HANDLE), depth_mem(VK_NULL_HANDLE),
|
|
color_view(VK_NULL_HANDLE), depth_view(VK_NULL_HANDLE),
|
|
fb(VK_NULL_HANDLE), w(0), h(0)
|
|
{ memset(&desc, 0, sizeof(desc)); }
|
|
VkImage color, depth;
|
|
VkDeviceMemory color_mem, depth_mem;
|
|
VkImageView color_view, depth_view;
|
|
VkFramebuffer fb;
|
|
unsigned w, h;
|
|
struct retro_vulkan_image desc; /* must outlive video_cb() */
|
|
};
|
|
|
|
/* One per frontend sync index. Everything the frontend may still be
|
|
* reading (the Target) is only touched again after wait_sync_index()
|
|
* for this index has returned. */
|
|
struct Slot
|
|
{
|
|
Slot() : pool(VK_NULL_HANDLE), cmd(VK_NULL_HANDLE), fence(VK_NULL_HANDLE),
|
|
ubo_set(VK_NULL_HANDLE) {}
|
|
Target target;
|
|
VkCommandPool pool;
|
|
VkCommandBuffer cmd;
|
|
VkFence fence;
|
|
Buffer ubo;
|
|
VkDescriptorSet ubo_set;
|
|
};
|
|
|
|
struct Globals
|
|
{
|
|
Globals() : iface(NULL), dev(VK_NULL_HANDLE), gpu(VK_NULL_HANDLE),
|
|
queue(VK_NULL_HANDLE), qfamily(0), inited(false),
|
|
depth_format(VK_FORMAT_UNDEFINED), render_pass(VK_NULL_HANDLE),
|
|
ubo_layout(VK_NULL_HANDLE), tex_layout(VK_NULL_HANDLE),
|
|
pipe_layout(VK_NULL_HANDLE), pipeline(VK_NULL_HANDLE),
|
|
sampler(VK_NULL_HANDLE), ubo_pool(VK_NULL_HANDLE),
|
|
tex_pool(VK_NULL_HANDLE), upload_pool(VK_NULL_HANDLE),
|
|
ubo_stride(0), last_mask(0), scene_ready(false) {}
|
|
|
|
const struct retro_hw_render_interface_vulkan *iface;
|
|
VkDevice dev;
|
|
VkPhysicalDevice gpu;
|
|
VkQueue queue;
|
|
uint32_t qfamily;
|
|
bool inited;
|
|
|
|
VkFormat depth_format;
|
|
VkRenderPass render_pass;
|
|
VkDescriptorSetLayout ubo_layout;
|
|
VkDescriptorSetLayout tex_layout;
|
|
VkPipelineLayout pipe_layout;
|
|
VkPipeline pipeline;
|
|
VkSampler sampler;
|
|
VkDescriptorPool ubo_pool;
|
|
VkDescriptorPool tex_pool;
|
|
VkCommandPool upload_pool;
|
|
VkDeviceSize ubo_stride;
|
|
|
|
uint32_t last_mask;
|
|
std::vector<Slot> slots;
|
|
|
|
Tex blank;
|
|
std::map<std::string, Tex> textures;
|
|
std::vector<MeshGPU> meshes;
|
|
bool scene_ready;
|
|
};
|
|
|
|
Globals g;
|
|
|
|
const VkFormat COLOR_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
/* ---- negotiation ---------------------------------------------- */
|
|
|
|
const VkApplicationInfo *get_application_info(void)
|
|
{
|
|
static VkApplicationInfo info;
|
|
memset(&info, 0, sizeof(info));
|
|
info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
info.pApplicationName = "Libretro 3DEngine";
|
|
info.applicationVersion = 1;
|
|
info.pEngineName = "Libretro 3DEngine";
|
|
info.engineVersion = 1;
|
|
/* 1.1 as recommended in libretro_vulkan.h (old Android loaders). We
|
|
* only use 1.0 functionality. */
|
|
info.apiVersion = VK_API_VERSION_1_1;
|
|
return &info;
|
|
}
|
|
|
|
/* ---- small helpers -------------------------------------------- */
|
|
|
|
uint32_t find_memory_type(uint32_t type_bits, VkMemoryPropertyFlags props)
|
|
{
|
|
uint32_t i;
|
|
VkPhysicalDeviceMemoryProperties mp;
|
|
vkGetPhysicalDeviceMemoryProperties(g.gpu, &mp);
|
|
for (i = 0; i < mp.memoryTypeCount; i++)
|
|
if ((type_bits & (1u << i)) && (mp.memoryTypes[i].propertyFlags & props) == props)
|
|
return i;
|
|
return VKR_UNDEF32;
|
|
}
|
|
|
|
bool alloc_memory(const VkMemoryRequirements& req, VkMemoryPropertyFlags props,
|
|
VkDeviceMemory *mem)
|
|
{
|
|
VkMemoryAllocateInfo ai;
|
|
memset(&ai, 0, sizeof(ai));
|
|
ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
ai.allocationSize = req.size;
|
|
ai.memoryTypeIndex = find_memory_type(req.memoryTypeBits, props);
|
|
if (ai.memoryTypeIndex == VKR_UNDEF32)
|
|
return false;
|
|
return vkAllocateMemory(g.dev, &ai, NULL, mem) == VK_SUCCESS;
|
|
}
|
|
|
|
void destroy_buffer(Buffer& b)
|
|
{
|
|
if (b.map)
|
|
vkUnmapMemory(g.dev, b.mem);
|
|
if (b.buf)
|
|
vkDestroyBuffer(g.dev, b.buf, NULL);
|
|
if (b.mem)
|
|
vkFreeMemory(g.dev, b.mem, NULL);
|
|
b = Buffer();
|
|
}
|
|
|
|
/* Host visible + coherent. */
|
|
bool create_buffer(VkDeviceSize size, VkBufferUsageFlags usage, Buffer& out)
|
|
{
|
|
VkMemoryRequirements req;
|
|
VkBufferCreateInfo bi;
|
|
memset(&bi, 0, sizeof(bi));
|
|
bi.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
bi.size = size;
|
|
bi.usage = usage;
|
|
bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
|
|
out = Buffer();
|
|
out.size = size;
|
|
if (vkCreateBuffer(g.dev, &bi, NULL, &out.buf) != VK_SUCCESS)
|
|
return false;
|
|
|
|
vkGetBufferMemoryRequirements(g.dev, out.buf, &req);
|
|
if (!alloc_memory(req, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &out.mem))
|
|
goto fail;
|
|
if (vkBindBufferMemory(g.dev, out.buf, out.mem, 0) != VK_SUCCESS)
|
|
goto fail;
|
|
|
|
{
|
|
void *p = NULL;
|
|
if (vkMapMemory(g.dev, out.mem, 0, VK_WHOLE_SIZE, 0, &p) != VK_SUCCESS)
|
|
goto fail;
|
|
out.map = (uint8_t*)p;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
destroy_buffer(out);
|
|
return false;
|
|
}
|
|
|
|
bool create_image(unsigned w, unsigned h, unsigned mips, VkFormat format,
|
|
VkImageUsageFlags usage, VkImageCreateFlags flags,
|
|
VkImage *image, VkDeviceMemory *mem)
|
|
{
|
|
VkMemoryRequirements req;
|
|
VkImageCreateInfo ii;
|
|
memset(&ii, 0, sizeof(ii));
|
|
ii.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
ii.flags = flags;
|
|
ii.imageType = VK_IMAGE_TYPE_2D;
|
|
ii.format = format;
|
|
ii.extent.width = w;
|
|
ii.extent.height = h;
|
|
ii.extent.depth = 1;
|
|
ii.mipLevels = mips;
|
|
ii.arrayLayers = 1;
|
|
ii.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
ii.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
ii.usage = usage;
|
|
ii.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
ii.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
*image = VK_NULL_HANDLE;
|
|
*mem = VK_NULL_HANDLE;
|
|
if (vkCreateImage(g.dev, &ii, NULL, image) != VK_SUCCESS)
|
|
return false;
|
|
vkGetImageMemoryRequirements(g.dev, *image, &req);
|
|
if (!alloc_memory(req, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem) ||
|
|
vkBindImageMemory(g.dev, *image, *mem, 0) != VK_SUCCESS)
|
|
{
|
|
if (*mem)
|
|
vkFreeMemory(g.dev, *mem, NULL);
|
|
vkDestroyImage(g.dev, *image, NULL);
|
|
*image = VK_NULL_HANDLE;
|
|
*mem = VK_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void fill_view_info(VkImageViewCreateInfo& vi, VkImage image, VkFormat format,
|
|
VkImageAspectFlags aspect, unsigned mips)
|
|
{
|
|
memset(&vi, 0, sizeof(vi));
|
|
vi.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
vi.image = image;
|
|
vi.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
vi.format = format;
|
|
vi.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
vi.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
vi.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
vi.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
vi.subresourceRange.aspectMask = aspect;
|
|
vi.subresourceRange.baseMipLevel = 0;
|
|
vi.subresourceRange.levelCount = mips;
|
|
vi.subresourceRange.baseArrayLayer = 0;
|
|
vi.subresourceRange.layerCount = 1;
|
|
}
|
|
|
|
void image_barrier(VkCommandBuffer cmd, VkImage image, unsigned base_mip,
|
|
unsigned levels, VkImageLayout from, VkImageLayout to,
|
|
VkAccessFlags src_access, VkAccessFlags dst_access,
|
|
VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage)
|
|
{
|
|
VkImageMemoryBarrier b;
|
|
memset(&b, 0, sizeof(b));
|
|
b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
b.srcAccessMask = src_access;
|
|
b.dstAccessMask = dst_access;
|
|
b.oldLayout = from;
|
|
b.newLayout = to;
|
|
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
b.image = image;
|
|
b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
b.subresourceRange.baseMipLevel = base_mip;
|
|
b.subresourceRange.levelCount = levels;
|
|
b.subresourceRange.baseArrayLayer = 0;
|
|
b.subresourceRange.layerCount = 1;
|
|
vkCmdPipelineBarrier(cmd, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &b);
|
|
}
|
|
|
|
/* ---- one-shot uploads (init / scene load only) ---------------- */
|
|
|
|
VkCommandBuffer begin_oneshot(void)
|
|
{
|
|
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
|
VkCommandBufferAllocateInfo ai;
|
|
VkCommandBufferBeginInfo bi;
|
|
memset(&ai, 0, sizeof(ai));
|
|
ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
ai.commandPool = g.upload_pool;
|
|
ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
ai.commandBufferCount = 1;
|
|
if (vkAllocateCommandBuffers(g.dev, &ai, &cmd) != VK_SUCCESS)
|
|
return VK_NULL_HANDLE;
|
|
|
|
memset(&bi, 0, sizeof(bi));
|
|
bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(cmd, &bi);
|
|
return cmd;
|
|
}
|
|
|
|
bool end_oneshot(VkCommandBuffer cmd)
|
|
{
|
|
VkSubmitInfo si;
|
|
VkResult res;
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
memset(&si, 0, sizeof(si));
|
|
si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
si.commandBufferCount = 1;
|
|
si.pCommandBuffers = &cmd;
|
|
|
|
/* The frontend shares this queue; every submit must be locked. */
|
|
g.iface->lock_queue(g.iface->handle);
|
|
res = vkQueueSubmit(g.queue, 1, &si, VK_NULL_HANDLE);
|
|
if (res == VK_SUCCESS)
|
|
res = vkQueueWaitIdle(g.queue);
|
|
g.iface->unlock_queue(g.iface->handle);
|
|
|
|
vkFreeCommandBuffers(g.dev, g.upload_pool, 1, &cmd);
|
|
return res == VK_SUCCESS;
|
|
}
|
|
|
|
void destroy_texture(Tex& t)
|
|
{
|
|
if (t.view)
|
|
vkDestroyImageView(g.dev, t.view, NULL);
|
|
if (t.image)
|
|
vkDestroyImage(g.dev, t.image, NULL);
|
|
if (t.mem)
|
|
vkFreeMemory(g.dev, t.mem, NULL);
|
|
t = Tex();
|
|
}
|
|
|
|
/* RGBA8 -> sampled, mip-mapped texture. Mirrors GL::Texture::upload_data
|
|
* (linear + mipmaps, repeat). The loaders hand us bottom-left-origin
|
|
* rows. */
|
|
bool create_texture(const uint8_t *rgba, unsigned w, unsigned h, Tex& t)
|
|
{
|
|
unsigned i, mips = 1;
|
|
Buffer staging;
|
|
VkCommandBuffer cmd;
|
|
VkBufferImageCopy copy;
|
|
VkFormatProperties fp;
|
|
VkImageViewCreateInfo vi;
|
|
int32_t mw = (int32_t)w, mh = (int32_t)h;
|
|
const VkDeviceSize size = (VkDeviceSize)w * h * 4;
|
|
|
|
vkGetPhysicalDeviceFormatProperties(g.gpu, VK_FORMAT_R8G8B8A8_UNORM, &fp);
|
|
if (fp.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT &&
|
|
fp.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT &&
|
|
fp.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)
|
|
{
|
|
unsigned m = std::max(w, h);
|
|
mips = 1;
|
|
while (m > 1) { m >>= 1; mips++; }
|
|
}
|
|
|
|
t = Tex();
|
|
if (!create_buffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, staging))
|
|
return false;
|
|
memcpy(staging.map, rgba, (size_t)size);
|
|
|
|
if (!create_image(w, h, mips, VK_FORMAT_R8G8B8A8_UNORM,
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
|
VK_IMAGE_USAGE_SAMPLED_BIT, 0, &t.image, &t.mem))
|
|
{
|
|
destroy_buffer(staging);
|
|
return false;
|
|
}
|
|
|
|
cmd = begin_oneshot();
|
|
if (!cmd)
|
|
{
|
|
destroy_buffer(staging);
|
|
destroy_texture(t);
|
|
return false;
|
|
}
|
|
|
|
image_barrier(cmd, t.image, 0, mips, VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
|
|
|
memset(©, 0, sizeof(copy));
|
|
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
copy.imageSubresource.layerCount = 1;
|
|
copy.imageExtent.width = w;
|
|
copy.imageExtent.height = h;
|
|
copy.imageExtent.depth = 1;
|
|
vkCmdCopyBufferToImage(cmd, staging.buf, t.image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
|
|
|
|
for (i = 1; i < mips; i++)
|
|
{
|
|
VkImageBlit blit;
|
|
int32_t nw = mw > 1 ? mw / 2 : 1;
|
|
int32_t nh = mh > 1 ? mh / 2 : 1;
|
|
|
|
image_barrier(cmd, t.image, i - 1, 1,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
|
|
|
memset(&blit, 0, sizeof(blit));
|
|
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
blit.srcSubresource.mipLevel = i - 1;
|
|
blit.srcSubresource.layerCount = 1;
|
|
blit.srcOffsets[1].x = mw;
|
|
blit.srcOffsets[1].y = mh;
|
|
blit.srcOffsets[1].z = 1;
|
|
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
blit.dstSubresource.mipLevel = i;
|
|
blit.dstSubresource.layerCount = 1;
|
|
blit.dstOffsets[1].x = nw;
|
|
blit.dstOffsets[1].y = nh;
|
|
blit.dstOffsets[1].z = 1;
|
|
vkCmdBlitImage(cmd, t.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
t.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR);
|
|
|
|
image_barrier(cmd, t.image, i - 1, 1,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
|
|
|
mw = nw;
|
|
mh = nh;
|
|
}
|
|
|
|
image_barrier(cmd, t.image, mips - 1, 1,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
|
|
|
if (!end_oneshot(cmd))
|
|
{
|
|
destroy_buffer(staging);
|
|
destroy_texture(t);
|
|
return false;
|
|
}
|
|
destroy_buffer(staging);
|
|
|
|
fill_view_info(vi, t.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, mips);
|
|
if (vkCreateImageView(g.dev, &vi, NULL, &t.view) != VK_SUCCESS)
|
|
{
|
|
destroy_texture(t);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Returns NULL on failure; the caller then falls back to the blank texture. */
|
|
const Tex *get_texture(const std::string& path)
|
|
{
|
|
unsigned w = 0, h = 0;
|
|
uint8_t *data = NULL;
|
|
bool ok = false;
|
|
Tex t;
|
|
std::string ext;
|
|
std::map<std::string, Tex>::iterator it;
|
|
|
|
if (!path.size())
|
|
return NULL;
|
|
|
|
it = g.textures.find(path);
|
|
if (it != g.textures.end())
|
|
return it->second.view ? &it->second : NULL;
|
|
|
|
ext = Path::ext(path);
|
|
if (ext == "png")
|
|
ok = rpng_load_image_rgba(path.c_str(), &data, &w, &h);
|
|
else if (ext == "tga")
|
|
ok = texture_image_load_tga(path.c_str(), data, w, h);
|
|
else
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Unsupported texture extension: \"%s\"\n", ext.c_str());
|
|
|
|
if (ok && data && create_texture(data, w, h, t))
|
|
{
|
|
free(data);
|
|
return &(g.textures[path] = t);
|
|
}
|
|
|
|
if (ok)
|
|
free(data);
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Failed to load texture: %s\n", path.c_str());
|
|
g.textures[path] = Tex();
|
|
return NULL;
|
|
}
|
|
|
|
/* ---- render targets / per-sync-index slots -------------------- */
|
|
|
|
void destroy_target(Target& t)
|
|
{
|
|
if (t.fb)
|
|
vkDestroyFramebuffer(g.dev, t.fb, NULL);
|
|
if (t.color_view)
|
|
vkDestroyImageView(g.dev, t.color_view, NULL);
|
|
if (t.depth_view)
|
|
vkDestroyImageView(g.dev, t.depth_view, NULL);
|
|
if (t.color)
|
|
vkDestroyImage(g.dev, t.color, NULL);
|
|
if (t.depth)
|
|
vkDestroyImage(g.dev, t.depth, NULL);
|
|
if (t.color_mem)
|
|
vkFreeMemory(g.dev, t.color_mem, NULL);
|
|
if (t.depth_mem)
|
|
vkFreeMemory(g.dev, t.depth_mem, NULL);
|
|
t = Target();
|
|
}
|
|
|
|
bool create_target(Target& t, unsigned w, unsigned h)
|
|
{
|
|
VkImageViewCreateInfo vi;
|
|
VkFramebufferCreateInfo fi;
|
|
VkImageView atts[2];
|
|
|
|
t = Target();
|
|
|
|
/* MUTABLE_FORMAT: the frontend may reinterpret the 8-bit image as
|
|
* sRGB. */
|
|
if (!create_image(w, h, 1, COLOR_FORMAT,
|
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
|
|
VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, &t.color, &t.color_mem))
|
|
goto fail;
|
|
if (!create_image(w, h, 1, g.depth_format,
|
|
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 0, &t.depth, &t.depth_mem))
|
|
goto fail;
|
|
|
|
fill_view_info(vi, t.color, COLOR_FORMAT, VK_IMAGE_ASPECT_COLOR_BIT, 1);
|
|
if (vkCreateImageView(g.dev, &vi, NULL, &t.color_view) != VK_SUCCESS)
|
|
goto fail;
|
|
|
|
{
|
|
VkImageAspectFlags da = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
if (g.depth_format == VK_FORMAT_D24_UNORM_S8_UINT)
|
|
da |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
fill_view_info(vi, t.depth, g.depth_format, da, 1);
|
|
}
|
|
if (vkCreateImageView(g.dev, &vi, NULL, &t.depth_view) != VK_SUCCESS)
|
|
goto fail;
|
|
|
|
atts[0] = t.color_view;
|
|
atts[1] = t.depth_view;
|
|
memset(&fi, 0, sizeof(fi));
|
|
fi.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
fi.renderPass = g.render_pass;
|
|
fi.attachmentCount = 2;
|
|
fi.pAttachments = atts;
|
|
fi.width = w;
|
|
fi.height = h;
|
|
fi.layers = 1;
|
|
if (vkCreateFramebuffer(g.dev, &fi, NULL, &t.fb) != VK_SUCCESS)
|
|
goto fail;
|
|
|
|
t.w = w;
|
|
t.h = h;
|
|
|
|
/* What we give the frontend. The render pass leaves the image in
|
|
* SHADER_READ_ONLY_OPTIMAL, and the view carries the create info so
|
|
* the frontend can re-view it (e.g. as sRGB). */
|
|
t.desc.image_view = t.color_view;
|
|
t.desc.image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
t.desc.create_info = vi;
|
|
fill_view_info(t.desc.create_info, t.color, COLOR_FORMAT, VK_IMAGE_ASPECT_COLOR_BIT, 1);
|
|
return true;
|
|
|
|
fail:
|
|
destroy_target(t);
|
|
return false;
|
|
}
|
|
|
|
void destroy_slot(Slot& s)
|
|
{
|
|
destroy_target(s.target);
|
|
destroy_buffer(s.ubo);
|
|
if (s.fence)
|
|
vkDestroyFence(g.dev, s.fence, NULL);
|
|
if (s.pool)
|
|
vkDestroyCommandPool(g.dev, s.pool, NULL); /* frees s.cmd */
|
|
s = Slot();
|
|
}
|
|
|
|
/* Waits only for *our* submissions. */
|
|
void wait_own_work(void)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < g.slots.size(); i++)
|
|
if (g.slots[i].fence)
|
|
vkWaitForFences(g.dev, 1, &g.slots[i].fence, VK_TRUE, (uint64_t)-1);
|
|
}
|
|
|
|
void destroy_slots(void)
|
|
{
|
|
size_t i;
|
|
wait_own_work();
|
|
for (i = 0; i < g.slots.size(); i++)
|
|
destroy_slot(g.slots[i]);
|
|
g.slots.clear();
|
|
if (g.ubo_pool)
|
|
vkResetDescriptorPool(g.dev, g.ubo_pool, 0);
|
|
}
|
|
|
|
bool create_slots(uint32_t mask)
|
|
{
|
|
unsigned i, n = 0;
|
|
while ((mask >> n) != 0 && n < 32)
|
|
n++;
|
|
if (!n || n > VKR_MAX_SLOTS)
|
|
{
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Unsupported sync index mask 0x%x\n", mask);
|
|
return false;
|
|
}
|
|
|
|
g.slots.resize(n);
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
Slot& s = g.slots[i];
|
|
VkCommandPoolCreateInfo pi;
|
|
VkCommandBufferAllocateInfo ai;
|
|
VkFenceCreateInfo fi;
|
|
VkDescriptorSetAllocateInfo di;
|
|
|
|
memset(&pi, 0, sizeof(pi));
|
|
pi.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
pi.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
|
|
pi.queueFamilyIndex = g.qfamily;
|
|
if (vkCreateCommandPool(g.dev, &pi, NULL, &s.pool) != VK_SUCCESS)
|
|
return false;
|
|
|
|
memset(&ai, 0, sizeof(ai));
|
|
ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
ai.commandPool = s.pool;
|
|
ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
ai.commandBufferCount = 1;
|
|
if (vkAllocateCommandBuffers(g.dev, &ai, &s.cmd) != VK_SUCCESS)
|
|
return false;
|
|
|
|
memset(&fi, 0, sizeof(fi));
|
|
fi.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
|
fi.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
|
if (vkCreateFence(g.dev, &fi, NULL, &s.fence) != VK_SUCCESS)
|
|
return false;
|
|
|
|
memset(&di, 0, sizeof(di));
|
|
di.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
di.descriptorPool = g.ubo_pool;
|
|
di.descriptorSetCount = 1;
|
|
di.pSetLayouts = &g.ubo_layout;
|
|
if (vkAllocateDescriptorSets(g.dev, &di, &s.ubo_set) != VK_SUCCESS)
|
|
return false;
|
|
}
|
|
g.last_mask = mask;
|
|
return true;
|
|
}
|
|
|
|
/* Needs: GPU done with s.ubo (we waited s.fence). */
|
|
bool ensure_ubo(Slot& s, VkDeviceSize needed)
|
|
{
|
|
VkDescriptorBufferInfo bi;
|
|
VkWriteDescriptorSet w;
|
|
|
|
if (s.ubo.buf && s.ubo.size >= needed)
|
|
return true;
|
|
|
|
destroy_buffer(s.ubo);
|
|
if (!create_buffer(needed, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, s.ubo))
|
|
return false;
|
|
|
|
memset(&bi, 0, sizeof(bi));
|
|
bi.buffer = s.ubo.buf;
|
|
bi.offset = 0;
|
|
bi.range = sizeof(DrawUBO);
|
|
|
|
memset(&w, 0, sizeof(w));
|
|
w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
w.dstSet = s.ubo_set;
|
|
w.dstBinding = 0;
|
|
w.descriptorCount = 1;
|
|
w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
|
w.pBufferInfo = &bi;
|
|
vkUpdateDescriptorSets(g.dev, 1, &w, 0, NULL);
|
|
return true;
|
|
}
|
|
|
|
/* ---- pipeline ------------------------------------------------- */
|
|
|
|
VkShaderModule make_module(const uint32_t *code, size_t bytes)
|
|
{
|
|
VkShaderModule m = VK_NULL_HANDLE;
|
|
VkShaderModuleCreateInfo mi;
|
|
memset(&mi, 0, sizeof(mi));
|
|
mi.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
mi.codeSize = bytes;
|
|
mi.pCode = code;
|
|
if (vkCreateShaderModule(g.dev, &mi, NULL, &m) != VK_SUCCESS)
|
|
return VK_NULL_HANDLE;
|
|
return m;
|
|
}
|
|
|
|
bool create_pipeline(ShaderMode mode)
|
|
{
|
|
VkShaderModule vs, fs;
|
|
VkPipelineShaderStageCreateInfo stages[2];
|
|
VkSpecializationMapEntry spec_entry;
|
|
VkSpecializationInfo spec;
|
|
int32_t spec_value = (int32_t)mode;
|
|
VkVertexInputBindingDescription bind;
|
|
VkVertexInputAttributeDescription attrs[3];
|
|
VkPipelineVertexInputStateCreateInfo vin;
|
|
VkPipelineInputAssemblyStateCreateInfo ia;
|
|
VkPipelineViewportStateCreateInfo vp;
|
|
VkPipelineRasterizationStateCreateInfo rs;
|
|
VkPipelineMultisampleStateCreateInfo ms;
|
|
VkPipelineDepthStencilStateCreateInfo ds;
|
|
VkPipelineColorBlendAttachmentState cba;
|
|
VkPipelineColorBlendStateCreateInfo cb;
|
|
VkDynamicState dyn_states[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
|
|
VkPipelineDynamicStateCreateInfo dyn;
|
|
VkGraphicsPipelineCreateInfo pi;
|
|
VkResult res;
|
|
|
|
vs = make_module(vk_mesh_vert_spv, sizeof(vk_mesh_vert_spv));
|
|
fs = make_module(vk_mesh_frag_spv, sizeof(vk_mesh_frag_spv));
|
|
if (!vs || !fs)
|
|
{
|
|
if (vs) vkDestroyShaderModule(g.dev, vs, NULL);
|
|
if (fs) vkDestroyShaderModule(g.dev, fs, NULL);
|
|
return false;
|
|
}
|
|
|
|
spec_entry.constantID = 0;
|
|
spec_entry.offset = 0;
|
|
spec_entry.size = sizeof(spec_value);
|
|
spec.mapEntryCount = 1;
|
|
spec.pMapEntries = &spec_entry;
|
|
spec.dataSize = sizeof(spec_value);
|
|
spec.pData = &spec_value;
|
|
|
|
memset(stages, 0, sizeof(stages));
|
|
stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
|
stages[0].module = vs;
|
|
stages[0].pName = "main";
|
|
stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
stages[1].module = fs;
|
|
stages[1].pName = "main";
|
|
stages[1].pSpecializationInfo = &spec;
|
|
|
|
bind.binding = 0;
|
|
bind.stride = sizeof(GL::Vertex);
|
|
bind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
|
attrs[0].location = 0; attrs[0].binding = 0;
|
|
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT; attrs[0].offset = offsetof(GL::Vertex, vert);
|
|
attrs[1].location = 1; attrs[1].binding = 0;
|
|
attrs[1].format = VK_FORMAT_R32G32B32_SFLOAT; attrs[1].offset = offsetof(GL::Vertex, normal);
|
|
attrs[2].location = 2; attrs[2].binding = 0;
|
|
attrs[2].format = VK_FORMAT_R32G32_SFLOAT; attrs[2].offset = offsetof(GL::Vertex, tex);
|
|
|
|
memset(&vin, 0, sizeof(vin));
|
|
vin.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
|
vin.vertexBindingDescriptionCount = 1;
|
|
vin.pVertexBindingDescriptions = &bind;
|
|
vin.vertexAttributeDescriptionCount = 3;
|
|
vin.pVertexAttributeDescriptions = attrs;
|
|
|
|
memset(&ia, 0, sizeof(ia));
|
|
ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
|
ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
|
|
memset(&vp, 0, sizeof(vp));
|
|
vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
|
vp.viewportCount = 1;
|
|
vp.scissorCount = 1;
|
|
|
|
/* The clip-space fix (Y flip) makes front faces counter-clockwise in
|
|
* Vulkan's framebuffer space, */
|
|
memset(&rs, 0, sizeof(rs));
|
|
rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
|
rs.polygonMode = VK_POLYGON_MODE_FILL;
|
|
rs.cullMode = VK_CULL_MODE_BACK_BIT;
|
|
rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
|
rs.lineWidth = 1.0f;
|
|
|
|
memset(&ms, 0, sizeof(ms));
|
|
ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
|
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
memset(&ds, 0, sizeof(ds));
|
|
ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
|
ds.depthTestEnable = VK_TRUE;
|
|
ds.depthWriteEnable = VK_TRUE;
|
|
ds.depthCompareOp = VK_COMPARE_OP_LESS;
|
|
|
|
memset(&cba, 0, sizeof(cba));
|
|
cba.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
|
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
memset(&cb, 0, sizeof(cb));
|
|
cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
|
cb.attachmentCount = 1;
|
|
cb.pAttachments = &cba;
|
|
|
|
memset(&dyn, 0, sizeof(dyn));
|
|
dyn.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
|
dyn.dynamicStateCount = 2;
|
|
dyn.pDynamicStates = dyn_states;
|
|
|
|
memset(&pi, 0, sizeof(pi));
|
|
pi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
pi.stageCount = 2;
|
|
pi.pStages = stages;
|
|
pi.pVertexInputState = &vin;
|
|
pi.pInputAssemblyState = &ia;
|
|
pi.pViewportState = &vp;
|
|
pi.pRasterizationState = &rs;
|
|
pi.pMultisampleState = &ms;
|
|
pi.pDepthStencilState = &ds;
|
|
pi.pColorBlendState = &cb;
|
|
pi.pDynamicState = &dyn;
|
|
pi.layout = g.pipe_layout;
|
|
pi.renderPass = g.render_pass;
|
|
pi.subpass = 0;
|
|
|
|
res = vkCreateGraphicsPipelines(g.dev, VK_NULL_HANDLE, 1, &pi, NULL, &g.pipeline);
|
|
vkDestroyShaderModule(g.dev, vs, NULL);
|
|
vkDestroyShaderModule(g.dev, fs, NULL);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
g.pipeline = VK_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool pick_depth_format(void)
|
|
{
|
|
static const VkFormat candidates[] = {
|
|
VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM
|
|
};
|
|
unsigned i;
|
|
for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++)
|
|
{
|
|
VkFormatProperties fp;
|
|
vkGetPhysicalDeviceFormatProperties(g.gpu, candidates[i], &fp);
|
|
if (fp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
|
{
|
|
g.depth_format = candidates[i];
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool create_render_pass(void)
|
|
{
|
|
VkAttachmentDescription att[2];
|
|
VkAttachmentReference cref, dref;
|
|
VkSubpassDescription sub;
|
|
VkSubpassDependency deps[2];
|
|
VkRenderPassCreateInfo ri;
|
|
|
|
memset(att, 0, sizeof(att));
|
|
att[0].format = COLOR_FORMAT;
|
|
att[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
att[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
att[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
att[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
att[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
att[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; /* we clear; frontend may have moved it */
|
|
att[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
att[1].format = g.depth_format;
|
|
att[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
att[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
att[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
att[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
att[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
att[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
att[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
cref.attachment = 0; cref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
dref.attachment = 1; dref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
memset(&sub, 0, sizeof(sub));
|
|
sub.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
sub.colorAttachmentCount = 1;
|
|
sub.pColorAttachments = &cref;
|
|
sub.pDepthStencilAttachment = &dref;
|
|
|
|
/* In: do not write the image until earlier users (the frontend's
|
|
* reads of the previous frame of this sync index) are done. */
|
|
memset(deps, 0, sizeof(deps));
|
|
deps[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
deps[0].dstSubpass = 0;
|
|
deps[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
deps[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
|
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
|
deps[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT;
|
|
deps[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
/* Out: this is the barrier the frontend relies on when we pass no
|
|
* semaphores to set_image(). */
|
|
deps[1].srcSubpass = 0;
|
|
deps[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
deps[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
deps[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
deps[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
deps[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
memset(&ri, 0, sizeof(ri));
|
|
ri.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
ri.attachmentCount = 2;
|
|
ri.pAttachments = att;
|
|
ri.subpassCount = 1;
|
|
ri.pSubpasses = ⊂
|
|
ri.dependencyCount = 2;
|
|
ri.pDependencies = deps;
|
|
return vkCreateRenderPass(g.dev, &ri, NULL, &g.render_pass) == VK_SUCCESS;
|
|
}
|
|
|
|
bool create_static_objects(void)
|
|
{
|
|
VkPhysicalDeviceProperties props;
|
|
VkDescriptorSetLayoutBinding b[2];
|
|
VkDescriptorSetLayoutCreateInfo li;
|
|
VkPushConstantRange pcr;
|
|
VkPipelineLayoutCreateInfo pli;
|
|
VkSamplerCreateInfo si;
|
|
VkDescriptorPoolSize ps;
|
|
VkDescriptorPoolCreateInfo dpi;
|
|
VkCommandPoolCreateInfo cpi;
|
|
VkDescriptorSetLayout layouts[2];
|
|
static const uint8_t white[4] = { 255, 255, 255, 255 };
|
|
|
|
vkGetPhysicalDeviceProperties(g.gpu, &props);
|
|
g.ubo_stride = props.limits.minUniformBufferOffsetAlignment;
|
|
if (g.ubo_stride < sizeof(DrawUBO))
|
|
{
|
|
VkDeviceSize a = g.ubo_stride ? g.ubo_stride : 1;
|
|
g.ubo_stride = ((sizeof(DrawUBO) + a - 1) / a) * a;
|
|
}
|
|
|
|
if (!pick_depth_format() || !create_render_pass())
|
|
return false;
|
|
|
|
/* set 0: per-draw dynamic UBO (fragment). */
|
|
memset(b, 0, sizeof(b));
|
|
b[0].binding = 0;
|
|
b[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
|
b[0].descriptorCount = 1;
|
|
b[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
memset(&li, 0, sizeof(li));
|
|
li.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
li.bindingCount = 1;
|
|
li.pBindings = b;
|
|
if (vkCreateDescriptorSetLayout(g.dev, &li, NULL, &g.ubo_layout) != VK_SUCCESS)
|
|
return false;
|
|
|
|
/* set 1: diffuse + ambient maps. */
|
|
memset(b, 0, sizeof(b));
|
|
b[0].binding = 0;
|
|
b[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
b[0].descriptorCount = 1;
|
|
b[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
b[1] = b[0];
|
|
b[1].binding = 1;
|
|
li.bindingCount = 2;
|
|
if (vkCreateDescriptorSetLayout(g.dev, &li, NULL, &g.tex_layout) != VK_SUCCESS)
|
|
return false;
|
|
|
|
pcr.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
|
pcr.offset = 0;
|
|
pcr.size = sizeof(PushConstants); /* 128 B = guaranteed minimum */
|
|
layouts[0] = g.ubo_layout;
|
|
layouts[1] = g.tex_layout;
|
|
memset(&pli, 0, sizeof(pli));
|
|
pli.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
pli.setLayoutCount = 2;
|
|
pli.pSetLayouts = layouts;
|
|
pli.pushConstantRangeCount = 1;
|
|
pli.pPushConstantRanges = &pcr;
|
|
if (vkCreatePipelineLayout(g.dev, &pli, NULL, &g.pipe_layout) != VK_SUCCESS)
|
|
return false;
|
|
|
|
memset(&si, 0, sizeof(si));
|
|
si.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
|
si.magFilter = VK_FILTER_LINEAR;
|
|
si.minFilter = VK_FILTER_LINEAR;
|
|
si.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
|
si.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
si.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
si.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
|
si.maxLod = VK_LOD_CLAMP_NONE;
|
|
if (vkCreateSampler(g.dev, &si, NULL, &g.sampler) != VK_SUCCESS)
|
|
return false;
|
|
|
|
ps.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
|
ps.descriptorCount = VKR_MAX_SLOTS;
|
|
memset(&dpi, 0, sizeof(dpi));
|
|
dpi.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
dpi.maxSets = VKR_MAX_SLOTS;
|
|
dpi.poolSizeCount = 1;
|
|
dpi.pPoolSizes = &ps;
|
|
if (vkCreateDescriptorPool(g.dev, &dpi, NULL, &g.ubo_pool) != VK_SUCCESS)
|
|
return false;
|
|
|
|
memset(&cpi, 0, sizeof(cpi));
|
|
cpi.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
cpi.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
|
|
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
cpi.queueFamilyIndex = g.qfamily;
|
|
if (vkCreateCommandPool(g.dev, &cpi, NULL, &g.upload_pool) != VK_SUCCESS)
|
|
return false;
|
|
|
|
return create_texture(white, 1, 1, g.blank);
|
|
}
|
|
|
|
void write_mesh_descriptors(MeshGPU& m, const Tex *diffuse, const Tex *ambient)
|
|
{
|
|
VkDescriptorImageInfo ii[2];
|
|
VkWriteDescriptorSet w[2];
|
|
unsigned i;
|
|
|
|
if (!diffuse) diffuse = &g.blank;
|
|
if (!ambient) ambient = diffuse; /* GL path: ambient falls back to diffuse, then blank */
|
|
|
|
memset(ii, 0, sizeof(ii));
|
|
ii[0].sampler = g.sampler;
|
|
ii[0].imageView = diffuse->view;
|
|
ii[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
ii[1] = ii[0];
|
|
ii[1].imageView = ambient->view;
|
|
|
|
memset(w, 0, sizeof(w));
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
w[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
w[i].dstSet = m.tex_set;
|
|
w[i].dstBinding = i;
|
|
w[i].descriptorCount = 1;
|
|
w[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
w[i].pImageInfo = &ii[i];
|
|
}
|
|
vkUpdateDescriptorSets(g.dev, 2, w, 0, NULL);
|
|
}
|
|
|
|
void copy_vec4(float *dst, const glm::vec3& v, float w)
|
|
{
|
|
dst[0] = v.x; dst[1] = v.y; dst[2] = v.z; dst[3] = w;
|
|
}
|
|
|
|
void destroy_everything_but_state(void)
|
|
{
|
|
if (g.dev == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
unload_scene();
|
|
destroy_slots();
|
|
destroy_texture(g.blank);
|
|
if (g.upload_pool) vkDestroyCommandPool(g.dev, g.upload_pool, NULL);
|
|
if (g.ubo_pool) vkDestroyDescriptorPool(g.dev, g.ubo_pool, NULL);
|
|
if (g.sampler) vkDestroySampler(g.dev, g.sampler, NULL);
|
|
if (g.pipe_layout) vkDestroyPipelineLayout(g.dev, g.pipe_layout, NULL);
|
|
if (g.tex_layout) vkDestroyDescriptorSetLayout(g.dev, g.tex_layout, NULL);
|
|
if (g.ubo_layout) vkDestroyDescriptorSetLayout(g.dev, g.ubo_layout, NULL);
|
|
if (g.render_pass) vkDestroyRenderPass(g.dev, g.render_pass, NULL);
|
|
}
|
|
} /* anonymous namespace */
|
|
|
|
/* ====================== public API ================================ */
|
|
|
|
bool set_negotiation_interface(retro_environment_t env)
|
|
{
|
|
static struct retro_hw_render_context_negotiation_interface_vulkan neg;
|
|
struct retro_hw_render_context_negotiation_interface query;
|
|
unsigned version = 1; /* frontends without the SUPPORT query only know v1 */
|
|
|
|
memset(&query, 0, sizeof(query));
|
|
query.interface_type = RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN;
|
|
if (env(RETRO_ENVIRONMENT_GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT, &query))
|
|
{
|
|
if (query.interface_version == 0)
|
|
return false; /* frontend has no Vulkan negotiation at all */
|
|
version = query.interface_version < 2 ? query.interface_version : 2;
|
|
}
|
|
|
|
/* We only provide application info. Device/instance creation is left
|
|
* to the frontend on purpose: an OpenXR frontend has to create them
|
|
* through xrCreateVulkan{Instance,Device}KHR so the runtime can add
|
|
* its required extensions and pick the physical device. */
|
|
memset(&neg, 0, sizeof(neg));
|
|
neg.interface_type = RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN;
|
|
neg.interface_version = version;
|
|
neg.get_application_info = get_application_info;
|
|
neg.create_device = NULL;
|
|
neg.destroy_device = NULL;
|
|
neg.create_instance = NULL;
|
|
neg.create_device2 = NULL;
|
|
return env(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, &neg);
|
|
}
|
|
|
|
bool init(retro_environment_t env)
|
|
{
|
|
const struct retro_hw_render_interface_vulkan *iface = NULL;
|
|
|
|
if (g.inited)
|
|
{
|
|
/* context_reset without context_destroy: the device was lost/replaced. */
|
|
VKR_LOG(RETRO_LOG_WARN, "[Vulkan] Context reset without destroy, dropping old state.\n");
|
|
g = Globals();
|
|
}
|
|
|
|
if (!env(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, (void*)&iface) || !iface)
|
|
{
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Frontend gave no HW render interface.\n");
|
|
return false;
|
|
}
|
|
if (iface->interface_type != RETRO_HW_RENDER_INTERFACE_VULKAN ||
|
|
iface->interface_version < RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION)
|
|
{
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Need HW render interface version %d, frontend has %u.\n",
|
|
RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION, iface->interface_version);
|
|
return false;
|
|
}
|
|
|
|
vulkan_symbol_wrapper_init(iface->get_instance_proc_addr);
|
|
if (!vulkan_symbol_wrapper_load_core_instance_symbols(iface->instance) ||
|
|
!vulkan_symbol_wrapper_load_core_device_symbols(iface->device))
|
|
{
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Failed to load Vulkan entry points.\n");
|
|
return false;
|
|
}
|
|
|
|
g.iface = iface;
|
|
g.dev = iface->device;
|
|
g.gpu = iface->gpu;
|
|
g.queue = iface->queue;
|
|
g.qfamily = iface->queue_index; /* despite the name this is the queue *family* */
|
|
|
|
if (!create_static_objects())
|
|
{
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Failed to create renderer objects.\n");
|
|
destroy_everything_but_state();
|
|
g = Globals();
|
|
return false;
|
|
}
|
|
|
|
g.inited = true;
|
|
return true;
|
|
}
|
|
|
|
bool initialized(void)
|
|
{
|
|
return g.inited;
|
|
}
|
|
|
|
void destroy(void)
|
|
{
|
|
if (!g.inited)
|
|
return;
|
|
destroy_everything_but_state();
|
|
g = Globals();
|
|
}
|
|
|
|
void unload_scene(void)
|
|
{
|
|
size_t i;
|
|
std::map<std::string, Tex>::iterator it;
|
|
|
|
if (g.dev == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
wait_own_work(); /* in-flight frames may still read these */
|
|
|
|
for (i = 0; i < g.meshes.size(); i++)
|
|
destroy_buffer(g.meshes[i].vbo);
|
|
g.meshes.clear();
|
|
|
|
for (it = g.textures.begin(); it != g.textures.end(); ++it)
|
|
destroy_texture(it->second);
|
|
g.textures.clear();
|
|
|
|
if (g.tex_pool)
|
|
vkDestroyDescriptorPool(g.dev, g.tex_pool, NULL);
|
|
g.tex_pool = VK_NULL_HANDLE;
|
|
if (g.pipeline)
|
|
vkDestroyPipeline(g.dev, g.pipeline, NULL);
|
|
g.pipeline = VK_NULL_HANDLE;
|
|
g.scene_ready = false;
|
|
}
|
|
|
|
bool load_scene(const std::vector<OBJ::Part>& parts, ShaderMode mode)
|
|
{
|
|
size_t i;
|
|
VkDescriptorPoolSize ps;
|
|
VkDescriptorPoolCreateInfo dpi;
|
|
|
|
if (!g.inited)
|
|
return false;
|
|
unload_scene();
|
|
|
|
if (!create_pipeline(mode))
|
|
{
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Pipeline creation failed.\n");
|
|
return false;
|
|
}
|
|
|
|
ps.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
ps.descriptorCount = (uint32_t)(parts.size() * 2 + 2);
|
|
memset(&dpi, 0, sizeof(dpi));
|
|
dpi.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
dpi.maxSets = (uint32_t)parts.size() + 1;
|
|
dpi.poolSizeCount = 1;
|
|
dpi.pPoolSizes = &ps;
|
|
if (vkCreateDescriptorPool(g.dev, &dpi, NULL, &g.tex_pool) != VK_SUCCESS)
|
|
return false;
|
|
|
|
g.meshes.resize(parts.size());
|
|
for (i = 0; i < parts.size(); i++)
|
|
{
|
|
const OBJ::Part& p = parts[i];
|
|
MeshGPU& m = g.meshes[i];
|
|
VkDescriptorSetAllocateInfo ai;
|
|
const Tex *diffuse, *ambient;
|
|
|
|
m.count = (uint32_t)p.vertices.size();
|
|
m.ambient = p.material.ambient;
|
|
m.diffuse = p.material.diffuse;
|
|
m.specular = p.material.specular;
|
|
m.spec_power = p.material.specular_power;
|
|
m.alpha = p.material.alpha_mod;
|
|
|
|
if (m.count)
|
|
{
|
|
if (!create_buffer(m.count * sizeof(GL::Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, m.vbo))
|
|
return false;
|
|
memcpy(m.vbo.map, &p.vertices[0], m.count * sizeof(GL::Vertex));
|
|
}
|
|
|
|
memset(&ai, 0, sizeof(ai));
|
|
ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
ai.descriptorPool = g.tex_pool;
|
|
ai.descriptorSetCount = 1;
|
|
ai.pSetLayouts = &g.tex_layout;
|
|
if (vkAllocateDescriptorSets(g.dev, &ai, &m.tex_set) != VK_SUCCESS)
|
|
return false;
|
|
|
|
diffuse = get_texture(p.material.diffuse_map);
|
|
ambient = p.material.ambient_map == p.material.diffuse_map ?
|
|
diffuse : get_texture(p.material.ambient_map);
|
|
write_mesh_descriptors(m, diffuse, ambient);
|
|
}
|
|
|
|
g.scene_ready = true;
|
|
return true;
|
|
}
|
|
|
|
bool render(const Frame& fr, retro_video_refresh_t video_cb)
|
|
{
|
|
const struct retro_hw_render_interface_vulkan *I = g.iface;
|
|
uint32_t mask, idx;
|
|
unsigned p;
|
|
size_t mi;
|
|
VkDeviceSize draws, needed, draw_index = 0;
|
|
VkCommandBufferBeginInfo bi;
|
|
VkRenderPassBeginInfo rp;
|
|
VkClearValue clears[2];
|
|
VkSubmitInfo si;
|
|
VkResult res;
|
|
glm::mat4 clip(1.0f);
|
|
|
|
if (!g.inited || !g.scene_ready || !fr.num_passes || fr.num_passes > 2 ||
|
|
!fr.width || !fr.height)
|
|
return false;
|
|
|
|
mask = I->get_sync_index_mask(I->handle);
|
|
if (mask != g.last_mask || g.slots.empty())
|
|
{
|
|
/* Documented as "device is idle when the mask changes". */
|
|
destroy_slots();
|
|
if (!create_slots(mask))
|
|
return false;
|
|
}
|
|
|
|
idx = I->get_sync_index(I->handle);
|
|
if (idx >= g.slots.size())
|
|
return false;
|
|
|
|
/* Frontend is done with everything it was given for this index... */
|
|
I->wait_sync_index(I->handle);
|
|
Slot& s = g.slots[idx];
|
|
/* ...and so are our own earlier submissions for it. */
|
|
vkWaitForFences(g.dev, 1, &s.fence, VK_TRUE, (uint64_t)-1);
|
|
|
|
if (s.target.w != fr.width || s.target.h != fr.height)
|
|
{
|
|
destroy_target(s.target);
|
|
if (!create_target(s.target, fr.width, fr.height))
|
|
{
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] Failed to create %ux%u target.\n", fr.width, fr.height);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
draws = (VkDeviceSize)g.meshes.size() * fr.num_passes;
|
|
needed = (draws ? draws : 1) * g.ubo_stride;
|
|
if (!ensure_ubo(s, needed))
|
|
return false;
|
|
|
|
/* GL clip space (Y up, z -1..1) -> Vulkan (Y down, z 0..1). */
|
|
clip[1][1] = -1.0f;
|
|
clip[2][2] = 0.5f;
|
|
clip[3][2] = 0.5f;
|
|
|
|
vkResetCommandPool(g.dev, s.pool, 0);
|
|
memset(&bi, 0, sizeof(bi));
|
|
bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(s.cmd, &bi);
|
|
|
|
memset(clears, 0, sizeof(clears));
|
|
clears[0].color.float32[0] = 0.2f;
|
|
clears[0].color.float32[1] = 0.2f;
|
|
clears[0].color.float32[2] = 0.2f;
|
|
clears[0].color.float32[3] = 1.0f;
|
|
clears[1].depthStencil.depth = 1.0f;
|
|
|
|
memset(&rp, 0, sizeof(rp));
|
|
rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
rp.renderPass = g.render_pass;
|
|
rp.framebuffer = s.target.fb;
|
|
rp.renderArea.extent.width = fr.width;
|
|
rp.renderArea.extent.height = fr.height;
|
|
rp.clearValueCount = 2;
|
|
rp.pClearValues = clears;
|
|
vkCmdBeginRenderPass(s.cmd, &rp, VK_SUBPASS_CONTENTS_INLINE);
|
|
vkCmdBindPipeline(s.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g.pipeline);
|
|
|
|
for (p = 0; p < fr.num_passes; p++)
|
|
{
|
|
const Pass& ps = fr.passes[p];
|
|
VkViewport vp;
|
|
VkRect2D sc;
|
|
glm::mat4 vp_mat = clip * ps.proj * ps.view;
|
|
unsigned pw = ps.width;
|
|
|
|
if (ps.x >= fr.width)
|
|
continue;
|
|
if (ps.x + pw > fr.width)
|
|
pw = fr.width - ps.x;
|
|
|
|
vp.x = (float)ps.x; vp.y = 0.0f;
|
|
vp.width = (float)pw; vp.height = (float)ps.height;
|
|
vp.minDepth = 0.0f; vp.maxDepth = 1.0f;
|
|
sc.offset.x = (int32_t)ps.x; sc.offset.y = 0;
|
|
sc.extent.width = pw; sc.extent.height = ps.height;
|
|
vkCmdSetViewport(s.cmd, 0, 1, &vp);
|
|
vkCmdSetScissor(s.cmd, 0, 1, &sc);
|
|
|
|
for (mi = 0; mi < g.meshes.size(); mi++)
|
|
{
|
|
const MeshGPU& m = g.meshes[mi];
|
|
DrawUBO *u;
|
|
PushConstants pc;
|
|
glm::mat4 mvp = vp_mat * fr.model;
|
|
uint32_t dyn_offset = (uint32_t)(draw_index * g.ubo_stride);
|
|
VkDeviceSize zero = 0;
|
|
|
|
if (!m.count)
|
|
continue;
|
|
|
|
u = (DrawUBO*)(s.ubo.map + draw_index * g.ubo_stride);
|
|
draw_index++;
|
|
copy_vec4(u->light, fr.light, 0.0f);
|
|
copy_vec4(u->light_ambient, fr.ambient, 0.0f);
|
|
copy_vec4(u->eye, ps.eye_pos, 0.0f);
|
|
copy_vec4(u->mtl_ambient, m.ambient, 0.0f);
|
|
copy_vec4(u->mtl_diffuse, m.diffuse, 0.0f);
|
|
copy_vec4(u->mtl_specular, m.specular, 0.0f);
|
|
u->mtl_params[0] = m.spec_power;
|
|
u->mtl_params[1] = m.alpha;
|
|
u->mtl_params[2] = 0.0f;
|
|
u->mtl_params[3] = 0.0f;
|
|
|
|
memcpy(pc.model, &fr.model[0][0], sizeof(pc.model));
|
|
memcpy(pc.mvp, &mvp[0][0], sizeof(pc.mvp));
|
|
|
|
vkCmdBindDescriptorSets(s.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g.pipe_layout,
|
|
0, 1, &s.ubo_set, 1, &dyn_offset);
|
|
vkCmdBindDescriptorSets(s.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, g.pipe_layout,
|
|
1, 1, &m.tex_set, 0, NULL);
|
|
vkCmdPushConstants(s.cmd, g.pipe_layout, VK_SHADER_STAGE_VERTEX_BIT,
|
|
0, sizeof(pc), &pc);
|
|
vkCmdBindVertexBuffers(s.cmd, 0, 1, &m.vbo.buf, &zero);
|
|
vkCmdDraw(s.cmd, m.count, 1, 0, 0);
|
|
}
|
|
}
|
|
|
|
vkCmdEndRenderPass(s.cmd);
|
|
vkEndCommandBuffer(s.cmd);
|
|
|
|
memset(&si, 0, sizeof(si));
|
|
si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
si.commandBufferCount = 1;
|
|
si.pCommandBuffers = &s.cmd;
|
|
|
|
vkResetFences(g.dev, 1, &s.fence);
|
|
I->lock_queue(I->handle);
|
|
res = vkQueueSubmit(g.queue, 1, &si, s.fence);
|
|
I->unlock_queue(I->handle);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
/* Keep the fence signalled so the next wait cannot deadlock. */
|
|
VkSubmitInfo empty;
|
|
memset(&empty, 0, sizeof(empty));
|
|
empty.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
I->lock_queue(I->handle);
|
|
vkQueueSubmit(g.queue, 0, &empty, s.fence);
|
|
I->unlock_queue(I->handle);
|
|
VKR_LOG(RETRO_LOG_ERROR, "[Vulkan] vkQueueSubmit failed (%d).\n", (int)res);
|
|
return false;
|
|
}
|
|
|
|
/* No semaphores: the render pass' external dependency is the barrier. */
|
|
I->set_image(I->handle, &s.target.desc, 0, NULL, VK_QUEUE_FAMILY_IGNORED);
|
|
video_cb(RETRO_HW_FRAME_BUFFER_VALID, fr.width, fr.height, 0);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_VULKAN */
|