Create basic GPU fractal renderer
This commit is contained in:
commit
b13d307754
5
build.sh
Executable file
5
build.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
glslc -fshader-stage=vert vert_shader.glsl -o vert_shader.spv
|
||||||
|
glslc -fshader-stage=frag frag_shader.glsl -o frag_shader.spv
|
||||||
|
clang -std=c11 -pedantic -Wall -Wextra -lglfw -lvulkan main.c -o gpu-fractal
|
53
frag_shader.glsl
Normal file
53
frag_shader.glsl
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
#define MAXITER 1000
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 out_colour;
|
||||||
|
|
||||||
|
layout(push_constant) uniform Constants {
|
||||||
|
vec2 img_size;
|
||||||
|
vec2 shift;
|
||||||
|
float zoom;
|
||||||
|
} params;
|
||||||
|
|
||||||
|
vec2 ss_offsets[4] = vec2[](
|
||||||
|
vec2(-0.25, -0.25),
|
||||||
|
vec2( 0.25, -0.25),
|
||||||
|
vec2(-0.25, 0.25),
|
||||||
|
vec2( 0.25, 0.25)
|
||||||
|
);
|
||||||
|
|
||||||
|
vec2 maptoz(vec2 xy)
|
||||||
|
{
|
||||||
|
return params.zoom * ((xy / params.img_size.xy) * 4.0 - 2.0) + params.shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
float mandelbrot(vec2 c) {
|
||||||
|
vec2 z = vec2(0.0, 0.0);
|
||||||
|
for (int i = 0; i < MAXITER; ++i) {
|
||||||
|
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
|
||||||
|
if (length(z) > 2.0)
|
||||||
|
return float(i) / float(MAXITER);
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 colour(float i) {
|
||||||
|
return vec3(i, i * i, i * i * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec2 c;
|
||||||
|
vec3 col = vec3(0.0, 0.0, 0.0);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
c = maptoz(gl_FragCoord.xy + ss_offsets[i]);
|
||||||
|
col += colour(mandelbrot(c));
|
||||||
|
}
|
||||||
|
out_colour = vec4(col, 1.0);
|
||||||
|
}
|
580
main.c
Normal file
580
main.c
Normal file
@ -0,0 +1,580 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define GLFW_INCLUDE_VULKAN
|
||||||
|
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
#define CLAMP(x, min, max) MAX((min), MIN(x, (max)))
|
||||||
|
|
||||||
|
#define QUEUE_FAMILY_PROP_BUFFER_SIZE 16
|
||||||
|
#define EXT_PROP_BUFFER_SIZE 256
|
||||||
|
#define SURFACE_FMT_BUFFER_SIZE 64
|
||||||
|
#define PRESENT_MODE_BUFFER_SIZE 8
|
||||||
|
#define SWAPCHAIN_IMG_BUFFER_SIZE 4
|
||||||
|
|
||||||
|
#define MAX_SHADER_SIZE (4 * 1024)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float width, height;
|
||||||
|
struct {
|
||||||
|
float x, y;
|
||||||
|
} shift;
|
||||||
|
float zoom;
|
||||||
|
} params_t;
|
||||||
|
|
||||||
|
static const char *layers[] = { "VK_LAYER_KHRONOS_validation" };
|
||||||
|
static const char *swapchain_ext_name = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
|
||||||
|
|
||||||
|
static VkShaderModule load_shader_module(VkDevice dev, const char *path)
|
||||||
|
{
|
||||||
|
static char buf[MAX_SHADER_SIZE]
|
||||||
|
__attribute__((aligned(__alignof__(uint32_t))));
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
const int size = fread(buf, 1, MAX_SHADER_SIZE, f);
|
||||||
|
assert(size > 0 && feof(f));
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
const VkShaderModuleCreateInfo create_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||||
|
.codeSize = size,
|
||||||
|
.pCode = (uint32_t *)buf,
|
||||||
|
};
|
||||||
|
VkShaderModule mod;
|
||||||
|
const VkResult result
|
||||||
|
= vkCreateShaderModule(dev, &create_info, NULL, &mod);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
// Set up window and surface
|
||||||
|
glfwInit();
|
||||||
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||||
|
GLFWwindow *window
|
||||||
|
= glfwCreateWindow(900, 900, "GPU Fractal", NULL, NULL);
|
||||||
|
assert(window);
|
||||||
|
|
||||||
|
// Initialise Vulkan instance.
|
||||||
|
const VkApplicationInfo app_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||||
|
.pApplicationName = "Vulkan Test",
|
||||||
|
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
|
||||||
|
.pEngineName = "No Engine",
|
||||||
|
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
|
||||||
|
.apiVersion = VK_API_VERSION_1_0,
|
||||||
|
};
|
||||||
|
uint32_t glfw_ext_count = 0;
|
||||||
|
const char **glfw_ext_names
|
||||||
|
= glfwGetRequiredInstanceExtensions(&glfw_ext_count);
|
||||||
|
const VkInstanceCreateInfo inst_create_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||||
|
.pApplicationInfo = &app_info,
|
||||||
|
.enabledExtensionCount = glfw_ext_count,
|
||||||
|
.ppEnabledExtensionNames = glfw_ext_names,
|
||||||
|
.enabledLayerCount = sizeof(layers) / sizeof(layers[0]),
|
||||||
|
.ppEnabledLayerNames = layers,
|
||||||
|
};
|
||||||
|
VkInstance inst;
|
||||||
|
VkResult result = vkCreateInstance(&inst_create_info, NULL, &inst);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Create surface
|
||||||
|
VkSurfaceKHR surface;
|
||||||
|
result = glfwCreateWindowSurface(inst, window, NULL, &surface);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Select a physical device
|
||||||
|
VkPhysicalDevice physical_dev;
|
||||||
|
uint32_t dev_count = 1;
|
||||||
|
result = vkEnumeratePhysicalDevices(inst, &dev_count, &physical_dev);
|
||||||
|
assert(result == VK_SUCCESS || result == VK_INCOMPLETE);
|
||||||
|
|
||||||
|
// Select queue family
|
||||||
|
uint32_t queue_family_count = QUEUE_FAMILY_PROP_BUFFER_SIZE;
|
||||||
|
VkQueueFamilyProperties
|
||||||
|
queue_family_props[QUEUE_FAMILY_PROP_BUFFER_SIZE];
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(
|
||||||
|
physical_dev, &queue_family_count, queue_family_props);
|
||||||
|
uint32_t queue_family;
|
||||||
|
bool found_queue_family = false;
|
||||||
|
for (unsigned i = 0; i < queue_family_count; ++i) {
|
||||||
|
if (queue_family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||||
|
VkBool32 surface_support = false;
|
||||||
|
vkGetPhysicalDeviceSurfaceSupportKHR(
|
||||||
|
physical_dev, i, surface, &surface_support);
|
||||||
|
if (surface_support) {
|
||||||
|
found_queue_family = true;
|
||||||
|
queue_family = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found_queue_family);
|
||||||
|
|
||||||
|
// Create the logical device and get the queue handle.
|
||||||
|
float queue_priority = 1.0;
|
||||||
|
const VkDeviceQueueCreateInfo queue_create_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||||
|
.queueFamilyIndex = queue_family,
|
||||||
|
.queueCount = 1,
|
||||||
|
.pQueuePriorities = &queue_priority,
|
||||||
|
};
|
||||||
|
const VkDeviceCreateInfo dev_create_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||||
|
.pQueueCreateInfos = &queue_create_info,
|
||||||
|
.queueCreateInfoCount = 1,
|
||||||
|
.enabledExtensionCount = 1,
|
||||||
|
.ppEnabledExtensionNames = &swapchain_ext_name,
|
||||||
|
};
|
||||||
|
VkDevice dev;
|
||||||
|
result = vkCreateDevice(physical_dev, &dev_create_info, NULL, &dev);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
VkQueue queue;
|
||||||
|
vkGetDeviceQueue(dev, queue_family, 0, &queue);
|
||||||
|
|
||||||
|
// Check that the physical device has swap chain support
|
||||||
|
uint32_t ext_count = EXT_PROP_BUFFER_SIZE;
|
||||||
|
VkExtensionProperties ext_props[EXT_PROP_BUFFER_SIZE];
|
||||||
|
result = vkEnumerateDeviceExtensionProperties(
|
||||||
|
physical_dev, NULL, &ext_count, ext_props);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
bool swapchain_support = false;
|
||||||
|
for (unsigned i = 0; i < ext_count; ++i) {
|
||||||
|
if (strcmp(swapchain_ext_name, ext_props[i].extensionName) == 0) {
|
||||||
|
swapchain_support = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(swapchain_support);
|
||||||
|
|
||||||
|
// Select a surface format, preferring R8G8B8A8_SRGB with
|
||||||
|
// SRGB_NONLINEAR colour space.
|
||||||
|
uint32_t surface_fmt_count = SURFACE_FMT_BUFFER_SIZE;
|
||||||
|
VkSurfaceFormatKHR surface_fmts[SURFACE_FMT_BUFFER_SIZE];
|
||||||
|
result = vkGetPhysicalDeviceSurfaceFormatsKHR(
|
||||||
|
physical_dev, surface, &surface_fmt_count, surface_fmts);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
assert(surface_fmt_count > 0);
|
||||||
|
VkSurfaceFormatKHR surface_fmt = surface_fmts[0];
|
||||||
|
for (unsigned i = 0; i < surface_fmt_count; ++i) {
|
||||||
|
if (surface_fmts[i].format == VK_FORMAT_R8G8B8A8_SRGB
|
||||||
|
&& surface_fmts[i].colorSpace
|
||||||
|
== VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||||
|
surface_fmt = surface_fmts[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query surface capabilities
|
||||||
|
VkSurfaceCapabilitiesKHR surface_caps;
|
||||||
|
result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
|
||||||
|
physical_dev, surface, &surface_caps);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Select swap chain extent
|
||||||
|
VkExtent2D swapchain_extent;
|
||||||
|
if (surface_caps.currentExtent.width == UINT32_MAX) {
|
||||||
|
int width, height;
|
||||||
|
glfwGetFramebufferSize(window, &width, &height);
|
||||||
|
swapchain_extent.width = CLAMP(
|
||||||
|
(uint32_t)width, surface_caps.minImageExtent.width,
|
||||||
|
surface_caps.maxImageExtent.width);
|
||||||
|
swapchain_extent.height = CLAMP(
|
||||||
|
(uint32_t)height, surface_caps.minImageExtent.height,
|
||||||
|
surface_caps.maxImageExtent.height);
|
||||||
|
} else {
|
||||||
|
swapchain_extent = surface_caps.currentExtent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select image count
|
||||||
|
uint32_t swapchain_img_count = surface_caps.minImageCount + 1;
|
||||||
|
assert(
|
||||||
|
surface_caps.maxImageCount == 0
|
||||||
|
|| swapchain_img_count <= surface_caps.maxImageCount);
|
||||||
|
|
||||||
|
// Create the swap chain
|
||||||
|
const VkSwapchainCreateInfoKHR swapchain_create_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||||
|
.surface = surface,
|
||||||
|
.minImageCount = swapchain_img_count,
|
||||||
|
.imageFormat = surface_fmt.format,
|
||||||
|
.imageColorSpace = surface_fmt.colorSpace,
|
||||||
|
.imageExtent = swapchain_extent,
|
||||||
|
.imageArrayLayers = 1,
|
||||||
|
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||||
|
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
|
.preTransform = surface_caps.currentTransform,
|
||||||
|
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
||||||
|
.presentMode = VK_PRESENT_MODE_FIFO_KHR,
|
||||||
|
.clipped = VK_TRUE,
|
||||||
|
.oldSwapchain = VK_NULL_HANDLE,
|
||||||
|
};
|
||||||
|
VkSwapchainKHR swapchain;
|
||||||
|
result = vkCreateSwapchainKHR(
|
||||||
|
dev, &swapchain_create_info, NULL, &swapchain);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Retreive swapchain images
|
||||||
|
VkImage swapchain_imgs[SWAPCHAIN_IMG_BUFFER_SIZE];
|
||||||
|
swapchain_img_count = SWAPCHAIN_IMG_BUFFER_SIZE;
|
||||||
|
result = vkGetSwapchainImagesKHR(
|
||||||
|
dev, swapchain, &swapchain_img_count, swapchain_imgs);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Create swapchain image views
|
||||||
|
VkImageView swapchain_img_views[SWAPCHAIN_IMG_BUFFER_SIZE];
|
||||||
|
VkImageViewCreateInfo img_view_create_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
|
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||||
|
.format = surface_fmt.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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (unsigned i = 0; i < swapchain_img_count; ++i) {
|
||||||
|
img_view_create_info.image = swapchain_imgs[i];
|
||||||
|
result = vkCreateImageView(
|
||||||
|
dev, &img_view_create_info, NULL, swapchain_img_views + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load vertex and fragment shader modules
|
||||||
|
VkShaderModule vert_shader = load_shader_module(dev, "vert_shader.spv");
|
||||||
|
VkShaderModule frag_shader = load_shader_module(dev, "frag_shader.spv");
|
||||||
|
const VkPipelineShaderStageCreateInfo shader_stages[] = {
|
||||||
|
[0] = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
||||||
|
.module = vert_shader,
|
||||||
|
.pName = "main",
|
||||||
|
},
|
||||||
|
[1] = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||||
|
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.module = frag_shader,
|
||||||
|
.pName = "main",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up input and viewport configuration
|
||||||
|
const VkPipelineVertexInputStateCreateInfo vertex_input_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||||
|
};
|
||||||
|
const VkPipelineInputAssemblyStateCreateInfo input_assembly_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||||
|
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
|
||||||
|
};
|
||||||
|
const VkViewport viewport = {
|
||||||
|
.width = (float)swapchain_extent.width,
|
||||||
|
.height = (float)swapchain_extent.height,
|
||||||
|
};
|
||||||
|
const VkRect2D scissor = { .extent = swapchain_extent };
|
||||||
|
VkPipelineViewportStateCreateInfo viewport_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||||
|
.viewportCount = 1,
|
||||||
|
.pViewports = &viewport,
|
||||||
|
.scissorCount = 1,
|
||||||
|
.pScissors = &scissor,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populate rasterizer configuration
|
||||||
|
const VkPipelineRasterizationStateCreateInfo rasterizer_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||||
|
.depthClampEnable = VK_FALSE,
|
||||||
|
.rasterizerDiscardEnable = VK_FALSE,
|
||||||
|
.polygonMode = VK_POLYGON_MODE_FILL,
|
||||||
|
.lineWidth = 1.0f,
|
||||||
|
.cullMode = VK_CULL_MODE_NONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populate multisampling configuration
|
||||||
|
const VkPipelineMultisampleStateCreateInfo multisampling_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||||
|
.sampleShadingEnable = VK_FALSE,
|
||||||
|
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up colour blending configuration
|
||||||
|
const VkPipelineColorBlendAttachmentState colour_blend_attatchment = {
|
||||||
|
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
|
||||||
|
| VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||||
|
.blendEnable = VK_FALSE,
|
||||||
|
};
|
||||||
|
const VkPipelineColorBlendStateCreateInfo colour_blend_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||||
|
.logicOpEnable = VK_FALSE,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = &colour_blend_attatchment,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create pipeline layout
|
||||||
|
const VkPushConstantRange push_constant_range = {
|
||||||
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
.offset = 0,
|
||||||
|
.size = sizeof(params_t),
|
||||||
|
};
|
||||||
|
const VkPipelineLayoutCreateInfo pipeline_layout_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||||
|
.pushConstantRangeCount = 1,
|
||||||
|
.pPushConstantRanges = &push_constant_range,
|
||||||
|
};
|
||||||
|
VkPipelineLayout pipeline_layout;
|
||||||
|
result = vkCreatePipelineLayout(
|
||||||
|
dev, &pipeline_layout_config, NULL, &pipeline_layout);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Set up colour attachment
|
||||||
|
const VkAttachmentDescription colour_attachment = {
|
||||||
|
.format = surface_fmt.format,
|
||||||
|
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||||
|
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||||
|
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
|
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
|
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||||
|
};
|
||||||
|
const VkAttachmentReference colour_attachment_ref = {
|
||||||
|
.attachment = 0,
|
||||||
|
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||||
|
};
|
||||||
|
const VkSubpassDescription subpass = {
|
||||||
|
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
|
.colorAttachmentCount = 1,
|
||||||
|
.pColorAttachments = &colour_attachment_ref,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create render pass
|
||||||
|
const VkSubpassDependency dependency = {
|
||||||
|
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||||
|
.dstSubpass = 0,
|
||||||
|
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
|
.srcAccessMask = 0,
|
||||||
|
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||||
|
};
|
||||||
|
const VkRenderPassCreateInfo render_pass_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = &colour_attachment,
|
||||||
|
.subpassCount = 1,
|
||||||
|
.pSubpasses = &subpass,
|
||||||
|
.dependencyCount = 1,
|
||||||
|
.pDependencies = &dependency,
|
||||||
|
};
|
||||||
|
VkRenderPass render_pass;
|
||||||
|
result
|
||||||
|
= vkCreateRenderPass(dev, &render_pass_config, NULL, &render_pass);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Create pipeline
|
||||||
|
const VkGraphicsPipelineCreateInfo pipeline_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||||
|
.stageCount = 2,
|
||||||
|
.pStages = shader_stages,
|
||||||
|
.pVertexInputState = &vertex_input_config,
|
||||||
|
.pInputAssemblyState = &input_assembly_config,
|
||||||
|
.pViewportState = &viewport_config,
|
||||||
|
.pRasterizationState = &rasterizer_config,
|
||||||
|
.pMultisampleState = &multisampling_config,
|
||||||
|
.pColorBlendState = &colour_blend_config,
|
||||||
|
.layout = pipeline_layout,
|
||||||
|
.renderPass = render_pass,
|
||||||
|
.subpass = 0,
|
||||||
|
};
|
||||||
|
VkPipeline pipeline;
|
||||||
|
result = vkCreateGraphicsPipelines(
|
||||||
|
dev, VK_NULL_HANDLE, 1, &pipeline_config, NULL, &pipeline);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Create framebuffers
|
||||||
|
VkFramebuffer framebuffers[SWAPCHAIN_IMG_BUFFER_SIZE];
|
||||||
|
for (unsigned i = 0; i < swapchain_img_count; ++i) {
|
||||||
|
const VkFramebufferCreateInfo framebuffer_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||||
|
.renderPass = render_pass,
|
||||||
|
.attachmentCount = 1,
|
||||||
|
.pAttachments = swapchain_img_views + i,
|
||||||
|
.width = swapchain_extent.width,
|
||||||
|
.height = swapchain_extent.height,
|
||||||
|
.layers = 1,
|
||||||
|
};
|
||||||
|
result = vkCreateFramebuffer(
|
||||||
|
dev, &framebuffer_config, NULL, framebuffers + i);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create command pool
|
||||||
|
const VkCommandPoolCreateInfo cmd_pool_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||||
|
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||||
|
.queueFamilyIndex = queue_family,
|
||||||
|
};
|
||||||
|
VkCommandPool cmd_pool;
|
||||||
|
result = vkCreateCommandPool(dev, &cmd_pool_config, NULL, &cmd_pool);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Allocate command buffer
|
||||||
|
const VkCommandBufferAllocateInfo cmd_buf_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||||
|
.commandPool = cmd_pool,
|
||||||
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||||
|
.commandBufferCount = 1,
|
||||||
|
};
|
||||||
|
VkCommandBuffer cmd_buf;
|
||||||
|
result = vkAllocateCommandBuffers(dev, &cmd_buf_config, &cmd_buf);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Create syncronisation primitives
|
||||||
|
VkSemaphore img_avail, render_fin;
|
||||||
|
VkSemaphoreCreateInfo semaphore_config
|
||||||
|
= { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
|
||||||
|
result = vkCreateSemaphore(dev, &semaphore_config, NULL, &img_avail);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
result = vkCreateSemaphore(dev, &semaphore_config, NULL, &render_fin);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
VkFence in_flight;
|
||||||
|
VkFenceCreateInfo fence_config = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||||
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||||
|
};
|
||||||
|
result = vkCreateFence(dev, &fence_config, NULL, &in_flight);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
params_t params = {
|
||||||
|
.width = (float)swapchain_extent.width,
|
||||||
|
.height = (float)swapchain_extent.height,
|
||||||
|
.shift = {
|
||||||
|
.x = -0.743643887037158704752191506114774,
|
||||||
|
.y = 0.131825904205311970493132056385139,
|
||||||
|
},
|
||||||
|
.zoom = 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
while (!glfwWindowShouldClose(window)) {
|
||||||
|
glfwPollEvents();
|
||||||
|
|
||||||
|
// Wait for previous frame to finish
|
||||||
|
vkWaitForFences(dev, 1, &in_flight, VK_TRUE, UINT64_MAX);
|
||||||
|
vkResetFences(dev, 1, &in_flight);
|
||||||
|
|
||||||
|
// Aquire image from swapchain
|
||||||
|
uint32_t img_index;
|
||||||
|
result = vkAcquireNextImageKHR(
|
||||||
|
dev, swapchain, UINT64_MAX, img_avail, VK_NULL_HANDLE,
|
||||||
|
&img_index);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Reset command buffer, then begin writing
|
||||||
|
vkResetCommandBuffer(cmd_buf, 0);
|
||||||
|
const VkCommandBufferBeginInfo cmd_begin = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
|
};
|
||||||
|
result = vkBeginCommandBuffer(cmd_buf, &cmd_begin);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Start render pass
|
||||||
|
VkClearValue clear_value = { { { 0.0, 0.0, 0.0, 1.0 } } };
|
||||||
|
VkRenderPassBeginInfo render_pass_begin = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
|
.renderPass = render_pass,
|
||||||
|
.framebuffer = framebuffers[img_index],
|
||||||
|
.renderArea.extent = swapchain_extent,
|
||||||
|
.clearValueCount = 1,
|
||||||
|
.pClearValues = &clear_value,
|
||||||
|
};
|
||||||
|
vkCmdBeginRenderPass(
|
||||||
|
cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
vkCmdPushConstants(
|
||||||
|
cmd_buf, pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0,
|
||||||
|
sizeof(params), ¶ms);
|
||||||
|
|
||||||
|
// Bind graphics pipeline and draw
|
||||||
|
vkCmdBindPipeline(
|
||||||
|
cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
|
vkCmdDraw(cmd_buf, 4, 1, 0, 0);
|
||||||
|
|
||||||
|
// Finish render pass and end writing to command buffer
|
||||||
|
vkCmdEndRenderPass(cmd_buf);
|
||||||
|
result = vkEndCommandBuffer(cmd_buf);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Submit command buffer
|
||||||
|
VkPipelineStageFlags wait_stage
|
||||||
|
= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
const VkSubmitInfo submit = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
.pWaitSemaphores = &img_avail,
|
||||||
|
.pWaitDstStageMask = &wait_stage,
|
||||||
|
.commandBufferCount = 1,
|
||||||
|
.pCommandBuffers = &cmd_buf,
|
||||||
|
.signalSemaphoreCount = 1,
|
||||||
|
.pSignalSemaphores = &render_fin,
|
||||||
|
};
|
||||||
|
result = vkQueueSubmit(queue, 1, &submit, in_flight);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Present frame
|
||||||
|
const VkPresentInfoKHR present_info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||||
|
.waitSemaphoreCount = 1,
|
||||||
|
.pWaitSemaphores = &render_fin,
|
||||||
|
.swapchainCount = 1,
|
||||||
|
.pSwapchains = &swapchain,
|
||||||
|
.pImageIndices = &img_index,
|
||||||
|
};
|
||||||
|
result = vkQueuePresentKHR(queue, &present_info);
|
||||||
|
assert(result == VK_SUCCESS);
|
||||||
|
|
||||||
|
// Increment zoom
|
||||||
|
params.zoom *= 0.99;
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDeviceWaitIdle(dev);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cleanup
|
||||||
|
*/
|
||||||
|
|
||||||
|
vkDestroySemaphore(dev, img_avail, NULL);
|
||||||
|
vkDestroySemaphore(dev, render_fin, NULL);
|
||||||
|
vkDestroyFence(dev, in_flight, NULL);
|
||||||
|
vkDestroyCommandPool(dev, cmd_pool, NULL);
|
||||||
|
vkDestroyPipeline(dev, pipeline, NULL);
|
||||||
|
vkDestroyRenderPass(dev, render_pass, NULL);
|
||||||
|
vkDestroyPipelineLayout(dev, pipeline_layout, NULL);
|
||||||
|
vkDestroyShaderModule(dev, vert_shader, NULL);
|
||||||
|
vkDestroyShaderModule(dev, frag_shader, NULL);
|
||||||
|
for (unsigned i = 0; i < swapchain_img_count; ++i) {
|
||||||
|
vkDestroyFramebuffer(dev, framebuffers[i], NULL);
|
||||||
|
vkDestroyImageView(dev, swapchain_img_views[i], NULL);
|
||||||
|
}
|
||||||
|
vkDestroySwapchainKHR(dev, swapchain, NULL);
|
||||||
|
vkDestroyDevice(dev, NULL);
|
||||||
|
vkDestroySurfaceKHR(inst, surface, NULL);
|
||||||
|
vkDestroyInstance(inst, NULL);
|
||||||
|
glfwDestroyWindow(window);
|
||||||
|
glfwTerminate();
|
||||||
|
}
|
17
vert_shader.glsl
Normal file
17
vert_shader.glsl
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
vec2 positions[4] = vec2[](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 1.0, -1.0),
|
||||||
|
vec2(-1.0, 1.0),
|
||||||
|
vec2( 1.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user