diff --git a/build.sh b/build.sh new file mode 100644 index 000000000..d9ab95475 --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +cmake -GNinja -D UPDATE_DEPS=ON -D BUILD_WERROR=ON -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Debug ../ +ninja diff --git a/cube/CMakeLists.txt b/cube/CMakeLists.txt index 880cf741c..67f1514d1 100644 --- a/cube/CMakeLists.txt +++ b/cube/CMakeLists.txt @@ -231,10 +231,10 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|GNU") add_executable(vkcube) target_sources(vkcube PRIVATE cube.c - ${PROJECT_SOURCE_DIR}/cube/cube.vert - ${PROJECT_SOURCE_DIR}/cube/cube.frag - cube.vert.inc - cube.frag.inc + ${PROJECT_SOURCE_DIR}/cube/cube-nc.vert + ${PROJECT_SOURCE_DIR}/cube/cube-nc.frag + cube-nc.vert.inc + cube-nc.frag.inc ${OPTIONAL_WAYLAND_DATA_FILES} ) target_compile_definitions(vkcube PUBLIC ${CUBE_PLATFORM}) @@ -248,10 +248,10 @@ elseif(WIN32) add_executable(vkcube WIN32) target_sources(vkcube PRIVATE cube.c - ${PROJECT_SOURCE_DIR}/cube/cube.vert - ${PROJECT_SOURCE_DIR}/cube/cube.frag - cube.vert.inc - cube.frag.inc + ${PROJECT_SOURCE_DIR}/cube/cube-nc.vert + ${PROJECT_SOURCE_DIR}/cube/cube-nc.frag + cube-nc.vert.inc + cube-nc.frag.inc ) target_link_libraries(vkcube PRIVATE Vulkan::Headers volk::volk_headers) else() @@ -358,3 +358,39 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux|BSD") install(TARGETS vkcube-wayland) endif() endif() + + +# ---------------------------------------------------------------------------- +# vkcube-heap + +if(CMAKE_SYSTEM_NAME MATCHES "Linux|BSD|GNU") + add_executable(vkcube-heap) + target_sources(vkcube-heap PRIVATE + cube-heap.c + ${PROJECT_SOURCE_DIR}/cube/cube.vert + ${PROJECT_SOURCE_DIR}/cube/cube.frag + cube.vert.inc + cube.frag.inc + ${OPTIONAL_WAYLAND_DATA_FILES} + ) + target_compile_definitions(vkcube-heap PUBLIC ${CUBE_PLATFORM}) + include(CheckLibraryExists) + CHECK_LIBRARY_EXISTS("rt" clock_gettime "" NEED_RT) + if (NEED_RT) + target_link_libraries(vkcube-heap PRIVATE rt) + endif() + target_link_libraries(vkcube-heap PRIVATE Vulkan::Headers volk::volk_headers Threads::Threads) +else() + add_executable(vkcube-heap + WIN32 + cube-heap.c + ${PROJECT_SOURCE_DIR}/cube/cube.vert + ${PROJECT_SOURCE_DIR}/cube/cube.frag + cube.vert.inc + cube.frag.inc) + target_link_libraries(vkcube-heap Vulkan::Headers volk::volk_headers) +endif() + +target_include_directories(vkcube-heap PRIVATE .) + +install(TARGETS vkcube-heap) diff --git a/cube/cube-heap.c b/cube/cube-heap.c new file mode 100644 index 000000000..a14898d6a --- /dev/null +++ b/cube/cube-heap.c @@ -0,0 +1,4734 @@ +/* + * Copyright (c) 2015-2019 The Khronos Group Inc. + * Copyright (c) 2015-2019 Valve Corporation + * Copyright (c) 2015-2019 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Chia-I Wu + * Author: Courtney Goeltzenleuchter + * Author: Ian Elliott + * Author: Ian Elliott + * Author: Jon Ashburn + * Author: Gwan-gyeong Mun + * Author: Tony Barbour + * Author: Bill Hollings + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_XCB_KHR) +#include +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) +#include +#include "xdg-shell-client-header.h" +#include "xdg-decoration-client-header.h" +#endif + +#ifdef _WIN32 +#ifdef _MSC_VER +#pragma comment(linker, "/subsystem:windows") +#endif // MSVC +#define APP_NAME_STR_LEN 80 +#endif // _WIN32 + +// Volk requires VK_NO_PROTOTYPES before including vulkan.h +#define VK_NO_PROTOTYPES +#include +#define VOLK_IMPLEMENTATION +#include "volk.h" + +#include "linmath.h" +#include "object_type_string_helper.h" +#include "vk_khr_descriptor_heap.h" + +#include "gettime.h" +#include "inttypes.h" +#define MILLION 1000000L +#define BILLION 1000000000L + +#define DEMO_TEXTURE_COUNT 1 +#define APP_SHORT_NAME "vkcube" +#define APP_LONG_NAME "Vulkan Cube" + +// Allow a maximum of two outstanding presentation operations. +#define FRAME_LAG 2 + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#if defined(NDEBUG) && defined(__GNUC__) +#define U_ASSERT_ONLY __attribute__((unused)) +#else +#define U_ASSERT_ONLY +#endif + +#if defined(__GNUC__) +#define UNUSED __attribute__((unused)) +#else +#define UNUSED +#endif + +#define DESCRIPTOR_HEAP 0 + +#ifdef _WIN32 +bool in_callback = false; +#define ERR_EXIT(err_msg, err_class) \ + do { \ + if (!demo->suppress_popups) MessageBox(NULL, err_msg, err_class, MB_OK); \ + exit(1); \ + } while (0) +void DbgMsg(char *fmt, ...) { + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + fflush(stdout); +} + +#elif defined __ANDROID__ +#include +#define ERR_EXIT(err_msg, err_class) \ + do { \ + ((void)__android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", err_msg)); \ + exit(1); \ + } while (0) +#ifdef VARARGS_WORKS_ON_ANDROID +void DbgMsg(const char *fmt, ...) { + va_list va; + va_start(va, fmt); + __android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", fmt, va); + va_end(va); +} +#else // VARARGS_WORKS_ON_ANDROID +#define DbgMsg(fmt, ...) \ + do { \ + ((void)__android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", fmt, ##__VA_ARGS__)); \ + } while (0) +#endif // VARARGS_WORKS_ON_ANDROID +#else +#define ERR_EXIT(err_msg, err_class) \ + do { \ + printf("%s\n", err_msg); \ + fflush(stdout); \ + exit(1); \ + } while (0) +void DbgMsg(char *fmt, ...) { + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + fflush(stdout); +} +#endif + +/* + * structure to track all objects related to a texture. + */ +struct texture_object { + VkSampler sampler; + + VkImage image; + VkBuffer buffer; + VkImageLayout imageLayout; + + VkMemoryAllocateInfo mem_alloc; + VkDeviceMemory mem; + VkImageView view; + int32_t tex_width, tex_height; +}; + +static char *tex_files[] = {"lunarg.ppm"}; + +static int validation_error = 0; + +struct vktexcube_vs_uniform { + // Must start with MVP + float mvp[4][4]; + float position[12 * 3][4]; + float attr[12 * 3][4]; +}; + +//-------------------------------------------------------------------------------------- +// Mesh and VertexFormat Data +//-------------------------------------------------------------------------------------- +// clang-format off +static const float g_vertex_buffer_data[] = { + -1.0f,-1.0f,-1.0f, // -X side + -1.0f,-1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + + -1.0f,-1.0f,-1.0f, // -Z side + 1.0f, 1.0f,-1.0f, + 1.0f,-1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + + -1.0f,-1.0f,-1.0f, // -Y side + 1.0f,-1.0f,-1.0f, + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f,-1.0f, + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + + -1.0f, 1.0f,-1.0f, // +Y side + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f,-1.0f, + + 1.0f, 1.0f,-1.0f, // +X side + 1.0f, 1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f,-1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + + -1.0f, 1.0f, 1.0f, // +Z side + -1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, +}; + +static const float g_uv_buffer_data[] = { + 0.0f, 1.0f, // -X side + 1.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, + + 1.0f, 1.0f, // -Z side + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + + 1.0f, 0.0f, // -Y side + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + + 1.0f, 0.0f, // +Y side + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + + 1.0f, 0.0f, // +X side + 0.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + + 0.0f, 0.0f, // +Z side + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, +}; +// clang-format on + +void dumpMatrix(const char *note, mat4x4 MVP) { + int i; + + printf("%s: \n", note); + for (i = 0; i < 4; i++) { + printf("%f, %f, %f, %f\n", MVP[i][0], MVP[i][1], MVP[i][2], MVP[i][3]); + } + printf("\n"); + fflush(stdout); +} + +void dumpVec4(const char *note, vec4 vector) { + printf("%s: \n", note); + printf("%f, %f, %f, %f\n", vector[0], vector[1], vector[2], vector[3]); + printf("\n"); + fflush(stdout); +} + +char const *to_string(VkPhysicalDeviceType const type) { + switch (type) { + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + return "Other"; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + return "IntegratedGpu"; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + return "DiscreteGpu"; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + return "VirtualGpu"; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + return "Cpu"; + default: + return "Unknown"; + } +} + +typedef struct { + VkImage image; + VkCommandBuffer cmd; + VkCommandBuffer graphics_to_present_cmd; + VkImageView view; + VkBuffer uniform_buffer; + VkDeviceMemory uniform_memory; + void *uniform_memory_ptr; + VkFramebuffer framebuffer; + VkDescriptorSet descriptor_set; +} SwapchainImageResources; + +struct demo { +#if defined(VK_USE_PLATFORM_WIN32_KHR) +#define APP_NAME_STR_LEN 80 + HINSTANCE connection; // hInstance - Windows Instance + char name[APP_NAME_STR_LEN]; // Name to put on the window/icon + HWND window; // hWnd - window handle + POINT minsize; // minimum window size +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + Display *display; + Window xlib_window; + Atom xlib_wm_delete_window; +#elif defined(VK_USE_PLATFORM_XCB_KHR) + Display *display; + xcb_connection_t *connection; + xcb_screen_t *screen; + xcb_window_t xcb_window; + xcb_intern_atom_reply_t *atom_wm_delete_window; +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_surface *window; + struct xdg_wm_base *xdg_wm_base; + struct zxdg_decoration_manager_v1 *xdg_decoration_mgr; + struct zxdg_toplevel_decoration_v1 *toplevel_decoration; + struct xdg_surface *xdg_surface; + int xdg_surface_has_been_configured; + struct xdg_toplevel *xdg_toplevel; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + IDirectFB *dfb; + IDirectFBSurface *window; + IDirectFBEventBuffer *event_buffer; +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + struct ANativeWindow *window; +#elif defined(VK_USE_PLATFORM_METAL_EXT) + void *caMetalLayer; +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + screen_context_t screen_context; + screen_window_t screen_window; + screen_event_t screen_event; +#endif + + VkSurfaceKHR surface; + bool prepared; + bool use_staging_buffer; + bool separate_present_queue; + bool is_minimized; + bool invalid_gpu_selection; + int32_t gpu_number; + + bool VK_KHR_incremental_present_enabled; + + bool VK_GOOGLE_display_timing_enabled; + bool syncd_with_actual_presents; + uint64_t refresh_duration; + uint64_t refresh_duration_multiplier; + uint64_t target_IPD; // image present duration (inverse of frame rate) + uint64_t prev_desired_present_time; + uint32_t next_present_id; + uint32_t last_early_id; // 0 if no early images + uint32_t last_late_id; // 0 if no late images + + VkInstance inst; + VkPhysicalDevice gpu; + VkDevice device; + VkQueue graphics_queue; + VkQueue present_queue; + uint32_t graphics_queue_family_index; + uint32_t present_queue_family_index; + VkSemaphore image_acquired_semaphores[FRAME_LAG]; + VkSemaphore draw_complete_semaphores[FRAME_LAG]; + VkSemaphore image_ownership_semaphores[FRAME_LAG]; + VkPhysicalDeviceProperties gpu_props; + VkQueueFamilyProperties *queue_props; + +#if DESCRIPTOR_HEAP + VkPhysicalDeviceDescriptorHeapFeaturesKHR descriptorHeapFeature; + VkPhysicalDeviceDescriptorHeapPropertiesKHR descriptorHeapProperties; + + // 0:sampler heap, 1:buffer heap; 2:image heap; + VkBuffer descHeap[3]; + + VkDeviceMemory descHeapMemory[3]; + typedef enum VkHeapType { + sampler = 0x0, + buffer = 0x1, + image = 0x2 + }; +#endif + + VkPhysicalDeviceMemoryProperties memory_properties; + + uint32_t enabled_extension_count; + uint32_t enabled_layer_count; + char *extension_names[64]; + char *enabled_layers[64]; + + int width, height; + VkFormat format; + VkColorSpaceKHR color_space; + + uint32_t swapchainImageCount; + VkSwapchainKHR swapchain; + SwapchainImageResources *swapchain_image_resources; + VkPresentModeKHR presentMode; + VkFence fences[FRAME_LAG]; + int frame_index; + bool first_swapchain_frame; + + VkCommandPool cmd_pool; + VkCommandPool present_cmd_pool; + + struct { + VkFormat format; + + VkImage image; + VkMemoryAllocateInfo mem_alloc; + VkDeviceMemory mem; + VkImageView view; + } depth; + + struct texture_object textures[DEMO_TEXTURE_COUNT]; + struct texture_object staging_texture; + + VkCommandBuffer cmd; // Buffer for initialization commands + VkPipelineLayout pipeline_layout; + VkDescriptorSetLayout desc_layout; + VkPipelineCache pipelineCache; + VkRenderPass render_pass; + VkPipeline pipeline; + + mat4x4 projection_matrix; + mat4x4 view_matrix; + mat4x4 model_matrix; + + float spin_angle; + float spin_increment; + bool pause; + + VkShaderModule vert_shader_module; + VkShaderModule frag_shader_module; + + VkDescriptorPool desc_pool; + + bool quit; + int32_t curFrame; + int32_t frameCount; + bool validate; + bool validate_checks_disabled; + bool use_break; + bool suppress_popups; + bool force_errors; + + VkDebugUtilsMessengerEXT dbg_messenger; + + uint32_t current_buffer; + uint32_t queue_family_count; +}; + +VKAPI_ATTR VkBool32 VKAPI_CALL debug_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, + void *pUserData) { + char prefix[64] = ""; + char *message = (char *)malloc(strlen(pCallbackData->pMessage) + 5000); + assert(message); + struct demo *demo = (struct demo *)pUserData; + + if (demo->use_break) { +#ifndef WIN32 + raise(SIGTRAP); +#else + DebugBreak(); +#endif + } + + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + strcat(prefix, "VERBOSE : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + strcat(prefix, "INFO : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + strcat(prefix, "WARNING : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + strcat(prefix, "ERROR : "); + } + + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { + strcat(prefix, "GENERAL"); + } else { + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + strcat(prefix, "VALIDATION"); + validation_error = 1; + } + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + strcat(prefix, "|"); + } + strcat(prefix, "PERFORMANCE"); + } + } + + sprintf(message, "%s - Message Id Number: %d | Message Id Name: %s\n\t%s\n", prefix, pCallbackData->messageIdNumber, + pCallbackData->pMessageIdName == NULL ? "" : pCallbackData->pMessageIdName, pCallbackData->pMessage); + if (pCallbackData->objectCount > 0) { + char tmp_message[500]; + sprintf(tmp_message, "\n\tObjects - %d\n", pCallbackData->objectCount); + strcat(message, tmp_message); + for (uint32_t object = 0; object < pCallbackData->objectCount; ++object) { + sprintf(tmp_message, "\t\tObject[%d] - %s", object, string_VkObjectType(pCallbackData->pObjects[object].objectType)); + strcat(message, tmp_message); + + VkObjectType t = pCallbackData->pObjects[object].objectType; + if (t == VK_OBJECT_TYPE_INSTANCE || t == VK_OBJECT_TYPE_PHYSICAL_DEVICE || t == VK_OBJECT_TYPE_DEVICE || + t == VK_OBJECT_TYPE_COMMAND_BUFFER || t == VK_OBJECT_TYPE_QUEUE) { + sprintf(tmp_message, ", Handle %p", (void *)(uintptr_t)(pCallbackData->pObjects[object].objectHandle)); + strcat(message, tmp_message); + } else { + sprintf(tmp_message, ", Handle Ox%" PRIx64, (pCallbackData->pObjects[object].objectHandle)); + strcat(message, tmp_message); + } + + if (NULL != pCallbackData->pObjects[object].pObjectName && strlen(pCallbackData->pObjects[object].pObjectName) > 0) { + sprintf(tmp_message, ", Name \"%s\"", pCallbackData->pObjects[object].pObjectName); + strcat(message, tmp_message); + } + sprintf(tmp_message, "\n"); + strcat(message, tmp_message); + } + } + if (pCallbackData->cmdBufLabelCount > 0) { + char tmp_message[500]; + sprintf(tmp_message, "\n\tCommand Buffer Labels - %d\n", pCallbackData->cmdBufLabelCount); + strcat(message, tmp_message); + for (uint32_t cmd_buf_label = 0; cmd_buf_label < pCallbackData->cmdBufLabelCount; ++cmd_buf_label) { + sprintf(tmp_message, "\t\tLabel[%d] - %s { %f, %f, %f, %f}\n", cmd_buf_label, + pCallbackData->pCmdBufLabels[cmd_buf_label].pLabelName, pCallbackData->pCmdBufLabels[cmd_buf_label].color[0], + pCallbackData->pCmdBufLabels[cmd_buf_label].color[1], pCallbackData->pCmdBufLabels[cmd_buf_label].color[2], + pCallbackData->pCmdBufLabels[cmd_buf_label].color[3]); + strcat(message, tmp_message); + } + } + +#ifdef _WIN32 + + in_callback = true; + if (!demo->suppress_popups) MessageBox(NULL, message, "Alert", MB_OK); + in_callback = false; + +#elif defined(ANDROID) + + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + __android_log_print(ANDROID_LOG_WARN, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + __android_log_print(ANDROID_LOG_ERROR, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + __android_log_print(ANDROID_LOG_VERBOSE, APP_SHORT_NAME, "%s", message); + } else { + __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message); + } + +#else + + printf("%s\n", message); + fflush(stdout); + +#endif + + free(message); + + // Don't bail out, but keep going. + return false; +} + +bool ActualTimeLate(uint64_t desired, uint64_t actual, uint64_t rdur) { + // The desired time was the earliest time that the present should have + // occured. In almost every case, the actual time should be later than the + // desired time. We should only consider the actual time "late" if it is + // after "desired + rdur". + if (actual <= desired) { + // The actual time was before or equal to the desired time. This will + // probably never happen, but in case it does, return false since the + // present was obviously NOT late. + return false; + } + uint64_t deadline = desired + rdur; + if (actual > deadline) { + return true; + } else { + return false; + } +} +bool CanPresentEarlier(uint64_t earliest, uint64_t actual, uint64_t margin, uint64_t rdur) { + if (earliest < actual) { + // Consider whether this present could have occured earlier. Make sure + // that earliest time was at least 2msec earlier than actual time, and + // that the margin was at least 2msec: + uint64_t diff = actual - earliest; + if ((diff >= (2 * MILLION)) && (margin >= (2 * MILLION))) { + // This present could have occured earlier because both: 1) the + // earliest time was at least 2 msec before actual time, and 2) the + // margin was at least 2msec. + return true; + } + } + return false; +} + +// Forward declarations: +static void demo_resize(struct demo *demo); +static void demo_create_surface(struct demo *demo); + +#if defined(__GNUC__) || defined(__clang__) +#define DECORATE_PRINTF(_fmt_argnum, _first_param_num) __attribute__((format(printf, _fmt_argnum, _first_param_num))) +#else +#define DECORATE_PRINTF(_fmt_num, _first_param_num) +#endif + +DECORATE_PRINTF(4, 5) +static void demo_name_object(struct demo *demo, VkObjectType object_type, uint64_t vulkan_handle, const char *format, ...) { + if (!demo->validate) { + return; + } + VkResult U_ASSERT_ONLY err; + char name[1024]; + va_list argptr; + va_start(argptr, format); + vsnprintf(name, sizeof(name), format, argptr); + va_end(argptr); + name[sizeof(name) - 1] = '\0'; + + VkDebugUtilsObjectNameInfoEXT obj_name = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .pNext = NULL, + .objectType = object_type, + .objectHandle = vulkan_handle, + .pObjectName = name, + }; + err = vkSetDebugUtilsObjectNameEXT(demo->device, &obj_name); + assert(!err); +} + +DECORATE_PRINTF(4, 5) +static void demo_push_cb_label(struct demo *demo, VkCommandBuffer cb, const float *color, const char *format, ...) { + if (!demo->validate) { + return; + } + char name[1024]; + va_list argptr; + va_start(argptr, format); + vsnprintf(name, sizeof(name), format, argptr); + va_end(argptr); + name[sizeof(name) - 1] = '\0'; + + VkDebugUtilsLabelEXT label = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, + .pNext = NULL, + .pLabelName = name, + }; + if (color) { + memcpy(label.color, color, sizeof(label.color)); + } + + vkCmdBeginDebugUtilsLabelEXT(cb, &label); +} + +static void demo_pop_cb_label(struct demo *demo, VkCommandBuffer cb) { + if (!demo->validate) { + return; + } + vkCmdEndDebugUtilsLabelEXT(cb); +} + +static bool memory_type_from_properties(struct demo *demo, uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) { + // Search memtypes to find first index with those properties + for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) { + if ((typeBits & 1) == 1) { + // Type is available, does it match user properties? + if ((demo->memory_properties.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) { + *typeIndex = i; + return true; + } + } + typeBits >>= 1; + } + // No memory types matched, return failure + return false; +} + +static void demo_flush_init_cmd(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + // This function could get called twice if the texture uses a staging buffer + // In that case the second call should be ignored + if (demo->cmd == VK_NULL_HANDLE) return; + + err = vkEndCommandBuffer(demo->cmd); + assert(!err); + + VkFence fence; + VkFenceCreateInfo fence_ci = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = 0}; + if (demo->force_errors) { + // Remove sType to intentionally force validation layer errors. + fence_ci.sType = 0; + } + err = vkCreateFence(demo->device, &fence_ci, NULL, &fence); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_FENCE, (uint64_t)fence, "InitFence"); + + const VkCommandBuffer cmd_bufs[] = {demo->cmd}; + VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 0, + .pWaitSemaphores = NULL, + .pWaitDstStageMask = NULL, + .commandBufferCount = 1, + .pCommandBuffers = cmd_bufs, + .signalSemaphoreCount = 0, + .pSignalSemaphores = NULL}; + + err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, fence); + assert(!err); + + err = vkWaitForFences(demo->device, 1, &fence, VK_TRUE, UINT64_MAX); + assert(!err); + + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, cmd_bufs); + vkDestroyFence(demo->device, fence, NULL); + demo->cmd = VK_NULL_HANDLE; +} + +static void demo_set_image_layout(struct demo *demo, VkImage image, VkImageAspectFlags aspectMask, VkImageLayout old_image_layout, + VkImageLayout new_image_layout, VkAccessFlagBits srcAccessMask, VkPipelineStageFlags src_stages, + VkPipelineStageFlags dest_stages) { + assert(demo->cmd); + + VkImageMemoryBarrier image_memory_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = srcAccessMask, + .dstAccessMask = 0, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .oldLayout = old_image_layout, + .newLayout = new_image_layout, + .image = image, + .subresourceRange = {aspectMask, 0, 1, 0, 1}}; + + switch (new_image_layout) { + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + /* Make sure anything that was copying from this image has completed */ + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: + image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + break; + + default: + image_memory_barrier.dstAccessMask = 0; + break; + } + + VkImageMemoryBarrier *pmemory_barrier = &image_memory_barrier; + + vkCmdPipelineBarrier(demo->cmd, src_stages, dest_stages, 0, 0, NULL, 0, NULL, 1, pmemory_barrier); +} + +static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { + const VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = NULL, + }; + const VkClearValue clear_values[2] = { + [0] = {.color.float32 = {0.2f, 0.2f, 0.2f, 0.2f}}, + [1] = {.depthStencil = {1.0f, 0}}, + }; + const VkRenderPassBeginInfo rp_begin = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = NULL, + .renderPass = demo->render_pass, + .framebuffer = demo->swapchain_image_resources[demo->current_buffer].framebuffer, + .renderArea.offset.x = 0, + .renderArea.offset.y = 0, + .renderArea.extent.width = demo->width, + .renderArea.extent.height = demo->height, + .clearValueCount = 2, + .pClearValues = clear_values, + }; + VkResult U_ASSERT_ONLY err; + + err = vkBeginCommandBuffer(cmd_buf, &cmd_buf_info); + + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)cmd_buf, "CubeDrawCommandBuf"); + + const float begin_color[4] = {0.4f, 0.3f, 0.2f, 0.1f}; + demo_push_cb_label(demo, cmd_buf, begin_color, "DrawBegin"); + + assert(!err); + vkCmdBeginRenderPass(cmd_buf, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); + + const float renderpass_color[4] = {8.4f, 7.3f, 6.2f, 7.1f}; + demo_push_cb_label(demo, cmd_buf, renderpass_color, "InsideRenderPass"); + + vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline); + vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline_layout, 0, 1, + &demo->swapchain_image_resources[demo->current_buffer].descriptor_set, 0, NULL); + VkViewport viewport; + memset(&viewport, 0, sizeof(viewport)); + float viewport_dimension; + if (demo->width < demo->height) { + viewport_dimension = (float)demo->width; + viewport.y = (demo->height - demo->width) / 2.0f; + } else { + viewport_dimension = (float)demo->height; + viewport.x = (demo->width - demo->height) / 2.0f; + } + viewport.height = viewport_dimension; + viewport.width = viewport_dimension; + viewport.minDepth = (float)0.0f; + viewport.maxDepth = (float)1.0f; + vkCmdSetViewport(cmd_buf, 0, 1, &viewport); + + VkRect2D scissor; + memset(&scissor, 0, sizeof(scissor)); + scissor.extent.width = demo->width; + scissor.extent.height = demo->height; + scissor.offset.x = 0; + scissor.offset.y = 0; + vkCmdSetScissor(cmd_buf, 0, 1, &scissor); + + const float draw_color[4] = {-0.4f, -0.3f, -0.2f, -0.1f}; + demo_push_cb_label(demo, cmd_buf, draw_color, "ActualDraw"); + vkCmdDraw(cmd_buf, 12 * 3, 1, 0, 0); + demo_pop_cb_label(demo, cmd_buf); + + // Note that ending the renderpass changes the image's layout from + // COLOR_ATTACHMENT_OPTIMAL to PRESENT_SRC_KHR + vkCmdEndRenderPass(cmd_buf); + demo_pop_cb_label(demo, cmd_buf); + + if (demo->separate_present_queue) { + // We have to transfer ownership from the graphics queue family to the + // present queue family to be able to present. Note that we don't have + // to transfer from present queue family back to graphics queue family at + // the start of the next frame because we don't care about the image's + // contents at that point. + VkImageMemoryBarrier image_ownership_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = demo->graphics_queue_family_index, + .dstQueueFamilyIndex = demo->present_queue_family_index, + .image = demo->swapchain_image_resources[demo->current_buffer].image, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, + NULL, 1, &image_ownership_barrier); + } + demo_pop_cb_label(demo, cmd_buf); + err = vkEndCommandBuffer(cmd_buf); + assert(!err); +} + +void demo_build_image_ownership_cmd(struct demo *demo, int i) { + VkResult U_ASSERT_ONLY err; + + const VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = NULL, + }; + err = vkBeginCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd, &cmd_buf_info); + assert(!err); + + VkImageMemoryBarrier image_ownership_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = demo->graphics_queue_family_index, + .dstQueueFamilyIndex = demo->present_queue_family_index, + .image = demo->swapchain_image_resources[i].image, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + vkCmdPipelineBarrier(demo->swapchain_image_resources[i].graphics_to_present_cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &image_ownership_barrier); + err = vkEndCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd); + assert(!err); +} + +void demo_update_data_buffer(struct demo *demo) { + mat4x4 MVP, Model, VP; + int matrixSize = sizeof(MVP); + + mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix); + + // Rotate around the Y axis + mat4x4_dup(Model, demo->model_matrix); + mat4x4_rotate_Y(demo->model_matrix, Model, (float)degreesToRadians(demo->spin_angle)); + mat4x4_orthonormalize(demo->model_matrix, demo->model_matrix); + mat4x4_mul(MVP, VP, demo->model_matrix); + + memcpy(demo->swapchain_image_resources[demo->current_buffer].uniform_memory_ptr, (const void *)&MVP[0][0], matrixSize); +} + +void DemoUpdateTargetIPD(struct demo *demo) { + // Look at what happened to previous presents, and make appropriate + // adjustments in timing: + VkResult U_ASSERT_ONLY err; + VkPastPresentationTimingGOOGLE *past = NULL; + uint32_t count = 0; + + err = vkGetPastPresentationTimingGOOGLE(demo->device, demo->swapchain, &count, NULL); + assert(!err); + if (count) { + past = (VkPastPresentationTimingGOOGLE *)malloc(sizeof(VkPastPresentationTimingGOOGLE) * count); + assert(past); + err = vkGetPastPresentationTimingGOOGLE(demo->device, demo->swapchain, &count, past); + assert(!err); + + bool early = false; + bool late = false; + bool calibrate_next = false; + for (uint32_t i = 0; i < count; i++) { + if (!demo->syncd_with_actual_presents) { + // This is the first time that we've received an + // actualPresentTime for this swapchain. In order to not + // perceive these early frames as "late", we need to sync-up + // our future desiredPresentTime's with the + // actualPresentTime(s) that we're receiving now. + calibrate_next = true; + + // So that we don't suspect any pending presents as late, + // record them all as suspected-late presents: + demo->last_late_id = demo->next_present_id - 1; + demo->last_early_id = 0; + demo->syncd_with_actual_presents = true; + break; + } else if (CanPresentEarlier(past[i].earliestPresentTime, past[i].actualPresentTime, past[i].presentMargin, + demo->refresh_duration)) { + // This image could have been presented earlier. We don't want + // to decrease the target_IPD until we've seen early presents + // for at least two seconds. + if (demo->last_early_id == past[i].presentID) { + // We've now seen two seconds worth of early presents. + // Flag it as such, and reset the counter: + early = true; + demo->last_early_id = 0; + } else if (demo->last_early_id == 0) { + // This is the first early present we've seen. + // Calculate the presentID for two seconds from now. + uint64_t lastEarlyTime = past[i].actualPresentTime + (2 * BILLION); + uint32_t howManyPresents = (uint32_t)((lastEarlyTime - past[i].actualPresentTime) / demo->target_IPD); + demo->last_early_id = past[i].presentID + howManyPresents; + } else { + // We are in the midst of a set of early images, + // and so we won't do anything. + } + late = false; + demo->last_late_id = 0; + } else if (ActualTimeLate(past[i].desiredPresentTime, past[i].actualPresentTime, demo->refresh_duration)) { + // This image was presented after its desired time. Since + // there's a delay between calling vkQueuePresentKHR and when + // we get the timing data, several presents may have been late. + // Thus, we need to threat all of the outstanding presents as + // being likely late, so that we only increase the target_IPD + // once for all of those presents. + if ((demo->last_late_id == 0) || (demo->last_late_id < past[i].presentID)) { + late = true; + // Record the last suspected-late present: + demo->last_late_id = demo->next_present_id - 1; + } else { + // We are in the midst of a set of likely-late images, + // and so we won't do anything. + } + early = false; + demo->last_early_id = 0; + } else { + // Since this image was not presented early or late, reset + // any sets of early or late presentIDs: + early = false; + late = false; + calibrate_next = true; + demo->last_early_id = 0; + demo->last_late_id = 0; + } + } + + if (early) { + // Since we've seen at least two-seconds worth of presnts that + // could have occured earlier than desired, let's decrease the + // target_IPD (i.e. increase the frame rate): + // + // TODO(ianelliott): Try to calculate a better target_IPD based + // on the most recently-seen present (this is overly-simplistic). + demo->refresh_duration_multiplier--; + if (demo->refresh_duration_multiplier == 0) { + // This should never happen, but in case it does, don't + // try to go faster. + demo->refresh_duration_multiplier = 1; + } + demo->target_IPD = demo->refresh_duration * demo->refresh_duration_multiplier; + } + if (late) { + // Since we found a new instance of a late present, we want to + // increase the target_IPD (i.e. decrease the frame rate): + // + // TODO(ianelliott): Try to calculate a better target_IPD based + // on the most recently-seen present (this is overly-simplistic). + demo->refresh_duration_multiplier++; + demo->target_IPD = demo->refresh_duration * demo->refresh_duration_multiplier; + } + + if (calibrate_next) { + int64_t multiple = demo->next_present_id - past[count - 1].presentID; + demo->prev_desired_present_time = (past[count - 1].actualPresentTime + (multiple * demo->target_IPD)); + } + free(past); + } +} + +static void demo_draw(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + // Ensure no more than FRAME_LAG renderings are outstanding + vkWaitForFences(demo->device, 1, &demo->fences[demo->frame_index], VK_TRUE, UINT64_MAX); + vkResetFences(demo->device, 1, &demo->fences[demo->frame_index]); + + do { + // Get the index of the next available swapchain image: + err = vkAcquireNextImageKHR(demo->device, demo->swapchain, UINT64_MAX, demo->image_acquired_semaphores[demo->frame_index], + VK_NULL_HANDLE, &demo->current_buffer); + + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + // demo->swapchain is out of date (e.g. the window was resized) and + // must be recreated: + demo_resize(demo); + } else if (err == VK_SUBOPTIMAL_KHR) { + // demo->swapchain is not as optimal as it could be, but the platform's + // presentation engine will still present the image correctly. + break; + } else if (err == VK_ERROR_SURFACE_LOST_KHR) { + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + demo_create_surface(demo); + demo_resize(demo); + } else { + assert(!err); + } + } while (err != VK_SUCCESS); + + demo_update_data_buffer(demo); + + if (demo->VK_GOOGLE_display_timing_enabled) { + // Look at what happened to previous presents, and make appropriate + // adjustments in timing: + DemoUpdateTargetIPD(demo); + + // Note: a real application would position its geometry to that it's in + // the correct locatoin for when the next image is presented. It might + // also wait, so that there's less latency between any input and when + // the next image is rendered/presented. This demo program is so + // simple that it doesn't do either of those. + } + + // Wait for the image acquired semaphore to be signaled to ensure + // that the image won't be rendered to until the presentation + // engine has fully released ownership to the application, and it is + // okay to render to the image. + VkPipelineStageFlags pipe_stage_flags; + VkSubmitInfo submit_info; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = NULL; + pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.pWaitDstStageMask = &pipe_stage_flags; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &demo->image_acquired_semaphores[demo->frame_index]; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].cmd; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; + err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, demo->fences[demo->frame_index]); + assert(!err); + + if (demo->separate_present_queue) { + // If we are using separate queues, change image ownership to the + // present queue before presenting, waiting for the draw complete + // semaphore and signalling the ownership released semaphore when finished + VkFence nullFence = VK_NULL_HANDLE; + pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].graphics_to_present_cmd; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &demo->image_ownership_semaphores[demo->frame_index]; + err = vkQueueSubmit(demo->present_queue, 1, &submit_info, nullFence); + assert(!err); + } + + // If we are using separate queues we have to wait for image ownership, + // otherwise wait for draw complete + VkPresentInfoKHR present = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = NULL, + .waitSemaphoreCount = 1, + .pWaitSemaphores = (demo->separate_present_queue) ? &demo->image_ownership_semaphores[demo->frame_index] + : &demo->draw_complete_semaphores[demo->frame_index], + .swapchainCount = 1, + .pSwapchains = &demo->swapchain, + .pImageIndices = &demo->current_buffer, + }; + + VkRectLayerKHR rect; + VkPresentRegionKHR region; + VkPresentRegionsKHR regions; + if (demo->VK_KHR_incremental_present_enabled) { + // If using VK_KHR_incremental_present, we provide a hint of the region + // that contains changed content relative to the previously-presented + // image. The implementation can use this hint in order to save + // work/power (by only copying the region in the hint). The + // implementation is free to ignore the hint though, and so we must + // ensure that the entire image has the correctly-drawn content. + uint32_t eighthOfWidth = demo->width / 8; + uint32_t eighthOfHeight = demo->height / 8; + + if (demo->first_swapchain_frame) { + rect.offset.x = 0; + rect.offset.y = 0; + rect.extent.width = demo->width; + rect.extent.height = demo->height; + } else { + rect.offset.x = eighthOfWidth; + rect.offset.y = eighthOfHeight; + rect.extent.width = eighthOfWidth * 6; + rect.extent.height = eighthOfHeight * 6; + } + rect.layer = 0; + + region.rectangleCount = 1; + region.pRectangles = ▭ + + regions.sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR; + regions.pNext = present.pNext; + regions.swapchainCount = present.swapchainCount; + regions.pRegions = ®ion; + present.pNext = ®ions; + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + VkPresentTimeGOOGLE ptime; + if (demo->prev_desired_present_time == 0) { + // This must be the first present for this swapchain. + // + // We don't know where we are relative to the presentation engine's + // display's refresh cycle. We also don't know how long rendering + // takes. Let's make a grossly-simplified assumption that the + // desiredPresentTime should be half way between now and + // now+target_IPD. We will adjust over time. + uint64_t curtime = getTimeInNanoseconds(); + if (curtime == 0) { + // Since we didn't find out the current time, don't give a + // desiredPresentTime: + ptime.desiredPresentTime = 0; + } else { + ptime.desiredPresentTime = curtime + (demo->target_IPD >> 1); + } + } else { + ptime.desiredPresentTime = (demo->prev_desired_present_time + demo->target_IPD); + } + ptime.presentID = demo->next_present_id++; + demo->prev_desired_present_time = ptime.desiredPresentTime; + + VkPresentTimesInfoGOOGLE present_time = { + .sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE, + .pNext = present.pNext, + .swapchainCount = present.swapchainCount, + .pTimes = &ptime, + }; + if (demo->VK_GOOGLE_display_timing_enabled) { + present.pNext = &present_time; + } + } + + err = vkQueuePresentKHR(demo->present_queue, &present); + demo->frame_index += 1; + demo->frame_index %= FRAME_LAG; + demo->first_swapchain_frame = false; + + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + // demo->swapchain is out of date (e.g. the window was resized) and + // must be recreated: + demo_resize(demo); + } else if (err == VK_SUBOPTIMAL_KHR) { + // SUBOPTIMAL could be due to a resize + VkSurfaceCapabilitiesKHR surfCapabilities; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(demo->gpu, demo->surface, &surfCapabilities); + assert(!err); + if (surfCapabilities.currentExtent.width != (uint32_t)demo->width || + surfCapabilities.currentExtent.height != (uint32_t)demo->height) { + demo_resize(demo); + } + } else if (err == VK_ERROR_SURFACE_LOST_KHR) { + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + demo_create_surface(demo); + demo_resize(demo); + } else { + assert(!err); + } +} + +static void demo_prepare_buffers(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + VkSwapchainKHR oldSwapchain = demo->swapchain; + + // Check the surface capabilities and formats + VkSurfaceCapabilitiesKHR surfCapabilities; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(demo->gpu, demo->surface, &surfCapabilities); + assert(!err); + + uint32_t presentModeCount; + err = vkGetPhysicalDeviceSurfacePresentModesKHR(demo->gpu, demo->surface, &presentModeCount, NULL); + assert(!err); + VkPresentModeKHR *presentModes = (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR)); + assert(presentModes); + err = vkGetPhysicalDeviceSurfacePresentModesKHR(demo->gpu, demo->surface, &presentModeCount, presentModes); + assert(!err); + + VkExtent2D swapchainExtent; + // width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF. + if (surfCapabilities.currentExtent.width == 0xFFFFFFFF) { + // If the surface size is undefined, the size is set to the size + // of the images requested, which must fit within the minimum and + // maximum values. + swapchainExtent.width = demo->width; + swapchainExtent.height = demo->height; + + if (swapchainExtent.width < surfCapabilities.minImageExtent.width) { + swapchainExtent.width = surfCapabilities.minImageExtent.width; + } else if (swapchainExtent.width > surfCapabilities.maxImageExtent.width) { + swapchainExtent.width = surfCapabilities.maxImageExtent.width; + } + + if (swapchainExtent.height < surfCapabilities.minImageExtent.height) { + swapchainExtent.height = surfCapabilities.minImageExtent.height; + } else if (swapchainExtent.height > surfCapabilities.maxImageExtent.height) { + swapchainExtent.height = surfCapabilities.maxImageExtent.height; + } + } else { + // If the surface size is defined, the swap chain size must match + swapchainExtent = surfCapabilities.currentExtent; + demo->width = surfCapabilities.currentExtent.width; + demo->height = surfCapabilities.currentExtent.height; + } + + if (surfCapabilities.maxImageExtent.width == 0 || surfCapabilities.maxImageExtent.height == 0) { + demo->is_minimized = true; + return; + } else { + demo->is_minimized = false; + } + + // The FIFO present mode is guaranteed by the spec to be supported + // and to have no tearing. It's a great default present mode to use. + VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; + + // There are times when you may wish to use another present mode. The + // following code shows how to select them, and the comments provide some + // reasons you may wish to use them. + // + // It should be noted that Vulkan 1.0 doesn't provide a method for + // synchronizing rendering with the presentation engine's display. There + // is a method provided for throttling rendering with the display, but + // there are some presentation engines for which this method will not work. + // If an application doesn't throttle its rendering, and if it renders much + // faster than the refresh rate of the display, this can waste power on + // mobile devices. That is because power is being spent rendering images + // that may never be seen. + + // VK_PRESENT_MODE_IMMEDIATE_KHR is for applications that don't care about + // tearing, or have some way of synchronizing their rendering with the + // display. + // VK_PRESENT_MODE_MAILBOX_KHR may be useful for applications that + // generally render a new presentable image every refresh cycle, but are + // occasionally early. In this case, the application wants the new image + // to be displayed instead of the previously-queued-for-presentation image + // that has not yet been displayed. + // VK_PRESENT_MODE_FIFO_RELAXED_KHR is for applications that generally + // render a new presentable image every refresh cycle, but are occasionally + // late. In this case (perhaps because of stuttering/latency concerns), + // the application wants the late image to be immediately displayed, even + // though that may mean some tearing. + + if (demo->presentMode != swapchainPresentMode) { + for (size_t i = 0; i < presentModeCount; ++i) { + if (presentModes[i] == demo->presentMode) { + swapchainPresentMode = demo->presentMode; + break; + } + } + } + if (swapchainPresentMode != demo->presentMode) { + ERR_EXIT("Present mode specified is not supported\n", "Present mode unsupported"); + } + + // Determine the number of VkImages to use in the swap chain. + // Application desires to acquire 3 images at a time for triple + // buffering + uint32_t desiredNumOfSwapchainImages = 3; + if (desiredNumOfSwapchainImages < surfCapabilities.minImageCount) { + desiredNumOfSwapchainImages = surfCapabilities.minImageCount; + } + // If maxImageCount is 0, we can ask for as many images as we want; + // otherwise we're limited to maxImageCount + if ((surfCapabilities.maxImageCount > 0) && (desiredNumOfSwapchainImages > surfCapabilities.maxImageCount)) { + // Application must settle for fewer images than desired: + desiredNumOfSwapchainImages = surfCapabilities.maxImageCount; + } + + VkSurfaceTransformFlagsKHR preTransform; + if (surfCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { + preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + } else { + preTransform = surfCapabilities.currentTransform; + } + + // Find a supported composite alpha mode - one of these is guaranteed to be set + VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + VkCompositeAlphaFlagBitsKHR compositeAlphaFlags[4] = { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + }; + for (uint32_t i = 0; i < ARRAY_SIZE(compositeAlphaFlags); i++) { + if (surfCapabilities.supportedCompositeAlpha & compositeAlphaFlags[i]) { + compositeAlpha = compositeAlphaFlags[i]; + break; + } + } + + VkSwapchainCreateInfoKHR swapchain_ci = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = NULL, + .surface = demo->surface, + .minImageCount = desiredNumOfSwapchainImages, + .imageFormat = demo->format, + .imageColorSpace = demo->color_space, + .imageExtent = + { + .width = swapchainExtent.width, + .height = swapchainExtent.height, + }, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .preTransform = preTransform, + .compositeAlpha = compositeAlpha, + .imageArrayLayers = 1, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + .presentMode = swapchainPresentMode, + .oldSwapchain = oldSwapchain, + .clipped = true, + }; + uint32_t i; + err = vkCreateSwapchainKHR(demo->device, &swapchain_ci, NULL, &demo->swapchain); + assert(!err); + + // If we just re-created an existing swapchain, we should destroy the old + // swapchain at this point. + // Note: destroying the swapchain also cleans up all its associated + // presentable images once the platform is done with them. + if (oldSwapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(demo->device, oldSwapchain, NULL); + } + + err = vkGetSwapchainImagesKHR(demo->device, demo->swapchain, &demo->swapchainImageCount, NULL); + assert(!err); + + VkImage *swapchainImages = (VkImage *)malloc(demo->swapchainImageCount * sizeof(VkImage)); + assert(swapchainImages); + err = vkGetSwapchainImagesKHR(demo->device, demo->swapchain, &demo->swapchainImageCount, swapchainImages); + assert(!err); + + demo->swapchain_image_resources = + (SwapchainImageResources *)malloc(sizeof(SwapchainImageResources) * demo->swapchainImageCount); + assert(demo->swapchain_image_resources); + + for (i = 0; i < demo->swapchainImageCount; i++) { + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)swapchainImages[i], "SwapchainImage(%u)", i); + } + for (i = 0; i < demo->swapchainImageCount; i++) { + VkImageViewCreateInfo color_image_view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .format = demo->format, + .components = + { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = + {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .flags = 0, + }; + + demo->swapchain_image_resources[i].image = swapchainImages[i]; + + color_image_view.image = demo->swapchain_image_resources[i].image; + + err = vkCreateImageView(demo->device, &color_image_view, NULL, &demo->swapchain_image_resources[i].view); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->swapchain_image_resources[i].view, "SwapchainView(%u)", + i); + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + VkRefreshCycleDurationGOOGLE rc_dur; + err = vkGetRefreshCycleDurationGOOGLE(demo->device, demo->swapchain, &rc_dur); + assert(!err); + demo->refresh_duration = rc_dur.refreshDuration; + + demo->syncd_with_actual_presents = false; + // Initially target 1X the refresh duration: + demo->target_IPD = demo->refresh_duration; + demo->refresh_duration_multiplier = 1; + demo->prev_desired_present_time = 0; + demo->next_present_id = 1; + } + + if (NULL != swapchainImages) { + free(swapchainImages); + } + + if (NULL != presentModes) { + free(presentModes); + } +} + +static void demo_prepare_depth(struct demo *demo) { + const VkFormat depth_format = VK_FORMAT_D16_UNORM; + const VkImageCreateInfo image = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .imageType = VK_IMAGE_TYPE_2D, + .format = depth_format, + .extent = {demo->width, demo->height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .flags = 0, + }; + + VkImageViewCreateInfo view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .image = VK_NULL_HANDLE, + .format = depth_format, + .subresourceRange = + {.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}, + .flags = 0, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + }; + + if (demo->force_errors) { + // Intentionally force a bad pNext value to generate a validation layer error + view.pNext = ℑ + } + + VkMemoryRequirements mem_reqs; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + demo->depth.format = depth_format; + + /* create image */ + err = vkCreateImage(demo->device, &image, NULL, &demo->depth.image); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)demo->depth.image, "DepthImage"); + + vkGetImageMemoryRequirements(demo->device, demo->depth.image, &mem_reqs); + assert(!err); + + demo->depth.mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + demo->depth.mem_alloc.pNext = NULL; + demo->depth.mem_alloc.allocationSize = mem_reqs.size; + demo->depth.mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &demo->depth.mem_alloc.memoryTypeIndex); + assert(pass); + + /* allocate memory */ + err = vkAllocateMemory(demo->device, &demo->depth.mem_alloc, NULL, &demo->depth.mem); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->depth.mem, "DepthMem"); + + /* bind memory */ + err = vkBindImageMemory(demo->device, demo->depth.image, demo->depth.mem, 0); + assert(!err); + + /* create image view */ + view.image = demo->depth.image; + err = vkCreateImageView(demo->device, &view, NULL, &demo->depth.view); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->depth.view, "DepthView"); +} + +/* Convert ppm image data from header file into RGBA texture image */ +#include "lunarg.ppm.h" +bool loadTexture(const char *filename, uint8_t *rgba_data, VkSubresourceLayout *layout, int32_t *width, int32_t *height) { + (void)filename; + char *cPtr; + cPtr = (char *)lunarg_ppm; + if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "P6\n", 3)) { + return false; + } + while (strncmp(cPtr++, "\n", 1)) + ; + sscanf(cPtr, "%u %u", width, height); + if (rgba_data == NULL) { + return true; + } + while (strncmp(cPtr++, "\n", 1)) + ; + if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "255\n", 4)) { + return false; + } + while (strncmp(cPtr++, "\n", 1)) + ; + for (int y = 0; y < *height; y++) { + uint8_t *rowPtr = rgba_data; + for (int x = 0; x < *width; x++) { + memcpy(rowPtr, cPtr, 3); + rowPtr[3] = 255; /* Alpha of 1 */ + rowPtr += 4; + cPtr += 3; + } + rgba_data += layout->rowPitch; + } + return true; +} + +static void demo_prepare_texture_buffer(struct demo *demo, const char *filename, struct texture_object *tex_obj) { + int32_t tex_width; + int32_t tex_height; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + if (!loadTexture(filename, NULL, NULL, &tex_width, &tex_height)) { + ERR_EXIT("Failed to load textures", "Load Texture Failure"); + } + + tex_obj->tex_width = tex_width; + tex_obj->tex_height = tex_height; + + const VkBufferCreateInfo buffer_create_info = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .size = tex_width * tex_height * 4, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL}; + + err = vkCreateBuffer(demo->device, &buffer_create_info, NULL, &tex_obj->buffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)tex_obj->buffer, "TexBuffer(%s)", filename); + + VkMemoryRequirements mem_reqs; + vkGetBufferMemoryRequirements(demo->device, tex_obj->buffer, &mem_reqs); + + tex_obj->mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + tex_obj->mem_alloc.pNext = NULL; + tex_obj->mem_alloc.allocationSize = mem_reqs.size; + tex_obj->mem_alloc.memoryTypeIndex = 0; + + VkFlags requirements = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, requirements, &tex_obj->mem_alloc.memoryTypeIndex); + assert(pass); + + err = vkAllocateMemory(demo->device, &tex_obj->mem_alloc, NULL, &(tex_obj->mem)); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)tex_obj->mem, "TexBufMemory(%s)", filename); + + /* bind memory */ + err = vkBindBufferMemory(demo->device, tex_obj->buffer, tex_obj->mem, 0); + assert(!err); + + VkSubresourceLayout layout; + memset(&layout, 0, sizeof(layout)); + layout.rowPitch = tex_width * 4; + + void *data; + err = vkMapMemory(demo->device, tex_obj->mem, 0, tex_obj->mem_alloc.allocationSize, 0, &data); + assert(!err); + + if (!loadTexture(filename, data, &layout, &tex_width, &tex_height)) { + fprintf(stderr, "Error loading texture: %s\n", filename); + } + + vkUnmapMemory(demo->device, tex_obj->mem); +} + +static void demo_prepare_texture_image(struct demo *demo, const char *filename, struct texture_object *tex_obj, + VkImageTiling tiling, VkImageUsageFlags usage, VkFlags required_props) { + const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM; + int32_t tex_width; + int32_t tex_height; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + if (!loadTexture(filename, NULL, NULL, &tex_width, &tex_height)) { + ERR_EXIT("Failed to load textures", "Load Texture Failure"); + } + + tex_obj->tex_width = tex_width; + tex_obj->tex_height = tex_height; + + const VkImageCreateInfo image_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .imageType = VK_IMAGE_TYPE_2D, + .format = tex_format, + .extent = {tex_width, tex_height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = tiling, + .usage = usage, + .flags = 0, + .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, + }; + + VkMemoryRequirements mem_reqs; + + err = vkCreateImage(demo->device, &image_create_info, NULL, &tex_obj->image); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)tex_obj->image, "TexImage(%s)", filename); + + vkGetImageMemoryRequirements(demo->device, tex_obj->image, &mem_reqs); + + tex_obj->mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + tex_obj->mem_alloc.pNext = NULL; + tex_obj->mem_alloc.allocationSize = mem_reqs.size; + tex_obj->mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, required_props, &tex_obj->mem_alloc.memoryTypeIndex); + assert(pass); + + /* allocate memory */ + err = vkAllocateMemory(demo->device, &tex_obj->mem_alloc, NULL, &(tex_obj->mem)); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)tex_obj->mem, "TexImageMem(%s)", filename); + + /* bind memory */ + err = vkBindImageMemory(demo->device, tex_obj->image, tex_obj->mem, 0); + assert(!err); + + if (required_props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { + const VkImageSubresource subres = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .arrayLayer = 0, + }; + VkSubresourceLayout layout; + void *data; + + vkGetImageSubresourceLayout(demo->device, tex_obj->image, &subres, &layout); + + err = vkMapMemory(demo->device, tex_obj->mem, 0, tex_obj->mem_alloc.allocationSize, 0, &data); + assert(!err); + + if (!loadTexture(filename, data, &layout, &tex_width, &tex_height)) { + fprintf(stderr, "Error loading texture: %s\n", filename); + } + + vkUnmapMemory(demo->device, tex_obj->mem); + } + + tex_obj->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +} + +static void demo_destroy_texture(struct demo *demo, struct texture_object *tex_objs) { + /* clean up staging resources */ + vkFreeMemory(demo->device, tex_objs->mem, NULL); + if (tex_objs->image) vkDestroyImage(demo->device, tex_objs->image, NULL); + if (tex_objs->buffer) vkDestroyBuffer(demo->device, tex_objs->buffer, NULL); +} + +static void demo_prepare_textures(struct demo *demo) { + const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM; + VkFormatProperties props; + uint32_t i; + + vkGetPhysicalDeviceFormatProperties(demo->gpu, tex_format, &props); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + VkResult U_ASSERT_ONLY err; + + if ((props.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) && !demo->use_staging_buffer) { + demo_push_cb_label(demo, demo->cmd, NULL, "DirectTexture(%u)", i); + /* Device can texture using linear textures */ + demo_prepare_texture_image(demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + // Nothing in the pipeline needs to be complete to start, and don't allow fragment + // shader to run until layout transition completes + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, + demo->textures[i].imageLayout, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + demo->staging_texture.image = 0; + demo_pop_cb_label(demo, demo->cmd); // "DirectTexture" + } else if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) { + /* Must use staging buffer to copy linear texture to optimized */ + demo_push_cb_label(demo, demo->cmd, NULL, "StagingTexture(%u)", i); + + memset(&demo->staging_texture, 0, sizeof(demo->staging_texture)); + demo_prepare_texture_buffer(demo, tex_files[i], &demo->staging_texture); + + demo_prepare_texture_image(demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_OPTIMAL, + (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + demo_push_cb_label(demo, demo->cmd, NULL, "StagingBufferCopy(%u)", i); + + VkBufferImageCopy copy_region = { + .bufferOffset = 0, + .bufferRowLength = demo->staging_texture.tex_width, + .bufferImageHeight = demo->staging_texture.tex_height, + .imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {demo->staging_texture.tex_width, demo->staging_texture.tex_height, 1}, + }; + + vkCmdCopyBufferToImage(demo->cmd, demo->staging_texture.buffer, demo->textures[i].image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + demo_pop_cb_label(demo, demo->cmd); // "StagingBufferCopy" + + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + demo->textures[i].imageLayout, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + demo_pop_cb_label(demo, demo->cmd); // "StagingTexture" + + } else { + /* Can't support VK_FORMAT_R8G8B8A8_UNORM !? */ + assert(!"No support for R8G8B8A8_UNORM as texture image format"); + } + + const VkSamplerCreateInfo sampler = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = NULL, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 1, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, + .unnormalizedCoordinates = VK_FALSE, + }; + + VkImageViewCreateInfo view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .image = VK_NULL_HANDLE, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = tex_format, + .components = + { + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + .flags = 0, + }; + + /* create sampler */ + err = vkCreateSampler(demo->device, &sampler, NULL, &demo->textures[i].sampler); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SAMPLER, (uint64_t)demo->textures[i].sampler, "Sampler(%u)", i); + + /* create image view */ + view.image = demo->textures[i].image; + err = vkCreateImageView(demo->device, &view, NULL, &demo->textures[i].view); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->textures[i].view, "TexImageView(%u)", i); + assert(!err); + } +} + +void demo_prepare_cube_data_buffers(struct demo *demo) { + VkBufferCreateInfo buf_info; + VkMemoryRequirements mem_reqs; + VkMemoryAllocateInfo mem_alloc; + mat4x4 MVP, VP; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + struct vktexcube_vs_uniform data; + + mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix); + mat4x4_mul(MVP, VP, demo->model_matrix); + memcpy(data.mvp, MVP, sizeof(MVP)); + // dumpMatrix("MVP", MVP); + + for (unsigned int i = 0; i < 12 * 3; i++) { + data.position[i][0] = g_vertex_buffer_data[i * 3]; + data.position[i][1] = g_vertex_buffer_data[i * 3 + 1]; + data.position[i][2] = g_vertex_buffer_data[i * 3 + 2]; + data.position[i][3] = 1.0f; + data.attr[i][0] = g_uv_buffer_data[2 * i]; + data.attr[i][1] = g_uv_buffer_data[2 * i + 1]; + data.attr[i][2] = 0; + data.attr[i][3] = 0; + } + + memset(&buf_info, 0, sizeof(buf_info)); + buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + buf_info.size = sizeof(data); + + for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { + err = vkCreateBuffer(demo->device, &buf_info, NULL, &demo->swapchain_image_resources[i].uniform_buffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)demo->swapchain_image_resources[i].uniform_buffer, + "SwapchainUniformBuf(%u)", i); + + vkGetBufferMemoryRequirements(demo->device, demo->swapchain_image_resources[i].uniform_buffer, &mem_reqs); + + mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + mem_alloc.pNext = NULL; + mem_alloc.allocationSize = mem_reqs.size; + mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &mem_alloc.memoryTypeIndex); + assert(pass); + + err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &demo->swapchain_image_resources[i].uniform_memory); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->swapchain_image_resources[i].uniform_memory, + "SwapchainUniformMem(%u)", i); + + err = vkMapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, 0, VK_WHOLE_SIZE, 0, + &demo->swapchain_image_resources[i].uniform_memory_ptr); + assert(!err); + + memcpy(demo->swapchain_image_resources[i].uniform_memory_ptr, &data, sizeof data); + + err = vkBindBufferMemory(demo->device, demo->swapchain_image_resources[i].uniform_buffer, + demo->swapchain_image_resources[i].uniform_memory, 0); + assert(!err); + } +} + +#if DESCRIPTOR_HEAP +void demo_prepare_cube_descriptor_heap(struct demo *demo) +{ + VkResult err = VK_SUCCESS; + VkBufferCreateInfo heapInfo; + VkMemoryRequirements memReqs; + VkMemoryAllocateInfo memAllocInfo; + + { + //create sampler descriptor heap + memset(&heapInfo, 0, sizeof(heapInfo)); + heapInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + heapInfo.flags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR; + heapInfo.size = demo->descriptorHeapProperties.maxSamplerHeapSize; + err = vkCreateBuffer(demo->device, &heapInfo, NULL, &demo->descHeap[0]); + + // VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements); + vkGetBufferMemoryRequirements(demo->device, demo->descHeap[0], &memReqs); + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = NULL; + memAllocInfo.allocationSize = memReqs.size; + // memAllocInfo.memoryTypeIndex = 0; + + memory_type_from_properties(demo, memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + &memAllocInfo.memoryTypeIndex); + + + err = vkAllocateMemory(demo->device, &memAllocInfo, NULL, &demo->descHeapMemory[0]); + err = vkBindBufferMemory(demo->device, demo->descHeap[0], demo->descHeapMemory[0], 0); + + //heap buffer host ponter + VkHostAddressStridedRangeKHR bufferRagge = { + .size = demo->descriptorHeapProperties.bufferDescriptorSize, + .stride = demo->descriptorHeapProperties.bufferHeapDescriptorAlignment, + .pData = NULL + }; + vkMapMemory(demo->device, demo->descHeapMemory[0], 0, VK_WHOLE_SIZE, 0, &bufferRagge.pData); + + PFN_vkWriteBufferDescriptorsKHR vkWriteBufferDescriptorsKHR = + (PFN_vkWriteBufferDescriptorsKHR) vkGetDeviceProcAddr(demo->device, "vkWriteBufferDescriptorsKHR"); + // PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR = + // (PFN_vkGetBufferDeviceAddressKHR) vkGetDeviceProcAddr(demo->device, "vkGetBufferDeviceAddressKHR111111111111"); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) + { + VkBufferDeviceAddressInfo addressInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = NULL, + .buffer = demo->swapchain_image_resources[i].uniform_buffer + }; + + // prepare buffer descriptors + VkBufferDescriptorInfoKHR bufferDescInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DESCRIPTOR_INFO_KHR, + .pNext = NULL, + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .addressRange.address = 0, + .addressRange.size = sizeof(struct vktexcube_vs_uniform), + }; + bufferDescInfo.addressRange.address = vkGetBufferDeviceAddressKHR(demo->device, &addressInfo); + + err = vkWriteBufferDescriptorsKHR(demo->device, 1, &bufferDescInfo ,bufferRagge); + + bufferRagge.pData += bufferRagge.size; + } + + vkUnmapMemory(demo->device, demo->descHeapMemory[0]); + } + + { + //create buffer descriptor heap + memset(&heapInfo, 0, sizeof(heapInfo)); + memset(&memReqs, 0, sizeof(memReqs)); + memset(&memAllocInfo, 0, sizeof(memAllocInfo)); + + heapInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + heapInfo.flags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR; + heapInfo.size = demo->descriptorHeapProperties.maxBufferHeapSize; + err = vkCreateBuffer(demo->device, &heapInfo, NULL, &demo->descHeap[1]); + + vkGetBufferMemoryRequirements(demo->device, demo->descHeap[1], &memReqs); + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = NULL; + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = 0; + err = vkAllocateMemory(demo->device, &memAllocInfo, NULL, &demo->descHeapMemory[1]); + err = vkBindBufferMemory(demo->device, demo->descHeap[1], demo->descHeapMemory[1], 0); + } + + { + //create image descriptor heap + memset(&heapInfo, 0, sizeof(heapInfo)); + memset(&memReqs, 0, sizeof(memReqs)); + memset(&memAllocInfo, 0, sizeof(memAllocInfo)); + + heapInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + heapInfo.flags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR; + heapInfo.size = demo->descriptorHeapProperties.maxImageHeapSize; + err = vkCreateBuffer(demo->device, &heapInfo, NULL, &demo->descHeap[2]); + + vkGetBufferMemoryRequirements(demo->device, demo->descHeap[2], &memReqs); + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = NULL; + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = 0; + err = vkAllocateMemory(demo->device, &memAllocInfo, NULL, &demo->descHeapMemory[2]); + err = vkBindBufferMemory(demo->device, demo->descHeap[2], demo->descHeapMemory[2], 0); + } + + // try write buffer to buffer heap + + assert(!err); +} +#endif + +static void demo_prepare_descriptor_layout(struct demo *demo) { +#if DESCRIPTOR_HEAP + +#else + const VkDescriptorSetLayoutBinding layout_bindings[2] = { + [0] = + { + .binding = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = DEMO_TEXTURE_COUNT, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = NULL, + }, + [1] = + { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .pImmutableSamplers = NULL, + }, + }; + const VkDescriptorSetLayoutCreateInfo descriptor_layout = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = NULL, + .bindingCount = 2, + .pBindings = layout_bindings, + }; + VkResult U_ASSERT_ONLY err; + + err = vkCreateDescriptorSetLayout(demo->device, &descriptor_layout, NULL, &demo->desc_layout); + assert(!err); + + const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = NULL, + .setLayoutCount = 1, + .pSetLayouts = &demo->desc_layout, + }; + + err = vkCreatePipelineLayout(demo->device, &pPipelineLayoutCreateInfo, NULL, &demo->pipeline_layout); + assert(!err); +#endif +} + +static void demo_prepare_render_pass(struct demo *demo) { + // The initial layout for the color and depth attachments will be LAYOUT_UNDEFINED + // because at the start of the renderpass, we don't care about their contents. + // At the start of the subpass, the color attachment's layout will be transitioned + // to LAYOUT_COLOR_ATTACHMENT_OPTIMAL and the depth stencil attachment's layout + // will be transitioned to LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL. At the end of + // the renderpass, the color attachment's layout will be transitioned to + // LAYOUT_PRESENT_SRC_KHR to be ready to present. This is all done as part of + // the renderpass, no barriers are necessary. + const VkAttachmentDescription attachments[2] = { + [0] = + { + .format = demo->format, + .flags = 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_PRESENT_SRC_KHR, + }, + [1] = + { + .format = demo->depth.format, + .flags = 0, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .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, + }, + }; + const VkAttachmentReference color_reference = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + const VkAttachmentReference depth_reference = { + .attachment = 1, + .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }; + const VkSubpassDescription subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .flags = 0, + .inputAttachmentCount = 0, + .pInputAttachments = NULL, + .colorAttachmentCount = 1, + .pColorAttachments = &color_reference, + .pResolveAttachments = NULL, + .pDepthStencilAttachment = &depth_reference, + .preserveAttachmentCount = 0, + .pPreserveAttachments = NULL, + }; + + VkSubpassDependency attachmentDependencies[2] = { + [0] = + { + // Depth buffer is shared between swapchain images + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0, + }, + [1] = + { + // Image Layout Transition + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + .dependencyFlags = 0, + }, + }; + + const VkRenderPassCreateInfo rp_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .attachmentCount = 2, + .pAttachments = attachments, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 2, + .pDependencies = attachmentDependencies, + }; + VkResult U_ASSERT_ONLY err; + + err = vkCreateRenderPass(demo->device, &rp_info, NULL, &demo->render_pass); + assert(!err); +} + +static VkShaderModule demo_prepare_shader_module(const char *name, struct demo *demo, const uint32_t *code, size_t size) { + VkShaderModule module; + VkShaderModuleCreateInfo moduleCreateInfo; + VkResult U_ASSERT_ONLY err; + + moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + moduleCreateInfo.pNext = NULL; + moduleCreateInfo.flags = 0; + moduleCreateInfo.codeSize = size; + moduleCreateInfo.pCode = code; + + err = vkCreateShaderModule(demo->device, &moduleCreateInfo, NULL, &module); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)module, "%s", name); + + return module; +} + +static void demo_prepare_vs(struct demo *demo) { + const uint32_t vs_code[] = { +#include "cube.vert.inc" + }; + demo->vert_shader_module = demo_prepare_shader_module("cube.vert", demo, vs_code, sizeof(vs_code)); +} + +static void demo_prepare_fs(struct demo *demo) { + const uint32_t fs_code[] = { +#include "cube.frag.inc" + }; + demo->frag_shader_module = demo_prepare_shader_module("cube.frag", demo, fs_code, sizeof(fs_code)); +} + +static void demo_prepare_pipeline(struct demo *demo) { +#define NUM_DYNAMIC_STATES 2 /*Viewport + Scissor*/ + + VkGraphicsPipelineCreateInfo pipeline; + VkPipelineCacheCreateInfo pipelineCache; + VkPipelineVertexInputStateCreateInfo vi; + VkPipelineInputAssemblyStateCreateInfo ia; + VkPipelineRasterizationStateCreateInfo rs; + VkPipelineColorBlendStateCreateInfo cb; + VkPipelineDepthStencilStateCreateInfo ds; + VkPipelineViewportStateCreateInfo vp; + VkPipelineMultisampleStateCreateInfo ms; + VkDynamicState dynamicStateEnables[NUM_DYNAMIC_STATES]; + VkPipelineDynamicStateCreateInfo dynamicState; + VkResult U_ASSERT_ONLY err; + + memset(dynamicStateEnables, 0, sizeof dynamicStateEnables); + memset(&dynamicState, 0, sizeof dynamicState); + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.pDynamicStates = dynamicStateEnables; + + memset(&pipeline, 0, sizeof(pipeline)); + pipeline.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline.layout = demo->pipeline_layout; + + memset(&vi, 0, sizeof(vi)); + vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + memset(&ia, 0, sizeof(ia)); + ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + memset(&rs, 0, sizeof(rs)); + rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rs.polygonMode = VK_POLYGON_MODE_FILL; + rs.cullMode = VK_CULL_MODE_BACK_BIT; + rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rs.depthClampEnable = VK_FALSE; + rs.rasterizerDiscardEnable = VK_FALSE; + rs.depthBiasEnable = VK_FALSE; + rs.lineWidth = 1.0f; + + memset(&cb, 0, sizeof(cb)); + cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + VkPipelineColorBlendAttachmentState att_state[1]; + memset(att_state, 0, sizeof(att_state)); + att_state[0].colorWriteMask = 0xf; + att_state[0].blendEnable = VK_FALSE; + cb.attachmentCount = 1; + cb.pAttachments = att_state; + + memset(&vp, 0, sizeof(vp)); + vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + vp.viewportCount = 1; + dynamicStateEnables[dynamicState.dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT; + vp.scissorCount = 1; + dynamicStateEnables[dynamicState.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; + + memset(&ds, 0, sizeof(ds)); + ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + ds.depthTestEnable = VK_TRUE; + ds.depthWriteEnable = VK_TRUE; + ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; + ds.depthBoundsTestEnable = VK_FALSE; + ds.back.failOp = VK_STENCIL_OP_KEEP; + ds.back.passOp = VK_STENCIL_OP_KEEP; + ds.back.compareOp = VK_COMPARE_OP_ALWAYS; + ds.stencilTestEnable = VK_FALSE; + ds.front = ds.back; + + memset(&ms, 0, sizeof(ms)); + ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms.pSampleMask = NULL; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + demo_prepare_vs(demo); + demo_prepare_fs(demo); + + // Two stages: vs and fs + VkPipelineShaderStageCreateInfo shaderStages[2]; + memset(&shaderStages, 0, 2 * sizeof(VkPipelineShaderStageCreateInfo)); + + shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + shaderStages[0].module = demo->vert_shader_module; + 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].module = demo->frag_shader_module; + shaderStages[1].pName = "main"; + + memset(&pipelineCache, 0, sizeof(pipelineCache)); + pipelineCache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + + err = vkCreatePipelineCache(demo->device, &pipelineCache, NULL, &demo->pipelineCache); + assert(!err); + + pipeline.pVertexInputState = &vi; + pipeline.pInputAssemblyState = &ia; + pipeline.pRasterizationState = &rs; + pipeline.pColorBlendState = &cb; + pipeline.pMultisampleState = &ms; + pipeline.pViewportState = &vp; + pipeline.pDepthStencilState = &ds; + pipeline.stageCount = ARRAY_SIZE(shaderStages); + pipeline.pStages = shaderStages; + pipeline.renderPass = demo->render_pass; + pipeline.pDynamicState = &dynamicState; + + err = vkCreateGraphicsPipelines(demo->device, demo->pipelineCache, 1, &pipeline, NULL, &demo->pipeline); + assert(!err); + + vkDestroyShaderModule(demo->device, demo->frag_shader_module, NULL); + vkDestroyShaderModule(demo->device, demo->vert_shader_module, NULL); +} + +static void demo_prepare_descriptor_pool(struct demo *demo) { + const VkDescriptorPoolSize type_counts[2] = { + [0] = + { + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = demo->swapchainImageCount, + }, + [1] = + { + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, + }, + }; + const VkDescriptorPoolCreateInfo descriptor_pool = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = NULL, + .maxSets = demo->swapchainImageCount, + .poolSizeCount = 2, + .pPoolSizes = type_counts, + }; + VkResult U_ASSERT_ONLY err; + + err = vkCreateDescriptorPool(demo->device, &descriptor_pool, NULL, &demo->desc_pool); + assert(!err); +} + +static void demo_prepare_descriptor_set(struct demo *demo) { + VkDescriptorImageInfo tex_descs[DEMO_TEXTURE_COUNT]; + VkWriteDescriptorSet writes[2]; + VkResult U_ASSERT_ONLY err; + + VkDescriptorSetAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = NULL, + .descriptorPool = demo->desc_pool, + .descriptorSetCount = 1, + .pSetLayouts = &demo->desc_layout}; + + VkDescriptorBufferInfo buffer_info; + buffer_info.offset = 0; + buffer_info.range = sizeof(struct vktexcube_vs_uniform); + + memset(&tex_descs, 0, sizeof(tex_descs)); + for (unsigned int i = 0; i < DEMO_TEXTURE_COUNT; i++) { + tex_descs[i].sampler = demo->textures[i].sampler; + tex_descs[i].imageView = demo->textures[i].view; + tex_descs[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + + memset(&writes, 0, sizeof(writes)); + + writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[0].dstBinding = 1; + writes[0].descriptorCount = DEMO_TEXTURE_COUNT; + writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writes[0].pImageInfo = tex_descs; + + writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[1].descriptorCount = 1; + writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + writes[1].pBufferInfo = &buffer_info; + + for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->swapchain_image_resources[i].descriptor_set); + assert(!err); + buffer_info.buffer = demo->swapchain_image_resources[i].uniform_buffer; + writes[0].dstSet = demo->swapchain_image_resources[i].descriptor_set; + writes[1].dstSet = demo->swapchain_image_resources[i].descriptor_set; + vkUpdateDescriptorSets(demo->device, 2, writes, 0, NULL); + } +} + +static void demo_prepare_framebuffers(struct demo *demo) { + VkImageView attachments[2]; + attachments[1] = demo->depth.view; + + const VkFramebufferCreateInfo fb_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = NULL, + .renderPass = demo->render_pass, + .attachmentCount = 2, + .pAttachments = attachments, + .width = demo->width, + .height = demo->height, + .layers = 1, + }; + VkResult U_ASSERT_ONLY err; + uint32_t i; + + for (i = 0; i < demo->swapchainImageCount; i++) { + attachments[0] = demo->swapchain_image_resources[i].view; + err = vkCreateFramebuffer(demo->device, &fb_info, NULL, &demo->swapchain_image_resources[i].framebuffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_FRAMEBUFFER, (uint64_t)demo->swapchain_image_resources[i].framebuffer, + "Framebuffer(%u)", i); + } +} + +static void demo_prepare(struct demo *demo) { + demo_prepare_buffers(demo); + + if (demo->is_minimized) { + demo->prepared = false; + return; + } + + VkResult U_ASSERT_ONLY err; + if (demo->cmd_pool == VK_NULL_HANDLE) { + const VkCommandPoolCreateInfo cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .queueFamilyIndex = demo->graphics_queue_family_index, + .flags = 0, + }; + err = vkCreateCommandPool(demo->device, &cmd_pool_info, NULL, &demo->cmd_pool); + assert(!err); + } + + const VkCommandBufferAllocateInfo cmd = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = demo->cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->cmd); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)demo->cmd, "PrepareCB"); + VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = 0, + .pInheritanceInfo = NULL, + }; + err = vkBeginCommandBuffer(demo->cmd, &cmd_buf_info); + demo_push_cb_label(demo, demo->cmd, NULL, "Prepare"); + assert(!err); + + demo_prepare_depth(demo); + demo_prepare_textures(demo); + demo_prepare_cube_data_buffers(demo); + + demo_prepare_descriptor_layout(demo); + demo_prepare_render_pass(demo); + demo_prepare_pipeline(demo); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->swapchain_image_resources[i].cmd); + assert(!err); + } + + if (demo->separate_present_queue) { + const VkCommandPoolCreateInfo present_cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .queueFamilyIndex = demo->present_queue_family_index, + .flags = 0, + }; + err = vkCreateCommandPool(demo->device, &present_cmd_pool_info, NULL, &demo->present_cmd_pool); + assert(!err); + const VkCommandBufferAllocateInfo present_cmd_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = demo->present_cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateCommandBuffers(demo->device, &present_cmd_info, + &demo->swapchain_image_resources[i].graphics_to_present_cmd); + assert(!err); + demo_build_image_ownership_cmd(demo, i); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, + (uint64_t)demo->swapchain_image_resources[i].graphics_to_present_cmd, "GfxToPresent(%u)", i); + } + } + + demo_prepare_descriptor_pool(demo); + demo_prepare_descriptor_set(demo); + + demo_prepare_framebuffers(demo); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + demo->current_buffer = i; + demo_draw_build_cmd(demo, demo->swapchain_image_resources[i].cmd); + } + + /* + * Prepare functions above may generate pipeline commands + * that need to be flushed before beginning the render loop. + */ + demo_pop_cb_label(demo, demo->cmd); // "Prepare" + demo_flush_init_cmd(demo); + if (demo->staging_texture.buffer) { + demo_destroy_texture(demo, &demo->staging_texture); + } + + demo->current_buffer = 0; + demo->prepared = true; + demo->first_swapchain_frame = true; +} + +static void demo_cleanup(struct demo *demo) { + uint32_t i; + + demo->prepared = false; + vkDeviceWaitIdle(demo->device); + + // Wait for fences from present operations + for (i = 0; i < FRAME_LAG; i++) { + vkWaitForFences(demo->device, 1, &demo->fences[i], VK_TRUE, UINT64_MAX); + vkDestroyFence(demo->device, demo->fences[i], NULL); + vkDestroySemaphore(demo->device, demo->image_acquired_semaphores[i], NULL); + vkDestroySemaphore(demo->device, demo->draw_complete_semaphores[i], NULL); + if (demo->separate_present_queue) { + vkDestroySemaphore(demo->device, demo->image_ownership_semaphores[i], NULL); + } + } + + // If the window is currently minimized, demo_resize has already done some cleanup for us. + if (!demo->is_minimized) { + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); + } + vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); + + vkDestroyPipeline(demo->device, demo->pipeline, NULL); + vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL); + vkDestroyRenderPass(demo->device, demo->render_pass, NULL); + vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL); + vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + vkDestroyImageView(demo->device, demo->textures[i].view, NULL); + vkDestroyImage(demo->device, demo->textures[i].image, NULL); + vkFreeMemory(demo->device, demo->textures[i].mem, NULL); + vkDestroySampler(demo->device, demo->textures[i].sampler, NULL); + } + vkDestroySwapchainKHR(demo->device, demo->swapchain, NULL); + + vkDestroyImageView(demo->device, demo->depth.view, NULL); + vkDestroyImage(demo->device, demo->depth.image, NULL); + vkFreeMemory(demo->device, demo->depth.mem, NULL); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); + vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); + vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); + vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); + } + free(demo->swapchain_image_resources); + free(demo->queue_props); + vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); + + if (demo->separate_present_queue) { + vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL); + } + } + vkDeviceWaitIdle(demo->device); + vkDestroyDevice(demo->device, NULL); + if (demo->validate) { + vkDestroyDebugUtilsMessengerEXT(demo->inst, demo->dbg_messenger, NULL); + } + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + +#if defined(VK_USE_PLATFORM_XLIB_KHR) + XDestroyWindow(demo->display, demo->xlib_window); + XCloseDisplay(demo->display); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + xcb_destroy_window(demo->connection, demo->xcb_window); + xcb_disconnect(demo->connection); + free(demo->atom_wm_delete_window); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (demo->keyboard) wl_keyboard_destroy(demo->keyboard); + if (demo->pointer) wl_pointer_destroy(demo->pointer); + if (demo->seat) wl_seat_destroy(demo->seat); + xdg_toplevel_destroy(demo->xdg_toplevel); + xdg_surface_destroy(demo->xdg_surface); + wl_surface_destroy(demo->window); + xdg_wm_base_destroy(demo->xdg_wm_base); + if (demo->xdg_decoration_mgr) { + zxdg_toplevel_decoration_v1_destroy(demo->toplevel_decoration); + zxdg_decoration_manager_v1_destroy(demo->xdg_decoration_mgr); + } + wl_compositor_destroy(demo->compositor); + wl_registry_destroy(demo->registry); + wl_display_disconnect(demo->display); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo->event_buffer->Release(demo->event_buffer); + demo->window->Release(demo->window); + demo->dfb->Release(demo->dfb); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + screen_destroy_event(demo->screen_event); + screen_destroy_window(demo->screen_window); + screen_destroy_context(demo->screen_context); +#endif + + vkDestroyInstance(demo->inst, NULL); +} + +static void demo_resize(struct demo *demo) { + uint32_t i; + + // Don't react to resize until after first initialization. + if (!demo->prepared) { + if (demo->is_minimized) { + demo_prepare(demo); + } + return; + } + // In order to properly resize the window, we must re-create the swapchain + // AND redo the command buffers, etc. + // + // First, perform part of the demo_cleanup() function: + demo->prepared = false; + vkDeviceWaitIdle(demo->device); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); + } + vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); + + vkDestroyPipeline(demo->device, demo->pipeline, NULL); + vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL); + vkDestroyRenderPass(demo->device, demo->render_pass, NULL); + vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL); + vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + vkDestroyImageView(demo->device, demo->textures[i].view, NULL); + vkDestroyImage(demo->device, demo->textures[i].image, NULL); + vkFreeMemory(demo->device, demo->textures[i].mem, NULL); + vkDestroySampler(demo->device, demo->textures[i].sampler, NULL); + } + + vkDestroyImageView(demo->device, demo->depth.view, NULL); + vkDestroyImage(demo->device, demo->depth.image, NULL); + vkFreeMemory(demo->device, demo->depth.mem, NULL); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); + vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); + vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); + vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); + } + vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); + demo->cmd_pool = VK_NULL_HANDLE; + if (demo->separate_present_queue) { + vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL); + } + free(demo->swapchain_image_resources); + + // Second, re-perform the demo_prepare() function, which will re-create the + // swapchain: + demo_prepare(demo); +} + +// On MS-Windows, make this a global, so it's available to WndProc() +struct demo demo; + +#if defined(VK_USE_PLATFORM_WIN32_KHR) +static void demo_run(struct demo *demo) { + if (!demo->prepared) return; + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + PostQuitMessage(validation_error); + } +} + +// MS-Windows event handling function: +LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CLOSE: + PostQuitMessage(validation_error); + break; + case WM_PAINT: + // The validation callback calls MessageBox which can generate paint + // events - don't make more Vulkan calls if we got here from the + // callback + if (!in_callback) { + demo_run(&demo); + } + break; + case WM_GETMINMAXINFO: // set window's minimum size + ((MINMAXINFO *)lParam)->ptMinTrackSize = demo.minsize; + return 0; + case WM_ERASEBKGND: + return 1; + case WM_SIZE: + // Resize the application to the new window size, except when + // it was minimized. Vulkan doesn't support images or swapchains + // with width=0 and height=0. + if (wParam != SIZE_MINIMIZED) { + demo.width = lParam & 0xffff; + demo.height = (lParam & 0xffff0000) >> 16; + demo_resize(&demo); + } + break; + case WM_KEYDOWN: + switch (wParam) { + case VK_ESCAPE: + PostQuitMessage(validation_error); + break; + case VK_LEFT: + demo.spin_angle -= demo.spin_increment; + break; + case VK_RIGHT: + demo.spin_angle += demo.spin_increment; + break; + case VK_SPACE: + demo.pause = !demo.pause; + break; + } + return 0; + default: + break; + } + return (DefWindowProc(hWnd, uMsg, wParam, lParam)); +} + +static void demo_create_window(struct demo *demo) { + WNDCLASSEX win_class; + + // Initialize the window class structure: + win_class.cbSize = sizeof(WNDCLASSEX); + win_class.style = CS_HREDRAW | CS_VREDRAW; + win_class.lpfnWndProc = WndProc; + win_class.cbClsExtra = 0; + win_class.cbWndExtra = 0; + win_class.hInstance = demo->connection; // hInstance + win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + win_class.hCursor = LoadCursor(NULL, IDC_ARROW); + win_class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + win_class.lpszMenuName = NULL; + win_class.lpszClassName = demo->name; + win_class.hIconSm = LoadIcon(NULL, IDI_WINLOGO); + // Register window class: + if (!RegisterClassEx(&win_class)) { + // It didn't work, so try to give a useful error: + printf("Unexpected error trying to start the application!\n"); + fflush(stdout); + exit(1); + } + // Create window with the registered class: + RECT wr = {0, 0, demo->width, demo->height}; + AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); + demo->window = CreateWindowEx(0, + demo->name, // class name + demo->name, // app name + WS_OVERLAPPEDWINDOW | // window style + WS_VISIBLE | WS_SYSMENU, + 100, 100, // x/y coords + wr.right - wr.left, // width + wr.bottom - wr.top, // height + NULL, // handle to parent + NULL, // handle to menu + demo->connection, // hInstance + NULL); // no extra parameters + if (!demo->window) { + // It didn't work, so try to give a useful error: + printf("Cannot create a window in which to draw!\n"); + fflush(stdout); + exit(1); + } + // Window client area size must be at least 1 pixel high, to prevent crash. + demo->minsize.x = GetSystemMetrics(SM_CXMINTRACK); + demo->minsize.y = GetSystemMetrics(SM_CYMINTRACK) + 1; +} +#elif defined(VK_USE_PLATFORM_XLIB_KHR) +static void demo_create_xlib_window(struct demo *demo) { + const char *display_envar = getenv("DISPLAY"); + if (display_envar == NULL || display_envar[0] == '\0') { + printf("Environment variable DISPLAY requires a valid value.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + XInitThreads(); + demo->display = XOpenDisplay(NULL); + long visualMask = VisualScreenMask; + int numberOfVisuals; + XVisualInfo vInfoTemplate = {}; + vInfoTemplate.screen = DefaultScreen(demo->display); + XVisualInfo *visualInfo = XGetVisualInfo(demo->display, visualMask, &vInfoTemplate, &numberOfVisuals); + + Colormap colormap = + XCreateColormap(demo->display, RootWindow(demo->display, vInfoTemplate.screen), visualInfo->visual, AllocNone); + + XSetWindowAttributes windowAttributes = {}; + windowAttributes.colormap = colormap; + windowAttributes.background_pixel = 0xFFFFFFFF; + windowAttributes.border_pixel = 0; + windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; + + demo->xlib_window = XCreateWindow(demo->display, RootWindow(demo->display, vInfoTemplate.screen), 0, 0, demo->width, + demo->height, 0, visualInfo->depth, InputOutput, visualInfo->visual, + CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes); + + XSelectInput(demo->display, demo->xlib_window, ExposureMask | KeyPressMask); + XMapWindow(demo->display, demo->xlib_window); + XFlush(demo->display); + demo->xlib_wm_delete_window = XInternAtom(demo->display, "WM_DELETE_WINDOW", False); +} +static void demo_handle_xlib_event(struct demo *demo, const XEvent *event) { + switch (event->type) { + case ClientMessage: + if ((Atom)event->xclient.data.l[0] == demo->xlib_wm_delete_window) demo->quit = true; + break; + case KeyPress: + switch (event->xkey.keycode) { + case 0x9: // Escape + demo->quit = true; + break; + case 0x71: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case 0x72: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case 0x41: // space bar + demo->pause = !demo->pause; + break; + } + break; + case ConfigureNotify: + if ((demo->width != event->xconfigure.width) || (demo->height != event->xconfigure.height)) { + demo->width = event->xconfigure.width; + demo->height = event->xconfigure.height; + demo_resize(demo); + } + break; + default: + break; + } +} + +static void demo_run_xlib(struct demo *demo) { + while (!demo->quit) { + XEvent event; + + if (demo->pause) { + XNextEvent(demo->display, &event); + demo_handle_xlib_event(demo, &event); + } + while (XPending(demo->display) > 0) { + XNextEvent(demo->display, &event); + demo_handle_xlib_event(demo, &event); + } + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } +} +#elif defined(VK_USE_PLATFORM_XCB_KHR) +static void demo_handle_xcb_event(struct demo *demo, const xcb_generic_event_t *event) { + uint8_t event_code = event->response_type & 0x7f; + switch (event_code) { + case XCB_EXPOSE: + // TODO: Resize window + break; + case XCB_CLIENT_MESSAGE: + if ((*(xcb_client_message_event_t *)event).data.data32[0] == (*demo->atom_wm_delete_window).atom) { + demo->quit = true; + } + break; + case XCB_KEY_RELEASE: { + const xcb_key_release_event_t *key = (const xcb_key_release_event_t *)event; + + switch (key->detail) { + case 0x9: // Escape + demo->quit = true; + break; + case 0x71: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case 0x72: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case 0x41: // space bar + demo->pause = !demo->pause; + break; + } + } break; + case XCB_CONFIGURE_NOTIFY: { + const xcb_configure_notify_event_t *cfg = (const xcb_configure_notify_event_t *)event; + if ((demo->width != cfg->width) || (demo->height != cfg->height)) { + demo->width = cfg->width; + demo->height = cfg->height; + demo_resize(demo); + } + } break; + default: + break; + } +} + +static void demo_run_xcb(struct demo *demo) { + xcb_flush(demo->connection); + + while (!demo->quit) { + xcb_generic_event_t *event; + + if (demo->pause) { + event = xcb_wait_for_event(demo->connection); + } else { + event = xcb_poll_for_event(demo->connection); + } + while (event) { + demo_handle_xcb_event(demo, event); + free(event); + event = xcb_poll_for_event(demo->connection); + } + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } +} + +static void demo_create_xcb_window(struct demo *demo) { + uint32_t value_mask, value_list[32]; + + demo->xcb_window = xcb_generate_id(demo->connection); + + value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + value_list[0] = demo->screen->black_pixel; + value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + xcb_create_window(demo->connection, XCB_COPY_FROM_PARENT, demo->xcb_window, demo->screen->root, 0, 0, demo->width, demo->height, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, demo->screen->root_visual, value_mask, value_list); + + /* Magic code that will send notification when window is destroyed */ + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(demo->connection, 1, 12, "WM_PROTOCOLS"); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(demo->connection, cookie, 0); + + xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(demo->connection, 0, 16, "WM_DELETE_WINDOW"); + demo->atom_wm_delete_window = xcb_intern_atom_reply(demo->connection, cookie2, 0); + + xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, demo->xcb_window, (*reply).atom, 4, 32, 1, + &(*demo->atom_wm_delete_window).atom); + free(reply); + + xcb_map_window(demo->connection, demo->xcb_window); + + // Force the x/y coordinates to 100,100 results are identical in consecutive + // runs + const uint32_t coords[] = {100, 100}; + xcb_configure_window(demo->connection, demo->xcb_window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords); +} +// VK_USE_PLATFORM_XCB_KHR +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) +static void demo_run(struct demo *demo) { + while (!demo->quit) { + // Flush any commands to the server + wl_display_flush(demo->display); + + if (demo->pause) { + // block and wait for input + wl_display_dispatch(demo->display); + } else { + // Lock the display event queue in case the driver is doing something on another thread + // while we wait, keep pumping events + while (wl_display_prepare_read(demo->display) != 0) { + wl_display_dispatch_pending(demo->display); + } + // Actually do the read from the socket + wl_display_read_events(demo->display); + + // Pump events + wl_display_dispatch_pending(demo->display); + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } + } +} + +static void handle_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { + struct demo *demo = (struct demo *)data; + xdg_surface_ack_configure(xdg_surface, serial); + if (demo->xdg_surface_has_been_configured) { + demo_resize(demo); + } + demo->xdg_surface_has_been_configured = 1; +} + +static const struct xdg_surface_listener xdg_surface_listener = {handle_surface_configure}; + +static void handle_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel UNUSED, int32_t width, int32_t height, + struct wl_array *states UNUSED) { + struct demo *demo = (struct demo *)data; + /* zero values imply the program may choose its own size, so in that case + * stay with the existing value (which on startup is the default) */ + if (width > 0) { + demo->width = width; + } + if (height > 0) { + demo->height = height; + } + /* This should be followed by a surface configure */ +} + +static void handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel UNUSED) { + struct demo *demo = (struct demo *)data; + demo->quit = true; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = {handle_toplevel_configure, handle_toplevel_close}; + +static void demo_create_window(struct demo *demo) { + if (!demo->xdg_wm_base) { + printf("Compositor did not provide the standard protocol xdg-wm-base\n"); + fflush(stdout); + exit(1); + } + + demo->window = wl_compositor_create_surface(demo->compositor); + if (!demo->window) { + printf("Can not create wayland_surface from compositor!\n"); + fflush(stdout); + exit(1); + } + + demo->xdg_surface = xdg_wm_base_get_xdg_surface(demo->xdg_wm_base, demo->window); + if (!demo->xdg_surface) { + printf("Can not get xdg_surface from wayland_surface!\n"); + fflush(stdout); + exit(1); + } + demo->xdg_toplevel = xdg_surface_get_toplevel(demo->xdg_surface); + if (!demo->xdg_toplevel) { + printf("Can not allocate xdg_toplevel for xdg_surface!\n"); + fflush(stdout); + exit(1); + } + xdg_surface_add_listener(demo->xdg_surface, &xdg_surface_listener, demo); + xdg_toplevel_add_listener(demo->xdg_toplevel, &xdg_toplevel_listener, demo); + xdg_toplevel_set_title(demo->xdg_toplevel, APP_SHORT_NAME); + if (demo->xdg_decoration_mgr) { + // if supported, let the compositor render titlebars for us + demo->toplevel_decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration(demo->xdg_decoration_mgr, demo->xdg_toplevel); + zxdg_toplevel_decoration_v1_set_mode(demo->toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } + + wl_surface_commit(demo->window); +} +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) +static void demo_create_directfb_window(struct demo *demo) { + DFBResult ret; + + ret = DirectFBInit(NULL, NULL); + if (ret) { + printf("DirectFBInit failed to initialize DirectFB!\n"); + fflush(stdout); + exit(1); + } + + ret = DirectFBCreate(&demo->dfb); + if (ret) { + printf("DirectFBCreate failed to create main interface of DirectFB!\n"); + fflush(stdout); + exit(1); + } + + DFBSurfaceDescription desc; + desc.flags = DSDESC_CAPS | DSDESC_WIDTH | DSDESC_HEIGHT; + desc.caps = DSCAPS_PRIMARY; + desc.width = demo->width; + desc.height = demo->height; + ret = demo->dfb->CreateSurface(demo->dfb, &desc, &demo->window); + if (ret) { + printf("CreateSurface failed to create DirectFB surface interface!\n"); + fflush(stdout); + exit(1); + } + + ret = demo->dfb->CreateInputEventBuffer(demo->dfb, DICAPS_KEYS, DFB_FALSE, &demo->event_buffer); + if (ret) { + printf("CreateInputEventBuffer failed to create DirectFB event buffer interface!\n"); + fflush(stdout); + exit(1); + } +} + +static void demo_handle_directfb_event(struct demo *demo, const DFBInputEvent *event) { + if (event->type != DIET_KEYPRESS) return; + switch (event->key_symbol) { + case DIKS_ESCAPE: // Escape + demo->quit = true; + break; + case DIKS_CURSOR_LEFT: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case DIKS_CURSOR_RIGHT: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case DIKS_SPACE: // space bar + demo->pause = !demo->pause; + break; + default: + break; + } +} + +static void demo_run_directfb(struct demo *demo) { + while (!demo->quit) { + DFBInputEvent event; + + if (demo->pause) { + demo->event_buffer->WaitForEvent(demo->event_buffer); + if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); + } else { + if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } + } +} +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) +static void demo_run(struct demo *demo) { + if (!demo->prepared) return; + + demo_draw(demo); + demo->curFrame++; +} +#elif defined(VK_USE_PLATFORM_METAL_EXT) +static void demo_run(struct demo *demo) { + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = TRUE; + } +} +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) +static VkResult demo_create_display_surface(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + uint32_t display_count; + uint32_t mode_count; + uint32_t plane_count; + VkDisplayPropertiesKHR display_props; + VkDisplayKHR display; + VkDisplayModePropertiesKHR mode_props; + VkDisplayPlanePropertiesKHR *plane_props; + VkBool32 found_plane = VK_FALSE; + uint32_t plane_index; + VkExtent2D image_extent; + VkDisplaySurfaceCreateInfoKHR create_info; + + // Get the first display + display_count = 1; + err = vkGetPhysicalDeviceDisplayPropertiesKHR(demo->gpu, &display_count, &display_props); + assert(!err || (err == VK_INCOMPLETE)); + + display = display_props.display; + + // Get the first mode of the display + err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, NULL); + assert(!err); + + if (mode_count == 0) { + printf("Cannot find any mode for the display!\n"); + fflush(stdout); + exit(1); + } + + mode_count = 1; + err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, &mode_props); + assert(!err || (err == VK_INCOMPLETE)); + + // Get the list of planes + err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, NULL); + assert(!err); + + if (plane_count == 0) { + printf("Cannot find any plane!\n"); + fflush(stdout); + exit(1); + } + + plane_props = malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count); + assert(plane_props); + + err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, plane_props); + assert(!err); + + // Find a plane compatible with the display + for (plane_index = 0; plane_index < plane_count; plane_index++) { + uint32_t supported_count; + VkDisplayKHR *supported_displays; + + // Disqualify planes that are bound to a different display + if ((plane_props[plane_index].currentDisplay != VK_NULL_HANDLE) && (plane_props[plane_index].currentDisplay != display)) { + continue; + } + + err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, NULL); + assert(!err); + + if (supported_count == 0) { + continue; + } + + supported_displays = malloc(sizeof(VkDisplayKHR) * supported_count); + assert(supported_displays); + + err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, supported_displays); + assert(!err); + + for (uint32_t i = 0; i < supported_count; i++) { + if (supported_displays[i] == display) { + found_plane = VK_TRUE; + break; + } + } + + free(supported_displays); + + if (found_plane) { + break; + } + } + + if (!found_plane) { + printf("Cannot find a plane compatible with the display!\n"); + fflush(stdout); + exit(1); + } + + free(plane_props); + + VkDisplayPlaneCapabilitiesKHR planeCaps; + vkGetDisplayPlaneCapabilitiesKHR(demo->gpu, mode_props.displayMode, plane_index, &planeCaps); + // Find a supported alpha mode + VkDisplayPlaneAlphaFlagBitsKHR alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; + VkDisplayPlaneAlphaFlagBitsKHR alphaModes[4] = { + VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR, + }; + for (uint32_t i = 0; i < sizeof(alphaModes); i++) { + if (planeCaps.supportedAlpha & alphaModes[i]) { + alphaMode = alphaModes[i]; + break; + } + } + image_extent.width = mode_props.parameters.visibleRegion.width; + image_extent.height = mode_props.parameters.visibleRegion.height; + + create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR; + create_info.pNext = NULL; + create_info.flags = 0; + create_info.displayMode = mode_props.displayMode; + create_info.planeIndex = plane_index; + create_info.planeStackIndex = plane_props[plane_index].currentStackIndex; + create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + create_info.alphaMode = alphaMode; + create_info.globalAlpha = 1.0f; + create_info.imageExtent = image_extent; + + return vkCreateDisplayPlaneSurfaceKHR(demo->inst, &create_info, NULL, &demo->surface); +} + +static void demo_run_display(struct demo *demo) { + while (!demo->quit) { + demo_draw(demo); + demo->curFrame++; + + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = true; + } + } +} +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + +#include + +static void demo_run(struct demo *demo) { + int size[2] = {0, 0}; + screen_window_t win; + int val; + int rc; + + while (!demo->quit) { + while (!screen_get_event(demo->screen_context, demo->screen_event, demo->pause ? ~0 : 0)) { + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_TYPE, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_TYPE of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (val == SCREEN_EVENT_NONE) { + break; + } + switch (val) { + case SCREEN_EVENT_KEYBOARD: + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_FLAGS, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_FLAGS of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (val & KEY_DOWN) { + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_SYM, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_SYM of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + switch (val) { + case KEYCODE_ESCAPE: + demo->quit = true; + break; + case KEYCODE_SPACE: + demo->pause = !demo->pause; + break; + case KEYCODE_LEFT: + demo->spin_angle -= demo->spin_increment; + break; + case KEYCODE_RIGHT: + demo->spin_angle += demo->spin_increment; + break; + default: + break; + } + } + break; + case SCREEN_EVENT_PROPERTY: + rc = screen_get_event_property_pv(demo->screen_event, SCREEN_PROPERTY_WINDOW, (void **)&win); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_WINDOW of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_NAME, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_NAME of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (win == demo->screen_window) { + switch (val) { + case SCREEN_PROPERTY_SIZE: + rc = screen_get_window_property_iv(win, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_SIZE of the window in the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + demo->width = size[0]; + demo->height = size[1]; + demo_resize(demo); + break; + default: + /* We are not interested in any other events for now */ + break; + } + } + break; + } + } + + if (demo->pause) { + } else { + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = true; + } + } + } +} + +static void demo_create_window(struct demo *demo) { + const char *idstr = APP_SHORT_NAME; + int size[2]; + int usage = SCREEN_USAGE_VULKAN; + int rc; + + rc = screen_create_context(&demo->screen_context, 0); + if (rc) { + printf("Cannot create QNX Screen context!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + rc = screen_create_window(&demo->screen_window, demo->screen_context); + if (rc) { + printf("Cannot create QNX Screen window!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + rc = screen_create_event(&demo->screen_event); + if (rc) { + printf("Cannot create QNX Screen event!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + + /* Set window caption */ + screen_set_window_property_cv(demo->screen_window, SCREEN_PROPERTY_ID_STRING, strlen(idstr), idstr); + + /* Setup VULKAN usage flags */ + rc = screen_set_window_property_iv(demo->screen_window, SCREEN_PROPERTY_USAGE, &usage); + if (rc) { + printf("Cannot set SCREEN_USAGE_VULKAN flag!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + + /* Setup window size */ + if ((demo->width == 0) || (demo->height == 0)) { + /* Obtain automatically set window size provided by WM */ + rc = screen_get_window_property_iv(demo->screen_window, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot obtain current window size!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + demo->width = size[0]; + demo->height = size[1]; + } else { + size[0] = demo->width; + size[1] = demo->height; + rc = screen_set_window_property_iv(demo->screen_window, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot set window size!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + } +} +#endif + +/* + * Return 1 (true) if all layer names specified in check_names + * can be found in given layer properties. + */ +static VkBool32 demo_check_layers(uint32_t check_count, char **check_names, uint32_t layer_count, VkLayerProperties *layers) { + for (uint32_t i = 0; i < check_count; i++) { + VkBool32 found = 0; + for (uint32_t j = 0; j < layer_count; j++) { + if (!strcmp(check_names[i], layers[j].layerName)) { + found = 1; + break; + } + } + if (!found) { + fprintf(stderr, "Cannot find layer: %s\n", check_names[i]); + return 0; + } + } + return 1; +} +#if defined(VK_USE_PLATFORM_DISPLAY_KHR) +int find_display_gpu(int gpu_number, uint32_t gpu_count, VkPhysicalDevice *physical_devices) { + uint32_t display_count = 0; + VkResult U_ASSERT_ONLY result; + int gpu_return = gpu_number; + if (gpu_number >= 0) { + result = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_devices[gpu_number], &display_count, NULL); + assert(!result); + } else { + for (uint32_t i = 0; i < gpu_count; i++) { + result = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_devices[i], &display_count, NULL); + assert(!result); + if (display_count) { + gpu_return = i; + break; + } + } + } + if (display_count > 0) + return gpu_return; + else + return -1; +} +#endif +static void demo_init_vk(struct demo *demo) { + VkResult err; + uint32_t instance_extension_count = 0; + uint32_t instance_layer_count = 0; + char *instance_validation_layers[] = {"VK_LAYER_KHRONOS_validation"}; + demo->enabled_extension_count = 0; + demo->enabled_layer_count = 0; + demo->is_minimized = false; + demo->cmd_pool = VK_NULL_HANDLE; + + err = volkInitialize(); + if (err != VK_SUCCESS) { + ERR_EXIT( + "Unable to find the Vulkan runtime on the system.\n\n" + "This likely indicates that no Vulkan capable drivers are installed.", + "Installation Failure"); + } + // Look for validation layers + VkBool32 validation_found = 0; + if (demo->validate) { + err = vkEnumerateInstanceLayerProperties(&instance_layer_count, NULL); + assert(!err); + + if (instance_layer_count > 0) { + VkLayerProperties *instance_layers = malloc(sizeof(VkLayerProperties) * instance_layer_count); + err = vkEnumerateInstanceLayerProperties(&instance_layer_count, instance_layers); + assert(!err); + + validation_found = demo_check_layers(ARRAY_SIZE(instance_validation_layers), instance_validation_layers, + instance_layer_count, instance_layers); + if (validation_found) { + demo->enabled_layer_count = ARRAY_SIZE(instance_validation_layers); + demo->enabled_layers[0] = "VK_LAYER_KHRONOS_validation"; + } + free(instance_layers); + } + + if (!validation_found) { + ERR_EXIT( + "vkEnumerateInstanceLayerProperties failed to find required validation layer.\n\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + } + + /* Look for instance extensions */ + VkBool32 surfaceExtFound = 0; + VkBool32 platformSurfaceExtFound = 0; + bool portabilityEnumerationActive = false; + memset(demo->extension_names, 0, sizeof(demo->extension_names)); + + err = vkEnumerateInstanceExtensionProperties(NULL, &instance_extension_count, NULL); + assert(!err); + + if (instance_extension_count > 0) { + VkExtensionProperties *instance_extensions = malloc(sizeof(VkExtensionProperties) * instance_extension_count); + err = vkEnumerateInstanceExtensionProperties(NULL, &instance_extension_count, instance_extensions); + assert(!err); + for (uint32_t i = 0; i < instance_extension_count; i++) { + if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + surfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_SURFACE_EXTENSION_NAME; + } +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (!strcmp(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_WIN32_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + if (!strcmp(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_XLIB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_XCB_KHR) + if (!strcmp(VK_KHR_XCB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_XCB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (!strcmp(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + if (!strcmp(VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + if (!strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_DISPLAY_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + if (!strcmp(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_ANDROID_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_METAL_EXT) + if (!strcmp(VK_EXT_METAL_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_METAL_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + if (!strcmp(VK_QNX_SCREEN_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_QNX_SCREEN_SURFACE_EXTENSION_NAME; + } +#endif + if (!strcmp(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, instance_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + } + if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, instance_extensions[i].extensionName)) { + if (demo->validate) { + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + } + } + // We want cube to be able to enumerate drivers that support the portability_subset extension, so we have to enable the + // portability enumeration extension. + if (!strcmp(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, instance_extensions[i].extensionName)) { + portabilityEnumerationActive = true; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + } + assert(demo->enabled_extension_count < 64); + } + + free(instance_extensions); + } + + if (!surfaceExtFound) { + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + if (!platformSurfaceExtFound) { +#if defined(VK_USE_PLATFORM_WIN32_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_WIN32_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_EXT_METAL_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_XCB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_DISPLAY_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_ANDROID_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_XLIB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_QNX_SCREEN_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#endif + } + const VkApplicationInfo app = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = NULL, + .pApplicationName = APP_SHORT_NAME, + .applicationVersion = 0, + .pEngineName = APP_SHORT_NAME, + .engineVersion = 0, + .apiVersion = VK_API_VERSION_1_0, + }; + VkInstanceCreateInfo inst_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = NULL, + .flags = (portabilityEnumerationActive ? VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR : 0), + .pApplicationInfo = &app, + .enabledLayerCount = demo->enabled_layer_count, + .ppEnabledLayerNames = (const char *const *)instance_validation_layers, + .enabledExtensionCount = demo->enabled_extension_count, + .ppEnabledExtensionNames = (const char *const *)demo->extension_names, + }; + + /* + * This is info for a temp callback to use during CreateInstance. + * After the instance is created, we use the instance-based + * function to register the final callback. + */ + VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info; + if (demo->validate) { + // VK_EXT_debug_utils style + dbg_messenger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + dbg_messenger_create_info.pNext = NULL; + dbg_messenger_create_info.flags = 0; + dbg_messenger_create_info.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + dbg_messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + dbg_messenger_create_info.pfnUserCallback = debug_messenger_callback; + dbg_messenger_create_info.pUserData = demo; + inst_info.pNext = &dbg_messenger_create_info; + } + + err = vkCreateInstance(&inst_info, NULL, &demo->inst); + if (err == VK_ERROR_INCOMPATIBLE_DRIVER) { + ERR_EXIT( + "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } else if (err == VK_ERROR_EXTENSION_NOT_PRESENT) { + ERR_EXIT( + "Cannot find a specified extension library.\n" + "Make sure your layers path is set appropriately.\n", + "vkCreateInstance Failure"); + } else if (err) { + ERR_EXIT( + "vkCreateInstance failed.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + + volkLoadInstance(demo->inst); + + /* Make initial call to query gpu_count, then second call for gpu info */ + uint32_t gpu_count = 0; + err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, NULL); + assert(!err); + + if (gpu_count <= 0) { + ERR_EXIT( + "vkEnumeratePhysicalDevices reported zero accessible devices.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkEnumeratePhysicalDevices Failure"); + } + + VkPhysicalDevice *physical_devices = malloc(sizeof(VkPhysicalDevice) * gpu_count); + err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, physical_devices); + assert(!err); + if (demo->invalid_gpu_selection || (demo->gpu_number >= 0 && !((uint32_t)demo->gpu_number < gpu_count))) { + fprintf(stderr, "GPU %d specified is not present, GPU count = %u\n", demo->gpu_number, gpu_count); + ERR_EXIT("Specified GPU number is not present", "User Error"); + } + +#if defined(VK_USE_PLATFORM_DISPLAY_KHR) + demo->gpu_number = find_display_gpu(demo->gpu_number, gpu_count, physical_devices); + if (demo->gpu_number < 0) { + printf("Cannot find any display!\n"); + fflush(stdout); + exit(1); + } +#else + /* Try to auto select most suitable device */ + if (demo->gpu_number == -1) { + uint32_t count_device_type[VK_PHYSICAL_DEVICE_TYPE_CPU + 1]; + memset(count_device_type, 0, sizeof(count_device_type)); + + VkPhysicalDeviceProperties physicalDeviceProperties; + for (uint32_t i = 0; i < gpu_count; i++) { + vkGetPhysicalDeviceProperties(physical_devices[i], &physicalDeviceProperties); + assert(physicalDeviceProperties.deviceType <= VK_PHYSICAL_DEVICE_TYPE_CPU); + count_device_type[physicalDeviceProperties.deviceType]++; + } + + VkPhysicalDeviceType search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_CPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_CPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_OTHER]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_OTHER; + } + + for (uint32_t i = 0; i < gpu_count; i++) { + vkGetPhysicalDeviceProperties(physical_devices[i], &physicalDeviceProperties); + if (physicalDeviceProperties.deviceType == search_for_device_type) { + demo->gpu_number = i; + break; + } + } + } +#endif + assert(demo->gpu_number >= 0); + demo->gpu = physical_devices[demo->gpu_number]; + { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(demo->gpu, &physicalDeviceProperties); + fprintf(stderr, "Selected GPU %d: %s, type: %s\n", demo->gpu_number, physicalDeviceProperties.deviceName, + to_string(physicalDeviceProperties.deviceType)); + } + free(physical_devices); + + /* Look for device extensions */ + uint32_t device_extension_count = 0; + VkBool32 swapchainExtFound = 0; + demo->enabled_extension_count = 0; + memset(demo->extension_names, 0, sizeof(demo->extension_names)); + + err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL, &device_extension_count, NULL); + assert(!err); + + if (device_extension_count > 0) { + VkExtensionProperties *device_extensions = malloc(sizeof(VkExtensionProperties) * device_extension_count); + err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL, &device_extension_count, device_extensions); + assert(!err); + + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, device_extensions[i].extensionName)) { + swapchainExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; + } + if (!strcmp("VK_KHR_portability_subset", device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = "VK_KHR_portability_subset"; + } +#if DESCRIPTOR_HEAP + // VK_KHR_DESCRIPTOR_HEAP + if (!strcmp(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NAME, device_extensions[i].extensionName)) + { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NAME; + } + // VK_KHR_buffer_device_address + if (!strcmp(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, device_extensions[i].extensionName)) + { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME; + } +#endif + assert(demo->enabled_extension_count < 64); + } + + if (demo->VK_KHR_incremental_present_enabled) { + // Even though the user "enabled" the extension via the command + // line, we must make sure that it's enumerated for use with the + // device. Therefore, disable it here, and re-enable it again if + // enumerated. + demo->VK_KHR_incremental_present_enabled = false; + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME; + demo->VK_KHR_incremental_present_enabled = true; + DbgMsg("VK_KHR_incremental_present extension enabled\n"); + } + assert(demo->enabled_extension_count < 64); + } + if (!demo->VK_KHR_incremental_present_enabled) { + DbgMsg("VK_KHR_incremental_present extension NOT AVAILABLE\n"); + } + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + // Even though the user "enabled" the extension via the command + // line, we must make sure that it's enumerated for use with the + // device. Therefore, disable it here, and re-enable it again if + // enumerated. + demo->VK_GOOGLE_display_timing_enabled = false; + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME; + demo->VK_GOOGLE_display_timing_enabled = true; + DbgMsg("VK_GOOGLE_display_timing extension enabled\n"); + } + assert(demo->enabled_extension_count < 64); + } + if (!demo->VK_GOOGLE_display_timing_enabled) { + DbgMsg("VK_GOOGLE_display_timing extension NOT AVAILABLE\n"); + } + } + + free(device_extensions); + } + + if (!swapchainExtFound) { + ERR_EXIT("vkEnumerateDeviceExtensionProperties failed to find the " VK_KHR_SWAPCHAIN_EXTENSION_NAME + " extension.\n\nDo you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + + if (demo->validate) { + err = vkCreateDebugUtilsMessengerEXT(demo->inst, &dbg_messenger_create_info, NULL, &demo->dbg_messenger); + switch (err) { + case VK_SUCCESS: + break; + case VK_ERROR_OUT_OF_HOST_MEMORY: + ERR_EXIT("CreateDebugUtilsMessengerEXT: out of host memory\n", "CreateDebugUtilsMessengerEXT Failure"); + break; + default: + ERR_EXIT("CreateDebugUtilsMessengerEXT: unknown failure\n", "CreateDebugUtilsMessengerEXT Failure"); + break; + } + } + vkGetPhysicalDeviceProperties(demo->gpu, &demo->gpu_props); + + /* Call with NULL data to get count */ + vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_family_count, NULL); + assert(demo->queue_family_count >= 1); + + demo->queue_props = (VkQueueFamilyProperties *)malloc(demo->queue_family_count * sizeof(VkQueueFamilyProperties)); + vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_family_count, demo->queue_props); + + // Query fine-grained feature support for this device. + // If app has specific feature requirements it should check supported + // features based on this query + VkPhysicalDeviceFeatures physDevFeatures; + vkGetPhysicalDeviceFeatures(demo->gpu, &physDevFeatures); + +#if DESCRIPTOR_HEAP + //===============leiter + VkPhysicalDeviceFeatures2 features2 = {}; + features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + + // VkPhysicalDeviceDescriptorHeapFeaturesKHR descriptorHeapFeature = {}; + demo->descriptorHeapFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_KHR; + demo->descriptorHeapFeature.pNext = NULL; + + features2.pNext = (void*)(&demo->descriptorHeapFeature); + vkGetPhysicalDeviceFeatures2(demo->gpu, &features2); + + VkPhysicalDeviceProperties2 properties2 = {}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + + // VkPhysicalDeviceDescriptorHeapPropertiesKHR descriptorHeapProperties = {}; + demo->descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_KHR; + demo->descriptorHeapProperties.pNext = NULL; + properties2.pNext = (void *)(&demo->descriptorHeapProperties); + vkGetPhysicalDeviceProperties2(demo->gpu, &properties2); +#endif +} + +static void demo_create_device(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + float queue_priorities[1] = {0.0}; + VkDeviceQueueCreateInfo queues[2]; + queues[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queues[0].pNext = NULL; + queues[0].queueFamilyIndex = demo->graphics_queue_family_index; + queues[0].queueCount = 1; + queues[0].pQueuePriorities = queue_priorities; + queues[0].flags = 0; + + VkDeviceCreateInfo device = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = NULL, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = queues, + .enabledLayerCount = 0, + .ppEnabledLayerNames = NULL, + .enabledExtensionCount = demo->enabled_extension_count, + .ppEnabledExtensionNames = (const char *const *)demo->extension_names, + .pEnabledFeatures = NULL, // If specific features are required, pass them in here + }; + if (demo->separate_present_queue) { + queues[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queues[1].pNext = NULL; + queues[1].queueFamilyIndex = demo->present_queue_family_index; + queues[1].queueCount = 1; + queues[1].pQueuePriorities = queue_priorities; + queues[1].flags = 0; + device.queueCreateInfoCount = 2; + } + err = vkCreateDevice(demo->gpu, &device, NULL, &demo->device); + assert(!err); + + volkLoadDevice(demo->device); +} + +static void demo_create_surface(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + +// Create a WSI surface for the window: +#if defined(VK_USE_PLATFORM_WIN32_KHR) + VkWin32SurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.hinstance = demo->connection; + createInfo.hwnd = demo->window; + + err = vkCreateWin32SurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + VkWaylandSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.display = demo->display; + createInfo.surface = demo->window; + + err = vkCreateWaylandSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + VkAndroidSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.window = (struct ANativeWindow *)(demo->window); + + err = vkCreateAndroidSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + VkXlibSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.dpy = demo->display; + createInfo.window = demo->xlib_window; + + err = vkCreateXlibSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + VkXcbSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.connection = demo->connection; + createInfo.window = demo->xcb_window; + + err = vkCreateXcbSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + VkDirectFBSurfaceCreateInfoEXT createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.dfb = demo->dfb; + createInfo.surface = demo->window; + + err = vkCreateDirectFBSurfaceEXT(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + err = demo_create_display_surface(demo); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + VkMetalSurfaceCreateInfoEXT surface; + surface.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + surface.pNext = NULL; + surface.flags = 0; + surface.pLayer = demo->caMetalLayer; + + err = vkCreateMetalSurfaceEXT(demo->inst, &surface, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + VkScreenSurfaceCreateInfoQNX createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.context = demo->screen_context; + createInfo.window = demo->screen_window; + + err = vkCreateScreenSurfaceQNX(demo->inst, &createInfo, NULL, &demo->surface); +#endif + assert(!err); +} + +static VkSurfaceFormatKHR pick_surface_format(const VkSurfaceFormatKHR *surfaceFormats, uint32_t count) { + // Prefer non-SRGB formats... + for (uint32_t i = 0; i < count; i++) { + const VkFormat format = surfaceFormats[i].format; + + if (format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_B8G8R8A8_UNORM || + format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || format == VK_FORMAT_A2R10G10B10_UNORM_PACK32 || + format == VK_FORMAT_A1R5G5B5_UNORM_PACK16 || format == VK_FORMAT_R5G6B5_UNORM_PACK16 || + format == VK_FORMAT_R16G16B16A16_SFLOAT) { + return surfaceFormats[i]; + } + } + + printf("Can't find our preferred formats... Falling back to first exposed format. Rendering may be incorrect.\n"); + + assert(count >= 1); + return surfaceFormats[0]; +} + +static void demo_init_vk_swapchain(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + demo_create_surface(demo); + + // Iterate over each queue to learn whether it supports presenting: + VkBool32 *supportsPresent = (VkBool32 *)malloc(demo->queue_family_count * sizeof(VkBool32)); + for (uint32_t i = 0; i < demo->queue_family_count; i++) { + vkGetPhysicalDeviceSurfaceSupportKHR(demo->gpu, i, demo->surface, &supportsPresent[i]); + } + + // Search for a graphics and a present queue in the array of queue + // families, try to find one that supports both + uint32_t graphicsQueueFamilyIndex = UINT32_MAX; + uint32_t presentQueueFamilyIndex = UINT32_MAX; + for (uint32_t i = 0; i < demo->queue_family_count; i++) { + if ((demo->queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { + if (graphicsQueueFamilyIndex == UINT32_MAX) { + graphicsQueueFamilyIndex = i; + } + + if (supportsPresent[i] == VK_TRUE) { + graphicsQueueFamilyIndex = i; + presentQueueFamilyIndex = i; + break; + } + } + } + + if (presentQueueFamilyIndex == UINT32_MAX) { + // If didn't find a queue that supports both graphics and present, then + // find a separate present queue. + for (uint32_t i = 0; i < demo->queue_family_count; ++i) { + if (supportsPresent[i] == VK_TRUE) { + presentQueueFamilyIndex = i; + break; + } + } + } + + // Generate error if could not find both a graphics and a present queue + if (graphicsQueueFamilyIndex == UINT32_MAX || presentQueueFamilyIndex == UINT32_MAX) { + ERR_EXIT("Could not find both graphics and present queues\n", "Swapchain Initialization Failure"); + } + + demo->graphics_queue_family_index = graphicsQueueFamilyIndex; + demo->present_queue_family_index = presentQueueFamilyIndex; + demo->separate_present_queue = (demo->graphics_queue_family_index != demo->present_queue_family_index); + free(supportsPresent); + + demo_create_device(demo); + + vkGetDeviceQueue(demo->device, demo->graphics_queue_family_index, 0, &demo->graphics_queue); + + if (!demo->separate_present_queue) { + demo->present_queue = demo->graphics_queue; + } else { + vkGetDeviceQueue(demo->device, demo->present_queue_family_index, 0, &demo->present_queue); + } + + // Get the list of VkFormat's that are supported: + uint32_t formatCount; + err = vkGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface, &formatCount, NULL); + assert(!err); + VkSurfaceFormatKHR *surfFormats = (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR)); + err = vkGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface, &formatCount, surfFormats); + assert(!err); + VkSurfaceFormatKHR surfaceFormat = pick_surface_format(surfFormats, formatCount); + demo->format = surfaceFormat.format; + demo->color_space = surfaceFormat.colorSpace; + free(surfFormats); + + demo->quit = false; + demo->curFrame = 0; + + // Create semaphores to synchronize acquiring presentable buffers before + // rendering and waiting for drawing to be complete before presenting + VkSemaphoreCreateInfo semaphoreCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + }; + + // Create fences that we can use to throttle if we get too far + // ahead of the image presents + VkFenceCreateInfo fence_ci = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = VK_FENCE_CREATE_SIGNALED_BIT}; + for (uint32_t i = 0; i < FRAME_LAG; i++) { + err = vkCreateFence(demo->device, &fence_ci, NULL, &demo->fences[i]); + assert(!err); + + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_acquired_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_acquired_semaphores[i], "AcquireSem(%u)", i); + + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->draw_complete_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->draw_complete_semaphores[i], "DrawCompleteSem(%u)", i); + + if (demo->separate_present_queue) { + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_ownership_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_ownership_semaphores[i], "ImageOwnerSem(%u)", i); + } + } + demo->frame_index = 0; + demo->first_swapchain_frame = true; + + // Get Memory information and properties + vkGetPhysicalDeviceMemoryProperties(demo->gpu, &demo->memory_properties); +} + +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) +static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, + wl_fixed_t sy) {} + +static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) {} + +static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) { + struct demo *demo = data; + if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) { + xdg_toplevel_move(demo->xdg_toplevel, demo->seat, serial); + } +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, +}; + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {} + +static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) {} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) {} + +static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) { + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; + struct demo *demo = data; + switch (key) { + case KEY_ESC: // Escape + demo->quit = true; + break; + case KEY_LEFT: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case KEY_RIGHT: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case KEY_SPACE: // space bar + demo->pause = !demo->pause; + break; + } +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { + // Subscribe to pointer events + struct demo *demo = data; + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !demo->pointer) { + demo->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(demo->pointer, &pointer_listener, demo); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && demo->pointer) { + wl_pointer_destroy(demo->pointer); + demo->pointer = NULL; + } + // Subscribe to keyboard events + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + demo->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(demo->keyboard, &keyboard_listener, demo); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && demo->keyboard) { + wl_keyboard_destroy(demo->keyboard); + demo->keyboard = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void wm_base_ping(void *data UNUSED, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = {wm_base_ping}; + +static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version UNUSED) { + struct demo *demo = data; + // pickup wayland objects when they appear + if (strcmp(interface, wl_compositor_interface.name) == 0) { + uint32_t minVersion = version < 4 ? version : 4; + demo->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, minVersion); + if (demo->VK_KHR_incremental_present_enabled && minVersion < 4) { + fprintf(stderr, "Wayland compositor doesn't support VK_KHR_incremental_present, disabling.\n"); + demo->VK_KHR_incremental_present_enabled = false; + } + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + demo->xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(demo->xdg_wm_base, &wm_base_listener, NULL); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + demo->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(demo->seat, &seat_listener, demo); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + demo->xdg_decoration_mgr = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } +} + +static void registry_handle_global_remove(void *data UNUSED, struct wl_registry *registry UNUSED, uint32_t name UNUSED) {} + +static const struct wl_registry_listener registry_listener = {registry_handle_global, registry_handle_global_remove}; +#endif + +static void demo_init_connection(struct demo *demo) { +#if defined(VK_USE_PLATFORM_XCB_KHR) + const xcb_setup_t *setup; + xcb_screen_iterator_t iter; + int scr; + + const char *display_envar = getenv("DISPLAY"); + if (display_envar == NULL || display_envar[0] == '\0') { + printf("Environment variable DISPLAY requires a valid value.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + demo->connection = xcb_connect(NULL, &scr); + if (xcb_connection_has_error(demo->connection) > 0) { + printf("Cannot connect to XCB.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + setup = xcb_get_setup(demo->connection); + iter = xcb_setup_roots_iterator(setup); + while (scr-- > 0) xcb_screen_next(&iter); + + demo->screen = iter.data; +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo->display = wl_display_connect(NULL); + + if (demo->display == NULL) { + printf("Cannot connect to wayland.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + demo->registry = wl_display_get_registry(demo->display); + wl_registry_add_listener(demo->registry, ®istry_listener, demo); + wl_display_roundtrip(demo->display); +#endif +} + +static void demo_init(struct demo *demo, int argc, char **argv) { + vec3 eye = {0.0f, 3.0f, 5.0f}; + vec3 origin = {0, 0, 0}; + vec3 up = {0.0f, 1.0f, 0.0}; + + memset(demo, 0, sizeof(*demo)); + demo->presentMode = VK_PRESENT_MODE_FIFO_KHR; + demo->frameCount = INT32_MAX; + /* Autodetect suitable / best GPU by default */ + demo->gpu_number = -1; + demo->width = 500; + demo->height = 500; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--use_staging") == 0) { + demo->use_staging_buffer = true; + continue; + } + if ((strcmp(argv[i], "--present_mode") == 0) && (i < argc - 1)) { + demo->presentMode = atoi(argv[i + 1]); + i++; + continue; + } + if (strcmp(argv[i], "--break") == 0) { + demo->use_break = true; + continue; + } + if (strcmp(argv[i], "--validate") == 0) { + demo->validate = true; + continue; + } + if (strcmp(argv[i], "--validate-checks-disabled") == 0) { + demo->validate = true; + demo->validate_checks_disabled = true; + continue; + } + if (strcmp(argv[i], "--xlib") == 0) { + fprintf(stderr, "--xlib is deprecated and no longer does anything"); + continue; + } + if (strcmp(argv[i], "--c") == 0 && demo->frameCount == INT32_MAX && i < argc - 1 && + sscanf(argv[i + 1], "%d", &demo->frameCount) == 1 && demo->frameCount >= 0) { + i++; + continue; + } + if (strcmp(argv[i], "--width") == 0) { + if (i < argc - 1 && sscanf(argv[i + 1], "%d", &demo->width) == 1) { + if (demo->width > 0) { + i++; + continue; + } else { + ERR_EXIT("The --width parameter must be greater than 0", "User Error"); + } + } + ERR_EXIT("The --width parameter must be followed by a number", "User Error"); + } + if (strcmp(argv[i], "--height") == 0) { + if (i < argc - 1 && sscanf(argv[i + 1], "%d", &demo->height) == 1) { + if (demo->height > 0) { + i++; + continue; + } else { + ERR_EXIT("The --height parameter must be greater than 0", "User Error"); + } + } + ERR_EXIT("The --height parameter must be followed by a number", "User Error"); + } + if (strcmp(argv[i], "--suppress_popups") == 0) { + demo->suppress_popups = true; + continue; + } + if (strcmp(argv[i], "--display_timing") == 0) { + demo->VK_GOOGLE_display_timing_enabled = true; + continue; + } + if (strcmp(argv[i], "--incremental_present") == 0) { + demo->VK_KHR_incremental_present_enabled = true; + continue; + } + if ((strcmp(argv[i], "--gpu_number") == 0) && (i < argc - 1)) { + demo->gpu_number = atoi(argv[i + 1]); + if (demo->gpu_number < 0) demo->invalid_gpu_selection = true; + i++; + continue; + } + if (strcmp(argv[i], "--force_errors") == 0) { + demo->force_errors = true; + continue; + } + +#if defined(ANDROID) + ERR_EXIT("Usage: vkcube [--validate]\n", "Usage"); +#else + char *message = + "Usage:\n %s\t[--use_staging] [--validate] [--validate-checks-disabled]\n" + "\t[--break] [--c ] [--suppress_popups]\n" + "\t[--incremental_present] [--display_timing]\n" + "\t[--gpu_number ]\n" + "\t[--present_mode ]\n" + "\t[--width ] [--height ]\n" + "\t[--force_errors]\n" + "\t\n" + "\t\tVK_PRESENT_MODE_IMMEDIATE_KHR = %d\n" + "\t\tVK_PRESENT_MODE_MAILBOX_KHR = %d\n" + "\t\tVK_PRESENT_MODE_FIFO_KHR = %d\n" + "\t\tVK_PRESENT_MODE_FIFO_RELAXED_KHR = %d\n"; + int length = snprintf(NULL, 0, message, APP_SHORT_NAME, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR); + char *usage = (char *)malloc(length + 1); + if (!usage) { + exit(1); + } + snprintf(usage, length + 1, message, APP_SHORT_NAME, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR); +#if defined(_WIN32) + if (!demo->suppress_popups) MessageBox(NULL, usage, "Usage Error", MB_OK); +#else + fprintf(stderr, "%s", usage); + fflush(stderr); +#endif + free(usage); + exit(1); +#endif + } + + demo_init_connection(demo); + + demo_init_vk(demo); + + demo->spin_angle = 4.0f; + demo->spin_increment = 0.2f; + demo->pause = false; + + mat4x4_perspective(demo->projection_matrix, (float)degreesToRadians(45.0f), 1.0f, 0.1f, 100.0f); + mat4x4_look_at(demo->view_matrix, eye, origin, up); + mat4x4_identity(demo->model_matrix); + + demo->projection_matrix[1][1] *= -1; // Flip projection matrix from GL to Vulkan orientation. +} + +#if defined(VK_USE_PLATFORM_WIN32_KHR) +// Include header required for parsing the command line options. +#include + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow) { + MSG msg; // message + bool done; // flag saying when app is complete + int argc; + char **argv; + + // Ensure wParam is initialized. + msg.wParam = 0; + + // Use the CommandLine functions to get the command line arguments. + // Unfortunately, Microsoft outputs + // this information as wide characters for Unicode, and we simply want the + // Ascii version to be compatible + // with the non-Windows side. So, we have to convert the information to + // Ascii character strings. + LPWSTR *commandLineArgs = CommandLineToArgvW(GetCommandLineW(), &argc); + if (NULL == commandLineArgs) { + argc = 0; + } + + if (argc > 0) { + argv = (char **)malloc(sizeof(char *) * argc); + if (argv == NULL) { + argc = 0; + } else { + for (int iii = 0; iii < argc; iii++) { + size_t wideCharLen = wcslen(commandLineArgs[iii]); + size_t numConverted = 0; + + argv[iii] = (char *)malloc(sizeof(char) * (wideCharLen + 1)); + if (argv[iii] != NULL) { + wcstombs_s(&numConverted, argv[iii], wideCharLen + 1, commandLineArgs[iii], wideCharLen + 1); + } + } + } + } else { + argv = NULL; + } + + demo_init(&demo, argc, argv); + + // Free up the items we had to allocate for the command line arguments. + if (argc > 0 && argv != NULL) { + for (int iii = 0; iii < argc; iii++) { + if (argv[iii] != NULL) { + free(argv[iii]); + } + } + free(argv); + } + + demo.connection = hInstance; + strncpy(demo.name, "Vulkan Cube", APP_NAME_STR_LEN); + demo_create_window(&demo); + demo_init_vk_swapchain(&demo); + + demo_prepare(&demo); + + done = false; // initialize loop condition variable + + // main message loop + while (!done) { + if (demo.pause) { + const BOOL succ = WaitMessage(); + + if (!succ) { + struct demo *tmp = &demo; + struct demo *demo = tmp; + ERR_EXIT("WaitMessage() failed on paused demo", "event loop error"); + } + } + PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + if (msg.message == WM_QUIT) // check for a quit message + { + done = true; // if found, quit app + } else { + /* Translate and dispatch to event queue*/ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + RedrawWindow(demo.window, NULL, NULL, RDW_INTERNALPAINT); + } + + demo_cleanup(&demo); + + return (int)msg.wParam; +} + +#elif defined(VK_USE_PLATFORM_METAL_EXT) +static void demo_main(struct demo *demo, void *caMetalLayer, int argc, const char *argv[]) { + demo_init(demo, argc, (char **)argv); + demo->caMetalLayer = caMetalLayer; + demo_init_vk_swapchain(demo); + demo_prepare(demo); + demo->spin_angle = 0.4f; +} + +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) +#include +#include +#include "android_util.h" + +static bool initialized = false; +static bool active = false; +struct demo demo; + +static int32_t processInput(struct android_app *app, AInputEvent *event) { return 0; } + +static void processCommand(struct android_app *app, int32_t cmd) { + switch (cmd) { + case APP_CMD_INIT_WINDOW: { + if (app->window) { + // We're getting a new window. If the app is starting up, we + // need to initialize. If the app has already been + // initialized, that means that we lost our previous window, + // which means that we have a lot of work to do. At a minimum, + // we need to destroy the swapchain and surface associated with + // the old window, and create a new surface and swapchain. + // However, since there are a lot of other objects/state that + // is tied to the swapchain, it's easiest to simply cleanup and + // start over (i.e. use a brute-force approach of re-starting + // the app) + if (demo.prepared) { + demo_cleanup(&demo); + } + + // Parse Intents into argc, argv + // Use the following key to send arguments, i.e. + // --es args "--validate" + const char key[] = "args"; + char *appTag = (char *)APP_SHORT_NAME; + int argc = 0; + char **argv = get_args(app, key, appTag, &argc); + + __android_log_print(ANDROID_LOG_INFO, appTag, "argc = %i", argc); + for (int i = 0; i < argc; i++) __android_log_print(ANDROID_LOG_INFO, appTag, "argv[%i] = %s", i, argv[i]); + + demo_init(&demo, argc, argv); + + // Free the argv malloc'd by get_args + for (int i = 0; i < argc; i++) free(argv[i]); + + demo.window = (void *)app->window; + demo_init_vk_swapchain(&demo); + demo_prepare(&demo); + initialized = true; + } + break; + } + case APP_CMD_GAINED_FOCUS: { + active = true; + break; + } + case APP_CMD_LOST_FOCUS: { + active = false; + break; + } + } +} + +void android_main(struct android_app *app) { + demo.prepared = false; + + app->onAppCmd = processCommand; + app->onInputEvent = processInput; + + while (1) { + int events; + struct android_poll_source *source; + while (ALooper_pollOnce(active ? 0 : -1, NULL, &events, (void **)&source) >= 0) { + if (source) { + source->process(app, source); + } + + if (app->destroyRequested != 0) { + demo_cleanup(&demo); + return; + } + } + if (initialized && active) { + demo_run(&demo); + } + } +} +#else +int main(int argc, char **argv) { + struct demo demo; + + demo_init(&demo, argc, argv); +#if defined(VK_USE_PLATFORM_XCB_KHR) + demo_create_xcb_window(&demo); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + demo_create_xlib_window(&demo); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo_create_window(&demo); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo_create_directfb_window(&demo); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + demo_create_window(&demo); +#endif + + demo_init_vk_swapchain(&demo); + + demo_prepare(&demo); + +#if defined(VK_USE_PLATFORM_XCB_KHR) + demo_run_xcb(&demo); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + demo_run_xlib(&demo); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo_run(&demo); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo_run_directfb(&demo); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + demo_run_display(&demo); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + demo_run(&demo); +#endif + + demo_cleanup(&demo); + + return validation_error; +} +#endif diff --git a/cube/cube-nc.c b/cube/cube-nc.c new file mode 100644 index 000000000..483bf8bcf --- /dev/null +++ b/cube/cube-nc.c @@ -0,0 +1,4585 @@ +/* + * Copyright (c) 2015-2019 The Khronos Group Inc. + * Copyright (c) 2015-2019 Valve Corporation + * Copyright (c) 2015-2019 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Chia-I Wu + * Author: Courtney Goeltzenleuchter + * Author: Ian Elliott + * Author: Ian Elliott + * Author: Jon Ashburn + * Author: Gwan-gyeong Mun + * Author: Tony Barbour + * Author: Bill Hollings + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_XCB_KHR) +#include +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) +#include +#include "xdg-shell-client-header.h" +#include "xdg-decoration-client-header.h" +#endif + +#ifdef _WIN32 +#ifdef _MSC_VER +#pragma comment(linker, "/subsystem:windows") +#endif // MSVC +#define APP_NAME_STR_LEN 80 +#endif // _WIN32 + +// Volk requires VK_NO_PROTOTYPES before including vulkan.h +#define VK_NO_PROTOTYPES +#include +#define VOLK_IMPLEMENTATION +#include "volk.h" + +#include "linmath.h" +#include "object_type_string_helper.h" + +#include "gettime.h" +#include "inttypes.h" +#define MILLION 1000000L +#define BILLION 1000000000L + +#define DEMO_TEXTURE_COUNT 1 +#define APP_SHORT_NAME "vkcube" +#define APP_LONG_NAME "Vulkan Cube" + +// Allow a maximum of two outstanding presentation operations. +#define FRAME_LAG 2 + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#if defined(NDEBUG) && defined(__GNUC__) +#define U_ASSERT_ONLY __attribute__((unused)) +#else +#define U_ASSERT_ONLY +#endif + +#if defined(__GNUC__) +#define UNUSED __attribute__((unused)) +#else +#define UNUSED +#endif + +#ifdef _WIN32 +bool in_callback = false; +#define ERR_EXIT(err_msg, err_class) \ + do { \ + if (!demo->suppress_popups) MessageBox(NULL, err_msg, err_class, MB_OK); \ + exit(1); \ + } while (0) +void DbgMsg(char *fmt, ...) { + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + fflush(stdout); +} + +#elif defined __ANDROID__ +#include +#define ERR_EXIT(err_msg, err_class) \ + do { \ + ((void)__android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", err_msg)); \ + exit(1); \ + } while (0) +#ifdef VARARGS_WORKS_ON_ANDROID +void DbgMsg(const char *fmt, ...) { + va_list va; + va_start(va, fmt); + __android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", fmt, va); + va_end(va); +} +#else // VARARGS_WORKS_ON_ANDROID +#define DbgMsg(fmt, ...) \ + do { \ + ((void)__android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", fmt, ##__VA_ARGS__)); \ + } while (0) +#endif // VARARGS_WORKS_ON_ANDROID +#else +#define ERR_EXIT(err_msg, err_class) \ + do { \ + printf("%s\n", err_msg); \ + fflush(stdout); \ + exit(1); \ + } while (0) +void DbgMsg(char *fmt, ...) { + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + fflush(stdout); +} +#endif + +/* + * structure to track all objects related to a texture. + */ +struct texture_object { + VkSampler sampler; + + VkImage image; + VkBuffer buffer; + VkImageLayout imageLayout; + + VkMemoryAllocateInfo mem_alloc; + VkDeviceMemory mem; + VkImageView view; + int32_t tex_width, tex_height; +}; + +static char *tex_files[] = {"lunarg.ppm"}; + +static int validation_error = 0; + +struct vktexcube_vs_uniform { + // Must start with MVP + float mvp[4][4]; + float position[12 * 3][4]; + float attr[12 * 3][4]; +}; + +//-------------------------------------------------------------------------------------- +// Mesh and VertexFormat Data +//-------------------------------------------------------------------------------------- +// clang-format off +static const float g_vertex_buffer_data[] = { + -1.0f,-1.0f,-1.0f, // -X side + -1.0f,-1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + + -1.0f,-1.0f,-1.0f, // -Z side + 1.0f, 1.0f,-1.0f, + 1.0f,-1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + + -1.0f,-1.0f,-1.0f, // -Y side + 1.0f,-1.0f,-1.0f, + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f,-1.0f, + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + + -1.0f, 1.0f,-1.0f, // +Y side + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f,-1.0f, + + 1.0f, 1.0f,-1.0f, // +X side + 1.0f, 1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f,-1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + + -1.0f, 1.0f, 1.0f, // +Z side + -1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, +}; + +static const float g_uv_buffer_data[] = { + 0.0f, 1.0f, // -X side + 1.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, + + 1.0f, 1.0f, // -Z side + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + + 1.0f, 0.0f, // -Y side + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + + 1.0f, 0.0f, // +Y side + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + + 1.0f, 0.0f, // +X side + 0.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + + 0.0f, 0.0f, // +Z side + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, +}; +// clang-format on + +void dumpMatrix(const char *note, mat4x4 MVP) { + int i; + + printf("%s: \n", note); + for (i = 0; i < 4; i++) { + printf("%f, %f, %f, %f\n", MVP[i][0], MVP[i][1], MVP[i][2], MVP[i][3]); + } + printf("\n"); + fflush(stdout); +} + +void dumpVec4(const char *note, vec4 vector) { + printf("%s: \n", note); + printf("%f, %f, %f, %f\n", vector[0], vector[1], vector[2], vector[3]); + printf("\n"); + fflush(stdout); +} + +char const *to_string(VkPhysicalDeviceType const type) { + switch (type) { + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + return "Other"; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + return "IntegratedGpu"; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + return "DiscreteGpu"; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + return "VirtualGpu"; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + return "Cpu"; + default: + return "Unknown"; + } +} + +typedef struct { + VkImage image; + VkCommandBuffer cmd; + VkCommandBuffer graphics_to_present_cmd; + VkImageView view; + VkBuffer uniform_buffer; + VkDeviceMemory uniform_memory; + void *uniform_memory_ptr; + VkFramebuffer framebuffer; + VkDescriptorSet descriptor_set; + VkBuffer descriptor_heap[3]; +} SwapchainImageResources; + +struct demo { +#if defined(VK_USE_PLATFORM_WIN32_KHR) +#define APP_NAME_STR_LEN 80 + HINSTANCE connection; // hInstance - Windows Instance + char name[APP_NAME_STR_LEN]; // Name to put on the window/icon + HWND window; // hWnd - window handle + POINT minsize; // minimum window size +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + Display *display; + Window xlib_window; + Atom xlib_wm_delete_window; +#elif defined(VK_USE_PLATFORM_XCB_KHR) + Display *display; + xcb_connection_t *connection; + xcb_screen_t *screen; + xcb_window_t xcb_window; + xcb_intern_atom_reply_t *atom_wm_delete_window; +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_surface *window; + struct xdg_wm_base *xdg_wm_base; + struct zxdg_decoration_manager_v1 *xdg_decoration_mgr; + struct zxdg_toplevel_decoration_v1 *toplevel_decoration; + struct xdg_surface *xdg_surface; + int xdg_surface_has_been_configured; + struct xdg_toplevel *xdg_toplevel; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + IDirectFB *dfb; + IDirectFBSurface *window; + IDirectFBEventBuffer *event_buffer; +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + struct ANativeWindow *window; +#elif defined(VK_USE_PLATFORM_METAL_EXT) + void *caMetalLayer; +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + screen_context_t screen_context; + screen_window_t screen_window; + screen_event_t screen_event; +#endif + + VkSurfaceKHR surface; + bool prepared; + bool use_staging_buffer; + bool separate_present_queue; + bool is_minimized; + bool invalid_gpu_selection; + int32_t gpu_number; + + bool VK_KHR_incremental_present_enabled; + + bool VK_GOOGLE_display_timing_enabled; + bool syncd_with_actual_presents; + uint64_t refresh_duration; + uint64_t refresh_duration_multiplier; + uint64_t target_IPD; // image present duration (inverse of frame rate) + uint64_t prev_desired_present_time; + uint32_t next_present_id; + uint32_t last_early_id; // 0 if no early images + uint32_t last_late_id; // 0 if no late images + + VkInstance inst; + VkPhysicalDevice gpu; + VkDevice device; + VkQueue graphics_queue; + VkQueue present_queue; + uint32_t graphics_queue_family_index; + uint32_t present_queue_family_index; + VkSemaphore image_acquired_semaphores[FRAME_LAG]; + VkSemaphore draw_complete_semaphores[FRAME_LAG]; + VkSemaphore image_ownership_semaphores[FRAME_LAG]; + VkPhysicalDeviceProperties gpu_props; + VkQueueFamilyProperties *queue_props; + VkPhysicalDeviceMemoryProperties memory_properties; + + uint32_t enabled_extension_count; + uint32_t enabled_layer_count; + char *extension_names[64]; + char *enabled_layers[64]; + + int width, height; + VkFormat format; + VkColorSpaceKHR color_space; + + uint32_t swapchainImageCount; + VkSwapchainKHR swapchain; + SwapchainImageResources *swapchain_image_resources; + VkPresentModeKHR presentMode; + VkFence fences[FRAME_LAG]; + int frame_index; + bool first_swapchain_frame; + + VkCommandPool cmd_pool; + VkCommandPool present_cmd_pool; + + struct { + VkFormat format; + + VkImage image; + VkMemoryAllocateInfo mem_alloc; + VkDeviceMemory mem; + VkImageView view; + } depth; + + struct texture_object textures[DEMO_TEXTURE_COUNT]; + struct texture_object staging_texture; + + VkCommandBuffer cmd; // Buffer for initialization commands + VkPipelineLayout pipeline_layout; + VkDescriptorSetLayout desc_layout; + VkPipelineCache pipelineCache; + VkRenderPass render_pass; + VkPipeline pipeline; + + mat4x4 projection_matrix; + mat4x4 view_matrix; + mat4x4 model_matrix; + + float spin_angle; + float spin_increment; + bool pause; + + VkShaderModule vert_shader_module; + VkShaderModule frag_shader_module; + + VkDescriptorPool desc_pool; + + bool quit; + int32_t curFrame; + int32_t frameCount; + bool validate; + bool validate_checks_disabled; + bool use_break; + bool suppress_popups; + bool force_errors; + + VkDebugUtilsMessengerEXT dbg_messenger; + + uint32_t current_buffer; + uint32_t queue_family_count; +}; + +VKAPI_ATTR VkBool32 VKAPI_CALL debug_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, + void *pUserData) { + char prefix[64] = ""; + char *message = (char *)malloc(strlen(pCallbackData->pMessage) + 5000); + assert(message); + struct demo *demo = (struct demo *)pUserData; + + if (demo->use_break) { +#ifndef WIN32 + raise(SIGTRAP); +#else + DebugBreak(); +#endif + } + + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + strcat(prefix, "VERBOSE : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + strcat(prefix, "INFO : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + strcat(prefix, "WARNING : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + strcat(prefix, "ERROR : "); + } + + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { + strcat(prefix, "GENERAL"); + } else { + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + strcat(prefix, "VALIDATION"); + validation_error = 1; + } + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + strcat(prefix, "|"); + } + strcat(prefix, "PERFORMANCE"); + } + } + + sprintf(message, "%s - Message Id Number: %d | Message Id Name: %s\n\t%s\n", prefix, pCallbackData->messageIdNumber, + pCallbackData->pMessageIdName == NULL ? "" : pCallbackData->pMessageIdName, pCallbackData->pMessage); + if (pCallbackData->objectCount > 0) { + char tmp_message[500]; + sprintf(tmp_message, "\n\tObjects - %d\n", pCallbackData->objectCount); + strcat(message, tmp_message); + for (uint32_t object = 0; object < pCallbackData->objectCount; ++object) { + sprintf(tmp_message, "\t\tObject[%d] - %s", object, string_VkObjectType(pCallbackData->pObjects[object].objectType)); + strcat(message, tmp_message); + + VkObjectType t = pCallbackData->pObjects[object].objectType; + if (t == VK_OBJECT_TYPE_INSTANCE || t == VK_OBJECT_TYPE_PHYSICAL_DEVICE || t == VK_OBJECT_TYPE_DEVICE || + t == VK_OBJECT_TYPE_COMMAND_BUFFER || t == VK_OBJECT_TYPE_QUEUE) { + sprintf(tmp_message, ", Handle %p", (void *)(uintptr_t)(pCallbackData->pObjects[object].objectHandle)); + strcat(message, tmp_message); + } else { + sprintf(tmp_message, ", Handle Ox%" PRIx64, (pCallbackData->pObjects[object].objectHandle)); + strcat(message, tmp_message); + } + + if (NULL != pCallbackData->pObjects[object].pObjectName && strlen(pCallbackData->pObjects[object].pObjectName) > 0) { + sprintf(tmp_message, ", Name \"%s\"", pCallbackData->pObjects[object].pObjectName); + strcat(message, tmp_message); + } + sprintf(tmp_message, "\n"); + strcat(message, tmp_message); + } + } + if (pCallbackData->cmdBufLabelCount > 0) { + char tmp_message[500]; + sprintf(tmp_message, "\n\tCommand Buffer Labels - %d\n", pCallbackData->cmdBufLabelCount); + strcat(message, tmp_message); + for (uint32_t cmd_buf_label = 0; cmd_buf_label < pCallbackData->cmdBufLabelCount; ++cmd_buf_label) { + sprintf(tmp_message, "\t\tLabel[%d] - %s { %f, %f, %f, %f}\n", cmd_buf_label, + pCallbackData->pCmdBufLabels[cmd_buf_label].pLabelName, pCallbackData->pCmdBufLabels[cmd_buf_label].color[0], + pCallbackData->pCmdBufLabels[cmd_buf_label].color[1], pCallbackData->pCmdBufLabels[cmd_buf_label].color[2], + pCallbackData->pCmdBufLabels[cmd_buf_label].color[3]); + strcat(message, tmp_message); + } + } + +#ifdef _WIN32 + + in_callback = true; + if (!demo->suppress_popups) MessageBox(NULL, message, "Alert", MB_OK); + in_callback = false; + +#elif defined(ANDROID) + + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + __android_log_print(ANDROID_LOG_WARN, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + __android_log_print(ANDROID_LOG_ERROR, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + __android_log_print(ANDROID_LOG_VERBOSE, APP_SHORT_NAME, "%s", message); + } else { + __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message); + } + +#else + + printf("%s\n", message); + fflush(stdout); + +#endif + + free(message); + + // Don't bail out, but keep going. + return false; +} + +bool ActualTimeLate(uint64_t desired, uint64_t actual, uint64_t rdur) { + // The desired time was the earliest time that the present should have + // occured. In almost every case, the actual time should be later than the + // desired time. We should only consider the actual time "late" if it is + // after "desired + rdur". + if (actual <= desired) { + // The actual time was before or equal to the desired time. This will + // probably never happen, but in case it does, return false since the + // present was obviously NOT late. + return false; + } + uint64_t deadline = desired + rdur; + if (actual > deadline) { + return true; + } else { + return false; + } +} +bool CanPresentEarlier(uint64_t earliest, uint64_t actual, uint64_t margin, uint64_t rdur) { + if (earliest < actual) { + // Consider whether this present could have occured earlier. Make sure + // that earliest time was at least 2msec earlier than actual time, and + // that the margin was at least 2msec: + uint64_t diff = actual - earliest; + if ((diff >= (2 * MILLION)) && (margin >= (2 * MILLION))) { + // This present could have occured earlier because both: 1) the + // earliest time was at least 2 msec before actual time, and 2) the + // margin was at least 2msec. + return true; + } + } + return false; +} + +// Forward declarations: +static void demo_resize(struct demo *demo); +static void demo_create_surface(struct demo *demo); + +#if defined(__GNUC__) || defined(__clang__) +#define DECORATE_PRINTF(_fmt_argnum, _first_param_num) __attribute__((format(printf, _fmt_argnum, _first_param_num))) +#else +#define DECORATE_PRINTF(_fmt_num, _first_param_num) +#endif + +DECORATE_PRINTF(4, 5) +static void demo_name_object(struct demo *demo, VkObjectType object_type, uint64_t vulkan_handle, const char *format, ...) { + if (!demo->validate) { + return; + } + VkResult U_ASSERT_ONLY err; + char name[1024]; + va_list argptr; + va_start(argptr, format); + vsnprintf(name, sizeof(name), format, argptr); + va_end(argptr); + name[sizeof(name) - 1] = '\0'; + + VkDebugUtilsObjectNameInfoEXT obj_name = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .pNext = NULL, + .objectType = object_type, + .objectHandle = vulkan_handle, + .pObjectName = name, + }; + err = vkSetDebugUtilsObjectNameEXT(demo->device, &obj_name); + assert(!err); +} + +DECORATE_PRINTF(4, 5) +static void demo_push_cb_label(struct demo *demo, VkCommandBuffer cb, const float *color, const char *format, ...) { + if (!demo->validate) { + return; + } + char name[1024]; + va_list argptr; + va_start(argptr, format); + vsnprintf(name, sizeof(name), format, argptr); + va_end(argptr); + name[sizeof(name) - 1] = '\0'; + + VkDebugUtilsLabelEXT label = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, + .pNext = NULL, + .pLabelName = name, + }; + if (color) { + memcpy(label.color, color, sizeof(label.color)); + } + + vkCmdBeginDebugUtilsLabelEXT(cb, &label); +} + +static void demo_pop_cb_label(struct demo *demo, VkCommandBuffer cb) { + if (!demo->validate) { + return; + } + vkCmdEndDebugUtilsLabelEXT(cb); +} + +static bool memory_type_from_properties(struct demo *demo, uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) { + // Search memtypes to find first index with those properties + for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) { + if ((typeBits & 1) == 1) { + // Type is available, does it match user properties? + if ((demo->memory_properties.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) { + *typeIndex = i; + return true; + } + } + typeBits >>= 1; + } + // No memory types matched, return failure + return false; +} + +static void demo_flush_init_cmd(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + // This function could get called twice if the texture uses a staging buffer + // In that case the second call should be ignored + if (demo->cmd == VK_NULL_HANDLE) return; + + err = vkEndCommandBuffer(demo->cmd); + assert(!err); + + VkFence fence; + VkFenceCreateInfo fence_ci = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = 0}; + if (demo->force_errors) { + // Remove sType to intentionally force validation layer errors. + fence_ci.sType = 0; + } + err = vkCreateFence(demo->device, &fence_ci, NULL, &fence); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_FENCE, (uint64_t)fence, "InitFence"); + + const VkCommandBuffer cmd_bufs[] = {demo->cmd}; + VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 0, + .pWaitSemaphores = NULL, + .pWaitDstStageMask = NULL, + .commandBufferCount = 1, + .pCommandBuffers = cmd_bufs, + .signalSemaphoreCount = 0, + .pSignalSemaphores = NULL}; + + err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, fence); + assert(!err); + + err = vkWaitForFences(demo->device, 1, &fence, VK_TRUE, UINT64_MAX); + assert(!err); + + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, cmd_bufs); + vkDestroyFence(demo->device, fence, NULL); + demo->cmd = VK_NULL_HANDLE; +} + +static void demo_set_image_layout(struct demo *demo, VkImage image, VkImageAspectFlags aspectMask, VkImageLayout old_image_layout, + VkImageLayout new_image_layout, VkAccessFlagBits srcAccessMask, VkPipelineStageFlags src_stages, + VkPipelineStageFlags dest_stages) { + assert(demo->cmd); + + VkImageMemoryBarrier image_memory_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = srcAccessMask, + .dstAccessMask = 0, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .oldLayout = old_image_layout, + .newLayout = new_image_layout, + .image = image, + .subresourceRange = {aspectMask, 0, 1, 0, 1}}; + + switch (new_image_layout) { + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + /* Make sure anything that was copying from this image has completed */ + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: + image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + break; + + default: + image_memory_barrier.dstAccessMask = 0; + break; + } + + VkImageMemoryBarrier *pmemory_barrier = &image_memory_barrier; + + vkCmdPipelineBarrier(demo->cmd, src_stages, dest_stages, 0, 0, NULL, 0, NULL, 1, pmemory_barrier); +} + +static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { + const VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = NULL, + }; + const VkClearValue clear_values[2] = { + [0] = {.color.float32 = {0.2f, 0.2f, 0.2f, 0.2f}}, + [1] = {.depthStencil = {1.0f, 0}}, + }; + const VkRenderPassBeginInfo rp_begin = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = NULL, + .renderPass = demo->render_pass, + .framebuffer = demo->swapchain_image_resources[demo->current_buffer].framebuffer, + .renderArea.offset.x = 0, + .renderArea.offset.y = 0, + .renderArea.extent.width = demo->width, + .renderArea.extent.height = demo->height, + .clearValueCount = 2, + .pClearValues = clear_values, + }; + VkResult U_ASSERT_ONLY err; + + err = vkBeginCommandBuffer(cmd_buf, &cmd_buf_info); + + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)cmd_buf, "CubeDrawCommandBuf"); + + const float begin_color[4] = {0.4f, 0.3f, 0.2f, 0.1f}; + demo_push_cb_label(demo, cmd_buf, begin_color, "DrawBegin"); + + assert(!err); + vkCmdBeginRenderPass(cmd_buf, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); + + const float renderpass_color[4] = {8.4f, 7.3f, 6.2f, 7.1f}; + demo_push_cb_label(demo, cmd_buf, renderpass_color, "InsideRenderPass"); + + vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline); + vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline_layout, 0, 1, + &demo->swapchain_image_resources[demo->current_buffer].descriptor_set, 0, NULL); + VkViewport viewport; + memset(&viewport, 0, sizeof(viewport)); + float viewport_dimension; + if (demo->width < demo->height) { + viewport_dimension = (float)demo->width; + viewport.y = (demo->height - demo->width) / 2.0f; + } else { + viewport_dimension = (float)demo->height; + viewport.x = (demo->width - demo->height) / 2.0f; + } + viewport.height = viewport_dimension; + viewport.width = viewport_dimension; + viewport.minDepth = (float)0.0f; + viewport.maxDepth = (float)1.0f; + vkCmdSetViewport(cmd_buf, 0, 1, &viewport); + + VkRect2D scissor; + memset(&scissor, 0, sizeof(scissor)); + scissor.extent.width = demo->width; + scissor.extent.height = demo->height; + scissor.offset.x = 0; + scissor.offset.y = 0; + vkCmdSetScissor(cmd_buf, 0, 1, &scissor); + + const float draw_color[4] = {-0.4f, -0.3f, -0.2f, -0.1f}; + demo_push_cb_label(demo, cmd_buf, draw_color, "ActualDraw"); + vkCmdDraw(cmd_buf, 12 * 3, 1, 0, 0); + demo_pop_cb_label(demo, cmd_buf); + + // Note that ending the renderpass changes the image's layout from + // COLOR_ATTACHMENT_OPTIMAL to PRESENT_SRC_KHR + vkCmdEndRenderPass(cmd_buf); + demo_pop_cb_label(demo, cmd_buf); + + if (demo->separate_present_queue) { + // We have to transfer ownership from the graphics queue family to the + // present queue family to be able to present. Note that we don't have + // to transfer from present queue family back to graphics queue family at + // the start of the next frame because we don't care about the image's + // contents at that point. + VkImageMemoryBarrier image_ownership_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = demo->graphics_queue_family_index, + .dstQueueFamilyIndex = demo->present_queue_family_index, + .image = demo->swapchain_image_resources[demo->current_buffer].image, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, + NULL, 1, &image_ownership_barrier); + } + demo_pop_cb_label(demo, cmd_buf); + err = vkEndCommandBuffer(cmd_buf); + assert(!err); +} + +void demo_build_image_ownership_cmd(struct demo *demo, int i) { + VkResult U_ASSERT_ONLY err; + + const VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = NULL, + }; + err = vkBeginCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd, &cmd_buf_info); + assert(!err); + + VkImageMemoryBarrier image_ownership_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = demo->graphics_queue_family_index, + .dstQueueFamilyIndex = demo->present_queue_family_index, + .image = demo->swapchain_image_resources[i].image, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + vkCmdPipelineBarrier(demo->swapchain_image_resources[i].graphics_to_present_cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &image_ownership_barrier); + err = vkEndCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd); + assert(!err); +} + +void demo_update_data_buffer(struct demo *demo) { + mat4x4 MVP, Model, VP; + int matrixSize = sizeof(MVP); + + mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix); + + // Rotate around the Y axis + mat4x4_dup(Model, demo->model_matrix); + mat4x4_rotate_Y(demo->model_matrix, Model, (float)degreesToRadians(demo->spin_angle)); + mat4x4_orthonormalize(demo->model_matrix, demo->model_matrix); + mat4x4_mul(MVP, VP, demo->model_matrix); + + memcpy(demo->swapchain_image_resources[demo->current_buffer].uniform_memory_ptr, (const void *)&MVP[0][0], matrixSize); +} + +void DemoUpdateTargetIPD(struct demo *demo) { + // Look at what happened to previous presents, and make appropriate + // adjustments in timing: + VkResult U_ASSERT_ONLY err; + VkPastPresentationTimingGOOGLE *past = NULL; + uint32_t count = 0; + + err = vkGetPastPresentationTimingGOOGLE(demo->device, demo->swapchain, &count, NULL); + assert(!err); + if (count) { + past = (VkPastPresentationTimingGOOGLE *)malloc(sizeof(VkPastPresentationTimingGOOGLE) * count); + assert(past); + err = vkGetPastPresentationTimingGOOGLE(demo->device, demo->swapchain, &count, past); + assert(!err); + + bool early = false; + bool late = false; + bool calibrate_next = false; + for (uint32_t i = 0; i < count; i++) { + if (!demo->syncd_with_actual_presents) { + // This is the first time that we've received an + // actualPresentTime for this swapchain. In order to not + // perceive these early frames as "late", we need to sync-up + // our future desiredPresentTime's with the + // actualPresentTime(s) that we're receiving now. + calibrate_next = true; + + // So that we don't suspect any pending presents as late, + // record them all as suspected-late presents: + demo->last_late_id = demo->next_present_id - 1; + demo->last_early_id = 0; + demo->syncd_with_actual_presents = true; + break; + } else if (CanPresentEarlier(past[i].earliestPresentTime, past[i].actualPresentTime, past[i].presentMargin, + demo->refresh_duration)) { + // This image could have been presented earlier. We don't want + // to decrease the target_IPD until we've seen early presents + // for at least two seconds. + if (demo->last_early_id == past[i].presentID) { + // We've now seen two seconds worth of early presents. + // Flag it as such, and reset the counter: + early = true; + demo->last_early_id = 0; + } else if (demo->last_early_id == 0) { + // This is the first early present we've seen. + // Calculate the presentID for two seconds from now. + uint64_t lastEarlyTime = past[i].actualPresentTime + (2 * BILLION); + uint32_t howManyPresents = (uint32_t)((lastEarlyTime - past[i].actualPresentTime) / demo->target_IPD); + demo->last_early_id = past[i].presentID + howManyPresents; + } else { + // We are in the midst of a set of early images, + // and so we won't do anything. + } + late = false; + demo->last_late_id = 0; + } else if (ActualTimeLate(past[i].desiredPresentTime, past[i].actualPresentTime, demo->refresh_duration)) { + // This image was presented after its desired time. Since + // there's a delay between calling vkQueuePresentKHR and when + // we get the timing data, several presents may have been late. + // Thus, we need to threat all of the outstanding presents as + // being likely late, so that we only increase the target_IPD + // once for all of those presents. + if ((demo->last_late_id == 0) || (demo->last_late_id < past[i].presentID)) { + late = true; + // Record the last suspected-late present: + demo->last_late_id = demo->next_present_id - 1; + } else { + // We are in the midst of a set of likely-late images, + // and so we won't do anything. + } + early = false; + demo->last_early_id = 0; + } else { + // Since this image was not presented early or late, reset + // any sets of early or late presentIDs: + early = false; + late = false; + calibrate_next = true; + demo->last_early_id = 0; + demo->last_late_id = 0; + } + } + + if (early) { + // Since we've seen at least two-seconds worth of presnts that + // could have occured earlier than desired, let's decrease the + // target_IPD (i.e. increase the frame rate): + // + // TODO(ianelliott): Try to calculate a better target_IPD based + // on the most recently-seen present (this is overly-simplistic). + demo->refresh_duration_multiplier--; + if (demo->refresh_duration_multiplier == 0) { + // This should never happen, but in case it does, don't + // try to go faster. + demo->refresh_duration_multiplier = 1; + } + demo->target_IPD = demo->refresh_duration * demo->refresh_duration_multiplier; + } + if (late) { + // Since we found a new instance of a late present, we want to + // increase the target_IPD (i.e. decrease the frame rate): + // + // TODO(ianelliott): Try to calculate a better target_IPD based + // on the most recently-seen present (this is overly-simplistic). + demo->refresh_duration_multiplier++; + demo->target_IPD = demo->refresh_duration * demo->refresh_duration_multiplier; + } + + if (calibrate_next) { + int64_t multiple = demo->next_present_id - past[count - 1].presentID; + demo->prev_desired_present_time = (past[count - 1].actualPresentTime + (multiple * demo->target_IPD)); + } + free(past); + } +} + +static void demo_draw(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + // Ensure no more than FRAME_LAG renderings are outstanding + vkWaitForFences(demo->device, 1, &demo->fences[demo->frame_index], VK_TRUE, UINT64_MAX); + vkResetFences(demo->device, 1, &demo->fences[demo->frame_index]); + + do { + // Get the index of the next available swapchain image: + err = vkAcquireNextImageKHR(demo->device, demo->swapchain, UINT64_MAX, demo->image_acquired_semaphores[demo->frame_index], + VK_NULL_HANDLE, &demo->current_buffer); + + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + // demo->swapchain is out of date (e.g. the window was resized) and + // must be recreated: + demo_resize(demo); + } else if (err == VK_SUBOPTIMAL_KHR) { + // demo->swapchain is not as optimal as it could be, but the platform's + // presentation engine will still present the image correctly. + break; + } else if (err == VK_ERROR_SURFACE_LOST_KHR) { + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + demo_create_surface(demo); + demo_resize(demo); + } else { + assert(!err); + } + } while (err != VK_SUCCESS); + + demo_update_data_buffer(demo); + + if (demo->VK_GOOGLE_display_timing_enabled) { + // Look at what happened to previous presents, and make appropriate + // adjustments in timing: + DemoUpdateTargetIPD(demo); + + // Note: a real application would position its geometry to that it's in + // the correct locatoin for when the next image is presented. It might + // also wait, so that there's less latency between any input and when + // the next image is rendered/presented. This demo program is so + // simple that it doesn't do either of those. + } + + // Wait for the image acquired semaphore to be signaled to ensure + // that the image won't be rendered to until the presentation + // engine has fully released ownership to the application, and it is + // okay to render to the image. + VkPipelineStageFlags pipe_stage_flags; + VkSubmitInfo submit_info; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = NULL; + pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.pWaitDstStageMask = &pipe_stage_flags; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &demo->image_acquired_semaphores[demo->frame_index]; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].cmd; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; + err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, demo->fences[demo->frame_index]); + assert(!err); + + if (demo->separate_present_queue) { + // If we are using separate queues, change image ownership to the + // present queue before presenting, waiting for the draw complete + // semaphore and signalling the ownership released semaphore when finished + VkFence nullFence = VK_NULL_HANDLE; + pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].graphics_to_present_cmd; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &demo->image_ownership_semaphores[demo->frame_index]; + err = vkQueueSubmit(demo->present_queue, 1, &submit_info, nullFence); + assert(!err); + } + + // If we are using separate queues we have to wait for image ownership, + // otherwise wait for draw complete + VkPresentInfoKHR present = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = NULL, + .waitSemaphoreCount = 1, + .pWaitSemaphores = (demo->separate_present_queue) ? &demo->image_ownership_semaphores[demo->frame_index] + : &demo->draw_complete_semaphores[demo->frame_index], + .swapchainCount = 1, + .pSwapchains = &demo->swapchain, + .pImageIndices = &demo->current_buffer, + }; + + VkRectLayerKHR rect; + VkPresentRegionKHR region; + VkPresentRegionsKHR regions; + if (demo->VK_KHR_incremental_present_enabled) { + // If using VK_KHR_incremental_present, we provide a hint of the region + // that contains changed content relative to the previously-presented + // image. The implementation can use this hint in order to save + // work/power (by only copying the region in the hint). The + // implementation is free to ignore the hint though, and so we must + // ensure that the entire image has the correctly-drawn content. + uint32_t eighthOfWidth = demo->width / 8; + uint32_t eighthOfHeight = demo->height / 8; + + if (demo->first_swapchain_frame) { + rect.offset.x = 0; + rect.offset.y = 0; + rect.extent.width = demo->width; + rect.extent.height = demo->height; + } else { + rect.offset.x = eighthOfWidth; + rect.offset.y = eighthOfHeight; + rect.extent.width = eighthOfWidth * 6; + rect.extent.height = eighthOfHeight * 6; + } + rect.layer = 0; + + region.rectangleCount = 1; + region.pRectangles = ▭ + + regions.sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR; + regions.pNext = present.pNext; + regions.swapchainCount = present.swapchainCount; + regions.pRegions = ®ion; + present.pNext = ®ions; + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + VkPresentTimeGOOGLE ptime; + if (demo->prev_desired_present_time == 0) { + // This must be the first present for this swapchain. + // + // We don't know where we are relative to the presentation engine's + // display's refresh cycle. We also don't know how long rendering + // takes. Let's make a grossly-simplified assumption that the + // desiredPresentTime should be half way between now and + // now+target_IPD. We will adjust over time. + uint64_t curtime = getTimeInNanoseconds(); + if (curtime == 0) { + // Since we didn't find out the current time, don't give a + // desiredPresentTime: + ptime.desiredPresentTime = 0; + } else { + ptime.desiredPresentTime = curtime + (demo->target_IPD >> 1); + } + } else { + ptime.desiredPresentTime = (demo->prev_desired_present_time + demo->target_IPD); + } + ptime.presentID = demo->next_present_id++; + demo->prev_desired_present_time = ptime.desiredPresentTime; + + VkPresentTimesInfoGOOGLE present_time = { + .sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE, + .pNext = present.pNext, + .swapchainCount = present.swapchainCount, + .pTimes = &ptime, + }; + if (demo->VK_GOOGLE_display_timing_enabled) { + present.pNext = &present_time; + } + } + + err = vkQueuePresentKHR(demo->present_queue, &present); + demo->frame_index += 1; + demo->frame_index %= FRAME_LAG; + demo->first_swapchain_frame = false; + + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + // demo->swapchain is out of date (e.g. the window was resized) and + // must be recreated: + demo_resize(demo); + } else if (err == VK_SUBOPTIMAL_KHR) { + // SUBOPTIMAL could be due to a resize + VkSurfaceCapabilitiesKHR surfCapabilities; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(demo->gpu, demo->surface, &surfCapabilities); + assert(!err); + if (surfCapabilities.currentExtent.width != (uint32_t)demo->width || + surfCapabilities.currentExtent.height != (uint32_t)demo->height) { + demo_resize(demo); + } + } else if (err == VK_ERROR_SURFACE_LOST_KHR) { + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + demo_create_surface(demo); + demo_resize(demo); + } else { + assert(!err); + } +} + +static void demo_prepare_buffers(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + VkSwapchainKHR oldSwapchain = demo->swapchain; + + // Check the surface capabilities and formats + VkSurfaceCapabilitiesKHR surfCapabilities; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(demo->gpu, demo->surface, &surfCapabilities); + assert(!err); + + uint32_t presentModeCount; + err = vkGetPhysicalDeviceSurfacePresentModesKHR(demo->gpu, demo->surface, &presentModeCount, NULL); + assert(!err); + VkPresentModeKHR *presentModes = (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR)); + assert(presentModes); + err = vkGetPhysicalDeviceSurfacePresentModesKHR(demo->gpu, demo->surface, &presentModeCount, presentModes); + assert(!err); + + VkExtent2D swapchainExtent; + // width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF. + if (surfCapabilities.currentExtent.width == 0xFFFFFFFF) { + // If the surface size is undefined, the size is set to the size + // of the images requested, which must fit within the minimum and + // maximum values. + swapchainExtent.width = demo->width; + swapchainExtent.height = demo->height; + + if (swapchainExtent.width < surfCapabilities.minImageExtent.width) { + swapchainExtent.width = surfCapabilities.minImageExtent.width; + } else if (swapchainExtent.width > surfCapabilities.maxImageExtent.width) { + swapchainExtent.width = surfCapabilities.maxImageExtent.width; + } + + if (swapchainExtent.height < surfCapabilities.minImageExtent.height) { + swapchainExtent.height = surfCapabilities.minImageExtent.height; + } else if (swapchainExtent.height > surfCapabilities.maxImageExtent.height) { + swapchainExtent.height = surfCapabilities.maxImageExtent.height; + } + } else { + // If the surface size is defined, the swap chain size must match + swapchainExtent = surfCapabilities.currentExtent; + demo->width = surfCapabilities.currentExtent.width; + demo->height = surfCapabilities.currentExtent.height; + } + + if (surfCapabilities.maxImageExtent.width == 0 || surfCapabilities.maxImageExtent.height == 0) { + demo->is_minimized = true; + return; + } else { + demo->is_minimized = false; + } + + // The FIFO present mode is guaranteed by the spec to be supported + // and to have no tearing. It's a great default present mode to use. + VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; + + // There are times when you may wish to use another present mode. The + // following code shows how to select them, and the comments provide some + // reasons you may wish to use them. + // + // It should be noted that Vulkan 1.0 doesn't provide a method for + // synchronizing rendering with the presentation engine's display. There + // is a method provided for throttling rendering with the display, but + // there are some presentation engines for which this method will not work. + // If an application doesn't throttle its rendering, and if it renders much + // faster than the refresh rate of the display, this can waste power on + // mobile devices. That is because power is being spent rendering images + // that may never be seen. + + // VK_PRESENT_MODE_IMMEDIATE_KHR is for applications that don't care about + // tearing, or have some way of synchronizing their rendering with the + // display. + // VK_PRESENT_MODE_MAILBOX_KHR may be useful for applications that + // generally render a new presentable image every refresh cycle, but are + // occasionally early. In this case, the application wants the new image + // to be displayed instead of the previously-queued-for-presentation image + // that has not yet been displayed. + // VK_PRESENT_MODE_FIFO_RELAXED_KHR is for applications that generally + // render a new presentable image every refresh cycle, but are occasionally + // late. In this case (perhaps because of stuttering/latency concerns), + // the application wants the late image to be immediately displayed, even + // though that may mean some tearing. + + if (demo->presentMode != swapchainPresentMode) { + for (size_t i = 0; i < presentModeCount; ++i) { + if (presentModes[i] == demo->presentMode) { + swapchainPresentMode = demo->presentMode; + break; + } + } + } + if (swapchainPresentMode != demo->presentMode) { + ERR_EXIT("Present mode specified is not supported\n", "Present mode unsupported"); + } + + // Determine the number of VkImages to use in the swap chain. + // Application desires to acquire 3 images at a time for triple + // buffering + uint32_t desiredNumOfSwapchainImages = 3; + if (desiredNumOfSwapchainImages < surfCapabilities.minImageCount) { + desiredNumOfSwapchainImages = surfCapabilities.minImageCount; + } + // If maxImageCount is 0, we can ask for as many images as we want; + // otherwise we're limited to maxImageCount + if ((surfCapabilities.maxImageCount > 0) && (desiredNumOfSwapchainImages > surfCapabilities.maxImageCount)) { + // Application must settle for fewer images than desired: + desiredNumOfSwapchainImages = surfCapabilities.maxImageCount; + } + + VkSurfaceTransformFlagsKHR preTransform; + if (surfCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { + preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + } else { + preTransform = surfCapabilities.currentTransform; + } + + // Find a supported composite alpha mode - one of these is guaranteed to be set + VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + VkCompositeAlphaFlagBitsKHR compositeAlphaFlags[4] = { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + }; + for (uint32_t i = 0; i < ARRAY_SIZE(compositeAlphaFlags); i++) { + if (surfCapabilities.supportedCompositeAlpha & compositeAlphaFlags[i]) { + compositeAlpha = compositeAlphaFlags[i]; + break; + } + } + + VkSwapchainCreateInfoKHR swapchain_ci = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = NULL, + .surface = demo->surface, + .minImageCount = desiredNumOfSwapchainImages, + .imageFormat = demo->format, + .imageColorSpace = demo->color_space, + .imageExtent = + { + .width = swapchainExtent.width, + .height = swapchainExtent.height, + }, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .preTransform = preTransform, + .compositeAlpha = compositeAlpha, + .imageArrayLayers = 1, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + .presentMode = swapchainPresentMode, + .oldSwapchain = oldSwapchain, + .clipped = true, + }; + uint32_t i; + err = vkCreateSwapchainKHR(demo->device, &swapchain_ci, NULL, &demo->swapchain); + assert(!err); + + // If we just re-created an existing swapchain, we should destroy the old + // swapchain at this point. + // Note: destroying the swapchain also cleans up all its associated + // presentable images once the platform is done with them. + if (oldSwapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(demo->device, oldSwapchain, NULL); + } + + err = vkGetSwapchainImagesKHR(demo->device, demo->swapchain, &demo->swapchainImageCount, NULL); + assert(!err); + + VkImage *swapchainImages = (VkImage *)malloc(demo->swapchainImageCount * sizeof(VkImage)); + assert(swapchainImages); + err = vkGetSwapchainImagesKHR(demo->device, demo->swapchain, &demo->swapchainImageCount, swapchainImages); + assert(!err); + + demo->swapchain_image_resources = + (SwapchainImageResources *)malloc(sizeof(SwapchainImageResources) * demo->swapchainImageCount); + assert(demo->swapchain_image_resources); + + for (i = 0; i < demo->swapchainImageCount; i++) { + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)swapchainImages[i], "SwapchainImage(%u)", i); + } + for (i = 0; i < demo->swapchainImageCount; i++) { + VkImageViewCreateInfo color_image_view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .format = demo->format, + .components = + { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = + {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .flags = 0, + }; + + demo->swapchain_image_resources[i].image = swapchainImages[i]; + + color_image_view.image = demo->swapchain_image_resources[i].image; + + err = vkCreateImageView(demo->device, &color_image_view, NULL, &demo->swapchain_image_resources[i].view); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->swapchain_image_resources[i].view, "SwapchainView(%u)", + i); + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + VkRefreshCycleDurationGOOGLE rc_dur; + err = vkGetRefreshCycleDurationGOOGLE(demo->device, demo->swapchain, &rc_dur); + assert(!err); + demo->refresh_duration = rc_dur.refreshDuration; + + demo->syncd_with_actual_presents = false; + // Initially target 1X the refresh duration: + demo->target_IPD = demo->refresh_duration; + demo->refresh_duration_multiplier = 1; + demo->prev_desired_present_time = 0; + demo->next_present_id = 1; + } + + if (NULL != swapchainImages) { + free(swapchainImages); + } + + if (NULL != presentModes) { + free(presentModes); + } +} + +static void demo_prepare_depth(struct demo *demo) { + const VkFormat depth_format = VK_FORMAT_D16_UNORM; + const VkImageCreateInfo image = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .imageType = VK_IMAGE_TYPE_2D, + .format = depth_format, + .extent = {demo->width, demo->height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .flags = 0, + }; + + VkImageViewCreateInfo view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .image = VK_NULL_HANDLE, + .format = depth_format, + .subresourceRange = + {.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}, + .flags = 0, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + }; + + if (demo->force_errors) { + // Intentionally force a bad pNext value to generate a validation layer error + view.pNext = ℑ + } + + VkMemoryRequirements mem_reqs; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + demo->depth.format = depth_format; + + /* create image */ + err = vkCreateImage(demo->device, &image, NULL, &demo->depth.image); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)demo->depth.image, "DepthImage"); + + vkGetImageMemoryRequirements(demo->device, demo->depth.image, &mem_reqs); + assert(!err); + + demo->depth.mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + demo->depth.mem_alloc.pNext = NULL; + demo->depth.mem_alloc.allocationSize = mem_reqs.size; + demo->depth.mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &demo->depth.mem_alloc.memoryTypeIndex); + assert(pass); + + /* allocate memory */ + err = vkAllocateMemory(demo->device, &demo->depth.mem_alloc, NULL, &demo->depth.mem); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->depth.mem, "DepthMem"); + + /* bind memory */ + err = vkBindImageMemory(demo->device, demo->depth.image, demo->depth.mem, 0); + assert(!err); + + /* create image view */ + view.image = demo->depth.image; + err = vkCreateImageView(demo->device, &view, NULL, &demo->depth.view); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->depth.view, "DepthView"); +} + +/* Convert ppm image data from header file into RGBA texture image */ +#include "lunarg.ppm.h" +bool loadTexture(const char *filename, uint8_t *rgba_data, VkSubresourceLayout *layout, int32_t *width, int32_t *height) { + (void)filename; + char *cPtr; + cPtr = (char *)lunarg_ppm; + if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "P6\n", 3)) { + return false; + } + while (strncmp(cPtr++, "\n", 1)) + ; + sscanf(cPtr, "%u %u", width, height); + if (rgba_data == NULL) { + return true; + } + while (strncmp(cPtr++, "\n", 1)) + ; + if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "255\n", 4)) { + return false; + } + while (strncmp(cPtr++, "\n", 1)) + ; + for (int y = 0; y < *height; y++) { + uint8_t *rowPtr = rgba_data; + for (int x = 0; x < *width; x++) { + memcpy(rowPtr, cPtr, 3); + rowPtr[3] = 255; /* Alpha of 1 */ + rowPtr += 4; + cPtr += 3; + } + rgba_data += layout->rowPitch; + } + return true; +} + +static void demo_prepare_texture_buffer(struct demo *demo, const char *filename, struct texture_object *tex_obj) { + int32_t tex_width; + int32_t tex_height; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + if (!loadTexture(filename, NULL, NULL, &tex_width, &tex_height)) { + ERR_EXIT("Failed to load textures", "Load Texture Failure"); + } + + tex_obj->tex_width = tex_width; + tex_obj->tex_height = tex_height; + + const VkBufferCreateInfo buffer_create_info = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .size = tex_width * tex_height * 4, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL}; + + err = vkCreateBuffer(demo->device, &buffer_create_info, NULL, &tex_obj->buffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)tex_obj->buffer, "TexBuffer(%s)", filename); + + VkMemoryRequirements mem_reqs; + vkGetBufferMemoryRequirements(demo->device, tex_obj->buffer, &mem_reqs); + + tex_obj->mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + tex_obj->mem_alloc.pNext = NULL; + tex_obj->mem_alloc.allocationSize = mem_reqs.size; + tex_obj->mem_alloc.memoryTypeIndex = 0; + + VkFlags requirements = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, requirements, &tex_obj->mem_alloc.memoryTypeIndex); + assert(pass); + + err = vkAllocateMemory(demo->device, &tex_obj->mem_alloc, NULL, &(tex_obj->mem)); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)tex_obj->mem, "TexBufMemory(%s)", filename); + + /* bind memory */ + err = vkBindBufferMemory(demo->device, tex_obj->buffer, tex_obj->mem, 0); + assert(!err); + + VkSubresourceLayout layout; + memset(&layout, 0, sizeof(layout)); + layout.rowPitch = tex_width * 4; + + void *data; + err = vkMapMemory(demo->device, tex_obj->mem, 0, tex_obj->mem_alloc.allocationSize, 0, &data); + assert(!err); + + if (!loadTexture(filename, data, &layout, &tex_width, &tex_height)) { + fprintf(stderr, "Error loading texture: %s\n", filename); + } + + vkUnmapMemory(demo->device, tex_obj->mem); +} + +static void demo_prepare_texture_image(struct demo *demo, const char *filename, struct texture_object *tex_obj, + VkImageTiling tiling, VkImageUsageFlags usage, VkFlags required_props) { + const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM; + int32_t tex_width; + int32_t tex_height; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + if (!loadTexture(filename, NULL, NULL, &tex_width, &tex_height)) { + ERR_EXIT("Failed to load textures", "Load Texture Failure"); + } + + tex_obj->tex_width = tex_width; + tex_obj->tex_height = tex_height; + + const VkImageCreateInfo image_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .imageType = VK_IMAGE_TYPE_2D, + .format = tex_format, + .extent = {tex_width, tex_height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = tiling, + .usage = usage, + .flags = 0, + .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, + }; + + VkMemoryRequirements mem_reqs; + + err = vkCreateImage(demo->device, &image_create_info, NULL, &tex_obj->image); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)tex_obj->image, "TexImage(%s)", filename); + + vkGetImageMemoryRequirements(demo->device, tex_obj->image, &mem_reqs); + + tex_obj->mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + tex_obj->mem_alloc.pNext = NULL; + tex_obj->mem_alloc.allocationSize = mem_reqs.size; + tex_obj->mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, required_props, &tex_obj->mem_alloc.memoryTypeIndex); + assert(pass); + + /* allocate memory */ + err = vkAllocateMemory(demo->device, &tex_obj->mem_alloc, NULL, &(tex_obj->mem)); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)tex_obj->mem, "TexImageMem(%s)", filename); + + /* bind memory */ + err = vkBindImageMemory(demo->device, tex_obj->image, tex_obj->mem, 0); + assert(!err); + + if (required_props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { + const VkImageSubresource subres = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .arrayLayer = 0, + }; + VkSubresourceLayout layout; + void *data; + + vkGetImageSubresourceLayout(demo->device, tex_obj->image, &subres, &layout); + + err = vkMapMemory(demo->device, tex_obj->mem, 0, tex_obj->mem_alloc.allocationSize, 0, &data); + assert(!err); + + if (!loadTexture(filename, data, &layout, &tex_width, &tex_height)) { + fprintf(stderr, "Error loading texture: %s\n", filename); + } + + vkUnmapMemory(demo->device, tex_obj->mem); + } + + tex_obj->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +} + +static void demo_destroy_texture(struct demo *demo, struct texture_object *tex_objs) { + /* clean up staging resources */ + vkFreeMemory(demo->device, tex_objs->mem, NULL); + if (tex_objs->image) vkDestroyImage(demo->device, tex_objs->image, NULL); + if (tex_objs->buffer) vkDestroyBuffer(demo->device, tex_objs->buffer, NULL); +} + +static void demo_prepare_textures(struct demo *demo) { + const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM; + VkFormatProperties props; + uint32_t i; + + vkGetPhysicalDeviceFormatProperties(demo->gpu, tex_format, &props); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + VkResult U_ASSERT_ONLY err; + + if ((props.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) && !demo->use_staging_buffer) { + demo_push_cb_label(demo, demo->cmd, NULL, "DirectTexture(%u)", i); + /* Device can texture using linear textures */ + demo_prepare_texture_image(demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + // Nothing in the pipeline needs to be complete to start, and don't allow fragment + // shader to run until layout transition completes + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, + demo->textures[i].imageLayout, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + demo->staging_texture.image = 0; + demo_pop_cb_label(demo, demo->cmd); // "DirectTexture" + } else if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) { + /* Must use staging buffer to copy linear texture to optimized */ + demo_push_cb_label(demo, demo->cmd, NULL, "StagingTexture(%u)", i); + + memset(&demo->staging_texture, 0, sizeof(demo->staging_texture)); + demo_prepare_texture_buffer(demo, tex_files[i], &demo->staging_texture); + + demo_prepare_texture_image(demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_OPTIMAL, + (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + demo_push_cb_label(demo, demo->cmd, NULL, "StagingBufferCopy(%u)", i); + + VkBufferImageCopy copy_region = { + .bufferOffset = 0, + .bufferRowLength = demo->staging_texture.tex_width, + .bufferImageHeight = demo->staging_texture.tex_height, + .imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {demo->staging_texture.tex_width, demo->staging_texture.tex_height, 1}, + }; + + vkCmdCopyBufferToImage(demo->cmd, demo->staging_texture.buffer, demo->textures[i].image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + demo_pop_cb_label(demo, demo->cmd); // "StagingBufferCopy" + + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + demo->textures[i].imageLayout, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + demo_pop_cb_label(demo, demo->cmd); // "StagingTexture" + + } else { + /* Can't support VK_FORMAT_R8G8B8A8_UNORM !? */ + assert(!"No support for R8G8B8A8_UNORM as texture image format"); + } + + const VkSamplerCreateInfo sampler = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = NULL, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 1, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, + .unnormalizedCoordinates = VK_FALSE, + }; + + VkImageViewCreateInfo view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .image = VK_NULL_HANDLE, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = tex_format, + .components = + { + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + .flags = 0, + }; + + /* create sampler */ + err = vkCreateSampler(demo->device, &sampler, NULL, &demo->textures[i].sampler); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SAMPLER, (uint64_t)demo->textures[i].sampler, "Sampler(%u)", i); + + /* create image view */ + view.image = demo->textures[i].image; + err = vkCreateImageView(demo->device, &view, NULL, &demo->textures[i].view); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->textures[i].view, "TexImageView(%u)", i); + assert(!err); + } +} + +void demo_prepare_cube_data_buffers(struct demo *demo) { + VkBufferCreateInfo buf_info; + VkMemoryRequirements mem_reqs; + VkMemoryAllocateInfo mem_alloc; + mat4x4 MVP, VP; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + struct vktexcube_vs_uniform data; + + mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix); + mat4x4_mul(MVP, VP, demo->model_matrix); + memcpy(data.mvp, MVP, sizeof(MVP)); + // dumpMatrix("MVP", MVP); + + for (unsigned int i = 0; i < 12 * 3; i++) { + data.position[i][0] = g_vertex_buffer_data[i * 3]; + data.position[i][1] = g_vertex_buffer_data[i * 3 + 1]; + data.position[i][2] = g_vertex_buffer_data[i * 3 + 2]; + data.position[i][3] = 1.0f; + data.attr[i][0] = g_uv_buffer_data[2 * i]; + data.attr[i][1] = g_uv_buffer_data[2 * i + 1]; + data.attr[i][2] = 0; + data.attr[i][3] = 0; + } + + memset(&buf_info, 0, sizeof(buf_info)); + buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + buf_info.size = sizeof(data); + + for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { + err = vkCreateBuffer(demo->device, &buf_info, NULL, &demo->swapchain_image_resources[i].uniform_buffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)demo->swapchain_image_resources[i].uniform_buffer, + "SwapchainUniformBuf(%u)", i); + + vkGetBufferMemoryRequirements(demo->device, demo->swapchain_image_resources[i].uniform_buffer, &mem_reqs); + + mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + mem_alloc.pNext = NULL; + mem_alloc.allocationSize = mem_reqs.size; + mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &mem_alloc.memoryTypeIndex); + assert(pass); + + err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &demo->swapchain_image_resources[i].uniform_memory); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->swapchain_image_resources[i].uniform_memory, + "SwapchainUniformMem(%u)", i); + + err = vkMapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, 0, VK_WHOLE_SIZE, 0, + &demo->swapchain_image_resources[i].uniform_memory_ptr); + assert(!err); + + memcpy(demo->swapchain_image_resources[i].uniform_memory_ptr, &data, sizeof data); + + err = vkBindBufferMemory(demo->device, demo->swapchain_image_resources[i].uniform_buffer, + demo->swapchain_image_resources[i].uniform_memory, 0); + assert(!err); + } +} + +static void demo_prepare_descriptor_layout(struct demo *demo) { + const VkDescriptorSetLayoutBinding layout_bindings[3] = { + [0] = + { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .pImmutableSamplers = NULL, + }, + [1] = + { + .binding = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = NULL, + }, + [2] = + { + .binding = 2, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = NULL, + }, + }; + const VkDescriptorSetLayoutCreateInfo descriptor_layout = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = NULL, + .bindingCount = 3, + .pBindings = layout_bindings, + }; + VkResult U_ASSERT_ONLY err; + + err = vkCreateDescriptorSetLayout(demo->device, &descriptor_layout, NULL, &demo->desc_layout); + assert(!err); + + const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = NULL, + .setLayoutCount = 1, + .pSetLayouts = &demo->desc_layout, + }; + + err = vkCreatePipelineLayout(demo->device, &pPipelineLayoutCreateInfo, NULL, &demo->pipeline_layout); + assert(!err); +} + +static void demo_prepare_render_pass(struct demo *demo) { + // The initial layout for the color and depth attachments will be LAYOUT_UNDEFINED + // because at the start of the renderpass, we don't care about their contents. + // At the start of the subpass, the color attachment's layout will be transitioned + // to LAYOUT_COLOR_ATTACHMENT_OPTIMAL and the depth stencil attachment's layout + // will be transitioned to LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL. At the end of + // the renderpass, the color attachment's layout will be transitioned to + // LAYOUT_PRESENT_SRC_KHR to be ready to present. This is all done as part of + // the renderpass, no barriers are necessary. + const VkAttachmentDescription attachments[2] = { + [0] = + { + .format = demo->format, + .flags = 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_PRESENT_SRC_KHR, + }, + [1] = + { + .format = demo->depth.format, + .flags = 0, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .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, + }, + }; + const VkAttachmentReference color_reference = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + const VkAttachmentReference depth_reference = { + .attachment = 1, + .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }; + const VkSubpassDescription subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .flags = 0, + .inputAttachmentCount = 0, + .pInputAttachments = NULL, + .colorAttachmentCount = 1, + .pColorAttachments = &color_reference, + .pResolveAttachments = NULL, + .pDepthStencilAttachment = &depth_reference, + .preserveAttachmentCount = 0, + .pPreserveAttachments = NULL, + }; + + VkSubpassDependency attachmentDependencies[2] = { + [0] = + { + // Depth buffer is shared between swapchain images + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0, + }, + [1] = + { + // Image Layout Transition + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + .dependencyFlags = 0, + }, + }; + + const VkRenderPassCreateInfo rp_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .attachmentCount = 2, + .pAttachments = attachments, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 2, + .pDependencies = attachmentDependencies, + }; + VkResult U_ASSERT_ONLY err; + + err = vkCreateRenderPass(demo->device, &rp_info, NULL, &demo->render_pass); + assert(!err); +} + +static VkShaderModule demo_prepare_shader_module(const char *name, struct demo *demo, const uint32_t *code, size_t size) { + VkShaderModule module; + VkShaderModuleCreateInfo moduleCreateInfo; + VkResult U_ASSERT_ONLY err; + + moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + moduleCreateInfo.pNext = NULL; + moduleCreateInfo.flags = 0; + moduleCreateInfo.codeSize = size; + moduleCreateInfo.pCode = code; + + err = vkCreateShaderModule(demo->device, &moduleCreateInfo, NULL, &module); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)module, "%s", name); + + return module; +} + +static void demo_prepare_vs(struct demo *demo) { + const uint32_t vs_code[] = { +#include "cube-nc.vert.inc" + }; + demo->vert_shader_module = demo_prepare_shader_module("cube-nc.vert", demo, vs_code, sizeof(vs_code)); +} + +static void demo_prepare_fs(struct demo *demo) { + const uint32_t fs_code[] = { +#include "cube-nc.frag.inc" + }; + demo->frag_shader_module = demo_prepare_shader_module("cube-nc.frag", demo, fs_code, sizeof(fs_code)); +} + +static void demo_prepare_pipeline(struct demo *demo) { +#define NUM_DYNAMIC_STATES 2 /*Viewport + Scissor*/ + + VkGraphicsPipelineCreateInfo pipeline; + VkPipelineCacheCreateInfo pipelineCache; + VkPipelineVertexInputStateCreateInfo vi; + VkPipelineInputAssemblyStateCreateInfo ia; + VkPipelineRasterizationStateCreateInfo rs; + VkPipelineColorBlendStateCreateInfo cb; + VkPipelineDepthStencilStateCreateInfo ds; + VkPipelineViewportStateCreateInfo vp; + VkPipelineMultisampleStateCreateInfo ms; + VkDynamicState dynamicStateEnables[NUM_DYNAMIC_STATES]; + VkPipelineDynamicStateCreateInfo dynamicState; + VkResult U_ASSERT_ONLY err; + + memset(dynamicStateEnables, 0, sizeof dynamicStateEnables); + memset(&dynamicState, 0, sizeof dynamicState); + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.pDynamicStates = dynamicStateEnables; + + memset(&pipeline, 0, sizeof(pipeline)); + pipeline.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline.layout = demo->pipeline_layout; + + memset(&vi, 0, sizeof(vi)); + vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + memset(&ia, 0, sizeof(ia)); + ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + memset(&rs, 0, sizeof(rs)); + rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rs.polygonMode = VK_POLYGON_MODE_FILL; + rs.cullMode = VK_CULL_MODE_BACK_BIT; + rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rs.depthClampEnable = VK_FALSE; + rs.rasterizerDiscardEnable = VK_FALSE; + rs.depthBiasEnable = VK_FALSE; + rs.lineWidth = 1.0f; + + memset(&cb, 0, sizeof(cb)); + cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + VkPipelineColorBlendAttachmentState att_state[1]; + memset(att_state, 0, sizeof(att_state)); + att_state[0].colorWriteMask = 0xf; + att_state[0].blendEnable = VK_FALSE; + cb.attachmentCount = 1; + cb.pAttachments = att_state; + + memset(&vp, 0, sizeof(vp)); + vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + vp.viewportCount = 1; + dynamicStateEnables[dynamicState.dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT; + vp.scissorCount = 1; + dynamicStateEnables[dynamicState.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; + + memset(&ds, 0, sizeof(ds)); + ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + ds.depthTestEnable = VK_TRUE; + ds.depthWriteEnable = VK_TRUE; + ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; + ds.depthBoundsTestEnable = VK_FALSE; + ds.back.failOp = VK_STENCIL_OP_KEEP; + ds.back.passOp = VK_STENCIL_OP_KEEP; + ds.back.compareOp = VK_COMPARE_OP_ALWAYS; + ds.stencilTestEnable = VK_FALSE; + ds.front = ds.back; + + memset(&ms, 0, sizeof(ms)); + ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms.pSampleMask = NULL; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + demo_prepare_vs(demo); + demo_prepare_fs(demo); + + // Two stages: vs and fs + VkPipelineShaderStageCreateInfo shaderStages[2]; + memset(&shaderStages, 0, 2 * sizeof(VkPipelineShaderStageCreateInfo)); + + shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + shaderStages[0].module = demo->vert_shader_module; + 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].module = demo->frag_shader_module; + shaderStages[1].pName = "main"; + + memset(&pipelineCache, 0, sizeof(pipelineCache)); + pipelineCache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + + err = vkCreatePipelineCache(demo->device, &pipelineCache, NULL, &demo->pipelineCache); + assert(!err); + + pipeline.pVertexInputState = &vi; + pipeline.pInputAssemblyState = &ia; + pipeline.pRasterizationState = &rs; + pipeline.pColorBlendState = &cb; + pipeline.pMultisampleState = &ms; + pipeline.pViewportState = &vp; + pipeline.pDepthStencilState = &ds; + pipeline.stageCount = ARRAY_SIZE(shaderStages); + pipeline.pStages = shaderStages; + pipeline.renderPass = demo->render_pass; + pipeline.pDynamicState = &dynamicState; + + err = vkCreateGraphicsPipelines(demo->device, demo->pipelineCache, 1, &pipeline, NULL, &demo->pipeline); + assert(!err); + + vkDestroyShaderModule(demo->device, demo->frag_shader_module, NULL); + vkDestroyShaderModule(demo->device, demo->vert_shader_module, NULL); +} + +static void demo_prepare_descriptor_pool(struct demo *demo) { + const VkDescriptorPoolSize type_counts[3] = { + [0] = + { + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = demo->swapchainImageCount, + }, + [1] = + { + .type = VK_DESCRIPTOR_TYPE_SAMPLER, + .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, + }, + [2] = + { + .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, + }, + }; + const VkDescriptorPoolCreateInfo descriptor_pool = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .pNext = NULL, + .maxSets = demo->swapchainImageCount, + .poolSizeCount = 3, + .pPoolSizes = type_counts, + }; + VkResult U_ASSERT_ONLY err; + + err = vkCreateDescriptorPool(demo->device, &descriptor_pool, NULL, &demo->desc_pool); + assert(!err); +} + +static void demo_prepare_descriptor_set(struct demo *demo) { + VkDescriptorImageInfo tex_descs[DEMO_TEXTURE_COUNT]; + VkWriteDescriptorSet writes[3]; + VkResult U_ASSERT_ONLY err; + + VkDescriptorSetAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .pNext = NULL, + .descriptorPool = demo->desc_pool, + .descriptorSetCount = 1, + .pSetLayouts = &demo->desc_layout}; + + VkDescriptorBufferInfo buffer_info; + buffer_info.offset = 0; + buffer_info.range = sizeof(struct vktexcube_vs_uniform); + + memset(&tex_descs, 0, sizeof(tex_descs)); + for (unsigned int i = 0; i < DEMO_TEXTURE_COUNT; i++) { + tex_descs[i].sampler = demo->textures[i].sampler; + tex_descs[i].imageView = demo->textures[i].view; + tex_descs[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + + memset(&writes, 0, sizeof(writes)); + + writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[0].descriptorCount = 1; + writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + writes[0].pBufferInfo = &buffer_info; + + writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[1].dstBinding = 1; + writes[1].descriptorCount = 1; + writes[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + writes[1].dstArrayElement = 0; + writes[1].pImageInfo = tex_descs; + + writes[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[2].dstBinding = 2; + writes[2].descriptorCount = 1; + writes[2].dstArrayElement = 0; + writes[2].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + writes[2].pImageInfo = tex_descs; + + for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->swapchain_image_resources[i].descriptor_set); + assert(!err); + buffer_info.buffer = demo->swapchain_image_resources[i].uniform_buffer; + writes[0].dstSet = demo->swapchain_image_resources[i].descriptor_set; + writes[1].dstSet = demo->swapchain_image_resources[i].descriptor_set; + writes[2].dstSet = demo->swapchain_image_resources[i].descriptor_set; + vkUpdateDescriptorSets(demo->device, 3, writes, 0, NULL); + } +} + +static void demo_prepare_framebuffers(struct demo *demo) { + VkImageView attachments[2]; + attachments[1] = demo->depth.view; + + const VkFramebufferCreateInfo fb_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = NULL, + .renderPass = demo->render_pass, + .attachmentCount = 2, + .pAttachments = attachments, + .width = demo->width, + .height = demo->height, + .layers = 1, + }; + VkResult U_ASSERT_ONLY err; + uint32_t i; + + for (i = 0; i < demo->swapchainImageCount; i++) { + attachments[0] = demo->swapchain_image_resources[i].view; + err = vkCreateFramebuffer(demo->device, &fb_info, NULL, &demo->swapchain_image_resources[i].framebuffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_FRAMEBUFFER, (uint64_t)demo->swapchain_image_resources[i].framebuffer, + "Framebuffer(%u)", i); + } +} + +static void demo_prepare(struct demo *demo) { + demo_prepare_buffers(demo); + + if (demo->is_minimized) { + demo->prepared = false; + return; + } + + VkResult U_ASSERT_ONLY err; + if (demo->cmd_pool == VK_NULL_HANDLE) { + const VkCommandPoolCreateInfo cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .queueFamilyIndex = demo->graphics_queue_family_index, + .flags = 0, + }; + err = vkCreateCommandPool(demo->device, &cmd_pool_info, NULL, &demo->cmd_pool); + assert(!err); + } + + const VkCommandBufferAllocateInfo cmd = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = demo->cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->cmd); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)demo->cmd, "PrepareCB"); + VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = 0, + .pInheritanceInfo = NULL, + }; + err = vkBeginCommandBuffer(demo->cmd, &cmd_buf_info); + demo_push_cb_label(demo, demo->cmd, NULL, "Prepare"); + assert(!err); + + demo_prepare_depth(demo); + demo_prepare_textures(demo); + demo_prepare_cube_data_buffers(demo); + + demo_prepare_descriptor_layout(demo); + demo_prepare_render_pass(demo); + demo_prepare_pipeline(demo); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->swapchain_image_resources[i].cmd); + assert(!err); + } + + if (demo->separate_present_queue) { + const VkCommandPoolCreateInfo present_cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .queueFamilyIndex = demo->present_queue_family_index, + .flags = 0, + }; + err = vkCreateCommandPool(demo->device, &present_cmd_pool_info, NULL, &demo->present_cmd_pool); + assert(!err); + const VkCommandBufferAllocateInfo present_cmd_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = demo->present_cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateCommandBuffers(demo->device, &present_cmd_info, + &demo->swapchain_image_resources[i].graphics_to_present_cmd); + assert(!err); + demo_build_image_ownership_cmd(demo, i); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, + (uint64_t)demo->swapchain_image_resources[i].graphics_to_present_cmd, "GfxToPresent(%u)", i); + } + } + + demo_prepare_descriptor_pool(demo); + demo_prepare_descriptor_set(demo); + + demo_prepare_framebuffers(demo); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + demo->current_buffer = i; + demo_draw_build_cmd(demo, demo->swapchain_image_resources[i].cmd); + } + + /* + * Prepare functions above may generate pipeline commands + * that need to be flushed before beginning the render loop. + */ + demo_pop_cb_label(demo, demo->cmd); // "Prepare" + demo_flush_init_cmd(demo); + if (demo->staging_texture.buffer) { + demo_destroy_texture(demo, &demo->staging_texture); + } + + demo->current_buffer = 0; + demo->prepared = true; + demo->first_swapchain_frame = true; +} + +static void demo_cleanup(struct demo *demo) { + uint32_t i; + + demo->prepared = false; + vkDeviceWaitIdle(demo->device); + + // Wait for fences from present operations + for (i = 0; i < FRAME_LAG; i++) { + vkWaitForFences(demo->device, 1, &demo->fences[i], VK_TRUE, UINT64_MAX); + vkDestroyFence(demo->device, demo->fences[i], NULL); + vkDestroySemaphore(demo->device, demo->image_acquired_semaphores[i], NULL); + vkDestroySemaphore(demo->device, demo->draw_complete_semaphores[i], NULL); + if (demo->separate_present_queue) { + vkDestroySemaphore(demo->device, demo->image_ownership_semaphores[i], NULL); + } + } + + // If the window is currently minimized, demo_resize has already done some cleanup for us. + if (!demo->is_minimized) { + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); + } + vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); + + vkDestroyPipeline(demo->device, demo->pipeline, NULL); + vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL); + vkDestroyRenderPass(demo->device, demo->render_pass, NULL); + vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL); + vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + vkDestroyImageView(demo->device, demo->textures[i].view, NULL); + vkDestroyImage(demo->device, demo->textures[i].image, NULL); + vkFreeMemory(demo->device, demo->textures[i].mem, NULL); + vkDestroySampler(demo->device, demo->textures[i].sampler, NULL); + } + vkDestroySwapchainKHR(demo->device, demo->swapchain, NULL); + + vkDestroyImageView(demo->device, demo->depth.view, NULL); + vkDestroyImage(demo->device, demo->depth.image, NULL); + vkFreeMemory(demo->device, demo->depth.mem, NULL); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); + vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); + vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); + vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); + } + free(demo->swapchain_image_resources); + free(demo->queue_props); + vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); + + if (demo->separate_present_queue) { + vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL); + } + } + vkDeviceWaitIdle(demo->device); + vkDestroyDevice(demo->device, NULL); + if (demo->validate) { + vkDestroyDebugUtilsMessengerEXT(demo->inst, demo->dbg_messenger, NULL); + } + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + +#if defined(VK_USE_PLATFORM_XLIB_KHR) + XDestroyWindow(demo->display, demo->xlib_window); + XCloseDisplay(demo->display); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + xcb_destroy_window(demo->connection, demo->xcb_window); + xcb_disconnect(demo->connection); + free(demo->atom_wm_delete_window); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (demo->keyboard) wl_keyboard_destroy(demo->keyboard); + if (demo->pointer) wl_pointer_destroy(demo->pointer); + if (demo->seat) wl_seat_destroy(demo->seat); + xdg_toplevel_destroy(demo->xdg_toplevel); + xdg_surface_destroy(demo->xdg_surface); + wl_surface_destroy(demo->window); + xdg_wm_base_destroy(demo->xdg_wm_base); + if (demo->xdg_decoration_mgr) { + zxdg_toplevel_decoration_v1_destroy(demo->toplevel_decoration); + zxdg_decoration_manager_v1_destroy(demo->xdg_decoration_mgr); + } + wl_compositor_destroy(demo->compositor); + wl_registry_destroy(demo->registry); + wl_display_disconnect(demo->display); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo->event_buffer->Release(demo->event_buffer); + demo->window->Release(demo->window); + demo->dfb->Release(demo->dfb); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + screen_destroy_event(demo->screen_event); + screen_destroy_window(demo->screen_window); + screen_destroy_context(demo->screen_context); +#endif + + vkDestroyInstance(demo->inst, NULL); +} + +static void demo_resize(struct demo *demo) { + uint32_t i; + + // Don't react to resize until after first initialization. + if (!demo->prepared) { + if (demo->is_minimized) { + demo_prepare(demo); + } + return; + } + // In order to properly resize the window, we must re-create the swapchain + // AND redo the command buffers, etc. + // + // First, perform part of the demo_cleanup() function: + demo->prepared = false; + vkDeviceWaitIdle(demo->device); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); + } + vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); + + vkDestroyPipeline(demo->device, demo->pipeline, NULL); + vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL); + vkDestroyRenderPass(demo->device, demo->render_pass, NULL); + vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL); + vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + vkDestroyImageView(demo->device, demo->textures[i].view, NULL); + vkDestroyImage(demo->device, demo->textures[i].image, NULL); + vkFreeMemory(demo->device, demo->textures[i].mem, NULL); + vkDestroySampler(demo->device, demo->textures[i].sampler, NULL); + } + + vkDestroyImageView(demo->device, demo->depth.view, NULL); + vkDestroyImage(demo->device, demo->depth.image, NULL); + vkFreeMemory(demo->device, demo->depth.mem, NULL); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); + vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); + vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); + vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); + } + vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); + demo->cmd_pool = VK_NULL_HANDLE; + if (demo->separate_present_queue) { + vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL); + } + free(demo->swapchain_image_resources); + + // Second, re-perform the demo_prepare() function, which will re-create the + // swapchain: + demo_prepare(demo); +} + +// On MS-Windows, make this a global, so it's available to WndProc() +struct demo demo; + +#if defined(VK_USE_PLATFORM_WIN32_KHR) +static void demo_run(struct demo *demo) { + if (!demo->prepared) return; + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + PostQuitMessage(validation_error); + } +} + +// MS-Windows event handling function: +LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CLOSE: + PostQuitMessage(validation_error); + break; + case WM_PAINT: + // The validation callback calls MessageBox which can generate paint + // events - don't make more Vulkan calls if we got here from the + // callback + if (!in_callback) { + demo_run(&demo); + } + break; + case WM_GETMINMAXINFO: // set window's minimum size + ((MINMAXINFO *)lParam)->ptMinTrackSize = demo.minsize; + return 0; + case WM_ERASEBKGND: + return 1; + case WM_SIZE: + // Resize the application to the new window size, except when + // it was minimized. Vulkan doesn't support images or swapchains + // with width=0 and height=0. + if (wParam != SIZE_MINIMIZED) { + demo.width = lParam & 0xffff; + demo.height = (lParam & 0xffff0000) >> 16; + demo_resize(&demo); + } + break; + case WM_KEYDOWN: + switch (wParam) { + case VK_ESCAPE: + PostQuitMessage(validation_error); + break; + case VK_LEFT: + demo.spin_angle -= demo.spin_increment; + break; + case VK_RIGHT: + demo.spin_angle += demo.spin_increment; + break; + case VK_SPACE: + demo.pause = !demo.pause; + break; + } + return 0; + default: + break; + } + return (DefWindowProc(hWnd, uMsg, wParam, lParam)); +} + +static void demo_create_window(struct demo *demo) { + WNDCLASSEX win_class; + + // Initialize the window class structure: + win_class.cbSize = sizeof(WNDCLASSEX); + win_class.style = CS_HREDRAW | CS_VREDRAW; + win_class.lpfnWndProc = WndProc; + win_class.cbClsExtra = 0; + win_class.cbWndExtra = 0; + win_class.hInstance = demo->connection; // hInstance + win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + win_class.hCursor = LoadCursor(NULL, IDC_ARROW); + win_class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + win_class.lpszMenuName = NULL; + win_class.lpszClassName = demo->name; + win_class.hIconSm = LoadIcon(NULL, IDI_WINLOGO); + // Register window class: + if (!RegisterClassEx(&win_class)) { + // It didn't work, so try to give a useful error: + printf("Unexpected error trying to start the application!\n"); + fflush(stdout); + exit(1); + } + // Create window with the registered class: + RECT wr = {0, 0, demo->width, demo->height}; + AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); + demo->window = CreateWindowEx(0, + demo->name, // class name + demo->name, // app name + WS_OVERLAPPEDWINDOW | // window style + WS_VISIBLE | WS_SYSMENU, + 100, 100, // x/y coords + wr.right - wr.left, // width + wr.bottom - wr.top, // height + NULL, // handle to parent + NULL, // handle to menu + demo->connection, // hInstance + NULL); // no extra parameters + if (!demo->window) { + // It didn't work, so try to give a useful error: + printf("Cannot create a window in which to draw!\n"); + fflush(stdout); + exit(1); + } + // Window client area size must be at least 1 pixel high, to prevent crash. + demo->minsize.x = GetSystemMetrics(SM_CXMINTRACK); + demo->minsize.y = GetSystemMetrics(SM_CYMINTRACK) + 1; +} +#elif defined(VK_USE_PLATFORM_XLIB_KHR) +static void demo_create_xlib_window(struct demo *demo) { + const char *display_envar = getenv("DISPLAY"); + if (display_envar == NULL || display_envar[0] == '\0') { + printf("Environment variable DISPLAY requires a valid value.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + XInitThreads(); + demo->display = XOpenDisplay(NULL); + long visualMask = VisualScreenMask; + int numberOfVisuals; + XVisualInfo vInfoTemplate = {}; + vInfoTemplate.screen = DefaultScreen(demo->display); + XVisualInfo *visualInfo = XGetVisualInfo(demo->display, visualMask, &vInfoTemplate, &numberOfVisuals); + + Colormap colormap = + XCreateColormap(demo->display, RootWindow(demo->display, vInfoTemplate.screen), visualInfo->visual, AllocNone); + + XSetWindowAttributes windowAttributes = {}; + windowAttributes.colormap = colormap; + windowAttributes.background_pixel = 0xFFFFFFFF; + windowAttributes.border_pixel = 0; + windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; + + demo->xlib_window = XCreateWindow(demo->display, RootWindow(demo->display, vInfoTemplate.screen), 0, 0, demo->width, + demo->height, 0, visualInfo->depth, InputOutput, visualInfo->visual, + CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes); + + XSelectInput(demo->display, demo->xlib_window, ExposureMask | KeyPressMask); + XMapWindow(demo->display, demo->xlib_window); + XFlush(demo->display); + demo->xlib_wm_delete_window = XInternAtom(demo->display, "WM_DELETE_WINDOW", False); +} +static void demo_handle_xlib_event(struct demo *demo, const XEvent *event) { + switch (event->type) { + case ClientMessage: + if ((Atom)event->xclient.data.l[0] == demo->xlib_wm_delete_window) demo->quit = true; + break; + case KeyPress: + switch (event->xkey.keycode) { + case 0x9: // Escape + demo->quit = true; + break; + case 0x71: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case 0x72: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case 0x41: // space bar + demo->pause = !demo->pause; + break; + } + break; + case ConfigureNotify: + if ((demo->width != event->xconfigure.width) || (demo->height != event->xconfigure.height)) { + demo->width = event->xconfigure.width; + demo->height = event->xconfigure.height; + demo_resize(demo); + } + break; + default: + break; + } +} + +static void demo_run_xlib(struct demo *demo) { + while (!demo->quit) { + XEvent event; + + if (demo->pause) { + XNextEvent(demo->display, &event); + demo_handle_xlib_event(demo, &event); + } + while (XPending(demo->display) > 0) { + XNextEvent(demo->display, &event); + demo_handle_xlib_event(demo, &event); + } + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } +} +#elif defined(VK_USE_PLATFORM_XCB_KHR) +static void demo_handle_xcb_event(struct demo *demo, const xcb_generic_event_t *event) { + uint8_t event_code = event->response_type & 0x7f; + switch (event_code) { + case XCB_EXPOSE: + // TODO: Resize window + break; + case XCB_CLIENT_MESSAGE: + if ((*(xcb_client_message_event_t *)event).data.data32[0] == (*demo->atom_wm_delete_window).atom) { + demo->quit = true; + } + break; + case XCB_KEY_RELEASE: { + const xcb_key_release_event_t *key = (const xcb_key_release_event_t *)event; + + switch (key->detail) { + case 0x9: // Escape + demo->quit = true; + break; + case 0x71: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case 0x72: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case 0x41: // space bar + demo->pause = !demo->pause; + break; + } + } break; + case XCB_CONFIGURE_NOTIFY: { + const xcb_configure_notify_event_t *cfg = (const xcb_configure_notify_event_t *)event; + if ((demo->width != cfg->width) || (demo->height != cfg->height)) { + demo->width = cfg->width; + demo->height = cfg->height; + demo_resize(demo); + } + } break; + default: + break; + } +} + +static void demo_run_xcb(struct demo *demo) { + xcb_flush(demo->connection); + + while (!demo->quit) { + xcb_generic_event_t *event; + + if (demo->pause) { + event = xcb_wait_for_event(demo->connection); + } else { + event = xcb_poll_for_event(demo->connection); + } + while (event) { + demo_handle_xcb_event(demo, event); + free(event); + event = xcb_poll_for_event(demo->connection); + } + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } +} + +static void demo_create_xcb_window(struct demo *demo) { + uint32_t value_mask, value_list[32]; + + demo->xcb_window = xcb_generate_id(demo->connection); + + value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + value_list[0] = demo->screen->black_pixel; + value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + xcb_create_window(demo->connection, XCB_COPY_FROM_PARENT, demo->xcb_window, demo->screen->root, 0, 0, demo->width, demo->height, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, demo->screen->root_visual, value_mask, value_list); + + /* Magic code that will send notification when window is destroyed */ + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(demo->connection, 1, 12, "WM_PROTOCOLS"); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(demo->connection, cookie, 0); + + xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(demo->connection, 0, 16, "WM_DELETE_WINDOW"); + demo->atom_wm_delete_window = xcb_intern_atom_reply(demo->connection, cookie2, 0); + + xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, demo->xcb_window, (*reply).atom, 4, 32, 1, + &(*demo->atom_wm_delete_window).atom); + free(reply); + + xcb_map_window(demo->connection, demo->xcb_window); + + // Force the x/y coordinates to 100,100 results are identical in consecutive + // runs + const uint32_t coords[] = {100, 100}; + xcb_configure_window(demo->connection, demo->xcb_window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords); +} +// VK_USE_PLATFORM_XCB_KHR +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) +static void demo_run(struct demo *demo) { + while (!demo->quit) { + // Flush any commands to the server + wl_display_flush(demo->display); + + if (demo->pause) { + // block and wait for input + wl_display_dispatch(demo->display); + } else { + // Lock the display event queue in case the driver is doing something on another thread + // while we wait, keep pumping events + while (wl_display_prepare_read(demo->display) != 0) { + wl_display_dispatch_pending(demo->display); + } + // Actually do the read from the socket + wl_display_read_events(demo->display); + + // Pump events + wl_display_dispatch_pending(demo->display); + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } + } +} + +static void handle_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { + struct demo *demo = (struct demo *)data; + xdg_surface_ack_configure(xdg_surface, serial); + if (demo->xdg_surface_has_been_configured) { + demo_resize(demo); + } + demo->xdg_surface_has_been_configured = 1; +} + +static const struct xdg_surface_listener xdg_surface_listener = {handle_surface_configure}; + +static void handle_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel UNUSED, int32_t width, int32_t height, + struct wl_array *states UNUSED) { + struct demo *demo = (struct demo *)data; + /* zero values imply the program may choose its own size, so in that case + * stay with the existing value (which on startup is the default) */ + if (width > 0) { + demo->width = width; + } + if (height > 0) { + demo->height = height; + } + /* This should be followed by a surface configure */ +} + +static void handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel UNUSED) { + struct demo *demo = (struct demo *)data; + demo->quit = true; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = {handle_toplevel_configure, handle_toplevel_close}; + +static void demo_create_window(struct demo *demo) { + if (!demo->xdg_wm_base) { + printf("Compositor did not provide the standard protocol xdg-wm-base\n"); + fflush(stdout); + exit(1); + } + + demo->window = wl_compositor_create_surface(demo->compositor); + if (!demo->window) { + printf("Can not create wayland_surface from compositor!\n"); + fflush(stdout); + exit(1); + } + + demo->xdg_surface = xdg_wm_base_get_xdg_surface(demo->xdg_wm_base, demo->window); + if (!demo->xdg_surface) { + printf("Can not get xdg_surface from wayland_surface!\n"); + fflush(stdout); + exit(1); + } + demo->xdg_toplevel = xdg_surface_get_toplevel(demo->xdg_surface); + if (!demo->xdg_toplevel) { + printf("Can not allocate xdg_toplevel for xdg_surface!\n"); + fflush(stdout); + exit(1); + } + xdg_surface_add_listener(demo->xdg_surface, &xdg_surface_listener, demo); + xdg_toplevel_add_listener(demo->xdg_toplevel, &xdg_toplevel_listener, demo); + xdg_toplevel_set_title(demo->xdg_toplevel, APP_SHORT_NAME); + if (demo->xdg_decoration_mgr) { + // if supported, let the compositor render titlebars for us + demo->toplevel_decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration(demo->xdg_decoration_mgr, demo->xdg_toplevel); + zxdg_toplevel_decoration_v1_set_mode(demo->toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } + + wl_surface_commit(demo->window); +} +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) +static void demo_create_directfb_window(struct demo *demo) { + DFBResult ret; + + ret = DirectFBInit(NULL, NULL); + if (ret) { + printf("DirectFBInit failed to initialize DirectFB!\n"); + fflush(stdout); + exit(1); + } + + ret = DirectFBCreate(&demo->dfb); + if (ret) { + printf("DirectFBCreate failed to create main interface of DirectFB!\n"); + fflush(stdout); + exit(1); + } + + DFBSurfaceDescription desc; + desc.flags = DSDESC_CAPS | DSDESC_WIDTH | DSDESC_HEIGHT; + desc.caps = DSCAPS_PRIMARY; + desc.width = demo->width; + desc.height = demo->height; + ret = demo->dfb->CreateSurface(demo->dfb, &desc, &demo->window); + if (ret) { + printf("CreateSurface failed to create DirectFB surface interface!\n"); + fflush(stdout); + exit(1); + } + + ret = demo->dfb->CreateInputEventBuffer(demo->dfb, DICAPS_KEYS, DFB_FALSE, &demo->event_buffer); + if (ret) { + printf("CreateInputEventBuffer failed to create DirectFB event buffer interface!\n"); + fflush(stdout); + exit(1); + } +} + +static void demo_handle_directfb_event(struct demo *demo, const DFBInputEvent *event) { + if (event->type != DIET_KEYPRESS) return; + switch (event->key_symbol) { + case DIKS_ESCAPE: // Escape + demo->quit = true; + break; + case DIKS_CURSOR_LEFT: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case DIKS_CURSOR_RIGHT: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case DIKS_SPACE: // space bar + demo->pause = !demo->pause; + break; + default: + break; + } +} + +static void demo_run_directfb(struct demo *demo) { + while (!demo->quit) { + DFBInputEvent event; + + if (demo->pause) { + demo->event_buffer->WaitForEvent(demo->event_buffer); + if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); + } else { + if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } + } +} +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) +static void demo_run(struct demo *demo) { + if (!demo->prepared) return; + + demo_draw(demo); + demo->curFrame++; +} +#elif defined(VK_USE_PLATFORM_METAL_EXT) +static void demo_run(struct demo *demo) { + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = TRUE; + } +} +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) +static VkResult demo_create_display_surface(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + uint32_t display_count; + uint32_t mode_count; + uint32_t plane_count; + VkDisplayPropertiesKHR display_props; + VkDisplayKHR display; + VkDisplayModePropertiesKHR mode_props; + VkDisplayPlanePropertiesKHR *plane_props; + VkBool32 found_plane = VK_FALSE; + uint32_t plane_index; + VkExtent2D image_extent; + VkDisplaySurfaceCreateInfoKHR create_info; + + // Get the first display + display_count = 1; + err = vkGetPhysicalDeviceDisplayPropertiesKHR(demo->gpu, &display_count, &display_props); + assert(!err || (err == VK_INCOMPLETE)); + + display = display_props.display; + + // Get the first mode of the display + err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, NULL); + assert(!err); + + if (mode_count == 0) { + printf("Cannot find any mode for the display!\n"); + fflush(stdout); + exit(1); + } + + mode_count = 1; + err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, &mode_props); + assert(!err || (err == VK_INCOMPLETE)); + + // Get the list of planes + err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, NULL); + assert(!err); + + if (plane_count == 0) { + printf("Cannot find any plane!\n"); + fflush(stdout); + exit(1); + } + + plane_props = malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count); + assert(plane_props); + + err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, plane_props); + assert(!err); + + // Find a plane compatible with the display + for (plane_index = 0; plane_index < plane_count; plane_index++) { + uint32_t supported_count; + VkDisplayKHR *supported_displays; + + // Disqualify planes that are bound to a different display + if ((plane_props[plane_index].currentDisplay != VK_NULL_HANDLE) && (plane_props[plane_index].currentDisplay != display)) { + continue; + } + + err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, NULL); + assert(!err); + + if (supported_count == 0) { + continue; + } + + supported_displays = malloc(sizeof(VkDisplayKHR) * supported_count); + assert(supported_displays); + + err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, supported_displays); + assert(!err); + + for (uint32_t i = 0; i < supported_count; i++) { + if (supported_displays[i] == display) { + found_plane = VK_TRUE; + break; + } + } + + free(supported_displays); + + if (found_plane) { + break; + } + } + + if (!found_plane) { + printf("Cannot find a plane compatible with the display!\n"); + fflush(stdout); + exit(1); + } + + free(plane_props); + + VkDisplayPlaneCapabilitiesKHR planeCaps; + vkGetDisplayPlaneCapabilitiesKHR(demo->gpu, mode_props.displayMode, plane_index, &planeCaps); + // Find a supported alpha mode + VkDisplayPlaneAlphaFlagBitsKHR alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; + VkDisplayPlaneAlphaFlagBitsKHR alphaModes[4] = { + VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR, + }; + for (uint32_t i = 0; i < sizeof(alphaModes); i++) { + if (planeCaps.supportedAlpha & alphaModes[i]) { + alphaMode = alphaModes[i]; + break; + } + } + image_extent.width = mode_props.parameters.visibleRegion.width; + image_extent.height = mode_props.parameters.visibleRegion.height; + + create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR; + create_info.pNext = NULL; + create_info.flags = 0; + create_info.displayMode = mode_props.displayMode; + create_info.planeIndex = plane_index; + create_info.planeStackIndex = plane_props[plane_index].currentStackIndex; + create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + create_info.alphaMode = alphaMode; + create_info.globalAlpha = 1.0f; + create_info.imageExtent = image_extent; + + return vkCreateDisplayPlaneSurfaceKHR(demo->inst, &create_info, NULL, &demo->surface); +} + +static void demo_run_display(struct demo *demo) { + while (!demo->quit) { + demo_draw(demo); + demo->curFrame++; + + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = true; + } + } +} +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + +#include + +static void demo_run(struct demo *demo) { + int size[2] = {0, 0}; + screen_window_t win; + int val; + int rc; + + while (!demo->quit) { + while (!screen_get_event(demo->screen_context, demo->screen_event, demo->pause ? ~0 : 0)) { + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_TYPE, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_TYPE of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (val == SCREEN_EVENT_NONE) { + break; + } + switch (val) { + case SCREEN_EVENT_KEYBOARD: + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_FLAGS, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_FLAGS of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (val & KEY_DOWN) { + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_SYM, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_SYM of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + switch (val) { + case KEYCODE_ESCAPE: + demo->quit = true; + break; + case KEYCODE_SPACE: + demo->pause = !demo->pause; + break; + case KEYCODE_LEFT: + demo->spin_angle -= demo->spin_increment; + break; + case KEYCODE_RIGHT: + demo->spin_angle += demo->spin_increment; + break; + default: + break; + } + } + break; + case SCREEN_EVENT_PROPERTY: + rc = screen_get_event_property_pv(demo->screen_event, SCREEN_PROPERTY_WINDOW, (void **)&win); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_WINDOW of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_NAME, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_NAME of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (win == demo->screen_window) { + switch (val) { + case SCREEN_PROPERTY_SIZE: + rc = screen_get_window_property_iv(win, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_SIZE of the window in the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + demo->width = size[0]; + demo->height = size[1]; + demo_resize(demo); + break; + default: + /* We are not interested in any other events for now */ + break; + } + } + break; + } + } + + if (demo->pause) { + } else { + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = true; + } + } + } +} + +static void demo_create_window(struct demo *demo) { + const char *idstr = APP_SHORT_NAME; + int size[2]; + int usage = SCREEN_USAGE_VULKAN; + int rc; + + rc = screen_create_context(&demo->screen_context, 0); + if (rc) { + printf("Cannot create QNX Screen context!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + rc = screen_create_window(&demo->screen_window, demo->screen_context); + if (rc) { + printf("Cannot create QNX Screen window!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + rc = screen_create_event(&demo->screen_event); + if (rc) { + printf("Cannot create QNX Screen event!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + + /* Set window caption */ + screen_set_window_property_cv(demo->screen_window, SCREEN_PROPERTY_ID_STRING, strlen(idstr), idstr); + + /* Setup VULKAN usage flags */ + rc = screen_set_window_property_iv(demo->screen_window, SCREEN_PROPERTY_USAGE, &usage); + if (rc) { + printf("Cannot set SCREEN_USAGE_VULKAN flag!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + + /* Setup window size */ + if ((demo->width == 0) || (demo->height == 0)) { + /* Obtain automatically set window size provided by WM */ + rc = screen_get_window_property_iv(demo->screen_window, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot obtain current window size!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + demo->width = size[0]; + demo->height = size[1]; + } else { + size[0] = demo->width; + size[1] = demo->height; + rc = screen_set_window_property_iv(demo->screen_window, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot set window size!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + } +} +#endif + +/* + * Return 1 (true) if all layer names specified in check_names + * can be found in given layer properties. + */ +static VkBool32 demo_check_layers(uint32_t check_count, char **check_names, uint32_t layer_count, VkLayerProperties *layers) { + for (uint32_t i = 0; i < check_count; i++) { + VkBool32 found = 0; + for (uint32_t j = 0; j < layer_count; j++) { + if (!strcmp(check_names[i], layers[j].layerName)) { + found = 1; + break; + } + } + if (!found) { + fprintf(stderr, "Cannot find layer: %s\n", check_names[i]); + return 0; + } + } + return 1; +} +#if defined(VK_USE_PLATFORM_DISPLAY_KHR) +int find_display_gpu(int gpu_number, uint32_t gpu_count, VkPhysicalDevice *physical_devices) { + uint32_t display_count = 0; + VkResult U_ASSERT_ONLY result; + int gpu_return = gpu_number; + if (gpu_number >= 0) { + result = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_devices[gpu_number], &display_count, NULL); + assert(!result); + } else { + for (uint32_t i = 0; i < gpu_count; i++) { + result = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_devices[i], &display_count, NULL); + assert(!result); + if (display_count) { + gpu_return = i; + break; + } + } + } + if (display_count > 0) + return gpu_return; + else + return -1; +} +#endif +static void demo_init_vk(struct demo *demo) { + VkResult err; + uint32_t instance_extension_count = 0; + uint32_t instance_layer_count = 0; + char *instance_validation_layers[] = {"VK_LAYER_KHRONOS_validation"}; + demo->enabled_extension_count = 0; + demo->enabled_layer_count = 0; + demo->is_minimized = false; + demo->cmd_pool = VK_NULL_HANDLE; + + err = volkInitialize(); + if (err != VK_SUCCESS) { + ERR_EXIT( + "Unable to find the Vulkan runtime on the system.\n\n" + "This likely indicates that no Vulkan capable drivers are installed.", + "Installation Failure"); + } + // Look for validation layers + VkBool32 validation_found = 0; + if (demo->validate) { + err = vkEnumerateInstanceLayerProperties(&instance_layer_count, NULL); + assert(!err); + + if (instance_layer_count > 0) { + VkLayerProperties *instance_layers = malloc(sizeof(VkLayerProperties) * instance_layer_count); + err = vkEnumerateInstanceLayerProperties(&instance_layer_count, instance_layers); + assert(!err); + + validation_found = demo_check_layers(ARRAY_SIZE(instance_validation_layers), instance_validation_layers, + instance_layer_count, instance_layers); + if (validation_found) { + demo->enabled_layer_count = ARRAY_SIZE(instance_validation_layers); + demo->enabled_layers[0] = "VK_LAYER_KHRONOS_validation"; + } + free(instance_layers); + } + + if (!validation_found) { + ERR_EXIT( + "vkEnumerateInstanceLayerProperties failed to find required validation layer.\n\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + } + + /* Look for instance extensions */ + VkBool32 surfaceExtFound = 0; + VkBool32 platformSurfaceExtFound = 0; + bool portabilityEnumerationActive = false; + memset(demo->extension_names, 0, sizeof(demo->extension_names)); + + err = vkEnumerateInstanceExtensionProperties(NULL, &instance_extension_count, NULL); + assert(!err); + + if (instance_extension_count > 0) { + VkExtensionProperties *instance_extensions = malloc(sizeof(VkExtensionProperties) * instance_extension_count); + err = vkEnumerateInstanceExtensionProperties(NULL, &instance_extension_count, instance_extensions); + assert(!err); + for (uint32_t i = 0; i < instance_extension_count; i++) { + if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + surfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_SURFACE_EXTENSION_NAME; + } +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (!strcmp(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_WIN32_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + if (!strcmp(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_XLIB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_XCB_KHR) + if (!strcmp(VK_KHR_XCB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_XCB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (!strcmp(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + if (!strcmp(VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + if (!strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_DISPLAY_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + if (!strcmp(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_ANDROID_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_METAL_EXT) + if (!strcmp(VK_EXT_METAL_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_METAL_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + if (!strcmp(VK_QNX_SCREEN_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_QNX_SCREEN_SURFACE_EXTENSION_NAME; + } +#endif + if (!strcmp(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, instance_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + } + if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, instance_extensions[i].extensionName)) { + if (demo->validate) { + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + } + } + // We want cube to be able to enumerate drivers that support the portability_subset extension, so we have to enable the + // portability enumeration extension. + if (!strcmp(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, instance_extensions[i].extensionName)) { + portabilityEnumerationActive = true; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + } + assert(demo->enabled_extension_count < 64); + } + + free(instance_extensions); + } + + if (!surfaceExtFound) { + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + if (!platformSurfaceExtFound) { +#if defined(VK_USE_PLATFORM_WIN32_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_WIN32_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_EXT_METAL_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_XCB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_DISPLAY_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_ANDROID_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_XLIB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_QNX_SCREEN_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#endif + } + const VkApplicationInfo app = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = NULL, + .pApplicationName = APP_SHORT_NAME, + .applicationVersion = 0, + .pEngineName = APP_SHORT_NAME, + .engineVersion = 0, + .apiVersion = VK_API_VERSION_1_0, + }; + VkInstanceCreateInfo inst_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = NULL, + .flags = (portabilityEnumerationActive ? VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR : 0), + .pApplicationInfo = &app, + .enabledLayerCount = demo->enabled_layer_count, + .ppEnabledLayerNames = (const char *const *)instance_validation_layers, + .enabledExtensionCount = demo->enabled_extension_count, + .ppEnabledExtensionNames = (const char *const *)demo->extension_names, + }; + + /* + * This is info for a temp callback to use during CreateInstance. + * After the instance is created, we use the instance-based + * function to register the final callback. + */ + VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info; + if (demo->validate) { + // VK_EXT_debug_utils style + dbg_messenger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + dbg_messenger_create_info.pNext = NULL; + dbg_messenger_create_info.flags = 0; + dbg_messenger_create_info.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + dbg_messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + dbg_messenger_create_info.pfnUserCallback = debug_messenger_callback; + dbg_messenger_create_info.pUserData = demo; + inst_info.pNext = &dbg_messenger_create_info; + } + + err = vkCreateInstance(&inst_info, NULL, &demo->inst); + if (err == VK_ERROR_INCOMPATIBLE_DRIVER) { + ERR_EXIT( + "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } else if (err == VK_ERROR_EXTENSION_NOT_PRESENT) { + ERR_EXIT( + "Cannot find a specified extension library.\n" + "Make sure your layers path is set appropriately.\n", + "vkCreateInstance Failure"); + } else if (err) { + ERR_EXIT( + "vkCreateInstance failed.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + + volkLoadInstance(demo->inst); + + /* Make initial call to query gpu_count, then second call for gpu info */ + uint32_t gpu_count = 0; + err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, NULL); + assert(!err); + + if (gpu_count <= 0) { + ERR_EXIT( + "vkEnumeratePhysicalDevices reported zero accessible devices.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkEnumeratePhysicalDevices Failure"); + } + + VkPhysicalDevice *physical_devices = malloc(sizeof(VkPhysicalDevice) * gpu_count); + err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, physical_devices); + assert(!err); + if (demo->invalid_gpu_selection || (demo->gpu_number >= 0 && !((uint32_t)demo->gpu_number < gpu_count))) { + fprintf(stderr, "GPU %d specified is not present, GPU count = %u\n", demo->gpu_number, gpu_count); + ERR_EXIT("Specified GPU number is not present", "User Error"); + } + +#if defined(VK_USE_PLATFORM_DISPLAY_KHR) + demo->gpu_number = find_display_gpu(demo->gpu_number, gpu_count, physical_devices); + if (demo->gpu_number < 0) { + printf("Cannot find any display!\n"); + fflush(stdout); + exit(1); + } +#else + /* Try to auto select most suitable device */ + if (demo->gpu_number == -1) { + uint32_t count_device_type[VK_PHYSICAL_DEVICE_TYPE_CPU + 1]; + memset(count_device_type, 0, sizeof(count_device_type)); + + VkPhysicalDeviceProperties physicalDeviceProperties; + for (uint32_t i = 0; i < gpu_count; i++) { + vkGetPhysicalDeviceProperties(physical_devices[i], &physicalDeviceProperties); + assert(physicalDeviceProperties.deviceType <= VK_PHYSICAL_DEVICE_TYPE_CPU); + count_device_type[physicalDeviceProperties.deviceType]++; + } + + VkPhysicalDeviceType search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_CPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_CPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_OTHER]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_OTHER; + } + + for (uint32_t i = 0; i < gpu_count; i++) { + vkGetPhysicalDeviceProperties(physical_devices[i], &physicalDeviceProperties); + if (physicalDeviceProperties.deviceType == search_for_device_type) { + demo->gpu_number = i; + break; + } + } + } +#endif + assert(demo->gpu_number >= 0); + demo->gpu = physical_devices[demo->gpu_number]; + { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(demo->gpu, &physicalDeviceProperties); + fprintf(stderr, "Selected GPU %d: %s, type: %s\n", demo->gpu_number, physicalDeviceProperties.deviceName, + to_string(physicalDeviceProperties.deviceType)); + } + free(physical_devices); + + /* Look for device extensions */ + uint32_t device_extension_count = 0; + VkBool32 swapchainExtFound = 0; + demo->enabled_extension_count = 0; + memset(demo->extension_names, 0, sizeof(demo->extension_names)); + + err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL, &device_extension_count, NULL); + assert(!err); + + if (device_extension_count > 0) { + VkExtensionProperties *device_extensions = malloc(sizeof(VkExtensionProperties) * device_extension_count); + err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL, &device_extension_count, device_extensions); + assert(!err); + + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, device_extensions[i].extensionName)) { + swapchainExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; + } + if (!strcmp("VK_KHR_portability_subset", device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = "VK_KHR_portability_subset"; + } + assert(demo->enabled_extension_count < 64); + } + + if (demo->VK_KHR_incremental_present_enabled) { + // Even though the user "enabled" the extension via the command + // line, we must make sure that it's enumerated for use with the + // device. Therefore, disable it here, and re-enable it again if + // enumerated. + demo->VK_KHR_incremental_present_enabled = false; + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME; + demo->VK_KHR_incremental_present_enabled = true; + DbgMsg("VK_KHR_incremental_present extension enabled\n"); + } + assert(demo->enabled_extension_count < 64); + } + if (!demo->VK_KHR_incremental_present_enabled) { + DbgMsg("VK_KHR_incremental_present extension NOT AVAILABLE\n"); + } + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + // Even though the user "enabled" the extension via the command + // line, we must make sure that it's enumerated for use with the + // device. Therefore, disable it here, and re-enable it again if + // enumerated. + demo->VK_GOOGLE_display_timing_enabled = false; + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME; + demo->VK_GOOGLE_display_timing_enabled = true; + DbgMsg("VK_GOOGLE_display_timing extension enabled\n"); + } + assert(demo->enabled_extension_count < 64); + } + if (!demo->VK_GOOGLE_display_timing_enabled) { + DbgMsg("VK_GOOGLE_display_timing extension NOT AVAILABLE\n"); + } + } + + free(device_extensions); + } + + if (!swapchainExtFound) { + ERR_EXIT("vkEnumerateDeviceExtensionProperties failed to find the " VK_KHR_SWAPCHAIN_EXTENSION_NAME + " extension.\n\nDo you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + + if (demo->validate) { + err = vkCreateDebugUtilsMessengerEXT(demo->inst, &dbg_messenger_create_info, NULL, &demo->dbg_messenger); + switch (err) { + case VK_SUCCESS: + break; + case VK_ERROR_OUT_OF_HOST_MEMORY: + ERR_EXIT("CreateDebugUtilsMessengerEXT: out of host memory\n", "CreateDebugUtilsMessengerEXT Failure"); + break; + default: + ERR_EXIT("CreateDebugUtilsMessengerEXT: unknown failure\n", "CreateDebugUtilsMessengerEXT Failure"); + break; + } + } + vkGetPhysicalDeviceProperties(demo->gpu, &demo->gpu_props); + + /* Call with NULL data to get count */ + vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_family_count, NULL); + assert(demo->queue_family_count >= 1); + + demo->queue_props = (VkQueueFamilyProperties *)malloc(demo->queue_family_count * sizeof(VkQueueFamilyProperties)); + vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_family_count, demo->queue_props); + + // Query fine-grained feature support for this device. + // If app has specific feature requirements it should check supported + // features based on this query + VkPhysicalDeviceFeatures physDevFeatures; + vkGetPhysicalDeviceFeatures(demo->gpu, &physDevFeatures); +} + +static void demo_create_device(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + float queue_priorities[1] = {0.0}; + VkDeviceQueueCreateInfo queues[2]; + queues[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queues[0].pNext = NULL; + queues[0].queueFamilyIndex = demo->graphics_queue_family_index; + queues[0].queueCount = 1; + queues[0].pQueuePriorities = queue_priorities; + queues[0].flags = 0; + + VkDeviceCreateInfo device = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = NULL, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = queues, + .enabledLayerCount = 0, + .ppEnabledLayerNames = NULL, + .enabledExtensionCount = demo->enabled_extension_count, + .ppEnabledExtensionNames = (const char *const *)demo->extension_names, + .pEnabledFeatures = NULL, // If specific features are required, pass them in here + }; + if (demo->separate_present_queue) { + queues[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queues[1].pNext = NULL; + queues[1].queueFamilyIndex = demo->present_queue_family_index; + queues[1].queueCount = 1; + queues[1].pQueuePriorities = queue_priorities; + queues[1].flags = 0; + device.queueCreateInfoCount = 2; + } + err = vkCreateDevice(demo->gpu, &device, NULL, &demo->device); + assert(!err); + + volkLoadDevice(demo->device); +} + +static void demo_create_surface(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + +// Create a WSI surface for the window: +#if defined(VK_USE_PLATFORM_WIN32_KHR) + VkWin32SurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.hinstance = demo->connection; + createInfo.hwnd = demo->window; + + err = vkCreateWin32SurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + VkWaylandSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.display = demo->display; + createInfo.surface = demo->window; + + err = vkCreateWaylandSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + VkAndroidSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.window = (struct ANativeWindow *)(demo->window); + + err = vkCreateAndroidSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + VkXlibSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.dpy = demo->display; + createInfo.window = demo->xlib_window; + + err = vkCreateXlibSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + VkXcbSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.connection = demo->connection; + createInfo.window = demo->xcb_window; + + err = vkCreateXcbSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + VkDirectFBSurfaceCreateInfoEXT createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.dfb = demo->dfb; + createInfo.surface = demo->window; + + err = vkCreateDirectFBSurfaceEXT(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + err = demo_create_display_surface(demo); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + VkMetalSurfaceCreateInfoEXT surface; + surface.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + surface.pNext = NULL; + surface.flags = 0; + surface.pLayer = demo->caMetalLayer; + + err = vkCreateMetalSurfaceEXT(demo->inst, &surface, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + VkScreenSurfaceCreateInfoQNX createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.context = demo->screen_context; + createInfo.window = demo->screen_window; + + err = vkCreateScreenSurfaceQNX(demo->inst, &createInfo, NULL, &demo->surface); +#endif + assert(!err); +} + +static VkSurfaceFormatKHR pick_surface_format(const VkSurfaceFormatKHR *surfaceFormats, uint32_t count) { + // Prefer non-SRGB formats... + for (uint32_t i = 0; i < count; i++) { + const VkFormat format = surfaceFormats[i].format; + + if (format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_B8G8R8A8_UNORM || + format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || format == VK_FORMAT_A2R10G10B10_UNORM_PACK32 || + format == VK_FORMAT_A1R5G5B5_UNORM_PACK16 || format == VK_FORMAT_R5G6B5_UNORM_PACK16 || + format == VK_FORMAT_R16G16B16A16_SFLOAT) { + return surfaceFormats[i]; + } + } + + printf("Can't find our preferred formats... Falling back to first exposed format. Rendering may be incorrect.\n"); + + assert(count >= 1); + return surfaceFormats[0]; +} + +static void demo_init_vk_swapchain(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + demo_create_surface(demo); + + // Iterate over each queue to learn whether it supports presenting: + VkBool32 *supportsPresent = (VkBool32 *)malloc(demo->queue_family_count * sizeof(VkBool32)); + for (uint32_t i = 0; i < demo->queue_family_count; i++) { + vkGetPhysicalDeviceSurfaceSupportKHR(demo->gpu, i, demo->surface, &supportsPresent[i]); + } + + // Search for a graphics and a present queue in the array of queue + // families, try to find one that supports both + uint32_t graphicsQueueFamilyIndex = UINT32_MAX; + uint32_t presentQueueFamilyIndex = UINT32_MAX; + for (uint32_t i = 0; i < demo->queue_family_count; i++) { + if ((demo->queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { + if (graphicsQueueFamilyIndex == UINT32_MAX) { + graphicsQueueFamilyIndex = i; + } + + if (supportsPresent[i] == VK_TRUE) { + graphicsQueueFamilyIndex = i; + presentQueueFamilyIndex = i; + break; + } + } + } + + if (presentQueueFamilyIndex == UINT32_MAX) { + // If didn't find a queue that supports both graphics and present, then + // find a separate present queue. + for (uint32_t i = 0; i < demo->queue_family_count; ++i) { + if (supportsPresent[i] == VK_TRUE) { + presentQueueFamilyIndex = i; + break; + } + } + } + + // Generate error if could not find both a graphics and a present queue + if (graphicsQueueFamilyIndex == UINT32_MAX || presentQueueFamilyIndex == UINT32_MAX) { + ERR_EXIT("Could not find both graphics and present queues\n", "Swapchain Initialization Failure"); + } + + demo->graphics_queue_family_index = graphicsQueueFamilyIndex; + demo->present_queue_family_index = presentQueueFamilyIndex; + demo->separate_present_queue = (demo->graphics_queue_family_index != demo->present_queue_family_index); + free(supportsPresent); + + demo_create_device(demo); + + vkGetDeviceQueue(demo->device, demo->graphics_queue_family_index, 0, &demo->graphics_queue); + + if (!demo->separate_present_queue) { + demo->present_queue = demo->graphics_queue; + } else { + vkGetDeviceQueue(demo->device, demo->present_queue_family_index, 0, &demo->present_queue); + } + + // Get the list of VkFormat's that are supported: + uint32_t formatCount; + err = vkGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface, &formatCount, NULL); + assert(!err); + VkSurfaceFormatKHR *surfFormats = (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR)); + err = vkGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface, &formatCount, surfFormats); + assert(!err); + VkSurfaceFormatKHR surfaceFormat = pick_surface_format(surfFormats, formatCount); + demo->format = surfaceFormat.format; + demo->color_space = surfaceFormat.colorSpace; + free(surfFormats); + + demo->quit = false; + demo->curFrame = 0; + + // Create semaphores to synchronize acquiring presentable buffers before + // rendering and waiting for drawing to be complete before presenting + VkSemaphoreCreateInfo semaphoreCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + }; + + // Create fences that we can use to throttle if we get too far + // ahead of the image presents + VkFenceCreateInfo fence_ci = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = VK_FENCE_CREATE_SIGNALED_BIT}; + for (uint32_t i = 0; i < FRAME_LAG; i++) { + err = vkCreateFence(demo->device, &fence_ci, NULL, &demo->fences[i]); + assert(!err); + + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_acquired_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_acquired_semaphores[i], "AcquireSem(%u)", i); + + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->draw_complete_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->draw_complete_semaphores[i], "DrawCompleteSem(%u)", i); + + if (demo->separate_present_queue) { + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_ownership_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_ownership_semaphores[i], "ImageOwnerSem(%u)", i); + } + } + demo->frame_index = 0; + demo->first_swapchain_frame = true; + + // Get Memory information and properties + vkGetPhysicalDeviceMemoryProperties(demo->gpu, &demo->memory_properties); +} + +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) +static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, + wl_fixed_t sy) {} + +static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) {} + +static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) { + struct demo *demo = data; + if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) { + xdg_toplevel_move(demo->xdg_toplevel, demo->seat, serial); + } +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, +}; + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {} + +static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) {} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) {} + +static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) { + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; + struct demo *demo = data; + switch (key) { + case KEY_ESC: // Escape + demo->quit = true; + break; + case KEY_LEFT: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case KEY_RIGHT: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case KEY_SPACE: // space bar + demo->pause = !demo->pause; + break; + } +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { + // Subscribe to pointer events + struct demo *demo = data; + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !demo->pointer) { + demo->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(demo->pointer, &pointer_listener, demo); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && demo->pointer) { + wl_pointer_destroy(demo->pointer); + demo->pointer = NULL; + } + // Subscribe to keyboard events + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + demo->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(demo->keyboard, &keyboard_listener, demo); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && demo->keyboard) { + wl_keyboard_destroy(demo->keyboard); + demo->keyboard = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void wm_base_ping(void *data UNUSED, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = {wm_base_ping}; + +static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version UNUSED) { + struct demo *demo = data; + // pickup wayland objects when they appear + if (strcmp(interface, wl_compositor_interface.name) == 0) { + uint32_t minVersion = version < 4 ? version : 4; + demo->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, minVersion); + if (demo->VK_KHR_incremental_present_enabled && minVersion < 4) { + fprintf(stderr, "Wayland compositor doesn't support VK_KHR_incremental_present, disabling.\n"); + demo->VK_KHR_incremental_present_enabled = false; + } + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + demo->xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(demo->xdg_wm_base, &wm_base_listener, NULL); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + demo->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(demo->seat, &seat_listener, demo); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + demo->xdg_decoration_mgr = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } +} + +static void registry_handle_global_remove(void *data UNUSED, struct wl_registry *registry UNUSED, uint32_t name UNUSED) {} + +static const struct wl_registry_listener registry_listener = {registry_handle_global, registry_handle_global_remove}; +#endif + +static void demo_init_connection(struct demo *demo) { +#if defined(VK_USE_PLATFORM_XCB_KHR) + const xcb_setup_t *setup; + xcb_screen_iterator_t iter; + int scr; + + const char *display_envar = getenv("DISPLAY"); + if (display_envar == NULL || display_envar[0] == '\0') { + printf("Environment variable DISPLAY requires a valid value.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + demo->connection = xcb_connect(NULL, &scr); + if (xcb_connection_has_error(demo->connection) > 0) { + printf("Cannot connect to XCB.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + setup = xcb_get_setup(demo->connection); + iter = xcb_setup_roots_iterator(setup); + while (scr-- > 0) xcb_screen_next(&iter); + + demo->screen = iter.data; +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo->display = wl_display_connect(NULL); + + if (demo->display == NULL) { + printf("Cannot connect to wayland.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + demo->registry = wl_display_get_registry(demo->display); + wl_registry_add_listener(demo->registry, ®istry_listener, demo); + wl_display_roundtrip(demo->display); +#endif +} + +static void demo_init(struct demo *demo, int argc, char **argv) { + vec3 eye = {0.0f, 3.0f, 5.0f}; + vec3 origin = {0, 0, 0}; + vec3 up = {0.0f, 1.0f, 0.0}; + + memset(demo, 0, sizeof(*demo)); + demo->presentMode = VK_PRESENT_MODE_FIFO_KHR; + demo->frameCount = INT32_MAX; + /* Autodetect suitable / best GPU by default */ + demo->gpu_number = -1; + demo->width = 500; + demo->height = 500; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--use_staging") == 0) { + demo->use_staging_buffer = true; + continue; + } + if ((strcmp(argv[i], "--present_mode") == 0) && (i < argc - 1)) { + demo->presentMode = atoi(argv[i + 1]); + i++; + continue; + } + if (strcmp(argv[i], "--break") == 0) { + demo->use_break = true; + continue; + } + if (strcmp(argv[i], "--validate") == 0) { + demo->validate = true; + continue; + } + if (strcmp(argv[i], "--validate-checks-disabled") == 0) { + demo->validate = true; + demo->validate_checks_disabled = true; + continue; + } + if (strcmp(argv[i], "--xlib") == 0) { + fprintf(stderr, "--xlib is deprecated and no longer does anything"); + continue; + } + if (strcmp(argv[i], "--c") == 0 && demo->frameCount == INT32_MAX && i < argc - 1 && + sscanf(argv[i + 1], "%d", &demo->frameCount) == 1 && demo->frameCount >= 0) { + i++; + continue; + } + if (strcmp(argv[i], "--width") == 0) { + if (i < argc - 1 && sscanf(argv[i + 1], "%d", &demo->width) == 1) { + if (demo->width > 0) { + i++; + continue; + } else { + ERR_EXIT("The --width parameter must be greater than 0", "User Error"); + } + } + ERR_EXIT("The --width parameter must be followed by a number", "User Error"); + } + if (strcmp(argv[i], "--height") == 0) { + if (i < argc - 1 && sscanf(argv[i + 1], "%d", &demo->height) == 1) { + if (demo->height > 0) { + i++; + continue; + } else { + ERR_EXIT("The --height parameter must be greater than 0", "User Error"); + } + } + ERR_EXIT("The --height parameter must be followed by a number", "User Error"); + } + if (strcmp(argv[i], "--suppress_popups") == 0) { + demo->suppress_popups = true; + continue; + } + if (strcmp(argv[i], "--display_timing") == 0) { + demo->VK_GOOGLE_display_timing_enabled = true; + continue; + } + if (strcmp(argv[i], "--incremental_present") == 0) { + demo->VK_KHR_incremental_present_enabled = true; + continue; + } + if ((strcmp(argv[i], "--gpu_number") == 0) && (i < argc - 1)) { + demo->gpu_number = atoi(argv[i + 1]); + if (demo->gpu_number < 0) demo->invalid_gpu_selection = true; + i++; + continue; + } + if (strcmp(argv[i], "--force_errors") == 0) { + demo->force_errors = true; + continue; + } + +#if defined(ANDROID) + ERR_EXIT("Usage: vkcube [--validate]\n", "Usage"); +#else + char *message = + "Usage:\n %s\t[--use_staging] [--validate] [--validate-checks-disabled]\n" + "\t[--break] [--c ] [--suppress_popups]\n" + "\t[--incremental_present] [--display_timing]\n" + "\t[--gpu_number ]\n" + "\t[--present_mode ]\n" + "\t[--width ] [--height ]\n" + "\t[--force_errors]\n" + "\t\n" + "\t\tVK_PRESENT_MODE_IMMEDIATE_KHR = %d\n" + "\t\tVK_PRESENT_MODE_MAILBOX_KHR = %d\n" + "\t\tVK_PRESENT_MODE_FIFO_KHR = %d\n" + "\t\tVK_PRESENT_MODE_FIFO_RELAXED_KHR = %d\n"; + int length = snprintf(NULL, 0, message, APP_SHORT_NAME, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR); + char *usage = (char *)malloc(length + 1); + if (!usage) { + exit(1); + } + snprintf(usage, length + 1, message, APP_SHORT_NAME, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR); +#if defined(_WIN32) + if (!demo->suppress_popups) MessageBox(NULL, usage, "Usage Error", MB_OK); +#else + fprintf(stderr, "%s", usage); + fflush(stderr); +#endif + free(usage); + exit(1); +#endif + } + + demo_init_connection(demo); + + demo_init_vk(demo); + + demo->spin_angle = 4.0f; + demo->spin_increment = 0.2f; + demo->pause = false; + + mat4x4_perspective(demo->projection_matrix, (float)degreesToRadians(45.0f), 1.0f, 0.1f, 100.0f); + mat4x4_look_at(demo->view_matrix, eye, origin, up); + mat4x4_identity(demo->model_matrix); + + demo->projection_matrix[1][1] *= -1; // Flip projection matrix from GL to Vulkan orientation. +} + +#if defined(VK_USE_PLATFORM_WIN32_KHR) +// Include header required for parsing the command line options. +#include + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow) { + MSG msg; // message + bool done; // flag saying when app is complete + int argc; + char **argv; + + // Ensure wParam is initialized. + msg.wParam = 0; + + // Use the CommandLine functions to get the command line arguments. + // Unfortunately, Microsoft outputs + // this information as wide characters for Unicode, and we simply want the + // Ascii version to be compatible + // with the non-Windows side. So, we have to convert the information to + // Ascii character strings. + LPWSTR *commandLineArgs = CommandLineToArgvW(GetCommandLineW(), &argc); + if (NULL == commandLineArgs) { + argc = 0; + } + + if (argc > 0) { + argv = (char **)malloc(sizeof(char *) * argc); + if (argv == NULL) { + argc = 0; + } else { + for (int iii = 0; iii < argc; iii++) { + size_t wideCharLen = wcslen(commandLineArgs[iii]); + size_t numConverted = 0; + + argv[iii] = (char *)malloc(sizeof(char) * (wideCharLen + 1)); + if (argv[iii] != NULL) { + wcstombs_s(&numConverted, argv[iii], wideCharLen + 1, commandLineArgs[iii], wideCharLen + 1); + } + } + } + } else { + argv = NULL; + } + + demo_init(&demo, argc, argv); + + // Free up the items we had to allocate for the command line arguments. + if (argc > 0 && argv != NULL) { + for (int iii = 0; iii < argc; iii++) { + if (argv[iii] != NULL) { + free(argv[iii]); + } + } + free(argv); + } + + demo.connection = hInstance; + strncpy(demo.name, "Vulkan Cube", APP_NAME_STR_LEN); + demo_create_window(&demo); + demo_init_vk_swapchain(&demo); + + demo_prepare(&demo); + + done = false; // initialize loop condition variable + + // main message loop + while (!done) { + if (demo.pause) { + const BOOL succ = WaitMessage(); + + if (!succ) { + struct demo *tmp = &demo; + struct demo *demo = tmp; + ERR_EXIT("WaitMessage() failed on paused demo", "event loop error"); + } + } + PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + if (msg.message == WM_QUIT) // check for a quit message + { + done = true; // if found, quit app + } else { + /* Translate and dispatch to event queue*/ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + RedrawWindow(demo.window, NULL, NULL, RDW_INTERNALPAINT); + } + + demo_cleanup(&demo); + + return (int)msg.wParam; +} + +#elif defined(VK_USE_PLATFORM_METAL_EXT) +static void demo_main(struct demo *demo, void *caMetalLayer, int argc, const char *argv[]) { + demo_init(demo, argc, (char **)argv); + demo->caMetalLayer = caMetalLayer; + demo_init_vk_swapchain(demo); + demo_prepare(demo); + demo->spin_angle = 0.4f; +} + +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) +#include +#include +#include "android_util.h" + +static bool initialized = false; +static bool active = false; +struct demo demo; + +static int32_t processInput(struct android_app *app, AInputEvent *event) { return 0; } + +static void processCommand(struct android_app *app, int32_t cmd) { + switch (cmd) { + case APP_CMD_INIT_WINDOW: { + if (app->window) { + // We're getting a new window. If the app is starting up, we + // need to initialize. If the app has already been + // initialized, that means that we lost our previous window, + // which means that we have a lot of work to do. At a minimum, + // we need to destroy the swapchain and surface associated with + // the old window, and create a new surface and swapchain. + // However, since there are a lot of other objects/state that + // is tied to the swapchain, it's easiest to simply cleanup and + // start over (i.e. use a brute-force approach of re-starting + // the app) + if (demo.prepared) { + demo_cleanup(&demo); + } + + // Parse Intents into argc, argv + // Use the following key to send arguments, i.e. + // --es args "--validate" + const char key[] = "args"; + char *appTag = (char *)APP_SHORT_NAME; + int argc = 0; + char **argv = get_args(app, key, appTag, &argc); + + __android_log_print(ANDROID_LOG_INFO, appTag, "argc = %i", argc); + for (int i = 0; i < argc; i++) __android_log_print(ANDROID_LOG_INFO, appTag, "argv[%i] = %s", i, argv[i]); + + demo_init(&demo, argc, argv); + + // Free the argv malloc'd by get_args + for (int i = 0; i < argc; i++) free(argv[i]); + + demo.window = (void *)app->window; + demo_init_vk_swapchain(&demo); + demo_prepare(&demo); + initialized = true; + } + break; + } + case APP_CMD_GAINED_FOCUS: { + active = true; + break; + } + case APP_CMD_LOST_FOCUS: { + active = false; + break; + } + } +} + +void android_main(struct android_app *app) { + demo.prepared = false; + + app->onAppCmd = processCommand; + app->onInputEvent = processInput; + + while (1) { + int events; + struct android_poll_source *source; + while (ALooper_pollOnce(active ? 0 : -1, NULL, &events, (void **)&source) >= 0) { + if (source) { + source->process(app, source); + } + + if (app->destroyRequested != 0) { + demo_cleanup(&demo); + return; + } + } + if (initialized && active) { + demo_run(&demo); + } + } +} +#else +int main(int argc, char **argv) { + struct demo demo; + + demo_init(&demo, argc, argv); +#if defined(VK_USE_PLATFORM_XCB_KHR) + demo_create_xcb_window(&demo); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + demo_create_xlib_window(&demo); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo_create_window(&demo); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo_create_directfb_window(&demo); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + demo_create_window(&demo); +#endif + + demo_init_vk_swapchain(&demo); + + demo_prepare(&demo); + +#if defined(VK_USE_PLATFORM_XCB_KHR) + demo_run_xcb(&demo); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + demo_run_xlib(&demo); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo_run(&demo); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo_run_directfb(&demo); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + demo_run_display(&demo); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + demo_run(&demo); +#endif + + demo_cleanup(&demo); + + return validation_error; +} +#endif diff --git a/cube/cube-nc.frag b/cube/cube-nc.frag new file mode 100644 index 000000000..8514f32ed --- /dev/null +++ b/cube/cube-nc.frag @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015-2016 The Khronos Group Inc. + * Copyright (c) 2015-2016 Valve Corporation + * Copyright (c) 2015-2016 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Fragment shader for cube demo + */ + +/* + * Fragment shader for cube demo [SPV_KHR_descriptor_heap] + */ + +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +#extension GL_EXT_descriptor_heap : enable +uniform sampler tex_sampler[]; +uniform texture2D tex[]; + +layout (location = 0) in vec4 texcoord; +layout (location = 1) in vec3 frag_pos; +layout (location = 0) out vec4 uFragColor; + +const vec3 lightDir= vec3(0.424, 0.566, 0.707); + +void main() { + vec3 dX = dFdx(frag_pos); + vec3 dY = dFdy(frag_pos); + vec3 normal = normalize(cross(dX,dY)); + float light = max(0.0, dot(lightDir, normal)); + uFragColor = light * texture(sampler2D(tex[0], tex_sampler[0]), texcoord.xy); +} \ No newline at end of file diff --git a/cube/cube-nc.frag.inc b/cube/cube-nc.frag.inc new file mode 100644 index 000000000..53ed7829b --- /dev/null +++ b/cube/cube-nc.frag.inc @@ -0,0 +1,51 @@ + // 1115.1.0 + 0x07230203,0x00010000,0x0008000b,0x0000003a,0x00000000,0x00020011,0x00000001,0x00020011, + 0x00001179,0x00020011,0x00001408,0x0007000a,0x5f565053,0x5f52484b,0x63736564,0x74706972, + 0x685f726f,0x00706165,0x0008000a,0x5f565053,0x5f52484b,0x79746e75,0x5f646570,0x6e696f70, + 0x73726574,0x00000000,0x0006000b,0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000, + 0x0003000e,0x00000000,0x00000001,0x0008000f,0x00000004,0x00000004,0x6e69616d,0x00000000, + 0x0000000b,0x00000022,0x00000034,0x00030010,0x00000004,0x00000007,0x00030003,0x00000002, + 0x00000190,0x00090004,0x415f4c47,0x735f4252,0x72617065,0x5f657461,0x64616873,0x6f5f7265, + 0x63656a62,0x00007374,0x00090004,0x415f4c47,0x735f4252,0x69646168,0x6c5f676e,0x75676e61, + 0x5f656761,0x70303234,0x006b6361,0x00070004,0x455f4c47,0x645f5458,0x72637365,0x6f747069, + 0x65685f72,0x00007061,0x00040005,0x00000004,0x6e69616d,0x00000000,0x00030005,0x00000009, + 0x00005864,0x00050005,0x0000000b,0x67617266,0x736f705f,0x00000000,0x00030005,0x0000000e, + 0x00005964,0x00040005,0x00000011,0x6d726f6e,0x00006c61,0x00040005,0x00000017,0x6867696c, + 0x00000074,0x00050005,0x00000022,0x61724675,0x6c6f4367,0x0000726f,0x00050005,0x00000025, + 0x67616d69,0x65685f65,0x00007061,0x00060005,0x0000002c,0x706d6173,0x5f72656c,0x70616568, + 0x00000000,0x00050005,0x00000034,0x63786574,0x64726f6f,0x00000000,0x00040047,0x0000000b, + 0x0000001e,0x00000001,0x00040047,0x00000022,0x0000001e,0x00000000,0x00040047,0x00000025, + 0x0000000b,0x00001404,0x00040047,0x0000002c,0x0000000b,0x00001402,0x00040047,0x00000034, + 0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016, + 0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000003,0x00040020,0x00000008, + 0x00000007,0x00000007,0x00040020,0x0000000a,0x00000001,0x00000007,0x0004003b,0x0000000a, + 0x0000000b,0x00000001,0x00040020,0x00000016,0x00000007,0x00000006,0x0004002b,0x00000006, + 0x00000018,0x00000000,0x0004002b,0x00000006,0x00000019,0x3ed91687,0x0004002b,0x00000006, + 0x0000001a,0x3f10e560,0x0004002b,0x00000006,0x0000001b,0x3f34fdf4,0x0006002c,0x00000007, + 0x0000001c,0x00000019,0x0000001a,0x0000001b,0x00040017,0x00000020,0x00000006,0x00000004, + 0x00040020,0x00000021,0x00000003,0x00000020,0x0004003b,0x00000021,0x00000022,0x00000003, + 0x00031141,0x00000024,0x00000000,0x00041142,0x00000024,0x00000025,0x00000000,0x00040015, + 0x00000026,0x00000020,0x00000001,0x0004002b,0x00000026,0x00000027,0x00000000,0x00090019, + 0x00000028,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,0x00000000, + 0x0003001d,0x00000029,0x00000028,0x00041142,0x00000024,0x0000002c,0x00000000,0x0002001a, + 0x0000002d,0x0003001d,0x0000002e,0x0000002d,0x0003001b,0x00000031,0x00000028,0x00040020, + 0x00000033,0x00000001,0x00000020,0x0004003b,0x00000033,0x00000034,0x00000001,0x00040017, + 0x00000035,0x00000006,0x00000002,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003, + 0x000200f8,0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003b,0x00000008, + 0x0000000e,0x00000007,0x0004003b,0x00000008,0x00000011,0x00000007,0x0004003b,0x00000016, + 0x00000017,0x00000007,0x0004003d,0x00000007,0x0000000c,0x0000000b,0x000400cf,0x00000007, + 0x0000000d,0x0000000c,0x0003003e,0x00000009,0x0000000d,0x0004003d,0x00000007,0x0000000f, + 0x0000000b,0x000400d0,0x00000007,0x00000010,0x0000000f,0x0003003e,0x0000000e,0x00000010, + 0x0004003d,0x00000007,0x00000012,0x00000009,0x0004003d,0x00000007,0x00000013,0x0000000e, + 0x0007000c,0x00000007,0x00000014,0x00000001,0x00000044,0x00000012,0x00000013,0x0006000c, + 0x00000007,0x00000015,0x00000001,0x00000045,0x00000014,0x0003003e,0x00000011,0x00000015, + 0x0004003d,0x00000007,0x0000001d,0x00000011,0x00050094,0x00000006,0x0000001e,0x0000001c, + 0x0000001d,0x0007000c,0x00000006,0x0000001f,0x00000001,0x00000028,0x00000018,0x0000001e, + 0x0003003e,0x00000017,0x0000001f,0x0004003d,0x00000006,0x00000023,0x00000017,0x00061143, + 0x00000024,0x0000002a,0x00000029,0x00000025,0x00000027,0x0004003d,0x00000028,0x0000002b, + 0x0000002a,0x00061143,0x00000024,0x0000002f,0x0000002e,0x0000002c,0x00000027,0x0004003d, + 0x0000002d,0x00000030,0x0000002f,0x00050056,0x00000031,0x00000032,0x0000002b,0x00000030, + 0x0004003d,0x00000020,0x00000036,0x00000034,0x0007004f,0x00000035,0x00000037,0x00000036, + 0x00000036,0x00000000,0x00000001,0x00050057,0x00000020,0x00000038,0x00000032,0x00000037, + 0x0005008e,0x00000020,0x00000039,0x00000038,0x00000023,0x0003003e,0x00000022,0x00000039, + 0x000100fd,0x00010038 diff --git a/cube/cube-nc.vert b/cube/cube-nc.vert new file mode 100644 index 000000000..9c000d838 --- /dev/null +++ b/cube/cube-nc.vert @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015-2016 The Khronos Group Inc. + * Copyright (c) 2015-2016 Valve Corporation + * Copyright (c) 2015-2016 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Vertex shader used by Cube demo. + */ + +/* + * Vertex shader used by Cube demo. [SPV_KHR_descriptor_heap] + */ + +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +#extension GL_EXT_descriptor_heap : enable + +uniform UniformBuffer { + mat4 MVP; + vec4 position[12*3]; + vec4 attr[12*3]; +} ubuf[]; + +layout (location = 0) out vec4 texcoord; +layout (location = 1) out vec3 frag_pos; + +void main() +{ + texcoord = ubuf[0].attr[gl_VertexIndex]; + gl_Position = ubuf[0].MVP * ubuf[0].position[gl_VertexIndex]; + frag_pos = gl_Position.xyz; +} + diff --git a/cube/cube-nc.vert.inc b/cube/cube-nc.vert.inc new file mode 100644 index 000000000..391ebf9d6 --- /dev/null +++ b/cube/cube-nc.vert.inc @@ -0,0 +1,76 @@ + // 1115.1.0 + 0x07230203,0x00010000,0x0008000b,0x0000003e,0x00000000,0x00020011,0x00000001,0x00020011, + 0x00001179,0x00020011,0x00001408,0x0007000a,0x5f565053,0x5f52484b,0x63736564,0x74706972, + 0x685f726f,0x00706165,0x0008000a,0x5f565053,0x5f52484b,0x79746e75,0x5f646570,0x6e696f70, + 0x73726574,0x00000000,0x0006000b,0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000, + 0x0003000e,0x00000000,0x00000001,0x0009000f,0x00000000,0x00000004,0x6e69616d,0x00000000, + 0x00000009,0x0000001b,0x00000023,0x0000003a,0x00030003,0x00000002,0x00000190,0x00090004, + 0x415f4c47,0x735f4252,0x72617065,0x5f657461,0x64616873,0x6f5f7265,0x63656a62,0x00007374, + 0x00090004,0x415f4c47,0x735f4252,0x69646168,0x6c5f676e,0x75676e61,0x5f656761,0x70303234, + 0x006b6361,0x00070004,0x455f4c47,0x645f5458,0x72637365,0x6f747069,0x65685f72,0x00007061, + 0x00040005,0x00000004,0x6e69616d,0x00000000,0x00050005,0x00000009,0x63786574,0x64726f6f, + 0x00000000,0x00050005,0x0000000b,0x66667562,0x685f7265,0x00706165,0x00060005,0x00000013, + 0x66696e55,0x426d726f,0x65666675,0x00000072,0x00040006,0x00000013,0x00000000,0x0050564d, + 0x00060006,0x00000013,0x00000001,0x69736f70,0x6e6f6974,0x00000000,0x00050006,0x00000013, + 0x00000002,0x72747461,0x00000000,0x00060005,0x0000001b,0x565f6c67,0x65747265,0x646e4978, + 0x00007865,0x00060005,0x00000021,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006, + 0x00000021,0x00000000,0x505f6c67,0x7469736f,0x006e6f69,0x00070006,0x00000021,0x00000001, + 0x505f6c67,0x746e696f,0x657a6953,0x00000000,0x00070006,0x00000021,0x00000002,0x435f6c67, + 0x4470696c,0x61747369,0x0065636e,0x00030005,0x00000023,0x00000000,0x00060005,0x00000026, + 0x66696e55,0x426d726f,0x65666675,0x00000072,0x00040006,0x00000026,0x00000000,0x0050564d, + 0x00060006,0x00000026,0x00000001,0x69736f70,0x6e6f6974,0x00000000,0x00050006,0x00000026, + 0x00000002,0x72747461,0x00000000,0x00060005,0x0000002e,0x66696e55,0x426d726f,0x65666675, + 0x00000072,0x00040006,0x0000002e,0x00000000,0x0050564d,0x00060006,0x0000002e,0x00000001, + 0x69736f70,0x6e6f6974,0x00000000,0x00050006,0x0000002e,0x00000002,0x72747461,0x00000000, + 0x00050005,0x0000003a,0x67617266,0x736f705f,0x00000000,0x00040047,0x00000009,0x0000001e, + 0x00000000,0x00040047,0x0000000b,0x0000000b,0x00001403,0x00040047,0x00000011,0x00000006, + 0x00000010,0x00040047,0x00000012,0x00000006,0x00000010,0x00030047,0x00000013,0x00000002, + 0x00040048,0x00000013,0x00000000,0x00000005,0x00050048,0x00000013,0x00000000,0x00000007, + 0x00000010,0x00050048,0x00000013,0x00000000,0x00000023,0x00000000,0x00050048,0x00000013, + 0x00000001,0x00000023,0x00000040,0x00050048,0x00000013,0x00000002,0x00000023,0x00000280, + 0x00040047,0x0000001b,0x0000000b,0x0000002a,0x00030047,0x00000021,0x00000002,0x00050048, + 0x00000021,0x00000000,0x0000000b,0x00000000,0x00050048,0x00000021,0x00000001,0x0000000b, + 0x00000001,0x00050048,0x00000021,0x00000002,0x0000000b,0x00000003,0x00040047,0x00000024, + 0x00000006,0x00000010,0x00040047,0x00000025,0x00000006,0x00000010,0x00030047,0x00000026, + 0x00000002,0x00040048,0x00000026,0x00000000,0x00000005,0x00050048,0x00000026,0x00000000, + 0x00000007,0x00000010,0x00050048,0x00000026,0x00000000,0x00000023,0x00000000,0x00050048, + 0x00000026,0x00000001,0x00000023,0x00000040,0x00050048,0x00000026,0x00000002,0x00000023, + 0x00000280,0x00040047,0x0000002c,0x00000006,0x00000010,0x00040047,0x0000002d,0x00000006, + 0x00000010,0x00030047,0x0000002e,0x00000002,0x00040048,0x0000002e,0x00000000,0x00000005, + 0x00050048,0x0000002e,0x00000000,0x00000007,0x00000010,0x00050048,0x0000002e,0x00000000, + 0x00000023,0x00000000,0x00050048,0x0000002e,0x00000001,0x00000023,0x00000040,0x00050048, + 0x0000002e,0x00000002,0x00000023,0x00000280,0x00040047,0x0000003a,0x0000001e,0x00000001, + 0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020, + 0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003,0x00000007, + 0x0004003b,0x00000008,0x00000009,0x00000003,0x00031141,0x0000000a,0x00000000,0x00041142, + 0x0000000a,0x0000000b,0x00000000,0x00040015,0x0000000c,0x00000020,0x00000001,0x0004002b, + 0x0000000c,0x0000000d,0x00000000,0x00040018,0x0000000e,0x00000007,0x00000004,0x00040015, + 0x0000000f,0x00000020,0x00000000,0x0004002b,0x0000000f,0x00000010,0x00000024,0x0004001c, + 0x00000011,0x00000007,0x00000010,0x0004001c,0x00000012,0x00000007,0x00000010,0x0005001e, + 0x00000013,0x0000000e,0x00000011,0x00000012,0x0003001d,0x00000014,0x00000013,0x00031141, + 0x00000015,0x00000002,0x000313fb,0x00000017,0x00000002,0x0004002b,0x0000000c,0x00000019, + 0x00000002,0x00040020,0x0000001a,0x00000001,0x0000000c,0x0004003b,0x0000001a,0x0000001b, + 0x00000001,0x0004002b,0x0000000f,0x0000001f,0x00000001,0x0004001c,0x00000020,0x00000006, + 0x0000001f,0x0005001e,0x00000021,0x00000007,0x00000006,0x00000020,0x00040020,0x00000022, + 0x00000003,0x00000021,0x0004003b,0x00000022,0x00000023,0x00000003,0x0004001c,0x00000024, + 0x00000007,0x00000010,0x0004001c,0x00000025,0x00000007,0x00000010,0x0005001e,0x00000026, + 0x0000000e,0x00000024,0x00000025,0x0003001d,0x00000027,0x00000026,0x0004001c,0x0000002c, + 0x00000007,0x00000010,0x0004001c,0x0000002d,0x00000007,0x00000010,0x0005001e,0x0000002e, + 0x0000000e,0x0000002c,0x0000002d,0x0003001d,0x0000002f,0x0000002e,0x0004002b,0x0000000c, + 0x00000032,0x00000001,0x00040017,0x00000038,0x00000006,0x00000003,0x00040020,0x00000039, + 0x00000003,0x00000038,0x0004003b,0x00000039,0x0000003a,0x00000003,0x00050036,0x00000002, + 0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x00061143,0x00000015,0x00000016, + 0x00000014,0x0000000b,0x0000000d,0x000513ff,0x00000015,0x00000018,0x00000016,0x00000017, + 0x0004003d,0x0000000c,0x0000001c,0x0000001b,0x00071143,0x00000015,0x0000001d,0x00000013, + 0x00000018,0x00000019,0x0000001c,0x0004003d,0x00000007,0x0000001e,0x0000001d,0x0003003e, + 0x00000009,0x0000001e,0x00061143,0x00000015,0x00000028,0x00000027,0x0000000b,0x0000000d, + 0x000513ff,0x00000015,0x00000029,0x00000028,0x00000017,0x00061143,0x00000015,0x0000002a, + 0x00000026,0x00000029,0x0000000d,0x0004003d,0x0000000e,0x0000002b,0x0000002a,0x00061143, + 0x00000015,0x00000030,0x0000002f,0x0000000b,0x0000000d,0x000513ff,0x00000015,0x00000031, + 0x00000030,0x00000017,0x0004003d,0x0000000c,0x00000033,0x0000001b,0x00071143,0x00000015, + 0x00000034,0x0000002e,0x00000031,0x00000032,0x00000033,0x0004003d,0x00000007,0x00000035, + 0x00000034,0x00050091,0x00000007,0x00000036,0x0000002b,0x00000035,0x00050041,0x00000008, + 0x00000037,0x00000023,0x0000000d,0x0003003e,0x00000037,0x00000036,0x00050041,0x00000008, + 0x0000003b,0x00000023,0x0000000d,0x0004003d,0x00000007,0x0000003c,0x0000003b,0x0008004f, + 0x00000038,0x0000003d,0x0000003c,0x0000003c,0x00000000,0x00000001,0x00000002,0x0003003e, + 0x0000003a,0x0000003d,0x000100fd,0x00010038 diff --git a/cube/cube.back b/cube/cube.back new file mode 100644 index 000000000..f8f4c68b1 --- /dev/null +++ b/cube/cube.back @@ -0,0 +1,4804 @@ +/* + * Copyright (c) 2015-2019 The Khronos Group Inc. + * Copyright (c) 2015-2019 Valve Corporation + * Copyright (c) 2015-2019 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Chia-I Wu + * Author: Courtney Goeltzenleuchter + * Author: Ian Elliott + * Author: Ian Elliott + * Author: Jon Ashburn + * Author: Gwan-gyeong Mun + * Author: Tony Barbour + * Author: Bill Hollings + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_XCB_KHR) +#include +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) +#include +#include "xdg-shell-client-header.h" +#include "xdg-decoration-client-header.h" +#endif + +#ifdef _WIN32 +#ifdef _MSC_VER +#pragma comment(linker, "/subsystem:windows") +#endif // MSVC +#define APP_NAME_STR_LEN 80 +#endif // _WIN32 + +// Volk requires VK_NO_PROTOTYPES before including vulkan.h +#define VK_NO_PROTOTYPES +#include +#define VOLK_IMPLEMENTATION +#include "volk.h" + +#include "linmath.h" +#include "object_type_string_helper.h" +#include "vk_khr_descriptor_heap.h" + +#include "gettime.h" +#include "inttypes.h" +#define MILLION 1000000L +#define BILLION 1000000000L + +#define DEMO_TEXTURE_COUNT 1 +#define APP_SHORT_NAME "vkcube" +#define APP_LONG_NAME "Vulkan Cube" + +// Allow a maximum of two outstanding presentation operations. +#define FRAME_LAG 2 + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#if defined(NDEBUG) && defined(__GNUC__) +#define U_ASSERT_ONLY __attribute__((unused)) +#else +#define U_ASSERT_ONLY +#endif + +#if defined(__GNUC__) +#define UNUSED __attribute__((unused)) +#else +#define UNUSED +#endif + +#define DESCRIPTOR_HEAP 0 + +#ifdef _WIN32 +bool in_callback = false; +#define ERR_EXIT(err_msg, err_class) \ + do { \ + if (!demo->suppress_popups) MessageBox(NULL, err_msg, err_class, MB_OK); \ + exit(1); \ + } while (0) +void DbgMsg(char *fmt, ...) { + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + fflush(stdout); +} + +#elif defined __ANDROID__ +#include +#define ERR_EXIT(err_msg, err_class) \ + do { \ + ((void)__android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", err_msg)); \ + exit(1); \ + } while (0) +#ifdef VARARGS_WORKS_ON_ANDROID +void DbgMsg(const char *fmt, ...) { + va_list va; + va_start(va, fmt); + __android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", fmt, va); + va_end(va); +} +#else // VARARGS_WORKS_ON_ANDROID +#define DbgMsg(fmt, ...) \ + do { \ + ((void)__android_log_print(ANDROID_LOG_INFO, "Vulkan Cube", fmt, ##__VA_ARGS__)); \ + } while (0) +#endif // VARARGS_WORKS_ON_ANDROID +#else +#define ERR_EXIT(err_msg, err_class) \ + do { \ + printf("%s\n", err_msg); \ + fflush(stdout); \ + exit(1); \ + } while (0) +void DbgMsg(char *fmt, ...) { + va_list va; + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); + fflush(stdout); +} +#endif + +/* + * structure to track all objects related to a texture. + */ +struct texture_object { + VkSampler sampler; + + VkImage image; + VkBuffer buffer; + VkImageLayout imageLayout; + + VkMemoryAllocateInfo mem_alloc; + VkDeviceMemory mem; + VkImageView view; + int32_t tex_width, tex_height; +}; + +static char *tex_files[] = {"lunarg.ppm"}; + +static int validation_error = 0; + +struct vktexcube_vs_uniform { + // Must start with MVP + float mvp[4][4]; + float position[12 * 3][4]; + float attr[12 * 3][4]; +}; + +//-------------------------------------------------------------------------------------- +// Mesh and VertexFormat Data +//-------------------------------------------------------------------------------------- +// clang-format off +static const float g_vertex_buffer_data[] = { + -1.0f,-1.0f,-1.0f, // -X side + -1.0f,-1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + + -1.0f,-1.0f,-1.0f, // -Z side + 1.0f, 1.0f,-1.0f, + 1.0f,-1.0f,-1.0f, + -1.0f,-1.0f,-1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + + -1.0f,-1.0f,-1.0f, // -Y side + 1.0f,-1.0f,-1.0f, + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f,-1.0f, + 1.0f,-1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + + -1.0f, 1.0f,-1.0f, // +Y side + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f,-1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f,-1.0f, + + 1.0f, 1.0f,-1.0f, // +X side + 1.0f, 1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f,-1.0f,-1.0f, + 1.0f, 1.0f,-1.0f, + + -1.0f, 1.0f, 1.0f, // +Z side + -1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f,-1.0f, 1.0f, + 1.0f,-1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, +}; + +static const float g_uv_buffer_data[] = { + 0.0f, 1.0f, // -X side + 1.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + 0.0f, 1.0f, + + 1.0f, 1.0f, // -Z side + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + + 1.0f, 0.0f, // -Y side + 1.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + + 1.0f, 0.0f, // +Y side + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + + 1.0f, 0.0f, // +X side + 0.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + + 0.0f, 0.0f, // +Z side + 0.0f, 1.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, +}; +// clang-format on + +void dumpMatrix(const char *note, mat4x4 MVP) { + int i; + + printf("%s: \n", note); + for (i = 0; i < 4; i++) { + printf("%f, %f, %f, %f\n", MVP[i][0], MVP[i][1], MVP[i][2], MVP[i][3]); + } + printf("\n"); + fflush(stdout); +} + +void dumpVec4(const char *note, vec4 vector) { + printf("%s: \n", note); + printf("%f, %f, %f, %f\n", vector[0], vector[1], vector[2], vector[3]); + printf("\n"); + fflush(stdout); +} + +char const *to_string(VkPhysicalDeviceType const type) { + switch (type) { + case VK_PHYSICAL_DEVICE_TYPE_OTHER: + return "Other"; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + return "IntegratedGpu"; + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + return "DiscreteGpu"; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + return "VirtualGpu"; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + return "Cpu"; + default: + return "Unknown"; + } +} + +typedef struct { + VkImage image; + VkCommandBuffer cmd; + VkCommandBuffer graphics_to_present_cmd; + VkImageView view; + VkBuffer uniform_buffer; + VkDeviceMemory uniform_memory; + void *uniform_memory_ptr; + VkFramebuffer framebuffer; + VkDescriptorSet descriptor_set; +} SwapchainImageResources; + +struct demo { +#if defined(VK_USE_PLATFORM_WIN32_KHR) +#define APP_NAME_STR_LEN 80 + HINSTANCE connection; // hInstance - Windows Instance + char name[APP_NAME_STR_LEN]; // Name to put on the window/icon + HWND window; // hWnd - window handle + POINT minsize; // minimum window size +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + Display *display; + Window xlib_window; + Atom xlib_wm_delete_window; +#elif defined(VK_USE_PLATFORM_XCB_KHR) + Display *display; + xcb_connection_t *connection; + xcb_screen_t *screen; + xcb_window_t xcb_window; + xcb_intern_atom_reply_t *atom_wm_delete_window; +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_surface *window; + struct xdg_wm_base *xdg_wm_base; + struct zxdg_decoration_manager_v1 *xdg_decoration_mgr; + struct zxdg_toplevel_decoration_v1 *toplevel_decoration; + struct xdg_surface *xdg_surface; + int xdg_surface_has_been_configured; + struct xdg_toplevel *xdg_toplevel; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + IDirectFB *dfb; + IDirectFBSurface *window; + IDirectFBEventBuffer *event_buffer; +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + struct ANativeWindow *window; +#elif defined(VK_USE_PLATFORM_METAL_EXT) + void *caMetalLayer; +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + screen_context_t screen_context; + screen_window_t screen_window; + screen_event_t screen_event; +#endif + + VkSurfaceKHR surface; + bool prepared; + bool use_staging_buffer; + bool separate_present_queue; + bool is_minimized; + bool invalid_gpu_selection; + int32_t gpu_number; + + bool VK_KHR_incremental_present_enabled; + + bool VK_GOOGLE_display_timing_enabled; + bool syncd_with_actual_presents; + uint64_t refresh_duration; + uint64_t refresh_duration_multiplier; + uint64_t target_IPD; // image present duration (inverse of frame rate) + uint64_t prev_desired_present_time; + uint32_t next_present_id; + uint32_t last_early_id; // 0 if no early images + uint32_t last_late_id; // 0 if no late images + + VkInstance inst; + VkPhysicalDevice gpu; + VkDevice device; + VkQueue graphics_queue; + VkQueue present_queue; + uint32_t graphics_queue_family_index; + uint32_t present_queue_family_index; + VkSemaphore image_acquired_semaphores[FRAME_LAG]; + VkSemaphore draw_complete_semaphores[FRAME_LAG]; + VkSemaphore image_ownership_semaphores[FRAME_LAG]; + VkPhysicalDeviceProperties gpu_props; + VkQueueFamilyProperties *queue_props; + +#if DESCRIPTOR_HEAP + VkPhysicalDeviceDescriptorHeapFeaturesKHR descriptorHeapFeature; + VkPhysicalDeviceDescriptorHeapPropertiesKHR descriptorHeapProperties; + + // 0:sampler heap, 1:buffer heap; 2:image heap; + VkBuffer descHeap[3]; + + VkDeviceMemory descHeapMemory[3]; + typedef enum VkHeapType { + sampler = 0x0, + buffer = 0x1, + image = 0x2 + }; +#endif + + VkPhysicalDeviceMemoryProperties memory_properties; + + uint32_t enabled_extension_count; + uint32_t enabled_layer_count; + char *extension_names[64]; + char *enabled_layers[64]; + + int width, height; + VkFormat format; + VkColorSpaceKHR color_space; + + uint32_t swapchainImageCount; + VkSwapchainKHR swapchain; + SwapchainImageResources *swapchain_image_resources; + VkPresentModeKHR presentMode; + VkFence fences[FRAME_LAG]; + int frame_index; + bool first_swapchain_frame; + + VkCommandPool cmd_pool; + VkCommandPool present_cmd_pool; + + struct { + VkFormat format; + + VkImage image; + VkMemoryAllocateInfo mem_alloc; + VkDeviceMemory mem; + VkImageView view; + } depth; + + struct texture_object textures[DEMO_TEXTURE_COUNT]; + struct texture_object staging_texture; + + VkCommandBuffer cmd; // Buffer for initialization commands + VkPipelineLayout pipeline_layout; + VkDescriptorSetLayout desc_layout; + VkPipelineCache pipelineCache; + VkRenderPass render_pass; + VkPipeline pipeline; + + mat4x4 projection_matrix; + mat4x4 view_matrix; + mat4x4 model_matrix; + + float spin_angle; + float spin_increment; + bool pause; + + VkShaderModule vert_shader_module; + VkShaderModule frag_shader_module; + + VkDescriptorPool desc_pool; + + bool quit; + int32_t curFrame; + int32_t frameCount; + bool validate; + bool validate_checks_disabled; + bool use_break; + bool suppress_popups; + bool force_errors; + + VkDebugUtilsMessengerEXT dbg_messenger; + + uint32_t current_buffer; + uint32_t queue_family_count; +}; + +VKAPI_ATTR VkBool32 VKAPI_CALL debug_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, + void *pUserData) { + char prefix[64] = ""; + char *message = (char *)malloc(strlen(pCallbackData->pMessage) + 5000); + assert(message); + struct demo *demo = (struct demo *)pUserData; + + if (demo->use_break) { +#ifndef WIN32 + raise(SIGTRAP); +#else + DebugBreak(); +#endif + } + + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + strcat(prefix, "VERBOSE : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + strcat(prefix, "INFO : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + strcat(prefix, "WARNING : "); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + strcat(prefix, "ERROR : "); + } + + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { + strcat(prefix, "GENERAL"); + } else { + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + strcat(prefix, "VALIDATION"); + validation_error = 1; + } + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { + if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + strcat(prefix, "|"); + } + strcat(prefix, "PERFORMANCE"); + } + } + + sprintf(message, "%s - Message Id Number: %d | Message Id Name: %s\n\t%s\n", prefix, pCallbackData->messageIdNumber, + pCallbackData->pMessageIdName == NULL ? "" : pCallbackData->pMessageIdName, pCallbackData->pMessage); + if (pCallbackData->objectCount > 0) { + char tmp_message[500]; + sprintf(tmp_message, "\n\tObjects - %d\n", pCallbackData->objectCount); + strcat(message, tmp_message); + for (uint32_t object = 0; object < pCallbackData->objectCount; ++object) { + sprintf(tmp_message, "\t\tObject[%d] - %s", object, string_VkObjectType(pCallbackData->pObjects[object].objectType)); + strcat(message, tmp_message); + + VkObjectType t = pCallbackData->pObjects[object].objectType; + if (t == VK_OBJECT_TYPE_INSTANCE || t == VK_OBJECT_TYPE_PHYSICAL_DEVICE || t == VK_OBJECT_TYPE_DEVICE || + t == VK_OBJECT_TYPE_COMMAND_BUFFER || t == VK_OBJECT_TYPE_QUEUE) { + sprintf(tmp_message, ", Handle %p", (void *)(uintptr_t)(pCallbackData->pObjects[object].objectHandle)); + strcat(message, tmp_message); + } else { + sprintf(tmp_message, ", Handle Ox%" PRIx64, (pCallbackData->pObjects[object].objectHandle)); + strcat(message, tmp_message); + } + + if (NULL != pCallbackData->pObjects[object].pObjectName && strlen(pCallbackData->pObjects[object].pObjectName) > 0) { + sprintf(tmp_message, ", Name \"%s\"", pCallbackData->pObjects[object].pObjectName); + strcat(message, tmp_message); + } + sprintf(tmp_message, "\n"); + strcat(message, tmp_message); + } + } + if (pCallbackData->cmdBufLabelCount > 0) { + char tmp_message[500]; + sprintf(tmp_message, "\n\tCommand Buffer Labels - %d\n", pCallbackData->cmdBufLabelCount); + strcat(message, tmp_message); + for (uint32_t cmd_buf_label = 0; cmd_buf_label < pCallbackData->cmdBufLabelCount; ++cmd_buf_label) { + sprintf(tmp_message, "\t\tLabel[%d] - %s { %f, %f, %f, %f}\n", cmd_buf_label, + pCallbackData->pCmdBufLabels[cmd_buf_label].pLabelName, pCallbackData->pCmdBufLabels[cmd_buf_label].color[0], + pCallbackData->pCmdBufLabels[cmd_buf_label].color[1], pCallbackData->pCmdBufLabels[cmd_buf_label].color[2], + pCallbackData->pCmdBufLabels[cmd_buf_label].color[3]); + strcat(message, tmp_message); + } + } + +#ifdef _WIN32 + + in_callback = true; + if (!demo->suppress_popups) MessageBox(NULL, message, "Alert", MB_OK); + in_callback = false; + +#elif defined(ANDROID) + + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + __android_log_print(ANDROID_LOG_WARN, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + __android_log_print(ANDROID_LOG_ERROR, APP_SHORT_NAME, "%s", message); + } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + __android_log_print(ANDROID_LOG_VERBOSE, APP_SHORT_NAME, "%s", message); + } else { + __android_log_print(ANDROID_LOG_INFO, APP_SHORT_NAME, "%s", message); + } + +#else + + printf("%s\n", message); + fflush(stdout); + +#endif + + free(message); + + // Don't bail out, but keep going. + return false; +} + +bool ActualTimeLate(uint64_t desired, uint64_t actual, uint64_t rdur) { + // The desired time was the earliest time that the present should have + // occured. In almost every case, the actual time should be later than the + // desired time. We should only consider the actual time "late" if it is + // after "desired + rdur". + if (actual <= desired) { + // The actual time was before or equal to the desired time. This will + // probably never happen, but in case it does, return false since the + // present was obviously NOT late. + return false; + } + uint64_t deadline = desired + rdur; + if (actual > deadline) { + return true; + } else { + return false; + } +} +bool CanPresentEarlier(uint64_t earliest, uint64_t actual, uint64_t margin, uint64_t rdur) { + if (earliest < actual) { + // Consider whether this present could have occured earlier. Make sure + // that earliest time was at least 2msec earlier than actual time, and + // that the margin was at least 2msec: + uint64_t diff = actual - earliest; + if ((diff >= (2 * MILLION)) && (margin >= (2 * MILLION))) { + // This present could have occured earlier because both: 1) the + // earliest time was at least 2 msec before actual time, and 2) the + // margin was at least 2msec. + return true; + } + } + return false; +} + +// Forward declarations: +static void demo_resize(struct demo *demo); +static void demo_create_surface(struct demo *demo); + +#if defined(__GNUC__) || defined(__clang__) +#define DECORATE_PRINTF(_fmt_argnum, _first_param_num) __attribute__((format(printf, _fmt_argnum, _first_param_num))) +#else +#define DECORATE_PRINTF(_fmt_num, _first_param_num) +#endif + +DECORATE_PRINTF(4, 5) +static void demo_name_object(struct demo *demo, VkObjectType object_type, uint64_t vulkan_handle, const char *format, ...) { + if (!demo->validate) { + return; + } + VkResult U_ASSERT_ONLY err; + char name[1024]; + va_list argptr; + va_start(argptr, format); + vsnprintf(name, sizeof(name), format, argptr); + va_end(argptr); + name[sizeof(name) - 1] = '\0'; + + VkDebugUtilsObjectNameInfoEXT obj_name = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .pNext = NULL, + .objectType = object_type, + .objectHandle = vulkan_handle, + .pObjectName = name, + }; + err = vkSetDebugUtilsObjectNameEXT(demo->device, &obj_name); + assert(!err); +} + +DECORATE_PRINTF(4, 5) +static void demo_push_cb_label(struct demo *demo, VkCommandBuffer cb, const float *color, const char *format, ...) { + if (!demo->validate) { + return; + } + char name[1024]; + va_list argptr; + va_start(argptr, format); + vsnprintf(name, sizeof(name), format, argptr); + va_end(argptr); + name[sizeof(name) - 1] = '\0'; + + VkDebugUtilsLabelEXT label = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, + .pNext = NULL, + .pLabelName = name, + }; + if (color) { + memcpy(label.color, color, sizeof(label.color)); + } + + vkCmdBeginDebugUtilsLabelEXT(cb, &label); +} + +static void demo_pop_cb_label(struct demo *demo, VkCommandBuffer cb) { + if (!demo->validate) { + return; + } + vkCmdEndDebugUtilsLabelEXT(cb); +} + +static bool memory_type_from_properties(struct demo *demo, uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex) { + // Search memtypes to find first index with those properties + for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) { + if ((typeBits & 1) == 1) { + // Type is available, does it match user properties? + if ((demo->memory_properties.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask) { + *typeIndex = i; + return true; + } + } + typeBits >>= 1; + } + // No memory types matched, return failure + return false; +} + +static void demo_flush_init_cmd(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + // This function could get called twice if the texture uses a staging buffer + // In that case the second call should be ignored + if (demo->cmd == VK_NULL_HANDLE) return; + + err = vkEndCommandBuffer(demo->cmd); + assert(!err); + + VkFence fence; + VkFenceCreateInfo fence_ci = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = 0}; + if (demo->force_errors) { + // Remove sType to intentionally force validation layer errors. + fence_ci.sType = 0; + } + err = vkCreateFence(demo->device, &fence_ci, NULL, &fence); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_FENCE, (uint64_t)fence, "InitFence"); + + const VkCommandBuffer cmd_bufs[] = {demo->cmd}; + VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 0, + .pWaitSemaphores = NULL, + .pWaitDstStageMask = NULL, + .commandBufferCount = 1, + .pCommandBuffers = cmd_bufs, + .signalSemaphoreCount = 0, + .pSignalSemaphores = NULL}; + + err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, fence); + assert(!err); + + err = vkWaitForFences(demo->device, 1, &fence, VK_TRUE, UINT64_MAX); + assert(!err); + + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, cmd_bufs); + vkDestroyFence(demo->device, fence, NULL); + demo->cmd = VK_NULL_HANDLE; +} + +static void demo_set_image_layout(struct demo *demo, VkImage image, VkImageAspectFlags aspectMask, VkImageLayout old_image_layout, + VkImageLayout new_image_layout, VkAccessFlagBits srcAccessMask, VkPipelineStageFlags src_stages, + VkPipelineStageFlags dest_stages) { + assert(demo->cmd); + + VkImageMemoryBarrier image_memory_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = srcAccessMask, + .dstAccessMask = 0, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .oldLayout = old_image_layout, + .newLayout = new_image_layout, + .image = image, + .subresourceRange = {aspectMask, 0, 1, 0, 1}}; + + switch (new_image_layout) { + case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: + /* Make sure anything that was copying from this image has completed */ + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + break; + + case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: + image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + break; + + case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: + image_memory_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + break; + + default: + image_memory_barrier.dstAccessMask = 0; + break; + } + + VkImageMemoryBarrier *pmemory_barrier = &image_memory_barrier; + + vkCmdPipelineBarrier(demo->cmd, src_stages, dest_stages, 0, 0, NULL, 0, NULL, 1, pmemory_barrier); +} + +static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { + const VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = NULL, + }; + const VkClearValue clear_values[2] = { + [0] = {.color.float32 = {0.2f, 0.2f, 0.2f, 0.2f}}, + [1] = {.depthStencil = {1.0f, 0}}, + }; + const VkRenderPassBeginInfo rp_begin = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = NULL, + .renderPass = demo->render_pass, + .framebuffer = demo->swapchain_image_resources[demo->current_buffer].framebuffer, + .renderArea.offset.x = 0, + .renderArea.offset.y = 0, + .renderArea.extent.width = demo->width, + .renderArea.extent.height = demo->height, + .clearValueCount = 2, + .pClearValues = clear_values, + }; + VkResult U_ASSERT_ONLY err; + + err = vkBeginCommandBuffer(cmd_buf, &cmd_buf_info); + + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)cmd_buf, "CubeDrawCommandBuf"); + + const float begin_color[4] = {0.4f, 0.3f, 0.2f, 0.1f}; + demo_push_cb_label(demo, cmd_buf, begin_color, "DrawBegin"); + + assert(!err); + vkCmdBeginRenderPass(cmd_buf, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); + + const float renderpass_color[4] = {8.4f, 7.3f, 6.2f, 7.1f}; + demo_push_cb_label(demo, cmd_buf, renderpass_color, "InsideRenderPass"); + + vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline); + vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline_layout, 0, 1, + &demo->swapchain_image_resources[demo->current_buffer].descriptor_set, 0, NULL); + VkViewport viewport; + memset(&viewport, 0, sizeof(viewport)); + float viewport_dimension; + if (demo->width < demo->height) { + viewport_dimension = (float)demo->width; + viewport.y = (demo->height - demo->width) / 2.0f; + } else { + viewport_dimension = (float)demo->height; + viewport.x = (demo->width - demo->height) / 2.0f; + } + viewport.height = viewport_dimension; + viewport.width = viewport_dimension; + viewport.minDepth = (float)0.0f; + viewport.maxDepth = (float)1.0f; + vkCmdSetViewport(cmd_buf, 0, 1, &viewport); + + VkRect2D scissor; + memset(&scissor, 0, sizeof(scissor)); + scissor.extent.width = demo->width; + scissor.extent.height = demo->height; + scissor.offset.x = 0; + scissor.offset.y = 0; + vkCmdSetScissor(cmd_buf, 0, 1, &scissor); + + const float draw_color[4] = {-0.4f, -0.3f, -0.2f, -0.1f}; + demo_push_cb_label(demo, cmd_buf, draw_color, "ActualDraw"); + vkCmdDraw(cmd_buf, 12 * 3, 1, 0, 0); + demo_pop_cb_label(demo, cmd_buf); + + // Note that ending the renderpass changes the image's layout from + // COLOR_ATTACHMENT_OPTIMAL to PRESENT_SRC_KHR + vkCmdEndRenderPass(cmd_buf); + demo_pop_cb_label(demo, cmd_buf); + + if (demo->separate_present_queue) { + // We have to transfer ownership from the graphics queue family to the + // present queue family to be able to present. Note that we don't have + // to transfer from present queue family back to graphics queue family at + // the start of the next frame because we don't care about the image's + // contents at that point. + VkImageMemoryBarrier image_ownership_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = demo->graphics_queue_family_index, + .dstQueueFamilyIndex = demo->present_queue_family_index, + .image = demo->swapchain_image_resources[demo->current_buffer].image, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, + NULL, 1, &image_ownership_barrier); + } + demo_pop_cb_label(demo, cmd_buf); + err = vkEndCommandBuffer(cmd_buf); + assert(!err); +} + +void demo_build_image_ownership_cmd(struct demo *demo, int i) { + VkResult U_ASSERT_ONLY err; + + const VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = NULL, + }; + err = vkBeginCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd, &cmd_buf_info); + assert(!err); + + VkImageMemoryBarrier image_ownership_barrier = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .srcQueueFamilyIndex = demo->graphics_queue_family_index, + .dstQueueFamilyIndex = demo->present_queue_family_index, + .image = demo->swapchain_image_resources[i].image, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; + + vkCmdPipelineBarrier(demo->swapchain_image_resources[i].graphics_to_present_cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &image_ownership_barrier); + err = vkEndCommandBuffer(demo->swapchain_image_resources[i].graphics_to_present_cmd); + assert(!err); +} + +void demo_update_data_buffer(struct demo *demo) { + mat4x4 MVP, Model, VP; + int matrixSize = sizeof(MVP); + + mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix); + + // Rotate around the Y axis + mat4x4_dup(Model, demo->model_matrix); + mat4x4_rotate_Y(demo->model_matrix, Model, (float)degreesToRadians(demo->spin_angle)); + mat4x4_orthonormalize(demo->model_matrix, demo->model_matrix); + mat4x4_mul(MVP, VP, demo->model_matrix); + + memcpy(demo->swapchain_image_resources[demo->current_buffer].uniform_memory_ptr, (const void *)&MVP[0][0], matrixSize); +} + +void DemoUpdateTargetIPD(struct demo *demo) { + // Look at what happened to previous presents, and make appropriate + // adjustments in timing: + VkResult U_ASSERT_ONLY err; + VkPastPresentationTimingGOOGLE *past = NULL; + uint32_t count = 0; + + err = vkGetPastPresentationTimingGOOGLE(demo->device, demo->swapchain, &count, NULL); + assert(!err); + if (count) { + past = (VkPastPresentationTimingGOOGLE *)malloc(sizeof(VkPastPresentationTimingGOOGLE) * count); + assert(past); + err = vkGetPastPresentationTimingGOOGLE(demo->device, demo->swapchain, &count, past); + assert(!err); + + bool early = false; + bool late = false; + bool calibrate_next = false; + for (uint32_t i = 0; i < count; i++) { + if (!demo->syncd_with_actual_presents) { + // This is the first time that we've received an + // actualPresentTime for this swapchain. In order to not + // perceive these early frames as "late", we need to sync-up + // our future desiredPresentTime's with the + // actualPresentTime(s) that we're receiving now. + calibrate_next = true; + + // So that we don't suspect any pending presents as late, + // record them all as suspected-late presents: + demo->last_late_id = demo->next_present_id - 1; + demo->last_early_id = 0; + demo->syncd_with_actual_presents = true; + break; + } else if (CanPresentEarlier(past[i].earliestPresentTime, past[i].actualPresentTime, past[i].presentMargin, + demo->refresh_duration)) { + // This image could have been presented earlier. We don't want + // to decrease the target_IPD until we've seen early presents + // for at least two seconds. + if (demo->last_early_id == past[i].presentID) { + // We've now seen two seconds worth of early presents. + // Flag it as such, and reset the counter: + early = true; + demo->last_early_id = 0; + } else if (demo->last_early_id == 0) { + // This is the first early present we've seen. + // Calculate the presentID for two seconds from now. + uint64_t lastEarlyTime = past[i].actualPresentTime + (2 * BILLION); + uint32_t howManyPresents = (uint32_t)((lastEarlyTime - past[i].actualPresentTime) / demo->target_IPD); + demo->last_early_id = past[i].presentID + howManyPresents; + } else { + // We are in the midst of a set of early images, + // and so we won't do anything. + } + late = false; + demo->last_late_id = 0; + } else if (ActualTimeLate(past[i].desiredPresentTime, past[i].actualPresentTime, demo->refresh_duration)) { + // This image was presented after its desired time. Since + // there's a delay between calling vkQueuePresentKHR and when + // we get the timing data, several presents may have been late. + // Thus, we need to threat all of the outstanding presents as + // being likely late, so that we only increase the target_IPD + // once for all of those presents. + if ((demo->last_late_id == 0) || (demo->last_late_id < past[i].presentID)) { + late = true; + // Record the last suspected-late present: + demo->last_late_id = demo->next_present_id - 1; + } else { + // We are in the midst of a set of likely-late images, + // and so we won't do anything. + } + early = false; + demo->last_early_id = 0; + } else { + // Since this image was not presented early or late, reset + // any sets of early or late presentIDs: + early = false; + late = false; + calibrate_next = true; + demo->last_early_id = 0; + demo->last_late_id = 0; + } + } + + if (early) { + // Since we've seen at least two-seconds worth of presnts that + // could have occured earlier than desired, let's decrease the + // target_IPD (i.e. increase the frame rate): + // + // TODO(ianelliott): Try to calculate a better target_IPD based + // on the most recently-seen present (this is overly-simplistic). + demo->refresh_duration_multiplier--; + if (demo->refresh_duration_multiplier == 0) { + // This should never happen, but in case it does, don't + // try to go faster. + demo->refresh_duration_multiplier = 1; + } + demo->target_IPD = demo->refresh_duration * demo->refresh_duration_multiplier; + } + if (late) { + // Since we found a new instance of a late present, we want to + // increase the target_IPD (i.e. decrease the frame rate): + // + // TODO(ianelliott): Try to calculate a better target_IPD based + // on the most recently-seen present (this is overly-simplistic). + demo->refresh_duration_multiplier++; + demo->target_IPD = demo->refresh_duration * demo->refresh_duration_multiplier; + } + + if (calibrate_next) { + int64_t multiple = demo->next_present_id - past[count - 1].presentID; + demo->prev_desired_present_time = (past[count - 1].actualPresentTime + (multiple * demo->target_IPD)); + } + free(past); + } +} + +static void demo_draw(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + // Ensure no more than FRAME_LAG renderings are outstanding + vkWaitForFences(demo->device, 1, &demo->fences[demo->frame_index], VK_TRUE, UINT64_MAX); + vkResetFences(demo->device, 1, &demo->fences[demo->frame_index]); + + do { + // Get the index of the next available swapchain image: + err = vkAcquireNextImageKHR(demo->device, demo->swapchain, UINT64_MAX, demo->image_acquired_semaphores[demo->frame_index], + VK_NULL_HANDLE, &demo->current_buffer); + + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + // demo->swapchain is out of date (e.g. the window was resized) and + // must be recreated: + demo_resize(demo); + } else if (err == VK_SUBOPTIMAL_KHR) { + // demo->swapchain is not as optimal as it could be, but the platform's + // presentation engine will still present the image correctly. + break; + } else if (err == VK_ERROR_SURFACE_LOST_KHR) { + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + demo_create_surface(demo); + demo_resize(demo); + } else { + assert(!err); + } + } while (err != VK_SUCCESS); + + demo_update_data_buffer(demo); + + if (demo->VK_GOOGLE_display_timing_enabled) { + // Look at what happened to previous presents, and make appropriate + // adjustments in timing: + DemoUpdateTargetIPD(demo); + + // Note: a real application would position its geometry to that it's in + // the correct locatoin for when the next image is presented. It might + // also wait, so that there's less latency between any input and when + // the next image is rendered/presented. This demo program is so + // simple that it doesn't do either of those. + } + + // Wait for the image acquired semaphore to be signaled to ensure + // that the image won't be rendered to until the presentation + // engine has fully released ownership to the application, and it is + // okay to render to the image. + VkPipelineStageFlags pipe_stage_flags; + VkSubmitInfo submit_info; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = NULL; + pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.pWaitDstStageMask = &pipe_stage_flags; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &demo->image_acquired_semaphores[demo->frame_index]; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].cmd; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; + err = vkQueueSubmit(demo->graphics_queue, 1, &submit_info, demo->fences[demo->frame_index]); + assert(!err); + + if (demo->separate_present_queue) { + // If we are using separate queues, change image ownership to the + // present queue before presenting, waiting for the draw complete + // semaphore and signalling the ownership released semaphore when finished + VkFence nullFence = VK_NULL_HANDLE; + pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &demo->draw_complete_semaphores[demo->frame_index]; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &demo->swapchain_image_resources[demo->current_buffer].graphics_to_present_cmd; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &demo->image_ownership_semaphores[demo->frame_index]; + err = vkQueueSubmit(demo->present_queue, 1, &submit_info, nullFence); + assert(!err); + } + + // If we are using separate queues we have to wait for image ownership, + // otherwise wait for draw complete + VkPresentInfoKHR present = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = NULL, + .waitSemaphoreCount = 1, + .pWaitSemaphores = (demo->separate_present_queue) ? &demo->image_ownership_semaphores[demo->frame_index] + : &demo->draw_complete_semaphores[demo->frame_index], + .swapchainCount = 1, + .pSwapchains = &demo->swapchain, + .pImageIndices = &demo->current_buffer, + }; + + VkRectLayerKHR rect; + VkPresentRegionKHR region; + VkPresentRegionsKHR regions; + if (demo->VK_KHR_incremental_present_enabled) { + // If using VK_KHR_incremental_present, we provide a hint of the region + // that contains changed content relative to the previously-presented + // image. The implementation can use this hint in order to save + // work/power (by only copying the region in the hint). The + // implementation is free to ignore the hint though, and so we must + // ensure that the entire image has the correctly-drawn content. + uint32_t eighthOfWidth = demo->width / 8; + uint32_t eighthOfHeight = demo->height / 8; + + if (demo->first_swapchain_frame) { + rect.offset.x = 0; + rect.offset.y = 0; + rect.extent.width = demo->width; + rect.extent.height = demo->height; + } else { + rect.offset.x = eighthOfWidth; + rect.offset.y = eighthOfHeight; + rect.extent.width = eighthOfWidth * 6; + rect.extent.height = eighthOfHeight * 6; + } + rect.layer = 0; + + region.rectangleCount = 1; + region.pRectangles = ▭ + + regions.sType = VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR; + regions.pNext = present.pNext; + regions.swapchainCount = present.swapchainCount; + regions.pRegions = ®ion; + present.pNext = ®ions; + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + VkPresentTimeGOOGLE ptime; + if (demo->prev_desired_present_time == 0) { + // This must be the first present for this swapchain. + // + // We don't know where we are relative to the presentation engine's + // display's refresh cycle. We also don't know how long rendering + // takes. Let's make a grossly-simplified assumption that the + // desiredPresentTime should be half way between now and + // now+target_IPD. We will adjust over time. + uint64_t curtime = getTimeInNanoseconds(); + if (curtime == 0) { + // Since we didn't find out the current time, don't give a + // desiredPresentTime: + ptime.desiredPresentTime = 0; + } else { + ptime.desiredPresentTime = curtime + (demo->target_IPD >> 1); + } + } else { + ptime.desiredPresentTime = (demo->prev_desired_present_time + demo->target_IPD); + } + ptime.presentID = demo->next_present_id++; + demo->prev_desired_present_time = ptime.desiredPresentTime; + + VkPresentTimesInfoGOOGLE present_time = { + .sType = VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE, + .pNext = present.pNext, + .swapchainCount = present.swapchainCount, + .pTimes = &ptime, + }; + if (demo->VK_GOOGLE_display_timing_enabled) { + present.pNext = &present_time; + } + } + + err = vkQueuePresentKHR(demo->present_queue, &present); + demo->frame_index += 1; + demo->frame_index %= FRAME_LAG; + demo->first_swapchain_frame = false; + + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + // demo->swapchain is out of date (e.g. the window was resized) and + // must be recreated: + demo_resize(demo); + } else if (err == VK_SUBOPTIMAL_KHR) { + // SUBOPTIMAL could be due to a resize + VkSurfaceCapabilitiesKHR surfCapabilities; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(demo->gpu, demo->surface, &surfCapabilities); + assert(!err); + if (surfCapabilities.currentExtent.width != (uint32_t)demo->width || + surfCapabilities.currentExtent.height != (uint32_t)demo->height) { + demo_resize(demo); + } + } else if (err == VK_ERROR_SURFACE_LOST_KHR) { + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + demo_create_surface(demo); + demo_resize(demo); + } else { + assert(!err); + } +} + +static void demo_prepare_buffers(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + VkSwapchainKHR oldSwapchain = demo->swapchain; + + // Check the surface capabilities and formats + VkSurfaceCapabilitiesKHR surfCapabilities; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(demo->gpu, demo->surface, &surfCapabilities); + assert(!err); + + uint32_t presentModeCount; + err = vkGetPhysicalDeviceSurfacePresentModesKHR(demo->gpu, demo->surface, &presentModeCount, NULL); + assert(!err); + VkPresentModeKHR *presentModes = (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR)); + assert(presentModes); + err = vkGetPhysicalDeviceSurfacePresentModesKHR(demo->gpu, demo->surface, &presentModeCount, presentModes); + assert(!err); + + VkExtent2D swapchainExtent; + // width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF. + if (surfCapabilities.currentExtent.width == 0xFFFFFFFF) { + // If the surface size is undefined, the size is set to the size + // of the images requested, which must fit within the minimum and + // maximum values. + swapchainExtent.width = demo->width; + swapchainExtent.height = demo->height; + + if (swapchainExtent.width < surfCapabilities.minImageExtent.width) { + swapchainExtent.width = surfCapabilities.minImageExtent.width; + } else if (swapchainExtent.width > surfCapabilities.maxImageExtent.width) { + swapchainExtent.width = surfCapabilities.maxImageExtent.width; + } + + if (swapchainExtent.height < surfCapabilities.minImageExtent.height) { + swapchainExtent.height = surfCapabilities.minImageExtent.height; + } else if (swapchainExtent.height > surfCapabilities.maxImageExtent.height) { + swapchainExtent.height = surfCapabilities.maxImageExtent.height; + } + } else { + // If the surface size is defined, the swap chain size must match + swapchainExtent = surfCapabilities.currentExtent; + demo->width = surfCapabilities.currentExtent.width; + demo->height = surfCapabilities.currentExtent.height; + } + + if (surfCapabilities.maxImageExtent.width == 0 || surfCapabilities.maxImageExtent.height == 0) { + demo->is_minimized = true; + return; + } else { + demo->is_minimized = false; + } + + // The FIFO present mode is guaranteed by the spec to be supported + // and to have no tearing. It's a great default present mode to use. + VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; + + // There are times when you may wish to use another present mode. The + // following code shows how to select them, and the comments provide some + // reasons you may wish to use them. + // + // It should be noted that Vulkan 1.0 doesn't provide a method for + // synchronizing rendering with the presentation engine's display. There + // is a method provided for throttling rendering with the display, but + // there are some presentation engines for which this method will not work. + // If an application doesn't throttle its rendering, and if it renders much + // faster than the refresh rate of the display, this can waste power on + // mobile devices. That is because power is being spent rendering images + // that may never be seen. + + // VK_PRESENT_MODE_IMMEDIATE_KHR is for applications that don't care about + // tearing, or have some way of synchronizing their rendering with the + // display. + // VK_PRESENT_MODE_MAILBOX_KHR may be useful for applications that + // generally render a new presentable image every refresh cycle, but are + // occasionally early. In this case, the application wants the new image + // to be displayed instead of the previously-queued-for-presentation image + // that has not yet been displayed. + // VK_PRESENT_MODE_FIFO_RELAXED_KHR is for applications that generally + // render a new presentable image every refresh cycle, but are occasionally + // late. In this case (perhaps because of stuttering/latency concerns), + // the application wants the late image to be immediately displayed, even + // though that may mean some tearing. + + if (demo->presentMode != swapchainPresentMode) { + for (size_t i = 0; i < presentModeCount; ++i) { + if (presentModes[i] == demo->presentMode) { + swapchainPresentMode = demo->presentMode; + break; + } + } + } + if (swapchainPresentMode != demo->presentMode) { + ERR_EXIT("Present mode specified is not supported\n", "Present mode unsupported"); + } + + // Determine the number of VkImages to use in the swap chain. + // Application desires to acquire 3 images at a time for triple + // buffering + uint32_t desiredNumOfSwapchainImages = 3; + if (desiredNumOfSwapchainImages < surfCapabilities.minImageCount) { + desiredNumOfSwapchainImages = surfCapabilities.minImageCount; + } + // If maxImageCount is 0, we can ask for as many images as we want; + // otherwise we're limited to maxImageCount + if ((surfCapabilities.maxImageCount > 0) && (desiredNumOfSwapchainImages > surfCapabilities.maxImageCount)) { + // Application must settle for fewer images than desired: + desiredNumOfSwapchainImages = surfCapabilities.maxImageCount; + } + + VkSurfaceTransformFlagsKHR preTransform; + if (surfCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { + preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + } else { + preTransform = surfCapabilities.currentTransform; + } + + // Find a supported composite alpha mode - one of these is guaranteed to be set + VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + VkCompositeAlphaFlagBitsKHR compositeAlphaFlags[4] = { + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + }; + for (uint32_t i = 0; i < ARRAY_SIZE(compositeAlphaFlags); i++) { + if (surfCapabilities.supportedCompositeAlpha & compositeAlphaFlags[i]) { + compositeAlpha = compositeAlphaFlags[i]; + break; + } + } + + VkSwapchainCreateInfoKHR swapchain_ci = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = NULL, + .surface = demo->surface, + .minImageCount = desiredNumOfSwapchainImages, + .imageFormat = demo->format, + .imageColorSpace = demo->color_space, + .imageExtent = + { + .width = swapchainExtent.width, + .height = swapchainExtent.height, + }, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .preTransform = preTransform, + .compositeAlpha = compositeAlpha, + .imageArrayLayers = 1, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + .presentMode = swapchainPresentMode, + .oldSwapchain = oldSwapchain, + .clipped = true, + }; + uint32_t i; + err = vkCreateSwapchainKHR(demo->device, &swapchain_ci, NULL, &demo->swapchain); + assert(!err); + + // If we just re-created an existing swapchain, we should destroy the old + // swapchain at this point. + // Note: destroying the swapchain also cleans up all its associated + // presentable images once the platform is done with them. + if (oldSwapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(demo->device, oldSwapchain, NULL); + } + + err = vkGetSwapchainImagesKHR(demo->device, demo->swapchain, &demo->swapchainImageCount, NULL); + assert(!err); + + VkImage *swapchainImages = (VkImage *)malloc(demo->swapchainImageCount * sizeof(VkImage)); + assert(swapchainImages); + err = vkGetSwapchainImagesKHR(demo->device, demo->swapchain, &demo->swapchainImageCount, swapchainImages); + assert(!err); + + demo->swapchain_image_resources = + (SwapchainImageResources *)malloc(sizeof(SwapchainImageResources) * demo->swapchainImageCount); + assert(demo->swapchain_image_resources); + + for (i = 0; i < demo->swapchainImageCount; i++) { + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)swapchainImages[i], "SwapchainImage(%u)", i); + } + for (i = 0; i < demo->swapchainImageCount; i++) { + VkImageViewCreateInfo color_image_view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .format = demo->format, + .components = + { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = + {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .flags = 0, + }; + + demo->swapchain_image_resources[i].image = swapchainImages[i]; + + color_image_view.image = demo->swapchain_image_resources[i].image; + + err = vkCreateImageView(demo->device, &color_image_view, NULL, &demo->swapchain_image_resources[i].view); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->swapchain_image_resources[i].view, "SwapchainView(%u)", + i); + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + VkRefreshCycleDurationGOOGLE rc_dur; + err = vkGetRefreshCycleDurationGOOGLE(demo->device, demo->swapchain, &rc_dur); + assert(!err); + demo->refresh_duration = rc_dur.refreshDuration; + + demo->syncd_with_actual_presents = false; + // Initially target 1X the refresh duration: + demo->target_IPD = demo->refresh_duration; + demo->refresh_duration_multiplier = 1; + demo->prev_desired_present_time = 0; + demo->next_present_id = 1; + } + + if (NULL != swapchainImages) { + free(swapchainImages); + } + + if (NULL != presentModes) { + free(presentModes); + } +} + +static void demo_prepare_depth(struct demo *demo) { + const VkFormat depth_format = VK_FORMAT_D16_UNORM; + const VkImageCreateInfo image = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .imageType = VK_IMAGE_TYPE_2D, + .format = depth_format, + .extent = {demo->width, demo->height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .flags = 0, + }; + + VkImageViewCreateInfo view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .image = VK_NULL_HANDLE, + .format = depth_format, + .subresourceRange = + {.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}, + .flags = 0, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + }; + + if (demo->force_errors) { + // Intentionally force a bad pNext value to generate a validation layer error + view.pNext = ℑ + } + + VkMemoryRequirements mem_reqs; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + demo->depth.format = depth_format; + + /* create image */ + err = vkCreateImage(demo->device, &image, NULL, &demo->depth.image); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)demo->depth.image, "DepthImage"); + + vkGetImageMemoryRequirements(demo->device, demo->depth.image, &mem_reqs); + assert(!err); + + demo->depth.mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + demo->depth.mem_alloc.pNext = NULL; + demo->depth.mem_alloc.allocationSize = mem_reqs.size; + demo->depth.mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &demo->depth.mem_alloc.memoryTypeIndex); + assert(pass); + + /* allocate memory */ + err = vkAllocateMemory(demo->device, &demo->depth.mem_alloc, NULL, &demo->depth.mem); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->depth.mem, "DepthMem"); + + /* bind memory */ + err = vkBindImageMemory(demo->device, demo->depth.image, demo->depth.mem, 0); + assert(!err); + + /* create image view */ + view.image = demo->depth.image; + err = vkCreateImageView(demo->device, &view, NULL, &demo->depth.view); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->depth.view, "DepthView"); +} + +/* Convert ppm image data from header file into RGBA texture image */ +#include "lunarg.ppm.h" +bool loadTexture(const char *filename, uint8_t *rgba_data, VkSubresourceLayout *layout, int32_t *width, int32_t *height) { + (void)filename; + char *cPtr; + cPtr = (char *)lunarg_ppm; + if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "P6\n", 3)) { + return false; + } + while (strncmp(cPtr++, "\n", 1)) + ; + sscanf(cPtr, "%u %u", width, height); + if (rgba_data == NULL) { + return true; + } + while (strncmp(cPtr++, "\n", 1)) + ; + if ((unsigned char *)cPtr >= (lunarg_ppm + lunarg_ppm_len) || strncmp(cPtr, "255\n", 4)) { + return false; + } + while (strncmp(cPtr++, "\n", 1)) + ; + for (int y = 0; y < *height; y++) { + uint8_t *rowPtr = rgba_data; + for (int x = 0; x < *width; x++) { + memcpy(rowPtr, cPtr, 3); + rowPtr[3] = 255; /* Alpha of 1 */ + rowPtr += 4; + cPtr += 3; + } + rgba_data += layout->rowPitch; + } + return true; +} + +static void demo_prepare_texture_buffer(struct demo *demo, const char *filename, struct texture_object *tex_obj) { + int32_t tex_width; + int32_t tex_height; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + if (!loadTexture(filename, NULL, NULL, &tex_width, &tex_height)) { + ERR_EXIT("Failed to load textures", "Load Texture Failure"); + } + + tex_obj->tex_width = tex_width; + tex_obj->tex_height = tex_height; + + const VkBufferCreateInfo buffer_create_info = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .size = tex_width * tex_height * 4, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL}; + + err = vkCreateBuffer(demo->device, &buffer_create_info, NULL, &tex_obj->buffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)tex_obj->buffer, "TexBuffer(%s)", filename); + + VkMemoryRequirements mem_reqs; + vkGetBufferMemoryRequirements(demo->device, tex_obj->buffer, &mem_reqs); + + tex_obj->mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + tex_obj->mem_alloc.pNext = NULL; + tex_obj->mem_alloc.allocationSize = mem_reqs.size; + tex_obj->mem_alloc.memoryTypeIndex = 0; + + VkFlags requirements = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, requirements, &tex_obj->mem_alloc.memoryTypeIndex); + assert(pass); + + err = vkAllocateMemory(demo->device, &tex_obj->mem_alloc, NULL, &(tex_obj->mem)); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)tex_obj->mem, "TexBufMemory(%s)", filename); + + /* bind memory */ + err = vkBindBufferMemory(demo->device, tex_obj->buffer, tex_obj->mem, 0); + assert(!err); + + VkSubresourceLayout layout; + memset(&layout, 0, sizeof(layout)); + layout.rowPitch = tex_width * 4; + + void *data; + err = vkMapMemory(demo->device, tex_obj->mem, 0, tex_obj->mem_alloc.allocationSize, 0, &data); + assert(!err); + + if (!loadTexture(filename, data, &layout, &tex_width, &tex_height)) { + fprintf(stderr, "Error loading texture: %s\n", filename); + } + + vkUnmapMemory(demo->device, tex_obj->mem); +} + +static void demo_prepare_texture_image(struct demo *demo, const char *filename, struct texture_object *tex_obj, + VkImageTiling tiling, VkImageUsageFlags usage, VkFlags required_props) { + const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM; + int32_t tex_width; + int32_t tex_height; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + + if (!loadTexture(filename, NULL, NULL, &tex_width, &tex_height)) { + ERR_EXIT("Failed to load textures", "Load Texture Failure"); + } + + tex_obj->tex_width = tex_width; + tex_obj->tex_height = tex_height; + + const VkImageCreateInfo image_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .imageType = VK_IMAGE_TYPE_2D, + .format = tex_format, + .extent = {tex_width, tex_height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = tiling, + .usage = usage, + .flags = 0, + .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, + }; + + VkMemoryRequirements mem_reqs; + + err = vkCreateImage(demo->device, &image_create_info, NULL, &tex_obj->image); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE, (uint64_t)tex_obj->image, "TexImage(%s)", filename); + + vkGetImageMemoryRequirements(demo->device, tex_obj->image, &mem_reqs); + + tex_obj->mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + tex_obj->mem_alloc.pNext = NULL; + tex_obj->mem_alloc.allocationSize = mem_reqs.size; + tex_obj->mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, required_props, &tex_obj->mem_alloc.memoryTypeIndex); + assert(pass); + + /* allocate memory */ + err = vkAllocateMemory(demo->device, &tex_obj->mem_alloc, NULL, &(tex_obj->mem)); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)tex_obj->mem, "TexImageMem(%s)", filename); + + /* bind memory */ + err = vkBindImageMemory(demo->device, tex_obj->image, tex_obj->mem, 0); + assert(!err); + + if (required_props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { + const VkImageSubresource subres = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .arrayLayer = 0, + }; + VkSubresourceLayout layout; + void *data; + + vkGetImageSubresourceLayout(demo->device, tex_obj->image, &subres, &layout); + + err = vkMapMemory(demo->device, tex_obj->mem, 0, tex_obj->mem_alloc.allocationSize, 0, &data); + assert(!err); + + if (!loadTexture(filename, data, &layout, &tex_width, &tex_height)) { + fprintf(stderr, "Error loading texture: %s\n", filename); + } + + vkUnmapMemory(demo->device, tex_obj->mem); + } + + tex_obj->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +} + +static void demo_destroy_texture(struct demo *demo, struct texture_object *tex_objs) { + /* clean up staging resources */ + vkFreeMemory(demo->device, tex_objs->mem, NULL); + if (tex_objs->image) vkDestroyImage(demo->device, tex_objs->image, NULL); + if (tex_objs->buffer) vkDestroyBuffer(demo->device, tex_objs->buffer, NULL); +} + +static void demo_prepare_textures(struct demo *demo) { + const VkFormat tex_format = VK_FORMAT_R8G8B8A8_UNORM; + VkFormatProperties props; + uint32_t i; + + vkGetPhysicalDeviceFormatProperties(demo->gpu, tex_format, &props); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + VkResult U_ASSERT_ONLY err; + + if ((props.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) && !demo->use_staging_buffer) { + demo_push_cb_label(demo, demo->cmd, NULL, "DirectTexture(%u)", i); + /* Device can texture using linear textures */ + demo_prepare_texture_image(demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + // Nothing in the pipeline needs to be complete to start, and don't allow fragment + // shader to run until layout transition completes + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, + demo->textures[i].imageLayout, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + demo->staging_texture.image = 0; + demo_pop_cb_label(demo, demo->cmd); // "DirectTexture" + } else if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) { + /* Must use staging buffer to copy linear texture to optimized */ + demo_push_cb_label(demo, demo->cmd, NULL, "StagingTexture(%u)", i); + + memset(&demo->staging_texture, 0, sizeof(demo->staging_texture)); + demo_prepare_texture_buffer(demo, tex_files[i], &demo->staging_texture); + + demo_prepare_texture_image(demo, tex_files[i], &demo->textures[i], VK_IMAGE_TILING_OPTIMAL, + (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_PREINITIALIZED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + demo_push_cb_label(demo, demo->cmd, NULL, "StagingBufferCopy(%u)", i); + + VkBufferImageCopy copy_region = { + .bufferOffset = 0, + .bufferRowLength = demo->staging_texture.tex_width, + .bufferImageHeight = demo->staging_texture.tex_height, + .imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {demo->staging_texture.tex_width, demo->staging_texture.tex_height, 1}, + }; + + vkCmdCopyBufferToImage(demo->cmd, demo->staging_texture.buffer, demo->textures[i].image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + demo_pop_cb_label(demo, demo->cmd); // "StagingBufferCopy" + + demo_set_image_layout(demo, demo->textures[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + demo->textures[i].imageLayout, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + demo_pop_cb_label(demo, demo->cmd); // "StagingTexture" + + } else { + /* Can't support VK_FORMAT_R8G8B8A8_UNORM !? */ + assert(!"No support for R8G8B8A8_UNORM as texture image format"); + } + + const VkSamplerCreateInfo sampler = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = NULL, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 1, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, + .unnormalizedCoordinates = VK_FALSE, + }; + + VkImageViewCreateInfo view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .image = VK_NULL_HANDLE, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = tex_format, + .components = + { + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + .flags = 0, + }; + + /* create sampler */ + err = vkCreateSampler(demo->device, &sampler, NULL, &demo->textures[i].sampler); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SAMPLER, (uint64_t)demo->textures[i].sampler, "Sampler(%u)", i); + + /* create image view */ + view.image = demo->textures[i].image; + err = vkCreateImageView(demo->device, &view, NULL, &demo->textures[i].view); + demo_name_object(demo, VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)demo->textures[i].view, "TexImageView(%u)", i); + assert(!err); + } +} + +void demo_prepare_cube_data_buffers(struct demo *demo) { + VkBufferCreateInfo buf_info; + VkMemoryRequirements mem_reqs; + VkMemoryAllocateInfo mem_alloc; + mat4x4 MVP, VP; + VkResult U_ASSERT_ONLY err; + bool U_ASSERT_ONLY pass; + struct vktexcube_vs_uniform data; + + mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix); + mat4x4_mul(MVP, VP, demo->model_matrix); + memcpy(data.mvp, MVP, sizeof(MVP)); + // dumpMatrix("MVP", MVP); + + for (unsigned int i = 0; i < 12 * 3; i++) { + data.position[i][0] = g_vertex_buffer_data[i * 3]; + data.position[i][1] = g_vertex_buffer_data[i * 3 + 1]; + data.position[i][2] = g_vertex_buffer_data[i * 3 + 2]; + data.position[i][3] = 1.0f; + data.attr[i][0] = g_uv_buffer_data[2 * i]; + data.attr[i][1] = g_uv_buffer_data[2 * i + 1]; + data.attr[i][2] = 0; + data.attr[i][3] = 0; + } + + memset(&buf_info, 0, sizeof(buf_info)); + buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + buf_info.size = sizeof(data); + + for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { + err = vkCreateBuffer(demo->device, &buf_info, NULL, &demo->swapchain_image_resources[i].uniform_buffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_BUFFER, (uint64_t)demo->swapchain_image_resources[i].uniform_buffer, + "SwapchainUniformBuf(%u)", i); + + vkGetBufferMemoryRequirements(demo->device, demo->swapchain_image_resources[i].uniform_buffer, &mem_reqs); + + mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + mem_alloc.pNext = NULL; + mem_alloc.allocationSize = mem_reqs.size; + mem_alloc.memoryTypeIndex = 0; + + pass = memory_type_from_properties(demo, mem_reqs.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &mem_alloc.memoryTypeIndex); + assert(pass); + + err = vkAllocateMemory(demo->device, &mem_alloc, NULL, &demo->swapchain_image_resources[i].uniform_memory); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)demo->swapchain_image_resources[i].uniform_memory, + "SwapchainUniformMem(%u)", i); + + err = vkMapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, 0, VK_WHOLE_SIZE, 0, + &demo->swapchain_image_resources[i].uniform_memory_ptr); + assert(!err); + + memcpy(demo->swapchain_image_resources[i].uniform_memory_ptr, &data, sizeof data); + + err = vkBindBufferMemory(demo->device, demo->swapchain_image_resources[i].uniform_buffer, + demo->swapchain_image_resources[i].uniform_memory, 0); + assert(!err); + } +} + +#if DESCRIPTOR_HEAP +void demo_prepare_cube_descriptor_heap(struct demo *demo) +{ + VkResult err = VK_SUCCESS; + VkBufferCreateInfo heapInfo; + VkMemoryRequirements memReqs; + VkMemoryAllocateInfo memAllocInfo; + + { + //create sampler descriptor heap + memset(&heapInfo, 0, sizeof(heapInfo)); + heapInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + heapInfo.flags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR; + heapInfo.size = demo->descriptorHeapProperties.maxSamplerHeapSize; + err = vkCreateBuffer(demo->device, &heapInfo, NULL, &demo->descHeap[0]); + + // VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements); + vkGetBufferMemoryRequirements(demo->device, demo->descHeap[0], &memReqs); + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = NULL; + memAllocInfo.allocationSize = memReqs.size; + // memAllocInfo.memoryTypeIndex = 0; + + memory_type_from_properties(demo, memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + &memAllocInfo.memoryTypeIndex); + + + err = vkAllocateMemory(demo->device, &memAllocInfo, NULL, &demo->descHeapMemory[0]); + err = vkBindBufferMemory(demo->device, demo->descHeap[0], demo->descHeapMemory[0], 0); + + //heap buffer host ponter + VkHostAddressStridedRangeKHR bufferRagge = { + .size = demo->descriptorHeapProperties.bufferDescriptorSize, + .stride = demo->descriptorHeapProperties.bufferHeapDescriptorAlignment, + .pData = NULL + }; + vkMapMemory(demo->device, demo->descHeapMemory[0], 0, VK_WHOLE_SIZE, 0, &bufferRagge.pData); + + PFN_vkWriteBufferDescriptorsKHR vkWriteBufferDescriptorsKHR = + (PFN_vkWriteBufferDescriptorsKHR) vkGetDeviceProcAddr(demo->device, "vkWriteBufferDescriptorsKHR"); + // PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR = + // (PFN_vkGetBufferDeviceAddressKHR) vkGetDeviceProcAddr(demo->device, "vkGetBufferDeviceAddressKHR111111111111"); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) + { + VkBufferDeviceAddressInfo addressInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = NULL, + .buffer = demo->swapchain_image_resources[i].uniform_buffer + }; + + // prepare buffer descriptors + VkBufferDescriptorInfoKHR bufferDescInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DESCRIPTOR_INFO_KHR, + .pNext = NULL, + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .addressRange.address = 0, + .addressRange.size = sizeof(struct vktexcube_vs_uniform), + }; + bufferDescInfo.addressRange.address = vkGetBufferDeviceAddressKHR(demo->device, &addressInfo); + + err = vkWriteBufferDescriptorsKHR(demo->device, 1, &bufferDescInfo ,bufferRagge); + + bufferRagge.pData += bufferRagge.size; + } + + vkUnmapMemory(demo->device, demo->descHeapMemory[0]); + } + + { + //create buffer descriptor heap + memset(&heapInfo, 0, sizeof(heapInfo)); + memset(&memReqs, 0, sizeof(memReqs)); + memset(&memAllocInfo, 0, sizeof(memAllocInfo)); + + heapInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + heapInfo.flags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR; + heapInfo.size = demo->descriptorHeapProperties.maxBufferHeapSize; + err = vkCreateBuffer(demo->device, &heapInfo, NULL, &demo->descHeap[1]); + + vkGetBufferMemoryRequirements(demo->device, demo->descHeap[1], &memReqs); + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = NULL; + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = 0; + err = vkAllocateMemory(demo->device, &memAllocInfo, NULL, &demo->descHeapMemory[1]); + err = vkBindBufferMemory(demo->device, demo->descHeap[1], demo->descHeapMemory[1], 0); + } + + { + //create image descriptor heap + memset(&heapInfo, 0, sizeof(heapInfo)); + memset(&memReqs, 0, sizeof(memReqs)); + memset(&memAllocInfo, 0, sizeof(memAllocInfo)); + + heapInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + heapInfo.flags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR; + heapInfo.size = demo->descriptorHeapProperties.maxImageHeapSize; + err = vkCreateBuffer(demo->device, &heapInfo, NULL, &demo->descHeap[2]); + + vkGetBufferMemoryRequirements(demo->device, demo->descHeap[2], &memReqs); + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = NULL; + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = 0; + err = vkAllocateMemory(demo->device, &memAllocInfo, NULL, &demo->descHeapMemory[2]); + err = vkBindBufferMemory(demo->device, demo->descHeap[2], demo->descHeapMemory[2], 0); + } + + // try write buffer to buffer heap + + assert(!err); +} +#endif + +// static void demo_prepare_descriptor_layout(struct demo *demo) { +// #if DESCRIPTOR_HEAP + +// #else +// const VkDescriptorSetLayoutBinding layout_bindings[2] = { +// [0] = +// { +// .binding = 0, +// .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, +// .descriptorCount = 1, +// .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, +// .pImmutableSamplers = NULL, +// }, +// [1] = +// { +// .binding = 1, +// .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, +// .descriptorCount = DEMO_TEXTURE_COUNT, +// .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, +// .pImmutableSamplers = NULL, +// }, +// }; +// const VkDescriptorSetLayoutCreateInfo descriptor_layout = { +// .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, +// .pNext = NULL, +// .bindingCount = 2, +// .pBindings = layout_bindings, +// }; +// VkResult U_ASSERT_ONLY err; + +// err = vkCreateDescriptorSetLayout(demo->device, &descriptor_layout, NULL, &demo->desc_layout); +// assert(!err); + +// const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = { +// .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, +// .pNext = NULL, +// .setLayoutCount = 1, +// .pSetLayouts = &demo->desc_layout, +// }; + +// err = vkCreatePipelineLayout(demo->device, &pPipelineLayoutCreateInfo, NULL, &demo->pipeline_layout); +// assert(!err); +// #endif +// } + +static void demo_prepare_render_pass(struct demo *demo) { + // The initial layout for the color and depth attachments will be LAYOUT_UNDEFINED + // because at the start of the renderpass, we don't care about their contents. + // At the start of the subpass, the color attachment's layout will be transitioned + // to LAYOUT_COLOR_ATTACHMENT_OPTIMAL and the depth stencil attachment's layout + // will be transitioned to LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL. At the end of + // the renderpass, the color attachment's layout will be transitioned to + // LAYOUT_PRESENT_SRC_KHR to be ready to present. This is all done as part of + // the renderpass, no barriers are necessary. + const VkAttachmentDescription attachments[2] = { + [0] = + { + .format = demo->format, + .flags = 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_PRESENT_SRC_KHR, + }, + [1] = + { + .format = demo->depth.format, + .flags = 0, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .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, + }, + }; + const VkAttachmentReference color_reference = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + const VkAttachmentReference depth_reference = { + .attachment = 1, + .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }; + const VkSubpassDescription subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .flags = 0, + .inputAttachmentCount = 0, + .pInputAttachments = NULL, + .colorAttachmentCount = 1, + .pColorAttachments = &color_reference, + .pResolveAttachments = NULL, + .pDepthStencilAttachment = &depth_reference, + .preserveAttachmentCount = 0, + .pPreserveAttachments = NULL, + }; + + VkSubpassDependency attachmentDependencies[2] = { + [0] = + { + // Depth buffer is shared between swapchain images + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0, + }, + [1] = + { + // Image Layout Transition + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, + .dependencyFlags = 0, + }, + }; + + const VkRenderPassCreateInfo rp_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .attachmentCount = 2, + .pAttachments = attachments, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 2, + .pDependencies = attachmentDependencies, + }; + VkResult U_ASSERT_ONLY err; + + err = vkCreateRenderPass(demo->device, &rp_info, NULL, &demo->render_pass); + assert(!err); +} + +static VkShaderModule demo_prepare_shader_module(const char *name, struct demo *demo, const uint32_t *code, size_t size) { + VkShaderModule module; + VkShaderModuleCreateInfo moduleCreateInfo; + VkResult U_ASSERT_ONLY err; + + moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + moduleCreateInfo.pNext = NULL; + moduleCreateInfo.flags = 0; + moduleCreateInfo.codeSize = size; + moduleCreateInfo.pCode = code; + + err = vkCreateShaderModule(demo->device, &moduleCreateInfo, NULL, &module); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SHADER_MODULE, (uint64_t)module, "%s", name); + + return module; +} + +static void demo_prepare_vs(struct demo *demo) { + const uint32_t vs_code[] = { +#include "cube.vert.inc" + }; + demo->vert_shader_module = demo_prepare_shader_module("cube.vert", demo, vs_code, sizeof(vs_code)); +} + +static void demo_prepare_fs(struct demo *demo) { + const uint32_t fs_code[] = { +#include "cube.frag.inc" + }; + demo->frag_shader_module = demo_prepare_shader_module("cube.frag", demo, fs_code, sizeof(fs_code)); +} + +static void demo_prepare_pipeline(struct demo *demo) { +#define NUM_DYNAMIC_STATES 2 /*Viewport + Scissor*/ + + VkGraphicsPipelineCreateInfo pipeline; + VkPipelineCacheCreateInfo pipelineCache; + VkPipelineVertexInputStateCreateInfo vi; + VkPipelineInputAssemblyStateCreateInfo ia; + VkPipelineRasterizationStateCreateInfo rs; + VkPipelineColorBlendStateCreateInfo cb; + VkPipelineDepthStencilStateCreateInfo ds; + VkPipelineViewportStateCreateInfo vp; + VkPipelineMultisampleStateCreateInfo ms; + VkDynamicState dynamicStateEnables[NUM_DYNAMIC_STATES]; + VkPipelineDynamicStateCreateInfo dynamicState; + VkResult U_ASSERT_ONLY err; + + memset(dynamicStateEnables, 0, sizeof dynamicStateEnables); + memset(&dynamicState, 0, sizeof dynamicState); + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.pDynamicStates = dynamicStateEnables; + + memset(&pipeline, 0, sizeof(pipeline)); + pipeline.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline.layout = demo->pipeline_layout; + + memset(&vi, 0, sizeof(vi)); + vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + memset(&ia, 0, sizeof(ia)); + ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + memset(&rs, 0, sizeof(rs)); + rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rs.polygonMode = VK_POLYGON_MODE_FILL; + rs.cullMode = VK_CULL_MODE_BACK_BIT; + rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rs.depthClampEnable = VK_FALSE; + rs.rasterizerDiscardEnable = VK_FALSE; + rs.depthBiasEnable = VK_FALSE; + rs.lineWidth = 1.0f; + + memset(&cb, 0, sizeof(cb)); + cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + VkPipelineColorBlendAttachmentState att_state[1]; + memset(att_state, 0, sizeof(att_state)); + att_state[0].colorWriteMask = 0xf; + att_state[0].blendEnable = VK_FALSE; + cb.attachmentCount = 1; + cb.pAttachments = att_state; + + memset(&vp, 0, sizeof(vp)); + vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + vp.viewportCount = 1; + dynamicStateEnables[dynamicState.dynamicStateCount++] = VK_DYNAMIC_STATE_VIEWPORT; + vp.scissorCount = 1; + dynamicStateEnables[dynamicState.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; + + memset(&ds, 0, sizeof(ds)); + ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + ds.depthTestEnable = VK_TRUE; + ds.depthWriteEnable = VK_TRUE; + ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; + ds.depthBoundsTestEnable = VK_FALSE; + ds.back.failOp = VK_STENCIL_OP_KEEP; + ds.back.passOp = VK_STENCIL_OP_KEEP; + ds.back.compareOp = VK_COMPARE_OP_ALWAYS; + ds.stencilTestEnable = VK_FALSE; + ds.front = ds.back; + + memset(&ms, 0, sizeof(ms)); + ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms.pSampleMask = NULL; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + demo_prepare_vs(demo); + demo_prepare_fs(demo); + + // Two stages: vs and fs + VkPipelineShaderStageCreateInfo shaderStages[2]; + memset(&shaderStages, 0, 2 * sizeof(VkPipelineShaderStageCreateInfo)); + + shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + shaderStages[0].module = demo->vert_shader_module; + 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].module = demo->frag_shader_module; + shaderStages[1].pName = "main"; + + memset(&pipelineCache, 0, sizeof(pipelineCache)); + pipelineCache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + + err = vkCreatePipelineCache(demo->device, &pipelineCache, NULL, &demo->pipelineCache); + assert(!err); + + pipeline.pVertexInputState = &vi; + pipeline.pInputAssemblyState = &ia; + pipeline.pRasterizationState = &rs; + pipeline.pColorBlendState = &cb; + pipeline.pMultisampleState = &ms; + pipeline.pViewportState = &vp; + pipeline.pDepthStencilState = &ds; + pipeline.stageCount = ARRAY_SIZE(shaderStages); + pipeline.pStages = shaderStages; + pipeline.renderPass = demo->render_pass; + pipeline.pDynamicState = &dynamicState; + +// #if DESCRIPTOR_HEAP + // add VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_KHR to next of VkGraphicsPipelineCreateInfo + VkPipelineCreateFlags2CreateInfoKHR pipe2CreateInfo = {}; + pipe2CreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO_KHR; + pipe2CreateInfo.pNext = NULL; + pipe2CreateInfo.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_KHR; + + pipeline.pNext = &pipe2CreateInfo; + pipeline.layout = NULL; + + // more info in pNext chain of VkPipelineShaderStageCreateInfo + // VkShaderDescriptorSetAndBindingMappingInfoKHR + VkDescriptorSetAndBindingMappingKHR mappings[3] = {}; + // uniform buffer layout(set = 0, binding = 0) uniform buf + mappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_KHR; + mappings[0].pNext = NULL; + mappings[0].descriptorSet = 0; + mappings[0].bindingCount = 1; + mappings[0].firstBinding = 0; + mappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_INDEX_KHR; + mappings[0].sourceData.constantIndex.heapIndex = 0; + mappings[0].sourceData.constantIndex.pEmbeddedSampler = NULL; + mappings[0].sourceData.constantIndex.useCombinedImageSamplerIndex = VK_FALSE; + mappings[0].sourceData.constantIndex.samplerHeapIndex = 0; + + // old: sampled texture layout (binding = 1) uniform sampler2D tex; + // heap: sampled texture layout (set = 1, binding = 0) uniform sampler2D tex; + // sampler heap: set = 1, binding = 0; + // image heap: set = 2, binding = 0; + // imageIndex = heapIndex + (Binding - firstBinding) + arrayIndex + // samplerIndex = samplerHeapIndex + (Binding - firstBinding) + arrayIndex + + mappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_KHR; + mappings[1].pNext = NULL; + mappings[1].descriptorSet = 1; // combined image and sampler + mappings[1].bindingCount = 1; + mappings[1].firstBinding = 0; + mappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_INDEX_KHR; + mappings[1].sourceData.constantIndex.heapIndex = 0; + mappings[1].sourceData.constantIndex.useCombinedImageSamplerIndex = VK_FALSE; + mappings[1].sourceData.constantIndex.samplerHeapIndex = 0; + mappings[1].sourceData.constantIndex.pEmbeddedSampler = NULL; + + mappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_KHR; + mappings[1].pNext = NULL; + mappings[1].descriptorSet = 1; // combined image and sampler + mappings[1].bindingCount = 1; + mappings[1].firstBinding = 0; + mappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_INDEX_KHR; + mappings[1].sourceData.constantIndex.heapIndex = 0; + mappings[1].sourceData.constantIndex.useCombinedImageSamplerIndex = VK_FALSE; + mappings[1].sourceData.constantIndex.samplerHeapIndex = 0; + mappings[1].sourceData.constantIndex.pEmbeddedSampler = NULL; + + + + VkShaderDescriptorSetAndBindingMappingInfoKHR shaderDescriptorSet = {}; + shaderStages->pNext = &shaderDescriptorSet; + shaderDescriptorSet.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_KHR; + shaderDescriptorSet.pNext = NULL; + shaderDescriptorSet.mappingCount = 2; + shaderDescriptorSet.pMappings = mappings; + + + + + + +// #endif + + err = vkCreateGraphicsPipelines(demo->device, demo->pipelineCache, 1, &pipeline, NULL, &demo->pipeline); + assert(!err); + + vkDestroyShaderModule(demo->device, demo->frag_shader_module, NULL); + vkDestroyShaderModule(demo->device, demo->vert_shader_module, NULL); +} + +// static void demo_prepare_descriptor_pool(struct demo *demo) { +// const VkDescriptorPoolSize type_counts[2] = { +// [0] = +// { +// .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, +// .descriptorCount = demo->swapchainImageCount, +// }, +// [1] = +// { +// .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, +// .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, +// }, +// }; +// const VkDescriptorPoolCreateInfo descriptor_pool = { +// .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, +// .pNext = NULL, +// .maxSets = demo->swapchainImageCount, +// .poolSizeCount = 2, +// .pPoolSizes = type_counts, +// }; +// VkResult U_ASSERT_ONLY err; + +// err = vkCreateDescriptorPool(demo->device, &descriptor_pool, NULL, &demo->desc_pool); +// assert(!err); +// } + +// static void demo_prepare_descriptor_set(struct demo *demo) { +// VkDescriptorImageInfo tex_descs[DEMO_TEXTURE_COUNT]; +// VkWriteDescriptorSet writes[2]; +// VkResult U_ASSERT_ONLY err; + +// VkDescriptorSetAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, +// .pNext = NULL, +// .descriptorPool = demo->desc_pool, +// .descriptorSetCount = 1, +// .pSetLayouts = &demo->desc_layout}; + +// VkDescriptorBufferInfo buffer_info; +// buffer_info.offset = 0; +// buffer_info.range = sizeof(struct vktexcube_vs_uniform); + +// memset(&tex_descs, 0, sizeof(tex_descs)); +// for (unsigned int i = 0; i < DEMO_TEXTURE_COUNT; i++) { +// tex_descs[i].sampler = demo->textures[i].sampler; +// tex_descs[i].imageView = demo->textures[i].view; +// tex_descs[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +// } + +// memset(&writes, 0, sizeof(writes)); + +// writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +// writes[0].descriptorCount = 1; +// writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +// writes[0].pBufferInfo = &buffer_info; + +// writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +// writes[1].dstBinding = 1; +// writes[1].descriptorCount = DEMO_TEXTURE_COUNT; +// writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +// writes[1].pImageInfo = tex_descs; + +// for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { +// err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->swapchain_image_resources[i].descriptor_set); +// assert(!err); +// buffer_info.buffer = demo->swapchain_image_resources[i].uniform_buffer; +// writes[0].dstSet = demo->swapchain_image_resources[i].descriptor_set; +// writes[1].dstSet = demo->swapchain_image_resources[i].descriptor_set; +// vkUpdateDescriptorSets(demo->device, 2, writes, 0, NULL); +// } +// } + +static void demo_prepare_framebuffers(struct demo *demo) { + VkImageView attachments[2]; + attachments[1] = demo->depth.view; + + const VkFramebufferCreateInfo fb_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = NULL, + .renderPass = demo->render_pass, + .attachmentCount = 2, + .pAttachments = attachments, + .width = demo->width, + .height = demo->height, + .layers = 1, + }; + VkResult U_ASSERT_ONLY err; + uint32_t i; + + for (i = 0; i < demo->swapchainImageCount; i++) { + attachments[0] = demo->swapchain_image_resources[i].view; + err = vkCreateFramebuffer(demo->device, &fb_info, NULL, &demo->swapchain_image_resources[i].framebuffer); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_FRAMEBUFFER, (uint64_t)demo->swapchain_image_resources[i].framebuffer, + "Framebuffer(%u)", i); + } +} + +static void demo_prepare(struct demo *demo) { + demo_prepare_buffers(demo); + + if (demo->is_minimized) { + demo->prepared = false; + return; + } + + VkResult U_ASSERT_ONLY err; + if (demo->cmd_pool == VK_NULL_HANDLE) { + const VkCommandPoolCreateInfo cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .queueFamilyIndex = demo->graphics_queue_family_index, + .flags = 0, + }; + err = vkCreateCommandPool(demo->device, &cmd_pool_info, NULL, &demo->cmd_pool); + assert(!err); + } + + const VkCommandBufferAllocateInfo cmd = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = demo->cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->cmd); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)demo->cmd, "PrepareCB"); + VkCommandBufferBeginInfo cmd_buf_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = 0, + .pInheritanceInfo = NULL, + }; + err = vkBeginCommandBuffer(demo->cmd, &cmd_buf_info); + demo_push_cb_label(demo, demo->cmd, NULL, "Prepare"); + assert(!err); + + demo_prepare_depth(demo); + demo_prepare_textures(demo); + demo_prepare_cube_data_buffers(demo); + + // demo_prepare_descriptor_layout(demo); + demo_prepare_render_pass(demo); + demo_prepare_pipeline(demo); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateCommandBuffers(demo->device, &cmd, &demo->swapchain_image_resources[i].cmd); + assert(!err); + } + + if (demo->separate_present_queue) { + const VkCommandPoolCreateInfo present_cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = NULL, + .queueFamilyIndex = demo->present_queue_family_index, + .flags = 0, + }; + err = vkCreateCommandPool(demo->device, &present_cmd_pool_info, NULL, &demo->present_cmd_pool); + assert(!err); + const VkCommandBufferAllocateInfo present_cmd_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = demo->present_cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + err = vkAllocateCommandBuffers(demo->device, &present_cmd_info, + &demo->swapchain_image_resources[i].graphics_to_present_cmd); + assert(!err); + demo_build_image_ownership_cmd(demo, i); + demo_name_object(demo, VK_OBJECT_TYPE_COMMAND_BUFFER, + (uint64_t)demo->swapchain_image_resources[i].graphics_to_present_cmd, "GfxToPresent(%u)", i); + } + } + + // demo_prepare_descriptor_pool(demo); + // demo_prepare_descriptor_set(demo); + + demo_prepare_framebuffers(demo); + + for (uint32_t i = 0; i < demo->swapchainImageCount; i++) { + demo->current_buffer = i; + demo_draw_build_cmd(demo, demo->swapchain_image_resources[i].cmd); + } + + /* + * Prepare functions above may generate pipeline commands + * that need to be flushed before beginning the render loop. + */ + demo_pop_cb_label(demo, demo->cmd); // "Prepare" + demo_flush_init_cmd(demo); + if (demo->staging_texture.buffer) { + demo_destroy_texture(demo, &demo->staging_texture); + } + + demo->current_buffer = 0; + demo->prepared = true; + demo->first_swapchain_frame = true; +} + +static void demo_cleanup(struct demo *demo) { + uint32_t i; + + demo->prepared = false; + vkDeviceWaitIdle(demo->device); + + // Wait for fences from present operations + for (i = 0; i < FRAME_LAG; i++) { + vkWaitForFences(demo->device, 1, &demo->fences[i], VK_TRUE, UINT64_MAX); + vkDestroyFence(demo->device, demo->fences[i], NULL); + vkDestroySemaphore(demo->device, demo->image_acquired_semaphores[i], NULL); + vkDestroySemaphore(demo->device, demo->draw_complete_semaphores[i], NULL); + if (demo->separate_present_queue) { + vkDestroySemaphore(demo->device, demo->image_ownership_semaphores[i], NULL); + } + } + + // If the window is currently minimized, demo_resize has already done some cleanup for us. + if (!demo->is_minimized) { + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); + } + vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); + + vkDestroyPipeline(demo->device, demo->pipeline, NULL); + vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL); + vkDestroyRenderPass(demo->device, demo->render_pass, NULL); + vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL); + vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + vkDestroyImageView(demo->device, demo->textures[i].view, NULL); + vkDestroyImage(demo->device, demo->textures[i].image, NULL); + vkFreeMemory(demo->device, demo->textures[i].mem, NULL); + vkDestroySampler(demo->device, demo->textures[i].sampler, NULL); + } + vkDestroySwapchainKHR(demo->device, demo->swapchain, NULL); + + vkDestroyImageView(demo->device, demo->depth.view, NULL); + vkDestroyImage(demo->device, demo->depth.image, NULL); + vkFreeMemory(demo->device, demo->depth.mem, NULL); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); + vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); + vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); + vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); + } + free(demo->swapchain_image_resources); + free(demo->queue_props); + vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); + + if (demo->separate_present_queue) { + vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL); + } + } + vkDeviceWaitIdle(demo->device); + vkDestroyDevice(demo->device, NULL); + if (demo->validate) { + vkDestroyDebugUtilsMessengerEXT(demo->inst, demo->dbg_messenger, NULL); + } + vkDestroySurfaceKHR(demo->inst, demo->surface, NULL); + +#if defined(VK_USE_PLATFORM_XLIB_KHR) + XDestroyWindow(demo->display, demo->xlib_window); + XCloseDisplay(demo->display); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + xcb_destroy_window(demo->connection, demo->xcb_window); + xcb_disconnect(demo->connection); + free(demo->atom_wm_delete_window); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (demo->keyboard) wl_keyboard_destroy(demo->keyboard); + if (demo->pointer) wl_pointer_destroy(demo->pointer); + if (demo->seat) wl_seat_destroy(demo->seat); + xdg_toplevel_destroy(demo->xdg_toplevel); + xdg_surface_destroy(demo->xdg_surface); + wl_surface_destroy(demo->window); + xdg_wm_base_destroy(demo->xdg_wm_base); + if (demo->xdg_decoration_mgr) { + zxdg_toplevel_decoration_v1_destroy(demo->toplevel_decoration); + zxdg_decoration_manager_v1_destroy(demo->xdg_decoration_mgr); + } + wl_compositor_destroy(demo->compositor); + wl_registry_destroy(demo->registry); + wl_display_disconnect(demo->display); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo->event_buffer->Release(demo->event_buffer); + demo->window->Release(demo->window); + demo->dfb->Release(demo->dfb); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + screen_destroy_event(demo->screen_event); + screen_destroy_window(demo->screen_window); + screen_destroy_context(demo->screen_context); +#endif + + vkDestroyInstance(demo->inst, NULL); +} + +static void demo_resize(struct demo *demo) { + uint32_t i; + + // Don't react to resize until after first initialization. + if (!demo->prepared) { + if (demo->is_minimized) { + demo_prepare(demo); + } + return; + } + // In order to properly resize the window, we must re-create the swapchain + // AND redo the command buffers, etc. + // + // First, perform part of the demo_cleanup() function: + demo->prepared = false; + vkDeviceWaitIdle(demo->device); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL); + } + vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL); + + vkDestroyPipeline(demo->device, demo->pipeline, NULL); + vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL); + vkDestroyRenderPass(demo->device, demo->render_pass, NULL); + vkDestroyPipelineLayout(demo->device, demo->pipeline_layout, NULL); + vkDestroyDescriptorSetLayout(demo->device, demo->desc_layout, NULL); + + for (i = 0; i < DEMO_TEXTURE_COUNT; i++) { + vkDestroyImageView(demo->device, demo->textures[i].view, NULL); + vkDestroyImage(demo->device, demo->textures[i].image, NULL); + vkFreeMemory(demo->device, demo->textures[i].mem, NULL); + vkDestroySampler(demo->device, demo->textures[i].sampler, NULL); + } + + vkDestroyImageView(demo->device, demo->depth.view, NULL); + vkDestroyImage(demo->device, demo->depth.image, NULL); + vkFreeMemory(demo->device, demo->depth.mem, NULL); + + for (i = 0; i < demo->swapchainImageCount; i++) { + vkDestroyImageView(demo->device, demo->swapchain_image_resources[i].view, NULL); + vkFreeCommandBuffers(demo->device, demo->cmd_pool, 1, &demo->swapchain_image_resources[i].cmd); + vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL); + vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory); + vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL); + } + vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL); + demo->cmd_pool = VK_NULL_HANDLE; + if (demo->separate_present_queue) { + vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL); + } + free(demo->swapchain_image_resources); + + // Second, re-perform the demo_prepare() function, which will re-create the + // swapchain: + demo_prepare(demo); +} + +// On MS-Windows, make this a global, so it's available to WndProc() +struct demo demo; + +#if defined(VK_USE_PLATFORM_WIN32_KHR) +static void demo_run(struct demo *demo) { + if (!demo->prepared) return; + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + PostQuitMessage(validation_error); + } +} + +// MS-Windows event handling function: +LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CLOSE: + PostQuitMessage(validation_error); + break; + case WM_PAINT: + // The validation callback calls MessageBox which can generate paint + // events - don't make more Vulkan calls if we got here from the + // callback + if (!in_callback) { + demo_run(&demo); + } + break; + case WM_GETMINMAXINFO: // set window's minimum size + ((MINMAXINFO *)lParam)->ptMinTrackSize = demo.minsize; + return 0; + case WM_ERASEBKGND: + return 1; + case WM_SIZE: + // Resize the application to the new window size, except when + // it was minimized. Vulkan doesn't support images or swapchains + // with width=0 and height=0. + if (wParam != SIZE_MINIMIZED) { + demo.width = lParam & 0xffff; + demo.height = (lParam & 0xffff0000) >> 16; + demo_resize(&demo); + } + break; + case WM_KEYDOWN: + switch (wParam) { + case VK_ESCAPE: + PostQuitMessage(validation_error); + break; + case VK_LEFT: + demo.spin_angle -= demo.spin_increment; + break; + case VK_RIGHT: + demo.spin_angle += demo.spin_increment; + break; + case VK_SPACE: + demo.pause = !demo.pause; + break; + } + return 0; + default: + break; + } + return (DefWindowProc(hWnd, uMsg, wParam, lParam)); +} + +static void demo_create_window(struct demo *demo) { + WNDCLASSEX win_class; + + // Initialize the window class structure: + win_class.cbSize = sizeof(WNDCLASSEX); + win_class.style = CS_HREDRAW | CS_VREDRAW; + win_class.lpfnWndProc = WndProc; + win_class.cbClsExtra = 0; + win_class.cbWndExtra = 0; + win_class.hInstance = demo->connection; // hInstance + win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + win_class.hCursor = LoadCursor(NULL, IDC_ARROW); + win_class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + win_class.lpszMenuName = NULL; + win_class.lpszClassName = demo->name; + win_class.hIconSm = LoadIcon(NULL, IDI_WINLOGO); + // Register window class: + if (!RegisterClassEx(&win_class)) { + // It didn't work, so try to give a useful error: + printf("Unexpected error trying to start the application!\n"); + fflush(stdout); + exit(1); + } + // Create window with the registered class: + RECT wr = {0, 0, demo->width, demo->height}; + AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); + demo->window = CreateWindowEx(0, + demo->name, // class name + demo->name, // app name + WS_OVERLAPPEDWINDOW | // window style + WS_VISIBLE | WS_SYSMENU, + 100, 100, // x/y coords + wr.right - wr.left, // width + wr.bottom - wr.top, // height + NULL, // handle to parent + NULL, // handle to menu + demo->connection, // hInstance + NULL); // no extra parameters + if (!demo->window) { + // It didn't work, so try to give a useful error: + printf("Cannot create a window in which to draw!\n"); + fflush(stdout); + exit(1); + } + // Window client area size must be at least 1 pixel high, to prevent crash. + demo->minsize.x = GetSystemMetrics(SM_CXMINTRACK); + demo->minsize.y = GetSystemMetrics(SM_CYMINTRACK) + 1; +} +#elif defined(VK_USE_PLATFORM_XLIB_KHR) +static void demo_create_xlib_window(struct demo *demo) { + const char *display_envar = getenv("DISPLAY"); + if (display_envar == NULL || display_envar[0] == '\0') { + printf("Environment variable DISPLAY requires a valid value.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + XInitThreads(); + demo->display = XOpenDisplay(NULL); + long visualMask = VisualScreenMask; + int numberOfVisuals; + XVisualInfo vInfoTemplate = {}; + vInfoTemplate.screen = DefaultScreen(demo->display); + XVisualInfo *visualInfo = XGetVisualInfo(demo->display, visualMask, &vInfoTemplate, &numberOfVisuals); + + Colormap colormap = + XCreateColormap(demo->display, RootWindow(demo->display, vInfoTemplate.screen), visualInfo->visual, AllocNone); + + XSetWindowAttributes windowAttributes = {}; + windowAttributes.colormap = colormap; + windowAttributes.background_pixel = 0xFFFFFFFF; + windowAttributes.border_pixel = 0; + windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; + + demo->xlib_window = XCreateWindow(demo->display, RootWindow(demo->display, vInfoTemplate.screen), 0, 0, demo->width, + demo->height, 0, visualInfo->depth, InputOutput, visualInfo->visual, + CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes); + + XSelectInput(demo->display, demo->xlib_window, ExposureMask | KeyPressMask); + XMapWindow(demo->display, demo->xlib_window); + XFlush(demo->display); + demo->xlib_wm_delete_window = XInternAtom(demo->display, "WM_DELETE_WINDOW", False); +} +static void demo_handle_xlib_event(struct demo *demo, const XEvent *event) { + switch (event->type) { + case ClientMessage: + if ((Atom)event->xclient.data.l[0] == demo->xlib_wm_delete_window) demo->quit = true; + break; + case KeyPress: + switch (event->xkey.keycode) { + case 0x9: // Escape + demo->quit = true; + break; + case 0x71: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case 0x72: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case 0x41: // space bar + demo->pause = !demo->pause; + break; + } + break; + case ConfigureNotify: + if ((demo->width != event->xconfigure.width) || (demo->height != event->xconfigure.height)) { + demo->width = event->xconfigure.width; + demo->height = event->xconfigure.height; + demo_resize(demo); + } + break; + default: + break; + } +} + +static void demo_run_xlib(struct demo *demo) { + while (!demo->quit) { + XEvent event; + + if (demo->pause) { + XNextEvent(demo->display, &event); + demo_handle_xlib_event(demo, &event); + } + while (XPending(demo->display) > 0) { + XNextEvent(demo->display, &event); + demo_handle_xlib_event(demo, &event); + } + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } +} +#elif defined(VK_USE_PLATFORM_XCB_KHR) +static void demo_handle_xcb_event(struct demo *demo, const xcb_generic_event_t *event) { + uint8_t event_code = event->response_type & 0x7f; + switch (event_code) { + case XCB_EXPOSE: + // TODO: Resize window + break; + case XCB_CLIENT_MESSAGE: + if ((*(xcb_client_message_event_t *)event).data.data32[0] == (*demo->atom_wm_delete_window).atom) { + demo->quit = true; + } + break; + case XCB_KEY_RELEASE: { + const xcb_key_release_event_t *key = (const xcb_key_release_event_t *)event; + + switch (key->detail) { + case 0x9: // Escape + demo->quit = true; + break; + case 0x71: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case 0x72: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case 0x41: // space bar + demo->pause = !demo->pause; + break; + } + } break; + case XCB_CONFIGURE_NOTIFY: { + const xcb_configure_notify_event_t *cfg = (const xcb_configure_notify_event_t *)event; + if ((demo->width != cfg->width) || (demo->height != cfg->height)) { + demo->width = cfg->width; + demo->height = cfg->height; + demo_resize(demo); + } + } break; + default: + break; + } +} + +static void demo_run_xcb(struct demo *demo) { + xcb_flush(demo->connection); + + while (!demo->quit) { + xcb_generic_event_t *event; + + if (demo->pause) { + event = xcb_wait_for_event(demo->connection); + } else { + event = xcb_poll_for_event(demo->connection); + } + while (event) { + demo_handle_xcb_event(demo, event); + free(event); + event = xcb_poll_for_event(demo->connection); + } + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } +} + +static void demo_create_xcb_window(struct demo *demo) { + uint32_t value_mask, value_list[32]; + + demo->xcb_window = xcb_generate_id(demo->connection); + + value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + value_list[0] = demo->screen->black_pixel; + value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + xcb_create_window(demo->connection, XCB_COPY_FROM_PARENT, demo->xcb_window, demo->screen->root, 0, 0, demo->width, demo->height, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, demo->screen->root_visual, value_mask, value_list); + + /* Magic code that will send notification when window is destroyed */ + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(demo->connection, 1, 12, "WM_PROTOCOLS"); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(demo->connection, cookie, 0); + + xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(demo->connection, 0, 16, "WM_DELETE_WINDOW"); + demo->atom_wm_delete_window = xcb_intern_atom_reply(demo->connection, cookie2, 0); + + xcb_change_property(demo->connection, XCB_PROP_MODE_REPLACE, demo->xcb_window, (*reply).atom, 4, 32, 1, + &(*demo->atom_wm_delete_window).atom); + free(reply); + + xcb_map_window(demo->connection, demo->xcb_window); + + // Force the x/y coordinates to 100,100 results are identical in consecutive + // runs + const uint32_t coords[] = {100, 100}; + xcb_configure_window(demo->connection, demo->xcb_window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords); +} +// VK_USE_PLATFORM_XCB_KHR +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) +static void demo_run(struct demo *demo) { + while (!demo->quit) { + // Flush any commands to the server + wl_display_flush(demo->display); + + if (demo->pause) { + // block and wait for input + wl_display_dispatch(demo->display); + } else { + // Lock the display event queue in case the driver is doing something on another thread + // while we wait, keep pumping events + while (wl_display_prepare_read(demo->display) != 0) { + wl_display_dispatch_pending(demo->display); + } + // Actually do the read from the socket + wl_display_read_events(demo->display); + + // Pump events + wl_display_dispatch_pending(demo->display); + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } + } +} + +static void handle_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) { + struct demo *demo = (struct demo *)data; + xdg_surface_ack_configure(xdg_surface, serial); + if (demo->xdg_surface_has_been_configured) { + demo_resize(demo); + } + demo->xdg_surface_has_been_configured = 1; +} + +static const struct xdg_surface_listener xdg_surface_listener = {handle_surface_configure}; + +static void handle_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel UNUSED, int32_t width, int32_t height, + struct wl_array *states UNUSED) { + struct demo *demo = (struct demo *)data; + /* zero values imply the program may choose its own size, so in that case + * stay with the existing value (which on startup is the default) */ + if (width > 0) { + demo->width = width; + } + if (height > 0) { + demo->height = height; + } + /* This should be followed by a surface configure */ +} + +static void handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel UNUSED) { + struct demo *demo = (struct demo *)data; + demo->quit = true; +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = {handle_toplevel_configure, handle_toplevel_close}; + +static void demo_create_window(struct demo *demo) { + if (!demo->xdg_wm_base) { + printf("Compositor did not provide the standard protocol xdg-wm-base\n"); + fflush(stdout); + exit(1); + } + + demo->window = wl_compositor_create_surface(demo->compositor); + if (!demo->window) { + printf("Can not create wayland_surface from compositor!\n"); + fflush(stdout); + exit(1); + } + + demo->xdg_surface = xdg_wm_base_get_xdg_surface(demo->xdg_wm_base, demo->window); + if (!demo->xdg_surface) { + printf("Can not get xdg_surface from wayland_surface!\n"); + fflush(stdout); + exit(1); + } + demo->xdg_toplevel = xdg_surface_get_toplevel(demo->xdg_surface); + if (!demo->xdg_toplevel) { + printf("Can not allocate xdg_toplevel for xdg_surface!\n"); + fflush(stdout); + exit(1); + } + xdg_surface_add_listener(demo->xdg_surface, &xdg_surface_listener, demo); + xdg_toplevel_add_listener(demo->xdg_toplevel, &xdg_toplevel_listener, demo); + xdg_toplevel_set_title(demo->xdg_toplevel, APP_SHORT_NAME); + if (demo->xdg_decoration_mgr) { + // if supported, let the compositor render titlebars for us + demo->toplevel_decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration(demo->xdg_decoration_mgr, demo->xdg_toplevel); + zxdg_toplevel_decoration_v1_set_mode(demo->toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } + + wl_surface_commit(demo->window); +} +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) +static void demo_create_directfb_window(struct demo *demo) { + DFBResult ret; + + ret = DirectFBInit(NULL, NULL); + if (ret) { + printf("DirectFBInit failed to initialize DirectFB!\n"); + fflush(stdout); + exit(1); + } + + ret = DirectFBCreate(&demo->dfb); + if (ret) { + printf("DirectFBCreate failed to create main interface of DirectFB!\n"); + fflush(stdout); + exit(1); + } + + DFBSurfaceDescription desc; + desc.flags = DSDESC_CAPS | DSDESC_WIDTH | DSDESC_HEIGHT; + desc.caps = DSCAPS_PRIMARY; + desc.width = demo->width; + desc.height = demo->height; + ret = demo->dfb->CreateSurface(demo->dfb, &desc, &demo->window); + if (ret) { + printf("CreateSurface failed to create DirectFB surface interface!\n"); + fflush(stdout); + exit(1); + } + + ret = demo->dfb->CreateInputEventBuffer(demo->dfb, DICAPS_KEYS, DFB_FALSE, &demo->event_buffer); + if (ret) { + printf("CreateInputEventBuffer failed to create DirectFB event buffer interface!\n"); + fflush(stdout); + exit(1); + } +} + +static void demo_handle_directfb_event(struct demo *demo, const DFBInputEvent *event) { + if (event->type != DIET_KEYPRESS) return; + switch (event->key_symbol) { + case DIKS_ESCAPE: // Escape + demo->quit = true; + break; + case DIKS_CURSOR_LEFT: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case DIKS_CURSOR_RIGHT: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case DIKS_SPACE: // space bar + demo->pause = !demo->pause; + break; + default: + break; + } +} + +static void demo_run_directfb(struct demo *demo) { + while (!demo->quit) { + DFBInputEvent event; + + if (demo->pause) { + demo->event_buffer->WaitForEvent(demo->event_buffer); + if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); + } else { + if (!demo->event_buffer->GetEvent(demo->event_buffer, DFB_EVENT(&event))) demo_handle_directfb_event(demo, &event); + + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) demo->quit = true; + } + } +} +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) +static void demo_run(struct demo *demo) { + if (!demo->prepared) return; + + demo_draw(demo); + demo->curFrame++; +} +#elif defined(VK_USE_PLATFORM_METAL_EXT) +static void demo_run(struct demo *demo) { + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = TRUE; + } +} +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) +static VkResult demo_create_display_surface(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + uint32_t display_count; + uint32_t mode_count; + uint32_t plane_count; + VkDisplayPropertiesKHR display_props; + VkDisplayKHR display; + VkDisplayModePropertiesKHR mode_props; + VkDisplayPlanePropertiesKHR *plane_props; + VkBool32 found_plane = VK_FALSE; + uint32_t plane_index; + VkExtent2D image_extent; + VkDisplaySurfaceCreateInfoKHR create_info; + + // Get the first display + display_count = 1; + err = vkGetPhysicalDeviceDisplayPropertiesKHR(demo->gpu, &display_count, &display_props); + assert(!err || (err == VK_INCOMPLETE)); + + display = display_props.display; + + // Get the first mode of the display + err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, NULL); + assert(!err); + + if (mode_count == 0) { + printf("Cannot find any mode for the display!\n"); + fflush(stdout); + exit(1); + } + + mode_count = 1; + err = vkGetDisplayModePropertiesKHR(demo->gpu, display, &mode_count, &mode_props); + assert(!err || (err == VK_INCOMPLETE)); + + // Get the list of planes + err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, NULL); + assert(!err); + + if (plane_count == 0) { + printf("Cannot find any plane!\n"); + fflush(stdout); + exit(1); + } + + plane_props = malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count); + assert(plane_props); + + err = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(demo->gpu, &plane_count, plane_props); + assert(!err); + + // Find a plane compatible with the display + for (plane_index = 0; plane_index < plane_count; plane_index++) { + uint32_t supported_count; + VkDisplayKHR *supported_displays; + + // Disqualify planes that are bound to a different display + if ((plane_props[plane_index].currentDisplay != VK_NULL_HANDLE) && (plane_props[plane_index].currentDisplay != display)) { + continue; + } + + err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, NULL); + assert(!err); + + if (supported_count == 0) { + continue; + } + + supported_displays = malloc(sizeof(VkDisplayKHR) * supported_count); + assert(supported_displays); + + err = vkGetDisplayPlaneSupportedDisplaysKHR(demo->gpu, plane_index, &supported_count, supported_displays); + assert(!err); + + for (uint32_t i = 0; i < supported_count; i++) { + if (supported_displays[i] == display) { + found_plane = VK_TRUE; + break; + } + } + + free(supported_displays); + + if (found_plane) { + break; + } + } + + if (!found_plane) { + printf("Cannot find a plane compatible with the display!\n"); + fflush(stdout); + exit(1); + } + + free(plane_props); + + VkDisplayPlaneCapabilitiesKHR planeCaps; + vkGetDisplayPlaneCapabilitiesKHR(demo->gpu, mode_props.displayMode, plane_index, &planeCaps); + // Find a supported alpha mode + VkDisplayPlaneAlphaFlagBitsKHR alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; + VkDisplayPlaneAlphaFlagBitsKHR alphaModes[4] = { + VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR, + VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR, + }; + for (uint32_t i = 0; i < sizeof(alphaModes); i++) { + if (planeCaps.supportedAlpha & alphaModes[i]) { + alphaMode = alphaModes[i]; + break; + } + } + image_extent.width = mode_props.parameters.visibleRegion.width; + image_extent.height = mode_props.parameters.visibleRegion.height; + + create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR; + create_info.pNext = NULL; + create_info.flags = 0; + create_info.displayMode = mode_props.displayMode; + create_info.planeIndex = plane_index; + create_info.planeStackIndex = plane_props[plane_index].currentStackIndex; + create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + create_info.alphaMode = alphaMode; + create_info.globalAlpha = 1.0f; + create_info.imageExtent = image_extent; + + return vkCreateDisplayPlaneSurfaceKHR(demo->inst, &create_info, NULL, &demo->surface); +} + +static void demo_run_display(struct demo *demo) { + while (!demo->quit) { + demo_draw(demo); + demo->curFrame++; + + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = true; + } + } +} +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + +#include + +static void demo_run(struct demo *demo) { + int size[2] = {0, 0}; + screen_window_t win; + int val; + int rc; + + while (!demo->quit) { + while (!screen_get_event(demo->screen_context, demo->screen_event, demo->pause ? ~0 : 0)) { + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_TYPE, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_TYPE of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (val == SCREEN_EVENT_NONE) { + break; + } + switch (val) { + case SCREEN_EVENT_KEYBOARD: + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_FLAGS, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_FLAGS of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (val & KEY_DOWN) { + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_SYM, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_SYM of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + switch (val) { + case KEYCODE_ESCAPE: + demo->quit = true; + break; + case KEYCODE_SPACE: + demo->pause = !demo->pause; + break; + case KEYCODE_LEFT: + demo->spin_angle -= demo->spin_increment; + break; + case KEYCODE_RIGHT: + demo->spin_angle += demo->spin_increment; + break; + default: + break; + } + } + break; + case SCREEN_EVENT_PROPERTY: + rc = screen_get_event_property_pv(demo->screen_event, SCREEN_PROPERTY_WINDOW, (void **)&win); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_WINDOW of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + rc = screen_get_event_property_iv(demo->screen_event, SCREEN_PROPERTY_NAME, &val); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_NAME of the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + if (win == demo->screen_window) { + switch (val) { + case SCREEN_PROPERTY_SIZE: + rc = screen_get_window_property_iv(win, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot get SCREEN_PROPERTY_SIZE of the window in the event! (%s)\n", strerror(errno)); + fflush(stdout); + demo->quit = true; + break; + } + demo->width = size[0]; + demo->height = size[1]; + demo_resize(demo); + break; + default: + /* We are not interested in any other events for now */ + break; + } + } + break; + } + } + + if (demo->pause) { + } else { + demo_draw(demo); + demo->curFrame++; + if (demo->frameCount != INT32_MAX && demo->curFrame == demo->frameCount) { + demo->quit = true; + } + } + } +} + +static void demo_create_window(struct demo *demo) { + const char *idstr = APP_SHORT_NAME; + int size[2]; + int usage = SCREEN_USAGE_VULKAN; + int rc; + + rc = screen_create_context(&demo->screen_context, 0); + if (rc) { + printf("Cannot create QNX Screen context!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + rc = screen_create_window(&demo->screen_window, demo->screen_context); + if (rc) { + printf("Cannot create QNX Screen window!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + rc = screen_create_event(&demo->screen_event); + if (rc) { + printf("Cannot create QNX Screen event!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + + /* Set window caption */ + screen_set_window_property_cv(demo->screen_window, SCREEN_PROPERTY_ID_STRING, strlen(idstr), idstr); + + /* Setup VULKAN usage flags */ + rc = screen_set_window_property_iv(demo->screen_window, SCREEN_PROPERTY_USAGE, &usage); + if (rc) { + printf("Cannot set SCREEN_USAGE_VULKAN flag!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + + /* Setup window size */ + if ((demo->width == 0) || (demo->height == 0)) { + /* Obtain automatically set window size provided by WM */ + rc = screen_get_window_property_iv(demo->screen_window, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot obtain current window size!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + demo->width = size[0]; + demo->height = size[1]; + } else { + size[0] = demo->width; + size[1] = demo->height; + rc = screen_set_window_property_iv(demo->screen_window, SCREEN_PROPERTY_SIZE, size); + if (rc) { + printf("Cannot set window size!\n"); + fflush(stdout); + exit(EXIT_FAILURE); + } + } +} +#endif + +/* + * Return 1 (true) if all layer names specified in check_names + * can be found in given layer properties. + */ +static VkBool32 demo_check_layers(uint32_t check_count, char **check_names, uint32_t layer_count, VkLayerProperties *layers) { + for (uint32_t i = 0; i < check_count; i++) { + VkBool32 found = 0; + for (uint32_t j = 0; j < layer_count; j++) { + if (!strcmp(check_names[i], layers[j].layerName)) { + found = 1; + break; + } + } + if (!found) { + fprintf(stderr, "Cannot find layer: %s\n", check_names[i]); + return 0; + } + } + return 1; +} +#if defined(VK_USE_PLATFORM_DISPLAY_KHR) +int find_display_gpu(int gpu_number, uint32_t gpu_count, VkPhysicalDevice *physical_devices) { + uint32_t display_count = 0; + VkResult U_ASSERT_ONLY result; + int gpu_return = gpu_number; + if (gpu_number >= 0) { + result = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_devices[gpu_number], &display_count, NULL); + assert(!result); + } else { + for (uint32_t i = 0; i < gpu_count; i++) { + result = vkGetPhysicalDeviceDisplayPropertiesKHR(physical_devices[i], &display_count, NULL); + assert(!result); + if (display_count) { + gpu_return = i; + break; + } + } + } + if (display_count > 0) + return gpu_return; + else + return -1; +} +#endif +static void demo_init_vk(struct demo *demo) { + VkResult err; + uint32_t instance_extension_count = 0; + uint32_t instance_layer_count = 0; + char *instance_validation_layers[] = {"VK_LAYER_KHRONOS_validation"}; + demo->enabled_extension_count = 0; + demo->enabled_layer_count = 0; + demo->is_minimized = false; + demo->cmd_pool = VK_NULL_HANDLE; + + err = volkInitialize(); + if (err != VK_SUCCESS) { + ERR_EXIT( + "Unable to find the Vulkan runtime on the system.\n\n" + "This likely indicates that no Vulkan capable drivers are installed.", + "Installation Failure"); + } + // Look for validation layers + VkBool32 validation_found = 0; + if (demo->validate) { + err = vkEnumerateInstanceLayerProperties(&instance_layer_count, NULL); + assert(!err); + + if (instance_layer_count > 0) { + VkLayerProperties *instance_layers = malloc(sizeof(VkLayerProperties) * instance_layer_count); + err = vkEnumerateInstanceLayerProperties(&instance_layer_count, instance_layers); + assert(!err); + + validation_found = demo_check_layers(ARRAY_SIZE(instance_validation_layers), instance_validation_layers, + instance_layer_count, instance_layers); + if (validation_found) { + demo->enabled_layer_count = ARRAY_SIZE(instance_validation_layers); + demo->enabled_layers[0] = "VK_LAYER_KHRONOS_validation"; + } + free(instance_layers); + } + + if (!validation_found) { + ERR_EXIT( + "vkEnumerateInstanceLayerProperties failed to find required validation layer.\n\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + } + + /* Look for instance extensions */ + VkBool32 surfaceExtFound = 0; + VkBool32 platformSurfaceExtFound = 0; + bool portabilityEnumerationActive = false; + memset(demo->extension_names, 0, sizeof(demo->extension_names)); + + err = vkEnumerateInstanceExtensionProperties(NULL, &instance_extension_count, NULL); + assert(!err); + + if (instance_extension_count > 0) { + VkExtensionProperties *instance_extensions = malloc(sizeof(VkExtensionProperties) * instance_extension_count); + err = vkEnumerateInstanceExtensionProperties(NULL, &instance_extension_count, instance_extensions); + assert(!err); + for (uint32_t i = 0; i < instance_extension_count; i++) { + if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + surfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_SURFACE_EXTENSION_NAME; + } +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (!strcmp(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_WIN32_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + if (!strcmp(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_XLIB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_XCB_KHR) + if (!strcmp(VK_KHR_XCB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_XCB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (!strcmp(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + if (!strcmp(VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + if (!strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_DISPLAY_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + if (!strcmp(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_ANDROID_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_METAL_EXT) + if (!strcmp(VK_EXT_METAL_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_METAL_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + if (!strcmp(VK_QNX_SCREEN_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_QNX_SCREEN_SURFACE_EXTENSION_NAME; + } +#endif + if (!strcmp(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, instance_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + } + if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, instance_extensions[i].extensionName)) { + if (demo->validate) { + demo->extension_names[demo->enabled_extension_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + } + } + // We want cube to be able to enumerate drivers that support the portability_subset extension, so we have to enable the + // portability enumeration extension. + if (!strcmp(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, instance_extensions[i].extensionName)) { + portabilityEnumerationActive = true; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + } + assert(demo->enabled_extension_count < 64); + } + + free(instance_extensions); + } + + if (!surfaceExtFound) { + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + if (!platformSurfaceExtFound) { +#if defined(VK_USE_PLATFORM_WIN32_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_WIN32_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_EXT_METAL_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_XCB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_DISPLAY_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_ANDROID_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_KHR_XLIB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_QNX_SCREEN_SURFACE_EXTENSION_NAME + " extension.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); +#endif + } + const VkApplicationInfo app = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = NULL, + .pApplicationName = APP_SHORT_NAME, + .applicationVersion = 0, + .pEngineName = APP_SHORT_NAME, + .engineVersion = 0, + .apiVersion = VK_API_VERSION_1_0, + }; + VkInstanceCreateInfo inst_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = NULL, + .flags = (portabilityEnumerationActive ? VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR : 0), + .pApplicationInfo = &app, + .enabledLayerCount = demo->enabled_layer_count, + .ppEnabledLayerNames = (const char *const *)instance_validation_layers, + .enabledExtensionCount = demo->enabled_extension_count, + .ppEnabledExtensionNames = (const char *const *)demo->extension_names, + }; + + /* + * This is info for a temp callback to use during CreateInstance. + * After the instance is created, we use the instance-based + * function to register the final callback. + */ + VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info; + if (demo->validate) { + // VK_EXT_debug_utils style + dbg_messenger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + dbg_messenger_create_info.pNext = NULL; + dbg_messenger_create_info.flags = 0; + dbg_messenger_create_info.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + dbg_messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + dbg_messenger_create_info.pfnUserCallback = debug_messenger_callback; + dbg_messenger_create_info.pUserData = demo; + inst_info.pNext = &dbg_messenger_create_info; + } + + err = vkCreateInstance(&inst_info, NULL, &demo->inst); + if (err == VK_ERROR_INCOMPATIBLE_DRIVER) { + ERR_EXIT( + "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } else if (err == VK_ERROR_EXTENSION_NOT_PRESENT) { + ERR_EXIT( + "Cannot find a specified extension library.\n" + "Make sure your layers path is set appropriately.\n", + "vkCreateInstance Failure"); + } else if (err) { + ERR_EXIT( + "vkCreateInstance failed.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + + volkLoadInstance(demo->inst); + + /* Make initial call to query gpu_count, then second call for gpu info */ + uint32_t gpu_count = 0; + err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, NULL); + assert(!err); + + if (gpu_count <= 0) { + ERR_EXIT( + "vkEnumeratePhysicalDevices reported zero accessible devices.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkEnumeratePhysicalDevices Failure"); + } + + VkPhysicalDevice *physical_devices = malloc(sizeof(VkPhysicalDevice) * gpu_count); + err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, physical_devices); + assert(!err); + if (demo->invalid_gpu_selection || (demo->gpu_number >= 0 && !((uint32_t)demo->gpu_number < gpu_count))) { + fprintf(stderr, "GPU %d specified is not present, GPU count = %u\n", demo->gpu_number, gpu_count); + ERR_EXIT("Specified GPU number is not present", "User Error"); + } + +#if defined(VK_USE_PLATFORM_DISPLAY_KHR) + demo->gpu_number = find_display_gpu(demo->gpu_number, gpu_count, physical_devices); + if (demo->gpu_number < 0) { + printf("Cannot find any display!\n"); + fflush(stdout); + exit(1); + } +#else + /* Try to auto select most suitable device */ + if (demo->gpu_number == -1) { + uint32_t count_device_type[VK_PHYSICAL_DEVICE_TYPE_CPU + 1]; + memset(count_device_type, 0, sizeof(count_device_type)); + + VkPhysicalDeviceProperties physicalDeviceProperties; + for (uint32_t i = 0; i < gpu_count; i++) { + vkGetPhysicalDeviceProperties(physical_devices[i], &physicalDeviceProperties); + assert(physicalDeviceProperties.deviceType <= VK_PHYSICAL_DEVICE_TYPE_CPU); + count_device_type[physicalDeviceProperties.deviceType]++; + } + + VkPhysicalDeviceType search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_CPU]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_CPU; + } else if (count_device_type[VK_PHYSICAL_DEVICE_TYPE_OTHER]) { + search_for_device_type = VK_PHYSICAL_DEVICE_TYPE_OTHER; + } + + for (uint32_t i = 0; i < gpu_count; i++) { + vkGetPhysicalDeviceProperties(physical_devices[i], &physicalDeviceProperties); + if (physicalDeviceProperties.deviceType == search_for_device_type) { + demo->gpu_number = i; + break; + } + } + } +#endif + assert(demo->gpu_number >= 0); + demo->gpu = physical_devices[demo->gpu_number]; + { + VkPhysicalDeviceProperties physicalDeviceProperties; + vkGetPhysicalDeviceProperties(demo->gpu, &physicalDeviceProperties); + fprintf(stderr, "Selected GPU %d: %s, type: %s\n", demo->gpu_number, physicalDeviceProperties.deviceName, + to_string(physicalDeviceProperties.deviceType)); + } + free(physical_devices); + + /* Look for device extensions */ + uint32_t device_extension_count = 0; + VkBool32 swapchainExtFound = 0; + demo->enabled_extension_count = 0; + memset(demo->extension_names, 0, sizeof(demo->extension_names)); + + err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL, &device_extension_count, NULL); + assert(!err); + + if (device_extension_count > 0) { + VkExtensionProperties *device_extensions = malloc(sizeof(VkExtensionProperties) * device_extension_count); + err = vkEnumerateDeviceExtensionProperties(demo->gpu, NULL, &device_extension_count, device_extensions); + assert(!err); + + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, device_extensions[i].extensionName)) { + swapchainExtFound = 1; + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; + } + if (!strcmp("VK_KHR_portability_subset", device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = "VK_KHR_portability_subset"; + } +#if DESCRIPTOR_HEAP + // VK_KHR_DESCRIPTOR_HEAP + if (!strcmp(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NAME, device_extensions[i].extensionName)) + { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NAME; + } + // VK_KHR_buffer_device_address + if (!strcmp(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, device_extensions[i].extensionName)) + { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME; + } +#endif + assert(demo->enabled_extension_count < 64); + } + + if (demo->VK_KHR_incremental_present_enabled) { + // Even though the user "enabled" the extension via the command + // line, we must make sure that it's enumerated for use with the + // device. Therefore, disable it here, and re-enable it again if + // enumerated. + demo->VK_KHR_incremental_present_enabled = false; + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME; + demo->VK_KHR_incremental_present_enabled = true; + DbgMsg("VK_KHR_incremental_present extension enabled\n"); + } + assert(demo->enabled_extension_count < 64); + } + if (!demo->VK_KHR_incremental_present_enabled) { + DbgMsg("VK_KHR_incremental_present extension NOT AVAILABLE\n"); + } + } + + if (demo->VK_GOOGLE_display_timing_enabled) { + // Even though the user "enabled" the extension via the command + // line, we must make sure that it's enumerated for use with the + // device. Therefore, disable it here, and re-enable it again if + // enumerated. + demo->VK_GOOGLE_display_timing_enabled = false; + for (uint32_t i = 0; i < device_extension_count; i++) { + if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, device_extensions[i].extensionName)) { + demo->extension_names[demo->enabled_extension_count++] = VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME; + demo->VK_GOOGLE_display_timing_enabled = true; + DbgMsg("VK_GOOGLE_display_timing extension enabled\n"); + } + assert(demo->enabled_extension_count < 64); + } + if (!demo->VK_GOOGLE_display_timing_enabled) { + DbgMsg("VK_GOOGLE_display_timing extension NOT AVAILABLE\n"); + } + } + + free(device_extensions); + } + + if (!swapchainExtFound) { + ERR_EXIT("vkEnumerateDeviceExtensionProperties failed to find the " VK_KHR_SWAPCHAIN_EXTENSION_NAME + " extension.\n\nDo you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n", + "vkCreateInstance Failure"); + } + + if (demo->validate) { + err = vkCreateDebugUtilsMessengerEXT(demo->inst, &dbg_messenger_create_info, NULL, &demo->dbg_messenger); + switch (err) { + case VK_SUCCESS: + break; + case VK_ERROR_OUT_OF_HOST_MEMORY: + ERR_EXIT("CreateDebugUtilsMessengerEXT: out of host memory\n", "CreateDebugUtilsMessengerEXT Failure"); + break; + default: + ERR_EXIT("CreateDebugUtilsMessengerEXT: unknown failure\n", "CreateDebugUtilsMessengerEXT Failure"); + break; + } + } + vkGetPhysicalDeviceProperties(demo->gpu, &demo->gpu_props); + + /* Call with NULL data to get count */ + vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_family_count, NULL); + assert(demo->queue_family_count >= 1); + + demo->queue_props = (VkQueueFamilyProperties *)malloc(demo->queue_family_count * sizeof(VkQueueFamilyProperties)); + vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_family_count, demo->queue_props); + + // Query fine-grained feature support for this device. + // If app has specific feature requirements it should check supported + // features based on this query + VkPhysicalDeviceFeatures physDevFeatures; + vkGetPhysicalDeviceFeatures(demo->gpu, &physDevFeatures); + +#if DESCRIPTOR_HEAP + //===============leiter + VkPhysicalDeviceFeatures2 features2 = {}; + features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + + // VkPhysicalDeviceDescriptorHeapFeaturesKHR descriptorHeapFeature = {}; + demo->descriptorHeapFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_KHR; + demo->descriptorHeapFeature.pNext = NULL; + + features2.pNext = (void*)(&demo->descriptorHeapFeature); + vkGetPhysicalDeviceFeatures2(demo->gpu, &features2); + + VkPhysicalDeviceProperties2 properties2 = {}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + + // VkPhysicalDeviceDescriptorHeapPropertiesKHR descriptorHeapProperties = {}; + demo->descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_KHR; + demo->descriptorHeapProperties.pNext = NULL; + properties2.pNext = (void *)(&demo->descriptorHeapProperties); + vkGetPhysicalDeviceProperties2(demo->gpu, &properties2); +#endif +} + +static void demo_create_device(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + float queue_priorities[1] = {0.0}; + VkDeviceQueueCreateInfo queues[2]; + queues[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queues[0].pNext = NULL; + queues[0].queueFamilyIndex = demo->graphics_queue_family_index; + queues[0].queueCount = 1; + queues[0].pQueuePriorities = queue_priorities; + queues[0].flags = 0; + + VkDeviceCreateInfo device = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = NULL, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = queues, + .enabledLayerCount = 0, + .ppEnabledLayerNames = NULL, + .enabledExtensionCount = demo->enabled_extension_count, + .ppEnabledExtensionNames = (const char *const *)demo->extension_names, + .pEnabledFeatures = NULL, // If specific features are required, pass them in here + }; + if (demo->separate_present_queue) { + queues[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queues[1].pNext = NULL; + queues[1].queueFamilyIndex = demo->present_queue_family_index; + queues[1].queueCount = 1; + queues[1].pQueuePriorities = queue_priorities; + queues[1].flags = 0; + device.queueCreateInfoCount = 2; + } + err = vkCreateDevice(demo->gpu, &device, NULL, &demo->device); + assert(!err); + + volkLoadDevice(demo->device); +} + +static void demo_create_surface(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + +// Create a WSI surface for the window: +#if defined(VK_USE_PLATFORM_WIN32_KHR) + VkWin32SurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.hinstance = demo->connection; + createInfo.hwnd = demo->window; + + err = vkCreateWin32SurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + VkWaylandSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.display = demo->display; + createInfo.surface = demo->window; + + err = vkCreateWaylandSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + VkAndroidSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.window = (struct ANativeWindow *)(demo->window); + + err = vkCreateAndroidSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + VkXlibSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.dpy = demo->display; + createInfo.window = demo->xlib_window; + + err = vkCreateXlibSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + VkXcbSurfaceCreateInfoKHR createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.connection = demo->connection; + createInfo.window = demo->xcb_window; + + err = vkCreateXcbSurfaceKHR(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + VkDirectFBSurfaceCreateInfoEXT createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.dfb = demo->dfb; + createInfo.surface = demo->window; + + err = vkCreateDirectFBSurfaceEXT(demo->inst, &createInfo, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + err = demo_create_display_surface(demo); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + VkMetalSurfaceCreateInfoEXT surface; + surface.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + surface.pNext = NULL; + surface.flags = 0; + surface.pLayer = demo->caMetalLayer; + + err = vkCreateMetalSurfaceEXT(demo->inst, &surface, NULL, &demo->surface); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + VkScreenSurfaceCreateInfoQNX createInfo; + createInfo.sType = VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.context = demo->screen_context; + createInfo.window = demo->screen_window; + + err = vkCreateScreenSurfaceQNX(demo->inst, &createInfo, NULL, &demo->surface); +#endif + assert(!err); +} + +static VkSurfaceFormatKHR pick_surface_format(const VkSurfaceFormatKHR *surfaceFormats, uint32_t count) { + // Prefer non-SRGB formats... + for (uint32_t i = 0; i < count; i++) { + const VkFormat format = surfaceFormats[i].format; + + if (format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_B8G8R8A8_UNORM || + format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || format == VK_FORMAT_A2R10G10B10_UNORM_PACK32 || + format == VK_FORMAT_A1R5G5B5_UNORM_PACK16 || format == VK_FORMAT_R5G6B5_UNORM_PACK16 || + format == VK_FORMAT_R16G16B16A16_SFLOAT) { + return surfaceFormats[i]; + } + } + + printf("Can't find our preferred formats... Falling back to first exposed format. Rendering may be incorrect.\n"); + + assert(count >= 1); + return surfaceFormats[0]; +} + +static void demo_init_vk_swapchain(struct demo *demo) { + VkResult U_ASSERT_ONLY err; + + demo_create_surface(demo); + + // Iterate over each queue to learn whether it supports presenting: + VkBool32 *supportsPresent = (VkBool32 *)malloc(demo->queue_family_count * sizeof(VkBool32)); + for (uint32_t i = 0; i < demo->queue_family_count; i++) { + vkGetPhysicalDeviceSurfaceSupportKHR(demo->gpu, i, demo->surface, &supportsPresent[i]); + } + + // Search for a graphics and a present queue in the array of queue + // families, try to find one that supports both + uint32_t graphicsQueueFamilyIndex = UINT32_MAX; + uint32_t presentQueueFamilyIndex = UINT32_MAX; + for (uint32_t i = 0; i < demo->queue_family_count; i++) { + if ((demo->queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { + if (graphicsQueueFamilyIndex == UINT32_MAX) { + graphicsQueueFamilyIndex = i; + } + + if (supportsPresent[i] == VK_TRUE) { + graphicsQueueFamilyIndex = i; + presentQueueFamilyIndex = i; + break; + } + } + } + + if (presentQueueFamilyIndex == UINT32_MAX) { + // If didn't find a queue that supports both graphics and present, then + // find a separate present queue. + for (uint32_t i = 0; i < demo->queue_family_count; ++i) { + if (supportsPresent[i] == VK_TRUE) { + presentQueueFamilyIndex = i; + break; + } + } + } + + // Generate error if could not find both a graphics and a present queue + if (graphicsQueueFamilyIndex == UINT32_MAX || presentQueueFamilyIndex == UINT32_MAX) { + ERR_EXIT("Could not find both graphics and present queues\n", "Swapchain Initialization Failure"); + } + + demo->graphics_queue_family_index = graphicsQueueFamilyIndex; + demo->present_queue_family_index = presentQueueFamilyIndex; + demo->separate_present_queue = (demo->graphics_queue_family_index != demo->present_queue_family_index); + free(supportsPresent); + + demo_create_device(demo); + + vkGetDeviceQueue(demo->device, demo->graphics_queue_family_index, 0, &demo->graphics_queue); + + if (!demo->separate_present_queue) { + demo->present_queue = demo->graphics_queue; + } else { + vkGetDeviceQueue(demo->device, demo->present_queue_family_index, 0, &demo->present_queue); + } + + // Get the list of VkFormat's that are supported: + uint32_t formatCount; + err = vkGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface, &formatCount, NULL); + assert(!err); + VkSurfaceFormatKHR *surfFormats = (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR)); + err = vkGetPhysicalDeviceSurfaceFormatsKHR(demo->gpu, demo->surface, &formatCount, surfFormats); + assert(!err); + VkSurfaceFormatKHR surfaceFormat = pick_surface_format(surfFormats, formatCount); + demo->format = surfaceFormat.format; + demo->color_space = surfaceFormat.colorSpace; + free(surfFormats); + + demo->quit = false; + demo->curFrame = 0; + + // Create semaphores to synchronize acquiring presentable buffers before + // rendering and waiting for drawing to be complete before presenting + VkSemaphoreCreateInfo semaphoreCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + }; + + // Create fences that we can use to throttle if we get too far + // ahead of the image presents + VkFenceCreateInfo fence_ci = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = VK_FENCE_CREATE_SIGNALED_BIT}; + for (uint32_t i = 0; i < FRAME_LAG; i++) { + err = vkCreateFence(demo->device, &fence_ci, NULL, &demo->fences[i]); + assert(!err); + + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_acquired_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_acquired_semaphores[i], "AcquireSem(%u)", i); + + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->draw_complete_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->draw_complete_semaphores[i], "DrawCompleteSem(%u)", i); + + if (demo->separate_present_queue) { + err = vkCreateSemaphore(demo->device, &semaphoreCreateInfo, NULL, &demo->image_ownership_semaphores[i]); + assert(!err); + demo_name_object(demo, VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)demo->image_ownership_semaphores[i], "ImageOwnerSem(%u)", i); + } + } + demo->frame_index = 0; + demo->first_swapchain_frame = true; + + // Get Memory information and properties + vkGetPhysicalDeviceMemoryProperties(demo->gpu, &demo->memory_properties); +} + +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) +static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, + wl_fixed_t sy) {} + +static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) {} + +static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) { + struct demo *demo = data; + if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) { + xdg_toplevel_move(demo->xdg_toplevel, demo->seat, serial); + } +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, +}; + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) {} + +static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) {} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) {} + +static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) { + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) return; + struct demo *demo = data; + switch (key) { + case KEY_ESC: // Escape + demo->quit = true; + break; + case KEY_LEFT: // left arrow key + demo->spin_angle -= demo->spin_increment; + break; + case KEY_RIGHT: // right arrow key + demo->spin_angle += demo->spin_increment; + break; + case KEY_SPACE: // space bar + demo->pause = !demo->pause; + break; + } +} + +static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, +}; + +static void seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { + // Subscribe to pointer events + struct demo *demo = data; + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !demo->pointer) { + demo->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(demo->pointer, &pointer_listener, demo); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && demo->pointer) { + wl_pointer_destroy(demo->pointer); + demo->pointer = NULL; + } + // Subscribe to keyboard events + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + demo->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(demo->keyboard, &keyboard_listener, demo); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && demo->keyboard) { + wl_keyboard_destroy(demo->keyboard); + demo->keyboard = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void wm_base_ping(void *data UNUSED, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener wm_base_listener = {wm_base_ping}; + +static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version UNUSED) { + struct demo *demo = data; + // pickup wayland objects when they appear + if (strcmp(interface, wl_compositor_interface.name) == 0) { + uint32_t minVersion = version < 4 ? version : 4; + demo->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, minVersion); + if (demo->VK_KHR_incremental_present_enabled && minVersion < 4) { + fprintf(stderr, "Wayland compositor doesn't support VK_KHR_incremental_present, disabling.\n"); + demo->VK_KHR_incremental_present_enabled = false; + } + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { + demo->xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(demo->xdg_wm_base, &wm_base_listener, NULL); + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + demo->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(demo->seat, &seat_listener, demo); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + demo->xdg_decoration_mgr = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } +} + +static void registry_handle_global_remove(void *data UNUSED, struct wl_registry *registry UNUSED, uint32_t name UNUSED) {} + +static const struct wl_registry_listener registry_listener = {registry_handle_global, registry_handle_global_remove}; +#endif + +static void demo_init_connection(struct demo *demo) { +#if defined(VK_USE_PLATFORM_XCB_KHR) + const xcb_setup_t *setup; + xcb_screen_iterator_t iter; + int scr; + + const char *display_envar = getenv("DISPLAY"); + if (display_envar == NULL || display_envar[0] == '\0') { + printf("Environment variable DISPLAY requires a valid value.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + demo->connection = xcb_connect(NULL, &scr); + if (xcb_connection_has_error(demo->connection) > 0) { + printf("Cannot connect to XCB.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + setup = xcb_get_setup(demo->connection); + iter = xcb_setup_roots_iterator(setup); + while (scr-- > 0) xcb_screen_next(&iter); + + demo->screen = iter.data; +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo->display = wl_display_connect(NULL); + + if (demo->display == NULL) { + printf("Cannot connect to wayland.\nExiting ...\n"); + fflush(stdout); + exit(1); + } + + demo->registry = wl_display_get_registry(demo->display); + wl_registry_add_listener(demo->registry, ®istry_listener, demo); + wl_display_roundtrip(demo->display); +#endif +} + +static void demo_init(struct demo *demo, int argc, char **argv) { + vec3 eye = {0.0f, 3.0f, 5.0f}; + vec3 origin = {0, 0, 0}; + vec3 up = {0.0f, 1.0f, 0.0}; + + memset(demo, 0, sizeof(*demo)); + demo->presentMode = VK_PRESENT_MODE_FIFO_KHR; + demo->frameCount = INT32_MAX; + /* Autodetect suitable / best GPU by default */ + demo->gpu_number = -1; + demo->width = 500; + demo->height = 500; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--use_staging") == 0) { + demo->use_staging_buffer = true; + continue; + } + if ((strcmp(argv[i], "--present_mode") == 0) && (i < argc - 1)) { + demo->presentMode = atoi(argv[i + 1]); + i++; + continue; + } + if (strcmp(argv[i], "--break") == 0) { + demo->use_break = true; + continue; + } + if (strcmp(argv[i], "--validate") == 0) { + demo->validate = true; + continue; + } + if (strcmp(argv[i], "--validate-checks-disabled") == 0) { + demo->validate = true; + demo->validate_checks_disabled = true; + continue; + } + if (strcmp(argv[i], "--xlib") == 0) { + fprintf(stderr, "--xlib is deprecated and no longer does anything"); + continue; + } + if (strcmp(argv[i], "--c") == 0 && demo->frameCount == INT32_MAX && i < argc - 1 && + sscanf(argv[i + 1], "%d", &demo->frameCount) == 1 && demo->frameCount >= 0) { + i++; + continue; + } + if (strcmp(argv[i], "--width") == 0) { + if (i < argc - 1 && sscanf(argv[i + 1], "%d", &demo->width) == 1) { + if (demo->width > 0) { + i++; + continue; + } else { + ERR_EXIT("The --width parameter must be greater than 0", "User Error"); + } + } + ERR_EXIT("The --width parameter must be followed by a number", "User Error"); + } + if (strcmp(argv[i], "--height") == 0) { + if (i < argc - 1 && sscanf(argv[i + 1], "%d", &demo->height) == 1) { + if (demo->height > 0) { + i++; + continue; + } else { + ERR_EXIT("The --height parameter must be greater than 0", "User Error"); + } + } + ERR_EXIT("The --height parameter must be followed by a number", "User Error"); + } + if (strcmp(argv[i], "--suppress_popups") == 0) { + demo->suppress_popups = true; + continue; + } + if (strcmp(argv[i], "--display_timing") == 0) { + demo->VK_GOOGLE_display_timing_enabled = true; + continue; + } + if (strcmp(argv[i], "--incremental_present") == 0) { + demo->VK_KHR_incremental_present_enabled = true; + continue; + } + if ((strcmp(argv[i], "--gpu_number") == 0) && (i < argc - 1)) { + demo->gpu_number = atoi(argv[i + 1]); + if (demo->gpu_number < 0) demo->invalid_gpu_selection = true; + i++; + continue; + } + if (strcmp(argv[i], "--force_errors") == 0) { + demo->force_errors = true; + continue; + } + +#if defined(ANDROID) + ERR_EXIT("Usage: vkcube [--validate]\n", "Usage"); +#else + char *message = + "Usage:\n %s\t[--use_staging] [--validate] [--validate-checks-disabled]\n" + "\t[--break] [--c ] [--suppress_popups]\n" + "\t[--incremental_present] [--display_timing]\n" + "\t[--gpu_number ]\n" + "\t[--present_mode ]\n" + "\t[--width ] [--height ]\n" + "\t[--force_errors]\n" + "\t\n" + "\t\tVK_PRESENT_MODE_IMMEDIATE_KHR = %d\n" + "\t\tVK_PRESENT_MODE_MAILBOX_KHR = %d\n" + "\t\tVK_PRESENT_MODE_FIFO_KHR = %d\n" + "\t\tVK_PRESENT_MODE_FIFO_RELAXED_KHR = %d\n"; + int length = snprintf(NULL, 0, message, APP_SHORT_NAME, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR); + char *usage = (char *)malloc(length + 1); + if (!usage) { + exit(1); + } + snprintf(usage, length + 1, message, APP_SHORT_NAME, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR); +#if defined(_WIN32) + if (!demo->suppress_popups) MessageBox(NULL, usage, "Usage Error", MB_OK); +#else + fprintf(stderr, "%s", usage); + fflush(stderr); +#endif + free(usage); + exit(1); +#endif + } + + demo_init_connection(demo); + + demo_init_vk(demo); + + demo->spin_angle = 4.0f; + demo->spin_increment = 0.2f; + demo->pause = false; + + mat4x4_perspective(demo->projection_matrix, (float)degreesToRadians(45.0f), 1.0f, 0.1f, 100.0f); + mat4x4_look_at(demo->view_matrix, eye, origin, up); + mat4x4_identity(demo->model_matrix); + + demo->projection_matrix[1][1] *= -1; // Flip projection matrix from GL to Vulkan orientation. +} + +#if defined(VK_USE_PLATFORM_WIN32_KHR) +// Include header required for parsing the command line options. +#include + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow) { + MSG msg; // message + bool done; // flag saying when app is complete + int argc; + char **argv; + + // Ensure wParam is initialized. + msg.wParam = 0; + + // Use the CommandLine functions to get the command line arguments. + // Unfortunately, Microsoft outputs + // this information as wide characters for Unicode, and we simply want the + // Ascii version to be compatible + // with the non-Windows side. So, we have to convert the information to + // Ascii character strings. + LPWSTR *commandLineArgs = CommandLineToArgvW(GetCommandLineW(), &argc); + if (NULL == commandLineArgs) { + argc = 0; + } + + if (argc > 0) { + argv = (char **)malloc(sizeof(char *) * argc); + if (argv == NULL) { + argc = 0; + } else { + for (int iii = 0; iii < argc; iii++) { + size_t wideCharLen = wcslen(commandLineArgs[iii]); + size_t numConverted = 0; + + argv[iii] = (char *)malloc(sizeof(char) * (wideCharLen + 1)); + if (argv[iii] != NULL) { + wcstombs_s(&numConverted, argv[iii], wideCharLen + 1, commandLineArgs[iii], wideCharLen + 1); + } + } + } + } else { + argv = NULL; + } + + demo_init(&demo, argc, argv); + + // Free up the items we had to allocate for the command line arguments. + if (argc > 0 && argv != NULL) { + for (int iii = 0; iii < argc; iii++) { + if (argv[iii] != NULL) { + free(argv[iii]); + } + } + free(argv); + } + + demo.connection = hInstance; + strncpy(demo.name, "Vulkan Cube", APP_NAME_STR_LEN); + demo_create_window(&demo); + demo_init_vk_swapchain(&demo); + + demo_prepare(&demo); + + done = false; // initialize loop condition variable + + // main message loop + while (!done) { + if (demo.pause) { + const BOOL succ = WaitMessage(); + + if (!succ) { + struct demo *tmp = &demo; + struct demo *demo = tmp; + ERR_EXIT("WaitMessage() failed on paused demo", "event loop error"); + } + } + PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + if (msg.message == WM_QUIT) // check for a quit message + { + done = true; // if found, quit app + } else { + /* Translate and dispatch to event queue*/ + TranslateMessage(&msg); + DispatchMessage(&msg); + } + RedrawWindow(demo.window, NULL, NULL, RDW_INTERNALPAINT); + } + + demo_cleanup(&demo); + + return (int)msg.wParam; +} + +#elif defined(VK_USE_PLATFORM_METAL_EXT) +static void demo_main(struct demo *demo, void *caMetalLayer, int argc, const char *argv[]) { + demo_init(demo, argc, (char **)argv); + demo->caMetalLayer = caMetalLayer; + demo_init_vk_swapchain(demo); + demo_prepare(demo); + demo->spin_angle = 0.4f; +} + +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) +#include +#include +#include "android_util.h" + +static bool initialized = false; +static bool active = false; +struct demo demo; + +static int32_t processInput(struct android_app *app, AInputEvent *event) { return 0; } + +static void processCommand(struct android_app *app, int32_t cmd) { + switch (cmd) { + case APP_CMD_INIT_WINDOW: { + if (app->window) { + // We're getting a new window. If the app is starting up, we + // need to initialize. If the app has already been + // initialized, that means that we lost our previous window, + // which means that we have a lot of work to do. At a minimum, + // we need to destroy the swapchain and surface associated with + // the old window, and create a new surface and swapchain. + // However, since there are a lot of other objects/state that + // is tied to the swapchain, it's easiest to simply cleanup and + // start over (i.e. use a brute-force approach of re-starting + // the app) + if (demo.prepared) { + demo_cleanup(&demo); + } + + // Parse Intents into argc, argv + // Use the following key to send arguments, i.e. + // --es args "--validate" + const char key[] = "args"; + char *appTag = (char *)APP_SHORT_NAME; + int argc = 0; + char **argv = get_args(app, key, appTag, &argc); + + __android_log_print(ANDROID_LOG_INFO, appTag, "argc = %i", argc); + for (int i = 0; i < argc; i++) __android_log_print(ANDROID_LOG_INFO, appTag, "argv[%i] = %s", i, argv[i]); + + demo_init(&demo, argc, argv); + + // Free the argv malloc'd by get_args + for (int i = 0; i < argc; i++) free(argv[i]); + + demo.window = (void *)app->window; + demo_init_vk_swapchain(&demo); + demo_prepare(&demo); + initialized = true; + } + break; + } + case APP_CMD_GAINED_FOCUS: { + active = true; + break; + } + case APP_CMD_LOST_FOCUS: { + active = false; + break; + } + } +} + +void android_main(struct android_app *app) { + demo.prepared = false; + + app->onAppCmd = processCommand; + app->onInputEvent = processInput; + + while (1) { + int events; + struct android_poll_source *source; + while (ALooper_pollOnce(active ? 0 : -1, NULL, &events, (void **)&source) >= 0) { + if (source) { + source->process(app, source); + } + + if (app->destroyRequested != 0) { + demo_cleanup(&demo); + return; + } + } + if (initialized && active) { + demo_run(&demo); + } + } +} +#else +int main(int argc, char **argv) { + struct demo demo; + + demo_init(&demo, argc, argv); +#if defined(VK_USE_PLATFORM_XCB_KHR) + demo_create_xcb_window(&demo); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + demo_create_xlib_window(&demo); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo_create_window(&demo); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo_create_directfb_window(&demo); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + demo_create_window(&demo); +#endif + + demo_init_vk_swapchain(&demo); + + demo_prepare(&demo); + +#if defined(VK_USE_PLATFORM_XCB_KHR) + demo_run_xcb(&demo); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + demo_run_xlib(&demo); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + demo_run(&demo); +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + demo_run_directfb(&demo); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + demo_run_display(&demo); +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + demo_run(&demo); +#endif + + demo_cleanup(&demo); + + return validation_error; +} +#endif diff --git a/cube/cube.c b/cube/cube.c index 78d0bb554..20c791ad0 100644 --- a/cube/cube.c +++ b/cube/cube.c @@ -67,6 +67,9 @@ #define APP_SHORT_NAME "vkcube" #define APP_LONG_NAME "Vulkan Cube" +#include "vk_khr_descriptor_heap.h" + + // Allow a maximum of two outstanding presentation operations. #define FRAME_LAG 2 @@ -300,8 +303,17 @@ typedef struct { void *uniform_memory_ptr; VkFramebuffer framebuffer; VkDescriptorSet descriptor_set; + VkBuffer descriptor_heap[3]; + VkDeviceMemory descHeapMemory[3]; } SwapchainImageResources; +enum { + bufferHeap, + samplerHeap, + imageHeap, + heapCount, +}; + struct demo { #if defined(VK_USE_PLATFORM_WIN32_KHR) #define APP_NAME_STR_LEN 80 @@ -381,6 +393,9 @@ struct demo { VkQueueFamilyProperties *queue_props; VkPhysicalDeviceMemoryProperties memory_properties; + VkPhysicalDeviceDescriptorHeapFeaturesKHR descriptorHeapFeature; + VkPhysicalDeviceDescriptorHeapPropertiesKHR descriptorHeapProperties; + uint32_t enabled_extension_count; uint32_t enabled_layer_count; char *extension_names[64]; @@ -812,8 +827,88 @@ static void demo_draw_build_cmd(struct demo *demo, VkCommandBuffer cmd_buf) { demo_push_cb_label(demo, cmd_buf, renderpass_color, "InsideRenderPass"); vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline); - vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline_layout, 0, 1, - &demo->swapchain_image_resources[demo->current_buffer].descriptor_set, 0, NULL); + // vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, demo->pipeline_layout, 0, 1, + // &demo->swapchain_image_resources[demo->current_buffer].descriptor_set, 0, NULL); + + PFN_vkCmdBindSamplerHeapKHR vkCmdBindSamplerHeapKHR = + (PFN_vkCmdBindSamplerHeapKHR) vkGetDeviceProcAddr(demo->device, "vkCmdBindSamplerHeapKHR"); + + PFN_vkCmdBindImageHeapKHR vkCmdBindImageHeapKHR = + (PFN_vkCmdBindImageHeapKHR) vkGetDeviceProcAddr(demo->device, "vkCmdBindImageHeapKHR"); + + PFN_vkCmdBindBufferHeapKHR vkCmdBindBufferHeapKHR = + (PFN_vkCmdBindBufferHeapKHR) vkGetDeviceProcAddr(demo->device, "vkCmdBindBufferHeapKHR"); + + for (unsigned int ii = 0; ii < heapCount; ii++) + { + if (ii == bufferHeap) + { + VkBufferDeviceAddressInfo addressInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = NULL, + .buffer = demo->swapchain_image_resources[demo->current_buffer].descriptor_heap[ii], + }; + + VkDeviceAddressRangeKHR heapRange = { + .address = vkGetBufferDeviceAddressKHR(demo->device, &addressInfo), + .size = demo->descriptorHeapProperties.bufferHeapAlignment, + }; + + VkBindHeapInfoKHR pBindInfo = { + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_KHR, + .pNext = NULL, + .heapRange = heapRange, + .reservedRangeOffset = ii * demo->descriptorHeapProperties.bufferHeapDescriptorAlignment, + }; + + vkCmdBindBufferHeapKHR(cmd_buf, &pBindInfo); + } + else if (ii == imageHeap) + { + VkBufferDeviceAddressInfo addressInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = NULL, + .buffer = demo->swapchain_image_resources[demo->current_buffer].descriptor_heap[ii], + }; + + VkDeviceAddressRangeKHR heapRange = { + .address = vkGetBufferDeviceAddressKHR(demo->device, &addressInfo), + .size = demo->descriptorHeapProperties.samplerHeapAlignment, + }; + + VkBindHeapInfoKHR pBindInfo = { + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_KHR, + .pNext = NULL, + .heapRange = heapRange, + .reservedRangeOffset = ii * demo->descriptorHeapProperties.imageHeapDescriptorAlignment, + }; + + vkCmdBindImageHeapKHR(cmd_buf, &pBindInfo); + } + else if (ii == samplerHeap) + { + VkBufferDeviceAddressInfo addressInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = NULL, + .buffer = demo->swapchain_image_resources[demo->current_buffer].descriptor_heap[ii], + }; + + VkDeviceAddressRangeKHR heapRange = { + .address = vkGetBufferDeviceAddressKHR(demo->device, &addressInfo), + .size = demo->descriptorHeapProperties.samplerHeapAlignment, + }; + + VkBindHeapInfoKHR pBindInfo = { + .sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_KHR, + .pNext = NULL, + .heapRange = heapRange, + .reservedRangeOffset = ii * demo->descriptorHeapProperties.samplerHeapDescriptorAlignment, + }; + + vkCmdBindSamplerHeapKHR(cmd_buf, &pBindInfo); + } + } + VkViewport viewport; memset(&viewport, 0, sizeof(viewport)); float viewport_dimension; @@ -1882,46 +1977,54 @@ void demo_prepare_cube_data_buffers(struct demo *demo) { } } -static void demo_prepare_descriptor_layout(struct demo *demo) { - const VkDescriptorSetLayoutBinding layout_bindings[2] = { - [0] = - { - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, - .pImmutableSamplers = NULL, - }, - [1] = - { - .binding = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = DEMO_TEXTURE_COUNT, - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - .pImmutableSamplers = NULL, - }, - }; - const VkDescriptorSetLayoutCreateInfo descriptor_layout = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = NULL, - .bindingCount = 2, - .pBindings = layout_bindings, - }; - VkResult U_ASSERT_ONLY err; - - err = vkCreateDescriptorSetLayout(demo->device, &descriptor_layout, NULL, &demo->desc_layout); - assert(!err); - - const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = { - .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .pNext = NULL, - .setLayoutCount = 1, - .pSetLayouts = &demo->desc_layout, - }; - - err = vkCreatePipelineLayout(demo->device, &pPipelineLayoutCreateInfo, NULL, &demo->pipeline_layout); - assert(!err); -} +// static void demo_prepare_descriptor_layout(struct demo *demo) { +// const VkDescriptorSetLayoutBinding layout_bindings[3] = { +// [0] = +// { +// .binding = 0, +// .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, +// .descriptorCount = 1, +// .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, +// .pImmutableSamplers = NULL, +// }, +// [1] = +// { +// .binding = 1, +// .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, +// .descriptorCount = 1, +// .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, +// .pImmutableSamplers = NULL, +// }, +// [2] = +// { +// .binding = 2, +// .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, +// .descriptorCount = 1, +// .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, +// .pImmutableSamplers = NULL, +// }, +// }; +// const VkDescriptorSetLayoutCreateInfo descriptor_layout = { +// .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, +// .pNext = NULL, +// .bindingCount = 3, +// .pBindings = layout_bindings, +// }; +// VkResult U_ASSERT_ONLY err; + +// err = vkCreateDescriptorSetLayout(demo->device, &descriptor_layout, NULL, &demo->desc_layout); +// assert(!err); + +// const VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = { +// .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, +// .pNext = NULL, +// .setLayoutCount = 1, +// .pSetLayouts = &demo->desc_layout, +// }; + +// err = vkCreatePipelineLayout(demo->device, &pPipelineLayoutCreateInfo, NULL, &demo->pipeline_layout); +// assert(!err); +// } static void demo_prepare_render_pass(struct demo *demo) { // The initial layout for the color and depth attachments will be LAYOUT_UNDEFINED @@ -2041,16 +2144,16 @@ static VkShaderModule demo_prepare_shader_module(const char *name, struct demo * static void demo_prepare_vs(struct demo *demo) { const uint32_t vs_code[] = { -#include "cube.vert.inc" +#include "cube-nc.vert.inc" }; - demo->vert_shader_module = demo_prepare_shader_module("cube.vert", demo, vs_code, sizeof(vs_code)); + demo->vert_shader_module = demo_prepare_shader_module("cube-nc.vert", demo, vs_code, sizeof(vs_code)); } static void demo_prepare_fs(struct demo *demo) { const uint32_t fs_code[] = { -#include "cube.frag.inc" +#include "cube-nc.frag.inc" }; - demo->frag_shader_module = demo_prepare_shader_module("cube.frag", demo, fs_code, sizeof(fs_code)); + demo->frag_shader_module = demo_prepare_shader_module("cube-nc.frag", demo, fs_code, sizeof(fs_code)); } static void demo_prepare_pipeline(struct demo *demo) { @@ -2076,7 +2179,7 @@ static void demo_prepare_pipeline(struct demo *demo) { memset(&pipeline, 0, sizeof(pipeline)); pipeline.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipeline.layout = demo->pipeline_layout; + // pipeline.layout = demo->pipeline_layout; memset(&vi, 0, sizeof(vi)); vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; @@ -2131,6 +2234,12 @@ static void demo_prepare_pipeline(struct demo *demo) { demo_prepare_vs(demo); demo_prepare_fs(demo); + memset(&pipelineCache, 0, sizeof(pipelineCache)); + pipelineCache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + + err = vkCreatePipelineCache(demo->device, &pipelineCache, NULL, &demo->pipelineCache); + assert(!err); + // Two stages: vs and fs VkPipelineShaderStageCreateInfo shaderStages[2]; memset(&shaderStages, 0, 2 * sizeof(VkPipelineShaderStageCreateInfo)); @@ -2145,11 +2254,18 @@ static void demo_prepare_pipeline(struct demo *demo) { shaderStages[1].module = demo->frag_shader_module; shaderStages[1].pName = "main"; - memset(&pipelineCache, 0, sizeof(pipelineCache)); - pipelineCache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + VkDescriptorHeapStridesKHR strideInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_HEAP_STRIDES_KHR, + .bufferHeapStride = 16, + .imageHeapStride = 32, + .samplerHeapStride = 16, + .pNext = NULL, + }; - err = vkCreatePipelineCache(demo->device, &pipelineCache, NULL, &demo->pipelineCache); - assert(!err); + VkPipelineCreateFlags2CreateInfoKHR pipe2CreateInfo = {}; + pipe2CreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO_KHR; + pipe2CreateInfo.pNext = &strideInfo; + pipe2CreateInfo.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_KHR; pipeline.pVertexInputState = &vi; pipeline.pInputAssemblyState = &ia; @@ -2162,6 +2278,8 @@ static void demo_prepare_pipeline(struct demo *demo) { pipeline.pStages = shaderStages; pipeline.renderPass = demo->render_pass; pipeline.pDynamicState = &dynamicState; + pipeline.layout = NULL; + pipeline.pNext = &pipe2CreateInfo; err = vkCreateGraphicsPipelines(demo->device, demo->pipelineCache, 1, &pipeline, NULL, &demo->pipeline); assert(!err); @@ -2170,75 +2288,268 @@ static void demo_prepare_pipeline(struct demo *demo) { vkDestroyShaderModule(demo->device, demo->vert_shader_module, NULL); } -static void demo_prepare_descriptor_pool(struct demo *demo) { - const VkDescriptorPoolSize type_counts[2] = { - [0] = +// static void demo_prepare_descriptor_pool(struct demo *demo) { +// const VkDescriptorPoolSize type_counts[3] = { +// [0] = +// { +// .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, +// .descriptorCount = demo->swapchainImageCount, +// }, +// [1] = +// { +// .type = VK_DESCRIPTOR_TYPE_SAMPLER, +// .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, +// }, +// [2] = +// { +// .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, +// .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, +// }, +// }; +// const VkDescriptorPoolCreateInfo descriptor_pool = { +// .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, +// .pNext = NULL, +// .maxSets = demo->swapchainImageCount, +// .poolSizeCount = 3, +// .pPoolSizes = type_counts, +// }; +// VkResult U_ASSERT_ONLY err; + +// err = vkCreateDescriptorPool(demo->device, &descriptor_pool, NULL, &demo->desc_pool); +// assert(!err); +// } + +// static void demo_prepare_descriptor_set(struct demo *demo) { +// VkDescriptorImageInfo tex_descs[DEMO_TEXTURE_COUNT]; +// VkWriteDescriptorSet writes[3]; +// VkResult U_ASSERT_ONLY err; + +// VkDescriptorSetAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, +// .pNext = NULL, +// .descriptorPool = demo->desc_pool, +// .descriptorSetCount = 1, +// .pSetLayouts = &demo->desc_layout}; + +// VkDescriptorBufferInfo buffer_info; +// buffer_info.offset = 0; +// buffer_info.range = sizeof(struct vktexcube_vs_uniform); + +// memset(&tex_descs, 0, sizeof(tex_descs)); +// for (unsigned int i = 0; i < DEMO_TEXTURE_COUNT; i++) { +// tex_descs[i].sampler = demo->textures[i].sampler; +// tex_descs[i].imageView = demo->textures[i].view; +// tex_descs[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +// } + +// memset(&writes, 0, sizeof(writes)); + +// writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +// writes[0].descriptorCount = 1; +// writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +// writes[0].pBufferInfo = &buffer_info; + +// writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +// writes[1].dstBinding = 1; +// writes[1].descriptorCount = 1; +// writes[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; +// writes[1].dstArrayElement = 0; +// writes[1].pImageInfo = tex_descs; + +// writes[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +// writes[2].dstBinding = 2; +// writes[2].descriptorCount = 1; +// writes[2].dstArrayElement = 0; +// writes[2].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; +// writes[2].pImageInfo = tex_descs; + +// for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { +// err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->swapchain_image_resources[i].descriptor_set); +// assert(!err); +// buffer_info.buffer = demo->swapchain_image_resources[i].uniform_buffer; +// writes[0].dstSet = demo->swapchain_image_resources[i].descriptor_set; +// writes[1].dstSet = demo->swapchain_image_resources[i].descriptor_set; +// writes[2].dstSet = demo->swapchain_image_resources[i].descriptor_set; +// vkUpdateDescriptorSets(demo->device, 3, writes, 0, NULL); +// } +// } + +static void demo_prepare_descriptor_heap(struct demo *demo) +{ + VkResult err = VK_SUCCESS; + VkBufferCreateInfo heapInfo; + VkMemoryRequirements memReqs; + VkMemoryAllocateInfo memAllocInfo; + + PFN_vkWriteBufferDescriptorsKHR vkWriteBufferDescriptorsKHR = + (PFN_vkWriteBufferDescriptorsKHR) vkGetDeviceProcAddr(demo->device, "vkWriteBufferDescriptorsKHR"); + + PFN_vkWriteSamplerDescriptorsKHR vkWriteSamplerDescriptorsKHR = + (PFN_vkWriteSamplerDescriptorsKHR) vkGetDeviceProcAddr(demo->device, "vkWriteSamplerDescriptorsKHR"); + + PFN_vkWriteImageDescriptorsKHR vkWriteImageDescriptorsKHR = + (PFN_vkWriteImageDescriptorsKHR) vkGetDeviceProcAddr(demo->device, "vkWriteImageDescriptorsKHR"); + + // create memory + for (unsigned int i = 0; i < demo->swapchainImageCount; i++) + { + for (unsigned int ii = 0; ii < heapCount; ii++) + { + memset(&heapInfo, 0, sizeof(heapInfo)); + memset(&memReqs, 0, sizeof(memReqs)); + memset(&memAllocInfo, 0, sizeof(memAllocInfo)); + + heapInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + heapInfo.flags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + heapInfo.size = 0; + if (ii == bufferHeap) { - .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = demo->swapchainImageCount, - }, - [1] = + heapInfo.size = demo->descriptorHeapProperties.bufferHeapAlignment; + } + else if (ii == imageHeap) { - .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = demo->swapchainImageCount * DEMO_TEXTURE_COUNT, - }, - }; - const VkDescriptorPoolCreateInfo descriptor_pool = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .pNext = NULL, - .maxSets = demo->swapchainImageCount, - .poolSizeCount = 2, - .pPoolSizes = type_counts, - }; - VkResult U_ASSERT_ONLY err; + heapInfo.size = demo->descriptorHeapProperties.imageHeapAlignment; + } + else if (ii == samplerHeap) + { + heapInfo.size = demo->descriptorHeapProperties.samplerHeapAlignment; + } - err = vkCreateDescriptorPool(demo->device, &descriptor_pool, NULL, &demo->desc_pool); - assert(!err); -} + err = vkCreateBuffer(demo->device, &heapInfo, NULL, &demo->swapchain_image_resources[i].descriptor_heap[ii]); + vkGetBufferMemoryRequirements(demo->device, demo->swapchain_image_resources[i].descriptor_heap[ii], &memReqs); + memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memAllocInfo.pNext = NULL; + memAllocInfo.allocationSize = memReqs.size; -static void demo_prepare_descriptor_set(struct demo *demo) { - VkDescriptorImageInfo tex_descs[DEMO_TEXTURE_COUNT]; - VkWriteDescriptorSet writes[2]; - VkResult U_ASSERT_ONLY err; + memory_type_from_properties(demo, memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + &memAllocInfo.memoryTypeIndex); - VkDescriptorSetAllocateInfo alloc_info = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .pNext = NULL, - .descriptorPool = demo->desc_pool, - .descriptorSetCount = 1, - .pSetLayouts = &demo->desc_layout}; + err = vkAllocateMemory(demo->device, &memAllocInfo, NULL, &demo->swapchain_image_resources[i].descHeapMemory[ii]); + err = vkBindBufferMemory(demo->device, + demo->swapchain_image_resources[i].descriptor_heap[ii], + demo->swapchain_image_resources[i].descHeapMemory[ii], + 0); - VkDescriptorBufferInfo buffer_info; - buffer_info.offset = 0; - buffer_info.range = sizeof(struct vktexcube_vs_uniform); + void* pData = NULL; + vkMapMemory(demo->device, + demo->swapchain_image_resources[i].descHeapMemory[ii], + 0, + VK_WHOLE_SIZE, + 0, + &pData); - memset(&tex_descs, 0, sizeof(tex_descs)); - for (unsigned int i = 0; i < DEMO_TEXTURE_COUNT; i++) { - tex_descs[i].sampler = demo->textures[i].sampler; - tex_descs[i].imageView = demo->textures[i].view; - tex_descs[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - } + // write srd - memset(&writes, 0, sizeof(writes)); + if (ii == bufferHeap) + { + //heap buffer host ponter + const VkHostAddressStridedRangeKHR bufferRagge = { + .size = demo->descriptorHeapProperties.bufferDescriptorSize, + .stride = demo->descriptorHeapProperties.bufferHeapDescriptorAlignment, + .pData = ((char*)pData + ii * demo->descriptorHeapProperties.bufferHeapDescriptorAlignment), + }; + + VkBufferDeviceAddressInfo addressInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = NULL, + .buffer = demo->swapchain_image_resources[i].uniform_buffer + }; + + VkBufferDescriptorInfoKHR bufferDescInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_DESCRIPTOR_INFO_KHR, + .pNext = NULL, + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .addressRange.address = 0, + .addressRange.size = sizeof(struct vktexcube_vs_uniform), + }; + + bufferDescInfo.addressRange.address = vkGetBufferDeviceAddressKHR(demo->device, &addressInfo); + + err = vkWriteBufferDescriptorsKHR(demo->device, 1, &bufferDescInfo ,&bufferRagge); + } + else if (ii == imageHeap) + { + VkImageViewCreateInfo view = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .image = demo->textures[0].image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .components = + { + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, + .flags = 0, + }; + + VkImageDescriptorImageInfoKHR Image = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_IMAGE_INFO_KHR, + .pNext = NULL, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .pView = &view, + }; + + VkImageDescriptorInfoKHR imageDescriptorInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_KHR, + .pNext = NULL, + .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + .data = { + .pImage = &Image, + } + }; - writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writes[0].descriptorCount = 1; - writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - writes[0].pBufferInfo = &buffer_info; + const VkHostAddressStridedRangeKHR bufferRagge = { + .size = demo->descriptorHeapProperties.imageDescriptorSize, + .stride = demo->descriptorHeapProperties.imageHeapDescriptorAlignment, + // .pData = (pData + ii * demo->descriptorHeapProperties.imageHeapDescriptorAlignment), + // Since offset equal to 0; + .pData = pData, - writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writes[1].dstBinding = 1; - writes[1].descriptorCount = DEMO_TEXTURE_COUNT; - writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - writes[1].pImageInfo = tex_descs; + }; - for (unsigned int i = 0; i < demo->swapchainImageCount; i++) { - err = vkAllocateDescriptorSets(demo->device, &alloc_info, &demo->swapchain_image_resources[i].descriptor_set); - assert(!err); - buffer_info.buffer = demo->swapchain_image_resources[i].uniform_buffer; - writes[0].dstSet = demo->swapchain_image_resources[i].descriptor_set; - writes[1].dstSet = demo->swapchain_image_resources[i].descriptor_set; - vkUpdateDescriptorSets(demo->device, 2, writes, 0, NULL); + err = vkWriteImageDescriptorsKHR(demo->device, 1, &imageDescriptorInfo, &bufferRagge); + } + else if (ii == samplerHeap) + { + VkSamplerCreateInfo sampler = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = NULL, + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 1, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, + .unnormalizedCoordinates = VK_FALSE, + }; + + //heap buffer host ponter + const VkHostAddressStridedRangeKHR bufferRagge = { + .size = demo->descriptorHeapProperties.samplerDescriptorSize, + .stride = demo->descriptorHeapProperties.samplerHeapDescriptorAlignment, + // .pData = (pData + ii * demo->descriptorHeapProperties.samplerHeapDescriptorAlignment), + // Since offset equal to 0; + .pData = pData, + + }; + + vkWriteSamplerDescriptorsKHR(demo->device, 1, &sampler, &bufferRagge); + } + + vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].descHeapMemory[ii]); + } } + assert(!err); } static void demo_prepare_framebuffers(struct demo *demo) { @@ -2311,7 +2622,7 @@ static void demo_prepare(struct demo *demo) { demo_prepare_textures(demo); demo_prepare_cube_data_buffers(demo); - demo_prepare_descriptor_layout(demo); + // demo_prepare_descriptor_layout(demo); demo_prepare_render_pass(demo); demo_prepare_pipeline(demo); @@ -2346,8 +2657,9 @@ static void demo_prepare(struct demo *demo) { } } - demo_prepare_descriptor_pool(demo); - demo_prepare_descriptor_set(demo); + // demo_prepare_descriptor_pool(demo); + // demo_prepare_descriptor_set(demo); + demo_prepare_descriptor_heap(demo); demo_prepare_framebuffers(demo); @@ -3711,6 +4023,17 @@ static void demo_init_vk(struct demo *demo) { if (!strcmp("VK_KHR_portability_subset", device_extensions[i].extensionName)) { demo->extension_names[demo->enabled_extension_count++] = "VK_KHR_portability_subset"; } + // VK_KHR_DESCRIPTOR_HEAP + if (!strcmp(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NAME, device_extensions[i].extensionName)) + { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NAME; + } + // VK_KHR_buffer_device_address + if (!strcmp(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, device_extensions[i].extensionName)) + { + demo->extension_names[demo->enabled_extension_count++] = VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME; + } + assert(demo->enabled_extension_count < 64); } @@ -3789,6 +4112,26 @@ static void demo_init_vk(struct demo *demo) { // features based on this query VkPhysicalDeviceFeatures physDevFeatures; vkGetPhysicalDeviceFeatures(demo->gpu, &physDevFeatures); + + VkPhysicalDeviceFeatures2 features2 = {}; + features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + + // VkPhysicalDeviceDescriptorHeapFeaturesKHR descriptorHeapFeature = {}; + demo->descriptorHeapFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_KHR; + demo->descriptorHeapFeature.pNext = NULL; + + features2.pNext = (void*)(&demo->descriptorHeapFeature); + vkGetPhysicalDeviceFeatures2(demo->gpu, &features2); + + VkPhysicalDeviceProperties2 properties2 = {}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + + // VkPhysicalDeviceDescriptorHeapPropertiesKHR descriptorHeapProperties = {}; + demo->descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_KHR; + demo->descriptorHeapProperties.pNext = NULL; + properties2.pNext = (void *)(&demo->descriptorHeapProperties); + vkGetPhysicalDeviceProperties2(demo->gpu, &properties2); + } static void demo_create_device(struct demo *demo) { diff --git a/cube/cube.vert.inc b/cube/cube.vert.inc index 5d029c645..2a749fc2a 100644 --- a/cube/cube.vert.inc +++ b/cube/cube.vert.inc @@ -1,5 +1,5 @@ - // 7.9.2888 - 0x07230203,0x00010000,0x00080007,0x0000002f,0x00000000,0x00020011,0x00000001,0x0006000b, + // 1115.0.0 + 0x07230203,0x00010000,0x0008000b,0x0000002f,0x00000000,0x00020011,0x00000001,0x0006000b, 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, 0x0009000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x00000009,0x00000015,0x0000001e, 0x0000002b,0x00030003,0x00000002,0x00000190,0x00090004,0x415f4c47,0x735f4252,0x72617065, @@ -15,14 +15,14 @@ 0x00000002,0x435f6c67,0x4470696c,0x61747369,0x0065636e,0x00030005,0x0000001e,0x00000000, 0x00050005,0x0000002b,0x67617266,0x736f705f,0x00000000,0x00040047,0x00000009,0x0000001e, 0x00000000,0x00040047,0x0000000d,0x00000006,0x00000010,0x00040047,0x0000000e,0x00000006, - 0x00000010,0x00040048,0x0000000f,0x00000000,0x00000005,0x00050048,0x0000000f,0x00000000, - 0x00000023,0x00000000,0x00050048,0x0000000f,0x00000000,0x00000007,0x00000010,0x00050048, - 0x0000000f,0x00000001,0x00000023,0x00000040,0x00050048,0x0000000f,0x00000002,0x00000023, - 0x00000280,0x00030047,0x0000000f,0x00000002,0x00040047,0x00000011,0x00000022,0x00000000, - 0x00040047,0x00000011,0x00000021,0x00000000,0x00040047,0x00000015,0x0000000b,0x0000002a, - 0x00050048,0x0000001c,0x00000000,0x0000000b,0x00000000,0x00050048,0x0000001c,0x00000001, - 0x0000000b,0x00000001,0x00050048,0x0000001c,0x00000002,0x0000000b,0x00000003,0x00030047, - 0x0000001c,0x00000002,0x00040047,0x0000002b,0x0000001e,0x00000001,0x00020013,0x00000002, + 0x00000010,0x00030047,0x0000000f,0x00000002,0x00040048,0x0000000f,0x00000000,0x00000005, + 0x00050048,0x0000000f,0x00000000,0x00000007,0x00000010,0x00050048,0x0000000f,0x00000000, + 0x00000023,0x00000000,0x00050048,0x0000000f,0x00000001,0x00000023,0x00000040,0x00050048, + 0x0000000f,0x00000002,0x00000023,0x00000280,0x00040047,0x00000011,0x00000021,0x00000000, + 0x00040047,0x00000011,0x00000022,0x00000000,0x00040047,0x00000015,0x0000000b,0x0000002a, + 0x00030047,0x0000001c,0x00000002,0x00050048,0x0000001c,0x00000000,0x0000000b,0x00000000, + 0x00050048,0x0000001c,0x00000001,0x0000000b,0x00000001,0x00050048,0x0000001c,0x00000002, + 0x0000000b,0x00000003,0x00040047,0x0000002b,0x0000001e,0x00000001,0x00020013,0x00000002, 0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007, 0x00000006,0x00000004,0x00040020,0x00000008,0x00000003,0x00000007,0x0004003b,0x00000008, 0x00000009,0x00000003,0x00040018,0x0000000a,0x00000007,0x00000004,0x00040015,0x0000000b, diff --git a/cube/vk_internal_ext_helper.h b/cube/vk_internal_ext_helper.h new file mode 100644 index 000000000..c21d6aee6 --- /dev/null +++ b/cube/vk_internal_ext_helper.h @@ -0,0 +1,26 @@ +/* +************************************************************************************************************************ +* +* Copyright (C) 2014-2021 Advanced Micro Devices, Inc. All rights reserved. +* +***********************************************************************************************************************/ +/** + ********************************************************************************************************************** + * @file vk_internal_ext_helper.h + * @brief Helper header for unpublished extensions. + ********************************************************************************************************************** + */ + +#ifndef VK_INTERNAL_EXT_HELPER_H_ +#define VK_INTERNAL_EXT_HELPER_H_ + +#define VK_EXTENSION_ENUM_BASE_VALUE 1000000000ull +#define VK_EXTENSION_ENUM_RANGE_SIZE 1000ull + +#define VK_EXTENSION_ENUM(extnr, type, offset) \ + ((type)(VK_EXTENSION_ENUM_BASE_VALUE + (((extnr)-1) * VK_EXTENSION_ENUM_RANGE_SIZE) + (offset))) + +#define VK_EXTENSION_BIT(type, bit) \ + ((type)(1ull << (bit))) + +#endif /* VK_INTERNAL_EXT_HELPER_H_ */ diff --git a/cube/vk_khr_descriptor_heap.h b/cube/vk_khr_descriptor_heap.h new file mode 100755 index 000000000..93862392d --- /dev/null +++ b/cube/vk_khr_descriptor_heap.h @@ -0,0 +1,375 @@ +/* +*********************************************************************************************************************** +* +* Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. +* +**********************************************************************************************************************/ +/** +********************************************************************************************************************** +* @file vk_khr_descriptor_heap.h +* @brief Temporary internal header for KHR descriptor heap extension. This file should be removed once the +* extension is published and the API gets included in the official Vulkan header. +********************************************************************************************************************** +*/ + +//# NOTE: generated via script using scripts/devext.py + +#ifndef VK_KHR_DESCRIPTOR_HEAP_H_ +#define VK_KHR_DESCRIPTOR_HEAP_H_ + +#pragma once +#include "vk_internal_ext_helper.h" + +#include "vk_khr_device_address_commands.h" + +#define VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER 136 + +// extended core or other extension enums: +#define VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_KHR VK_EXTENSION_BIT(VkBufferUsageFlagBits, 28) +#define VK_BUFFER_USAGE_2_DESCRIPTOR_HEAP_BIT_KHR VK_EXTENSION_BIT(VkBufferUsageFlagBits2KHR, 28) +#define VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_KHR VK_EXTENSION_BIT(VkPipelineCreateFlagBits2KHR, 36) +#define VK_PIPELINE_CREATE_2_BUFFER_AND_IMAGE_DESCRIPTOR_HEAP_ADDRESSES_MATCH_BIT_KHR VK_EXTENSION_BIT(VkPipelineCreateFlagBits2KHR, 39) +#define VK_IMAGE_CREATE_DESCRIPTOR_HEAP_CAPTURE_REPLAY_BIT_KHR VK_EXTENSION_BIT(VkImageCreateFlagBits, 16) +#define VK_STRUCTURE_TYPE_BUFFER_DESCRIPTOR_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 0) +#define VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_TEXEL_BUFFER_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 1) +#define VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_IMAGE_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 2) +#define VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 3) +#define VK_STRUCTURE_TYPE_BIND_HEAP_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 4) +#define VK_STRUCTURE_TYPE_DESCRIPTOR_HEAP_STRIDES_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 5) +#define VK_STRUCTURE_TYPE_PUSH_DATA_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 6) +#define VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 7) +#define VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 8) +#define VK_ACCESS_2_SAMPLER_HEAP_READ_BIT_KHR VK_EXTENSION_BIT(VkAccessFlagBits2, 57) +#define VK_ACCESS_2_IMAGE_HEAP_READ_BIT_KHR VK_EXTENSION_BIT(VkAccessFlagBits2, 58) +#define VK_ACCESS_2_BUFFER_HEAP_READ_BIT_KHR VK_EXTENSION_BIT(VkAccessFlagBits2, 59) +#define VK_STRUCTURE_TYPE_OPAQUE_CAPTURE_DATA_CREATE_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 9) +#define VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 10) +#define VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 11) +#define VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_INDEX_CREATE_INFO_EXT VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 12) +#define VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_DESCRIPTOR_HEAP_INFO_KHR VK_EXTENSION_ENUM(VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NUMBER, VkStructureType, 14) + +// copy-paste of new extension types/functions from merged header: +#define VK_KHR_descriptor_heap 1 +#define VK_KHR_DESCRIPTOR_HEAP_SPEC_VERSION 1 +#define VK_KHR_DESCRIPTOR_HEAP_EXTENSION_NAME "VK_KHR_descriptor_heap" + + +typedef enum VkDescriptorMappingSourceKHR { + VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_INDEX_KHR = 0, + VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_KHR = 1, + VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_INDIRECT_INDEX_KHR = 2, + VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_DATA_KHR = 3, + VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_DATA_KHR = 4, + VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_ADDRESS_KHR = 5, + VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_SHADER_RECORD_INDEX_KHR = 6, + VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_DATA_KHR = 7, + VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_ADDRESS_KHR = 8, + VK_DESCRIPTOR_MAPPING_SOURCE_MAX_ENUM_KHR = 0x7FFFFFFF +} VkDescriptorMappingSourceKHR; + +typedef struct VkHostAddressStridedRangeKHR { + void* pData; + size_t stride; + size_t size; +} VkHostAddressStridedRangeKHR; + +typedef struct VkBufferDescriptorInfoKHR { + VkStructureType sType; + const void* pNext; + VkDescriptorType type; + VkDeviceAddressRangeKHR addressRange; +} VkBufferDescriptorInfoKHR; + +typedef struct VkImageDescriptorTexelBufferInfoKHR { + VkStructureType sType; + const void* pNext; + VkFormat format; + VkDeviceAddressRangeKHR addressRange; +} VkImageDescriptorTexelBufferInfoKHR; + +typedef struct VkImageDescriptorImageInfoKHR { + VkStructureType sType; + const void* pNext; + const VkImageViewCreateInfo* pView; + VkImageLayout layout; +} VkImageDescriptorImageInfoKHR; + +typedef union VkImageDescriptorDataKHR { + const VkImageDescriptorImageInfoKHR* pImage; + const VkImageDescriptorTexelBufferInfoKHR* pTexelBuffer; +} VkImageDescriptorDataKHR; + +typedef struct VkImageDescriptorInfoKHR { + VkStructureType sType; + const void* pNext; + VkDescriptorType type; + VkImageDescriptorDataKHR data; +} VkImageDescriptorInfoKHR; + +typedef struct VkBindHeapInfoKHR { + VkStructureType sType; + const void* pNext; + VkDeviceAddressRangeKHR heapRange; + VkDeviceSize reservedRangeOffset; +} VkBindHeapInfoKHR; + +typedef struct VkDescriptorHeapStridesKHR { + VkStructureType sType; + const void* pNext; + VkDeviceSize samplerHeapStride; + VkDeviceSize imageHeapStride; + VkDeviceSize bufferHeapStride; +} VkDescriptorHeapStridesKHR; + +typedef struct VkPushDataInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t offset; + uint32_t size; + const void* pData; +} VkPushDataInfoKHR; + +typedef struct VkDescriptorMappingSourceConstantIndexKHR { + int32_t heapIndex; + const VkSamplerCreateInfo* pEmbeddedSampler; + VkBool32 useCombinedImageSamplerIndex; + int32_t samplerHeapIndex; +} VkDescriptorMappingSourceConstantIndexKHR; + +typedef struct VkDescriptorMappingSourcePushIndexKHR { + int32_t heapIndex; + uint32_t pushOffset; + const VkSamplerCreateInfo* pEmbeddedSampler; + VkBool32 useCombinedImageSamplerIndex; + int32_t samplerHeapIndex; + uint32_t samplerPushOffset; +} VkDescriptorMappingSourcePushIndexKHR; + +typedef struct VkDescriptorMappingSourceIndirectIndexKHR { + int32_t heapIndex; + uint32_t pushOffset; + uint32_t addressOffset; + const VkSamplerCreateInfo* pEmbeddedSampler; + VkBool32 useCombinedImageSamplerIndex; + int32_t samplerHeapIndex; + uint32_t samplerPushOffset; + uint32_t samplerAddressOffset; +} VkDescriptorMappingSourceIndirectIndexKHR; + +typedef struct VkDescriptorMappingSourceHeapDataKHR { + int32_t heapOffset; + uint32_t pushOffset; +} VkDescriptorMappingSourceHeapDataKHR; + +typedef struct VkDescriptorMappingSourceShaderRecordIndexKHR { + int32_t heapIndex; + uint32_t shaderRecordOffset; + const VkSamplerCreateInfo* pEmbeddedSampler; + VkBool32 useCombinedImageSamplerIndex; + int32_t samplerHeapIndex; + uint32_t samplerShaderRecordOffset; +} VkDescriptorMappingSourceShaderRecordIndexKHR; + +typedef union VkDescriptorMappingSourceDataKHR { + VkDescriptorMappingSourceConstantIndexKHR constantIndex; + VkDescriptorMappingSourcePushIndexKHR pushIndex; + VkDescriptorMappingSourceIndirectIndexKHR indirectIndex; + VkDescriptorMappingSourceHeapDataKHR heapData; + uint32_t pushDataOffset; + uint32_t pushAddressOffset; + VkDescriptorMappingSourceShaderRecordIndexKHR shaderRecordIndex; + uint32_t shaderRecordDataOffset; + uint32_t shaderRecordAddressOffset; +} VkDescriptorMappingSourceDataKHR; + +typedef struct VkDescriptorSetAndBindingMappingKHR { + VkStructureType sType; + const void* pNext; + uint32_t descriptorSet; + uint32_t firstBinding; + uint32_t bindingCount; + VkDescriptorMappingSourceKHR source; + VkDescriptorMappingSourceDataKHR sourceData; +} VkDescriptorSetAndBindingMappingKHR; + +typedef struct VkShaderDescriptorSetAndBindingMappingInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t mappingCount; + const VkDescriptorSetAndBindingMappingKHR* pMappings; +} VkShaderDescriptorSetAndBindingMappingInfoKHR; + +typedef struct VkOpaqueCaptureDataCreateInfoKHR { + VkStructureType sType; + const void* pNext; + size_t size; + const void* pData; +} VkOpaqueCaptureDataCreateInfoKHR; + +typedef struct VkPhysicalDeviceDescriptorHeapFeaturesKHR { + VkStructureType sType; + void* pNext; + VkBool32 descriptorHeap; + VkBool32 descriptorHeapCaptureReplay; +} VkPhysicalDeviceDescriptorHeapFeaturesKHR; + +typedef struct VkPhysicalDeviceDescriptorHeapPropertiesKHR { + VkStructureType sType; + void* pNext; + VkDeviceSize samplerHeapAlignment; + VkDeviceSize imageHeapAlignment; + VkDeviceSize bufferHeapAlignment; + VkDeviceSize samplerHeapDescriptorAlignment; + VkDeviceSize imageHeapDescriptorAlignment; + VkDeviceSize bufferHeapDescriptorAlignment; + VkDeviceSize maxSamplerHeapSize; + VkDeviceSize maxImageHeapSize; + VkDeviceSize maxBufferHeapSize; + VkDeviceSize samplerHeapReservedRange; + VkDeviceSize samplerHeapReservedRangeWithEmbedded; + VkDeviceSize imageHeapReservedRange; + VkDeviceSize bufferHeapReservedRange; + VkDeviceSize samplerDescriptorSize; + VkDeviceSize imageDescriptorSize; + VkDeviceSize bufferDescriptorSize; + VkDeviceSize maxPushDataSize; + size_t imageCaptureReplayOpaqueDataSize; + uint32_t maxDescriptorHeapEmbeddedSamplers; + uint32_t samplerYcbcrConversionCount; + VkBool32 separateImageAndBufferHeaps; + VkBool32 sparseDescriptorHeaps; + VkBool32 protectedDescriptorHeaps; +} VkPhysicalDeviceDescriptorHeapPropertiesKHR; + +typedef struct VkCommandBufferInheritanceDescriptorHeapInfoKHR { + VkStructureType sType; + const void* pNext; + VkDeviceAddress samplerHeap; + VkDeviceAddress imageHeap; + VkDeviceAddress bufferHeap; +} VkCommandBufferInheritanceDescriptorHeapInfoKHR; + +typedef struct VkSamplerCustomBorderColorIndexCreateInfoEXT { + VkStructureType sType; + const void* pNext; + uint32_t index; +} VkSamplerCustomBorderColorIndexCreateInfoEXT; + +typedef struct VkIndirectCommandsLayoutPushDataTokenNV { + VkStructureType sType; + const void* pNext; + uint32_t pushDataOffset; + uint32_t pushDataSize; +} VkIndirectCommandsLayoutPushDataTokenNV; + +typedef VkResult (VKAPI_PTR *PFN_vkWriteSamplerDescriptorsKHR)( + VkDevice device, + uint32_t samplerCount, + const VkSamplerCreateInfo* pSamplers, + const VkHostAddressStridedRangeKHR* pDescriptors); + +typedef VkResult (VKAPI_PTR *PFN_vkWriteImageDescriptorsKHR)( + VkDevice device, + uint32_t imageCount, + const VkImageDescriptorInfoKHR* pImages, + const VkHostAddressStridedRangeKHR* pDescriptors); + +typedef VkResult (VKAPI_PTR *PFN_vkWriteBufferDescriptorsKHR)( + VkDevice device, + uint32_t bufferCount, + const VkBufferDescriptorInfoKHR* pBuffers, + const VkHostAddressStridedRangeKHR* pDescriptors); + +typedef void (VKAPI_PTR *PFN_vkCmdBindSamplerHeapKHR)( + VkCommandBuffer commandBuffer, + const VkBindHeapInfoKHR* pBindInfo); + +typedef void (VKAPI_PTR *PFN_vkCmdBindImageHeapKHR)( + VkCommandBuffer commandBuffer, + const VkBindHeapInfoKHR* pBindInfo); + +typedef void (VKAPI_PTR *PFN_vkCmdBindBufferHeapKHR)( + VkCommandBuffer commandBuffer, + const VkBindHeapInfoKHR* pBindInfo); + +typedef void (VKAPI_PTR *PFN_vkCmdPushDataKHR)( + VkCommandBuffer commandBuffer, + const VkPushDataInfoKHR* pPushDataInfo); + +typedef VkResult (VKAPI_PTR *PFN_vkGetImageOpaqueCaptureDataKHR)( + VkDevice device, + VkImage image, + size_t size, + void* pData); + +typedef VkDeviceSize (VKAPI_PTR *PFN_vkGetPhysicalDeviceDescriptorSizeKHR)( + VkPhysicalDevice physicalDevice, + VkDescriptorType descriptorType); + +typedef VkResult (VKAPI_PTR *PFN_vkRegisterCustomBorderColorEXT)( + VkDevice device, + const VkSamplerCustomBorderColorCreateInfoEXT* pBorderColor, + VkBool32 requestIndex, + uint32_t* pIndex); + +typedef void (VKAPI_PTR *PFN_vkUnregisterCustomBorderColorEXT)( + VkDevice device, + uint32_t index); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkWriteSamplerDescriptorsKHR( + VkDevice device, + uint32_t samplerCount, + const VkSamplerCreateInfo* pSamplers, + const VkHostAddressStridedRangeKHR* pDescriptors); + +VKAPI_ATTR VkResult VKAPI_CALL vkWriteImageDescriptorsKHR( + VkDevice device, + uint32_t imageCount, + const VkImageDescriptorInfoKHR* pImages, + const VkHostAddressStridedRangeKHR* pDescriptors); + +VKAPI_ATTR VkResult VKAPI_CALL vkWriteBufferDescriptorsKHR( + VkDevice device, + uint32_t bufferCount, + const VkBufferDescriptorInfoKHR* pBuffers, + const VkHostAddressStridedRangeKHR* pDescriptors); + +VKAPI_ATTR void VKAPI_CALL vkCmdBindSamplerHeapKHR( + VkCommandBuffer commandBuffer, + const VkBindHeapInfoKHR* pBindInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdBindImageHeapKHR( + VkCommandBuffer commandBuffer, + const VkBindHeapInfoKHR* pBindInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdBindBufferHeapKHR( + VkCommandBuffer commandBuffer, + const VkBindHeapInfoKHR* pBindInfo); + +VKAPI_ATTR void VKAPI_CALL vkCmdPushDataKHR( + VkCommandBuffer commandBuffer, + const VkPushDataInfoKHR* pPushDataInfo); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetImageOpaqueCaptureDataKHR( + VkDevice device, + VkImage image, + size_t size, + void* pData); + +VKAPI_ATTR VkDeviceSize VKAPI_CALL vkGetPhysicalDeviceDescriptorSizeKHR( + VkPhysicalDevice physicalDevice, + VkDescriptorType descriptorType); + +VKAPI_ATTR VkResult VKAPI_CALL vkRegisterCustomBorderColorEXT( + VkDevice device, + const VkSamplerCustomBorderColorCreateInfoEXT* pBorderColor, + VkBool32 requestIndex, + uint32_t* pIndex); + +VKAPI_ATTR void VKAPI_CALL vkUnregisterCustomBorderColorEXT( + VkDevice device, + uint32_t index); +#endif + +#endif // VK_KHR_DESCRIPTOR_HEAP_H_ diff --git a/cube/vk_khr_device_address_commands.h b/cube/vk_khr_device_address_commands.h new file mode 100644 index 000000000..1b4bfeae6 --- /dev/null +++ b/cube/vk_khr_device_address_commands.h @@ -0,0 +1,337 @@ +/* +************************************************************************************************************************ +* +* Copyright (C) 2022 Advanced Micro Devices, Inc. All rights reserved. +* +***********************************************************************************************************************/ +/** +********************************************************************************************************************** +* @file vk_khr_device_address_commands.h +* @brief Temporary internal header for KHR pipeline binaries extension. This file should be removed once the +* extension is published and the API gets included in the official Vulkan header. +********************************************************************************************************************** +*/ + +#ifndef VK_KHR_DEVICE_ADDRESS_COMMANDS_H_ +#define VK_KHR_DEVICE_ADDRESS_COMMANDS_H_ + +#define VK_KHR_device_address_commands 1 +#define VK_KHR_DEVICE_ADDRESS_COMMANDS_SPEC_VERSION 1 +#define VK_KHR_DEVICE_ADDRESS_COMMANDS_EXTENSION_NAME "VK_KHR_device_address_commands" + +// ==================================================================================================================== +typedef struct VkDeviceAddressRangeKHR { + VkDeviceAddress address; + VkDeviceSize size; +} VkDeviceAddressRangeKHR; + +typedef VkBufferCopy2 VkMemoryCopyKHR; + +typedef struct VkCopyMemoryInfoKHR { + VkStructureType sType; + const void* pNext; + VkDeviceAddressRangeKHR srcRange; + VkDeviceAddressRangeKHR dstRange; + uint32_t copyCount; + const VkMemoryCopyKHR* pCopies; +} VkCopyMemoryInfoKHR; + +typedef VkBufferImageCopy2 VkMemoryImageCopyKHR; + +typedef struct VkMemoryRangeBarrierKHR { + VkStructureType sType; + const void* pNext; + VkPipelineStageFlags2 srcStageMask; + VkAccessFlags2 srcAccessMask; + VkPipelineStageFlags2 dstStageMask; + VkAccessFlags2 dstAccessMask; + uint32_t srcQueueFamilyIndex; + uint32_t dstQueueFamilyIndex; + VkDeviceAddressRangeKHR addressRange; +} VkMemoryRangeBarrierKHR; + +typedef struct VkCopyMemoryImageInfoKHR { + VkStructureType sType; + const void* pNext; + VkDeviceAddressRangeKHR addressRange; + VkImage image; + uint32_t copyCount; + const VkMemoryImageCopyKHR* pCopies; +} VkCopyMemoryImageInfoKHR; + +typedef struct VkMemoryRangeBarriersInfoKHR { + VkStructureType sType; + const void* pNext; + uint32_t memoryRangeBarrierCount; + const VkMemoryRangeBarrierKHR* pMemoryRangeBarriers; +} VkMemoryRangeBarriersInfoKHR; + +typedef struct VkConditionalRenderingBeginInfo2EXT { + VkStructureType sType; + const void* pNext; + VkDeviceAddressRangeKHR addressRange; + VkConditionalRenderingFlagsEXT flags; +} VkConditionalRenderingBeginInfo2EXT; + +// ==================================================================================================================== +typedef void(VKAPI_PTR* PFN_vkCmdBindIndexAddressKHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkIndexType indexType); + +typedef void(VKAPI_PTR* PFN_vkCmdBindVertexAddressesKHR)( + VkCommandBuffer commandBuffer, + uint32_t firstBinding, + uint32_t bindingCount, + const VkDeviceAddressRangeKHR* pRanges, + const VkDeviceSize* pStrides); + +typedef void(VKAPI_PTR* PFN_vkCmdDrawIndirect2KHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t drawCount, + uint32_t stride); + +typedef void(VKAPI_PTR* PFN_vkCmdDrawIndexedIndirect2KHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t drawCount, + uint32_t stride); + +typedef void(VKAPI_PTR* PFN_vkCmdDispatchIndirect2KHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range); + +typedef void(VKAPI_PTR* PFN_vkCmdDrawIndirectCount2KHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkDeviceAddressRangeKHR countRange, + uint32_t maxDrawCount, + uint32_t stride); + +typedef void(VKAPI_PTR* PFN_vkCmdDrawIndexedIndirectCount2KHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkDeviceAddressRangeKHR countRange, + uint32_t maxDrawCount, + uint32_t stride); + +typedef void(VKAPI_PTR* PFN_vkCmdDrawMeshTasksIndirect2EXT)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t drawCount, + uint32_t stride); + +typedef void(VKAPI_PTR* PFN_vkCmdDrawMeshTasksIndirectCount2EXT)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkDeviceAddressRangeKHR countRange, + uint32_t maxDrawCount, + uint32_t stride); + +typedef void(VKAPI_PTR* PFN_vkCmdCopyMemoryKHR)( + VkCommandBuffer commandBuffer, + const VkCopyMemoryInfoKHR pCopyMemoryInfo); + +typedef void(VKAPI_PTR* PFN_vkCmdCopyMemoryToImageKHR)( + VkCommandBuffer commandBuffer, + const VkCopyMemoryImageInfoKHR pCopyMemoryInfo); + +typedef void(VKAPI_PTR* PFN_vkCmdCopyImageToMemoryKHR)( + VkCommandBuffer commandBuffer, + const VkCopyMemoryImageInfoKHR pCopyMemoryInfo); + +typedef void(VKAPI_PTR* PFN_vkCmdUpdateMemoryKHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR dstRange, + VkDeviceSize dataSize, + const void* pData); + +typedef void(VKAPI_PTR* PFN_vkCmdFillMemoryKHR)( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t data); + +typedef void(VKAPI_PTR* PFN_vkCmdCopyQueryPoolResults2KHR)( + VkCommandBuffer commandBuffer, + VkQueryPool queryPool, + uint32_t firstQuery, + uint32_t queryCount, + VkDeviceAddressRangeKHR range, + VkDeviceSize stride, + VkQueryResultFlags flags); + +typedef void(VKAPI_PTR* PFN_vkCmdBeginConditionalRendering2EXT)( + VkCommandBuffer commandBuffer, + const VkConditionalRenderingBeginInfo2EXT* pConditionalRenderingBegin); + +typedef void(VKAPI_PTR* PFN_vkCmdBindTransformFeedbackBuffers2EXT)( + VkCommandBuffer commandBuffer, + uint32_t firstBinding, + uint32_t bindingCount, + const VkDeviceAddressRangeKHR* pRanges); + +typedef void(VKAPI_PTR* PFN_vkCmdBeginTransformFeedback2EXT)( + VkCommandBuffer commandBuffer, + uint32_t firstCounterRange, + uint32_t counterRangeCount, + const VkDeviceAddressRangeKHR* pCounterRanges); + +typedef void(VKAPI_PTR* PFN_vkCmdEndTransformFeedback2EXT)( + VkCommandBuffer commandBuffer, + uint32_t firstCounterRange, + uint32_t counterRangeCount, + const VkDeviceAddressRangeKHR* pCounterRanges); + +typedef void(VKAPI_PTR* PFN_vkCmdDrawIndirectByteCount2EXT)( + VkCommandBuffer commandBuffer, + uint32_t instanceCount, + uint32_t firstInstance, + VkDeviceAddressRangeKHR counterRange, + uint32_t counterOffset, + uint32_t vertexStride); + +typedef void(VKAPI_PTR* PFN_vkCmdWriteBufferMarker3AMD)( + VkCommandBuffer commandBuffer, + VkPipelineStageFlags2KHR stage, + VkDeviceAddress dstAddress, + uint32_t marker); + +#ifndef VK_NO_PROTOTYPES +// ==================================================================================================================== +// Index and Vertex Binding +void vkCmdBindIndexAddressKHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkIndexType indexType); + +void vkCmdBindVertexAddressesKHR( + VkCommandBuffer commandBuffer, + uint32_t firstBinding, + uint32_t bindingCount, + const VkDeviceAddressRangeKHR* pRanges, + const VkDeviceSize* pStrides); + +// ==================================================================================================================== +// Indirect Commands +void vkCmdDrawIndirect2KHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t drawCount, + uint32_t stride); + +void vkCmdDrawIndexedIndirect2KHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t drawCount, + uint32_t stride); + +void vkCmdDispatchIndirect2KHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range); + +void vkCmdDrawIndirectCount2KHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkDeviceAddressRangeKHR countRange, + uint32_t maxDrawCount, + uint32_t stride); + +void vkCmdDrawIndexedIndirectCount2KHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkDeviceAddressRangeKHR countRange, + uint32_t maxDrawCount, + uint32_t stride); + +void vkCmdDrawMeshTasksIndirect2EXT( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t drawCount, + uint32_t stride); + +void vkCmdDrawMeshTasksIndirectCount2EXT( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + VkDeviceAddressRangeKHR countRange, + uint32_t maxDrawCount, + uint32_t stride); + +// ==================================================================================================================== +// Transfer Commands +void vkCmdCopyMemoryKHR( + VkCommandBuffer commandBuffer, + const VkCopyMemoryInfoKHR pCopyMemoryInfo); + +void vkCmdCopyMemoryToImageKHR( + VkCommandBuffer commandBuffer, + const VkCopyMemoryImageInfoKHR pCopyMemoryInfo); + +void vkCmdCopyImageToMemoryKHR( + VkCommandBuffer commandBuffer, + const VkCopyMemoryImageInfoKHR pCopyMemoryInfo); + +void vkCmdUpdateMemoryKHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR dstRange, + VkDeviceSize dataSize, + const void* pData); + +void vkCmdFillMemoryKHR( + VkCommandBuffer commandBuffer, + VkDeviceAddressRangeKHR range, + uint32_t data); + +void vkCmdCopyQueryPoolResults2KHR( + VkCommandBuffer commandBuffer, + VkQueryPool queryPool, + uint32_t firstQuery, + uint32_t queryCount, + VkDeviceAddressRangeKHR range, + VkDeviceSize stride, + VkQueryResultFlags flags); + +// ==================================================================================================================== +// Conditional Rendering +void vkCmdBeginConditionalRendering2EXT( + VkCommandBuffer commandBuffer, + const VkConditionalRenderingBeginInfo2EXT* pConditionalRenderingBegin); + +// ==================================================================================================================== +// Transform Feedback +void vkCmdBindTransformFeedbackBuffers2EXT( + VkCommandBuffer commandBuffer, + uint32_t firstBinding, + uint32_t bindingCount, + const VkDeviceAddressRangeKHR* pRanges); + +void vkCmdBeginTransformFeedback2EXT( + VkCommandBuffer commandBuffer, + uint32_t firstCounterRange, + uint32_t counterRangeCount, + const VkDeviceAddressRangeKHR* pCounterRanges); + +void vkCmdEndTransformFeedback2EXT( + VkCommandBuffer commandBuffer, + uint32_t firstCounterRange, + uint32_t counterRangeCount, + const VkDeviceAddressRangeKHR* pCounterRanges); + +void vkCmdDrawIndirectByteCount2EXT( + VkCommandBuffer commandBuffer, + uint32_t instanceCount, + uint32_t firstInstance, + VkDeviceAddressRangeKHR counterRange, + uint32_t counterOffset, + uint32_t vertexStride); + +// ==================================================================================================================== +// Buffer Markers +void vkCmdWriteBufferMarker3AMD( + VkCommandBuffer commandBuffer, + VkPipelineStageFlags2KHR stage, + VkDeviceAddress dstAddress, + uint32_t marker); +#endif + +#endif /* VK_KHR_KHR_DEVICE_ADDRESS_COMMANDS_H_ */ diff --git a/icd/VkICD_mock_icd.json.in b/icd/VkICD_mock_icd.json.in index 867dabc43..9c286f9e4 100644 --- a/icd/VkICD_mock_icd.json.in +++ b/icd/VkICD_mock_icd.json.in @@ -2,6 +2,6 @@ "file_format_version": "1.0.1", "ICD": { "library_path": "@JSON_LIBRARY_PATH@", - "api_version": "1.3.293" + "api_version": "1.3.290" } } \ No newline at end of file diff --git a/windows-runtime-installer/VulkanRT-License.txt b/windows-runtime-installer/VulkanRT-License.txt index e8cd1a73c..6b160800d 100644 --- a/windows-runtime-installer/VulkanRT-License.txt +++ b/windows-runtime-installer/VulkanRT-License.txt @@ -1,6 +1,6 @@ -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (c) 2015-2024 Valve Corporation +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (c) 2015-2025 Valve Corporation The Vulkan Runtime is comprised of 100% open-source components (MIT, and Apache 2.0). The text of such licenses is included below along with the @@ -30,9 +30,9 @@ under the License. ============================MIT============================ Copyright (c) 2009 Dave Gamble -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -55,9 +55,9 @@ THE SOFTWARE. ============================MIT============================ Copyright (c) 2014 joseph werle -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and/or associated documentation files (the "Materials"), to