| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // |
| // Vulkan WSI platforms are included first |
| // |
| |
| #if defined(__linux__) |
| #define VK_USE_PLATFORM_XCB_KHR |
| #elif defined(__Fuchsia__) |
| // VK_USE_PLATFORM_FUCHSIA is already defined |
| #else |
| #error "Unsupported WSI platform" |
| #endif |
| |
| #include <vulkan/vulkan.h> |
| |
| // |
| // |
| // |
| |
| #include <getopt.h> |
| #include <math.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "common/macros.h" |
| #include "common/vk/assert.h" |
| #include "common/vk/debug_utils.h" |
| #include "common/vk/find_mem_type_idx.h" |
| #include "common/vk/pipeline_cache.h" |
| #include "spinel/platforms/vk/ext/find_target/find_target.h" |
| #include "spinel/platforms/vk/spinel_vk.h" |
| #include "spinel/spinel_assert.h" |
| #include "svg/svg.h" |
| #include "widget/coords.h" |
| #include "widget/fps.h" |
| #include "widget/mouse.h" |
| #include "widget/svg.h" |
| |
| // |
| // |
| // |
| |
| #ifndef M_PI |
| #define M_PI 3.14159265358979323846 |
| #endif |
| |
| ////////////////////////////////////////////// |
| // |
| // Define a platform-specific prefix for the pipeline cache |
| // |
| |
| #ifdef __Fuchsia__ |
| #define SPN_PLATFORM_PIPELINE_CACHE_PREFIX_STRING "/cache/." |
| #else |
| #define SPN_PLATFORM_PIPELINE_CACHE_PREFIX_STRING "." |
| #endif |
| |
| ////////////////////////////////////////////// |
| // |
| // LINUX |
| // |
| // clang-format off |
| // |
| #if defined(__linux__) |
| |
| #include "surface/surface_xcb.h" |
| |
| #define SPN_PLATFORM_EXTENSION_NAMES // For example: VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME |
| |
| #define SPN_PLATFORM_MIN_IMAGE_COUNT 2 |
| |
| #define SPN_PLATFORM_PRESENT_MODE VK_PRESENT_MODE_FIFO_KHR |
| // VK_PRESENT_MODE_IMMEDIATE_KHR |
| // VK_PRESENT_MODE_MAILBOX_KHR |
| // VK_PRESENT_MODE_FIFO_RELAXED_KHR |
| |
| #define SPN_PLATFORM_IMAGE_VIEW_FORMAT VK_FORMAT_B8G8R8A8_UNORM |
| |
| #define SPN_PLATFORM_SURFACE_FORMAT (VkSurfaceFormatKHR){ .format = SPN_PLATFORM_IMAGE_VIEW_FORMAT, \ |
| .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; |
| |
| ////////////////////////////////////////////// |
| // |
| // FUCHSIA |
| // |
| #elif defined(__Fuchsia__) |
| |
| #include "surface/surface_fuchsia_fb.h" |
| |
| #define SPN_PLATFORM_EXTENSION_NAMES |
| |
| #define SPN_PLATFORM_MIN_IMAGE_COUNT 2 |
| |
| #define SPN_PLATFORM_PRESENT_MODE VK_PRESENT_MODE_FIFO_KHR |
| // VK_PRESENT_MODE_MAILBOX_KHR |
| // VK_PRESENT_MODE_IMMEDIATE_KHR |
| // VK_PRESENT_MODE_FIFO_RELAXED_KHR |
| |
| #define SPN_PLATFORM_IMAGE_VIEW_FORMAT VK_FORMAT_R8G8B8A8_UNORM |
| |
| #define SPN_PLATFORM_SURFACE_FORMAT (VkSurfaceFormatKHR){ .format = SPN_PLATFORM_IMAGE_VIEW_FORMAT, \ |
| .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; |
| |
| ////////////////////////////////////////////// |
| // |
| // UNSUPPORTED |
| // |
| #else |
| #error "Unsupported WSI platform" |
| #endif |
| |
| // |
| // clang-format on |
| // |
| |
| // |
| // What are the max number of queues? |
| // |
| // FIXME(allanmac): There should be no limits. |
| // |
| #define SPN_VK_Q_COMPUTE_MAX_QUEUES UINT32_MAX |
| #define SPN_VK_Q_PRESENT_MAX_QUEUES 1 |
| |
| // |
| // |
| // |
| |
| #define SPN_ACQUIRE_DEFAULT_TIMEOUT ((uint64_t)15e9) // 15 seconds |
| |
| // |
| // Support acquiring either a fenced or unfenced presentable |
| // |
| typedef VkResult // |
| (*spinel_acquire_presentable_pfn_t)(VkDevice vk_d, |
| struct surface * const surface, |
| struct surface_presentable const ** const presentable, |
| void * payload); |
| |
| // |
| // Acquire a fenced presentable |
| // |
| static VkResult |
| spinel_acquire_fenced_presentable(VkDevice vk_d, |
| struct surface * surface, |
| struct surface_presentable const ** presentable, |
| void * payload) |
| { |
| // |
| // Wait for fence to signal |
| // |
| VkFence fence; |
| VkResult result = surface_next_fence(surface, &fence); |
| |
| switch (result) |
| { |
| case VK_SUCCESS: |
| result = vkWaitForFences(vk_d, 1, &fence, VK_TRUE, SPN_ACQUIRE_DEFAULT_TIMEOUT); |
| |
| if (result != VK_SUCCESS) |
| { |
| return result; |
| } |
| break; |
| |
| case VK_ERROR_OUT_OF_DATE_KHR: |
| case VK_ERROR_INITIALIZATION_FAILED: |
| case VK_ERROR_DEVICE_LOST: |
| default: |
| return result; |
| } |
| |
| // |
| // Fence is signaled so attempt to acquire a presentable |
| // |
| result = surface_acquire(surface, SPN_ACQUIRE_DEFAULT_TIMEOUT, presentable, payload); |
| |
| return result; |
| } |
| |
| // |
| // Acquire an unfenced presentable |
| // |
| static VkResult |
| spinel_acquire_unfenced_presentable(VkDevice vk_d, |
| struct surface * surface, |
| struct surface_presentable const ** presentable, |
| void * payload) |
| { |
| VkResult const result = surface_acquire(surface, // |
| SPN_ACQUIRE_DEFAULT_TIMEOUT, |
| presentable, |
| payload); |
| |
| return result; |
| } |
| |
| // |
| // |
| // |
| static void |
| spinel_usage(char * const argv[]) |
| { |
| static char const * const pms[] = { "VK_PRESENT_MODE_IMMEDIATE_KHR", |
| "VK_PRESENT_MODE_MAILBOX_KHR", |
| "VK_PRESENT_MODE_FIFO_KHR", |
| "VK_PRESENT_MODE_FIFO_RELAXED_KHR" }; |
| // |
| // clang-format off |
| // |
| fprintf(stderr, |
| "\n" |
| "Usage: %s -f <filename> [...]\n" |
| " -h Print usage.\n" |
| " -d <vendorID>:<deviceID> Execute on a specific Vulkan physical device. Defaults to first device.\n" |
| " -f <filename> Filename of SVG file.\n" |
| " -i <min image count> Minimum number of images in swapchain. Defaults to %u.\n" |
| " -j <pipeline stage> Select which pipeline stages are enabled on the first loop. Defaults to `11111`.\n" |
| " -k <pipeline stage> Select which pipeline stages are enabled after the first loop. Defaults to `11111`.\n" |
| " -n <frames> Maximum frames before exiting. Defaults to UINT32_MAX\n" |
| " -p <present mode> Select present mode [0-3]*. Defaults to %u/%s.\n" |
| " -q <compute>:<present> Select the compute and presentation queue family indices. Defaults to `0:0`\n" |
| " -r Rotate the SVG file around the origin. Disabled by default.\n" |
| " -t <seconds> Maximum seconds before exiting. Defaults to UINT32_MAX\n" |
| " -v Verbose SVG parsing output. Quiet by default.\n" |
| " -F Use VkFences to meter swapchain image acquires.\n" |
| " -Q Disable Vulkan validation layers. Enabled by default.\n" |
| " -D Disable Vulkan debug info labels. Enabled by default.\n" |
| " -X Skip clearing the image entirely before every render.\n" |
| " -c <x>,<y>:<scale> (<x>,<y>) is the SVG center. Scale by <scale> and translate to center of surface.\n" |
| "\n" |
| " * Present Modes\n" |
| " -------------\n" |
| " 0 : %s *\n" |
| " 1 : %s\n" |
| " 2 : %s\n" |
| " 3 : %s *\n" |
| " * may result in tearing\n" |
| "\n", |
| argv[0], |
| SPN_PLATFORM_MIN_IMAGE_COUNT, |
| SPN_PLATFORM_PRESENT_MODE, |
| pms[SPN_PLATFORM_PRESENT_MODE], |
| pms[0], |
| pms[1], |
| pms[2], |
| pms[3]); |
| // |
| // clang-format on |
| // |
| } |
| |
| // |
| // |
| // |
| struct spinel_state |
| { |
| spinel_context_t context; |
| spinel_swapchain_t swapchain; |
| VkExtent2D extent; |
| uint32_t image_count; |
| struct widget_control initial; |
| struct widget_control control; |
| |
| struct |
| { |
| float cx; |
| float cy; |
| float scale; |
| } center; |
| |
| uint64_t t0; |
| |
| bool is_center; |
| bool is_rotate; |
| bool is_exit; |
| }; |
| |
| // |
| // |
| // |
| struct spinel_vk |
| { |
| VkInstance i; |
| VkPhysicalDevice pd; |
| VkDevice d; |
| VkAllocationCallbacks const * ac; |
| |
| struct |
| { |
| struct |
| { |
| uint32_t index; |
| VkQueueFamilyProperties props; |
| } compute; |
| |
| struct |
| { |
| uint32_t index; |
| VkQueueFamilyProperties props; |
| |
| struct |
| { |
| uint32_t count; |
| uint32_t next; |
| VkQueue queues[SPN_VK_Q_PRESENT_MAX_QUEUES]; |
| } pool; |
| } present; |
| } q; |
| |
| struct |
| { |
| uint32_t count; |
| uint32_t next; |
| VkCommandPool * pools; |
| VkCommandBuffer * buffers; |
| VkSemaphore * timelines; |
| uint64_t * values; |
| } cmd; |
| }; |
| |
| // |
| // |
| // |
| static void |
| spinel_secs_set(struct spinel_state * state) |
| { |
| struct timespec ts; |
| |
| timespec_get(&ts, TIME_UTC); |
| |
| state->t0 = ts.tv_sec * 1000000000UL + ts.tv_nsec; |
| } |
| |
| static bool |
| spinel_secs_lte(struct spinel_state * state, uint32_t seconds) |
| { |
| struct timespec ts; |
| |
| timespec_get(&ts, TIME_UTC); |
| |
| uint64_t const t1 = ts.tv_sec * 1000000000UL + ts.tv_nsec; |
| |
| uint64_t const elapsed = t1 - state->t0; |
| |
| return (elapsed <= 1000000000UL * seconds); |
| } |
| |
| // |
| // NOTE(allanmac): Validation layers either correctly or incorrectly identifying |
| // that the presentation queue submissions are hanging on to the command buffers |
| // a little longer than expected. |
| // |
| // The "+2" appears to resolve this when I expected a "+1" to be all that was |
| // required given the self-clocking behavior of the render loop. |
| // |
| // The assumption was that every swapchain image could be "in flight" and its |
| // associated command buffer in the post-submission "pending" state. Adding one |
| // more command buffer enabled recording while the pending command buffers are |
| // in flight. |
| // |
| // Acquiring a fenced presentable doesn't impact this observation. |
| // |
| static void |
| spinel_vk_cmd_create(struct spinel_vk * vk, uint32_t image_count) |
| { |
| uint32_t const count = image_count + 2; |
| |
| vk->cmd.count = count; |
| vk->cmd.next = 0; |
| vk->cmd.pools = MALLOC_MACRO(count * sizeof(*vk->cmd.pools)); |
| vk->cmd.buffers = MALLOC_MACRO(count * sizeof(*vk->cmd.buffers)); |
| vk->cmd.timelines = MALLOC_MACRO(count * sizeof(*vk->cmd.timelines)); |
| vk->cmd.values = CALLOC_MACRO(count, sizeof(*vk->cmd.values)); |
| |
| VkCommandPoolCreateInfo const cpci = { |
| |
| .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
| .pNext = NULL, |
| .flags = 0, |
| .queueFamilyIndex = vk->q.present.index, |
| }; |
| |
| VkCommandBufferAllocateInfo cbai = { |
| |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, |
| .pNext = NULL, |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, |
| .commandBufferCount = 1, |
| /* .commandPool = */ // updated on each iteration |
| }; |
| |
| VkSemaphoreTypeCreateInfo const stci = { |
| |
| .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR, |
| .pNext = NULL, |
| .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, |
| .initialValue = 0UL |
| }; |
| |
| VkSemaphoreCreateInfo const sci = { |
| |
| .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, |
| .pNext = &stci, |
| .flags = 0 |
| }; |
| |
| for (uint32_t ii = 0; ii < count; ii++) |
| { |
| vk(CreateCommandPool(vk->d, &cpci, vk->ac, vk->cmd.pools + ii)); |
| |
| cbai.commandPool = vk->cmd.pools[ii]; |
| |
| vk(AllocateCommandBuffers(vk->d, &cbai, vk->cmd.buffers + ii)); |
| |
| vk(CreateSemaphore(vk->d, &sci, vk->ac, vk->cmd.timelines + ii)); |
| } |
| } |
| |
| // |
| // |
| // |
| static void |
| spinel_vk_cmd_destroy(struct spinel_vk * vk) |
| { |
| // VkCommand* |
| for (uint32_t ii = 0; ii < vk->cmd.count; ii++) |
| { |
| vkDestroySemaphore(vk->d, vk->cmd.timelines[ii], vk->ac); |
| vkFreeCommandBuffers(vk->d, vk->cmd.pools[ii], 1, vk->cmd.buffers + ii); |
| vkDestroyCommandPool(vk->d, vk->cmd.pools[ii], vk->ac); |
| } |
| |
| free(vk->cmd.values); |
| free(vk->cmd.timelines); |
| free(vk->cmd.buffers); |
| free(vk->cmd.pools); |
| } |
| |
| // |
| // |
| // |
| static void |
| spinel_vk_cmd_regen(struct spinel_vk * vk, uint32_t image_count) |
| { |
| spinel_vk_cmd_destroy(vk); |
| |
| spinel_vk_cmd_create(vk, image_count); |
| } |
| |
| // |
| // |
| // |
| static void |
| spinel_vk_q_cmd_create(struct spinel_vk * vk, uint32_t image_count) |
| { |
| vk->q.present.pool.count = MIN_MACRO(uint32_t, // |
| SPN_VK_Q_PRESENT_MAX_QUEUES, |
| vk->q.present.props.queueCount); |
| vk->q.present.pool.next = 0; |
| |
| for (uint32_t ii = 0; ii < vk->q.present.pool.count; ii++) |
| { |
| vkGetDeviceQueue(vk->d, vk->q.present.index, ii, vk->q.present.pool.queues + ii); |
| } |
| |
| spinel_vk_cmd_create(vk, image_count); |
| } |
| |
| // |
| // |
| // |
| static VkQueue |
| spinel_vk_q_next(struct spinel_vk * vk) |
| { |
| return vk->q.present.pool.queues[vk->q.present.pool.next++ % vk->q.present.pool.count]; |
| } |
| |
| // |
| // This is very simple and is only possible because Spinel and the surface |
| // module will meter access to images. |
| // |
| static void |
| spinel_vk_cb_next(struct spinel_vk * vk, |
| VkCommandBuffer * cb, |
| VkSemaphore * timeline, |
| uint64_t * value) |
| { |
| uint32_t const next = vk->cmd.next++ % vk->cmd.count; |
| |
| VkSemaphoreWaitInfo const swi = { |
| .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, |
| .pNext = NULL, |
| .flags = 0, |
| .semaphoreCount = 1, |
| .pSemaphores = vk->cmd.timelines + next, |
| .pValues = vk->cmd.values + next, |
| }; |
| |
| vk(WaitSemaphores(vk->d, &swi, UINT64_MAX)); |
| |
| vk(ResetCommandPool(vk->d, vk->cmd.pools[next], 0)); |
| |
| *cb = vk->cmd.buffers[next]; |
| *timeline = vk->cmd.timelines[next]; |
| *value = ++vk->cmd.values[next]; |
| } |
| |
| // |
| // |
| // |
| static void |
| spinel_vk_destroy(struct spinel_vk * vk) |
| { |
| // VkQueue |
| // -- nothing to destroy |
| |
| // VkCommand* |
| spinel_vk_cmd_destroy(vk); |
| |
| // VkDevice |
| vkDestroyDevice(vk->d, NULL); |
| |
| // VkInstance |
| vkDestroyInstance(vk->i, NULL); |
| } |
| |
| // |
| // |
| // |
| static void |
| spinel_state_input(void * data, struct surface_event const * event) |
| { |
| struct spinel_state * const state = data; |
| |
| switch (event->type) |
| { |
| case SURFACE_EVENT_TYPE_EXIT: { |
| state->is_exit = true; |
| break; |
| } |
| |
| case SURFACE_EVENT_TYPE_KEYBOARD_PRESS: { |
| switch (event->keyboard.code) |
| { |
| case SURFACE_KEY_1: |
| state->control.paths ^= true; |
| break; |
| |
| case SURFACE_KEY_2: |
| state->control.rasters ^= true; |
| break; |
| |
| case SURFACE_KEY_3: |
| state->control.styling ^= true; |
| break; |
| |
| case SURFACE_KEY_4: |
| state->control.composition ^= true; |
| break; |
| |
| case SURFACE_KEY_5: |
| state->control.render ^= true; |
| break; |
| |
| case SURFACE_KEY_6: |
| state->control.flags = 0; |
| break; |
| |
| case SURFACE_KEY_R: |
| state->is_rotate ^= true; |
| break; |
| |
| case SURFACE_KEY_ESCAPE: |
| state->is_exit = true; |
| break; |
| |
| default: |
| break; |
| } |
| break; |
| } |
| |
| case SURFACE_EVENT_TYPE_TOUCH_INPUT_CONTACT_COUNT: { |
| if (event->touch.contact_count.curr == 4) |
| { |
| state->is_rotate ^= true; |
| } |
| else if (event->touch.contact_count.curr == 5) |
| { |
| state->is_exit = true; |
| } |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| } |
| |
| // |
| // Regen will either succeed or terminally fail |
| // |
| static void |
| spinel_surface_regen(struct surface * surface, struct spinel_state * state) |
| { |
| VkResult result = surface_regen(surface, &state->extent, &state->image_count); |
| |
| switch (result) |
| { |
| case VK_SUCCESS: |
| break; |
| |
| case VK_ERROR_DEVICE_LOST: |
| vk_ok(result); // fatal -- vk_ok() aborts |
| break; |
| |
| case VK_ERROR_OUT_OF_HOST_MEMORY: |
| case VK_ERROR_OUT_OF_DEVICE_MEMORY: |
| case VK_ERROR_SURFACE_LOST_KHR: |
| case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: |
| case VK_ERROR_INITIALIZATION_FAILED: |
| default: |
| vk_ok(result); // fatal -- vk_ok() aborts |
| break; |
| } |
| |
| // |
| // Regen spinel swapchain |
| // |
| if (state->swapchain != NULL) |
| { |
| spinel(swapchain_release(state->swapchain)); |
| } |
| |
| spinel_swapchain_create_info_t const create_info = { |
| .extent = { |
| .width = state->extent.width, |
| .height = state->extent.height, |
| }, |
| .count = state->image_count, |
| }; |
| |
| spinel(swapchain_create(state->context, &create_info, &state->swapchain)); |
| } |
| |
| // |
| // |
| // |
| int |
| main(int argc, char * const argv[]) |
| { |
| // |
| // set defaults |
| // |
| uint32_t vendor_id = 0; |
| uint32_t device_id = 0; |
| uint32_t min_image_count = SPN_PLATFORM_MIN_IMAGE_COUNT; |
| uint32_t frame_count = UINT32_MAX; |
| uint32_t seconds = UINT32_MAX; |
| uint32_t qfis[2] = { 0, 0 }; |
| VkPresentModeKHR present_mode = SPN_PLATFORM_PRESENT_MODE; |
| bool is_verbose = false; |
| VkBool32 is_fence_acquired = false; |
| bool is_validation = true; |
| bool is_debug_info = true; |
| bool is_clear_before_render = true; |
| char const * filename = NULL; |
| |
| // |
| // initial state of widgets |
| // |
| struct spinel_state state = // |
| { // |
| .initial = WIDGET_CONTROL_PRSCR(), |
| .control = WIDGET_CONTROL_PRSCR() |
| }; |
| |
| // |
| // process options |
| // |
| int opt; |
| |
| while ((opt = getopt(argc, argv, "c:d:f:i:j:k:n:p:q:t:R:rvFQDXh")) != EOF) |
| { |
| switch (opt) |
| { |
| case 'c': |
| // formatting |
| { |
| state.is_center = true; |
| state.center.scale = 1.0f; |
| |
| char * str_comma; |
| |
| state.center.cx = strtof(optarg, &str_comma); |
| |
| if (str_comma != optarg) |
| { |
| if (*str_comma == ',') |
| { |
| char * str_colon; |
| |
| state.center.cy = strtof(str_comma + 1, &str_colon); |
| |
| if (*str_colon == ':') |
| { |
| state.center.scale = strtof(str_colon + 1, NULL); |
| } |
| } |
| } |
| } |
| break; |
| |
| case 'd': |
| // formatting |
| { |
| char * str_colon; |
| |
| vendor_id = (uint32_t)strtoul(optarg, &str_colon, 16); // returns 0 on error |
| |
| if (str_colon != optarg) |
| { |
| if (*str_colon == ':') |
| { |
| device_id = (uint32_t)strtoul(str_colon + 1, NULL, 16); // returns 0 on error |
| } |
| } |
| } |
| break; |
| |
| case 'f': |
| filename = optarg; |
| break; |
| |
| case 'i': |
| min_image_count = (uint32_t)strtoul(optarg, NULL, 10); |
| min_image_count = MAX_MACRO(uint32_t, 1, min_image_count); |
| break; |
| |
| case 'j': |
| state.initial.flags = (uint32_t)strtoul(optarg, NULL, 2); |
| break; |
| |
| case 'k': |
| state.control.flags = (uint32_t)strtoul(optarg, NULL, 2); |
| break; |
| |
| case 'n': |
| frame_count = (uint32_t)strtoul(optarg, NULL, 10); |
| frame_count = MAX_MACRO(uint32_t, 1, frame_count); |
| break; |
| |
| case 'p': |
| present_mode = (uint32_t)strtoul(optarg, NULL, 10); |
| present_mode = MIN_MACRO(uint32_t, present_mode, VK_PRESENT_MODE_FIFO_RELAXED_KHR); |
| break; |
| |
| case 'q': |
| qfis[0] = (uint32_t)strtoul(optarg, NULL, 10); // returns 0 on error |
| qfis[1] = (uint32_t)strtoul(strchr(optarg, ':') + 1, NULL, 10); // returns 0 on error |
| break; |
| |
| case 'r': |
| state.is_rotate ^= true; |
| break; |
| |
| case 't': |
| seconds = (uint32_t)strtoul(optarg, NULL, 10); |
| break; |
| |
| case 'v': |
| is_verbose = true; |
| break; |
| |
| case 'F': |
| is_fence_acquired = VK_TRUE; |
| break; |
| |
| case 'Q': |
| is_validation = false; |
| break; |
| |
| case 'D': |
| is_debug_info = false; |
| break; |
| |
| case 'X': |
| is_clear_before_render = false; |
| break; |
| |
| case 'h': |
| default: |
| spinel_usage(argv); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| // |
| // Vulkan handles that we'll need until shutdown |
| // |
| struct spinel_vk vk = { 0 }; |
| |
| // |
| // define Vulkan 1.2 app |
| // |
| VkApplicationInfo const app_info = { |
| |
| .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, |
| .pNext = NULL, |
| .pApplicationName = "Fuchsia Spinel/VK Bench", |
| .applicationVersion = 0, |
| .pEngineName = "Fuchsia Spinel/VK", |
| .engineVersion = 0, |
| .apiVersion = VK_API_VERSION_1_2 |
| }; |
| |
| // |
| // create a Vulkan instance |
| // |
| char const * const instance_layers[] = { |
| #if defined(__Fuchsia__) |
| "VK_LAYER_FUCHSIA_imagepipe_swapchain_fb", |
| #endif |
| // |
| // additional layers here... |
| // |
| "VK_LAYER_KHRONOS_validation" // keep this layer name last |
| }; |
| |
| char const * const instance_extensions[] = { |
| VK_KHR_SURFACE_EXTENSION_NAME, |
| #if defined(__linux__) |
| VK_KHR_XCB_SURFACE_EXTENSION_NAME, |
| #elif defined(__Fuchsia__) |
| VK_FUCHSIA_IMAGEPIPE_SURFACE_EXTENSION_NAME, |
| #endif |
| // |
| // additional extensions here... |
| // |
| VK_EXT_DEBUG_UTILS_EXTENSION_NAME, // keep this instance name last |
| }; |
| |
| uint32_t const instance_layer_count = ARRAY_LENGTH_MACRO(instance_layers) - // |
| (is_validation ? 0 : 1); |
| |
| uint32_t const instance_extension_count = ARRAY_LENGTH_MACRO(instance_extensions) - // |
| (is_debug_info ? 0 : 1); |
| |
| VkInstanceCreateInfo const ici = { |
| |
| .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, |
| .pNext = NULL, |
| .flags = 0, |
| .pApplicationInfo = &app_info, |
| .enabledLayerCount = instance_layer_count, |
| .ppEnabledLayerNames = instance_layers, |
| .enabledExtensionCount = instance_extension_count, |
| .ppEnabledExtensionNames = instance_extensions |
| }; |
| |
| vk(CreateInstance(&ici, NULL, &vk.i)); |
| |
| // |
| // initialize debug util pfns |
| // |
| if (is_debug_info) |
| { |
| vk_debug_utils_init(vk.i); |
| } |
| |
| // |
| // acquire all physical devices |
| // |
| uint32_t pd_count; |
| |
| vk(EnumeratePhysicalDevices(vk.i, &pd_count, NULL)); |
| |
| if (pd_count == 0) |
| { |
| fprintf(stderr, "No device found\n"); |
| |
| return EXIT_FAILURE; |
| } |
| |
| VkPhysicalDevice * pds = MALLOC_MACRO(pd_count * sizeof(*pds)); |
| |
| vk(EnumeratePhysicalDevices(vk.i, &pd_count, pds)); |
| |
| // |
| // default to selecting the first id |
| // |
| VkPhysicalDeviceProperties pdp; |
| |
| vkGetPhysicalDeviceProperties(pds[0], &pdp); |
| |
| // |
| // default vendor/device is the first physical device |
| // |
| if ((vendor_id == 0) && (device_id == 0)) |
| { |
| vendor_id = pdp.vendorID; |
| device_id = pdp.deviceID; |
| } |
| |
| // |
| // list all devices |
| // |
| for (uint32_t ii = 0; ii < pd_count; ii++) |
| { |
| VkPhysicalDeviceProperties pdp_tmp; |
| |
| vkGetPhysicalDeviceProperties(pds[ii], &pdp_tmp); |
| |
| bool const is_match = (pdp_tmp.vendorID == vendor_id) && // |
| (pdp_tmp.deviceID == device_id); |
| |
| if (is_match) |
| { |
| pdp = pdp_tmp; |
| vk.pd = pds[ii]; |
| } |
| |
| fprintf(stdout, |
| "%c %X : %X : %s\n", |
| is_match ? '*' : ' ', |
| pdp_tmp.vendorID, |
| pdp_tmp.deviceID, |
| pdp_tmp.deviceName); |
| } |
| |
| if (vk.pd == VK_NULL_HANDLE) |
| { |
| fprintf(stderr, "Error -- device %X : %X not found.\n", vendor_id, device_id); |
| |
| return EXIT_FAILURE; |
| } |
| |
| // |
| // free physical devices |
| // |
| free(pds); |
| |
| // |
| // find Spinel target |
| // |
| spinel_vk_target_t * target = spinel_vk_find_target(vendor_id, device_id); |
| |
| if (target == NULL) |
| { |
| fprintf(stderr, "Error: No target for %X:%X\n", vendor_id, device_id); |
| |
| return EXIT_FAILURE; |
| } |
| |
| // |
| // check that we have a valid filename before proceeding |
| // |
| if ((filename == NULL) || (optind != argc)) |
| { |
| spinel_usage(argv); |
| |
| return EXIT_FAILURE; |
| } |
| |
| // |
| // try to load the svg doc |
| // |
| struct svg * svg_doc; |
| |
| svg_doc = svg_open(filename, is_verbose); |
| |
| if (svg_doc == NULL) |
| { |
| fprintf(stderr, "Not a valid SVG file: \"%s\"\n", filename); |
| |
| return EXIT_FAILURE; |
| } |
| |
| // |
| // create surface |
| // |
| struct surface * surface; |
| |
| #if defined(__linux__) |
| surface = surface_xcb_create(vk.i, |
| vk.ac, |
| (VkRect2D[]){ { .offset = { 100, 100 }, // |
| .extent = { 1024, 1024 } } }, |
| "Fuchsia Spinel/VK Bench"); |
| #elif defined(__Fuchsia__) |
| surface = surface_fuchsia_create(vk.i, vk.ac); |
| #else |
| #error "Unsupported WSI platform" |
| #endif |
| |
| if (surface == NULL) |
| { |
| fprintf(stderr, "Error -- surface creation failed!\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // get queue properties |
| // |
| uint32_t qfp_count; |
| |
| vkGetPhysicalDeviceQueueFamilyProperties(vk.pd, &qfp_count, NULL); |
| |
| VkQueueFamilyProperties qfp[qfp_count]; |
| |
| vkGetPhysicalDeviceQueueFamilyProperties(vk.pd, &qfp_count, qfp); |
| |
| // |
| // make sure qfis[2] are in range |
| // |
| if ((qfis[0] >= qfp_count) || (qfis[1] >= qfp_count)) |
| { |
| fprintf(stderr, |
| "Error -- queue indices out of range: %u:%u >= [0-%u]:[0-%u].\n", |
| qfis[0], |
| qfis[1], |
| qfp_count - 1, |
| qfp_count - 1); |
| } |
| |
| // |
| // Validate a compute-capable queue has been selected. |
| // |
| if ((qfp[qfis[0]].queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) |
| { |
| fprintf(stderr, |
| "Error -- .queueFamilyIndex %u does not not support VK_QUEUE_COMPUTE_BIT.\n", |
| qfis[0]); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // Validate a graphics-capable queue has been selected. |
| // |
| if ((qfp[qfis[1]].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0) |
| { |
| fprintf(stderr, |
| "Error -- .queueFamilyIndex %u does not not support VK_QUEUE_GRAPHICS_BIT.\n", |
| qfis[0]); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // Validate a presentable queue has been selected. |
| // |
| VkBool32 is_queue_presentable; |
| |
| vk(GetPhysicalDeviceSurfaceSupportKHR(vk.pd, |
| qfis[1], |
| surface_to_vk(surface), |
| &is_queue_presentable)); |
| |
| if (!is_queue_presentable) |
| { |
| fprintf(stderr, |
| "Error -- .queueFamilyIndex %u does not not support surface presentation.\n", |
| qfis[1]); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // save queue props and index |
| // |
| vk.q.compute.index = qfis[0]; |
| vk.q.compute.props = qfp[qfis[0]]; |
| vk.q.present.index = qfis[1]; |
| vk.q.present.props = qfp[qfis[1]]; |
| |
| // |
| // max queue sizes |
| // |
| uint32_t const vk_q_compute_count = MIN_MACRO(uint32_t, // |
| SPN_VK_Q_COMPUTE_MAX_QUEUES, |
| vk.q.compute.props.queueCount); |
| |
| uint32_t const vk_q_present_count = MIN_MACRO(uint32_t, // |
| SPN_VK_Q_PRESENT_MAX_QUEUES, |
| vk.q.present.props.queueCount); |
| |
| // |
| // find max queue count |
| // |
| uint32_t const qps_size = MAX_MACRO(uint32_t, vk_q_compute_count, vk_q_present_count); |
| |
| // |
| // default queue priorities |
| // |
| float qps[qps_size]; |
| |
| for (uint32_t ii = 0; ii < qps_size; ii++) |
| { |
| qps[ii] = 1.0f; |
| } |
| |
| // |
| // These are the queues that will be used |
| // |
| VkDeviceQueueCreateInfo dqcis[2] = { |
| { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
| .pNext = NULL, |
| .flags = 0, |
| .queueFamilyIndex = vk.q.compute.index, |
| .queueCount = vk_q_compute_count, |
| .pQueuePriorities = qps }, |
| |
| { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
| .pNext = NULL, |
| .flags = 0, |
| .queueFamilyIndex = vk.q.present.index, |
| .queueCount = vk_q_present_count, |
| .pQueuePriorities = qps }, |
| }; |
| |
| // |
| // Are the queue families the same? If so, then only list one. |
| // |
| bool const is_same_queue = (vk.q.compute.index == vk.q.present.index); |
| |
| // |
| // probe Spinel device requirements for this target |
| // |
| spinel_vk_target_requirements_t spinel_tr = { 0 }; |
| |
| spinel_vk_target_get_requirements(target, &spinel_tr); |
| |
| // |
| // platform extensions |
| // |
| char const * platform_ext_names[] = { |
| |
| VK_KHR_SWAPCHAIN_EXTENSION_NAME, |
| SPN_PLATFORM_EXTENSION_NAMES |
| }; |
| |
| uint32_t const platform_ext_count = ARRAY_LENGTH_MACRO(platform_ext_names); |
| uint32_t const ext_name_count = spinel_tr.ext_name_count + platform_ext_count; |
| |
| char const * ext_names[ext_name_count]; |
| |
| memcpy(ext_names, platform_ext_names, sizeof(*ext_names) * platform_ext_count); |
| |
| // |
| // features |
| // |
| VkPhysicalDeviceVulkan12Features pdf12 = { |
| .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, |
| }; |
| |
| VkPhysicalDeviceVulkan11Features pdf11 = { |
| .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, |
| .pNext = &pdf12 |
| }; |
| |
| VkPhysicalDeviceFeatures2 pdf2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, |
| .pNext = &pdf11 }; |
| |
| // |
| // populate Spinel device requirements |
| // |
| spinel_tr.ext_names = ext_names + platform_ext_count; |
| spinel_tr.pdf = &pdf2.features; |
| spinel_tr.pdf11 = &pdf11; |
| spinel_tr.pdf12 = &pdf12; |
| |
| if (!spinel_vk_target_get_requirements(target, &spinel_tr)) |
| { |
| fprintf(stderr, "Error: spinel_vk_target_get_requirements() failure.\n"); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // create VkDevice |
| // |
| VkDeviceCreateInfo const vk_dci = { |
| |
| .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, |
| .pNext = &pdf2, |
| .flags = 0, |
| .queueCreateInfoCount = is_same_queue ? 1 : 2, |
| .pQueueCreateInfos = dqcis, |
| .enabledLayerCount = 0, |
| .ppEnabledLayerNames = NULL, |
| .enabledExtensionCount = ext_name_count, |
| .ppEnabledExtensionNames = ext_names, |
| .pEnabledFeatures = NULL |
| }; |
| |
| vk(CreateDevice(vk.pd, &vk_dci, NULL, &vk.d)); |
| |
| // |
| // create pipeline cache |
| // |
| VkPipelineCache vk_pc; |
| |
| vk_ok(vk_pipeline_cache_create(vk.d, |
| NULL, |
| SPN_PLATFORM_PIPELINE_CACHE_PREFIX_STRING "spinel_vk_bench_cache", |
| &vk_pc)); |
| |
| // |
| // save compute queue index and count |
| // |
| spinel_vk_context_create_info_t const cci = { |
| .vk = { |
| .pd = vk.pd, |
| .d = vk.d, |
| .pc = vk_pc, |
| .ac = vk.ac, |
| .q = { |
| .compute = { |
| .flags = dqcis[0].flags, |
| .count = dqcis[0].queueCount, |
| .family_index = dqcis[0].queueFamilyIndex, |
| }, |
| .shared = { |
| .family_count = is_same_queue ? 1 : 2, |
| .family_indices = { |
| dqcis[0].queueFamilyIndex, |
| dqcis[1].queueFamilyIndex, |
| }, |
| }, |
| }, |
| }, |
| .target = target, |
| .block_pool_size = 256UL << 20, // 256 MB |
| .handle_count = 1 << 18, // 256K handles |
| }; |
| |
| state.context = spinel_vk_context_create(&cci); |
| |
| if (state.context == NULL) |
| { |
| fprintf(stderr, "Error: failed to create context!\n"); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // the target is no longer needed |
| // |
| spinel_vk_target_dispose(target); |
| |
| // |
| // destroy pipeline cache |
| // |
| vk_ok(vk_pipeline_cache_destroy(vk.d, |
| NULL, |
| SPN_PLATFORM_PIPELINE_CACHE_PREFIX_STRING "spinel_vk_bench_cache", |
| vk_pc)); |
| |
| // |
| // Get context limits |
| // |
| spinel_context_limits_t limits; |
| |
| spinel(context_get_limits(state.context, &limits)); |
| |
| // |
| // create surface presentables |
| // |
| VkSurfaceFormatKHR const surface_format = SPN_PLATFORM_SURFACE_FORMAT; |
| VkFormat const image_view_format = SPN_PLATFORM_IMAGE_VIEW_FORMAT; |
| |
| VkImageUsageFlags const image_usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | // |
| VK_IMAGE_USAGE_TRANSFER_DST_BIT | // |
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| |
| VkExtent2D const max_image_extent = { limits.extent.width, // |
| limits.extent.height }; |
| |
| VkComponentMapping const image_view_components = { |
| .r = VK_COMPONENT_SWIZZLE_IDENTITY, |
| .g = VK_COMPONENT_SWIZZLE_IDENTITY, |
| .b = VK_COMPONENT_SWIZZLE_IDENTITY, |
| .a = VK_COMPONENT_SWIZZLE_IDENTITY, |
| }; |
| |
| vk_ok(surface_attach(surface, |
| vk.pd, |
| vk.d, |
| is_fence_acquired, |
| &surface_format, |
| min_image_count, |
| &max_image_extent, |
| image_usage, |
| image_view_format, |
| &image_view_components, |
| present_mode)); |
| |
| // |
| // create a transform stack |
| // |
| struct spinel_transform_stack * const ts = spinel_transform_stack_create(16); |
| |
| // |
| // Apply world space transform (reflect over y=x at subpixel resolution) |
| // |
| spinel_transform_stack_push_transform(ts, &limits.global_transform); |
| |
| // |
| // create builders |
| // |
| spinel_path_builder_t pb; |
| |
| spinel(path_builder_create(state.context, &pb)); |
| |
| spinel_raster_builder_t rb; |
| |
| spinel(raster_builder_create(state.context, &rb)); |
| |
| // |
| // create widgets |
| // |
| widget_svg_t svg = widget_svg_create(svg_doc, false); // don't linearize SVG colors |
| |
| struct widget * ws[] = |
| { |
| #if !defined(__linux__) |
| widget_mouse_create().widget, // topmost layer |
| #endif // |
| widget_coords_create(8.0f).widget, // |
| widget_fps_create(16.0f).widget, // |
| svg.widget, // bottommost layer |
| }; |
| |
| // |
| // initialize layout of widgets |
| // |
| struct widget_layout w_layout = { 0 }; |
| uint32_t group_depth_max = 0; |
| |
| widget_layout(ws, ARRAY_LENGTH_MACRO(ws), &w_layout, &group_depth_max); |
| |
| spinel_group_id parents[group_depth_max + 1]; // 1 or 2 for now |
| |
| // |
| // Create composition |
| // |
| |
| spinel_composition_t composition; |
| |
| spinel(composition_create(state.context, &composition)); |
| |
| // |
| // Create styling |
| // |
| // Sizing: 16 cmds per layer is conservative plus the number of groups and |
| // their trail back to the parent |
| // |
| uint32_t const layer_count = w_layout.group.layer.base + w_layout.group.layer.count; |
| |
| spinel_styling_create_info_t const styling_create_info = { |
| |
| .layer_count = layer_count, |
| .cmd_count = layer_count * 8 + ARRAY_LENGTH_MACRO(ws) * 32, |
| }; |
| |
| spinel_styling_t styling; |
| |
| spinel(styling_create(state.context, &styling_create_info, &styling)); |
| |
| // |
| // |
| // |
| struct widget_context w_context = { |
| .context = state.context, |
| .pb = pb, |
| .rb = rb, |
| .ts = ts, |
| .styling.curr = styling, |
| .composition.curr = composition, |
| .parents = parents, |
| }; |
| |
| // |
| // initialize the first loop |
| // |
| struct widget_control w_control = state.initial; |
| |
| // |
| // set up rendering extensions |
| // |
| spinel_vk_swapchain_submit_ext_graphics_signal_t ext_graphics_signal = { |
| .ext = NULL, |
| .type = SPN_VK_SWAPCHAIN_SUBMIT_EXT_TYPE_GRAPHICS_SIGNAL, |
| .signal = { |
| .count = 2, |
| // |
| // .semaphores[] |
| // .values[] |
| // |
| }, |
| }; |
| |
| spinel_vk_swapchain_submit_ext_graphics_store_t ext_graphics_store = { |
| .ext = &ext_graphics_signal, |
| .type = SPN_VK_SWAPCHAIN_SUBMIT_EXT_TYPE_GRAPHICS_STORE, |
| .queue_family_index = qfis[1], |
| .image_info.imageLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, |
| // |
| // .extent_index |
| // .cb |
| // .queue |
| // .old_layout |
| // .image |
| // .image_info |
| // |
| }; |
| |
| spinel_vk_swapchain_submit_ext_graphics_wait_t ext_graphics_wait = { |
| .ext = &ext_graphics_store, |
| .type = SPN_VK_SWAPCHAIN_SUBMIT_EXT_TYPE_GRAPHICS_WAIT, |
| .wait = { |
| .count = 1, |
| .stages = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT }, |
| // |
| // .semaphores[] |
| // .values[] |
| // |
| }, |
| }; |
| |
| spinel_vk_swapchain_submit_ext_compute_acquire_t ext_compute_acquire = { |
| .ext = &ext_graphics_wait, |
| .type = SPN_VK_SWAPCHAIN_SUBMIT_EXT_TYPE_COMPUTE_ACQUIRE, |
| .from_queue_family_index = qfis[1], |
| }; |
| |
| spinel_vk_swapchain_submit_ext_compute_fill_t ext_compute_fill = { |
| .ext = NULL, // &ext_graphics_wait or &ext_compute_acquire |
| .type = SPN_VK_SWAPCHAIN_SUBMIT_EXT_TYPE_COMPUTE_FILL, |
| .dword = 0xFFFFFFFF, |
| }; |
| |
| spinel_vk_swapchain_submit_ext_compute_render_t ext_compute_render = { |
| .ext = NULL, // &ext_compute_fill or &ext_compute_acquire, |
| .type = SPN_VK_SWAPCHAIN_SUBMIT_EXT_TYPE_COMPUTE_RENDER, |
| // |
| // .clip = { 0, 0, ... }, |
| // .extent_index |
| // |
| }; |
| |
| spinel_vk_swapchain_submit_ext_compute_release_t ext_compute_release = { |
| .ext = &ext_compute_render, |
| .type = SPN_VK_SWAPCHAIN_SUBMIT_EXT_TYPE_COMPUTE_RELEASE, |
| .to_queue_family_index = qfis[1], |
| }; |
| |
| spinel_swapchain_submit_t swapchain_submit = { |
| .ext = &ext_compute_release, |
| .styling = styling, |
| .composition = composition, |
| }; |
| |
| // |
| // refresh the platform surface and spinel swapchain |
| // |
| spinel_surface_regen(surface, &state); |
| |
| // |
| // create presentation queue pool and command buffers |
| // |
| spinel_vk_q_cmd_create(&vk, state.image_count); |
| |
| // |
| // which "acquire_presentable" function? |
| // |
| spinel_acquire_presentable_pfn_t acquire_presentable_pfn = // |
| is_fence_acquired // |
| ? spinel_acquire_fenced_presentable |
| : spinel_acquire_unfenced_presentable; |
| |
| // |
| // RENDER/INPUT LOOP |
| // |
| // render and process input |
| // |
| spinel_secs_set(&state); |
| |
| for (uint32_t ii = 0; (ii < frame_count) && spinel_secs_lte(&state, seconds); ii++) |
| { |
| // |
| // Explicit flushing is only for accurately benchmarking a path declaration. |
| // |
| if (w_control.paths) |
| { |
| spinel(path_builder_flush(pb)); |
| } |
| |
| // |
| // Explicit flushing is only for accurately benchmarking rasterization. |
| // |
| if (w_control.rasters) |
| { |
| spinel(raster_builder_flush(rb)); |
| } |
| |
| // |
| // RESET WIDGET COMPOSITION? |
| // |
| if (w_control.composition) |
| { |
| // unseal and reset composition |
| spinel(composition_unseal(composition)); |
| spinel(composition_reset(composition)); |
| |
| // update clip |
| spinel_pixel_clip_t const clip = { |
| .x0 = 0, |
| .y0 = 0, |
| .x1 = state.extent.width, |
| .y1 = state.extent.height, |
| }; |
| |
| spinel(composition_set_clip(composition, &clip)); |
| } |
| |
| // |
| // RESET WIDGET STYLING? |
| // |
| if (w_control.styling) |
| { |
| // unseal and reset styling |
| spinel(styling_unseal(styling)); |
| spinel(styling_reset(styling)); |
| |
| // |
| // until there is a container widget to implicitly initialize the |
| // root, explicitly initialize the styling root group |
| // |
| widget_regen_styling_root(&w_control, &w_context, &w_layout); |
| } |
| |
| // |
| // REGENERATE WIDGETS |
| // |
| widget_regen(ws, ARRAY_LENGTH_MACRO(ws), &w_control, &w_context); |
| |
| // |
| // SEAL COMPOSITION & STYLING |
| // |
| // The composition and styling are implicitly sealed by render() but let's |
| // explicitly seal them here in case we're skipping rendering in the |
| // benchmark. |
| // |
| // NOTE(allanmac): the composition/styling/render API is in flux. |
| // |
| spinel_composition_seal(composition); |
| spinel_styling_seal(styling); |
| |
| // |
| // RENDER? |
| // |
| if (w_control.render) |
| { |
| // |
| // ACQUIRE A PRESENTABLE |
| // |
| struct surface_presentable const * presentable; |
| |
| VkResult const acquire_result = acquire_presentable_pfn(vk.d, // |
| surface, |
| &presentable, |
| NULL); |
| // |
| // Possible results: |
| // |
| // VK_SUCCESS : render |
| // VK_TIMEOUT : fatal |
| // VK_SUBOPTIMAL_KHR : render then regen |
| // VK_ERROR_OUT_OF_DATE_KHR : regen |
| // VK_ERROR_DEVICE_LOST : fatal for now |
| // VK_ERROR_OUT_OF_HOST_MEMORY : fatal |
| // VK_ERROR_OUT_OF_DEVICE_MEMORY : fatal |
| // VK_ERROR_SURFACE_LOST_KHR : fatal for now |
| // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT : fatal for now |
| // |
| bool is_fatal = false; |
| bool is_render = false; |
| bool is_regen = false; |
| |
| switch (acquire_result) |
| { |
| case VK_SUCCESS: |
| is_render = true; |
| break; |
| |
| case VK_TIMEOUT: |
| is_fatal = true; |
| break; |
| |
| case VK_SUBOPTIMAL_KHR: |
| is_render = true; |
| is_regen = true; |
| break; |
| |
| case VK_ERROR_OUT_OF_DATE_KHR: |
| case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: |
| is_regen = true; |
| break; |
| |
| case VK_ERROR_DEVICE_LOST: |
| case VK_ERROR_OUT_OF_HOST_MEMORY: |
| case VK_ERROR_OUT_OF_DEVICE_MEMORY: |
| case VK_ERROR_SURFACE_LOST_KHR: |
| default: |
| is_fatal = true; |
| break; |
| } |
| |
| // |
| // UNHANDLED ERROR |
| // |
| if (is_fatal) |
| { |
| vk_ok(acquire_result); |
| |
| break; |
| } |
| |
| // |
| // RENDER |
| // |
| if (is_render) |
| { |
| // |
| // Is this a new presentable with an implicit undefined layout? |
| // |
| bool const is_layout_undefined = (presentable->acquire_count == 1); |
| |
| if (is_layout_undefined) |
| { |
| // compute_render -> compute_fill -> graphics_wait -> ... |
| ext_compute_render.ext = &ext_compute_fill; |
| ext_compute_fill.ext = &ext_graphics_wait; |
| } |
| else if (is_clear_before_render) |
| { |
| // compute_render -> compute_fill -> compute_acquire -> graphics_wait -> ... |
| ext_compute_render.ext = &ext_compute_fill; |
| ext_compute_fill.ext = &ext_compute_acquire; |
| } |
| else |
| { |
| // compute_render -> compute_acquire -> graphics_wait -> ... |
| ext_compute_render.ext = &ext_compute_acquire; |
| } |
| |
| // |
| // Update compute render extension for this presentable |
| // |
| ext_compute_render.clip.x1 = state.extent.width; |
| ext_compute_render.clip.y1 = state.extent.height; |
| ext_compute_render.extent_index = presentable->image_index; |
| |
| // |
| // Wait on presentable's "wait" semaphore |
| // |
| ext_graphics_wait.wait.semaphores[0] = presentable->wait.semaphore; |
| |
| // |
| // Update graphics store extension for this presentable |
| // |
| VkImageLayout const layout_prev = is_layout_undefined // |
| ? VK_IMAGE_LAYOUT_UNDEFINED |
| : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
| |
| ext_graphics_store.extent_index = presentable->image_index; |
| ext_graphics_store.queue = spinel_vk_q_next(&vk); |
| ext_graphics_store.layout_prev = layout_prev; |
| ext_graphics_store.image = presentable->image; |
| ext_graphics_store.image_info.imageView = presentable->image_view; |
| |
| // |
| // Signal presentable's "signal" semaphore |
| // |
| ext_graphics_signal.signal.semaphores[0] = presentable->signal; |
| |
| // |
| // Get a command buffer and its associated availability semaphore |
| // |
| spinel_vk_cb_next(&vk, |
| &ext_graphics_store.cb, |
| ext_graphics_signal.signal.semaphores + 1, |
| ext_graphics_signal.signal.values + 1); |
| |
| // |
| // Submit compute work |
| // |
| spinel(swapchain_submit(state.swapchain, &swapchain_submit)); |
| |
| // |
| // Present graphics work |
| // |
| VkPresentInfoKHR const pi = { |
| .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, |
| .pNext = NULL, |
| .waitSemaphoreCount = 1, |
| .pWaitSemaphores = &presentable->signal, // wait on "signal" semaphore |
| .swapchainCount = 1, |
| .pSwapchains = &presentable->swapchain, |
| .pImageIndices = &presentable->image_index, |
| .pResults = NULL, |
| }; |
| |
| (void)vkQueuePresentKHR(ext_graphics_store.queue, &pi); |
| } |
| |
| // |
| // REGEN SWAPCHAIN |
| // |
| if (is_regen) |
| { |
| spinel_surface_regen(surface, &state); |
| |
| // |
| // Why regenerate the command buffers? It seems unlikely that |
| // swapchain image count will ever change -- even when resized -- |
| // but the spec says nothing about this. The only way to |
| // determine the actual image count is through |
| // vkGetSwapchainImagesKHR() after creation of a new swapchain. |
| // |
| // Note that there is a vkDeviceWaitIdle() hiding in |
| // spinel_surface_regen() so we know the queue command buffers |
| // aren't executing. |
| // |
| spinel_vk_cmd_regen(&vk, state.image_count); |
| } |
| } |
| |
| // |
| // WIDGET INPUT |
| // |
| w_control = state.control; // reset control flags |
| |
| widget_surface_input(ws, |
| ARRAY_LENGTH_MACRO(ws), |
| &w_control, |
| surface, |
| spinel_state_input, |
| &state); |
| |
| if (state.is_center) |
| { |
| widget_svg_center(svg, |
| &w_control, |
| &state.extent, |
| state.center.cx, |
| state.center.cy, |
| state.center.scale); |
| } |
| |
| if (state.is_rotate) |
| { |
| widget_svg_rotate(svg, &w_control, (float)((ii % 360) * (M_PI * 2.0 / 360.0))); |
| } |
| |
| // |
| // EXIT? |
| // |
| if (state.is_exit) |
| { |
| break; |
| } |
| } |
| |
| //////////////////////////////////// |
| // |
| // DISPOSAL |
| // |
| |
| // done with swapchain |
| spinel_swapchain_release(state.swapchain); |
| |
| // unseal Spinel composition and styling to ensure rendering is complete |
| spinel(composition_unseal(composition)); |
| spinel(styling_unseal(styling)); |
| |
| // widgets -- may release paths and rasters |
| widget_destroy(ws, ARRAY_LENGTH_MACRO(ws), &w_context); |
| |
| // release the Spinel builders, composition and styling |
| spinel(path_builder_release(pb)); |
| spinel(raster_builder_release(rb)); |
| spinel(composition_release(composition)); |
| spinel(styling_release(styling)); |
| |
| // release the transform stack |
| spinel_transform_stack_release(ts); |
| |
| // release the Spinel context |
| spinel(context_release(state.context)); |
| |
| // svg doc |
| svg_dispose(svg_doc); |
| |
| // surface |
| surface_destroy(surface); // will implicitly `detach(surface)` |
| |
| // destroy vk handles |
| spinel_vk_destroy(&vk); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| // |
| // |
| // |