diff --git a/meson.build b/meson.build index 810c19b..9838cec 100644 --- a/meson.build +++ b/meson.build @@ -2,4 +2,19 @@ project('vulkan learning', 'c') deps = [dependency('glfw3'), dependency('vulkan'), dependency('dl'), dependency('threads'), dependency('X11'), dependency('xxf86vm'), dependency('xrandr'), dependency('xi'), dependency('cglm')] +glslc = find_program('glslc') + +shaders = ['src/vertex_simple.vert', 'src/fragment_simple.frag'] + +foreach shader : shaders + out = (shader.split('.')[-2] + '.spv').split('/')[-1] + + target = custom_target(out, + output : out, + input : shader, + command : [glslc, '@INPUT@', '-o', '@OUTPUT@'], + install : true, + install_dir : 'shaders') +endforeach + executable('triangle', 'src/main.c', dependencies: deps) diff --git a/src/fragment_simple.frag b/src/fragment_simple.frag new file mode 100644 index 0000000..7c5b0e7 --- /dev/null +++ b/src/fragment_simple.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/src/main.c b/src/main.c index d0c4530..5d6363b 100644 --- a/src/main.c +++ b/src/main.c @@ -53,6 +53,13 @@ typedef struct ApplicationState { VkExtent2D swapchain_extent; //describes how to access an image and allows access to it VkImageView* swapchain_image_views; + //shaders + VkShaderModule vertex_shader_module; + VkShaderModule fragment_shader_module; + VkRenderPass render_pass; + VkPipelineLayout pipeline_layout; + + VkPipeline graphics_pipeline; } ApplicationState; typedef struct QueueFamilyIndices { @@ -515,6 +522,267 @@ void create_image_views(ApplicationState* state) { printf("created image views\n"); } +char* load_file(const char* name, size_t* size) { + FILE *fileptr; + char *buffer; + size_t filelen; + + fileptr = fopen(name, "rb"); + fseek(fileptr, 0, SEEK_END); + filelen = ftell(fileptr); + rewind(fileptr); + + buffer = (char*)malloc(filelen * sizeof(char)); + fread(buffer, filelen, 1, fileptr); + fclose(fileptr); + + *size = filelen; + + return buffer; +} + +VkShaderModule create_shader_module(ApplicationState* state, const char* code, size_t size) { + VkShaderModuleCreateInfo create_info = {0}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = size; + create_info.pCode = (uint32_t*)code; + + VkShaderModule ret; + + if (vkCreateShaderModule(state->device, &create_info, NULL, &ret) != VK_SUCCESS) { + printf("shader creation failed\n"); + exit(1); + } + + return ret; +} + +void create_render_pass(ApplicationState* state) { + VkAttachmentDescription color_attachment = {0}; + color_attachment.format = state->swapchain_image_format; + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + //clear the attachment to a default color that we set + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + //actually store rendered content to the framebuffer + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + //we don't use the stencil buffer + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + + //we are clearing the attachment before rendering, so we don't care about the first format + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + //images to be presented + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + //there may be multiple subpasses, subpasses depend on the previous framebuffer contents + //they can be used for things sych as postprocessing + //every subpass references one or more attachment + VkAttachmentReference color_attachment_reference = {0}; + color_attachment_reference.attachment = 0; + color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + //now we create the subpass + //layout(location = 0) out vec4 outColor; + VkSubpassDescription subpass = {0}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_reference; + + //let's create the render pass + VkRenderPassCreateInfo render_pass_info = {0}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.attachmentCount = 1; + render_pass_info.pAttachments = &color_attachment; + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + + if (vkCreateRenderPass(state->device, &render_pass_info, NULL, &state->render_pass) != VK_SUCCESS) { + printf("failed to create render pass"); + exit(1); + } + + printf("created render pass\n"); +} + +//now we need to setup a graphics pipeline +//the usual stages of a pipeline are: +// +//input assembler: gets data from buffers (can have an index buffer) +//vertex shader : runs for every vertex and applies transformations, also generates per-vertex data +//tesselation : does geoemtry subdivision +//geometry shader: runs on every primitive and is able to discard it or generate more +//rasterization : turns primitives into fragments, which are pixel elements, fragments outside the screen are discarded +// and attributes are interpolated +//fragment shader: runs on every fragment, determines which framebuffer it's written to and the color and depth values +//color blending : applies operations to mix different fragments that map to the same pixel + +void create_graphics_pipeline(ApplicationState* state) { + size_t vertex_shader_size; + size_t fragment_shader_size; + char* vertex_shader = load_file("./vertex_simple.spv", &vertex_shader_size); + char* fragment_shader = load_file("./fragment_simple.spv", &fragment_shader_size); + + state->vertex_shader_module = create_shader_module(state, vertex_shader, vertex_shader_size); + state->fragment_shader_module = create_shader_module(state, fragment_shader, fragment_shader_size); + + VkPipelineShaderStageCreateInfo vertex_shader_stage_info = {0}; + vertex_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertex_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertex_shader_stage_info.module = state->vertex_shader_module; + vertex_shader_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo fragment_shader_stage_info = {0}; + fragment_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragment_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragment_shader_stage_info.module = state->fragment_shader_module; + fragment_shader_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo shader_stages[] = {vertex_shader_stage_info, fragment_shader_stage_info}; + + //now we setup the fixed function parts of the pipeline + //despite what is said in the notes, *some* state can actually be changed dynamically + //it is aptly named dynamic state + + VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + + VkPipelineDynamicStateCreateInfo dynamic_state = {0}; + dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state.dynamicStateCount = ARRSIZE(dynamic_states); + dynamic_state.pDynamicStates = dynamic_states; + + //vertex input format + //bindings: spacing between data, what data is per-vertex and what data is per instance + //attribute types, which bindings to load them from and at which offset + VkPipelineVertexInputStateCreateInfo vertex_input_info = {0}; + vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.vertexBindingDescriptionCount = 0; + vertex_input_info.pVertexBindingDescriptions = NULL; + vertex_input_info.vertexAttributeDescriptionCount = 0; + vertex_input_info.pVertexAttributeDescriptions = NULL; + + //input assembly + //type of geometry and primitive restart, which allows one to break lines and triangles + //in strip topologies + VkPipelineInputAssemblyStateCreateInfo input_assembly = {0}; + input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + //viewports and scissors + VkViewport viewport = {0}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)state->swapchain_extent.width; + viewport.height = (float)state->swapchain_extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + //we want to draw all of the framebuffer + VkRect2D scissor = {0}; + scissor.offset.x = 0; + scissor.offset.y = 0; + scissor.extent = state->swapchain_extent; + + //we're using dynamic viewports and scissors + VkPipelineViewportStateCreateInfo viewport_state = {0}; + viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.viewportCount = 1; + viewport_state.scissorCount = 1; + + //rasterizer setup + VkPipelineRasterizationStateCreateInfo rasterizer = {0}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; // Optional + rasterizer.depthBiasClamp = 0.0f; // Optional + rasterizer.depthBiasSlopeFactor = 0.0f; // Optional + + //enable multisampling antialiasing + VkPipelineMultisampleStateCreateInfo multisampling = {0}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampling.minSampleShading = 1.0f; // Optional + multisampling.pSampleMask = NULL; // Optional + multisampling.alphaToCoverageEnable = VK_FALSE; // Optional + multisampling.alphaToOneEnable = VK_FALSE; // Optional + + //configure color blending + VkPipelineColorBlendAttachmentState color_blend_attachment = {0}; + color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + color_blend_attachment.blendEnable = VK_FALSE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional + color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional + + VkPipelineColorBlendStateCreateInfo color_blending = {0}; + color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.logicOpEnable = VK_FALSE; + color_blending.logicOp = VK_LOGIC_OP_COPY; // Optional + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + color_blending.blendConstants[0] = 0.0f; // Optional + color_blending.blendConstants[1] = 0.0f; // Optional + color_blending.blendConstants[2] = 0.0f; // Optional + color_blending.blendConstants[3] = 0.0f; // Optional + + //used for things such as uniforms + VkPipelineLayoutCreateInfo pipeline_layout_create_info = {0}; + pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_create_info.setLayoutCount = 0; + pipeline_layout_create_info.pSetLayouts = NULL; + pipeline_layout_create_info.pushConstantRangeCount = 0; + pipeline_layout_create_info.pPushConstantRanges = NULL; + + if (vkCreatePipelineLayout(state->device, &pipeline_layout_create_info, NULL, &state->pipeline_layout) != VK_SUCCESS) { + printf("failed to create pipeline layout"); + exit(1); + } + + printf("created pipeline layout\n"); + + VkGraphicsPipelineCreateInfo pipeline_info = {0}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + //shaders + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + //fixed function parts + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pDepthStencilState = NULL; // Optional + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.pDynamicState = &dynamic_state; + + pipeline_info.layout = state->pipeline_layout; + pipeline_info.renderPass = state->render_pass; + pipeline_info.subpass = 0; + + //for basing pipelines on others ones + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; + pipeline_info.basePipelineIndex = -1; + + if (vkCreateGraphicsPipelines(state->device, VK_NULL_HANDLE, 1, &pipeline_info, NULL, &state->graphics_pipeline) != VK_SUCCESS) { + printf("failed to create graphics pipeline\n"); + exit(1); + } + printf("created graphics pipeline\n"); +} + void init_vulkan(ApplicationState* state) { create_instance(state); create_surface(state); @@ -522,6 +790,8 @@ void init_vulkan(ApplicationState* state) { create_logical_device(state); create_swap_chain(state); create_image_views(state); + create_render_pass(state); + create_graphics_pipeline(state); } void main_loop(ApplicationState* state) { @@ -531,6 +801,8 @@ void main_loop(ApplicationState* state) { } void terminate(ApplicationState* state) { + vkDestroyRenderPass(state->device, state->render_pass, NULL); + vkDestroyPipelineLayout(state->device, state->pipeline_layout, NULL); for (int i = 0; i < state->swapchain_image_count; i++) { vkDestroyImageView(state->device, state->swapchain_image_views[i], NULL); } diff --git a/src/vertex_simple.vert b/src/vertex_simple.vert new file mode 100644 index 0000000..f5b2f8d --- /dev/null +++ b/src/vertex_simple.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +}