diff --git a/Makefile b/Makefile index d93d338..bc5781b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ STATIC_LINKING=0 +HAVE_VULKAN ?= 0 ifeq ($(platform),) platform = unix @@ -23,6 +24,7 @@ ifneq (,$(findstring unix,$(platform))) TARGET := $(TARGET_NAME)_libretro.so fpic := -fPIC SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined + HAVE_VULKAN := 1 ifneq (,$(findstring gles,$(platform))) GLES = 1 else @@ -263,6 +265,9 @@ else GL_LIB := -L. -lopengl32 endif +ifeq ($(HAVE_VULKAN),1) + CXXFLAGS += -DHAVE_VULKAN +endif ifeq ($(DEBUG), 1) CXXFLAGS += -O0 -g diff --git a/Makefile.common b/Makefile.common index 05cee05..bd61190 100644 --- a/Makefile.common +++ b/Makefile.common @@ -26,6 +26,11 @@ else SOURCES_C += $(CORE_DIR)/libretro-common/glsym/glsym_gl.c endif +ifeq ($(HAVE_VULKAN),1) +SOURCES_C += $(CORE_DIR)/libretro-common/vulkan/vulkan_symbol_wrapper.c +SOURCES_CXX += $(CORE_DIR)/engine/vk_renderer.cpp +endif + ifneq ($(STATIC_LINKING), 1) #include zlib SOURCES_C += $(DEPS_DIR)/zlib/adler32.c \ diff --git a/engine/mesh.frag b/engine/mesh.frag new file mode 100644 index 0000000..ea52e9b --- /dev/null +++ b/engine/mesh.frag @@ -0,0 +1,50 @@ +#version 450 +/* 0 = model viewer, 1 = model viewer + discard hack, 2 = scene walker. */ +layout(constant_id = 0) const int MODE = 0; + +layout(location = 0) in vec2 vTex; +layout(location = 1) in vec4 vNormal; +layout(location = 2) in vec4 vPos; + +layout(set = 0, binding = 0) uniform Draw +{ + vec4 lightPos; + vec4 lightAmbient; + vec4 eyePos; + vec4 mtlAmbient; + vec4 mtlDiffuse; + vec4 mtlSpecular; + vec4 mtlParams; /* x = specular power, y = alpha mod */ +} u; + +layout(set = 1, binding = 0) uniform sampler2D sDiffuse; +layout(set = 1, binding = 1) uniform sampler2D sAmbient; + +layout(location = 0) out vec4 outColor; + +void main() +{ + vec4 colorDiffuseFull = texture(sDiffuse, vTex); + vec4 colorAmbientFull = texture(sAmbient, vTex); + + if (MODE == 1 && colorDiffuseFull.a < 0.5) + discard; + + vec3 lightDir = (MODE == 2) ? normalize(vPos.xyz - u.lightPos.xyz) : u.lightPos.xyz; + + vec3 colorDiffuse = mix(u.mtlDiffuse.rgb, colorDiffuseFull.rgb, vec3(colorDiffuseFull.a)); + vec3 colorAmbient = mix(u.mtlAmbient.rgb, colorAmbientFull.rgb, vec3(colorAmbientFull.a)); + + vec3 normal = normalize(vNormal.xyz); + float directivity = dot(lightDir, -normal); + + vec3 diffuse = colorDiffuse * clamp(directivity, 0.0, 1.0); + vec3 ambient = colorAmbient * u.lightAmbient.rgb; + + vec3 modelToFace = (MODE == 2) ? normalize(u.eyePos.xyz - vPos.xyz) : normalize(-vPos.xyz); + float specularity = pow(clamp(dot(modelToFace, reflect(lightDir, normal)), 0.0, 1.0), u.mtlParams.x); + vec3 specular = u.mtlSpecular.rgb * specularity; + + float alpha = (MODE == 1) ? u.mtlParams.y : u.mtlParams.y * colorDiffuseFull.a; + outColor = vec4(diffuse + ambient + specular, alpha); +} diff --git a/engine/mesh.vert b/engine/mesh.vert new file mode 100644 index 0000000..a2f3718 --- /dev/null +++ b/engine/mesh.vert @@ -0,0 +1,19 @@ +#version 450 +layout(location = 0) in vec3 aVertex; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTex; + +layout(push_constant) uniform PC { mat4 model; mat4 mvp; } pc; + +layout(location = 0) out vec2 vTex; +layout(location = 1) out vec4 vNormal; +layout(location = 2) out vec4 vPos; + +void main() +{ + vec4 v = vec4(aVertex, 1.0); + gl_Position = pc.mvp * v; + vTex = aTex; + vPos = pc.model * v; + vNormal = pc.model * vec4(aNormal, 0.0); +} diff --git a/engine/object.cpp b/engine/object.cpp index 170d531..550b5d1 100644 --- a/engine/object.cpp +++ b/engine/object.cpp @@ -131,10 +131,10 @@ namespace OBJ } } - static map parse_mtllib(const string& path, map >& textures) + static map parse_mtllib(const string& path) { - map materials; - Material current; + map materials; + RawMaterial current; string current_mtl; string line; @@ -155,7 +155,7 @@ namespace OBJ if (current_mtl.size()) materials[current_mtl] = current; - current = Material(); + current = RawMaterial(); current_mtl = data; } else if (type == "Ka") @@ -171,25 +171,9 @@ namespace OBJ else if (type == "Tr") current.alpha_mod = 1.0f - String::stof(data); else if (type == "map_Kd") - { - if (!textures[data]) - { - string diffuse_path = Path::join(Path::basedir(path), data); - textures[data] = std1::shared_ptr(new Texture(diffuse_path)); - } - - current.diffuse_map = textures[data]; - } + current.diffuse_map = Path::join(Path::basedir(path), data); else if (type == "map_Ka") - { - if (!textures[data]) - { - string ambient_path = Path::join(Path::basedir(path), data); - textures[data] = std1::shared_ptr(new Texture(ambient_path)); - } - - current.ambient_map = textures[data]; - } + current.ambient_map = Path::join(Path::basedir(path), data); } materials[current_mtl] = current; @@ -197,25 +181,35 @@ namespace OBJ return materials; } - vector > load_from_file(const string& path) + static void flush_part(vector& parts, vector& vertices, + const RawMaterial& material) + { + if (!vertices.size()) + return; + + Part part; + part.vertices.swap(vertices); + part.material = material; + parts.push_back(part); + } + + vector load_parts(const string& path) { vector vertex; vector normal; vector tex; vector vertices; + vector parts; - /* Texture cache. */ - map > textures; - Material current_material; + RawMaterial current_material; string line; - map materials; + map materials; ifstream file(path.c_str(), ios::in); - vector > meshes; if (!file.is_open()) - return meshes; + return parts; for (; getline(file, line); ) { @@ -235,55 +229,63 @@ namespace OBJ parse_vertex(data, vertices, vertex, normal, tex); else if (type == "texture") // Not standard OBJ, but do it like this for simplicity ... { - if (vertices.size()) // Different texture, new mesh. - { - std1::shared_ptr mesh(new Mesh()); - mesh->set_vertices(vertices); - vertices.clear(); + flush_part(parts, vertices, current_material); // Different texture, new part. - mesh->set_material(current_material); - meshes.push_back(mesh); - } - - if (!textures[data]) - { - string texture_path = Path::join(Path::basedir(path), data + ".png"); - textures[data] = std1::shared_ptr(new Texture(texture_path)); - } - - current_material = Material(); - current_material.diffuse_map = textures[data]; - current_material.ambient_map = textures[data]; + current_material = RawMaterial(); + current_material.diffuse_map = Path::join(Path::basedir(path), data + ".png"); + current_material.ambient_map = current_material.diffuse_map; } else if (type == "usemtl") { - if (vertices.size()) // Different texture, new mesh. - { - std1::shared_ptr mesh(new Mesh()); - mesh->set_vertices(vertices); - vertices.clear(); - - mesh->set_material(current_material); - meshes.push_back(mesh); - } - + flush_part(parts, vertices, current_material); // Different material, new part. current_material = materials[data]; } else if (type == "mtllib") - materials = parse_mtllib(Path::join(Path::basedir(path), data), textures); + materials = parse_mtllib(Path::join(Path::basedir(path), data)); } - if (vertices.size()) - { - std1::shared_ptr mesh(new Mesh()); - mesh->set_vertices(vertices); - vertices.clear(); + flush_part(parts, vertices, current_material); + return parts; + } - mesh->set_material(current_material); + static std1::shared_ptr cached_texture(const string& path, + map >& cache) + { + if (!path.size()) + return std1::shared_ptr(); + + std1::shared_ptr& slot = cache[path]; + if (!slot) + slot = std1::shared_ptr(new Texture(path)); + return slot; + } + + vector > load_from_file(const string& path) + { + unsigned i; + vector parts = load_parts(path); + vector > meshes; + map > textures; /* Texture cache. */ + + for (i = 0; i < parts.size(); i++) + { + const RawMaterial& raw = parts[i].material; + Material material; + + material.ambient = raw.ambient; + material.diffuse = raw.diffuse; + material.specular = raw.specular; + material.specular_power = raw.specular_power; + material.alpha_mod = raw.alpha_mod; + material.diffuse_map = cached_texture(raw.diffuse_map, textures); + material.ambient_map = cached_texture(raw.ambient_map, textures); + + std1::shared_ptr mesh(new Mesh()); + mesh->set_vertices(parts[i].vertices); + mesh->set_material(material); meshes.push_back(mesh); } return meshes; } } - diff --git a/engine/object.hpp b/engine/object.hpp index 79ed702..ed78cca 100644 --- a/engine/object.hpp +++ b/engine/object.hpp @@ -26,8 +26,39 @@ namespace OBJ { + /* Renderer-agnostic material. Texture maps are full file paths + * (empty if none) so both the GL and Vulkan back-ends can load them. */ + struct RawMaterial + { + RawMaterial() : + ambient(0, 0, 0), + diffuse(0, 0, 0), + specular(0, 0, 0), + specular_power(60.0f), + alpha_mod(1.0f) + {} + + glm::vec3 ambient; + glm::vec3 diffuse; + glm::vec3 specular; + float specular_power; + float alpha_mod; + std::string diffuse_map; + std::string ambient_map; + }; + + /* One draw call's worth of triangles that share a material. */ + struct Part + { + std::vector vertices; + RawMaterial material; + }; + + /* Pure CPU parse. Needs no GL/Vulkan context. */ + std::vector load_parts(const std::string& path); + + /* GL back-end: load_parts() + create GL::Mesh/GL::Texture objects. */ std::vector > load_from_file(const std::string& path); } #endif - diff --git a/engine/vk_renderer.cpp b/engine/vk_renderer.cpp new file mode 100644 index 0000000..e067515 --- /dev/null +++ b/engine/vk_renderer.cpp @@ -0,0 +1,1489 @@ +/* + * 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 . + */ +#ifdef HAVE_VULKAN + +#include +#include + +#include "vk_renderer.hpp" +#include "vk_shaders.inc" +#include "rpng.h" +#include "rtga.h" +#include "util.hpp" +#include "shared.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#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 slots; + + Tex blank; + std::map textures; + std::vector 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::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::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& 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 */ diff --git a/engine/vk_renderer.hpp b/engine/vk_renderer.hpp new file mode 100644 index 0000000..4fcb578 --- /dev/null +++ b/engine/vk_renderer.hpp @@ -0,0 +1,86 @@ +/* + * 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 . + */ +#ifndef VK_RENDERER_HPP__ +#define VK_RENDERER_HPP__ + +#ifdef HAVE_VULKAN + +#include +#include +#include "glm/glm.hpp" +#include "object.hpp" + +/* Minimal Vulkan mesh renderer used by the model viewer / scene walker + * when the frontend runs the core on a Vulkan context. + * + * Matrices passed in are the same OpenGL-convention matrices the GL path + * uses (perspective() / vr_projection() / lookAt()); this module applies + * the GL->Vulkan clip space correction (Y down, depth 0..1) itself, so the + * caller must NOT pre-flip Y. */ +namespace VKR +{ + enum ShaderMode + { + SHADER_MODEL = 0, + SHADER_MODEL_DISCARD = 1, + SHADER_SCENE = 2 + }; + + struct Pass + { + glm::mat4 proj; /* GL-convention projection */ + glm::mat4 view; + glm::vec3 eye_pos; + unsigned x; /* viewport/scissor origin (pixels) */ + unsigned width; + unsigned height; + }; + + struct Frame + { + glm::mat4 model; + glm::vec3 light; /* direction (model viewer) or position (scene) */ + glm::vec3 ambient; + unsigned width; /* size of the image handed to the frontend */ + unsigned height; + unsigned num_passes; /* 1 = flat, 2 = side-by-side stereo */ + Pass passes[2]; + }; + + /* Call right after RETRO_ENVIRONMENT_SET_HW_RENDER(VULKAN) succeeded. */ + bool set_negotiation_interface(retro_environment_t env); + + /* context_reset: fetch the frontend's Vulkan interface, build pipelines. */ + bool init(retro_environment_t env); + + /* context_destroy: device is still alive, free everything. */ + void destroy(void); + + /* True between a successful init() and destroy(). */ + bool initialized(void); + + bool load_scene(const std::vector& parts, ShaderMode mode); + void unload_scene(void); + + /* Renders, hands the image to the frontend with set_image() and calls + * video_cb(RETRO_HW_FRAME_BUFFER_VALID, ...). Returns false (and does + * not call video_cb) if nothing could be rendered. */ + bool render(const Frame& frame, retro_video_refresh_t video_cb); +} + +#endif /* HAVE_VULKAN */ +#endif diff --git a/engine/vk_shaders.inc b/engine/vk_shaders.inc new file mode 100644 index 0000000..e83f98b --- /dev/null +++ b/engine/vk_shaders.inc @@ -0,0 +1,217 @@ +/* Generated by: glslangValidator -V --vn -o . Sources: engine/shaders/mesh.{vert,frag}. Do not edit. */ + // 1115.1.0 + #pragma once +const uint32_t vk_mesh_vert_spv[] = { + 0x07230203,0x00010000,0x0008000b,0x0000003d,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x000c000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000c,0x00000018,0x00000029, + 0x0000002b,0x0000002d,0x00000032,0x00000035,0x00030003,0x00000002,0x000001c2,0x00040005, + 0x00000004,0x6e69616d,0x00000000,0x00030005,0x00000009,0x00000076,0x00040005,0x0000000c, + 0x72655661,0x00786574,0x00060005,0x00000016,0x505f6c67,0x65567265,0x78657472,0x00000000, + 0x00060006,0x00000016,0x00000000,0x505f6c67,0x7469736f,0x006e6f69,0x00070006,0x00000016, + 0x00000001,0x505f6c67,0x746e696f,0x657a6953,0x00000000,0x00070006,0x00000016,0x00000002, + 0x435f6c67,0x4470696c,0x61747369,0x0065636e,0x00070006,0x00000016,0x00000003,0x435f6c67, + 0x446c6c75,0x61747369,0x0065636e,0x00030005,0x00000018,0x00000000,0x00030005,0x0000001c, + 0x00004350,0x00050006,0x0000001c,0x00000000,0x65646f6d,0x0000006c,0x00040006,0x0000001c, + 0x00000001,0x0070766d,0x00030005,0x0000001e,0x00006370,0x00040005,0x00000029,0x78655476, + 0x00000000,0x00040005,0x0000002b,0x78655461,0x00000000,0x00040005,0x0000002d,0x736f5076, + 0x00000000,0x00040005,0x00000032,0x726f4e76,0x006c616d,0x00040005,0x00000035,0x726f4e61, + 0x006c616d,0x00040047,0x0000000c,0x0000001e,0x00000000,0x00030047,0x00000016,0x00000002, + 0x00050048,0x00000016,0x00000000,0x0000000b,0x00000000,0x00050048,0x00000016,0x00000001, + 0x0000000b,0x00000001,0x00050048,0x00000016,0x00000002,0x0000000b,0x00000003,0x00050048, + 0x00000016,0x00000003,0x0000000b,0x00000004,0x00030047,0x0000001c,0x00000002,0x00040048, + 0x0000001c,0x00000000,0x00000005,0x00050048,0x0000001c,0x00000000,0x00000007,0x00000010, + 0x00050048,0x0000001c,0x00000000,0x00000023,0x00000000,0x00040048,0x0000001c,0x00000001, + 0x00000005,0x00050048,0x0000001c,0x00000001,0x00000007,0x00000010,0x00050048,0x0000001c, + 0x00000001,0x00000023,0x00000040,0x00040047,0x00000029,0x0000001e,0x00000000,0x00040047, + 0x0000002b,0x0000001e,0x00000002,0x00040047,0x0000002d,0x0000001e,0x00000002,0x00040047, + 0x00000032,0x0000001e,0x00000001,0x00040047,0x00000035,0x0000001e,0x00000001,0x00020013, + 0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017, + 0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000007,0x00000007,0x00040017, + 0x0000000a,0x00000006,0x00000003,0x00040020,0x0000000b,0x00000001,0x0000000a,0x0004003b, + 0x0000000b,0x0000000c,0x00000001,0x0004002b,0x00000006,0x0000000e,0x3f800000,0x00040015, + 0x00000013,0x00000020,0x00000000,0x0004002b,0x00000013,0x00000014,0x00000001,0x0004001c, + 0x00000015,0x00000006,0x00000014,0x0006001e,0x00000016,0x00000007,0x00000006,0x00000015, + 0x00000015,0x00040020,0x00000017,0x00000003,0x00000016,0x0004003b,0x00000017,0x00000018, + 0x00000003,0x00040015,0x00000019,0x00000020,0x00000001,0x0004002b,0x00000019,0x0000001a, + 0x00000000,0x00040018,0x0000001b,0x00000007,0x00000004,0x0004001e,0x0000001c,0x0000001b, + 0x0000001b,0x00040020,0x0000001d,0x00000009,0x0000001c,0x0004003b,0x0000001d,0x0000001e, + 0x00000009,0x0004002b,0x00000019,0x0000001f,0x00000001,0x00040020,0x00000020,0x00000009, + 0x0000001b,0x00040020,0x00000025,0x00000003,0x00000007,0x00040017,0x00000027,0x00000006, + 0x00000002,0x00040020,0x00000028,0x00000003,0x00000027,0x0004003b,0x00000028,0x00000029, + 0x00000003,0x00040020,0x0000002a,0x00000001,0x00000027,0x0004003b,0x0000002a,0x0000002b, + 0x00000001,0x0004003b,0x00000025,0x0000002d,0x00000003,0x0004003b,0x00000025,0x00000032, + 0x00000003,0x0004003b,0x0000000b,0x00000035,0x00000001,0x0004002b,0x00000006,0x00000037, + 0x00000000,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005, + 0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003d,0x0000000a,0x0000000d,0x0000000c, + 0x00050051,0x00000006,0x0000000f,0x0000000d,0x00000000,0x00050051,0x00000006,0x00000010, + 0x0000000d,0x00000001,0x00050051,0x00000006,0x00000011,0x0000000d,0x00000002,0x00070050, + 0x00000007,0x00000012,0x0000000f,0x00000010,0x00000011,0x0000000e,0x0003003e,0x00000009, + 0x00000012,0x00050041,0x00000020,0x00000021,0x0000001e,0x0000001f,0x0004003d,0x0000001b, + 0x00000022,0x00000021,0x0004003d,0x00000007,0x00000023,0x00000009,0x00050091,0x00000007, + 0x00000024,0x00000022,0x00000023,0x00050041,0x00000025,0x00000026,0x00000018,0x0000001a, + 0x0003003e,0x00000026,0x00000024,0x0004003d,0x00000027,0x0000002c,0x0000002b,0x0003003e, + 0x00000029,0x0000002c,0x00050041,0x00000020,0x0000002e,0x0000001e,0x0000001a,0x0004003d, + 0x0000001b,0x0000002f,0x0000002e,0x0004003d,0x00000007,0x00000030,0x00000009,0x00050091, + 0x00000007,0x00000031,0x0000002f,0x00000030,0x0003003e,0x0000002d,0x00000031,0x00050041, + 0x00000020,0x00000033,0x0000001e,0x0000001a,0x0004003d,0x0000001b,0x00000034,0x00000033, + 0x0004003d,0x0000000a,0x00000036,0x00000035,0x00050051,0x00000006,0x00000038,0x00000036, + 0x00000000,0x00050051,0x00000006,0x00000039,0x00000036,0x00000001,0x00050051,0x00000006, + 0x0000003a,0x00000036,0x00000002,0x00070050,0x00000007,0x0000003b,0x00000038,0x00000039, + 0x0000003a,0x00000037,0x00050091,0x00000007,0x0000003c,0x00000034,0x0000003b,0x0003003e, + 0x00000032,0x0000003c,0x000100fd,0x00010038 +}; + // 1115.1.0 + #pragma once +const uint32_t vk_mesh_frag_spv[] = { + 0x07230203,0x00010000,0x0008000b,0x000000b4,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0009000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000011,0x00000034,0x0000005d, + 0x000000a9,0x00030010,0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005, + 0x00000004,0x6e69616d,0x00000000,0x00070005,0x00000009,0x6f6c6f63,0x66694472,0x65737566, + 0x6c6c7546,0x00000000,0x00050005,0x0000000d,0x66694473,0x65737566,0x00000000,0x00040005, + 0x00000011,0x78655476,0x00000000,0x00070005,0x00000014,0x6f6c6f63,0x626d4172,0x746e6569, + 0x6c6c7546,0x00000000,0x00050005,0x00000015,0x626d4173,0x746e6569,0x00000000,0x00040005, + 0x0000001b,0x45444f4d,0x00000000,0x00050005,0x0000002d,0x6867696c,0x72694474,0x00000000, + 0x00040005,0x00000034,0x736f5076,0x00000000,0x00040005,0x00000037,0x77617244,0x00000000, + 0x00060006,0x00000037,0x00000000,0x6867696c,0x736f5074,0x00000000,0x00070006,0x00000037, + 0x00000001,0x6867696c,0x626d4174,0x746e6569,0x00000000,0x00050006,0x00000037,0x00000002, + 0x50657965,0x0000736f,0x00060006,0x00000037,0x00000003,0x416c746d,0x6569626d,0x0000746e, + 0x00060006,0x00000037,0x00000004,0x446c746d,0x75666669,0x00006573,0x00060006,0x00000037, + 0x00000005,0x536c746d,0x75636570,0x0072616c,0x00060006,0x00000037,0x00000006,0x506c746d, + 0x6d617261,0x00000073,0x00030005,0x00000039,0x00000075,0x00060005,0x00000046,0x6f6c6f63, + 0x66694472,0x65737566,0x00000000,0x00060005,0x00000051,0x6f6c6f63,0x626d4172,0x746e6569, + 0x00000000,0x00040005,0x0000005c,0x6d726f6e,0x00006c61,0x00040005,0x0000005d,0x726f4e76, + 0x006c616d,0x00050005,0x00000061,0x65726964,0x76697463,0x00797469,0x00040005,0x00000066, + 0x66666964,0x00657375,0x00040005,0x0000006d,0x69626d61,0x00746e65,0x00050005,0x00000073, + 0x65646f6d,0x466f546c,0x00656361,0x00050005,0x00000085,0x63657073,0x72616c75,0x00797469, + 0x00050005,0x00000092,0x63657073,0x72616c75,0x00000000,0x00040005,0x00000099,0x68706c61, + 0x00000061,0x00050005,0x000000a9,0x4374756f,0x726f6c6f,0x00000000,0x00040047,0x0000000d, + 0x00000021,0x00000000,0x00040047,0x0000000d,0x00000022,0x00000001,0x00040047,0x00000011, + 0x0000001e,0x00000000,0x00040047,0x00000015,0x00000021,0x00000001,0x00040047,0x00000015, + 0x00000022,0x00000001,0x00040047,0x0000001b,0x00000001,0x00000000,0x00040047,0x00000034, + 0x0000001e,0x00000002,0x00030047,0x00000037,0x00000002,0x00050048,0x00000037,0x00000000, + 0x00000023,0x00000000,0x00050048,0x00000037,0x00000001,0x00000023,0x00000010,0x00050048, + 0x00000037,0x00000002,0x00000023,0x00000020,0x00050048,0x00000037,0x00000003,0x00000023, + 0x00000030,0x00050048,0x00000037,0x00000004,0x00000023,0x00000040,0x00050048,0x00000037, + 0x00000005,0x00000023,0x00000050,0x00050048,0x00000037,0x00000006,0x00000023,0x00000060, + 0x00040047,0x00000039,0x00000021,0x00000000,0x00040047,0x00000039,0x00000022,0x00000000, + 0x00040047,0x0000005d,0x0000001e,0x00000001,0x00040047,0x000000a9,0x0000001e,0x00000000, + 0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020, + 0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000007,0x00000007, + 0x00090019,0x0000000a,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001, + 0x00000000,0x0003001b,0x0000000b,0x0000000a,0x00040020,0x0000000c,0x00000000,0x0000000b, + 0x0004003b,0x0000000c,0x0000000d,0x00000000,0x00040017,0x0000000f,0x00000006,0x00000002, + 0x00040020,0x00000010,0x00000001,0x0000000f,0x0004003b,0x00000010,0x00000011,0x00000001, + 0x0004003b,0x0000000c,0x00000015,0x00000000,0x00020014,0x00000019,0x00040015,0x0000001a, + 0x00000020,0x00000001,0x00040032,0x0000001a,0x0000001b,0x00000000,0x0004002b,0x0000001a, + 0x0000001c,0x00000001,0x00060034,0x00000019,0x0000001d,0x000000aa,0x0000001b,0x0000001c, + 0x00040015,0x00000020,0x00000020,0x00000000,0x0004002b,0x00000020,0x00000021,0x00000003, + 0x00040020,0x00000022,0x00000007,0x00000006,0x0004002b,0x00000006,0x00000025,0x3f000000, + 0x00040017,0x0000002b,0x00000006,0x00000003,0x00040020,0x0000002c,0x00000007,0x0000002b, + 0x0004002b,0x0000001a,0x0000002e,0x00000002,0x00060034,0x00000019,0x0000002f,0x000000aa, + 0x0000001b,0x0000002e,0x00040020,0x00000033,0x00000001,0x00000007,0x0004003b,0x00000033, + 0x00000034,0x00000001,0x0009001e,0x00000037,0x00000007,0x00000007,0x00000007,0x00000007, + 0x00000007,0x00000007,0x00000007,0x00040020,0x00000038,0x00000002,0x00000037,0x0004003b, + 0x00000038,0x00000039,0x00000002,0x0004002b,0x0000001a,0x0000003a,0x00000000,0x00040020, + 0x0000003b,0x00000002,0x00000007,0x0004002b,0x0000001a,0x00000047,0x00000004,0x0004002b, + 0x0000001a,0x00000052,0x00000003,0x0004003b,0x00000033,0x0000005d,0x00000001,0x0004002b, + 0x00000006,0x00000069,0x00000000,0x0004002b,0x00000006,0x0000006a,0x3f800000,0x00060034, + 0x00000019,0x00000074,0x000000aa,0x0000001b,0x0000002e,0x0004002b,0x0000001a,0x0000008c, + 0x00000006,0x0004002b,0x00000020,0x0000008d,0x00000000,0x00040020,0x0000008e,0x00000002, + 0x00000006,0x0004002b,0x0000001a,0x00000093,0x00000005,0x00060034,0x00000019,0x0000009a, + 0x000000aa,0x0000001b,0x0000001c,0x0004002b,0x00000020,0x0000009e,0x00000001,0x00040020, + 0x000000a8,0x00000003,0x00000007,0x0004003b,0x000000a8,0x000000a9,0x00000003,0x00050036, + 0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000008, + 0x00000009,0x00000007,0x0004003b,0x00000008,0x00000014,0x00000007,0x0004003b,0x0000002c, + 0x0000002d,0x00000007,0x0004003b,0x0000002c,0x00000030,0x00000007,0x0004003b,0x0000002c, + 0x00000046,0x00000007,0x0004003b,0x0000002c,0x00000051,0x00000007,0x0004003b,0x0000002c, + 0x0000005c,0x00000007,0x0004003b,0x00000022,0x00000061,0x00000007,0x0004003b,0x0000002c, + 0x00000066,0x00000007,0x0004003b,0x0000002c,0x0000006d,0x00000007,0x0004003b,0x0000002c, + 0x00000073,0x00000007,0x0004003b,0x0000002c,0x00000075,0x00000007,0x0004003b,0x00000022, + 0x00000085,0x00000007,0x0004003b,0x0000002c,0x00000092,0x00000007,0x0004003b,0x00000022, + 0x00000099,0x00000007,0x0004003b,0x00000022,0x0000009b,0x00000007,0x0004003d,0x0000000b, + 0x0000000e,0x0000000d,0x0004003d,0x0000000f,0x00000012,0x00000011,0x00050057,0x00000007, + 0x00000013,0x0000000e,0x00000012,0x0003003e,0x00000009,0x00000013,0x0004003d,0x0000000b, + 0x00000016,0x00000015,0x0004003d,0x0000000f,0x00000017,0x00000011,0x00050057,0x00000007, + 0x00000018,0x00000016,0x00000017,0x0003003e,0x00000014,0x00000018,0x000300f7,0x0000001f, + 0x00000000,0x000400fa,0x0000001d,0x0000001e,0x0000001f,0x000200f8,0x0000001e,0x00050041, + 0x00000022,0x00000023,0x00000009,0x00000021,0x0004003d,0x00000006,0x00000024,0x00000023, + 0x000500b8,0x00000019,0x00000026,0x00000024,0x00000025,0x000200f9,0x0000001f,0x000200f8, + 0x0000001f,0x000700f5,0x00000019,0x00000027,0x0000001d,0x00000005,0x00000026,0x0000001e, + 0x000300f7,0x00000029,0x00000000,0x000400fa,0x00000027,0x00000028,0x00000029,0x000200f8, + 0x00000028,0x000100fc,0x000200f8,0x00000029,0x000300f7,0x00000032,0x00000000,0x000400fa, + 0x0000002f,0x00000031,0x00000041,0x000200f8,0x00000031,0x0004003d,0x00000007,0x00000035, + 0x00000034,0x0008004f,0x0000002b,0x00000036,0x00000035,0x00000035,0x00000000,0x00000001, + 0x00000002,0x00050041,0x0000003b,0x0000003c,0x00000039,0x0000003a,0x0004003d,0x00000007, + 0x0000003d,0x0000003c,0x0008004f,0x0000002b,0x0000003e,0x0000003d,0x0000003d,0x00000000, + 0x00000001,0x00000002,0x00050083,0x0000002b,0x0000003f,0x00000036,0x0000003e,0x0006000c, + 0x0000002b,0x00000040,0x00000001,0x00000045,0x0000003f,0x0003003e,0x00000030,0x00000040, + 0x000200f9,0x00000032,0x000200f8,0x00000041,0x00050041,0x0000003b,0x00000042,0x00000039, + 0x0000003a,0x0004003d,0x00000007,0x00000043,0x00000042,0x0008004f,0x0000002b,0x00000044, + 0x00000043,0x00000043,0x00000000,0x00000001,0x00000002,0x0003003e,0x00000030,0x00000044, + 0x000200f9,0x00000032,0x000200f8,0x00000032,0x0004003d,0x0000002b,0x00000045,0x00000030, + 0x0003003e,0x0000002d,0x00000045,0x00050041,0x0000003b,0x00000048,0x00000039,0x00000047, + 0x0004003d,0x00000007,0x00000049,0x00000048,0x0008004f,0x0000002b,0x0000004a,0x00000049, + 0x00000049,0x00000000,0x00000001,0x00000002,0x0004003d,0x00000007,0x0000004b,0x00000009, + 0x0008004f,0x0000002b,0x0000004c,0x0000004b,0x0000004b,0x00000000,0x00000001,0x00000002, + 0x00050041,0x00000022,0x0000004d,0x00000009,0x00000021,0x0004003d,0x00000006,0x0000004e, + 0x0000004d,0x00060050,0x0000002b,0x0000004f,0x0000004e,0x0000004e,0x0000004e,0x0008000c, + 0x0000002b,0x00000050,0x00000001,0x0000002e,0x0000004a,0x0000004c,0x0000004f,0x0003003e, + 0x00000046,0x00000050,0x00050041,0x0000003b,0x00000053,0x00000039,0x00000052,0x0004003d, + 0x00000007,0x00000054,0x00000053,0x0008004f,0x0000002b,0x00000055,0x00000054,0x00000054, + 0x00000000,0x00000001,0x00000002,0x0004003d,0x00000007,0x00000056,0x00000014,0x0008004f, + 0x0000002b,0x00000057,0x00000056,0x00000056,0x00000000,0x00000001,0x00000002,0x00050041, + 0x00000022,0x00000058,0x00000014,0x00000021,0x0004003d,0x00000006,0x00000059,0x00000058, + 0x00060050,0x0000002b,0x0000005a,0x00000059,0x00000059,0x00000059,0x0008000c,0x0000002b, + 0x0000005b,0x00000001,0x0000002e,0x00000055,0x00000057,0x0000005a,0x0003003e,0x00000051, + 0x0000005b,0x0004003d,0x00000007,0x0000005e,0x0000005d,0x0008004f,0x0000002b,0x0000005f, + 0x0000005e,0x0000005e,0x00000000,0x00000001,0x00000002,0x0006000c,0x0000002b,0x00000060, + 0x00000001,0x00000045,0x0000005f,0x0003003e,0x0000005c,0x00000060,0x0004003d,0x0000002b, + 0x00000062,0x0000002d,0x0004003d,0x0000002b,0x00000063,0x0000005c,0x0004007f,0x0000002b, + 0x00000064,0x00000063,0x00050094,0x00000006,0x00000065,0x00000062,0x00000064,0x0003003e, + 0x00000061,0x00000065,0x0004003d,0x0000002b,0x00000067,0x00000046,0x0004003d,0x00000006, + 0x00000068,0x00000061,0x0008000c,0x00000006,0x0000006b,0x00000001,0x0000002b,0x00000068, + 0x00000069,0x0000006a,0x0005008e,0x0000002b,0x0000006c,0x00000067,0x0000006b,0x0003003e, + 0x00000066,0x0000006c,0x0004003d,0x0000002b,0x0000006e,0x00000051,0x00050041,0x0000003b, + 0x0000006f,0x00000039,0x0000001c,0x0004003d,0x00000007,0x00000070,0x0000006f,0x0008004f, + 0x0000002b,0x00000071,0x00000070,0x00000070,0x00000000,0x00000001,0x00000002,0x00050085, + 0x0000002b,0x00000072,0x0000006e,0x00000071,0x0003003e,0x0000006d,0x00000072,0x000300f7, + 0x00000077,0x00000000,0x000400fa,0x00000074,0x00000076,0x0000007f,0x000200f8,0x00000076, + 0x00050041,0x0000003b,0x00000078,0x00000039,0x0000002e,0x0004003d,0x00000007,0x00000079, + 0x00000078,0x0008004f,0x0000002b,0x0000007a,0x00000079,0x00000079,0x00000000,0x00000001, + 0x00000002,0x0004003d,0x00000007,0x0000007b,0x00000034,0x0008004f,0x0000002b,0x0000007c, + 0x0000007b,0x0000007b,0x00000000,0x00000001,0x00000002,0x00050083,0x0000002b,0x0000007d, + 0x0000007a,0x0000007c,0x0006000c,0x0000002b,0x0000007e,0x00000001,0x00000045,0x0000007d, + 0x0003003e,0x00000075,0x0000007e,0x000200f9,0x00000077,0x000200f8,0x0000007f,0x0004003d, + 0x00000007,0x00000080,0x00000034,0x0008004f,0x0000002b,0x00000081,0x00000080,0x00000080, + 0x00000000,0x00000001,0x00000002,0x0004007f,0x0000002b,0x00000082,0x00000081,0x0006000c, + 0x0000002b,0x00000083,0x00000001,0x00000045,0x00000082,0x0003003e,0x00000075,0x00000083, + 0x000200f9,0x00000077,0x000200f8,0x00000077,0x0004003d,0x0000002b,0x00000084,0x00000075, + 0x0003003e,0x00000073,0x00000084,0x0004003d,0x0000002b,0x00000086,0x00000073,0x0004003d, + 0x0000002b,0x00000087,0x0000002d,0x0004003d,0x0000002b,0x00000088,0x0000005c,0x0007000c, + 0x0000002b,0x00000089,0x00000001,0x00000047,0x00000087,0x00000088,0x00050094,0x00000006, + 0x0000008a,0x00000086,0x00000089,0x0008000c,0x00000006,0x0000008b,0x00000001,0x0000002b, + 0x0000008a,0x00000069,0x0000006a,0x00060041,0x0000008e,0x0000008f,0x00000039,0x0000008c, + 0x0000008d,0x0004003d,0x00000006,0x00000090,0x0000008f,0x0007000c,0x00000006,0x00000091, + 0x00000001,0x0000001a,0x0000008b,0x00000090,0x0003003e,0x00000085,0x00000091,0x00050041, + 0x0000003b,0x00000094,0x00000039,0x00000093,0x0004003d,0x00000007,0x00000095,0x00000094, + 0x0008004f,0x0000002b,0x00000096,0x00000095,0x00000095,0x00000000,0x00000001,0x00000002, + 0x0004003d,0x00000006,0x00000097,0x00000085,0x0005008e,0x0000002b,0x00000098,0x00000096, + 0x00000097,0x0003003e,0x00000092,0x00000098,0x000300f7,0x0000009d,0x00000000,0x000400fa, + 0x0000009a,0x0000009c,0x000000a1,0x000200f8,0x0000009c,0x00060041,0x0000008e,0x0000009f, + 0x00000039,0x0000008c,0x0000009e,0x0004003d,0x00000006,0x000000a0,0x0000009f,0x0003003e, + 0x0000009b,0x000000a0,0x000200f9,0x0000009d,0x000200f8,0x000000a1,0x00060041,0x0000008e, + 0x000000a2,0x00000039,0x0000008c,0x0000009e,0x0004003d,0x00000006,0x000000a3,0x000000a2, + 0x00050041,0x00000022,0x000000a4,0x00000009,0x00000021,0x0004003d,0x00000006,0x000000a5, + 0x000000a4,0x00050085,0x00000006,0x000000a6,0x000000a3,0x000000a5,0x0003003e,0x0000009b, + 0x000000a6,0x000200f9,0x0000009d,0x000200f8,0x0000009d,0x0004003d,0x00000006,0x000000a7, + 0x0000009b,0x0003003e,0x00000099,0x000000a7,0x0004003d,0x0000002b,0x000000aa,0x00000066, + 0x0004003d,0x0000002b,0x000000ab,0x0000006d,0x00050081,0x0000002b,0x000000ac,0x000000aa, + 0x000000ab,0x0004003d,0x0000002b,0x000000ad,0x00000092,0x00050081,0x0000002b,0x000000ae, + 0x000000ac,0x000000ad,0x0004003d,0x00000006,0x000000af,0x00000099,0x00050051,0x00000006, + 0x000000b0,0x000000ae,0x00000000,0x00050051,0x00000006,0x000000b1,0x000000ae,0x00000001, + 0x00050051,0x00000006,0x000000b2,0x000000ae,0x00000002,0x00070050,0x00000007,0x000000b3, + 0x000000b0,0x000000b1,0x000000b2,0x000000af,0x0003003e,0x000000a9,0x000000b3,0x000100fd, + 0x00010038 +}; diff --git a/jni/Android.mk b/jni/Android.mk index b8ea4b5..ee82db3 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -3,13 +3,14 @@ LOCAL_PATH := $(call my-dir) CORE_DIR := $(LOCAL_PATH)/.. GLES := 1 +HAVE_VULKAN ?= 1 #The NDK's libc++ has no ; include/compat carries the same shim the #Apple and Emscripten builds use, and shared.hpp aliases std1 to it. INCFLAGS := -I$(CORE_DIR)/include/compat include $(CORE_DIR)/Makefile.common -COREFLAGS := -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DANDROID -DHAVE_RJPEG -DHAVE_RTGA -DHAVE_RBMP -DHAVE_RPNG -DINLINE="inline" $(INCFLAGS) +COREFLAGS := -DHAVE_OPENGLES -DHAVE_OPENGLES2 -DHAVE_VULKAN -DANDROID -DHAVE_RJPEG -DHAVE_RTGA -DHAVE_RBMP -DHAVE_RPNG -DINLINE="inline" $(INCFLAGS) GIT_VERSION := " $(shell git rev-parse --short HEAD || echo unknown)" ifneq ($(GIT_VERSION)," unknown") diff --git a/libretro-common/include/libretro_vulkan.h b/libretro-common/include/libretro_vulkan.h new file mode 100644 index 0000000..9d21da7 --- /dev/null +++ b/libretro-common/include/libretro_vulkan.h @@ -0,0 +1,505 @@ +/* Copyright (C) 2010-2020 The RetroArch team + * + * --------------------------------------------------------------------------------------------- + * The following license statement only applies to this libretro API header (libretro_vulkan.h) + * --------------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef LIBRETRO_VULKAN_H__ +#define LIBRETRO_VULKAN_H__ + +#include +#include + +#define RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION 5 +#define RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION 2 + +struct retro_vulkan_image +{ + VkImageView image_view; + VkImageLayout image_layout; + VkImageViewCreateInfo create_info; +}; + +typedef void (*retro_vulkan_set_image_t)(void *handle, + const struct retro_vulkan_image *image, + uint32_t num_semaphores, + const VkSemaphore *semaphores, + uint32_t src_queue_family); + +typedef uint32_t (*retro_vulkan_get_sync_index_t)(void *handle); +typedef uint32_t (*retro_vulkan_get_sync_index_mask_t)(void *handle); +typedef void (*retro_vulkan_set_command_buffers_t)(void *handle, + uint32_t num_cmd, + const VkCommandBuffer *cmd); +typedef void (*retro_vulkan_wait_sync_index_t)(void *handle); +typedef void (*retro_vulkan_lock_queue_t)(void *handle); +typedef void (*retro_vulkan_unlock_queue_t)(void *handle); +typedef void (*retro_vulkan_set_signal_semaphore_t)(void *handle, VkSemaphore semaphore); + +typedef const VkApplicationInfo *(*retro_vulkan_get_application_info_t)(void); + +struct retro_vulkan_context +{ + VkPhysicalDevice gpu; + VkDevice device; + VkQueue queue; + uint32_t queue_family_index; + VkQueue presentation_queue; + uint32_t presentation_queue_family_index; +}; + +/* This is only used in v1 of the negotiation interface. + * It is deprecated since it cannot express PDF2 features or optional extensions. */ +typedef bool (*retro_vulkan_create_device_t)( + struct retro_vulkan_context *context, + VkInstance instance, + VkPhysicalDevice gpu, + VkSurfaceKHR surface, + PFN_vkGetInstanceProcAddr get_instance_proc_addr, + const char **required_device_extensions, + unsigned num_required_device_extensions, + const char **required_device_layers, + unsigned num_required_device_layers, + const VkPhysicalDeviceFeatures *required_features); + +typedef void (*retro_vulkan_destroy_device_t)(void); + +/* v2 CONTEXT_NEGOTIATION_INTERFACE only. */ +typedef VkInstance (*retro_vulkan_create_instance_wrapper_t)( + void *opaque, const VkInstanceCreateInfo *create_info); + +/* v2 CONTEXT_NEGOTIATION_INTERFACE only. */ +typedef VkInstance (*retro_vulkan_create_instance_t)( + PFN_vkGetInstanceProcAddr get_instance_proc_addr, + const VkApplicationInfo *app, + retro_vulkan_create_instance_wrapper_t create_instance_wrapper, + void *opaque); + +/* v2 CONTEXT_NEGOTIATION_INTERFACE only. */ +typedef VkDevice (*retro_vulkan_create_device_wrapper_t)( + VkPhysicalDevice gpu, void *opaque, + const VkDeviceCreateInfo *create_info); + +/* v2 CONTEXT_NEGOTIATION_INTERFACE only. */ +typedef bool (*retro_vulkan_create_device2_t)( + struct retro_vulkan_context *context, + VkInstance instance, + VkPhysicalDevice gpu, + VkSurfaceKHR surface, + PFN_vkGetInstanceProcAddr get_instance_proc_addr, + retro_vulkan_create_device_wrapper_t create_device_wrapper, + void *opaque); + +/* Note on thread safety: + * The Vulkan API is heavily designed around multi-threading, and + * the libretro interface for it should also be threading friendly. + * A core should be able to build command buffers and submit + * command buffers to the GPU from any thread. + */ + +struct retro_hw_render_context_negotiation_interface_vulkan +{ + /* Must be set to RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN. */ + enum retro_hw_render_context_negotiation_interface_type interface_type; + /* Usually set to RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_VULKAN_VERSION, + * but can be lower depending on GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT. */ + unsigned interface_version; + + /* If non-NULL, returns a VkApplicationInfo struct that the frontend can use instead of + * its "default" application info. + * VkApplicationInfo::apiVersion also controls the target core Vulkan version for instance level functionality. + * Lifetime of the returned pointer must remain until the retro_vulkan_context is initialized. + * + * NOTE: For optimal compatibility with e.g. Android which is very slow to update its loader, + * a core version of 1.1 should be requested. Features beyond that can be requested with extensions. + * Vulkan 1.0 is only appropriate for legacy cores, but is still supported. + * A frontend is free to bump the instance creation apiVersion as necessary if the frontend requires more advanced core features. + * + * v2: This function must not be NULL, and must not return NULL. + * v1: It was not clearly defined if this function could return NULL. + * Frontends should be defensive and provide a default VkApplicationInfo + * if this function returns NULL or if this function is NULL. + */ + retro_vulkan_get_application_info_t get_application_info; + + /* If non-NULL, the libretro core will choose one or more physical devices, + * create one or more logical devices and create one or more queues. + * The core must prepare a designated PhysicalDevice, Device, Queue and queue family index + * which the frontend will use for its internal operation. + * + * If gpu is not VK_NULL_HANDLE, the physical device provided to the frontend must be this PhysicalDevice if the call succeeds. + * The core is still free to use other physical devices for other purposes that are private to the core. + * + * The frontend will request certain extensions and layers for a device which is created. + * The core must ensure that the queue and queue_family_index support GRAPHICS and COMPUTE. + * + * If surface is not VK_NULL_HANDLE, the core must consider presentation when creating the queues. + * If presentation to "surface" is supported on the queue, presentation_queue must be equal to queue. + * If not, a second queue must be provided in presentation_queue and presentation_queue_index. + * If surface is not VK_NULL_HANDLE, the instance from frontend will have been created with supported for + * VK_KHR_surface extension. + * + * The core is free to set its own queue priorities. + * Device provided to frontend is owned by the frontend, but any additional device resources must be freed by core + * in destroy_device callback. + * + * If this function returns true, a PhysicalDevice, Device and Queues are initialized. + * If false, none of the above have been initialized and the frontend will attempt + * to fallback to "default" device creation, as if this function was never called. + */ + retro_vulkan_create_device_t create_device; + + /* If non-NULL, this callback is called similar to context_destroy for HW_RENDER_INTERFACE. + * However, it will be called even if context_reset was not called. + * This can happen if the context never succeeds in being created. + * destroy_device will always be called before the VkInstance + * of the frontend is destroyed if create_device was called successfully so that the core has a chance of + * tearing down its own device resources. + * + * Only auxiliary resources should be freed here, i.e. resources which are not part of retro_vulkan_context. + * v2: Auxiliary instance resources created during create_instance can also be freed here. + */ + retro_vulkan_destroy_device_t destroy_device; + + /* v2 API: If interface_version is < 2, fields below must be ignored. + * If the frontend does not support interface version 2, the v1 entry points will be used instead. */ + + /* If non-NULL, this is called to create an instance, otherwise a VkInstance is created by the frontend. + * v1 interface bug: The only way to enable instance features is through core versions signalled in VkApplicationInfo. + * The frontend may request that certain extensions and layers + * are enabled on the VkInstance. Application may add additional features. + * If app is non-NULL, apiVersion controls the minimum core version required by the application. + * Return a VkInstance or VK_NULL_HANDLE. The VkInstance is owned by the frontend. + * + * Rather than call vkCreateInstance directly, a core must call the CreateInstance wrapper provided with: + * VkInstance instance = create_instance_wrapper(opaque, &create_info); + * If the core wishes to create a private instance for whatever reason (relying on shared memory for example), + * it may call vkCreateInstance directly. */ + retro_vulkan_create_instance_t create_instance; + + /* If non-NULL and frontend recognizes negotiation interface >= 2, create_device2 takes precedence over create_device. + * Similar to create_device, but is extended to better understand new core versions and PDF2 feature enablement. + * Requirements for create_device2 are the same as create_device unless a difference is mentioned. + * + * v2 consideration: + * If the chosen gpu by frontend cannot be supported, a core must return false. + * + * NOTE: "Cannot be supported" is intentionally vaguely defined. + * Refusing to run on an iGPU for a very intensive core with desktop GPU as a minimum spec may be in the gray area. + * Not supporting optional features is not a good reason to reject a physical device, however. + * + * On device creation feature with explicit gpu, a frontend should fall back create_device2 with gpu == VK_NULL_HANDLE and let core + * decide on a supported device if possible. + * + * A core must assume that the explicitly provided GPU is the only guaranteed attempt it has to create a device. + * A fallback may not be attempted if there are particular reasons why only a specific physical device can work, + * but these situations should be esoteric and rare in nature, e.g. a libretro frontend is implemented with external memory + * and only LUID matching would work. + * Cores and frontends should ensure "best effort" when negotiating like this and appropriate logging is encouraged. + * + * v1 note: In the v1 version of create_device, it was never expected that create_device would fail like this, + * and frontends are not expected to attempt fall backs. + * + * Rather than call vkCreateDevice directly, a core must call the CreateDevice wrapper provided with: + * VkDevice device = create_device_wrapper(gpu, opaque, &create_info); + * If the core wishes to create a private device for whatever reason (relying on shared memory for example), + * it may call vkCreateDevice directly. + * + * This allows the frontend to add additional extensions that it requires as well as adjust the PDF2 pNext as required. + * It is also possible adjust the queue create infos in case the frontend desires to allocate some private queues. + * + * The get_instance_proc_addr provided in create_device2 must be the same as create_instance. + * + * NOTE: The frontend must not disable features requested by application. + * NOTE: The frontend must not add any robustness features as some API behavior may change (VK_EXT_descriptor_buffer comes to mind). + * I.e. robustBufferAccess and the like. (nullDescriptor from robustness2 is allowed to be enabled). + */ + retro_vulkan_create_device2_t create_device2; +}; + +struct retro_hw_render_interface_vulkan +{ + /* Must be set to RETRO_HW_RENDER_INTERFACE_VULKAN. */ + enum retro_hw_render_interface_type interface_type; + /* Must be set to RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION. */ + unsigned interface_version; + + /* Opaque handle to the Vulkan backend in the frontend + * which must be passed along to all function pointers + * in this interface. + * + * The rationale for including a handle here (which libretro v1 + * doesn't currently do in general) is: + * + * - Vulkan cores should be able to be freely threaded without lots of fuzz. + * This would break frontends which currently rely on TLS + * to deal with multiple cores loaded at the same time. + * - Fixing this in general is TODO for an eventual libretro v2. + */ + void *handle; + + /* The Vulkan instance the context is using. */ + VkInstance instance; + /* The physical device used. */ + VkPhysicalDevice gpu; + /* The logical device used. */ + VkDevice device; + + /* Allows a core to fetch all its needed symbols without having to link + * against the loader itself. */ + PFN_vkGetDeviceProcAddr get_device_proc_addr; + PFN_vkGetInstanceProcAddr get_instance_proc_addr; + + /* The queue the core must use to submit data. + * This queue and index must remain constant throughout the lifetime + * of the context. + * + * This queue will be the queue that supports graphics and compute + * if the device supports compute. + */ + VkQueue queue; + unsigned queue_index; + + /* Before calling retro_video_refresh_t with RETRO_HW_FRAME_BUFFER_VALID, + * set which image to use for this frame. + * + * If num_semaphores is non-zero, the frontend will wait for the + * semaphores provided to be signaled before using the results further + * in the pipeline. + * + * Semaphores provided by a single call to set_image will only be + * waited for once (waiting for a semaphore resets it). + * E.g. set_image, video_refresh, and then another + * video_refresh without set_image, + * but same image will only wait for semaphores once. + * + * For this reason, ownership transfer will only occur if semaphores + * are waited on for a particular frame in the frontend. + * + * Using semaphores is optional for synchronization purposes, + * but if not using + * semaphores, an image memory barrier in vkCmdPipelineBarrier + * should be used in the graphics_queue. + * Example: + * + * vkCmdPipelineBarrier(cmd, + * srcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + * dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + * image_memory_barrier = { + * srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + * dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + * }); + * + * The use of pipeline barriers instead of semaphores is encouraged + * as it is simpler and more fine-grained. A layout transition + * must generally happen anyways which requires a + * pipeline barrier. + * + * The image passed to set_image must have imageUsage flags set to at least + * VK_IMAGE_USAGE_TRANSFER_SRC_BIT and VK_IMAGE_USAGE_SAMPLED_BIT. + * The core will naturally want to use flags such as + * VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT and/or + * VK_IMAGE_USAGE_TRANSFER_DST_BIT depending + * on how the final image is created. + * + * The image must also have been created with MUTABLE_FORMAT bit set if + * 8-bit formats are used, so that the frontend can reinterpret sRGB + * formats as it sees fit. + * + * Images passed to set_image should be created with TILING_OPTIMAL. + * The image layout should be transitioned to either + * VK_IMAGE_LAYOUT_GENERIC or VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. + * The actual image layout used must be set in image_layout. + * + * The image must be a 2D texture which may or not be layered + * and/or mipmapped. + * + * The image must be suitable for linear sampling. + * While the image_view is typically the only field used, + * the frontend may want to reinterpret the texture as sRGB vs. + * non-sRGB for example so the VkImageViewCreateInfo used to + * create the image view must also be passed in. + * + * The data in the pointer to the image struct will not be copied + * as the pNext field in create_info cannot be reliably deep-copied. + * The image pointer passed to set_image must be valid until + * retro_video_refresh_t has returned. + * + * If frame duping is used when passing NULL to retro_video_refresh_t, + * the frontend is free to either use the latest image passed to + * set_image or reuse the older pointer passed to set_image the + * frame RETRO_HW_FRAME_BUFFER_VALID was last used. + * + * Essentially, the lifetime of the pointer passed to + * retro_video_refresh_t should be extended if frame duping is used + * so that the frontend can reuse the older pointer. + * + * The image itself however, must not be touched by the core until + * wait_sync_index has been completed later - and that includes + * destroying it, or releasing the last reference that keeps it + * alive. The frontend reads the image on its own schedule: on a + * threaded video path that is a frame or more after set_image, + * under fast-forward many frames after, so an image whose lifetime + * is only "until the next set_image" is read after it is gone - a + * GPU page fault and a lost device. The core keeps every image it + * hands over until wait_sync_index for the sync index it was handed + * at has returned: one image per set bit of get_sync_index_mask + * suffices. + * + * The frontend may perform + * layout transitions on the image, so even read-only access is not defined. + * The exception to read-only rule is if GENERAL layout is used for the image. + * In this case, the frontend is not allowed to perform any layout transitions, + * so concurrent reads from core and frontend are allowed. + * + * If frame duping is used, or if set_command_buffers is used, + * the frontend will not wait for any semaphores. + * + * The src_queue_family is used to specify which queue family + * the image is currently owned by. If using multiple queue families + * (e.g. async compute), the frontend will need to acquire ownership of the + * image before rendering with it and release the image afterwards. + * + * If src_queue_family is equal to the queue family (queue_index), + * no ownership transfer will occur. + * Similarly, if src_queue_family is VK_QUEUE_FAMILY_IGNORED, + * no ownership transfer will occur. + * + * The frontend will always release ownership back to src_queue_family. + * Waiting for frontend to complete with wait_sync_index() ensures that + * the frontend has released ownership back to the application. + * Note that in Vulkan, transferring ownership is a two-part process. + * + * Example frame: + * - core releases ownership from src_queue_index to queue_index with VkImageMemoryBarrier. + * - core calls set_image with src_queue_index. + * - Frontend will acquire the image with src_queue_index -> queue_index as well, completing the ownership transfer. + * - Frontend renders the frame. + * - Frontend releases ownership with queue_index -> src_queue_index. + * - Next time image is used, core must acquire ownership from queue_index ... + * + * Since the frontend releases ownership, we cannot necessarily dupe the frame because + * the core needs to make the roundtrip of ownership transfer. + */ + retro_vulkan_set_image_t set_image; + + /* Get the current sync index for this frame which is obtained in + * frontend by calling e.g. vkAcquireNextImageKHR before calling + * retro_run(). + * + * This index will correspond to which swapchain buffer is currently + * the active one. + * + * Knowing this index is very useful for maintaining safe asynchronous CPU + * and GPU operation without stalling. + * + * The common pattern for synchronization is to receive fences when + * submitting command buffers to Vulkan (vkQueueSubmit) and add this fence + * to a list of fences for frame number get_sync_index(). + * + * Next time we receive the same get_sync_index(), we can wait for the + * fences from before, which will usually return immediately as the + * frontend will generally also avoid letting the GPU run ahead too much. + * + * After the fence has signaled, we know that the GPU has completed all + * GPU work related to work submitted in the frame we last saw get_sync_index(). + * + * This means we can safely reuse or free resources allocated in this frame. + * + * In theory, even if we wait for the fences correctly, it is not technically + * safe to write to the image we earlier passed to the frontend since we're + * not waiting for the frontend GPU jobs to complete. + * + * The frontend will guarantee that the appropriate pipeline barrier + * in graphics_queue has been used such that + * VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT cannot + * start until the frontend is done with the image. + */ + retro_vulkan_get_sync_index_t get_sync_index; + + /* Returns a bitmask of how many swapchain images we currently have + * in the frontend. + * + * If bit #N is set in the return value, get_sync_index can return N. + * Knowing this value is useful for preallocating per-frame management + * structures ahead of time. + * + * While this value will typically remain constant throughout the + * applications lifecycle, it may for example change if the frontend + * suddenly changes fullscreen state and/or latency. + * + * If this value ever changes, it is safe to assume that the device + * is completely idle and all synchronization objects can be deleted + * right away as desired. + */ + retro_vulkan_get_sync_index_mask_t get_sync_index_mask; + + /* Instead of submitting the command buffer to the queue first, the core + * can pass along its command buffer to the frontend, and the frontend + * will submit the command buffer together with the frontends command buffers. + * + * This has the advantage that the overhead of vkQueueSubmit can be + * amortized into a single call. For this mode, semaphores in set_image + * will be ignored, so vkCmdPipelineBarrier must be used to synchronize + * the core and frontend. + * + * The command buffers in set_command_buffers are only executed once, + * even if frame duping is used. + * + * If frame duping is used, set_image should be used for the frames + * which should be duped instead. + * + * Command buffers passed to the frontend with set_command_buffers + * must not actually be submitted to the GPU until retro_video_refresh_t + * is called. + * + * The frontend must submit the command buffer before submitting any + * other command buffers provided by set_command_buffers. */ + retro_vulkan_set_command_buffers_t set_command_buffers; + + /* Waits on CPU for device activity for the current sync index to complete. + * This is useful since the core will not have a relevant fence to sync with + * when the frontend is submitting the command buffers. */ + retro_vulkan_wait_sync_index_t wait_sync_index; + + /* If the core submits command buffers itself to any of the queues provided + * in this interface, the core must lock and unlock the frontend from + * racing on the VkQueue. + * + * Queue submission can happen on any thread. + * Even if queue submission happens on the same thread as retro_run(), + * the lock/unlock functions must still be called. + * + * NOTE: Queue submissions are heavy-weight. */ + retro_vulkan_lock_queue_t lock_queue; + retro_vulkan_unlock_queue_t unlock_queue; + + /* Sets a semaphore which is signaled when the image in set_image can safely be reused. + * The semaphore is consumed next call to retro_video_refresh_t. + * The semaphore will be signalled even for duped frames. + * The semaphore will be signalled only once, so set_signal_semaphore should be called every frame. + * The semaphore may be VK_NULL_HANDLE, which disables semaphore signalling for next call to retro_video_refresh_t. + * + * This is mostly useful to support use cases where you're rendering to a single image that + * is recycled in a ping-pong fashion with the frontend to save memory (but potentially less throughput). + */ + retro_vulkan_set_signal_semaphore_t set_signal_semaphore; +}; + +#endif diff --git a/libretro-common/include/vulkan/vulkan_symbol_wrapper.h b/libretro-common/include/vulkan/vulkan_symbol_wrapper.h new file mode 100644 index 0000000..4df6e88 --- /dev/null +++ b/libretro-common/include/vulkan/vulkan_symbol_wrapper.h @@ -0,0 +1,347 @@ + +/* This header is autogenerated by vulkan_loader_generator.py */ +#ifndef VULKAN_SYMBOL_WRAPPER_H +#define VULKAN_SYMBOL_WRAPPER_H +#define VK_NO_PROTOTYPES +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern PFN_vkCreateInstance vulkan_symbol_wrapper_vkCreateInstance; +#define vkCreateInstance vulkan_symbol_wrapper_vkCreateInstance +extern PFN_vkEnumerateInstanceVersion vulkan_symbol_wrapper_vkEnumerateInstanceVersion; +#define vkEnumerateInstanceVersion vulkan_symbol_wrapper_vkEnumerateInstanceVersion +extern PFN_vkEnumerateInstanceExtensionProperties vulkan_symbol_wrapper_vkEnumerateInstanceExtensionProperties; +#define vkEnumerateInstanceExtensionProperties vulkan_symbol_wrapper_vkEnumerateInstanceExtensionProperties +extern PFN_vkEnumerateInstanceLayerProperties vulkan_symbol_wrapper_vkEnumerateInstanceLayerProperties; +#define vkEnumerateInstanceLayerProperties vulkan_symbol_wrapper_vkEnumerateInstanceLayerProperties +extern PFN_vkDestroyInstance vulkan_symbol_wrapper_vkDestroyInstance; +#define vkDestroyInstance vulkan_symbol_wrapper_vkDestroyInstance +extern PFN_vkEnumeratePhysicalDevices vulkan_symbol_wrapper_vkEnumeratePhysicalDevices; +#define vkEnumeratePhysicalDevices vulkan_symbol_wrapper_vkEnumeratePhysicalDevices +extern PFN_vkGetPhysicalDeviceFeatures vulkan_symbol_wrapper_vkGetPhysicalDeviceFeatures; +#define vkGetPhysicalDeviceFeatures vulkan_symbol_wrapper_vkGetPhysicalDeviceFeatures +extern PFN_vkGetPhysicalDeviceFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceFormatProperties; +#define vkGetPhysicalDeviceFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceFormatProperties +extern PFN_vkGetPhysicalDeviceImageFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceImageFormatProperties; +#define vkGetPhysicalDeviceImageFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceImageFormatProperties +extern PFN_vkGetPhysicalDeviceProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceProperties; +#define vkGetPhysicalDeviceProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceProperties +extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceQueueFamilyProperties; +#define vkGetPhysicalDeviceQueueFamilyProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceQueueFamilyProperties +extern PFN_vkGetPhysicalDeviceMemoryProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceMemoryProperties; +#define vkGetPhysicalDeviceMemoryProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceMemoryProperties +extern PFN_vkGetDeviceProcAddr vulkan_symbol_wrapper_vkGetDeviceProcAddr; +#define vkGetDeviceProcAddr vulkan_symbol_wrapper_vkGetDeviceProcAddr +extern PFN_vkCreateDevice vulkan_symbol_wrapper_vkCreateDevice; +#define vkCreateDevice vulkan_symbol_wrapper_vkCreateDevice +extern PFN_vkDestroyDevice vulkan_symbol_wrapper_vkDestroyDevice; +#define vkDestroyDevice vulkan_symbol_wrapper_vkDestroyDevice +extern PFN_vkEnumerateDeviceExtensionProperties vulkan_symbol_wrapper_vkEnumerateDeviceExtensionProperties; +#define vkEnumerateDeviceExtensionProperties vulkan_symbol_wrapper_vkEnumerateDeviceExtensionProperties +extern PFN_vkEnumerateDeviceLayerProperties vulkan_symbol_wrapper_vkEnumerateDeviceLayerProperties; +#define vkEnumerateDeviceLayerProperties vulkan_symbol_wrapper_vkEnumerateDeviceLayerProperties +extern PFN_vkGetDeviceQueue vulkan_symbol_wrapper_vkGetDeviceQueue; +#define vkGetDeviceQueue vulkan_symbol_wrapper_vkGetDeviceQueue +extern PFN_vkQueueSubmit vulkan_symbol_wrapper_vkQueueSubmit; +#define vkQueueSubmit vulkan_symbol_wrapper_vkQueueSubmit +extern PFN_vkQueueWaitIdle vulkan_symbol_wrapper_vkQueueWaitIdle; +#define vkQueueWaitIdle vulkan_symbol_wrapper_vkQueueWaitIdle +extern PFN_vkDeviceWaitIdle vulkan_symbol_wrapper_vkDeviceWaitIdle; +#define vkDeviceWaitIdle vulkan_symbol_wrapper_vkDeviceWaitIdle +extern PFN_vkAllocateMemory vulkan_symbol_wrapper_vkAllocateMemory; +#define vkAllocateMemory vulkan_symbol_wrapper_vkAllocateMemory +extern PFN_vkFreeMemory vulkan_symbol_wrapper_vkFreeMemory; +#define vkFreeMemory vulkan_symbol_wrapper_vkFreeMemory +extern PFN_vkMapMemory vulkan_symbol_wrapper_vkMapMemory; +#define vkMapMemory vulkan_symbol_wrapper_vkMapMemory +extern PFN_vkUnmapMemory vulkan_symbol_wrapper_vkUnmapMemory; +#define vkUnmapMemory vulkan_symbol_wrapper_vkUnmapMemory +extern PFN_vkFlushMappedMemoryRanges vulkan_symbol_wrapper_vkFlushMappedMemoryRanges; +#define vkFlushMappedMemoryRanges vulkan_symbol_wrapper_vkFlushMappedMemoryRanges +extern PFN_vkInvalidateMappedMemoryRanges vulkan_symbol_wrapper_vkInvalidateMappedMemoryRanges; +#define vkInvalidateMappedMemoryRanges vulkan_symbol_wrapper_vkInvalidateMappedMemoryRanges +extern PFN_vkGetDeviceMemoryCommitment vulkan_symbol_wrapper_vkGetDeviceMemoryCommitment; +#define vkGetDeviceMemoryCommitment vulkan_symbol_wrapper_vkGetDeviceMemoryCommitment +extern PFN_vkBindBufferMemory vulkan_symbol_wrapper_vkBindBufferMemory; +#define vkBindBufferMemory vulkan_symbol_wrapper_vkBindBufferMemory +extern PFN_vkBindImageMemory vulkan_symbol_wrapper_vkBindImageMemory; +#define vkBindImageMemory vulkan_symbol_wrapper_vkBindImageMemory +extern PFN_vkGetBufferMemoryRequirements vulkan_symbol_wrapper_vkGetBufferMemoryRequirements; +#define vkGetBufferMemoryRequirements vulkan_symbol_wrapper_vkGetBufferMemoryRequirements +extern PFN_vkGetImageMemoryRequirements vulkan_symbol_wrapper_vkGetImageMemoryRequirements; +#define vkGetImageMemoryRequirements vulkan_symbol_wrapper_vkGetImageMemoryRequirements +extern PFN_vkGetImageSparseMemoryRequirements vulkan_symbol_wrapper_vkGetImageSparseMemoryRequirements; +#define vkGetImageSparseMemoryRequirements vulkan_symbol_wrapper_vkGetImageSparseMemoryRequirements +extern PFN_vkGetPhysicalDeviceSparseImageFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceSparseImageFormatProperties; +#define vkGetPhysicalDeviceSparseImageFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceSparseImageFormatProperties +extern PFN_vkQueueBindSparse vulkan_symbol_wrapper_vkQueueBindSparse; +#define vkQueueBindSparse vulkan_symbol_wrapper_vkQueueBindSparse +extern PFN_vkCreateFence vulkan_symbol_wrapper_vkCreateFence; +#define vkCreateFence vulkan_symbol_wrapper_vkCreateFence +extern PFN_vkDestroyFence vulkan_symbol_wrapper_vkDestroyFence; +#define vkDestroyFence vulkan_symbol_wrapper_vkDestroyFence +extern PFN_vkResetFences vulkan_symbol_wrapper_vkResetFences; +#define vkResetFences vulkan_symbol_wrapper_vkResetFences +extern PFN_vkGetFenceStatus vulkan_symbol_wrapper_vkGetFenceStatus; +#define vkGetFenceStatus vulkan_symbol_wrapper_vkGetFenceStatus +extern PFN_vkWaitForFences vulkan_symbol_wrapper_vkWaitForFences; +#define vkWaitForFences vulkan_symbol_wrapper_vkWaitForFences +extern PFN_vkCreateSemaphore vulkan_symbol_wrapper_vkCreateSemaphore; +#define vkCreateSemaphore vulkan_symbol_wrapper_vkCreateSemaphore +extern PFN_vkDestroySemaphore vulkan_symbol_wrapper_vkDestroySemaphore; +#define vkDestroySemaphore vulkan_symbol_wrapper_vkDestroySemaphore +extern PFN_vkCreateEvent vulkan_symbol_wrapper_vkCreateEvent; +#define vkCreateEvent vulkan_symbol_wrapper_vkCreateEvent +extern PFN_vkDestroyEvent vulkan_symbol_wrapper_vkDestroyEvent; +#define vkDestroyEvent vulkan_symbol_wrapper_vkDestroyEvent +extern PFN_vkGetEventStatus vulkan_symbol_wrapper_vkGetEventStatus; +#define vkGetEventStatus vulkan_symbol_wrapper_vkGetEventStatus +extern PFN_vkSetEvent vulkan_symbol_wrapper_vkSetEvent; +#define vkSetEvent vulkan_symbol_wrapper_vkSetEvent +extern PFN_vkResetEvent vulkan_symbol_wrapper_vkResetEvent; +#define vkResetEvent vulkan_symbol_wrapper_vkResetEvent +extern PFN_vkCreateQueryPool vulkan_symbol_wrapper_vkCreateQueryPool; +#define vkCreateQueryPool vulkan_symbol_wrapper_vkCreateQueryPool +extern PFN_vkDestroyQueryPool vulkan_symbol_wrapper_vkDestroyQueryPool; +#define vkDestroyQueryPool vulkan_symbol_wrapper_vkDestroyQueryPool +extern PFN_vkGetQueryPoolResults vulkan_symbol_wrapper_vkGetQueryPoolResults; +#define vkGetQueryPoolResults vulkan_symbol_wrapper_vkGetQueryPoolResults +extern PFN_vkCreateBuffer vulkan_symbol_wrapper_vkCreateBuffer; +#define vkCreateBuffer vulkan_symbol_wrapper_vkCreateBuffer +extern PFN_vkDestroyBuffer vulkan_symbol_wrapper_vkDestroyBuffer; +#define vkDestroyBuffer vulkan_symbol_wrapper_vkDestroyBuffer +extern PFN_vkCreateBufferView vulkan_symbol_wrapper_vkCreateBufferView; +#define vkCreateBufferView vulkan_symbol_wrapper_vkCreateBufferView +extern PFN_vkDestroyBufferView vulkan_symbol_wrapper_vkDestroyBufferView; +#define vkDestroyBufferView vulkan_symbol_wrapper_vkDestroyBufferView +extern PFN_vkCreateImage vulkan_symbol_wrapper_vkCreateImage; +#define vkCreateImage vulkan_symbol_wrapper_vkCreateImage +extern PFN_vkDestroyImage vulkan_symbol_wrapper_vkDestroyImage; +#define vkDestroyImage vulkan_symbol_wrapper_vkDestroyImage +extern PFN_vkGetImageSubresourceLayout vulkan_symbol_wrapper_vkGetImageSubresourceLayout; +#define vkGetImageSubresourceLayout vulkan_symbol_wrapper_vkGetImageSubresourceLayout +extern PFN_vkCreateImageView vulkan_symbol_wrapper_vkCreateImageView; +#define vkCreateImageView vulkan_symbol_wrapper_vkCreateImageView +extern PFN_vkDestroyImageView vulkan_symbol_wrapper_vkDestroyImageView; +#define vkDestroyImageView vulkan_symbol_wrapper_vkDestroyImageView +extern PFN_vkCreateShaderModule vulkan_symbol_wrapper_vkCreateShaderModule; +#define vkCreateShaderModule vulkan_symbol_wrapper_vkCreateShaderModule +extern PFN_vkDestroyShaderModule vulkan_symbol_wrapper_vkDestroyShaderModule; +#define vkDestroyShaderModule vulkan_symbol_wrapper_vkDestroyShaderModule +extern PFN_vkCreatePipelineCache vulkan_symbol_wrapper_vkCreatePipelineCache; +#define vkCreatePipelineCache vulkan_symbol_wrapper_vkCreatePipelineCache +extern PFN_vkDestroyPipelineCache vulkan_symbol_wrapper_vkDestroyPipelineCache; +#define vkDestroyPipelineCache vulkan_symbol_wrapper_vkDestroyPipelineCache +extern PFN_vkGetPipelineCacheData vulkan_symbol_wrapper_vkGetPipelineCacheData; +#define vkGetPipelineCacheData vulkan_symbol_wrapper_vkGetPipelineCacheData +extern PFN_vkMergePipelineCaches vulkan_symbol_wrapper_vkMergePipelineCaches; +#define vkMergePipelineCaches vulkan_symbol_wrapper_vkMergePipelineCaches +extern PFN_vkCreateGraphicsPipelines vulkan_symbol_wrapper_vkCreateGraphicsPipelines; +#define vkCreateGraphicsPipelines vulkan_symbol_wrapper_vkCreateGraphicsPipelines +extern PFN_vkCreateComputePipelines vulkan_symbol_wrapper_vkCreateComputePipelines; +#define vkCreateComputePipelines vulkan_symbol_wrapper_vkCreateComputePipelines +extern PFN_vkDestroyPipeline vulkan_symbol_wrapper_vkDestroyPipeline; +#define vkDestroyPipeline vulkan_symbol_wrapper_vkDestroyPipeline +extern PFN_vkCreatePipelineLayout vulkan_symbol_wrapper_vkCreatePipelineLayout; +#define vkCreatePipelineLayout vulkan_symbol_wrapper_vkCreatePipelineLayout +extern PFN_vkDestroyPipelineLayout vulkan_symbol_wrapper_vkDestroyPipelineLayout; +#define vkDestroyPipelineLayout vulkan_symbol_wrapper_vkDestroyPipelineLayout +extern PFN_vkCreateSampler vulkan_symbol_wrapper_vkCreateSampler; +#define vkCreateSampler vulkan_symbol_wrapper_vkCreateSampler +extern PFN_vkDestroySampler vulkan_symbol_wrapper_vkDestroySampler; +#define vkDestroySampler vulkan_symbol_wrapper_vkDestroySampler +extern PFN_vkCreateDescriptorSetLayout vulkan_symbol_wrapper_vkCreateDescriptorSetLayout; +#define vkCreateDescriptorSetLayout vulkan_symbol_wrapper_vkCreateDescriptorSetLayout +extern PFN_vkDestroyDescriptorSetLayout vulkan_symbol_wrapper_vkDestroyDescriptorSetLayout; +#define vkDestroyDescriptorSetLayout vulkan_symbol_wrapper_vkDestroyDescriptorSetLayout +extern PFN_vkCreateDescriptorPool vulkan_symbol_wrapper_vkCreateDescriptorPool; +#define vkCreateDescriptorPool vulkan_symbol_wrapper_vkCreateDescriptorPool +extern PFN_vkDestroyDescriptorPool vulkan_symbol_wrapper_vkDestroyDescriptorPool; +#define vkDestroyDescriptorPool vulkan_symbol_wrapper_vkDestroyDescriptorPool +extern PFN_vkResetDescriptorPool vulkan_symbol_wrapper_vkResetDescriptorPool; +#define vkResetDescriptorPool vulkan_symbol_wrapper_vkResetDescriptorPool +extern PFN_vkAllocateDescriptorSets vulkan_symbol_wrapper_vkAllocateDescriptorSets; +#define vkAllocateDescriptorSets vulkan_symbol_wrapper_vkAllocateDescriptorSets +extern PFN_vkFreeDescriptorSets vulkan_symbol_wrapper_vkFreeDescriptorSets; +#define vkFreeDescriptorSets vulkan_symbol_wrapper_vkFreeDescriptorSets +extern PFN_vkUpdateDescriptorSets vulkan_symbol_wrapper_vkUpdateDescriptorSets; +#define vkUpdateDescriptorSets vulkan_symbol_wrapper_vkUpdateDescriptorSets +extern PFN_vkCreateFramebuffer vulkan_symbol_wrapper_vkCreateFramebuffer; +#define vkCreateFramebuffer vulkan_symbol_wrapper_vkCreateFramebuffer +extern PFN_vkDestroyFramebuffer vulkan_symbol_wrapper_vkDestroyFramebuffer; +#define vkDestroyFramebuffer vulkan_symbol_wrapper_vkDestroyFramebuffer +extern PFN_vkCreateRenderPass vulkan_symbol_wrapper_vkCreateRenderPass; +#define vkCreateRenderPass vulkan_symbol_wrapper_vkCreateRenderPass +extern PFN_vkDestroyRenderPass vulkan_symbol_wrapper_vkDestroyRenderPass; +#define vkDestroyRenderPass vulkan_symbol_wrapper_vkDestroyRenderPass +extern PFN_vkGetRenderAreaGranularity vulkan_symbol_wrapper_vkGetRenderAreaGranularity; +#define vkGetRenderAreaGranularity vulkan_symbol_wrapper_vkGetRenderAreaGranularity +extern PFN_vkCreateCommandPool vulkan_symbol_wrapper_vkCreateCommandPool; +#define vkCreateCommandPool vulkan_symbol_wrapper_vkCreateCommandPool +extern PFN_vkDestroyCommandPool vulkan_symbol_wrapper_vkDestroyCommandPool; +#define vkDestroyCommandPool vulkan_symbol_wrapper_vkDestroyCommandPool +extern PFN_vkResetCommandPool vulkan_symbol_wrapper_vkResetCommandPool; +#define vkResetCommandPool vulkan_symbol_wrapper_vkResetCommandPool +extern PFN_vkAllocateCommandBuffers vulkan_symbol_wrapper_vkAllocateCommandBuffers; +#define vkAllocateCommandBuffers vulkan_symbol_wrapper_vkAllocateCommandBuffers +extern PFN_vkFreeCommandBuffers vulkan_symbol_wrapper_vkFreeCommandBuffers; +#define vkFreeCommandBuffers vulkan_symbol_wrapper_vkFreeCommandBuffers +extern PFN_vkBeginCommandBuffer vulkan_symbol_wrapper_vkBeginCommandBuffer; +#define vkBeginCommandBuffer vulkan_symbol_wrapper_vkBeginCommandBuffer +extern PFN_vkEndCommandBuffer vulkan_symbol_wrapper_vkEndCommandBuffer; +#define vkEndCommandBuffer vulkan_symbol_wrapper_vkEndCommandBuffer +extern PFN_vkResetCommandBuffer vulkan_symbol_wrapper_vkResetCommandBuffer; +#define vkResetCommandBuffer vulkan_symbol_wrapper_vkResetCommandBuffer +extern PFN_vkCmdBindPipeline vulkan_symbol_wrapper_vkCmdBindPipeline; +#define vkCmdBindPipeline vulkan_symbol_wrapper_vkCmdBindPipeline +extern PFN_vkCmdSetViewport vulkan_symbol_wrapper_vkCmdSetViewport; +#define vkCmdSetViewport vulkan_symbol_wrapper_vkCmdSetViewport +extern PFN_vkCmdSetScissor vulkan_symbol_wrapper_vkCmdSetScissor; +#define vkCmdSetScissor vulkan_symbol_wrapper_vkCmdSetScissor +extern PFN_vkCmdSetLineWidth vulkan_symbol_wrapper_vkCmdSetLineWidth; +#define vkCmdSetLineWidth vulkan_symbol_wrapper_vkCmdSetLineWidth +extern PFN_vkCmdSetDepthBias vulkan_symbol_wrapper_vkCmdSetDepthBias; +#define vkCmdSetDepthBias vulkan_symbol_wrapper_vkCmdSetDepthBias +extern PFN_vkCmdSetBlendConstants vulkan_symbol_wrapper_vkCmdSetBlendConstants; +#define vkCmdSetBlendConstants vulkan_symbol_wrapper_vkCmdSetBlendConstants +extern PFN_vkCmdSetDepthBounds vulkan_symbol_wrapper_vkCmdSetDepthBounds; +#define vkCmdSetDepthBounds vulkan_symbol_wrapper_vkCmdSetDepthBounds +extern PFN_vkCmdSetStencilCompareMask vulkan_symbol_wrapper_vkCmdSetStencilCompareMask; +#define vkCmdSetStencilCompareMask vulkan_symbol_wrapper_vkCmdSetStencilCompareMask +extern PFN_vkCmdSetStencilWriteMask vulkan_symbol_wrapper_vkCmdSetStencilWriteMask; +#define vkCmdSetStencilWriteMask vulkan_symbol_wrapper_vkCmdSetStencilWriteMask +extern PFN_vkCmdSetStencilReference vulkan_symbol_wrapper_vkCmdSetStencilReference; +#define vkCmdSetStencilReference vulkan_symbol_wrapper_vkCmdSetStencilReference +extern PFN_vkCmdBindDescriptorSets vulkan_symbol_wrapper_vkCmdBindDescriptorSets; +#define vkCmdBindDescriptorSets vulkan_symbol_wrapper_vkCmdBindDescriptorSets +extern PFN_vkCmdBindIndexBuffer vulkan_symbol_wrapper_vkCmdBindIndexBuffer; +#define vkCmdBindIndexBuffer vulkan_symbol_wrapper_vkCmdBindIndexBuffer +extern PFN_vkCmdBindVertexBuffers vulkan_symbol_wrapper_vkCmdBindVertexBuffers; +#define vkCmdBindVertexBuffers vulkan_symbol_wrapper_vkCmdBindVertexBuffers +extern PFN_vkCmdDraw vulkan_symbol_wrapper_vkCmdDraw; +#define vkCmdDraw vulkan_symbol_wrapper_vkCmdDraw +extern PFN_vkCmdDrawIndexed vulkan_symbol_wrapper_vkCmdDrawIndexed; +#define vkCmdDrawIndexed vulkan_symbol_wrapper_vkCmdDrawIndexed +extern PFN_vkCmdDrawIndirect vulkan_symbol_wrapper_vkCmdDrawIndirect; +#define vkCmdDrawIndirect vulkan_symbol_wrapper_vkCmdDrawIndirect +extern PFN_vkCmdDrawIndexedIndirect vulkan_symbol_wrapper_vkCmdDrawIndexedIndirect; +#define vkCmdDrawIndexedIndirect vulkan_symbol_wrapper_vkCmdDrawIndexedIndirect +extern PFN_vkCmdDispatch vulkan_symbol_wrapper_vkCmdDispatch; +#define vkCmdDispatch vulkan_symbol_wrapper_vkCmdDispatch +extern PFN_vkCmdDispatchIndirect vulkan_symbol_wrapper_vkCmdDispatchIndirect; +#define vkCmdDispatchIndirect vulkan_symbol_wrapper_vkCmdDispatchIndirect +extern PFN_vkCmdCopyBuffer vulkan_symbol_wrapper_vkCmdCopyBuffer; +#define vkCmdCopyBuffer vulkan_symbol_wrapper_vkCmdCopyBuffer +extern PFN_vkCmdCopyImage vulkan_symbol_wrapper_vkCmdCopyImage; +#define vkCmdCopyImage vulkan_symbol_wrapper_vkCmdCopyImage +extern PFN_vkCmdBlitImage vulkan_symbol_wrapper_vkCmdBlitImage; +#define vkCmdBlitImage vulkan_symbol_wrapper_vkCmdBlitImage +extern PFN_vkCmdCopyBufferToImage vulkan_symbol_wrapper_vkCmdCopyBufferToImage; +#define vkCmdCopyBufferToImage vulkan_symbol_wrapper_vkCmdCopyBufferToImage +extern PFN_vkCmdCopyImageToBuffer vulkan_symbol_wrapper_vkCmdCopyImageToBuffer; +#define vkCmdCopyImageToBuffer vulkan_symbol_wrapper_vkCmdCopyImageToBuffer +extern PFN_vkCmdUpdateBuffer vulkan_symbol_wrapper_vkCmdUpdateBuffer; +#define vkCmdUpdateBuffer vulkan_symbol_wrapper_vkCmdUpdateBuffer +extern PFN_vkCmdFillBuffer vulkan_symbol_wrapper_vkCmdFillBuffer; +#define vkCmdFillBuffer vulkan_symbol_wrapper_vkCmdFillBuffer +extern PFN_vkCmdClearColorImage vulkan_symbol_wrapper_vkCmdClearColorImage; +#define vkCmdClearColorImage vulkan_symbol_wrapper_vkCmdClearColorImage +extern PFN_vkCmdClearDepthStencilImage vulkan_symbol_wrapper_vkCmdClearDepthStencilImage; +#define vkCmdClearDepthStencilImage vulkan_symbol_wrapper_vkCmdClearDepthStencilImage +extern PFN_vkCmdClearAttachments vulkan_symbol_wrapper_vkCmdClearAttachments; +#define vkCmdClearAttachments vulkan_symbol_wrapper_vkCmdClearAttachments +extern PFN_vkCmdResolveImage vulkan_symbol_wrapper_vkCmdResolveImage; +#define vkCmdResolveImage vulkan_symbol_wrapper_vkCmdResolveImage +extern PFN_vkCmdSetEvent vulkan_symbol_wrapper_vkCmdSetEvent; +#define vkCmdSetEvent vulkan_symbol_wrapper_vkCmdSetEvent +extern PFN_vkCmdResetEvent vulkan_symbol_wrapper_vkCmdResetEvent; +#define vkCmdResetEvent vulkan_symbol_wrapper_vkCmdResetEvent +extern PFN_vkCmdWaitEvents vulkan_symbol_wrapper_vkCmdWaitEvents; +#define vkCmdWaitEvents vulkan_symbol_wrapper_vkCmdWaitEvents +extern PFN_vkCmdPipelineBarrier vulkan_symbol_wrapper_vkCmdPipelineBarrier; +#define vkCmdPipelineBarrier vulkan_symbol_wrapper_vkCmdPipelineBarrier +extern PFN_vkCmdBeginQuery vulkan_symbol_wrapper_vkCmdBeginQuery; +#define vkCmdBeginQuery vulkan_symbol_wrapper_vkCmdBeginQuery +extern PFN_vkCmdEndQuery vulkan_symbol_wrapper_vkCmdEndQuery; +#define vkCmdEndQuery vulkan_symbol_wrapper_vkCmdEndQuery +extern PFN_vkCmdResetQueryPool vulkan_symbol_wrapper_vkCmdResetQueryPool; +#define vkCmdResetQueryPool vulkan_symbol_wrapper_vkCmdResetQueryPool +extern PFN_vkCmdWriteTimestamp vulkan_symbol_wrapper_vkCmdWriteTimestamp; +#define vkCmdWriteTimestamp vulkan_symbol_wrapper_vkCmdWriteTimestamp +extern PFN_vkCmdCopyQueryPoolResults vulkan_symbol_wrapper_vkCmdCopyQueryPoolResults; +#define vkCmdCopyQueryPoolResults vulkan_symbol_wrapper_vkCmdCopyQueryPoolResults +extern PFN_vkCmdPushConstants vulkan_symbol_wrapper_vkCmdPushConstants; +#define vkCmdPushConstants vulkan_symbol_wrapper_vkCmdPushConstants +extern PFN_vkCmdBeginRenderPass vulkan_symbol_wrapper_vkCmdBeginRenderPass; +#define vkCmdBeginRenderPass vulkan_symbol_wrapper_vkCmdBeginRenderPass +extern PFN_vkCmdNextSubpass vulkan_symbol_wrapper_vkCmdNextSubpass; +#define vkCmdNextSubpass vulkan_symbol_wrapper_vkCmdNextSubpass +extern PFN_vkCmdEndRenderPass vulkan_symbol_wrapper_vkCmdEndRenderPass; +#define vkCmdEndRenderPass vulkan_symbol_wrapper_vkCmdEndRenderPass +extern PFN_vkCmdExecuteCommands vulkan_symbol_wrapper_vkCmdExecuteCommands; +#define vkCmdExecuteCommands vulkan_symbol_wrapper_vkCmdExecuteCommands +extern PFN_vkDestroySurfaceKHR vulkan_symbol_wrapper_vkDestroySurfaceKHR; +#define vkDestroySurfaceKHR vulkan_symbol_wrapper_vkDestroySurfaceKHR +extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceSupportKHR; +#define vkGetPhysicalDeviceSurfaceSupportKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceSupportKHR +extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceCapabilitiesKHR; +#define vkGetPhysicalDeviceSurfaceCapabilitiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceCapabilitiesKHR +extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceFormatsKHR; +#define vkGetPhysicalDeviceSurfaceFormatsKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceFormatsKHR +extern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfacePresentModesKHR; +#define vkGetPhysicalDeviceSurfacePresentModesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfacePresentModesKHR +extern PFN_vkCreateSwapchainKHR vulkan_symbol_wrapper_vkCreateSwapchainKHR; +#define vkCreateSwapchainKHR vulkan_symbol_wrapper_vkCreateSwapchainKHR +extern PFN_vkDestroySwapchainKHR vulkan_symbol_wrapper_vkDestroySwapchainKHR; +#define vkDestroySwapchainKHR vulkan_symbol_wrapper_vkDestroySwapchainKHR +extern PFN_vkGetSwapchainImagesKHR vulkan_symbol_wrapper_vkGetSwapchainImagesKHR; +#define vkGetSwapchainImagesKHR vulkan_symbol_wrapper_vkGetSwapchainImagesKHR +extern PFN_vkAcquireNextImageKHR vulkan_symbol_wrapper_vkAcquireNextImageKHR; +#define vkAcquireNextImageKHR vulkan_symbol_wrapper_vkAcquireNextImageKHR +extern PFN_vkQueuePresentKHR vulkan_symbol_wrapper_vkQueuePresentKHR; +#define vkQueuePresentKHR vulkan_symbol_wrapper_vkQueuePresentKHR +extern PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceDisplayPropertiesKHR; +#define vkGetPhysicalDeviceDisplayPropertiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceDisplayPropertiesKHR +extern PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceDisplayPlanePropertiesKHR; +#define vkGetPhysicalDeviceDisplayPlanePropertiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceDisplayPlanePropertiesKHR +extern PFN_vkGetDisplayPlaneSupportedDisplaysKHR vulkan_symbol_wrapper_vkGetDisplayPlaneSupportedDisplaysKHR; +#define vkGetDisplayPlaneSupportedDisplaysKHR vulkan_symbol_wrapper_vkGetDisplayPlaneSupportedDisplaysKHR +extern PFN_vkGetDisplayModePropertiesKHR vulkan_symbol_wrapper_vkGetDisplayModePropertiesKHR; +#define vkGetDisplayModePropertiesKHR vulkan_symbol_wrapper_vkGetDisplayModePropertiesKHR +extern PFN_vkCreateDisplayModeKHR vulkan_symbol_wrapper_vkCreateDisplayModeKHR; +#define vkCreateDisplayModeKHR vulkan_symbol_wrapper_vkCreateDisplayModeKHR +extern PFN_vkGetDisplayPlaneCapabilitiesKHR vulkan_symbol_wrapper_vkGetDisplayPlaneCapabilitiesKHR; +#define vkGetDisplayPlaneCapabilitiesKHR vulkan_symbol_wrapper_vkGetDisplayPlaneCapabilitiesKHR +extern PFN_vkCreateDisplayPlaneSurfaceKHR vulkan_symbol_wrapper_vkCreateDisplayPlaneSurfaceKHR; +#define vkCreateDisplayPlaneSurfaceKHR vulkan_symbol_wrapper_vkCreateDisplayPlaneSurfaceKHR +extern PFN_vkCreateSharedSwapchainsKHR vulkan_symbol_wrapper_vkCreateSharedSwapchainsKHR; +#define vkCreateSharedSwapchainsKHR vulkan_symbol_wrapper_vkCreateSharedSwapchainsKHR + +extern PFN_vkCreateDebugUtilsMessengerEXT vulkan_symbol_wrapper_vkCreateDebugUtilsMessengerEXT; +#define vkCreateDebugUtilsMessengerEXT vulkan_symbol_wrapper_vkCreateDebugUtilsMessengerEXT +extern PFN_vkDestroyDebugUtilsMessengerEXT vulkan_symbol_wrapper_vkDestroyDebugUtilsMessengerEXT; +#define vkDestroyDebugUtilsMessengerEXT vulkan_symbol_wrapper_vkDestroyDebugUtilsMessengerEXT +extern PFN_vkSetDebugUtilsObjectNameEXT vulkan_symbol_wrapper_vkSetDebugUtilsObjectNameEXT; +#define vkSetDebugUtilsObjectNameEXT vulkan_symbol_wrapper_vkSetDebugUtilsObjectNameEXT + +void vulkan_symbol_wrapper_init(PFN_vkGetInstanceProcAddr get_instance_proc_addr); +PFN_vkGetInstanceProcAddr vulkan_symbol_wrapper_instance_proc_addr(void); +VkBool32 vulkan_symbol_wrapper_load_global_symbols(void); +VkBool32 vulkan_symbol_wrapper_load_core_instance_symbols(VkInstance instance); +VkBool32 vulkan_symbol_wrapper_load_core_symbols(VkInstance instance); +VkBool32 vulkan_symbol_wrapper_load_core_device_symbols(VkDevice device); +VkBool32 vulkan_symbol_wrapper_load_instance_symbol(VkInstance instance, const char *name, PFN_vkVoidFunction *ppSymbol); +VkBool32 vulkan_symbol_wrapper_load_device_symbol(VkDevice device, const char *name, PFN_vkVoidFunction *ppSymbol); + +#define VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, name, pfn) vulkan_symbol_wrapper_load_instance_symbol(instance, name, (PFN_vkVoidFunction*) &(pfn)) +#define VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_EXTENSION_SYMBOL(instance, name) vulkan_symbol_wrapper_load_instance_symbol(instance, #name, (PFN_vkVoidFunction*) & name) +#define VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, name, pfn) vulkan_symbol_wrapper_load_device_symbol(device, name, (PFN_vkVoidFunction*) &(pfn)) +#define VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_EXTENSION_SYMBOL(device, name) vulkan_symbol_wrapper_load_device_symbol(device, #name, (PFN_vkVoidFunction*) & name) + +#ifdef __cplusplus +} +#endif +#endif diff --git a/libretro-common/vulkan/vulkan_symbol_wrapper.c b/libretro-common/vulkan/vulkan_symbol_wrapper.c new file mode 100644 index 0000000..36647ce --- /dev/null +++ b/libretro-common/vulkan/vulkan_symbol_wrapper.c @@ -0,0 +1,479 @@ + +/* This header is autogenerated by vulkan_loader_generator.py */ +#include + +PFN_vkCreateInstance vulkan_symbol_wrapper_vkCreateInstance; +PFN_vkEnumerateInstanceVersion vulkan_symbol_wrapper_vkEnumerateInstanceVersion; +PFN_vkEnumerateInstanceExtensionProperties vulkan_symbol_wrapper_vkEnumerateInstanceExtensionProperties; +PFN_vkEnumerateInstanceLayerProperties vulkan_symbol_wrapper_vkEnumerateInstanceLayerProperties; +PFN_vkDestroyInstance vulkan_symbol_wrapper_vkDestroyInstance; +PFN_vkEnumeratePhysicalDevices vulkan_symbol_wrapper_vkEnumeratePhysicalDevices; +PFN_vkGetPhysicalDeviceFeatures vulkan_symbol_wrapper_vkGetPhysicalDeviceFeatures; +PFN_vkGetPhysicalDeviceFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceFormatProperties; +PFN_vkGetPhysicalDeviceImageFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceImageFormatProperties; +PFN_vkGetPhysicalDeviceProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceProperties; +PFN_vkGetPhysicalDeviceQueueFamilyProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceQueueFamilyProperties; +PFN_vkGetPhysicalDeviceMemoryProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceMemoryProperties; +PFN_vkGetDeviceProcAddr vulkan_symbol_wrapper_vkGetDeviceProcAddr; +PFN_vkCreateDevice vulkan_symbol_wrapper_vkCreateDevice; +PFN_vkDestroyDevice vulkan_symbol_wrapper_vkDestroyDevice; +PFN_vkEnumerateDeviceExtensionProperties vulkan_symbol_wrapper_vkEnumerateDeviceExtensionProperties; +PFN_vkEnumerateDeviceLayerProperties vulkan_symbol_wrapper_vkEnumerateDeviceLayerProperties; +PFN_vkGetDeviceQueue vulkan_symbol_wrapper_vkGetDeviceQueue; +PFN_vkQueueSubmit vulkan_symbol_wrapper_vkQueueSubmit; +PFN_vkQueueWaitIdle vulkan_symbol_wrapper_vkQueueWaitIdle; +PFN_vkDeviceWaitIdle vulkan_symbol_wrapper_vkDeviceWaitIdle; +PFN_vkAllocateMemory vulkan_symbol_wrapper_vkAllocateMemory; +PFN_vkFreeMemory vulkan_symbol_wrapper_vkFreeMemory; +PFN_vkMapMemory vulkan_symbol_wrapper_vkMapMemory; +PFN_vkUnmapMemory vulkan_symbol_wrapper_vkUnmapMemory; +PFN_vkFlushMappedMemoryRanges vulkan_symbol_wrapper_vkFlushMappedMemoryRanges; +PFN_vkInvalidateMappedMemoryRanges vulkan_symbol_wrapper_vkInvalidateMappedMemoryRanges; +PFN_vkGetDeviceMemoryCommitment vulkan_symbol_wrapper_vkGetDeviceMemoryCommitment; +PFN_vkBindBufferMemory vulkan_symbol_wrapper_vkBindBufferMemory; +PFN_vkBindImageMemory vulkan_symbol_wrapper_vkBindImageMemory; +PFN_vkGetBufferMemoryRequirements vulkan_symbol_wrapper_vkGetBufferMemoryRequirements; +PFN_vkGetImageMemoryRequirements vulkan_symbol_wrapper_vkGetImageMemoryRequirements; +PFN_vkGetImageSparseMemoryRequirements vulkan_symbol_wrapper_vkGetImageSparseMemoryRequirements; +PFN_vkGetPhysicalDeviceSparseImageFormatProperties vulkan_symbol_wrapper_vkGetPhysicalDeviceSparseImageFormatProperties; +PFN_vkQueueBindSparse vulkan_symbol_wrapper_vkQueueBindSparse; +PFN_vkCreateFence vulkan_symbol_wrapper_vkCreateFence; +PFN_vkDestroyFence vulkan_symbol_wrapper_vkDestroyFence; +PFN_vkResetFences vulkan_symbol_wrapper_vkResetFences; +PFN_vkGetFenceStatus vulkan_symbol_wrapper_vkGetFenceStatus; +PFN_vkWaitForFences vulkan_symbol_wrapper_vkWaitForFences; +PFN_vkCreateSemaphore vulkan_symbol_wrapper_vkCreateSemaphore; +PFN_vkDestroySemaphore vulkan_symbol_wrapper_vkDestroySemaphore; +PFN_vkCreateEvent vulkan_symbol_wrapper_vkCreateEvent; +PFN_vkDestroyEvent vulkan_symbol_wrapper_vkDestroyEvent; +PFN_vkGetEventStatus vulkan_symbol_wrapper_vkGetEventStatus; +PFN_vkSetEvent vulkan_symbol_wrapper_vkSetEvent; +PFN_vkResetEvent vulkan_symbol_wrapper_vkResetEvent; +PFN_vkCreateQueryPool vulkan_symbol_wrapper_vkCreateQueryPool; +PFN_vkDestroyQueryPool vulkan_symbol_wrapper_vkDestroyQueryPool; +PFN_vkGetQueryPoolResults vulkan_symbol_wrapper_vkGetQueryPoolResults; +PFN_vkCreateBuffer vulkan_symbol_wrapper_vkCreateBuffer; +PFN_vkDestroyBuffer vulkan_symbol_wrapper_vkDestroyBuffer; +PFN_vkCreateBufferView vulkan_symbol_wrapper_vkCreateBufferView; +PFN_vkDestroyBufferView vulkan_symbol_wrapper_vkDestroyBufferView; +PFN_vkCreateImage vulkan_symbol_wrapper_vkCreateImage; +PFN_vkDestroyImage vulkan_symbol_wrapper_vkDestroyImage; +PFN_vkGetImageSubresourceLayout vulkan_symbol_wrapper_vkGetImageSubresourceLayout; +PFN_vkCreateImageView vulkan_symbol_wrapper_vkCreateImageView; +PFN_vkDestroyImageView vulkan_symbol_wrapper_vkDestroyImageView; +PFN_vkCreateShaderModule vulkan_symbol_wrapper_vkCreateShaderModule; +PFN_vkDestroyShaderModule vulkan_symbol_wrapper_vkDestroyShaderModule; +PFN_vkCreatePipelineCache vulkan_symbol_wrapper_vkCreatePipelineCache; +PFN_vkDestroyPipelineCache vulkan_symbol_wrapper_vkDestroyPipelineCache; +PFN_vkGetPipelineCacheData vulkan_symbol_wrapper_vkGetPipelineCacheData; +PFN_vkMergePipelineCaches vulkan_symbol_wrapper_vkMergePipelineCaches; +PFN_vkCreateGraphicsPipelines vulkan_symbol_wrapper_vkCreateGraphicsPipelines; +PFN_vkCreateComputePipelines vulkan_symbol_wrapper_vkCreateComputePipelines; +PFN_vkDestroyPipeline vulkan_symbol_wrapper_vkDestroyPipeline; +PFN_vkCreatePipelineLayout vulkan_symbol_wrapper_vkCreatePipelineLayout; +PFN_vkDestroyPipelineLayout vulkan_symbol_wrapper_vkDestroyPipelineLayout; +PFN_vkCreateSampler vulkan_symbol_wrapper_vkCreateSampler; +PFN_vkDestroySampler vulkan_symbol_wrapper_vkDestroySampler; +PFN_vkCreateDescriptorSetLayout vulkan_symbol_wrapper_vkCreateDescriptorSetLayout; +PFN_vkDestroyDescriptorSetLayout vulkan_symbol_wrapper_vkDestroyDescriptorSetLayout; +PFN_vkCreateDescriptorPool vulkan_symbol_wrapper_vkCreateDescriptorPool; +PFN_vkDestroyDescriptorPool vulkan_symbol_wrapper_vkDestroyDescriptorPool; +PFN_vkResetDescriptorPool vulkan_symbol_wrapper_vkResetDescriptorPool; +PFN_vkAllocateDescriptorSets vulkan_symbol_wrapper_vkAllocateDescriptorSets; +PFN_vkFreeDescriptorSets vulkan_symbol_wrapper_vkFreeDescriptorSets; +PFN_vkUpdateDescriptorSets vulkan_symbol_wrapper_vkUpdateDescriptorSets; +PFN_vkCreateFramebuffer vulkan_symbol_wrapper_vkCreateFramebuffer; +PFN_vkDestroyFramebuffer vulkan_symbol_wrapper_vkDestroyFramebuffer; +PFN_vkCreateRenderPass vulkan_symbol_wrapper_vkCreateRenderPass; +PFN_vkDestroyRenderPass vulkan_symbol_wrapper_vkDestroyRenderPass; +PFN_vkGetRenderAreaGranularity vulkan_symbol_wrapper_vkGetRenderAreaGranularity; +PFN_vkCreateCommandPool vulkan_symbol_wrapper_vkCreateCommandPool; +PFN_vkDestroyCommandPool vulkan_symbol_wrapper_vkDestroyCommandPool; +PFN_vkResetCommandPool vulkan_symbol_wrapper_vkResetCommandPool; +PFN_vkAllocateCommandBuffers vulkan_symbol_wrapper_vkAllocateCommandBuffers; +PFN_vkFreeCommandBuffers vulkan_symbol_wrapper_vkFreeCommandBuffers; +PFN_vkBeginCommandBuffer vulkan_symbol_wrapper_vkBeginCommandBuffer; +PFN_vkEndCommandBuffer vulkan_symbol_wrapper_vkEndCommandBuffer; +PFN_vkResetCommandBuffer vulkan_symbol_wrapper_vkResetCommandBuffer; +PFN_vkCmdBindPipeline vulkan_symbol_wrapper_vkCmdBindPipeline; +PFN_vkCmdSetViewport vulkan_symbol_wrapper_vkCmdSetViewport; +PFN_vkCmdSetScissor vulkan_symbol_wrapper_vkCmdSetScissor; +PFN_vkCmdSetLineWidth vulkan_symbol_wrapper_vkCmdSetLineWidth; +PFN_vkCmdSetDepthBias vulkan_symbol_wrapper_vkCmdSetDepthBias; +PFN_vkCmdSetBlendConstants vulkan_symbol_wrapper_vkCmdSetBlendConstants; +PFN_vkCmdSetDepthBounds vulkan_symbol_wrapper_vkCmdSetDepthBounds; +PFN_vkCmdSetStencilCompareMask vulkan_symbol_wrapper_vkCmdSetStencilCompareMask; +PFN_vkCmdSetStencilWriteMask vulkan_symbol_wrapper_vkCmdSetStencilWriteMask; +PFN_vkCmdSetStencilReference vulkan_symbol_wrapper_vkCmdSetStencilReference; +PFN_vkCmdBindDescriptorSets vulkan_symbol_wrapper_vkCmdBindDescriptorSets; +PFN_vkCmdBindIndexBuffer vulkan_symbol_wrapper_vkCmdBindIndexBuffer; +PFN_vkCmdBindVertexBuffers vulkan_symbol_wrapper_vkCmdBindVertexBuffers; +PFN_vkCmdDraw vulkan_symbol_wrapper_vkCmdDraw; +PFN_vkCmdDrawIndexed vulkan_symbol_wrapper_vkCmdDrawIndexed; +PFN_vkCmdDrawIndirect vulkan_symbol_wrapper_vkCmdDrawIndirect; +PFN_vkCmdDrawIndexedIndirect vulkan_symbol_wrapper_vkCmdDrawIndexedIndirect; +PFN_vkCmdDispatch vulkan_symbol_wrapper_vkCmdDispatch; +PFN_vkCmdDispatchIndirect vulkan_symbol_wrapper_vkCmdDispatchIndirect; +PFN_vkCmdCopyBuffer vulkan_symbol_wrapper_vkCmdCopyBuffer; +PFN_vkCmdCopyImage vulkan_symbol_wrapper_vkCmdCopyImage; +PFN_vkCmdBlitImage vulkan_symbol_wrapper_vkCmdBlitImage; +PFN_vkCmdCopyBufferToImage vulkan_symbol_wrapper_vkCmdCopyBufferToImage; +PFN_vkCmdCopyImageToBuffer vulkan_symbol_wrapper_vkCmdCopyImageToBuffer; +PFN_vkCmdUpdateBuffer vulkan_symbol_wrapper_vkCmdUpdateBuffer; +PFN_vkCmdFillBuffer vulkan_symbol_wrapper_vkCmdFillBuffer; +PFN_vkCmdClearColorImage vulkan_symbol_wrapper_vkCmdClearColorImage; +PFN_vkCmdClearDepthStencilImage vulkan_symbol_wrapper_vkCmdClearDepthStencilImage; +PFN_vkCmdClearAttachments vulkan_symbol_wrapper_vkCmdClearAttachments; +PFN_vkCmdResolveImage vulkan_symbol_wrapper_vkCmdResolveImage; +PFN_vkCmdSetEvent vulkan_symbol_wrapper_vkCmdSetEvent; +PFN_vkCmdResetEvent vulkan_symbol_wrapper_vkCmdResetEvent; +PFN_vkCmdWaitEvents vulkan_symbol_wrapper_vkCmdWaitEvents; +PFN_vkCmdPipelineBarrier vulkan_symbol_wrapper_vkCmdPipelineBarrier; +PFN_vkCmdBeginQuery vulkan_symbol_wrapper_vkCmdBeginQuery; +PFN_vkCmdEndQuery vulkan_symbol_wrapper_vkCmdEndQuery; +PFN_vkCmdResetQueryPool vulkan_symbol_wrapper_vkCmdResetQueryPool; +PFN_vkCmdWriteTimestamp vulkan_symbol_wrapper_vkCmdWriteTimestamp; +PFN_vkCmdCopyQueryPoolResults vulkan_symbol_wrapper_vkCmdCopyQueryPoolResults; +PFN_vkCmdPushConstants vulkan_symbol_wrapper_vkCmdPushConstants; +PFN_vkCmdBeginRenderPass vulkan_symbol_wrapper_vkCmdBeginRenderPass; +PFN_vkCmdNextSubpass vulkan_symbol_wrapper_vkCmdNextSubpass; +PFN_vkCmdEndRenderPass vulkan_symbol_wrapper_vkCmdEndRenderPass; +PFN_vkCmdExecuteCommands vulkan_symbol_wrapper_vkCmdExecuteCommands; +PFN_vkDestroySurfaceKHR vulkan_symbol_wrapper_vkDestroySurfaceKHR; +PFN_vkGetPhysicalDeviceSurfaceSupportKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceSupportKHR; +PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceCapabilitiesKHR; +PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfaceFormatsKHR; +PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceSurfacePresentModesKHR; +PFN_vkCreateSwapchainKHR vulkan_symbol_wrapper_vkCreateSwapchainKHR; +PFN_vkDestroySwapchainKHR vulkan_symbol_wrapper_vkDestroySwapchainKHR; +PFN_vkGetSwapchainImagesKHR vulkan_symbol_wrapper_vkGetSwapchainImagesKHR; +PFN_vkAcquireNextImageKHR vulkan_symbol_wrapper_vkAcquireNextImageKHR; +PFN_vkQueuePresentKHR vulkan_symbol_wrapper_vkQueuePresentKHR; +PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceDisplayPropertiesKHR; +PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vulkan_symbol_wrapper_vkGetPhysicalDeviceDisplayPlanePropertiesKHR; +PFN_vkGetDisplayPlaneSupportedDisplaysKHR vulkan_symbol_wrapper_vkGetDisplayPlaneSupportedDisplaysKHR; +PFN_vkGetDisplayModePropertiesKHR vulkan_symbol_wrapper_vkGetDisplayModePropertiesKHR; +PFN_vkCreateDisplayModeKHR vulkan_symbol_wrapper_vkCreateDisplayModeKHR; +PFN_vkGetDisplayPlaneCapabilitiesKHR vulkan_symbol_wrapper_vkGetDisplayPlaneCapabilitiesKHR; +PFN_vkCreateDisplayPlaneSurfaceKHR vulkan_symbol_wrapper_vkCreateDisplayPlaneSurfaceKHR; +PFN_vkCreateSharedSwapchainsKHR vulkan_symbol_wrapper_vkCreateSharedSwapchainsKHR; + +PFN_vkCreateDebugUtilsMessengerEXT vulkan_symbol_wrapper_vkCreateDebugUtilsMessengerEXT; +PFN_vkDestroyDebugUtilsMessengerEXT vulkan_symbol_wrapper_vkDestroyDebugUtilsMessengerEXT; +PFN_vkSetDebugUtilsObjectNameEXT vulkan_symbol_wrapper_vkSetDebugUtilsObjectNameEXT; + +static PFN_vkGetInstanceProcAddr GetInstanceProcAddr; +void vulkan_symbol_wrapper_init(PFN_vkGetInstanceProcAddr get_instance_proc_addr) +{ + GetInstanceProcAddr = get_instance_proc_addr; +} + +PFN_vkGetInstanceProcAddr vulkan_symbol_wrapper_instance_proc_addr(void) +{ + return GetInstanceProcAddr; +} + +VkBool32 vulkan_symbol_wrapper_load_instance_symbol(VkInstance instance, const char *name, PFN_vkVoidFunction *ppSymbol) +{ + *ppSymbol = GetInstanceProcAddr(instance, name); + return *ppSymbol != NULL; +} + +VkBool32 vulkan_symbol_wrapper_load_device_symbol(VkDevice device, const char *name, PFN_vkVoidFunction *ppSymbol) +{ + *ppSymbol = vkGetDeviceProcAddr(device, name); + return *ppSymbol != NULL; +} + +VkBool32 vulkan_symbol_wrapper_load_global_symbols(void) +{ + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(NULL, "vkCreateInstance", vkCreateInstance)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(NULL, "vkEnumerateInstanceExtensionProperties", vkEnumerateInstanceExtensionProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(NULL, "vkEnumerateInstanceLayerProperties", vkEnumerateInstanceLayerProperties)) return VK_FALSE; + VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(NULL, "vkEnumerateInstanceVersion", vkEnumerateInstanceVersion); + return VK_TRUE; +} + +VkBool32 vulkan_symbol_wrapper_load_core_symbols(VkInstance instance) +{ + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyInstance", vkDestroyInstance)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkEnumeratePhysicalDevices", vkEnumeratePhysicalDevices)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceFeatures", vkGetPhysicalDeviceFeatures)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceFormatProperties", vkGetPhysicalDeviceFormatProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceImageFormatProperties", vkGetPhysicalDeviceImageFormatProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceProperties", vkGetPhysicalDeviceProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceQueueFamilyProperties", vkGetPhysicalDeviceQueueFamilyProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceMemoryProperties", vkGetPhysicalDeviceMemoryProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetDeviceProcAddr", vkGetDeviceProcAddr)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateDevice", vkCreateDevice)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyDevice", vkDestroyDevice)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkEnumerateDeviceExtensionProperties", vkEnumerateDeviceExtensionProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkEnumerateDeviceLayerProperties", vkEnumerateDeviceLayerProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetDeviceQueue", vkGetDeviceQueue)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkQueueSubmit", vkQueueSubmit)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkQueueWaitIdle", vkQueueWaitIdle)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDeviceWaitIdle", vkDeviceWaitIdle)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkAllocateMemory", vkAllocateMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkFreeMemory", vkFreeMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkMapMemory", vkMapMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkUnmapMemory", vkUnmapMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkFlushMappedMemoryRanges", vkFlushMappedMemoryRanges)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkInvalidateMappedMemoryRanges", vkInvalidateMappedMemoryRanges)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetDeviceMemoryCommitment", vkGetDeviceMemoryCommitment)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkBindBufferMemory", vkBindBufferMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkBindImageMemory", vkBindImageMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetBufferMemoryRequirements", vkGetBufferMemoryRequirements)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetImageMemoryRequirements", vkGetImageMemoryRequirements)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetImageSparseMemoryRequirements", vkGetImageSparseMemoryRequirements)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceSparseImageFormatProperties", vkGetPhysicalDeviceSparseImageFormatProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkQueueBindSparse", vkQueueBindSparse)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateFence", vkCreateFence)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyFence", vkDestroyFence)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkResetFences", vkResetFences)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetFenceStatus", vkGetFenceStatus)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkWaitForFences", vkWaitForFences)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateSemaphore", vkCreateSemaphore)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroySemaphore", vkDestroySemaphore)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateEvent", vkCreateEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyEvent", vkDestroyEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetEventStatus", vkGetEventStatus)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkSetEvent", vkSetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkResetEvent", vkResetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateQueryPool", vkCreateQueryPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyQueryPool", vkDestroyQueryPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetQueryPoolResults", vkGetQueryPoolResults)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateBuffer", vkCreateBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyBuffer", vkDestroyBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateBufferView", vkCreateBufferView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyBufferView", vkDestroyBufferView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateImage", vkCreateImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyImage", vkDestroyImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetImageSubresourceLayout", vkGetImageSubresourceLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateImageView", vkCreateImageView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyImageView", vkDestroyImageView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateShaderModule", vkCreateShaderModule)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyShaderModule", vkDestroyShaderModule)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreatePipelineCache", vkCreatePipelineCache)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyPipelineCache", vkDestroyPipelineCache)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPipelineCacheData", vkGetPipelineCacheData)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkMergePipelineCaches", vkMergePipelineCaches)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateGraphicsPipelines", vkCreateGraphicsPipelines)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateComputePipelines", vkCreateComputePipelines)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyPipeline", vkDestroyPipeline)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreatePipelineLayout", vkCreatePipelineLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyPipelineLayout", vkDestroyPipelineLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateSampler", vkCreateSampler)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroySampler", vkDestroySampler)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateDescriptorSetLayout", vkCreateDescriptorSetLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyDescriptorSetLayout", vkDestroyDescriptorSetLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateDescriptorPool", vkCreateDescriptorPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyDescriptorPool", vkDestroyDescriptorPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkResetDescriptorPool", vkResetDescriptorPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkAllocateDescriptorSets", vkAllocateDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkFreeDescriptorSets", vkFreeDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkUpdateDescriptorSets", vkUpdateDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateFramebuffer", vkCreateFramebuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyFramebuffer", vkDestroyFramebuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateRenderPass", vkCreateRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyRenderPass", vkDestroyRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetRenderAreaGranularity", vkGetRenderAreaGranularity)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateCommandPool", vkCreateCommandPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyCommandPool", vkDestroyCommandPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkResetCommandPool", vkResetCommandPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkAllocateCommandBuffers", vkAllocateCommandBuffers)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkFreeCommandBuffers", vkFreeCommandBuffers)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkBeginCommandBuffer", vkBeginCommandBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkEndCommandBuffer", vkEndCommandBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkResetCommandBuffer", vkResetCommandBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdBindPipeline", vkCmdBindPipeline)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetViewport", vkCmdSetViewport)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetScissor", vkCmdSetScissor)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetLineWidth", vkCmdSetLineWidth)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetDepthBias", vkCmdSetDepthBias)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetBlendConstants", vkCmdSetBlendConstants)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetDepthBounds", vkCmdSetDepthBounds)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetStencilCompareMask", vkCmdSetStencilCompareMask)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetStencilWriteMask", vkCmdSetStencilWriteMask)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetStencilReference", vkCmdSetStencilReference)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdBindDescriptorSets", vkCmdBindDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdBindIndexBuffer", vkCmdBindIndexBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdBindVertexBuffers", vkCmdBindVertexBuffers)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdDraw", vkCmdDraw)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdDrawIndexed", vkCmdDrawIndexed)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdDrawIndirect", vkCmdDrawIndirect)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdDrawIndexedIndirect", vkCmdDrawIndexedIndirect)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdDispatch", vkCmdDispatch)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdDispatchIndirect", vkCmdDispatchIndirect)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdCopyBuffer", vkCmdCopyBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdCopyImage", vkCmdCopyImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdBlitImage", vkCmdBlitImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdCopyBufferToImage", vkCmdCopyBufferToImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdCopyImageToBuffer", vkCmdCopyImageToBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdUpdateBuffer", vkCmdUpdateBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdFillBuffer", vkCmdFillBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdClearColorImage", vkCmdClearColorImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdClearDepthStencilImage", vkCmdClearDepthStencilImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdClearAttachments", vkCmdClearAttachments)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdResolveImage", vkCmdResolveImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdSetEvent", vkCmdSetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdResetEvent", vkCmdResetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdWaitEvents", vkCmdWaitEvents)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdPipelineBarrier", vkCmdPipelineBarrier)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdBeginQuery", vkCmdBeginQuery)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdEndQuery", vkCmdEndQuery)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdResetQueryPool", vkCmdResetQueryPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdWriteTimestamp", vkCmdWriteTimestamp)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdCopyQueryPoolResults", vkCmdCopyQueryPoolResults)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdPushConstants", vkCmdPushConstants)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdBeginRenderPass", vkCmdBeginRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdNextSubpass", vkCmdNextSubpass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdEndRenderPass", vkCmdEndRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCmdExecuteCommands", vkCmdExecuteCommands)) return VK_FALSE; + return VK_TRUE; +} + +VkBool32 vulkan_symbol_wrapper_load_core_instance_symbols(VkInstance instance) +{ + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkDestroyInstance", vkDestroyInstance)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkEnumeratePhysicalDevices", vkEnumeratePhysicalDevices)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceFeatures", vkGetPhysicalDeviceFeatures)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceFormatProperties", vkGetPhysicalDeviceFormatProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceImageFormatProperties", vkGetPhysicalDeviceImageFormatProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceProperties", vkGetPhysicalDeviceProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceQueueFamilyProperties", vkGetPhysicalDeviceQueueFamilyProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceMemoryProperties", vkGetPhysicalDeviceMemoryProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetDeviceProcAddr", vkGetDeviceProcAddr)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkCreateDevice", vkCreateDevice)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkEnumerateDeviceExtensionProperties", vkEnumerateDeviceExtensionProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkEnumerateDeviceLayerProperties", vkEnumerateDeviceLayerProperties)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_INSTANCE_SYMBOL(instance, "vkGetPhysicalDeviceSparseImageFormatProperties", vkGetPhysicalDeviceSparseImageFormatProperties)) return VK_FALSE; + return VK_TRUE; +} + +VkBool32 vulkan_symbol_wrapper_load_core_device_symbols(VkDevice device) +{ + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyDevice", vkDestroyDevice)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetDeviceQueue", vkGetDeviceQueue)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkQueueSubmit", vkQueueSubmit)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkQueueWaitIdle", vkQueueWaitIdle)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDeviceWaitIdle", vkDeviceWaitIdle)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkAllocateMemory", vkAllocateMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkFreeMemory", vkFreeMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkMapMemory", vkMapMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkUnmapMemory", vkUnmapMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkFlushMappedMemoryRanges", vkFlushMappedMemoryRanges)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkInvalidateMappedMemoryRanges", vkInvalidateMappedMemoryRanges)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetDeviceMemoryCommitment", vkGetDeviceMemoryCommitment)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkBindBufferMemory", vkBindBufferMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkBindImageMemory", vkBindImageMemory)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetBufferMemoryRequirements", vkGetBufferMemoryRequirements)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetImageMemoryRequirements", vkGetImageMemoryRequirements)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetImageSparseMemoryRequirements", vkGetImageSparseMemoryRequirements)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkQueueBindSparse", vkQueueBindSparse)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateFence", vkCreateFence)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyFence", vkDestroyFence)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkResetFences", vkResetFences)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetFenceStatus", vkGetFenceStatus)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkWaitForFences", vkWaitForFences)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateSemaphore", vkCreateSemaphore)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroySemaphore", vkDestroySemaphore)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateEvent", vkCreateEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyEvent", vkDestroyEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetEventStatus", vkGetEventStatus)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkSetEvent", vkSetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkResetEvent", vkResetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateQueryPool", vkCreateQueryPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyQueryPool", vkDestroyQueryPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetQueryPoolResults", vkGetQueryPoolResults)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateBuffer", vkCreateBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyBuffer", vkDestroyBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateBufferView", vkCreateBufferView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyBufferView", vkDestroyBufferView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateImage", vkCreateImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyImage", vkDestroyImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetImageSubresourceLayout", vkGetImageSubresourceLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateImageView", vkCreateImageView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyImageView", vkDestroyImageView)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateShaderModule", vkCreateShaderModule)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyShaderModule", vkDestroyShaderModule)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreatePipelineCache", vkCreatePipelineCache)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyPipelineCache", vkDestroyPipelineCache)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetPipelineCacheData", vkGetPipelineCacheData)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkMergePipelineCaches", vkMergePipelineCaches)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateGraphicsPipelines", vkCreateGraphicsPipelines)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateComputePipelines", vkCreateComputePipelines)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyPipeline", vkDestroyPipeline)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreatePipelineLayout", vkCreatePipelineLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyPipelineLayout", vkDestroyPipelineLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateSampler", vkCreateSampler)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroySampler", vkDestroySampler)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateDescriptorSetLayout", vkCreateDescriptorSetLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyDescriptorSetLayout", vkDestroyDescriptorSetLayout)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateDescriptorPool", vkCreateDescriptorPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyDescriptorPool", vkDestroyDescriptorPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkResetDescriptorPool", vkResetDescriptorPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkAllocateDescriptorSets", vkAllocateDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkFreeDescriptorSets", vkFreeDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkUpdateDescriptorSets", vkUpdateDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateFramebuffer", vkCreateFramebuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyFramebuffer", vkDestroyFramebuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateRenderPass", vkCreateRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyRenderPass", vkDestroyRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkGetRenderAreaGranularity", vkGetRenderAreaGranularity)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCreateCommandPool", vkCreateCommandPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkDestroyCommandPool", vkDestroyCommandPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkResetCommandPool", vkResetCommandPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkAllocateCommandBuffers", vkAllocateCommandBuffers)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkFreeCommandBuffers", vkFreeCommandBuffers)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkBeginCommandBuffer", vkBeginCommandBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkEndCommandBuffer", vkEndCommandBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkResetCommandBuffer", vkResetCommandBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdBindPipeline", vkCmdBindPipeline)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetViewport", vkCmdSetViewport)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetScissor", vkCmdSetScissor)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetLineWidth", vkCmdSetLineWidth)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetDepthBias", vkCmdSetDepthBias)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetBlendConstants", vkCmdSetBlendConstants)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetDepthBounds", vkCmdSetDepthBounds)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetStencilCompareMask", vkCmdSetStencilCompareMask)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetStencilWriteMask", vkCmdSetStencilWriteMask)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetStencilReference", vkCmdSetStencilReference)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdBindDescriptorSets", vkCmdBindDescriptorSets)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdBindIndexBuffer", vkCmdBindIndexBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdBindVertexBuffers", vkCmdBindVertexBuffers)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdDraw", vkCmdDraw)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdDrawIndexed", vkCmdDrawIndexed)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdDrawIndirect", vkCmdDrawIndirect)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdDrawIndexedIndirect", vkCmdDrawIndexedIndirect)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdDispatch", vkCmdDispatch)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdDispatchIndirect", vkCmdDispatchIndirect)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdCopyBuffer", vkCmdCopyBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdCopyImage", vkCmdCopyImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdBlitImage", vkCmdBlitImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdCopyBufferToImage", vkCmdCopyBufferToImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdCopyImageToBuffer", vkCmdCopyImageToBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdUpdateBuffer", vkCmdUpdateBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdFillBuffer", vkCmdFillBuffer)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdClearColorImage", vkCmdClearColorImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdClearDepthStencilImage", vkCmdClearDepthStencilImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdClearAttachments", vkCmdClearAttachments)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdResolveImage", vkCmdResolveImage)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdSetEvent", vkCmdSetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdResetEvent", vkCmdResetEvent)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdWaitEvents", vkCmdWaitEvents)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdPipelineBarrier", vkCmdPipelineBarrier)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdBeginQuery", vkCmdBeginQuery)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdEndQuery", vkCmdEndQuery)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdResetQueryPool", vkCmdResetQueryPool)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdWriteTimestamp", vkCmdWriteTimestamp)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdCopyQueryPoolResults", vkCmdCopyQueryPoolResults)) + { + /* Don't return false here. Would cause MESA Intel Ivy Bridge drivers to not work at all. */ + } + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdPushConstants", vkCmdPushConstants)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdBeginRenderPass", vkCmdBeginRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdNextSubpass", vkCmdNextSubpass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdEndRenderPass", vkCmdEndRenderPass)) return VK_FALSE; + if (!VULKAN_SYMBOL_WRAPPER_LOAD_DEVICE_SYMBOL(device, "vkCmdExecuteCommands", vkCmdExecuteCommands)) return VK_FALSE; + return VK_TRUE; +} diff --git a/libretro.cpp b/libretro.cpp index fde8e6f..3e718f4 100644 --- a/libretro.cpp +++ b/libretro.cpp @@ -29,6 +29,10 @@ #include "program.h" #include "gl.hpp" +#ifdef HAVE_VULKAN +#include "engine/vk_renderer.hpp" +#endif + #define FPS 60.0 retro_position_t previous_location; @@ -53,6 +57,8 @@ retro_input_state_t input_state_cb; static const engine_program_t *engine_program_cb; struct retro_sensor_interface sensor_cb; +enum retro_hw_context_type active_hw_context = RETRO_HW_CONTEXT_NONE; + static bool display_position; #define BASE_WIDTH 320 @@ -76,6 +82,11 @@ unsigned vr_eye_width = 0; unsigned vr_eye_height = 0; bool vr_user_enable = true; +bool renderer_is_vulkan(void) +{ + return active_hw_context == RETRO_HW_CONTEXT_VULKAN; +} + void retro_init(void) { struct retro_log_callback log; @@ -403,6 +414,13 @@ static void context_reset(void) engine_program_cb->context_reset(); } +/* Only registered for Vulkan contexts (see try_hw_context()). */ +static void context_destroy(void) +{ + if (engine_program_cb && engine_program_cb->context_destroy) + engine_program_cb->context_destroy(); +} + char retro_path_info[1024]; static void location_initialized(void) @@ -430,14 +448,16 @@ static bool vr_request_session(void) if (!environ_cb(RETRO_ENVIRONMENT_SET_VR_CONTENT_INFO, &vr)) { - log_cb(RETRO_LOG_INFO, "VR session request failed.\n"); + if (log_cb) + log_cb(RETRO_LOG_INFO, "VR session request failed.\n"); return false; /* old frontend, no headset, or VR disabled -> flat */ } vr_eye_width = vr.recommended_eye_width ? vr.recommended_eye_width : 1024; vr_eye_height = vr.recommended_eye_height ? vr.recommended_eye_height : 1024; - log_cb(RETRO_LOG_INFO, "VR session request success. vr_eye_width: %d vr_eye_height: %d.\n", vr_eye_width, vr_eye_height); + if (log_cb) + log_cb(RETRO_LOG_INFO, "VR session request success. vr_eye_width: %d vr_eye_height: %d.\n", vr_eye_width, vr_eye_height); return true; } @@ -464,18 +484,98 @@ bool vr_poll_frame(struct retro_vr_frame_state *fs) } // END VR +static bool try_hw_context(enum retro_hw_context_type type) +{ + memset(&hw_render, 0, sizeof(hw_render)); + hw_render.context_type = type; + hw_render.context_reset = context_reset; + + if (type == RETRO_HW_CONTEXT_VULKAN) + { +#ifdef HAVE_VULKAN + hw_render.context_destroy = context_destroy; + if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) + return false; + + if (!VKR::set_negotiation_interface(environ_cb) && log_cb) + log_cb(RETRO_LOG_WARN, "Vulkan context negotiation not available, using frontend defaults.\n"); + return true; +#else + return false; +#endif + } + + hw_render.depth = true; + if (!camera_enable) + hw_render.bottom_left_origin = true; + return environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render); +} + +static bool init_hw_render(bool allow_vulkan) +{ + unsigned i, n = 0; + enum retro_hw_context_type order[2]; + enum retro_hw_context_type preferred = RETRO_HW_CONTEXT_NONE; + bool have_vulkan = false; +#ifdef HAVE_OPENGLES + const enum retro_hw_context_type gl_type = RETRO_HW_CONTEXT_OPENGLES2; +#else + const enum retro_hw_context_type gl_type = RETRO_HW_CONTEXT_OPENGL; +#endif + +#ifdef HAVE_VULKAN + have_vulkan = allow_vulkan; +#else + (void)allow_vulkan; +#endif + + environ_cb(RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER, &preferred); + + if (have_vulkan && preferred == RETRO_HW_CONTEXT_VULKAN) + { + order[n++] = RETRO_HW_CONTEXT_VULKAN; + order[n++] = gl_type; + } + else + { + order[n++] = gl_type; + if (have_vulkan) + order[n++] = RETRO_HW_CONTEXT_VULKAN; + } + + for (i = 0; i < n; i++) + { + if (try_hw_context(order[i])) + { + active_hw_context = order[i]; + if (log_cb) + log_cb(RETRO_LOG_INFO, "Using %s renderer (frontend preference: %d).\n", + order[i] == RETRO_HW_CONTEXT_VULKAN ? "Vulkan" : "OpenGL/GLES", + (int)preferred); + return true; + } + } + + active_hw_context = RETRO_HW_CONTEXT_NONE; + return false; +} + bool retro_load_game(const struct retro_game_info *info) { retro_variable var; + bool allow_vulkan = false; if (!info) return false; strcpy(retro_path_info, info->path); if (strstr(info->path, ".obj") || strstr(info->path, ".mtl")) + { engine_program_cb = &engine_program_modelviewer; + allow_vulkan = true; /* the mesh viewer/scene walker have a Vulkan back-end */ + } else - engine_program_cb = &engine_program_instancingviewer; + engine_program_cb = &engine_program_instancingviewer; /* GL only: camera textures, instanced cubes */ update_variables(); @@ -490,7 +590,8 @@ bool retro_load_game(const struct retro_game_info *info) if (sensor_initialized) return true; - log_cb(RETRO_LOG_INFO, "Sensor interface found, enabling...\n"); + if (log_cb) + log_cb(RETRO_LOG_INFO, "Sensor interface found, enabling...\n"); if (sensor_cb.set_sensor_state) { sensor_cb.set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_ENABLE, FPS); @@ -504,7 +605,8 @@ bool retro_load_game(const struct retro_game_info *info) if (sensor_initialized) return true; - log_cb(RETRO_LOG_INFO, "Sensor interface found, disabling...\n"); + if (log_cb) + log_cb(RETRO_LOG_INFO, "Sensor interface found, disabling...\n"); if (sensor_cb.set_sensor_state) { sensor_cb.set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, FPS); @@ -573,29 +675,24 @@ bool retro_load_game(const struct retro_game_info *info) } } -#ifdef HAVE_OPENGLES - hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES2; -#else - hw_render.context_type = RETRO_HW_CONTEXT_OPENGL; -#endif - hw_render.context_reset = context_reset; - hw_render.depth = true; - if (!camera_enable) - hw_render.bottom_left_origin = true; - if (!environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &hw_render)) + if (!init_hw_render(allow_vulkan)) return false; -#ifdef HAVE_OPENGLES - if (camera_enable && camera_cb.caps & (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER) && !gl_query_extension("BGRA8888")) + /* GL-only checks (they query a GL context / the camera GL paths). */ + if (!renderer_is_vulkan()) { - if (log_cb) - log_cb(RETRO_LOG_ERROR, "no BGRA8888 support for raw framebuffer, exiting...\n"); - return false; - } - support_unpack_row_length = gl_query_extension("GL_EXT_unpack_subimage"); +#ifdef HAVE_OPENGLES + if (camera_enable && camera_cb.caps & (1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER) && !gl_query_extension("BGRA8888")) + { + if (log_cb) + log_cb(RETRO_LOG_ERROR, "no BGRA8888 support for raw framebuffer, exiting...\n"); + return false; + } + support_unpack_row_length = gl_query_extension("GL_EXT_unpack_subimage"); #else - support_unpack_row_length = true; + support_unpack_row_length = true; #endif + } if (log_cb) log_cb(RETRO_LOG_INFO, "Loaded game!\n"); @@ -620,6 +717,8 @@ void retro_unload_game(void) delete[] convert_buffer; convert_buffer = NULL; vr_active = false; + /* Vulkan resources are released in context_destroy(), while the device + * is still alive; nothing to do for them here. */ } unsigned retro_get_region(void) @@ -668,6 +767,11 @@ size_t retro_get_memory_size(unsigned id) void retro_reset(void) { + /* A GL context can simply be re-populated; a Vulkan one must not be + * re-initialised behind the frontend's back. */ + if (renderer_is_vulkan()) + return; + if (engine_program_cb && engine_program_cb->context_reset) engine_program_cb->context_reset(); } @@ -681,4 +785,3 @@ void retro_cheat_set(unsigned index, bool enabled, const char *code) (void)enabled; (void)code; } - diff --git a/program.h b/program.h index 702a17b..cf5e9df 100644 --- a/program.h +++ b/program.h @@ -28,6 +28,7 @@ extern retro_input_poll_t input_poll_cb; extern retro_input_state_t input_state_cb; extern struct retro_hw_render_callback hw_render; extern retro_video_refresh_t video_cb; +extern retro_environment_t environ_cb; extern unsigned engine_width; extern unsigned engine_height; @@ -38,6 +39,9 @@ extern GLuint tex; extern bool location_camera_control_enable; extern bool sensor_enable; +extern enum retro_hw_context_type active_hw_context; +extern bool renderer_is_vulkan(void); + typedef struct { float latitude; @@ -56,6 +60,7 @@ typedef struct engine_program void (*context_reset)(void); void (*update_variables)(retro_environment_t environ_cb); glm::vec3 (*check_input)(void); + void (*context_destroy)(void); /* optional, may be NULL */ } engine_program_t; extern const engine_program_t engine_program_instancingviewer; diff --git a/program/modelviewer.cpp b/program/modelviewer.cpp index bb9d5fc..07769d7 100644 --- a/program/modelviewer.cpp +++ b/program/modelviewer.cpp @@ -23,6 +23,10 @@ #include "collision_detection.hpp" #include "location_math.h" +#ifdef HAVE_VULKAN +#include "../engine/vk_renderer.hpp" +#endif + #include #include @@ -70,6 +74,16 @@ static vec3 player_pos(0, 2, 0); extern bool vr_user_enable; +/* Per-frame scene state written by the input code and consumed by whichever + * renderer is active (GL meshes or the Vulkan renderer). */ +static mat4 scene_model(1.0f); +static mat4 scene_view(1.0f); +static vec3 scene_eye(0.0f, 0.0f, 0.0f); + +#ifdef HAVE_VULKAN +static bool vk_scene_ok = false; +#endif + static vec3 scenewalker_check_input(void) { input_poll_cb(); @@ -273,13 +287,8 @@ static vec3 scenewalker_check_input(void) } } - for (unsigned i = 0; i < meshes.size(); i++) - { - meshes[i]->set_view(view); - meshes[i]->set_eye(player_pos); - meshes[i]->set_lighting(light_r, light_g, light_b); - meshes[i]->set_ambient_lighting(ambient_light_r, ambient_light_g, ambient_light_b); - } + scene_view = view; + scene_eye = player_pos; return player_size; } @@ -392,19 +401,50 @@ static vec3 modelviewer_check_input(void) mat4 rotate_x = rotate(mat4(1.0), model_rotate_x, vec3(1, 0, 0)); mat4 rotate_y = rotate(mat4(1.0), model_rotate_y, vec3(0, 1, 0)); - mat4 model = translation * scaler * rotate_x * rotate_y; - - for (unsigned i = 0; i < meshes.size(); i++) - { - meshes[i]->set_model(model); - meshes[i]->set_ambient_lighting(ambient_light_r, ambient_light_g, ambient_light_b); - meshes[i]->set_lighting(light_r, light_g, light_b); - } + scene_model = translation * scaler * rotate_x * rotate_y; //check_collision_cube(); return player_size; } +/* Pushes the state computed by the input code into the GL meshes. */ +static void apply_scene_state_gl(void) +{ + const bool scene = (mode_engine == MODE_SCENEWALKER); + for (unsigned i = 0; i < meshes.size(); i++) + { + if (scene) + { + meshes[i]->set_view(scene_view); + meshes[i]->set_eye(scene_eye); + } + else + meshes[i]->set_model(scene_model); + + meshes[i]->set_lighting(light_r, light_g, light_b); + meshes[i]->set_ambient_lighting(ambient_light_r, ambient_light_g, ambient_light_b); + } +} + +static void init_lighting(void) +{ + if (mode_engine == MODE_SCENEWALKER) + { + light_r = normalize(0); + light_g = normalize(10); + light_b = normalize(0); + } + else + { + light_r = normalize(-1); + light_g = normalize(-1); + light_b = normalize(-1); + } + ambient_light_r = 0.25f; + ambient_light_g = 0.25f; + ambient_light_b = 0.25f; +} + static void init_mesh(const std::string& path) { if (log_cb) @@ -582,27 +622,70 @@ static void init_mesh(const std::string& path) } } - if (mode_engine == MODE_SCENEWALKER) + init_lighting(); +} + +#ifdef HAVE_VULKAN +/* Vulkan counterpart of init_mesh(): same OBJ parse, same collision data, + * but the GPU objects live in VKR instead of GL::Mesh. */ +static bool vulkan_load_scene(void) +{ + unsigned i, v; + const bool scene = (mode_engine == MODE_SCENEWALKER); + std::vector parts; + VKR::ShaderMode mode; + + if (log_cb) + log_cb(RETRO_LOG_INFO, "Loading Mesh (Vulkan) ...\n"); + + parts = OBJ::load_parts(mesh_path); + + if (scene) { - light_r = normalize(0); - light_g = normalize(10); - light_b = normalize(0); + for (i = 0; i < parts.size(); i++) + for (v = 0; v + 2 < parts[i].vertices.size(); v += 3) + coll_triangles_push(v, parts[i].vertices, player_size); + mode = VKR::SHADER_SCENE; } else - { - light_r = normalize(-1); - light_g = normalize(-1); - light_b = normalize(-1); - } - ambient_light_r = 0.25f; - ambient_light_g = 0.25f; - ambient_light_b = 0.25f; + mode = discard_hack_enable ? VKR::SHADER_MODEL_DISCARD : VKR::SHADER_MODEL; + + init_lighting(); + + vk_scene_ok = VKR::load_scene(parts, mode); + return vk_scene_ok; } +#endif extern char retro_path_info[1024]; static void modelviewer_context_reset(void) { +#ifdef HAVE_VULKAN + if (renderer_is_vulkan()) + { + vk_scene_ok = false; + + if (strstr(retro_path_info, ".mtl") || mode_engine == MODE_SCENEWALKER) + { + coll_triangles_clear(); + scenewalker_reset_mesh_path(); + mode_engine = MODE_SCENEWALKER; + } + + if (!VKR::init(environ_cb)) + { + if (log_cb) + log_cb(RETRO_LOG_ERROR, "Vulkan renderer init failed.\n"); + return; + } + + vulkan_load_scene(); + update = true; + return; + } +#endif + renderer_dead_state = true; meshes.clear(); blank.reset(); @@ -623,6 +706,33 @@ static void modelviewer_context_reset(void) update = true; } +/* Called from the frontend's context_destroy (Vulkan only). */ +static void modelviewer_context_destroy(void) +{ +#ifdef HAVE_VULKAN + vk_scene_ok = false; + VKR::destroy(); +#endif +} + +/* Option changes used to be applied by faking a context reset. For Vulkan + * only the scene (pipeline variant + geometry) needs rebuilding. */ +static void modelviewer_apply_option_change(void) +{ +#ifdef HAVE_VULKAN + if (renderer_is_vulkan()) + { + if (!VKR::initialized()) + return; /* will be built by the real context_reset */ + if (mode_engine == MODE_SCENEWALKER) + coll_triangles_clear(); + vulkan_load_scene(); + return; + } +#endif + modelviewer_context_reset(); +} + static void modelviewer_update_variables(retro_environment_t environ_cb) { struct retro_variable var; @@ -638,7 +748,7 @@ static void modelviewer_update_variables(retro_environment_t environ_cb) discard_hack_enable = true; if (!first_init) - modelviewer_context_reset(); + modelviewer_apply_option_change(); } var.key = "3dengine-modelviewer-vr-enable"; @@ -652,10 +762,62 @@ static void modelviewer_update_variables(retro_environment_t environ_cb) vr_user_enable = true; if (!first_init) - modelviewer_context_reset(); + modelviewer_apply_option_change(); } } +#ifdef HAVE_VULKAN +static void modelviewer_run_vulkan(unsigned width, unsigned height, bool scene, bool vr) +{ + VKR::Frame f; + + f.model = scene_model; + f.light = vec3(light_r, light_g, light_b); + f.ambient = vec3(ambient_light_r, ambient_light_g, ambient_light_b); + f.width = width; + f.height = height; + f.num_passes = 0; + + if (vr) + { + unsigned eye; + mat4 rig = translate(mat4(1.0), player_pos) * + rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0)); + + for (eye = 0; eye < 2; eye++) + { + const struct retro_vr_eye_state &es = vr_frame.eyes[eye]; + mat4 eye_world = rig * vr_eye_pose_matrix(es); + VKR::Pass &p = f.passes[eye]; + + /* Same matrices as the GL path; VKR applies the GL->Vulkan clip + * correction, so no scene_flip_y() handling here. */ + p.view = inverse(eye_world); + p.proj = vr_projection(es.fov_tan, 0.1f, 100.0f); + p.eye_pos = vec3(eye_world[3]); + p.x = eye * vr_eye_width; + p.width = vr_eye_width; + p.height = vr_eye_height; + } + f.num_passes = 2; + } + else + { + VKR::Pass &p = f.passes[0]; + p.proj = scene ? perspective(45.0f, 640.0f / 480.0f, 1.0f, 100.0f) + : perspective(45.0f, 4.0f / 3.0f, 0.2f, 100.0f); + p.view = scene ? scene_view : mat4(1.0f); + p.eye_pos = scene_eye; + p.x = 0; + p.width = width; + p.height = height; + f.num_passes = 1; + } + + if (!vk_scene_ok || !VKR::render(f, video_cb)) + video_cb(NULL, width, height, 0); /* dupe the last frame */ +} +#endif static void modelviewer_run(void) { @@ -675,6 +837,16 @@ static void modelviewer_run(void) width = vr ? vr_eye_width * 2 : engine_width; height = vr ? vr_eye_height : engine_height; +#ifdef HAVE_VULKAN + if (renderer_is_vulkan()) + { + modelviewer_run_vulkan(width, height, scene, vr); + return; + } +#endif + + apply_scene_state_gl(); + glBindFramebuffer(GL_FRAMEBUFFER, hw_render.get_current_framebuffer()); glViewport(0, 0, width, height); glClearColor(0.2f, 0.2f, 0.2f, 1.0f); @@ -768,4 +940,5 @@ const engine_program_t engine_program_modelviewer = { modelviewer_context_reset, modelviewer_update_variables, modelviewer_check_input, + modelviewer_context_destroy, };