Skip to content

Instantly share code, notes, and snippets.

@lalishansh
Last active June 30, 2026 06:12
Show Gist options
  • Select an option

  • Save lalishansh/6cd023aa11dfeb930cc02711efd27394 to your computer and use it in GitHub Desktop.

Select an option

Save lalishansh/6cd023aa11dfeb930cc02711efd27394 to your computer and use it in GitHub Desktop.
main_vk_simpler.cpp
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, &copy_region);
copy_region.size = indices_size;
vkCmdCopyBuffer(copy_cmd, staging_buffer2, index_buffer, 1, &copy_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){}
/*
* 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, &copyCmd));
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, &copyRegion);
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, &copyRegion);
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, &copyCmd));
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
@lalishansh

lalishansh commented Jun 14, 2026

Copy link
Copy Markdown
Author

...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment