Last active
June 30, 2026 06:12
-
-
Save lalishansh/6cd023aa11dfeb930cc02711efd27394 to your computer and use it in GitHub Desktop.
main_vk_simpler.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| struct VulkanContext_t; | |
| VulkanContext_t* VulkanContext(); | |
| void destroy(VulkanContext_t* ctx); | |
| struct VulkanDevice; | |
| VulkanDevice* device(VulkanContext_t* ctx); | |
| void submit(VulkanDevice* device, bool wait = false); | |
| void destroy(VulkanDevice* device); | |
| using u8 = unsigned char; | |
| using u32 = unsigned int; | |
| static_assert(sizeof(u32) == sizeof(u8)*4, "u64 must be 8*sizeof(u8)"); | |
| using u64 = unsigned long; | |
| static_assert(sizeof(u64) == sizeof(u8)*8, "u64 must be 8*sizeof(u8)"); | |
| using s8 = signed char; | |
| using s32 = signed int; | |
| static_assert(sizeof(s32) == sizeof(u8)*4, "s32 must be 4*sizeof(u8)"); | |
| using f32 = float; | |
| using vec3 = f32[3]; | |
| using mat4 = f32[4*4]; | |
| using u8_4 = u8[4]; | |
| // static const vec4 CLEAR_COLOR_NONE {0.0f, 0.0f, 0.0f, 0.0f}; | |
| // static const vec2 CLEAR_DEPTH_NONE {1.0f, 0.0f}; | |
| template<typename T> | |
| struct VulkanTexture; | |
| template<typename T> | |
| VulkanTexture<T>* texture(VulkanDevice* device, const u32 width, const u32 height); | |
| template<typename T> | |
| T* get(const VulkanTexture<T>* texture); | |
| template<typename T> | |
| void destroy(VulkanTexture<T>* texture); | |
| namespace Triangle { | |
| struct Shader; | |
| Shader* create(VulkanDevice* device); | |
| void destroy(Shader* shader); | |
| void begin(Shader* shader, VulkanTexture<u8_4>* output_1 /*, vec4 clear_color = CLEAR_COLOR_NONE, vec2 clear_depth = CLEAR_DEPTH_NONE*/); | |
| void record(Shader* shader); | |
| struct Vertex_t { | |
| vec3 pos; | |
| vec3 color; | |
| }; | |
| void set(Shader* shader, const Vertex_t* vertex_buff, const u32 vertex_count, const u32* index_buff, const u32 index_count); | |
| enum class ShaderParams; | |
| template<ShaderParams id, typename Typ> | |
| void set(Shader* shader, const Typ* value) { static_assert(false, "unsupported shader input"); } | |
| enum class ShaderParams { | |
| mvp = 0, | |
| }; | |
| template<> | |
| void set<ShaderParams::mvp, mat4>(Shader* shader, const mat4* value); | |
| } | |
| void getMVP(const vec3& triangle_position, mat4* mvp); | |
| void writeImage(const u8_4* bytes, const u32 width, const u32 height); | |
| // Main function | |
| void entry_main() { | |
| auto* context = VulkanContext(); | |
| auto* logical_device = device(context); | |
| constexpr u32 width = 800; | |
| constexpr u32 height = 600; | |
| auto* output = texture<u8_4>(logical_device, width, height); | |
| using namespace Triangle; | |
| auto* triangle_shader = create(logical_device); | |
| begin(triangle_shader, output); | |
| const Vertex_t vertices[3] { | |
| { { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, | |
| { { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, | |
| { { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }, | |
| }; | |
| const u32 indices[3] { 0, 1, 2 }; | |
| set(triangle_shader, vertices, 3, indices, 3); | |
| const vec3 triangle_positions[3] { | |
| {-1.5f, 0.0f, -4.0f}, | |
| { 0.0f, 0.0f, -2.5f}, | |
| { 1.5f, 0.0f, -4.0f}, | |
| }; | |
| mat4 mvp; | |
| getMVP(triangle_positions[0], &mvp); | |
| set<ShaderParams::mvp>(triangle_shader, &mvp); | |
| record(triangle_shader); | |
| getMVP(triangle_positions[1], &mvp); | |
| set<ShaderParams::mvp>(triangle_shader, &mvp); | |
| record(triangle_shader); | |
| getMVP(triangle_positions[2], &mvp); | |
| set<ShaderParams::mvp>(triangle_shader, &mvp); | |
| record(triangle_shader); | |
| submit(logical_device, true); | |
| writeImage(get(output), width, height); | |
| destroy(output); | |
| destroy(triangle_shader); | |
| destroy(logical_device); | |
| destroy(context); | |
| } | |
| #if defined(__ANDROID__) | |
| #include <android_native_app_glue.h> | |
| #include <android/asset_manager.h> | |
| #include <android/configuration.h> | |
| #include <android/log.h> | |
| #include <android/native_activity.h> | |
| // Global reference to android application object | |
| static android_app* androidapp; | |
| #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "vulkanExample", __VA_ARGS__) | |
| #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "vulkanExample", __VA_ARGS__)) | |
| #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "vulkanExample", __VA_ARGS__)) | |
| #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "vulkanExample", __VA_ARGS__)) | |
| // Function pointer prototypes | |
| // Not complete, just the functions used in the caps viewer! | |
| extern PFN_vkCreateInstance vkCreateInstance; | |
| extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; | |
| extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; | |
| extern PFN_vkCreateDevice vkCreateDevice; | |
| extern PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; | |
| extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; | |
| extern PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2; | |
| extern PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties; | |
| extern PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties; | |
| extern PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties; | |
| extern PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures; | |
| extern PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2; | |
| extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; | |
| extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; | |
| extern PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties; | |
| extern PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties; | |
| extern PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; | |
| extern PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2; | |
| extern PFN_vkCreateShaderModule vkCreateShaderModule; | |
| extern PFN_vkCreateBuffer vkCreateBuffer; | |
| extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; | |
| extern PFN_vkMapMemory vkMapMemory; | |
| extern PFN_vkUnmapMemory vkUnmapMemory; | |
| extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges; | |
| extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; | |
| extern PFN_vkBindBufferMemory vkBindBufferMemory; | |
| extern PFN_vkDestroyBuffer vkDestroyBuffer; | |
| extern PFN_vkAllocateMemory vkAllocateMemory; | |
| extern PFN_vkBindImageMemory vkBindImageMemory; | |
| extern PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout; | |
| extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer; | |
| extern PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; | |
| extern PFN_vkCmdCopyImage vkCmdCopyImage; | |
| extern PFN_vkCmdBlitImage vkCmdBlitImage; | |
| extern PFN_vkCmdClearAttachments vkCmdClearAttachments; | |
| extern PFN_vkCreateSampler vkCreateSampler; | |
| extern PFN_vkDestroySampler vkDestroySampler; | |
| extern PFN_vkDestroyImage vkDestroyImage; | |
| extern PFN_vkFreeMemory vkFreeMemory; | |
| extern PFN_vkCreateRenderPass vkCreateRenderPass; | |
| extern PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass; | |
| extern PFN_vkCmdEndRenderPass vkCmdEndRenderPass; | |
| extern PFN_vkCmdNextSubpass vkCmdNextSubpass; | |
| extern PFN_vkCmdExecuteCommands vkCmdExecuteCommands; | |
| extern PFN_vkCmdClearColorImage vkCmdClearColorImage; | |
| extern PFN_vkCreateImage vkCreateImage; | |
| extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; | |
| extern PFN_vkCreateImageView vkCreateImageView; | |
| extern PFN_vkDestroyImageView vkDestroyImageView; | |
| extern PFN_vkCreateSemaphore vkCreateSemaphore; | |
| extern PFN_vkDestroySemaphore vkDestroySemaphore; | |
| extern PFN_vkCreateFence vkCreateFence; | |
| extern PFN_vkDestroyFence vkDestroyFence; | |
| extern PFN_vkWaitForFences vkWaitForFences; | |
| extern PFN_vkResetFences vkResetFences; | |
| extern PFN_vkResetDescriptorPool vkResetDescriptorPool; | |
| extern PFN_vkCreateCommandPool vkCreateCommandPool; | |
| extern PFN_vkDestroyCommandPool vkDestroyCommandPool; | |
| extern PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; | |
| extern PFN_vkBeginCommandBuffer vkBeginCommandBuffer; | |
| extern PFN_vkEndCommandBuffer vkEndCommandBuffer; | |
| extern PFN_vkGetDeviceQueue vkGetDeviceQueue; | |
| extern PFN_vkQueueSubmit vkQueueSubmit; | |
| extern PFN_vkQueueWaitIdle vkQueueWaitIdle; | |
| extern PFN_vkDeviceWaitIdle vkDeviceWaitIdle; | |
| extern PFN_vkCreateFramebuffer vkCreateFramebuffer; | |
| extern PFN_vkCreatePipelineCache vkCreatePipelineCache; | |
| extern PFN_vkCreatePipelineLayout vkCreatePipelineLayout; | |
| extern PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines; | |
| extern PFN_vkCreateComputePipelines vkCreateComputePipelines; | |
| extern PFN_vkCreateDescriptorPool vkCreateDescriptorPool; | |
| extern PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout; | |
| extern PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets; | |
| extern PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets; | |
| extern PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets; | |
| extern PFN_vkCmdBindPipeline vkCmdBindPipeline; | |
| extern PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers; | |
| extern PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer; | |
| extern PFN_vkCmdSetViewport vkCmdSetViewport; | |
| extern PFN_vkCmdSetScissor vkCmdSetScissor; | |
| extern PFN_vkCmdSetLineWidth vkCmdSetLineWidth; | |
| extern PFN_vkCmdSetDepthBias vkCmdSetDepthBias; | |
| extern PFN_vkCmdPushConstants vkCmdPushConstants; | |
| extern PFN_vkCmdDrawIndexed vkCmdDrawIndexed; | |
| extern PFN_vkCmdDraw vkCmdDraw; | |
| extern PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect; | |
| extern PFN_vkCmdDrawIndirect vkCmdDrawIndirect; | |
| extern PFN_vkCmdDispatch vkCmdDispatch; | |
| extern PFN_vkDestroyPipeline vkDestroyPipeline; | |
| extern PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout; | |
| extern PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout; | |
| extern PFN_vkDestroyDevice vkDestroyDevice; | |
| extern PFN_vkDestroyInstance vkDestroyInstance; | |
| extern PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool; | |
| extern PFN_vkFreeCommandBuffers vkFreeCommandBuffers; | |
| extern PFN_vkDestroyRenderPass vkDestroyRenderPass; | |
| extern PFN_vkDestroyFramebuffer vkDestroyFramebuffer; | |
| extern PFN_vkDestroyShaderModule vkDestroyShaderModule; | |
| extern PFN_vkDestroyPipelineCache vkDestroyPipelineCache; | |
| extern PFN_vkCreateQueryPool vkCreateQueryPool; | |
| extern PFN_vkDestroyQueryPool vkDestroyQueryPool; | |
| extern PFN_vkGetQueryPoolResults vkGetQueryPoolResults; | |
| extern PFN_vkCmdBeginQuery vkCmdBeginQuery; | |
| extern PFN_vkCmdEndQuery vkCmdEndQuery; | |
| extern PFN_vkCmdResetQueryPool vkCmdResetQueryPool; | |
| extern PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults; | |
| extern PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties; | |
| extern PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements; | |
| extern PFN_vkQueueBindSparse vkQueueBindSparse; | |
| extern PFN_vkCmdBeginRendering vkCmdBeginRendering; | |
| extern PFN_vkCmdEndRendering vkCmdEndRendering; | |
| extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; | |
| extern PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; | |
| extern PFN_vkCmdFillBuffer vkCmdFillBuffer; | |
| extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; | |
| extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; | |
| extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; | |
| extern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; | |
| extern PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; | |
| extern PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; | |
| extern PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; | |
| extern PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; | |
| extern PFN_vkQueuePresentKHR vkQueuePresentKHR; | |
| extern PFN_vkResetCommandBuffer vkResetCommandBuffer; | |
| extern PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties; | |
| namespace util_android { | |
| /* @brief Touch control thresholds from Android NDK samples */ | |
| const int32_t DOUBLE_TAP_TIMEOUT = 300 * 1000000; | |
| const int32_t TAP_TIMEOUT = 180 * 1000000; | |
| const int32_t DOUBLE_TAP_SLOP = 100; | |
| const int32_t TAP_SLOP = 8; | |
| /** @brief Density of the device screen (in DPI) */ | |
| extern int32_t screenDensity; | |
| bool loadVulkanLibrary(); | |
| void loadVulkanFunctions(VkInstance instance); | |
| void freeVulkanLibrary(); | |
| void getDeviceConfig(); | |
| void showAlert(const char* message); | |
| } | |
| #define LOG(...) LOGI(__VA_ARGS__) | |
| void android_main(android_app* state) { | |
| androidapp = state; | |
| androidapp->onAppCmd = [] (android_app *app, int32_t cmd) { | |
| if (cmd == APP_CMD_INIT_WINDOW) { | |
| entry_main(); | |
| ANativeActivity_finish(app->activity); | |
| } | |
| }; | |
| int ident, events; | |
| struct android_poll_source* source; | |
| while ((ident = ALooper_pollOnce(-1, NULL, &events, (void**)&source)) > ALOOPER_POLL_TIMEOUT) { | |
| if (source != NULL) { | |
| source->process(androidapp, source); | |
| } | |
| if (androidapp->destroyRequested != 0) { | |
| break; | |
| } | |
| } | |
| } | |
| #else | |
| #include <stdio.h> | |
| #define LOG(...) printf("\n" __VA_ARGS__) | |
| int main(int argc, char* argv[]) { | |
| entry_main(); | |
| return 0; | |
| } | |
| #endif | |
| #if defined(_WIN32) | |
| #pragma comment(linker, "/subsystem:console") | |
| #endif | |
| #include "vulkan/vulkan.h" | |
| #include <source_location> | |
| #include <format> | |
| #include <vector> | |
| #include <span> | |
| #include <string_view> | |
| #include <iostream> | |
| #include <fstream> | |
| #include <limits> | |
| #include <cstring> | |
| #undef assert | |
| void assert(bool res, const char* msg = "Assertion failed", const std::source_location loc = std::source_location::current()) | |
| { | |
| if (!res) { | |
| auto message = std::format("Fatal: {} in {}:{} ({})", | |
| msg, loc.file_name(), loc.line(), loc.function_name()); | |
| // Exit Fatal | |
| #if defined(_WIN32) | |
| MessageBox(NULL, message.c_str(), NULL, MB_OK | MB_ICONERROR); | |
| #elif defined(__ANDROID__) | |
| LOGE("Fatal error: %s", message.c_str()); | |
| util_android::showAlert(message.c_str()); | |
| #endif | |
| std::cerr << message << "\n"; | |
| #if !defined(__ANDROID__) | |
| exit(-1); | |
| #endif | |
| } | |
| } | |
| void assert(VkResult res, std::source_location loc = std::source_location::current()) | |
| { | |
| const auto to_cstr = [](VkResult err) -> const char* { | |
| if (err == VK_SUCCESS) return nullptr; | |
| switch (err) { | |
| #define STR(r) case VK_##r: return "VkResult is "#r | |
| STR(NOT_READY); | |
| STR(TIMEOUT); | |
| STR(EVENT_SET); | |
| STR(EVENT_RESET); | |
| STR(INCOMPLETE); | |
| STR(ERROR_OUT_OF_HOST_MEMORY); | |
| STR(ERROR_OUT_OF_DEVICE_MEMORY); | |
| STR(ERROR_INITIALIZATION_FAILED); | |
| STR(ERROR_DEVICE_LOST); | |
| STR(ERROR_MEMORY_MAP_FAILED); | |
| STR(ERROR_LAYER_NOT_PRESENT); | |
| STR(ERROR_EXTENSION_NOT_PRESENT); | |
| STR(ERROR_FEATURE_NOT_PRESENT); | |
| STR(ERROR_INCOMPATIBLE_DRIVER); | |
| STR(ERROR_TOO_MANY_OBJECTS); | |
| STR(ERROR_FORMAT_NOT_SUPPORTED); | |
| STR(ERROR_SURFACE_LOST_KHR); | |
| STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); | |
| STR(SUBOPTIMAL_KHR); | |
| STR(ERROR_OUT_OF_DATE_KHR); | |
| STR(ERROR_INCOMPATIBLE_DISPLAY_KHR); | |
| STR(ERROR_VALIDATION_FAILED_EXT); | |
| STR(ERROR_INVALID_SHADER_NV); | |
| STR(ERROR_INCOMPATIBLE_SHADER_BINARY_EXT); | |
| #undef STR | |
| default: return "UNKNOWN_ERROR"; | |
| } | |
| }; | |
| assert(res == VK_SUCCESS, to_cstr(res), loc); | |
| } | |
| VkShaderModule loadShader( | |
| const char *fileName, VkDevice device | |
| #if defined(__ANDROID__) | |
| , AAssetManager* assetManager = androidapp->activity->assetManager | |
| // Android shaders are stored as assets in the apk | |
| // So they need to be loaded via the asset manager | |
| #endif | |
| ) { | |
| #if defined(__ANDROID__) | |
| // Load shader from compressed asset | |
| AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING); | |
| assert(asset); | |
| const size_t size = AAsset_getLength(asset); | |
| char *shaderCode = new char[size]; | |
| AAsset_read(asset, shaderCode, size); | |
| AAsset_close(asset); | |
| #else | |
| std::ifstream is(fileName, std::ios::binary | std::ios::in | std::ios::ate); | |
| assert (is.is_open() == true); | |
| const size_t size = is.tellg(); | |
| char* shaderCode = new char[size]; | |
| is.seekg(0, std::ios::beg); | |
| is.read(shaderCode, size); | |
| is.close(); | |
| #endif | |
| assert(size > 0); | |
| VkShaderModule shaderModule; | |
| VkShaderModuleCreateInfo moduleCreateInfo{}; | |
| moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
| moduleCreateInfo.codeSize = size; | |
| moduleCreateInfo.pCode = (uint32_t*)shaderCode; | |
| assert(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule)); | |
| delete[] shaderCode; | |
| return shaderModule; | |
| } | |
| #define VALIDATION 1 | |
| struct VulkanContext_t{ | |
| VkInstance instance; | |
| #if VALIDATION | |
| VkDebugReportCallbackEXT debug_report_callback; | |
| #endif | |
| }; | |
| static bool find_extn(std::string_view name, std::span<VkExtensionProperties> extns) { | |
| for (auto& extn : extns) { | |
| if (name == std::string_view(extn.extensionName)) return true; | |
| } | |
| return false; | |
| } | |
| VulkanContext_t* VulkanContext() { | |
| #if defined(__ANDROID__) | |
| vks::android::loadVulkanLibrary(); | |
| #endif | |
| const char* enable_extensions[] { | |
| #if VALIDATION | |
| VK_EXT_DEBUG_REPORT_EXTENSION_NAME, | |
| VK_EXT_DEBUG_UTILS_EXTENSION_NAME, | |
| #endif | |
| }; | |
| u32 instance_extension_count = 0; | |
| vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr); | |
| std::vector<VkExtensionProperties> instance_extensions(instance_extension_count); | |
| vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions.data()); | |
| LOG("Enabled Vulkan extensions:"); | |
| for (auto& extn : enable_extensions) { | |
| assert(find_extn(extn, instance_extensions), std::format("Required vk extension '{}' not available", extn).c_str()); | |
| LOG("- %s", extn); | |
| } | |
| #if VALIDATION | |
| const char* validation_layers[] { "VK_LAYER_KHRONOS_validation" }; | |
| u32 instance_layer_count = 0; | |
| vkEnumerateInstanceLayerProperties(&instance_layer_count, nullptr); | |
| std::vector<VkLayerProperties> instance_layers(instance_layer_count); | |
| vkEnumerateInstanceLayerProperties(&instance_layer_count, instance_layers.data()); | |
| const auto find_layer = [](std::string_view name, std::span<VkLayerProperties> layers) { | |
| for (auto& layer : layers) { | |
| if (std::string_view(layer.layerName) == name) return true; | |
| } | |
| return false; | |
| }; | |
| LOG("Enabled Vulkan layers:"); | |
| for (auto& layer : validation_layers) { | |
| assert(find_layer(layer, instance_layers), std::format("Required vk layer '{}' not available", layer).c_str()); | |
| LOG("- %s", layer); | |
| } | |
| #endif | |
| VkApplicationInfo const app_info { | |
| .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, | |
| .pApplicationName = "Vulkan headless example", | |
| .pEngineName = "VulkanExample", | |
| .apiVersion = VK_API_VERSION_1_1, | |
| }; | |
| VkInstanceCreateInfo const instance_info { | |
| .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, | |
| .pApplicationInfo = &app_info, | |
| #if VALIDATION | |
| .enabledLayerCount = std::size(validation_layers), | |
| .ppEnabledLayerNames = validation_layers, | |
| #endif | |
| .enabledExtensionCount = std::size(enable_extensions), | |
| .ppEnabledExtensionNames = enable_extensions, | |
| }; | |
| VkInstance instance; | |
| assert(vkCreateInstance(&instance_info, nullptr, &instance)); | |
| #if defined(__ANDROID__) | |
| vks::android::loadVulkanFunctions(instance); | |
| #endif | |
| #if VALIDATION | |
| auto vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); | |
| assert(vkCreateDebugReportCallbackEXT != nullptr); | |
| auto vk_debug_msg_callback = [](VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, u64 object, u64 location, s32 code, const char* layerPrefix, const char* msg, void* userData) -> VkBool32 { | |
| LOG("[VALIDATION]: %s - %s", layerPrefix, msg); | |
| return VK_FALSE; | |
| }; | |
| VkDebugReportCallbackCreateInfoEXT const callback_info = { | |
| .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, | |
| .flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT, | |
| .pfnCallback = vk_debug_msg_callback, | |
| }; | |
| VkDebugReportCallbackEXT debug_report_callback; | |
| assert(vkCreateDebugReportCallbackEXT(instance, &callback_info, nullptr, &debug_report_callback)); | |
| #endif | |
| return new VulkanContext_t{ | |
| .instance = instance, | |
| .debug_report_callback = debug_report_callback, | |
| }; | |
| } | |
| void destroy(VulkanContext_t* ctx) { | |
| #if VALIDATION | |
| auto vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(ctx->instance, "vkDestroyDebugReportCallbackEXT")); | |
| assert(vkDestroyDebugReportCallback != nullptr); | |
| vkDestroyDebugReportCallback(ctx->instance, ctx->debug_report_callback, nullptr); | |
| #endif | |
| vkDestroyInstance(ctx->instance, nullptr); | |
| #if defined(__ANDROID__) | |
| vks::android::freeVulkanLibrary(); | |
| #endif | |
| delete ctx; | |
| } | |
| struct VulkanDevice{ | |
| VulkanContext_t& ctx; | |
| VkPhysicalDevice physical_device; | |
| u32 graphics_queue_family_index; | |
| VkDevice logical_device; | |
| VkQueue graphics_queue; | |
| VkCommandPool command_pool; | |
| VkCommandBuffer command_buffer; | |
| }; | |
| namespace internal { | |
| static u32 get_mem_type_idx(VkPhysicalDevice physical_device, u32 mem_type_bits, VkMemoryPropertyFlags properties){ | |
| VkPhysicalDeviceMemoryProperties device_mem_props; | |
| vkGetPhysicalDeviceMemoryProperties(physical_device, &device_mem_props); | |
| for (uint32_t i = 0; i < device_mem_props.memoryTypeCount; i++) { | |
| if ((mem_type_bits & 1) == 1) { | |
| if ((device_mem_props.memoryTypes[i].propertyFlags & properties) == properties) { | |
| return i; | |
| } | |
| } | |
| mem_type_bits >>= 1; | |
| } | |
| return u32(0); | |
| } | |
| static VkCommandBuffer create_a_command_buffer(VkDevice logical_device, VkCommandPool command_pool){ | |
| VkCommandBuffer command_buffer; | |
| VkCommandBufferAllocateInfo const command_buffer_info { | |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, | |
| .commandPool = command_pool, | |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, | |
| .commandBufferCount = 1, | |
| }; | |
| assert(vkAllocateCommandBuffers(logical_device, &command_buffer_info, &command_buffer)); | |
| VkCommandBufferBeginInfo const begin_info { | |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, | |
| }; | |
| assert(vkBeginCommandBuffer(command_buffer, &begin_info)); | |
| return command_buffer; | |
| } | |
| static void submit_a_command_buffer(VulkanDevice* device, VkCommandBuffer command_buffer, bool wait = false){ | |
| VkSubmitInfo const submit_info { | |
| .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, | |
| .commandBufferCount = 1, | |
| .pCommandBuffers = &command_buffer, | |
| }; | |
| VkFence fence = VK_NULL_HANDLE; | |
| VkFenceCreateInfo const fence_info { | |
| .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, | |
| .flags = VK_FENCE_CREATE_SIGNALED_BIT, | |
| }; | |
| if (wait) assert(vkCreateFence(device->logical_device, &fence_info, nullptr, &fence)); | |
| assert(vkQueueSubmit(device->graphics_queue, 1, &submit_info, fence)); | |
| if (wait) { | |
| assert(vkWaitForFences(device->logical_device, 1, &fence, VK_TRUE, UINT64_MAX)); | |
| vkDestroyFence(device->logical_device, fence, nullptr); | |
| } | |
| } | |
| } | |
| VulkanDevice* device(VulkanContext_t* ctx){ | |
| // Select the best physical device | |
| VkPhysicalDevice physical_device; | |
| { | |
| u32 physical_device_count = 0; | |
| vkEnumeratePhysicalDevices(ctx->instance, &physical_device_count, nullptr); | |
| assert(physical_device_count > 0); | |
| std::vector<VkPhysicalDevice> physical_devices(physical_device_count); | |
| std::vector<u32> physical_devices_score(physical_device_count); | |
| vkEnumeratePhysicalDevices(ctx->instance, &physical_device_count, physical_devices.data()); | |
| for (u32 i = 0; i < physical_device_count; ++i) { | |
| VkPhysicalDeviceProperties physical_device_properties; | |
| vkGetPhysicalDeviceProperties(physical_devices[i], &physical_device_properties); | |
| // since we are not using extensions (like surface support etc.), no need to check for them | |
| switch (physical_device_properties.deviceType) { | |
| case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: physical_devices_score[i] = 1000; break; | |
| case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: physical_devices_score[i] = 500; break; | |
| case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: physical_devices_score[i] = 250; break; | |
| default: physical_devices_score[i] = 0; break; | |
| } | |
| } | |
| u32 best_physical_device_index = 0; | |
| for (u32 i = 1; i < physical_device_count; ++i) { | |
| if (physical_devices_score[i] > physical_devices_score[best_physical_device_index]) { | |
| best_physical_device_index = i; | |
| } | |
| } | |
| physical_device = physical_devices[best_physical_device_index]; | |
| } | |
| // Select graphics queue family | |
| u32 graphics_queue_family_index = std::numeric_limits<u32>::max(); | |
| { | |
| u32 queue_family_count; | |
| vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); | |
| std::vector<VkQueueFamilyProperties> queue_families(queue_family_count); | |
| vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.data()); | |
| for (u32 i = 0; i < queue_family_count; ++i) { | |
| if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | |
| graphics_queue_family_index = i; | |
| break; | |
| } | |
| } | |
| assert(graphics_queue_family_index != std::numeric_limits<u32>::max()); | |
| } | |
| // Create logical device & get graphics queue | |
| VkDevice logical_device; | |
| VkQueue graphics_queue; | |
| { | |
| float queue_priority = 1.0f; | |
| VkDeviceQueueCreateInfo const queue_info { | |
| .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, | |
| .queueFamilyIndex = graphics_queue_family_index, | |
| .queueCount = 1, | |
| .pQueuePriorities = &queue_priority, | |
| }; | |
| // let's see if VK_KHR_SPIRV_1_4_EXTENSION_NAME, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME really required | |
| VkDeviceCreateInfo const device_info { | |
| .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, | |
| .queueCreateInfoCount = 1, | |
| .pQueueCreateInfos = &queue_info, | |
| }; | |
| vkCreateDevice(physical_device, &device_info, nullptr, &logical_device); | |
| vkGetDeviceQueue(logical_device, graphics_queue_family_index, 0, &graphics_queue); | |
| } | |
| VkCommandPool command_pool; | |
| { | |
| VkCommandPoolCreateInfo const command_pool_info { | |
| .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, | |
| .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, | |
| .queueFamilyIndex = graphics_queue_family_index, | |
| }; | |
| vkCreateCommandPool(logical_device, &command_pool_info, nullptr, &command_pool); | |
| } | |
| return new VulkanDevice{ | |
| .ctx = *ctx, | |
| .physical_device = physical_device, | |
| .graphics_queue_family_index = graphics_queue_family_index, | |
| .logical_device = logical_device, | |
| .graphics_queue = graphics_queue, | |
| .command_pool = command_pool, | |
| // Ready to record commands | |
| .command_buffer = internal::create_a_command_buffer(logical_device, command_pool), | |
| }; | |
| } | |
| void submit(VulkanDevice* device, bool wait){ | |
| // TODO: use VulkanDevice fences/semaphores instead of like this. | |
| internal::submit_a_command_buffer(device, device->command_buffer, wait); | |
| vkResetCommandBuffer(device->command_buffer, 0); | |
| device->command_buffer = internal::create_a_command_buffer(device->logical_device, device->command_pool); | |
| } | |
| void destroy(VulkanDevice* device){ | |
| vkResetCommandBuffer(device->command_buffer, 0); | |
| vkDestroyDevice(device->logical_device, nullptr); | |
| delete device; | |
| } | |
| template<typename T> | |
| struct VulkanTexture{ | |
| VulkanDevice& device; | |
| const VkFormat format; | |
| VkImage image; | |
| VkDeviceMemory memory; | |
| VkImageView view; | |
| u32 wd, ht; | |
| }; | |
| template<typename T> | |
| VulkanTexture<T>* texture(VulkanDevice* device, const u32 width, const u32 height){ | |
| static_assert( | |
| sizeof(T) == sizeof(u8) | |
| || sizeof(T) == sizeof(u8[2]) // or u16 | |
| || sizeof(T) == sizeof(u8[3]) | |
| || sizeof(T) == sizeof(u8[4]) // or u16*2, u32 | |
| || sizeof(T) == sizeof(u8[2*3]) // or u16*3 | |
| || sizeof(T) == sizeof(u8[2*4]) // or u16*4, u32*2, u64 | |
| || sizeof(T) == sizeof(u8[4*3]) // or u32*3 | |
| || sizeof(T) == sizeof(u8[4*4]) // or u32*4, u64*2 | |
| || sizeof(T) == sizeof(u8[8*3]) // or u64*3 | |
| || sizeof(T) == sizeof(u8[8*4]) // or u64*4 | |
| ); | |
| constexpr VkFormat format = []{ | |
| const auto size = sizeof(T); | |
| static_assert(size == sizeof(u8[4]), "Not implemented yet!"); | |
| return VK_FORMAT_R8G8B8A8_UNORM; | |
| }(); | |
| VkImage image; | |
| { | |
| VkImageCreateInfo const image_info { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, | |
| .imageType = VK_IMAGE_TYPE_2D, | |
| .format = format, | |
| .extent = { | |
| .width = width, | |
| .height = height, | |
| .depth = 1, | |
| }, | |
| .mipLevels = 1, | |
| .arrayLayers = 1, | |
| .samples = VK_SAMPLE_COUNT_1_BIT, | |
| .tiling = VK_IMAGE_TILING_OPTIMAL, | |
| .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, | |
| }; | |
| assert(vkCreateImage(device->logical_device, &image_info, nullptr, &image)); | |
| } | |
| VkDeviceMemory memory; | |
| { | |
| VkMemoryRequirements mem_reqs; | |
| vkGetImageMemoryRequirements(device->logical_device, image, &mem_reqs); | |
| VkMemoryAllocateInfo const alloc_info { | |
| .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, | |
| .allocationSize = mem_reqs.size, | |
| .memoryTypeIndex = internal::get_mem_type_idx(device->physical_device, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), | |
| }; | |
| assert(vkAllocateMemory(device->logical_device, &alloc_info, nullptr, &memory)); | |
| assert(vkBindImageMemory(device->logical_device, image, memory, 0)); | |
| } | |
| VkImageView view; | |
| { | |
| VkImageViewCreateInfo const view_info { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, | |
| .image = image, | |
| .viewType = VK_IMAGE_VIEW_TYPE_2D, | |
| .format = format, | |
| .subresourceRange = { | |
| .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, | |
| .baseMipLevel = 0, | |
| .levelCount = 1, | |
| .baseArrayLayer = 0, | |
| .layerCount = 1, | |
| }, | |
| }; | |
| assert(vkCreateImageView(device->logical_device, &view_info, nullptr, &view)); | |
| } | |
| return new VulkanTexture<T>{ | |
| .device = *device, | |
| .format = format, | |
| .image = image, | |
| .memory = memory, | |
| .view = view, | |
| .wd = width, | |
| .ht = height, | |
| }; | |
| } | |
| template<typename T> | |
| T* get(const VulkanTexture<T>* texture){ return nullptr; } | |
| template<typename T> | |
| void destroy(VulkanTexture<T>* texture){} | |
| namespace Triangle { | |
| #define DEPTH_ENABLE 1 | |
| struct Shader{ | |
| static constexpr u32 output_count = 1; | |
| // TODO: uniform bindings | |
| static constexpr u32 size_push_constant = sizeof(mat4); | |
| static constexpr VkFormat output_formats[output_count] { VK_FORMAT_R8G8B8A8_UNORM }; | |
| #if DEPTH_ENABLE | |
| static constexpr VkFormat depth_format = VK_FORMAT_D32_SFLOAT; | |
| #endif | |
| VulkanDevice& device; | |
| VkRenderPass render_pass; | |
| VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE; | |
| VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; | |
| VkPipelineCache pipeline_cache = VK_NULL_HANDLE; | |
| VkShaderModule shader_modules[ 2 /* vertex, fragment */ ] = { VK_NULL_HANDLE }; | |
| VkPipeline pipeline = VK_NULL_HANDLE; | |
| // lazily created | |
| VkFramebuffer framebuffer = VK_NULL_HANDLE; | |
| VkImageView image_views[output_count + DEPTH_ENABLE] = { VK_NULL_HANDLE }; | |
| #if DEPTH_ENABLE | |
| VkImage depth_image = VK_NULL_HANDLE; | |
| VkDeviceMemory depth_memory = VK_NULL_HANDLE; | |
| VkImageView& depth_image_view = image_views[output_count]; // last one | |
| #endif | |
| VkExtent2D extent = { 0, 0 }; | |
| // set by set() | |
| VkBuffer vertex_buffer = VK_NULL_HANDLE, index_buffer = VK_NULL_HANDLE; | |
| VkDeviceMemory vertex_memory = VK_NULL_HANDLE, index_memory = VK_NULL_HANDLE; | |
| u32 index_count = 0; | |
| }; | |
| Shader* create(VulkanDevice* device){ | |
| VkRenderPass render_pass; | |
| { | |
| VkAttachmentDescription const attachment_descriptions[Shader::output_count + DEPTH_ENABLE] { | |
| { | |
| .format = Shader::output_formats[0], | |
| .samples = VK_SAMPLE_COUNT_1_BIT, | |
| .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, | |
| .storeOp = VK_ATTACHMENT_STORE_OP_STORE, | |
| .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, | |
| .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, | |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, | |
| .finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, | |
| }, | |
| #if DEPTH_ENABLE | |
| { | |
| .format = Shader::depth_format, | |
| .samples = VK_SAMPLE_COUNT_1_BIT, | |
| .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, | |
| .storeOp = VK_ATTACHMENT_STORE_OP_STORE, | |
| .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, | |
| .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, | |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, | |
| .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, | |
| } | |
| #endif | |
| }; | |
| VkAttachmentReference color_reference[Shader::output_count] { | |
| { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }, | |
| }; | |
| #if DEPTH_ENABLE | |
| VkAttachmentReference depth_reference { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; | |
| #endif | |
| VkSubpassDescription const subpass_this { | |
| .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, | |
| .colorAttachmentCount = Shader::output_count, | |
| .pColorAttachments = color_reference, | |
| #if DEPTH_ENABLE | |
| .pDepthStencilAttachment = &depth_reference, | |
| #endif | |
| }; | |
| VkSubpassDependency const subpass_in_out[] { | |
| { // in | |
| .srcSubpass = VK_SUBPASS_EXTERNAL, | |
| .dstSubpass = 0, | |
| .srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, | |
| .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, | |
| .srcAccessMask = VK_ACCESS_MEMORY_READ_BIT, | |
| .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, | |
| .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, | |
| }, | |
| { // out | |
| .srcSubpass = 0, | |
| .dstSubpass = VK_SUBPASS_EXTERNAL, | |
| .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, | |
| .dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, | |
| .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, | |
| .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, | |
| .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, | |
| } | |
| }; | |
| VkRenderPassCreateInfo const render_pass_info { | |
| .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, | |
| .attachmentCount = static_cast<u32>(std::size(attachment_descriptions)), | |
| .pAttachments = attachment_descriptions, | |
| .subpassCount = 1, | |
| .pSubpasses = &subpass_this, | |
| .dependencyCount = static_cast<u32>(std::size(subpass_in_out)), | |
| .pDependencies = subpass_in_out, | |
| }; | |
| assert(vkCreateRenderPass(device->logical_device, &render_pass_info, nullptr, &render_pass)); | |
| } | |
| VkDescriptorSetLayout descriptor_set_layout; | |
| { | |
| VkDescriptorSetLayoutCreateInfo const layout_info { | |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, | |
| .bindingCount = 0, | |
| .pBindings = nullptr, | |
| }; | |
| assert(vkCreateDescriptorSetLayout(device->logical_device, &layout_info, nullptr, &descriptor_set_layout)); | |
| } | |
| VkPipelineLayout pipeline_layout; | |
| { | |
| VkPushConstantRange push_constant_range { | |
| .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, | |
| .offset = 0, | |
| .size = Shader::size_push_constant, | |
| }; | |
| VkPipelineLayoutCreateInfo const layout_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, | |
| .pushConstantRangeCount = 1, | |
| .pPushConstantRanges = &push_constant_range, | |
| }; | |
| assert(vkCreatePipelineLayout(device->logical_device, &layout_info, nullptr, &pipeline_layout)); | |
| } | |
| VkPipelineCache pipeline_cache; | |
| { | |
| VkPipelineCacheCreateInfo const cache_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, | |
| }; | |
| assert(vkCreatePipelineCache(device->logical_device, &cache_info, nullptr, &pipeline_cache)); | |
| } | |
| VkShaderModule | |
| vertex_shader_module = loadShader("./triangle.vert.spv", device->logical_device), | |
| fragment_shader_module = loadShader("./triangle.frag.spv", device->logical_device); | |
| assert(vertex_shader_module != VK_NULL_HANDLE); | |
| assert(fragment_shader_module != VK_NULL_HANDLE); | |
| VkPipeline pipeline; | |
| { | |
| VkPipelineShaderStageCreateInfo const shader_stages[] { | |
| { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vertex_shader_module, .pName = "main" }, | |
| { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = fragment_shader_module, .pName = "main" }, | |
| }; | |
| VkVertexInputBindingDescription const vertex_input_binding_description { | |
| .binding = 0, | |
| .stride = sizeof(Vertex_t), | |
| .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, | |
| }; | |
| VkVertexInputAttributeDescription const vertex_input_attribute_descriptions[] { | |
| { .location = 0, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex_t, pos) }, | |
| { .location = 1, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex_t, color) }, | |
| }; | |
| VkPipelineVertexInputStateCreateInfo const vertex_input_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, | |
| .vertexBindingDescriptionCount = 1, | |
| .pVertexBindingDescriptions = &vertex_input_binding_description, | |
| .vertexAttributeDescriptionCount = static_cast<u32>(std::size(vertex_input_attribute_descriptions)), | |
| .pVertexAttributeDescriptions = vertex_input_attribute_descriptions, | |
| }; | |
| VkPipelineInputAssemblyStateCreateInfo const input_assembly_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, | |
| .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, | |
| }; | |
| VkPipelineRasterizationStateCreateInfo const rasterization_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, | |
| .depthClampEnable = VK_FALSE, | |
| .polygonMode = VK_POLYGON_MODE_FILL, | |
| .cullMode = VK_CULL_MODE_BACK_BIT, | |
| .frontFace = VK_FRONT_FACE_CLOCKWISE, | |
| .lineWidth = 1.0f, | |
| }; | |
| VkPipelineColorBlendAttachmentState const color_blend_attachment_state { | |
| .blendEnable = VK_FALSE, | |
| .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, | |
| }; | |
| VkPipelineColorBlendStateCreateInfo const color_blend_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, | |
| .attachmentCount = 1, | |
| .pAttachments = &color_blend_attachment_state, | |
| }; | |
| VkPipelineDepthStencilStateCreateInfo const depth_stencil_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, | |
| .depthTestEnable = VK_TRUE, | |
| .depthWriteEnable = VK_TRUE, | |
| .depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL, | |
| }; | |
| VkPipelineViewportStateCreateInfo const viewport_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, | |
| .viewportCount = 1, | |
| .pViewports = nullptr, | |
| .scissorCount = 1, | |
| .pScissors = nullptr, | |
| }; | |
| VkPipelineMultisampleStateCreateInfo const multisample_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, | |
| .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, | |
| }; | |
| VkDynamicState dynamic_states[] { | |
| VK_DYNAMIC_STATE_VIEWPORT, | |
| VK_DYNAMIC_STATE_SCISSOR, | |
| }; | |
| VkPipelineDynamicStateCreateInfo const dynamic_state_info { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, | |
| .dynamicStateCount = static_cast<u32>(std::size(dynamic_states)), | |
| .pDynamicStates = dynamic_states, | |
| }; | |
| VkGraphicsPipelineCreateInfo const pipeline_info { | |
| .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, | |
| .stageCount = static_cast<u32>(std::size(shader_stages)), | |
| .pStages = shader_stages, | |
| .pVertexInputState = &vertex_input_state_info, | |
| .pInputAssemblyState = &input_assembly_state_info, | |
| .pViewportState = &viewport_state_info, | |
| .pRasterizationState = &rasterization_state_info, | |
| .pMultisampleState = &multisample_state_info, | |
| .pDepthStencilState = &depth_stencil_state_info, | |
| .pColorBlendState = &color_blend_state_info, | |
| .pDynamicState = &dynamic_state_info, | |
| .layout = pipeline_layout, | |
| .renderPass = render_pass, | |
| .basePipelineHandle = VK_NULL_HANDLE, | |
| .basePipelineIndex = -1, | |
| }; | |
| assert(vkCreateGraphicsPipelines(device->logical_device, pipeline_cache, 1, &pipeline_info, nullptr, &pipeline)); | |
| } | |
| return new Shader{ | |
| .device = *device, | |
| .render_pass = render_pass, | |
| .descriptor_set_layout = descriptor_set_layout, | |
| .pipeline_layout = pipeline_layout, | |
| .pipeline_cache = pipeline_cache, | |
| .shader_modules = { vertex_shader_module, fragment_shader_module }, | |
| .pipeline = pipeline, | |
| // will be created/reused in begin() | |
| .framebuffer = VK_NULL_HANDLE, | |
| }; | |
| } | |
| void destroy(Shader* shader){ | |
| vkDestroyRenderPass(shader->device.logical_device, shader->render_pass, nullptr); | |
| vkDestroyDescriptorSetLayout(shader->device.logical_device, shader->descriptor_set_layout, nullptr); | |
| vkDestroyPipelineLayout(shader->device.logical_device, shader->pipeline_layout, nullptr); | |
| vkDestroyPipelineCache(shader->device.logical_device, shader->pipeline_cache, nullptr); | |
| vkDestroyShaderModule(shader->device.logical_device, shader->shader_modules[0], nullptr); | |
| vkDestroyShaderModule(shader->device.logical_device, shader->shader_modules[1], nullptr); | |
| vkDestroyPipeline(shader->device.logical_device, shader->pipeline, nullptr); | |
| delete shader; | |
| } | |
| void begin(Shader* shader, VulkanTexture<u8_4>* output_1 /*, vec4 clear_color = CLEAR_COLOR_NONE, vec2 clear_depth = CLEAR_DEPTH_NONE*/){ | |
| // FOR each output texture (Shader::output_count) | |
| assert(output_1 != nullptr); | |
| assert(output_1->format == Shader::output_formats[0]); | |
| bool recreate_framebuffer = false; | |
| if (shader->image_views[0] != output_1->view) { | |
| shader->image_views[0] = output_1->view; | |
| recreate_framebuffer = true; | |
| } | |
| // Also assert each output texture is same extent | |
| VkExtent2D const new_extent { output_1->wd, output_1->ht }; | |
| if (shader->extent.width != new_extent.width || shader->extent.height != new_extent.height) { | |
| shader->extent = new_extent; | |
| // Resize depth attachment | |
| #if DEPTH_ENABLE | |
| recreate_framebuffer |= shader->depth_image != VK_NULL_HANDLE; | |
| if (shader->depth_image != VK_NULL_HANDLE) { | |
| vkDestroyImageView(shader->device.logical_device, shader->depth_image_view, nullptr); | |
| vkFreeMemory(shader->device.logical_device, shader->depth_memory, nullptr); | |
| vkDestroyImage(shader->device.logical_device, shader->depth_image, nullptr); | |
| } | |
| VkImageCreateInfo const image_info { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, | |
| .imageType = VK_IMAGE_TYPE_2D, | |
| .format = Shader::depth_format, | |
| .extent = { .width = new_extent.width, .height = new_extent.height, .depth = 1 }, | |
| .mipLevels = 1, | |
| .arrayLayers = 1, | |
| .samples = VK_SAMPLE_COUNT_1_BIT, | |
| .tiling = VK_IMAGE_TILING_OPTIMAL, | |
| .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | |
| }; | |
| assert(vkCreateImage(shader->device.logical_device, &image_info, nullptr, &shader->depth_image)); | |
| VkMemoryRequirements mem_reqs; | |
| vkGetImageMemoryRequirements(shader->device.logical_device, shader->depth_image, &mem_reqs); | |
| VkMemoryAllocateInfo const alloc_info { | |
| .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, | |
| .allocationSize = mem_reqs.size, | |
| .memoryTypeIndex = internal::get_mem_type_idx(shader->device.physical_device, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), | |
| }; | |
| assert(vkAllocateMemory(shader->device.logical_device, &alloc_info, nullptr, &shader->depth_memory)); | |
| assert(vkBindImageMemory(shader->device.logical_device, shader->depth_image, shader->depth_memory, 0)); | |
| VkImageViewCreateInfo const view_info { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, | |
| .image = shader->depth_image, | |
| .viewType = VK_IMAGE_VIEW_TYPE_2D, | |
| .format = Shader::depth_format, | |
| .subresourceRange = { | |
| .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | (Shader::depth_format >= VK_FORMAT_D16_UNORM_S8_UINT ? VK_IMAGE_ASPECT_STENCIL_BIT : 0), | |
| .baseMipLevel = 0, | |
| .levelCount = 1, | |
| .baseArrayLayer = 0, | |
| .layerCount = 1, | |
| }, | |
| }; | |
| assert(vkCreateImageView(shader->device.logical_device, &view_info, nullptr, &shader->depth_image_view)); | |
| #endif | |
| } | |
| if (recreate_framebuffer) { | |
| VkFramebufferCreateInfo const framebuffer_info { | |
| .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, | |
| .renderPass = shader->render_pass, | |
| .attachmentCount = static_cast<u32>(std::size(shader->image_views)), | |
| .pAttachments = shader->image_views, | |
| .width = shader->extent.width, | |
| .height = shader->extent.height, | |
| .layers = 1, | |
| }; | |
| assert(vkCreateFramebuffer(shader->device.logical_device, &framebuffer_info, nullptr, &shader->framebuffer)); | |
| } | |
| // NOW WE BEGIN RECORDING THE COMMAND BUFFER | |
| VkRenderPassBeginInfo const render_pass_begin_info { | |
| .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, | |
| .renderPass = shader->render_pass, | |
| .framebuffer = shader->framebuffer, | |
| .renderArea = { .extent = shader->extent }, | |
| .clearValueCount = 0, | |
| }; | |
| vkCmdBeginRenderPass(shader->device.command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); | |
| VkViewport const viewport { | |
| .x = 0, | |
| .y = 0, | |
| .width = static_cast<f32>(shader->extent.width), | |
| .height = static_cast<f32>(shader->extent.height), | |
| .minDepth = 0.0f, | |
| .maxDepth = 1.0f, | |
| }; | |
| vkCmdSetViewport(shader->device.command_buffer, 0, 1, &viewport); | |
| VkRect2D const scissor { | |
| .offset = { 0, 0 }, | |
| .extent = shader->extent, | |
| }; | |
| vkCmdSetScissor(shader->device.command_buffer, 0, 1, &scissor); | |
| vkCmdBindPipeline(shader->device.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, shader->pipeline); | |
| } | |
| void record(Shader* shader){ | |
| vkCmdBindVertexBuffers(shader->device.command_buffer, 0, 1, &shader->vertex_buffer, nullptr); | |
| vkCmdBindIndexBuffer(shader->device.command_buffer, shader->index_buffer, 0, VK_INDEX_TYPE_UINT32); | |
| vkCmdDrawIndexed(shader->device.command_buffer, shader->index_count, 1, 0, 0, 0); | |
| } | |
| void set(Shader* shader, const Vertex_t* vertex_buff, const u32 vertex_count, const u32* index_buff, const u32 index_count){ | |
| VkDeviceSize const vertices_size = vertex_count * sizeof(Vertex_t); | |
| VkDeviceSize const indices_size = index_count * sizeof(u32); | |
| auto create_buffer = [logical_device = shader->device.logical_device, physical_device = shader->device.physical_device] | |
| (VkBufferUsageFlags usage, VkMemoryPropertyFlags mem_properties, VkDeviceSize size, void *data, VkBuffer *out_buffer, VkDeviceMemory *out_memory){ | |
| assert(size > 0); | |
| VkBufferCreateInfo const buffer_info { | |
| .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | |
| .size = size, | |
| .usage = usage, | |
| .sharingMode = VK_SHARING_MODE_EXCLUSIVE, | |
| }; | |
| assert(vkCreateBuffer(logical_device, &buffer_info, nullptr, out_buffer)); | |
| VkMemoryRequirements mem_reqs; | |
| vkGetBufferMemoryRequirements(logical_device, *out_buffer, &mem_reqs); | |
| VkMemoryAllocateInfo const alloc_info { | |
| .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, | |
| .allocationSize = mem_reqs.size, | |
| .memoryTypeIndex = internal::get_mem_type_idx(physical_device, mem_reqs.memoryTypeBits, mem_properties), | |
| }; | |
| assert(vkAllocateMemory(logical_device, &alloc_info, nullptr, out_memory)); | |
| assert(vkBindBufferMemory(logical_device, *out_buffer, *out_memory, 0)); | |
| if (data != nullptr) { | |
| void *mapped; | |
| assert(vkMapMemory(logical_device, *out_memory, 0, size, 0, &mapped)); | |
| std::memcpy(mapped, data, size); | |
| vkUnmapMemory(logical_device, *out_memory); | |
| } | |
| }; | |
| VkBuffer staging_buffer1, staging_buffer2, vertex_buffer, index_buffer; | |
| VkDeviceMemory staging_memory1, staging_memory2, vertex_memory, index_memory; | |
| create_buffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertices_size, (void*)vertex_buff, &staging_buffer1, &staging_memory1); | |
| create_buffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indices_size, (void*)index_buff, &staging_buffer2, &staging_memory2); | |
| create_buffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertices_size, nullptr, &vertex_buffer, &vertex_memory); | |
| create_buffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indices_size, nullptr, &index_buffer, &index_memory); | |
| VkCommandBuffer copy_cmd = internal::create_a_command_buffer(shader->device.logical_device, shader->device.command_pool); | |
| VkBufferCopy copy_region { | |
| .srcOffset = 0, | |
| .dstOffset = 0, | |
| .size = vertices_size, | |
| }; | |
| vkCmdCopyBuffer(copy_cmd, staging_buffer1, vertex_buffer, 1, ©_region); | |
| copy_region.size = indices_size; | |
| vkCmdCopyBuffer(copy_cmd, staging_buffer2, index_buffer, 1, ©_region); | |
| assert(vkEndCommandBuffer(copy_cmd)); | |
| internal::submit_a_command_buffer(&shader->device, copy_cmd, true); | |
| vkDestroyBuffer(shader->device.logical_device, staging_buffer1, nullptr); | |
| vkDestroyBuffer(shader->device.logical_device, staging_buffer2, nullptr); | |
| vkFreeMemory(shader->device.logical_device, staging_memory1, nullptr); | |
| vkFreeMemory(shader->device.logical_device, staging_memory2, nullptr); | |
| shader->vertex_buffer = vertex_buffer; | |
| shader->vertex_memory = vertex_memory; | |
| shader->index_buffer = index_buffer; | |
| shader->index_memory = index_memory; | |
| shader->index_count = index_count; | |
| } | |
| // per parameter | |
| template<> | |
| void set<ShaderParams::mvp, mat4>(Shader* shader, const mat4* value){ | |
| vkCmdPushConstants(shader->device.command_buffer, shader->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(*value), value); | |
| } | |
| } | |
| void getMVP(const vec3& triangle_position, mat4* mvp){} | |
| void writeImage(const u8_4* bytes, const u32 width, const u32 height){} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Vulkan Example - Minimal headless rendering example | |
| * | |
| * Copyright (C) 2017-2025 by Sascha Willems - www.saschawillems.de | |
| * | |
| * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) | |
| */ | |
| #if defined(_WIN32) | |
| #pragma comment(linker, "/subsystem:console") | |
| #elif defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| #include <android/native_activity.h> | |
| #include <android/asset_manager.h> | |
| #include <android_native_app_glue.h> | |
| #include <android/log.h> | |
| #include "VulkanAndroid.h" | |
| #endif | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <assert.h> | |
| #include <vector> | |
| #include <array> | |
| #include <iostream> | |
| #include <fstream> | |
| #include <algorithm> | |
| #include <format> | |
| #include <cmath> | |
| #define GLM_FORCE_RADIANS | |
| #define GLM_FORCE_DEPTH_ZERO_TO_ONE | |
| #include <glm/glm.hpp> | |
| #include <glm/gtc/matrix_transform.hpp> | |
| #if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) | |
| #define VK_ENABLE_BETA_EXTENSIONS | |
| #endif | |
| #include <vulkan/vulkan.h> | |
| // Macro to check and display Vulkan return results | |
| const char* errorString(VkResult errorCode) | |
| { | |
| switch (errorCode) | |
| { | |
| #define STR(r) case VK_ ##r: return #r | |
| STR(NOT_READY); | |
| STR(TIMEOUT); | |
| STR(EVENT_SET); | |
| STR(EVENT_RESET); | |
| STR(INCOMPLETE); | |
| STR(ERROR_OUT_OF_HOST_MEMORY); | |
| STR(ERROR_OUT_OF_DEVICE_MEMORY); | |
| STR(ERROR_INITIALIZATION_FAILED); | |
| STR(ERROR_DEVICE_LOST); | |
| STR(ERROR_MEMORY_MAP_FAILED); | |
| STR(ERROR_LAYER_NOT_PRESENT); | |
| STR(ERROR_EXTENSION_NOT_PRESENT); | |
| STR(ERROR_FEATURE_NOT_PRESENT); | |
| STR(ERROR_INCOMPATIBLE_DRIVER); | |
| STR(ERROR_TOO_MANY_OBJECTS); | |
| STR(ERROR_FORMAT_NOT_SUPPORTED); | |
| STR(ERROR_SURFACE_LOST_KHR); | |
| STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); | |
| STR(SUBOPTIMAL_KHR); | |
| STR(ERROR_OUT_OF_DATE_KHR); | |
| STR(ERROR_INCOMPATIBLE_DISPLAY_KHR); | |
| STR(ERROR_VALIDATION_FAILED_EXT); | |
| STR(ERROR_INVALID_SHADER_NV); | |
| STR(ERROR_INCOMPATIBLE_SHADER_BINARY_EXT); | |
| #undef STR | |
| default: | |
| return "UNKNOWN_ERROR"; | |
| } | |
| } | |
| void exitFatal(const std::string& message, int32_t exitCode) | |
| { | |
| #if defined(_WIN32) | |
| if (!errorModeSilent) { | |
| MessageBox(NULL, message.c_str(), NULL, MB_OK | MB_ICONERROR); | |
| } | |
| #elif defined(__ANDROID__) | |
| LOGE("Fatal error: %s", message.c_str()); | |
| vks::android::showAlert(message.c_str()); | |
| #endif | |
| std::cerr << message << "\n"; | |
| #if !defined(__ANDROID__) | |
| exit(exitCode); | |
| #endif | |
| } | |
| #if defined(__ANDROID__) | |
| #define VK_CHECK_RESULT(f) \ | |
| { \ | |
| VkResult res = (f); \ | |
| if (res != VK_SUCCESS) \ | |
| { \ | |
| std::string message = std::format("Fatal : VkResult is {} in {} at line {}", errorString(res), __FILE__, __LINE__); \ | |
| LOGE("%s", message.c_str()); \ | |
| exitFatal(message, -1); \ | |
| } \ | |
| } | |
| #else | |
| #define VK_CHECK_RESULT(f) \ | |
| { \ | |
| VkResult res = (f); \ | |
| if (res != VK_SUCCESS) \ | |
| { \ | |
| std::string message = std::format("Fatal : VkResult is {} in {} at line {}", errorString(res), __FILE__, __LINE__); \ | |
| exitFatal(message, -1); \ | |
| } \ | |
| } | |
| #endif | |
| #if defined(__ANDROID__) | |
| // Android shaders are stored as assets in the apk | |
| // So they need to be loaded via the asset manager | |
| VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device) | |
| { | |
| // Load shader from compressed asset | |
| AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING); | |
| assert(asset); | |
| size_t size = AAsset_getLength(asset); | |
| assert(size > 0); | |
| char *shaderCode = new char[size]; | |
| AAsset_read(asset, shaderCode, size); | |
| AAsset_close(asset); | |
| VkShaderModule shaderModule; | |
| VkShaderModuleCreateInfo moduleCreateInfo; | |
| moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
| moduleCreateInfo.pNext = NULL; | |
| moduleCreateInfo.codeSize = size; | |
| moduleCreateInfo.pCode = (uint32_t*)shaderCode; | |
| moduleCreateInfo.flags = 0; | |
| VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule)); | |
| delete[] shaderCode; | |
| return shaderModule; | |
| } | |
| #else | |
| VkShaderModule loadShader(const char *fileName, VkDevice device) | |
| { | |
| std::ifstream is(fileName, std::ios::binary | std::ios::in | std::ios::ate); | |
| if (is.is_open()) | |
| { | |
| size_t size = is.tellg(); | |
| is.seekg(0, std::ios::beg); | |
| char* shaderCode = new char[size]; | |
| is.read(shaderCode, size); | |
| is.close(); | |
| assert(size > 0); | |
| VkShaderModule shaderModule; | |
| VkShaderModuleCreateInfo moduleCreateInfo{}; | |
| moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
| moduleCreateInfo.codeSize = size; | |
| moduleCreateInfo.pCode = (uint32_t*)shaderCode; | |
| VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule)); | |
| delete[] shaderCode; | |
| return shaderModule; | |
| } | |
| else | |
| { | |
| std::cerr << "Error: Could not open shader file \"" << fileName << "\"" << "\n"; | |
| return VK_NULL_HANDLE; | |
| } | |
| } | |
| #endif | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| android_app* androidapp; | |
| #endif | |
| #define DEBUG (!NDEBUG) | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| #define LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "vulkanExample", __VA_ARGS__)) | |
| #else | |
| #define LOG(...) printf(__VA_ARGS__) | |
| #endif | |
| static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback( | |
| VkDebugReportFlagsEXT flags, | |
| VkDebugReportObjectTypeEXT objectType, | |
| uint64_t object, | |
| size_t location, | |
| int32_t messageCode, | |
| const char* pLayerPrefix, | |
| const char* pMessage, | |
| void* pUserData) | |
| { | |
| LOG("[VALIDATION]: %s - %s\n", pLayerPrefix, pMessage); | |
| return VK_FALSE; | |
| } | |
| class VulkanExample | |
| { | |
| public: | |
| VkInstance instance; | |
| VkPhysicalDevice physicalDevice; | |
| VkDevice device; | |
| uint32_t queueFamilyIndex; | |
| VkPipelineCache pipelineCache; | |
| VkQueue queue; | |
| VkCommandPool commandPool; | |
| VkCommandBuffer commandBuffer; | |
| VkDescriptorSetLayout descriptorSetLayout; | |
| VkPipelineLayout pipelineLayout; | |
| VkPipeline pipeline; | |
| std::vector<VkShaderModule> shaderModules; | |
| VkBuffer vertexBuffer, indexBuffer; | |
| VkDeviceMemory vertexMemory, indexMemory; | |
| struct FrameBufferAttachment { | |
| VkImage image; | |
| VkDeviceMemory memory; | |
| VkImageView view; | |
| }; | |
| int32_t width, height; | |
| VkFramebuffer framebuffer; | |
| FrameBufferAttachment colorAttachment, depthAttachment; | |
| VkRenderPass renderPass; | |
| VkDebugReportCallbackEXT debugReportCallback{}; | |
| std::string shaderDir = "glsl"; | |
| uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) { | |
| VkPhysicalDeviceMemoryProperties deviceMemoryProperties; | |
| vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); | |
| for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) { | |
| if ((typeBits & 1) == 1) { | |
| if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { | |
| return i; | |
| } | |
| } | |
| typeBits >>= 1; | |
| } | |
| return 0; | |
| } | |
| VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkBuffer *buffer, VkDeviceMemory *memory, VkDeviceSize size, void *data = nullptr) | |
| { | |
| // Create the buffer handle | |
| VkBufferCreateInfo bufferCreateInfo { | |
| .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, | |
| .size = size, | |
| .usage = usageFlags, | |
| }; | |
| bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
| VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, buffer)); | |
| // Create the memory backing up the buffer handle | |
| VkMemoryRequirements memReqs; | |
| VkMemoryAllocateInfo memAlloc { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; | |
| vkGetBufferMemoryRequirements(device, *buffer, &memReqs); | |
| memAlloc.allocationSize = memReqs.size; | |
| memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, memoryPropertyFlags); | |
| VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, memory)); | |
| if (data != nullptr) { | |
| void *mapped; | |
| VK_CHECK_RESULT(vkMapMemory(device, *memory, 0, size, 0, &mapped)); | |
| memcpy(mapped, data, size); | |
| vkUnmapMemory(device, *memory); | |
| } | |
| VK_CHECK_RESULT(vkBindBufferMemory(device, *buffer, *memory, 0)); | |
| return VK_SUCCESS; | |
| } | |
| /* | |
| Submit command buffer to a queue and wait for fence until queue operations have been finished | |
| */ | |
| void submitWork(VkCommandBuffer cmdBuffer, VkQueue queue) | |
| { | |
| VkSubmitInfo submitInfo { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO }; | |
| submitInfo.commandBufferCount = 1; | |
| submitInfo.pCommandBuffers = &cmdBuffer; | |
| VkFenceCreateInfo fenceInfo { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; | |
| VkFence fence; | |
| VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence)); | |
| VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence)); | |
| VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX)); | |
| vkDestroyFence(device, fence, nullptr); | |
| } | |
| VulkanExample() | |
| { | |
| LOG("Running headless rendering example\n"); | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| LOG("loading vulkan lib"); | |
| vks::android::loadVulkanLibrary(); | |
| #endif | |
| // if (commandLineParser.isSet("shaders")) { | |
| // shaderDir = commandLineParser.getValueAsString("shaders", "glsl"); | |
| // } | |
| shaderDir = "slang"; // "glsl"; | |
| VkApplicationInfo appInfo = {}; | |
| appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | |
| appInfo.pApplicationName = "Vulkan headless example"; | |
| appInfo.pEngineName = "VulkanExample"; | |
| appInfo.apiVersion = VK_API_VERSION_1_0; | |
| // Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions | |
| if (shaderDir == "slang") { | |
| appInfo.apiVersion = VK_API_VERSION_1_1; | |
| } | |
| /* | |
| Vulkan instance creation (without surface extensions) | |
| */ | |
| VkInstanceCreateInfo instanceCreateInfo = {}; | |
| instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | |
| instanceCreateInfo.pApplicationInfo = &appInfo; | |
| uint32_t layerCount = 1; | |
| const char* validationLayers[] = { "VK_LAYER_KHRONOS_validation" }; | |
| std::vector<const char*> instanceExtensions = {}; | |
| #if DEBUG | |
| // Check if layers are available | |
| uint32_t instanceLayerCount; | |
| vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); | |
| std::vector<VkLayerProperties> instanceLayers(instanceLayerCount); | |
| vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayers.data()); | |
| bool layersAvailable = true; | |
| for (auto layerName : validationLayers) { | |
| bool layerAvailable = false; | |
| for (auto& instanceLayer : instanceLayers) { | |
| if (strcmp(instanceLayer.layerName, layerName) == 0) { | |
| layerAvailable = true; | |
| break; | |
| } | |
| } | |
| if (!layerAvailable) { | |
| layersAvailable = false; | |
| break; | |
| } | |
| } | |
| if (layersAvailable) { | |
| instanceExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); | |
| instanceCreateInfo.ppEnabledLayerNames = validationLayers; | |
| instanceCreateInfo.enabledLayerCount = layerCount; | |
| } | |
| #endif | |
| #if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) | |
| // SRS - When running on macOS with MoltenVK, enable VK_KHR_get_physical_device_properties2 (required by VK_KHR_portability_subset) | |
| instanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); | |
| #if defined(VK_KHR_portability_enumeration) | |
| // SRS - When running on macOS with MoltenVK and VK_KHR_portability_enumeration is defined and supported by the instance, enable the extension and the flag | |
| uint32_t instanceExtCount = 0; | |
| vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, nullptr); | |
| if (instanceExtCount > 0) | |
| { | |
| std::vector<VkExtensionProperties> extensions(instanceExtCount); | |
| if (vkEnumerateInstanceExtensionProperties(nullptr, &instanceExtCount, &extensions.front()) == VK_SUCCESS) | |
| { | |
| for (VkExtensionProperties extension : extensions) | |
| { | |
| if (strcmp(extension.extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0) | |
| { | |
| instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); | |
| instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| #endif | |
| instanceCreateInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size(); | |
| instanceCreateInfo.ppEnabledExtensionNames = instanceExtensions.data(); | |
| VK_CHECK_RESULT(vkCreateInstance(&instanceCreateInfo, nullptr, &instance)); | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| vks::android::loadVulkanFunctions(instance); | |
| #endif | |
| #if DEBUG | |
| if (layersAvailable) { | |
| VkDebugReportCallbackCreateInfoEXT debugReportCreateInfo = {}; | |
| debugReportCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; | |
| debugReportCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; | |
| debugReportCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)debugMessageCallback; | |
| // We have to explicitly load this function. | |
| PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); | |
| assert(vkCreateDebugReportCallbackEXT); | |
| VK_CHECK_RESULT(vkCreateDebugReportCallbackEXT(instance, &debugReportCreateInfo, nullptr, &debugReportCallback)); | |
| } | |
| #endif | |
| /* | |
| Vulkan device creation | |
| */ | |
| uint32_t deviceCount = 0; | |
| VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr)); | |
| std::vector<VkPhysicalDevice> physicalDevices(deviceCount); | |
| VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data())); | |
| physicalDevice = physicalDevices[0]; | |
| VkPhysicalDeviceProperties deviceProperties; | |
| vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); | |
| LOG("GPU: %s\n", deviceProperties.deviceName); | |
| // Request a single graphics queue | |
| const float defaultQueuePriority(0.0f); | |
| VkDeviceQueueCreateInfo queueCreateInfo = {}; | |
| uint32_t queueFamilyCount; | |
| vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); | |
| std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount); | |
| vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data()); | |
| for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++) { | |
| if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | |
| queueFamilyIndex = i; | |
| queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
| queueCreateInfo.queueFamilyIndex = i; | |
| queueCreateInfo.queueCount = 1; | |
| queueCreateInfo.pQueuePriorities = &defaultQueuePriority; | |
| break; | |
| } | |
| } | |
| // Create logical device | |
| VkDeviceCreateInfo deviceCreateInfo = {}; | |
| deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | |
| deviceCreateInfo.queueCreateInfoCount = 1; | |
| deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; | |
| std::vector<const char*> deviceExtensions = {}; | |
| // Shaders generated by Slang require a certain SPIR-V environment that can't be satisfied by Vulkan 1.0, so we need to expliclity up that to at least 1.1 and enable some required extensions | |
| if (shaderDir == "slang") { | |
| deviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME); | |
| deviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); | |
| } | |
| #if (defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_subset) | |
| // When running on macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension | |
| uint32_t deviceExtCount = 0; | |
| vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, nullptr); | |
| if (deviceExtCount > 0) | |
| { | |
| std::vector<VkExtensionProperties> extensions(deviceExtCount); | |
| if (vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtCount, &extensions.front()) == VK_SUCCESS) | |
| { | |
| for (VkExtensionProperties extension : extensions) | |
| { | |
| if (strcmp(extension.extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0) | |
| { | |
| deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| deviceCreateInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size(); | |
| deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data(); | |
| VK_CHECK_RESULT(vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device)); | |
| // Get a graphics queue | |
| vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue); | |
| // Command pool | |
| VkCommandPoolCreateInfo cmdPoolInfo = {}; | |
| cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | |
| cmdPoolInfo.queueFamilyIndex = queueFamilyIndex; | |
| cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; | |
| VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool)); | |
| /* | |
| Prepare vertex and index buffers | |
| */ | |
| struct Vertex { | |
| float position[3]; | |
| float color[3]; | |
| }; | |
| { | |
| std::vector<Vertex> vertices = { | |
| { { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, | |
| { { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, | |
| { { 0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } } | |
| }; | |
| std::vector<uint32_t> indices = { 0, 1, 2 }; | |
| const VkDeviceSize vertexBufferSize = vertices.size() * sizeof(Vertex); | |
| const VkDeviceSize indexBufferSize = indices.size() * sizeof(uint32_t); | |
| VkBuffer stagingBuffer; | |
| VkDeviceMemory stagingMemory; | |
| // Command buffer for copy commands (reused) | |
| VkCommandBufferAllocateInfo cmdBufAllocateInfo { | |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, | |
| .commandPool = commandPool, | |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, | |
| .commandBufferCount = 1, | |
| }; | |
| VkCommandBuffer copyCmd; | |
| VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); | |
| VkCommandBufferBeginInfo cmdBufInfo { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; | |
| // Copy input data to VRAM using a staging buffer | |
| { | |
| // Vertices | |
| createBuffer( | |
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT, | |
| VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, | |
| &stagingBuffer, | |
| &stagingMemory, | |
| vertexBufferSize, | |
| vertices.data()); | |
| createBuffer( | |
| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, | |
| VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, | |
| &vertexBuffer, | |
| &vertexMemory, | |
| vertexBufferSize); | |
| VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); | |
| VkBufferCopy copyRegion = {}; | |
| copyRegion.size = vertexBufferSize; | |
| vkCmdCopyBuffer(copyCmd, stagingBuffer, vertexBuffer, 1, ©Region); | |
| VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); | |
| submitWork(copyCmd, queue); | |
| vkDestroyBuffer(device, stagingBuffer, nullptr); | |
| vkFreeMemory(device, stagingMemory, nullptr); | |
| // Indices | |
| createBuffer( | |
| VK_BUFFER_USAGE_TRANSFER_SRC_BIT, | |
| VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, | |
| &stagingBuffer, | |
| &stagingMemory, | |
| indexBufferSize, | |
| indices.data()); | |
| createBuffer( | |
| VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, | |
| VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, | |
| &indexBuffer, | |
| &indexMemory, | |
| indexBufferSize); | |
| VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); | |
| copyRegion.size = indexBufferSize; | |
| vkCmdCopyBuffer(copyCmd, stagingBuffer, indexBuffer, 1, ©Region); | |
| VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); | |
| submitWork(copyCmd, queue); | |
| vkDestroyBuffer(device, stagingBuffer, nullptr); | |
| vkFreeMemory(device, stagingMemory, nullptr); | |
| } | |
| } | |
| /* | |
| Create framebuffer attachments | |
| */ | |
| width = 1024; | |
| height = 1024; | |
| VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM; | |
| VkFormat depthFormat; | |
| // Since all depth formats may be optional, we need to find a suitable depth format to use | |
| // Start with the highest precision packed format | |
| std::vector<VkFormat> formatList = { | |
| VK_FORMAT_D32_SFLOAT_S8_UINT, | |
| VK_FORMAT_D32_SFLOAT, | |
| VK_FORMAT_D24_UNORM_S8_UINT, | |
| VK_FORMAT_D16_UNORM_S8_UINT, | |
| VK_FORMAT_D16_UNORM | |
| }; | |
| for (auto& format : formatList) | |
| { | |
| VkFormatProperties formatProps; | |
| vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps); | |
| if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) | |
| { | |
| depthFormat = format; | |
| } | |
| } | |
| { | |
| // Color attachment | |
| VkImageCreateInfo image { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; | |
| image.imageType = VK_IMAGE_TYPE_2D; | |
| image.format = colorFormat; | |
| image.extent.width = width; | |
| image.extent.height = height; | |
| image.extent.depth = 1; | |
| image.mipLevels = 1; | |
| image.arrayLayers = 1; | |
| image.samples = VK_SAMPLE_COUNT_1_BIT; | |
| image.tiling = VK_IMAGE_TILING_OPTIMAL; | |
| image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; | |
| VkMemoryAllocateInfo memAlloc { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; | |
| VkMemoryRequirements memReqs; | |
| VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &colorAttachment.image)); | |
| vkGetImageMemoryRequirements(device, colorAttachment.image, &memReqs); | |
| memAlloc.allocationSize = memReqs.size; | |
| memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); | |
| VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &colorAttachment.memory)); | |
| VK_CHECK_RESULT(vkBindImageMemory(device, colorAttachment.image, colorAttachment.memory, 0)); | |
| VkImageViewCreateInfo colorImageView { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; | |
| colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
| colorImageView.format = colorFormat; | |
| colorImageView.subresourceRange = {}; | |
| colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
| colorImageView.subresourceRange.baseMipLevel = 0; | |
| colorImageView.subresourceRange.levelCount = 1; | |
| colorImageView.subresourceRange.baseArrayLayer = 0; | |
| colorImageView.subresourceRange.layerCount = 1; | |
| colorImageView.image = colorAttachment.image; | |
| VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &colorAttachment.view)); | |
| // Depth stencil attachment | |
| image.format = depthFormat; | |
| image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; | |
| VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &depthAttachment.image)); | |
| vkGetImageMemoryRequirements(device, depthAttachment.image, &memReqs); | |
| memAlloc.allocationSize = memReqs.size; | |
| memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); | |
| VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &depthAttachment.memory)); | |
| VK_CHECK_RESULT(vkBindImageMemory(device, depthAttachment.image, depthAttachment.memory, 0)); | |
| VkImageViewCreateInfo depthStencilView { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; | |
| depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
| depthStencilView.format = depthFormat; | |
| depthStencilView.flags = 0; | |
| depthStencilView.subresourceRange = {}; | |
| depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; | |
| if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) | |
| depthStencilView.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; | |
| depthStencilView.subresourceRange.baseMipLevel = 0; | |
| depthStencilView.subresourceRange.levelCount = 1; | |
| depthStencilView.subresourceRange.baseArrayLayer = 0; | |
| depthStencilView.subresourceRange.layerCount = 1; | |
| depthStencilView.image = depthAttachment.image; | |
| VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &depthAttachment.view)); | |
| } | |
| /* | |
| Create renderpass | |
| */ | |
| { | |
| std::array<VkAttachmentDescription, 2> attchmentDescriptions = {}; | |
| // Color attachment | |
| attchmentDescriptions[0].format = colorFormat; | |
| attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT; | |
| attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; | |
| attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; | |
| attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | |
| attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |
| attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; | |
| // Depth attachment | |
| attchmentDescriptions[1].format = depthFormat; | |
| attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT; | |
| attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; | |
| attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |
| attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | |
| attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |
| attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; | |
| VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; | |
| VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; | |
| VkSubpassDescription subpassDescription = {}; | |
| subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; | |
| subpassDescription.colorAttachmentCount = 1; | |
| subpassDescription.pColorAttachments = &colorReference; | |
| subpassDescription.pDepthStencilAttachment = &depthReference; | |
| // Use subpass dependencies for layout transitions | |
| std::array<VkSubpassDependency, 2> dependencies; | |
| dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; | |
| dependencies[0].dstSubpass = 0; | |
| dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; | |
| dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; | |
| dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; | |
| dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
| dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; | |
| dependencies[1].srcSubpass = 0; | |
| dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; | |
| dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; | |
| dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; | |
| dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
| dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; | |
| dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; | |
| // Create the actual renderpass | |
| VkRenderPassCreateInfo renderPassInfo = {}; | |
| renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; | |
| renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size()); | |
| renderPassInfo.pAttachments = attchmentDescriptions.data(); | |
| renderPassInfo.subpassCount = 1; | |
| renderPassInfo.pSubpasses = &subpassDescription; | |
| renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size()); | |
| renderPassInfo.pDependencies = dependencies.data(); | |
| VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass)); | |
| VkImageView attachments[2]; | |
| attachments[0] = colorAttachment.view; | |
| attachments[1] = depthAttachment.view; | |
| VkFramebufferCreateInfo framebufferCreateInfo {.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; | |
| framebufferCreateInfo.renderPass = renderPass; | |
| framebufferCreateInfo.attachmentCount = 2; | |
| framebufferCreateInfo.pAttachments = attachments; | |
| framebufferCreateInfo.width = width; | |
| framebufferCreateInfo.height = height; | |
| framebufferCreateInfo.layers = 1; | |
| VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCreateInfo, nullptr, &framebuffer)); | |
| } | |
| /* | |
| Prepare graphics pipeline | |
| */ | |
| { | |
| std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {}; | |
| VkDescriptorSetLayoutCreateInfo descriptorLayout { | |
| .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, | |
| .bindingCount = (uint32_t)setLayoutBindings.size(), | |
| .pBindings = setLayoutBindings.data(), | |
| }; | |
| VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); | |
| VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; | |
| // MVP via push constant block | |
| VkPushConstantRange pushConstantRange { | |
| .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, | |
| .offset = 0, | |
| .size = sizeof(glm::mat4), | |
| }; | |
| pipelineLayoutCreateInfo.pushConstantRangeCount = 1; | |
| pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; | |
| VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); | |
| VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; | |
| pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; | |
| VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); | |
| // Create pipeline | |
| VkPipelineInputAssemblyStateCreateInfo inputAssemblyState { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, | |
| .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST | |
| }; | |
| VkPipelineRasterizationStateCreateInfo rasterizationState { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, | |
| .depthClampEnable = VK_FALSE, | |
| .polygonMode = VK_POLYGON_MODE_FILL, | |
| .cullMode = VK_CULL_MODE_BACK_BIT, | |
| .frontFace = VK_FRONT_FACE_CLOCKWISE, | |
| .lineWidth = 1.f, | |
| }; | |
| VkPipelineColorBlendAttachmentState blendAttachmentState { | |
| .blendEnable = VK_FALSE, | |
| .colorWriteMask = 0xf, | |
| }; | |
| VkPipelineColorBlendStateCreateInfo colorBlendState { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, | |
| .attachmentCount = 1, | |
| .pAttachments = &blendAttachmentState, | |
| }; | |
| VkPipelineDepthStencilStateCreateInfo depthStencilState { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, | |
| .depthTestEnable = VK_TRUE, | |
| .depthWriteEnable = VK_TRUE, | |
| .depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL, | |
| }; | |
| VkPipelineViewportStateCreateInfo viewportState { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, | |
| .viewportCount = 1, | |
| .scissorCount = 1, | |
| }; | |
| VkPipelineMultisampleStateCreateInfo multisampleState { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, | |
| .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, | |
| }; | |
| std::vector<VkDynamicState> dynamicStateEnables = { | |
| VK_DYNAMIC_STATE_VIEWPORT, | |
| VK_DYNAMIC_STATE_SCISSOR | |
| }; | |
| VkPipelineDynamicStateCreateInfo dynamicState { | |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, | |
| .dynamicStateCount = static_cast<uint32_t>(dynamicStateEnables.size()), | |
| .pDynamicStates = dynamicStateEnables.data(), | |
| }; | |
| VkGraphicsPipelineCreateInfo pipelineCreateInfo { | |
| .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, | |
| .layout = pipelineLayout, | |
| .renderPass = renderPass, | |
| .basePipelineHandle = VK_NULL_HANDLE, | |
| .basePipelineIndex = -1, | |
| }; | |
| std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages{}; | |
| pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; | |
| pipelineCreateInfo.pRasterizationState = &rasterizationState; | |
| pipelineCreateInfo.pColorBlendState = &colorBlendState; | |
| pipelineCreateInfo.pMultisampleState = &multisampleState; | |
| pipelineCreateInfo.pViewportState = &viewportState; | |
| pipelineCreateInfo.pDepthStencilState = &depthStencilState; | |
| pipelineCreateInfo.pDynamicState = &dynamicState; | |
| pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size()); | |
| pipelineCreateInfo.pStages = shaderStages.data(); | |
| // Vertex bindings an attributes | |
| // Binding description | |
| std::vector<VkVertexInputBindingDescription> vertexInputBindings = {{ | |
| .binding = 0, | |
| .stride = sizeof(Vertex), | |
| .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, | |
| }}; | |
| // Attribute descriptions | |
| std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = { | |
| { | |
| .location = 0, | |
| .binding = 0, | |
| .format = VK_FORMAT_R32G32B32_SFLOAT, | |
| .offset = 0, | |
| }, | |
| { | |
| .location = 1, | |
| .binding = 0, | |
| .format = VK_FORMAT_R32G32B32_SFLOAT, | |
| .offset = sizeof(float) * 3, | |
| }, | |
| }; | |
| VkPipelineVertexInputStateCreateInfo vertexInputState {.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; | |
| vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size()); | |
| vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); | |
| vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size()); | |
| vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); | |
| pipelineCreateInfo.pVertexInputState = &vertexInputState; | |
| // if (commandLineParser.isSet("shaders")) { | |
| // shaderDir = commandLineParser.getValueAsString("shaders", "glsl"); | |
| // } | |
| shaderDir = "./"; | |
| const std::string shadersPath = shaderDir; | |
| shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
| shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; | |
| shaderStages[0].pName = "main"; | |
| shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
| shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; | |
| shaderStages[1].pName = "main"; | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| shaderStages[0].module = loadShader(androidapp->activity->assetManager, (shadersPath + "triangle.vert.spv").c_str(), device); | |
| shaderStages[1].module = loadShader(androidapp->activity->assetManager, (shadersPath + "triangle.frag.spv").c_str(), device); | |
| #else | |
| shaderStages[0].module = loadShader((shadersPath + "triangle.vert.spv").c_str(), device); | |
| shaderStages[1].module = loadShader((shadersPath + "triangle.frag.spv").c_str(), device); | |
| #endif | |
| shaderModules = { shaderStages[0].module, shaderStages[1].module }; | |
| VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); | |
| } | |
| /* | |
| Command buffer creation | |
| */ | |
| { | |
| VkCommandBuffer commandBuffer; | |
| VkCommandBufferAllocateInfo cmdBufAllocateInfo { | |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, | |
| .commandPool = commandPool, | |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, | |
| .commandBufferCount = 1, | |
| }; | |
| VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &commandBuffer)); | |
| VkCommandBufferBeginInfo cmdBufInfo {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; | |
| VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo)); | |
| VkClearValue clearValues[2]; | |
| clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } }; | |
| clearValues[1].depthStencil = { 1.0f, 0 }; | |
| VkRenderPassBeginInfo renderPassBeginInfo = {}; | |
| renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | |
| renderPassBeginInfo.renderArea.extent.width = width; | |
| renderPassBeginInfo.renderArea.extent.height = height; | |
| renderPassBeginInfo.clearValueCount = 2; | |
| renderPassBeginInfo.pClearValues = clearValues; | |
| renderPassBeginInfo.renderPass = renderPass; | |
| renderPassBeginInfo.framebuffer = framebuffer; | |
| vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); | |
| VkViewport viewport = {}; | |
| viewport.height = (float)height; | |
| viewport.width = (float)width; | |
| viewport.minDepth = (float)0.0f; | |
| viewport.maxDepth = (float)1.0f; | |
| vkCmdSetViewport(commandBuffer, 0, 1, &viewport); | |
| // Update dynamic scissor state | |
| VkRect2D scissor = {}; | |
| scissor.extent.width = width; | |
| scissor.extent.height = height; | |
| vkCmdSetScissor(commandBuffer, 0, 1, &scissor); | |
| vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | |
| // Render scene | |
| VkDeviceSize offsets[1] = { 0 }; | |
| vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, offsets); | |
| vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); | |
| std::vector<glm::vec3> pos = { | |
| {-1.5f, 0.0f, -4.0f}, | |
| { 0.0f, 0.0f, -2.5f}, | |
| { 1.5f, 0.0f, -4.0f}, | |
| }; | |
| for (auto v : pos) { | |
| auto mvpMatrix = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f) * glm::translate(glm::mat4(1.f), v); | |
| vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(mvpMatrix), &mvpMatrix); | |
| vkCmdDrawIndexed(commandBuffer, 3, 1, 0, 0, 0); | |
| } | |
| vkCmdEndRenderPass(commandBuffer); | |
| VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); | |
| submitWork(commandBuffer, queue); | |
| vkDeviceWaitIdle(device); | |
| } | |
| /* | |
| Copy framebuffer image to host visible image | |
| */ | |
| const char* imagedata; | |
| { | |
| // Create the linear tiled destination image to copy to and to read the memory from | |
| VkImageCreateInfo imgCreateInfo {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; | |
| imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; | |
| imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; | |
| imgCreateInfo.extent.width = width; | |
| imgCreateInfo.extent.height = height; | |
| imgCreateInfo.extent.depth = 1; | |
| imgCreateInfo.arrayLayers = 1; | |
| imgCreateInfo.mipLevels = 1; | |
| imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; | |
| imgCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; | |
| imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; | |
| // Create the image | |
| VkImage dstImage; | |
| VK_CHECK_RESULT(vkCreateImage(device, &imgCreateInfo, nullptr, &dstImage)); | |
| // Create memory to back up the image | |
| VkMemoryRequirements memRequirements; | |
| VkMemoryAllocateInfo memAllocInfo {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; | |
| VkDeviceMemory dstImageMemory; | |
| vkGetImageMemoryRequirements(device, dstImage, &memRequirements); | |
| memAllocInfo.allocationSize = memRequirements.size; | |
| // Memory must be host visible to copy from | |
| memAllocInfo.memoryTypeIndex = getMemoryTypeIndex(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); | |
| VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &dstImageMemory)); | |
| VK_CHECK_RESULT(vkBindImageMemory(device, dstImage, dstImageMemory, 0)); | |
| // Do the actual blit from the offscreen image to our host visible destination image | |
| VkCommandBufferAllocateInfo cmdBufAllocateInfo { | |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, | |
| .commandPool = commandPool, | |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, | |
| .commandBufferCount = 1, | |
| }; | |
| VkCommandBuffer copyCmd; | |
| VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, ©Cmd)); | |
| VkCommandBufferBeginInfo cmdBufInfo {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; | |
| VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); | |
| // Transition destination image to transfer destination layout | |
| auto insertImageMemoryBarrier = []( | |
| VkCommandBuffer cmdbuffer, | |
| VkImage image, | |
| VkAccessFlags srcAccessMask, | |
| VkAccessFlags dstAccessMask, | |
| VkImageLayout oldImageLayout, | |
| VkImageLayout newImageLayout, | |
| VkPipelineStageFlags srcStageMask, | |
| VkPipelineStageFlags dstStageMask, | |
| VkImageSubresourceRange subresourceRange) | |
| { | |
| VkImageMemoryBarrier imageMemoryBarrier { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | |
| .srcAccessMask = srcAccessMask, | |
| .dstAccessMask = dstAccessMask, | |
| .oldLayout = oldImageLayout, | |
| .newLayout = newImageLayout, | |
| .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | |
| .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | |
| .image = image, | |
| .subresourceRange = subresourceRange, | |
| }; | |
| vkCmdPipelineBarrier( | |
| cmdbuffer, | |
| srcStageMask, | |
| dstStageMask, | |
| 0, | |
| 0, nullptr, | |
| 0, nullptr, | |
| 1, &imageMemoryBarrier); | |
| }; | |
| insertImageMemoryBarrier( | |
| copyCmd, | |
| dstImage, | |
| 0, | |
| VK_ACCESS_TRANSFER_WRITE_BIT, | |
| VK_IMAGE_LAYOUT_UNDEFINED, | |
| VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | |
| VK_PIPELINE_STAGE_TRANSFER_BIT, | |
| VK_PIPELINE_STAGE_TRANSFER_BIT, | |
| VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); | |
| // colorAttachment.image is already in VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, and does not need to be transitioned | |
| VkImageCopy imageCopyRegion{}; | |
| imageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
| imageCopyRegion.srcSubresource.layerCount = 1; | |
| imageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
| imageCopyRegion.dstSubresource.layerCount = 1; | |
| imageCopyRegion.extent.width = width; | |
| imageCopyRegion.extent.height = height; | |
| imageCopyRegion.extent.depth = 1; | |
| vkCmdCopyImage( | |
| copyCmd, | |
| colorAttachment.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, | |
| dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | |
| 1, | |
| &imageCopyRegion); | |
| // Transition destination image to general layout, which is the required layout for mapping the image memory later on | |
| insertImageMemoryBarrier( | |
| copyCmd, | |
| dstImage, | |
| VK_ACCESS_TRANSFER_WRITE_BIT, | |
| VK_ACCESS_MEMORY_READ_BIT, | |
| VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, | |
| VK_IMAGE_LAYOUT_GENERAL, | |
| VK_PIPELINE_STAGE_TRANSFER_BIT, | |
| VK_PIPELINE_STAGE_TRANSFER_BIT, | |
| VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); | |
| VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); | |
| submitWork(copyCmd, queue); | |
| // Get layout of the image (including row pitch) | |
| VkImageSubresource subResource{}; | |
| subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
| VkSubresourceLayout subResourceLayout; | |
| vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout); | |
| // Map image memory so we can start copying from it | |
| vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&imagedata); | |
| imagedata += subResourceLayout.offset; | |
| /* | |
| Save host visible framebuffer image to disk (ppm format) | |
| */ | |
| #if defined (VK_USE_PLATFORM_ANDROID_KHR) | |
| const char* filename = strcat(getenv("EXTERNAL_STORAGE"), "/headless.ppm"); | |
| #else | |
| const char* filename = "headless.ppm"; | |
| #endif | |
| std::ofstream file(filename, std::ios::out | std::ios::binary); | |
| // ppm header | |
| file << "P6\n" << width << "\n" << height << "\n" << 255 << "\n"; | |
| // If source is BGR (destination is always RGB) and we can't use blit (which does automatic conversion), we'll have to manually swizzle color components | |
| // Check if source is BGR and needs swizzle | |
| std::vector<VkFormat> formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM }; | |
| const bool colorSwizzle = (std::find(formatsBGR.begin(), formatsBGR.end(), VK_FORMAT_R8G8B8A8_UNORM) != formatsBGR.end()); | |
| // ppm binary pixel data | |
| for (int32_t y = 0; y < height; y++) { | |
| unsigned int *row = (unsigned int*)imagedata; | |
| for (int32_t x = 0; x < width; x++) { | |
| if (colorSwizzle) { | |
| file.write((char*)row + 2, 1); | |
| file.write((char*)row + 1, 1); | |
| file.write((char*)row, 1); | |
| } | |
| else { | |
| file.write((char*)row, 3); | |
| } | |
| row++; | |
| } | |
| imagedata += subResourceLayout.rowPitch; | |
| } | |
| file.close(); | |
| LOG("Framebuffer image saved to %s\n", filename); | |
| // Clean up resources | |
| vkUnmapMemory(device, dstImageMemory); | |
| vkFreeMemory(device, dstImageMemory, nullptr); | |
| vkDestroyImage(device, dstImage, nullptr); | |
| } | |
| vkQueueWaitIdle(queue); | |
| } | |
| ~VulkanExample() | |
| { | |
| vkDestroyBuffer(device, vertexBuffer, nullptr); | |
| vkFreeMemory(device, vertexMemory, nullptr); | |
| vkDestroyBuffer(device, indexBuffer, nullptr); | |
| vkFreeMemory(device, indexMemory, nullptr); | |
| vkDestroyImageView(device, colorAttachment.view, nullptr); | |
| vkDestroyImage(device, colorAttachment.image, nullptr); | |
| vkFreeMemory(device, colorAttachment.memory, nullptr); | |
| vkDestroyImageView(device, depthAttachment.view, nullptr); | |
| vkDestroyImage(device, depthAttachment.image, nullptr); | |
| vkFreeMemory(device, depthAttachment.memory, nullptr); | |
| vkDestroyRenderPass(device, renderPass, nullptr); | |
| vkDestroyFramebuffer(device, framebuffer, nullptr); | |
| vkDestroyPipelineLayout(device, pipelineLayout, nullptr); | |
| vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); | |
| vkDestroyPipeline(device, pipeline, nullptr); | |
| vkDestroyPipelineCache(device, pipelineCache, nullptr); | |
| vkDestroyCommandPool(device, commandPool, nullptr); | |
| for (auto shadermodule : shaderModules) { | |
| vkDestroyShaderModule(device, shadermodule, nullptr); | |
| } | |
| vkDestroyDevice(device, nullptr); | |
| #if DEBUG | |
| if (debugReportCallback) { | |
| PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); | |
| assert(vkDestroyDebugReportCallback); | |
| vkDestroyDebugReportCallback(instance, debugReportCallback, nullptr); | |
| } | |
| #endif | |
| vkDestroyInstance(instance, nullptr); | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| vks::android::freeVulkanLibrary(); | |
| #endif | |
| } | |
| }; | |
| #if defined(VK_USE_PLATFORM_ANDROID_KHR) | |
| void handleAppCommand(android_app * app, int32_t cmd) { | |
| if (cmd == APP_CMD_INIT_WINDOW) { | |
| VulkanExample *vulkanExample = new VulkanExample(); | |
| delete(vulkanExample); | |
| ANativeActivity_finish(app->activity); | |
| } | |
| } | |
| void android_main(android_app* state) { | |
| androidapp = state; | |
| androidapp->onAppCmd = handleAppCommand; | |
| int ident, events; | |
| struct android_poll_source* source; | |
| while ((ident = ALooper_pollOnce(-1, NULL, &events, (void**)&source)) > ALOOPER_POLL_TIMEOUT) { | |
| if (source != NULL) { | |
| source->process(androidapp, source); | |
| } | |
| if (androidapp->destroyRequested != 0) { | |
| break; | |
| } | |
| } | |
| } | |
| #else | |
| int main(int argc, char* argv[]) { | |
| // commandLineParser.add("help", { "--help" }, 0, "Show help"); | |
| // commandLineParser.add("shaders", { "-s", "--shaders" }, 1, "Select shader type to use (glsl, hlsl or slang)"); | |
| // commandLineParser.parse(argc, argv); | |
| // if (commandLineParser.isSet("help")) { | |
| // commandLineParser.printHelp(); | |
| // std::cin.get(); | |
| // return 0; | |
| // } | |
| VulkanExample *vulkanExample = new VulkanExample(); | |
| std::cout << "Finished. Press enter to terminate..."; | |
| std::cin.get(); | |
| delete(vulkanExample); | |
| return 0; | |
| } | |
| #endif |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
...