nanovg_vulkan/example/example_vulkan.c

405 lines
13 KiB
C

// https://github.com/danilw/nanovg_vulkan
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <vulkan/vulkan.h>
#ifndef DEMO_ANTIALIAS
#define DEMO_ANTIALIAS 1
#endif
#ifndef DEMO_STENCIL_STROKES
#define DEMO_STENCIL_STROKES 1
#endif
#ifndef DEMO_VULKAN_VALIDATON_LAYER
#define DEMO_VULKAN_VALIDATON_LAYER 0
#endif
#include "nanovg.h"
#include "nanovg_vk.h"
#include "demo.h"
#include "perf.h"
#include "vulkan_util.h"
void errorcb(int error, const char *desc) { printf("GLFW error %d: %s\n", error, desc); }
int blowup = 0;
int screenshot = 0;
int premult = 0;
bool resize_event = false;
static void key(GLFWwindow *window, int key, int scancode, int action, int mods) {
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
blowup = !blowup;
if (key == GLFW_KEY_S && action == GLFW_PRESS)
screenshot = 1;
if (key == GLFW_KEY_P && action == GLFW_PRESS)
premult = !premult;
}
void prepareFrame(VkDevice device, VkCommandBuffer cmd_buffer, FrameBuffers *fb) {
VkResult res;
// Get the index of the next available swapchain image:
res = vkAcquireNextImageKHR(device, fb->swap_chain, UINT64_MAX, fb->present_complete_semaphore[fb->current_frame],
VK_NULL_HANDLE, &fb->current_buffer);
if (res == VK_ERROR_OUT_OF_DATE_KHR) {
resize_event = true;
res = 0;
return;
}
const VkCommandBufferBeginInfo cmd_buf_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
vkBeginCommandBuffer(cmd_buffer, &cmd_buf_info);
VkClearValue clear_values[2];
clear_values[0].color.float32[0] = 0.3f;
clear_values[0].color.float32[1] = 0.3f;
clear_values[0].color.float32[2] = 0.32f;
clear_values[0].color.float32[3] = 1.0f;
clear_values[1].depthStencil.depth = 1.0f;
clear_values[1].depthStencil.stencil = 0;
VkRenderPassBeginInfo rp_begin;
rp_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
rp_begin.pNext = NULL;
rp_begin.renderPass = fb->render_pass;
rp_begin.framebuffer = fb->framebuffers[fb->current_buffer];
rp_begin.renderArea.offset.x = 0;
rp_begin.renderArea.offset.y = 0;
rp_begin.renderArea.extent.width = fb->buffer_size.width;
rp_begin.renderArea.extent.height = fb->buffer_size.height;
rp_begin.clearValueCount = 2;
rp_begin.pClearValues = clear_values;
vkCmdBeginRenderPass(cmd_buffer, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport;
viewport.width = (float) fb->buffer_size.width;
viewport.height = (float) fb->buffer_size.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
viewport.x = (float) rp_begin.renderArea.offset.x;
viewport.y = (float) rp_begin.renderArea.offset.y;
vkCmdSetViewport(cmd_buffer, 0, 1, &viewport);
VkRect2D scissor = rp_begin.renderArea;
vkCmdSetScissor(cmd_buffer, 0, 1, &scissor);
}
void submitFrame(VkDevice device, VkQueue graphicsQueue, VkQueue presentQueue, VkCommandBuffer cmd_buffer,
FrameBuffers *fb) {
VkResult res;
vkCmdEndRenderPass(cmd_buffer);
VkImageMemoryBarrier image_barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = 0,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = fb->swap_chain_buffers[fb->current_buffer].image,
.subresourceRange =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
vkCmdPipelineBarrier(cmd_buffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0, 0, NULL, 0, NULL, 1, &image_barrier);
vkEndCommandBuffer(cmd_buffer);
VkPipelineStageFlags pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
submit_info.pNext = NULL;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &fb->present_complete_semaphore[fb->current_frame];
submit_info.pWaitDstStageMask = &pipe_stage_flags;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &cmd_buffer;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &fb->render_complete_semaphore[fb->current_frame];
vkQueueSubmit(presentQueue, 1, &submit_info, fb->flight_fence[fb->current_frame]);
/* Now present the image in the window */
VkPresentInfoKHR present = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
present.pNext = NULL;
present.swapchainCount = 1;
present.pSwapchains = &fb->swap_chain;
present.pImageIndices = &fb->current_buffer;
present.waitSemaphoreCount = 1;
present.pWaitSemaphores = &fb->render_complete_semaphore[fb->current_frame];
res = vkQueuePresentKHR(presentQueue, &present);
if (res == VK_ERROR_OUT_OF_DATE_KHR) {
vkQueueWaitIdle(graphicsQueue);
vkQueueWaitIdle(presentQueue);
resize_event = true;
res = 0;
return;
}
fb->current_frame = (fb->current_frame + 1) % fb->swapchain_image_count;
fb->num_swaps++;
if (fb->num_swaps >= fb->swapchain_image_count) {
vkWaitForFences(device, 1, &fb->flight_fence[fb->current_frame], true, UINT64_MAX);
vkResetFences(device, 1, &fb->flight_fence[fb->current_frame]);
}
}
int main() {
GLFWwindow *window;
if (!glfwInit()) {
printf("Failed to init GLFW.");
return -1;
}
if (!glfwVulkanSupported()) {
printf("vulkan dose not supported\n");
return 1;
}
glfwSetErrorCallback(errorcb);
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window = glfwCreateWindow(1000, 600, "NanoVG Example Vulkan", NULL, NULL);
if (!window) {
glfwTerminate();
return -1;
}
glfwSetKeyCallback(window, key);
glfwSetTime(0);
bool enableValidationLayer = false;
#if DEMO_VULKAN_VALIDATON_LAYER
enableValidationLayer = true;
#endif
VkInstance instance = createVkInstance(enableValidationLayer);
VkResult res;
VkSurfaceKHR surface;
res = glfwCreateWindowSurface(instance, window, 0, &surface);
if (VK_SUCCESS != res) {
printf("glfwCreateWindowSurface failed\n");
exit(-1);
}
uint32_t gpu_count = 0;
res = vkEnumeratePhysicalDevices(instance, &gpu_count, NULL);
if (VK_SUCCESS != res && res != VK_INCOMPLETE) {
printf("vkEnumeratePhysicalDevices failed %d \n", res);
exit(-1);
}
if (gpu_count < 1) {
printf("No Vulkan device found.\n");
exit(-1);
}
VkPhysicalDevice gpu[32];
res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpu);
if (res != VK_SUCCESS && res != VK_INCOMPLETE) {
printf("vkEnumeratePhysicalDevices failed %d \n", res);
exit(-1);
}
uint32_t idx = 0;
bool use_idx = false;
bool discrete_idx = false;
for (uint32_t i = 0; i < gpu_count && (!discrete_idx); i++) {
uint32_t qfc = 0;
vkGetPhysicalDeviceQueueFamilyProperties(gpu[i], &qfc, NULL);
if (qfc < 1)
continue;
VkQueueFamilyProperties *queue_family_properties;
queue_family_properties = malloc(qfc * sizeof(VkQueueFamilyProperties));
vkGetPhysicalDeviceQueueFamilyProperties(gpu[i], &qfc, queue_family_properties);
for (uint32_t j = 0; j < qfc; j++) {
VkBool32 supports_present;
vkGetPhysicalDeviceSurfaceSupportKHR(gpu[i], j, surface, &supports_present);
if ((queue_family_properties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) && supports_present) {
VkPhysicalDeviceProperties pr;
vkGetPhysicalDeviceProperties(gpu[i], &pr);
idx = i;
use_idx = true;
if (pr.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
discrete_idx = true;
}
break;
}
}
free(queue_family_properties);
}
if (!use_idx) {
printf("Not found suitable queue which supports graphics.\n");
exit(-1);
}
printf("Using GPU device %lu\n", (unsigned long) idx);
VkNvgExt extQuery = {};
VulkanDevice *device = createVulkanDevice(gpu[idx], surface, &extQuery);
int winWidth, winHeight;
glfwGetWindowSize(window, &winWidth, &winHeight);
VkQueue executionQueue;
VkQueue presentQueue;
vkGetDeviceQueue(device->device, device->graphicsQueueFamilyIndex, 0, &executionQueue);
vkGetDeviceQueue(device->device, device->graphicsQueueFamilyIndex, 0, &presentQueue);
FrameBuffers fb = createFrameBuffers(device, surface, executionQueue, winWidth, winHeight, 0);
VkCommandBuffer *cmd_buffer = createCmdBuffer(device->device, device->commandPool, fb.swapchain_image_count);
VKNVGCreateInfo create_info = {0};
create_info.device = device->device;
create_info.gpu = device->gpu;
create_info.renderpass = fb.render_pass;
create_info.cmdBuffer = cmd_buffer;
create_info.swapchainImageCount = fb.swapchain_image_count;
create_info.frameCount = fb.swapchain_image_count;
create_info.currentFrame = &fb.current_frame;
create_info.commandPool = device->commandPool;
create_info.graphicsQueueFamilyIndex = device->graphicsQueueFamilyIndex;
/**
* Either explicitly set the following to false or query your hardware and enable these items as necessary.
* See usage inside `createVulkanDevice` for more info.
* Utilises the capabilities of your hardware based on enabled extensions, either implicit (API version) or explicit
*/
create_info.ext.dynamicState = extQuery.dynamicState;
create_info.ext.colorBlendEquation = extQuery.colorBlendEquation;
create_info.ext.colorWriteMask = extQuery.colorWriteMask;
int flags = 0;
#ifndef NDEBUG
flags |= NVG_DEBUG; // unused in nanovg_vk
#endif
#if DEMO_ANTIALIAS
flags |= NVG_ANTIALIAS;
#endif
#if DEMO_STENCIL_STROKES
flags |= NVG_STENCIL_STROKES;
#endif
NVGcontext *vg = nvgCreateVk(&create_info, flags, executionQueue);
DemoData data;
PerfGraph fps; //, cpuGraph, gpuGraph;
if (loadDemoData(vg, &data) == -1)
return -1;
initGraph(&fps, GRAPH_RENDER_FPS, "Frame Time");
// initGraph(&cpuGraph, GRAPH_RENDER_MS, "CPU Time");
// initGraph(&gpuGraph, GRAPH_RENDER_MS, "GPU Time");
double prevt = glfwGetTime();
while (!glfwWindowShouldClose(window)) {
float pxRatio;
double mx, my, t, dt;
int cwinWidth, cwinHeight;
glfwGetWindowSize(window, &cwinWidth, &cwinHeight);
if ((resize_event) || (winWidth != cwinWidth || winHeight != cwinHeight)) {
winWidth = cwinWidth;
winHeight = cwinHeight;
res = vkQueueWaitIdle(executionQueue);
assert(res == VK_SUCCESS);
/* NanoVG pipelines are bound to the old render pass. Recreate the context.
Demo images are NanoVG-owned (nvgCreateImage). For app-owned VkImages,
keep them and re-register with nvgCreateImageFromHandleVk instead of loadDemoData. */
freeDemoData(vg, &data);
nvgDeleteVk(vg);
vg = NULL;
destroyFrameBuffers(device, &fb, executionQueue);
fb = createFrameBuffers(device, surface, executionQueue, winWidth, winHeight, 0);
create_info.renderpass = fb.render_pass;
create_info.cmdBuffer = cmd_buffer;
create_info.swapchainImageCount = fb.swapchain_image_count;
create_info.frameCount = fb.swapchain_image_count;
create_info.currentFrame = &fb.current_frame;
create_info.commandPool = device->commandPool;
create_info.graphicsQueueFamilyIndex = device->graphicsQueueFamilyIndex;
vg = nvgCreateVk(&create_info, flags, executionQueue);
if (loadDemoData(vg, &data) == -1)
return -1;
resize_event = false;
} else {
prepareFrame(device->device, cmd_buffer[fb.current_frame], &fb);
if (resize_event)
continue;
t = glfwGetTime();
dt = t - prevt;
prevt = t;
updateGraph(&fps, (float) dt);
pxRatio = (float) fb.buffer_size.width / (float) winWidth;
glfwGetCursorPos(window, &mx, &my);
nvgBeginFrame(vg, (float) winWidth, (float) winHeight, pxRatio);
renderDemo(vg, mx, my, (float) winWidth, (float) winHeight, t, blowup, &data);
renderGraph(vg, 5, 5, &fps);
nvgEndFrame(vg);
submitFrame(device->device, executionQueue, presentQueue, cmd_buffer[fb.current_frame], &fb);
}
glfwPollEvents();
}
vkQueueWaitIdle(executionQueue);
vkQueueWaitIdle(presentQueue);
freeDemoData(vg, &data);
nvgDeleteVk(vg);
destroyFrameBuffers(device, &fb, executionQueue);
destroyVulkanDevice(device);
destroyDebugCallback(instance);
vkDestroyInstance(instance, NULL);
glfwDestroyWindow(window);
free(cmd_buffer);
printf("Average Frame Time: %.2f ms\n", getGraphAverage(&fps) * 1000.0f);
// printf(" CPU Time: %.2f ms\n", getGraphAverage(&cpuGraph) * 1000.0f);
// printf(" GPU Time: %.2f ms\n", getGraphAverage(&gpuGraph) * 1000.0f);
glfwTerminate();
return 0;
}