--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = triangle.vert triangle.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_cube
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkRenderPass renderPass;
+VkFramebuffer* framebuffers;
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+GLFWwindow* window;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[2];
+ float color[3];
+} Vertex;
+
+typedef enum {
+ AXIS_YZ, // rotate around X axis
+ AXIS_XZ, // rotate around Y axis
+ AXIS_XY // rotate around Z axis
+} Axis;
+
+uint32_t vertexCount = 0;
+typedef struct _Vec3 { float x; float y; float z;} Vec3;
+typedef struct _Vec2 { float x; float y;} Vec2;
+
+Vec3 cube[] = {
+
+ // bottom
+ { 0.5f, 0.5f, -0.5f},
+ {-0.5f, 0.5f, -0.5f},
+
+ { 0.5f, 0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ { 0.5f, 0.5f, -0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ {-0.5f, 0.5f, -0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+
+ // top
+ { 0.5f, -0.5f, -0.5f},
+ {-0.5f, -0.5f, -0.5f},
+
+ { 0.5f, -0.5f, 0.5f},
+ {-0.5f, -0.5f, 0.5f},
+
+ { 0.5f, -0.5f, -0.5f},
+ { 0.5f, -0.5f, 0.5f},
+
+ {-0.5f, -0.5f, -0.5f},
+ {-0.5f, -0.5f, 0.5f},
+
+ // front
+ {-0.5f, -0.5f, -0.5f},
+ {-0.5f, 0.5f, -0.5f},
+
+ {0.5f, -0.5f, -0.5f},
+ {0.5f, 0.5f, -0.5f},
+
+ // back
+ {-0.5f, -0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ {0.5f, -0.5f, 0.5f},
+ {0.5f, 0.5f, 0.5f},
+};
+
+
+// vertices don't need a z value. you only can write z and y to screen we can
+// use the projecion formula to change this
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[16];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+ swapchainFormat = surf_fmt.format;
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount+1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace,
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ .clipped = VK_TRUE
+ };
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .components = {VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY},
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+/* --- Render pass --- */
+void create_render_pass() {
+ VkAttachmentDescription att = {
+ .format = swapchainFormat,
+ .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
+ };
+ VkAttachmentReference colorRef = {0,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
+ VkSubpassDescription subpass = {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorRef
+ };
+ VkRenderPassCreateInfo rpInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &att,
+ .subpassCount = 1,
+ .pSubpasses = &subpass
+ };
+ if(vkCreateRenderPass(device,&rpInfo,NULL,&renderPass)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create render pass\n"); exit(1);
+ }
+}
+
+/* --- Framebuffers --- */
+void create_framebuffers() {
+ framebuffers = malloc(sizeof(VkFramebuffer)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkFramebufferCreateInfo fbInfo = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = renderPass,
+ .attachmentCount = 1,
+ .pAttachments = &swapchainImageViews[i],
+ .width = swapchainExtent.width,
+ .height = swapchainExtent.height,
+ .layers = 1
+ };
+ if(vkCreateFramebuffer(device,&fbInfo,NULL,&framebuffers[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create framebuffer\n"); exit(1);
+ }
+ }
+}
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+Vec3 rotate(Vec3 v, Axis axis, float angle) {
+ float c = cosf(angle);
+ float s = sinf(angle);
+
+ switch (axis) {
+ case AXIS_YZ: // X-axis rotation
+ return (Vec3){
+ .x = v.x,
+ .y = v.y * c - v.z * s,
+ .z = v.y * s + v.z * c
+ };
+
+ case AXIS_XZ: // Y-axis rotation
+ return (Vec3){
+ .x = v.x * c - v.z * s,
+ .y = v.y,
+ .z = v.x * s + v.z * c
+ };
+
+ case AXIS_XY: // Z-axis rotation
+ return (Vec3){
+ .x = v.x * c - v.y * s,
+ .y = v.x * s + v.y * c,
+ .z = v.z
+ };
+ }
+
+ // Fallback (should never happen)
+ return v;
+}
+
+Vec2 project(Vec3 mesh) {
+ return (Vec2) {
+ .x = mesh.x / (mesh.z + 2),
+ .y = mesh.y / (mesh.z + 2)
+ };
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+
+ // draw cube
+ vertexCount = SIZE(cube);
+ Vertex vertices[vertexCount];
+ float angle = M_PI*1.26;
+
+ for (int i = 0; i < vertexCount; ++i) {
+ Vec3 rotated = rotate(cube[i], AXIS_XZ, angle);
+ Vec2 projected = project(rotated);
+ vertices[i].pos[0] = projected.x;
+ vertices[i].pos[1] = projected.y;
+ vertices[i].color[0] = (1.0f / (float)(i + 1));
+ vertices[i].color[1] = (float)((float)i / (float)vertexCount);
+ vertices[i].color[2] = 0.0f;
+ }
+
+ // project(vertices, cube, vertexCount);
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = sizeof(Vertex) * vertexCount,
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+ if(vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create vertex buffer\n"); exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = 0
+ };
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+ for(uint32_t i=0;i<memProps.memoryTypeCount;i++){
+ if((memReq.memoryTypeBits & (1<<i)) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)){
+ allocInfo.memoryTypeIndex = i;
+ break;
+ }
+ }
+
+ if(vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate vertex buffer memory\n"); exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufInfo.size, 0, &data);
+ memcpy(data, vertices, bufInfo.size);
+ // memcpy(data, vertices, sizeof(Vertex) * vertexCount);
+ vkUnmapMemory(device, vertexBufferMemory);
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("triangle.vert.spv");
+ VkShaderModule fragModule = load_shader("triangle.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(Vertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+ // VkVertexInputAttributeDescription attrDesc[2] = {
+ // {0,0,VK_FORMAT_R32G32B32_SFLOAT,offsetof(Vertex,pos)},
+ // {1,0,VK_FORMAT_R32G32B32_SFLOAT,offsetof(Vertex,color)}
+ // };
+ VkVertexInputAttributeDescription attrDesc[2] = {
+ {0,0,VK_FORMAT_R32G32_SFLOAT,offsetof(Vertex,pos)}, // 2 components for pos
+ {1,0,VK_FORMAT_R32G32B32_SFLOAT,offsetof(Vertex,color)} // 3 components for color
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=2,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ /// topology is how to draw the mesh
+ // .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_BACK_BIT,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPipelineLayoutCreateInfo layoutInfo = { .sType=VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
+ vkCreatePipelineLayout(device,&layoutInfo,NULL,&pipelineLayout);
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = { .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = stages;
+ pipelineInfo.pVertexInputState = &vertexInput;
+ pipelineInfo.pInputAssemblyState = &inputAssembly;
+ pipelineInfo.pViewportState = &vpState;
+ pipelineInfo.pRasterizationState = &rasterizer;
+ pipelineInfo.pMultisampleState = &multisampling;
+ pipelineInfo.pColorBlendState = &colorBlending;
+ pipelineInfo.layout = pipelineLayout;
+ pipelineInfo.renderPass = renderPass;
+ pipelineInfo.subpass = 0;
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,&graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+/* --- Record command buffers --- */
+void record_command_buffers() {
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkCommandBufferBeginInfo begin={.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
+ vkBeginCommandBuffer(commandBuffers[i],&begin);
+
+ VkClearValue clearColor = {{0.0f,0.0f,0.0f,1.0f}};
+ VkRenderPassBeginInfo rpBegin = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = renderPass,
+ .framebuffer = framebuffers[i],
+ .renderArea = {.offset={0,0},.extent=swapchainExtent},
+ .clearValueCount = 1,
+ .pClearValues = &clearColor
+ };
+
+ vkCmdBeginRenderPass(commandBuffers[i],&rpBegin,VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(commandBuffers[i],VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline);
+
+ VkDeviceSize offsets[] = {0};
+ vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, &vertexBuffer, offsets);
+ vkCmdDraw(commandBuffers[i], vertexCount, 1, 0, 0);
+
+ vkCmdEndRenderPass(commandBuffers[i]);
+ vkEndCommandBuffer(commandBuffers[i]);
+ }
+}
+
+/* --- Draw frame --- */
+void draw_frame() {
+ uint32_t imgIndex;
+ vkAcquireNextImageKHR(device,swapchain,UINT64_MAX,VK_NULL_HANDLE,VK_NULL_HANDLE,&imgIndex);
+
+ VkSubmitInfo submit={.sType=VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount=1,
+ .pCommandBuffers=&commandBuffers[imgIndex]};
+ vkQueueSubmit(graphicsQueue,1,&submit,VK_NULL_HANDLE);
+ vkQueueWaitIdle(graphicsQueue);
+
+ VkPresentInfoKHR present={.sType=VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .swapchainCount=1,
+ .pSwapchains=&swapchain,
+ .pImageIndices=&imgIndex};
+ vkQueuePresentKHR(graphicsQueue,&present);
+}
+
+/* --- Main --- */
+int main(void) {
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+ uint32_t ext_count=0;
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ici,NULL,&instance);
+ volkLoadInstance(instance);
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8]; vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ physical=devices[0];
+
+ uint32_t qcount=0; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ VkQueueFamilyProperties qprops[16]; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres){graphicsQueueFamily=i;found=1;break;}
+ }
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ create_render_pass();
+ create_framebuffers();
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+ create_vertex_buffer();
+ record_command_buffers();
+
+ while(!glfwWindowShouldClose(window)){
+ glfwPollEvents();
+ draw_frame();
+ }
+
+ vkDeviceWaitIdle(device);
+
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+ for(uint32_t i=0;i<swapchainImageCount;i++) vkDestroyFramebuffer(device,framebuffers[i],NULL);
+ free(framebuffers); free(commandBuffers); free(swapchainImageViews); free(swapchainImages);
+ vkDestroyPipeline(device,graphicsPipeline,NULL);
+ vkDestroyPipelineLayout(device,pipelineLayout,NULL);
+ vkDestroyRenderPass(device,renderPass,NULL);
+ vkDestroySwapchainKHR(device,swapchain,NULL);
+ vkDestroyDevice(device,NULL);
+ vkDestroySurfaceKHR(instance,surface,NULL);
+ vkDestroyInstance(instance,NULL);
+ glfwDestroyWindow(window); glfwTerminate();
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+#version 450
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(fragColor, 1.0);
+}
--- /dev/null
+#version 450
+layout(location = 0) in vec2 inPos;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+void main() {
+ // flip Y in shader
+ gl_Position = vec4(inPos.x, inPos.y, 0.0, 1.0);
+ fragColor = inColor;
+}
+
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_cube
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+= Learning Vulkan in c
+
+Some simple example code for vulkan
+
+== Links
+* https://www.kdab.com/projection-matrices-with-vulkan-part-1/[projection matrices]
+* https://matthias-research.github.io/pages/tenMinutePhysics/index.html
+* https://youtu.be/bGe-d09Nc_M?si=BG0fisehhE5G8IB2
+* https://www.youtube.com/watch?v=bGe-d09Nc_M
+* https://www.youtube.com/watch?v=P6UKhR0T6cs
+* https://docs.vulkan.org/spec/latest/chapters/fundamentals.html
+* https://github.com/KhronosGroup/Vulkan-Guide?tab=readme-ov-file
+* https://vkguide.dev/docs/chapter-1/vulkan_init_flow/[learning basics]
--- /dev/null
+Great question — this gets to **one of the core ideas Vulkan is built on**, and it’s very different from OpenGL / D3D11.
+
+I’ll explain **what binding memory means**, **why Vulkan requires it**, and **what actually happens inside the driver/GPU**.
+
+---
+
+## The short answer (intuition first)
+
+> **Creating a buffer or image does *not* allocate memory.
+> Binding memory is what gives that object actual storage.**
+
+Until you bind memory:
+
+* A `VkBuffer` or `VkImage` is just a **description**
+* It has **no backing bytes**
+* The GPU cannot read or write it
+
+Binding memory:
+
+* Assigns a **physical region of GPU-accessible memory**
+* Tells Vulkan *where* the object’s data lives
+* Fixes alignment and offset requirements
+
+---
+
+## What Vulkan objects really are
+
+### 1️⃣ `VkBuffer` / `VkImage` (no memory yet)
+
+When you do:
+
+```c
+vkCreateBuffer(...)
+vkCreateImage(...)
+```
+
+You are creating:
+
+* A **metadata object**
+* Size, format, usage flags
+* No storage
+
+Think of it like:
+
+> “I want a 2D image, 512×512, RGBA, sampled and copied into”
+
+But **no memory is reserved yet**.
+
+---
+
+### 2️⃣ `VkDeviceMemory` (raw memory block)
+
+When you do:
+
+```c
+vkAllocateMemory(...)
+```
+
+You get:
+
+* A chunk of memory
+* From a specific **heap**
+* With specific properties (HOST_VISIBLE, DEVICE_LOCAL, etc.)
+
+This memory:
+
+* Is **untyped**
+* Has no idea if it will store vertices, textures, or uniforms
+
+It’s just **bytes**.
+
+---
+
+### 3️⃣ Binding connects the two
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+vkBindImageMemory(device, image, memory, offset);
+```
+
+This says:
+
+> “This buffer/image *lives* starting at this byte offset in that memory block.”
+
+After binding:
+
+* The buffer/image has **real storage**
+* GPU accesses to that object resolve to actual memory addresses
+* Alignment requirements are guaranteed
+
+---
+
+## What binding does internally
+
+When you bind memory, the driver:
+
+1. Verifies alignment and size requirements
+2. Computes GPU virtual addresses
+3. Programs internal page tables / mappings
+4. Associates resource metadata with physical memory
+
+On modern GPUs this involves:
+
+* IOMMU / GPU MMU
+* Virtual address translation
+* Cache policy setup
+
+📌 This is why Vulkan **cannot auto-bind** memory — it would hide too many important decisions.
+
+---
+
+## Why Vulkan separates creation and binding
+
+### 🔹 Explicit memory control
+
+Vulkan allows things like:
+
+* Sub-allocating **multiple buffers in one allocation**
+* Packing resources tightly
+* Choosing different heaps per resource
+* Avoiding fragmentation
+
+Example:
+
+```c
+VkDeviceMemory bigBlock;
+vkAllocateMemory(..., &bigBlock);
+
+vkBindBufferMemory(device, vertexBuffer, bigBlock, 0);
+vkBindBufferMemory(device, indexBuffer, bigBlock, 65536);
+vkBindImageMemory (device, textureImage, bigBlock, 131072);
+```
+
+This is **impossible** in OpenGL.
+
+---
+
+### 🔹 Performance predictability
+
+The driver:
+
+* Does **zero hidden allocations**
+* Does **no implicit migrations**
+* Never guesses where to put memory
+
+You choose:
+
+* Fast GPU memory
+* CPU-visible memory
+* BAR-mapped memory
+* Unified memory
+
+---
+
+### 🔹 Multi-heap GPUs
+
+GPUs often have:
+
+* VRAM
+* System RAM
+* BAR-mapped regions
+* Cached vs uncached heaps
+
+Binding ensures:
+
+* The resource lives in a compatible heap
+* Usage flags match memory properties
+
+---
+
+## What happens if you don’t bind?
+
+If you try to use an unbound resource:
+
+* Validation layers scream
+* Behavior is **undefined**
+* GPU may fault or crash
+
+Because:
+
+> The resource literally has no address.
+
+---
+
+## Mental model (very important)
+
+```
+VkBuffer / VkImage
+ ↓ describes usage & format
+VkDeviceMemory
+ ↓ raw storage
+vkBind*Memory
+ ↓ connects them
+GPU can now access bytes
+```
+
+---
+
+## Why other APIs hide this
+
+| API | Memory model |
+| ------ | --------------------------- |
+| OpenGL | Driver decides everything |
+| D3D11 | Driver allocates implicitly |
+| Vulkan | You control everything |
+| D3D12 | Same as Vulkan |
+
+Vulkan trades **verbosity** for:
+
+* Zero surprises
+* Maximum performance
+* Precise memory control
+
+---
+
+## Key takeaway
+
+> **Binding memory is what turns a “resource description” into a “real GPU resource.”**
+
+Without binding:
+
+* No storage
+* No address
+* No GPU access
+
+---
+
+If you want next, I can:
+
+* Show a diagram of heaps → memory → buffers
+* Explain why images have stricter alignment
+* Explain aliasing & memory reuse
+* Explain how bindless / sparse resources change this
+
+Just say the word.
--- /dev/null
+Ah, that sentence is a bit dense. Let’s unpack it carefully.
+
+---
+
+### 1️⃣ Context
+
+Vulkan is a low-level API. When you create resources (like images, buffers, pipelines, etc.), it can be **expensive** in terms of time or GPU/driver work. For example, creating a pipeline involves compiling shaders, setting up internal GPU structures, etc.
+
+The sentence usually appears in **documentation about command pools or resource creation**:
+
+> *“Allow the implementation to amortize the cost of resource creation across multiple command buffers.”*
+
+---
+
+### 2️⃣ Breaking it down
+
+* **Amortize the cost:**
+ In computer science, “amortize” means **spread out a one-time expensive operation over multiple uses** so that each individual use appears cheaper.
+
+* **Resource creation:**
+ This refers to creating Vulkan objects (buffers, images, pipelines, etc.) or allocating memory for them.
+
+* **Across multiple command buffers:**
+ Instead of paying the full cost every time you create a command buffer or resource, the driver can **share internal data or precompute things**, so that subsequent command buffers don’t have to redo all the work.
+
+---
+
+### 3️⃣ In practical Vulkan terms
+
+* If a command pool or pipeline is created with the right flags (like `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`), the driver might:
+
+ * Reuse internal memory for command buffers
+ * Cache GPU-side structures
+ * Avoid redundant validations
+* This **reduces the overhead per command buffer**, especially when creating many of them.
+
+---
+
+### 4️⃣ Analogy
+
+Think of it like **printing tickets at a theme park**:
+
+* Printing one ticket from scratch each time is slow.
+* If the printer can **preprint some templates or share ink across tickets**, then each ticket after the first is faster.
+
+That’s what “amortizing the cost” means in Vulkan: the driver can share the work of resource creation across multiple command buffers or operations, instead of starting from scratch every time.
+
+---
+
+If you want, I can make a **tiny diagram showing how Vulkan command buffers and resource creation share internal data** — it makes “amortization” very visual.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely — this is a **great** function to walk through, because it touches almost every core Vulkan concept: memory types, staging, layout transitions, and synchronization.
+
+I’ll go **top → bottom**, explaining **what each block does and *why***.
+I’ll avoid hand-wavy language and stick to concrete GPU/CPU behavior.
+
+---
+
+# `create_texture_image()` — line-by-line walkthrough
+
+---
+
+## 1️⃣ CPU-side pixel data
+
+```c
+uint32_t pixels[] = {
+ 0xFFFFFFFF, 0xFFFF69B4,
+ 0xFFFF69B4, 0xFFFFFFFF
+};
+```
+
+* This is a **2×2 RGBA texture** stored in **CPU memory**
+* Each `uint32_t` is `0xAABBGGRR` (little-endian)
+* Vulkan does **not** care where this comes from (file, procedural, etc.)
+
+---
+
+```c
+VkDeviceSize size = sizeof(pixels);
+```
+
+* Size of the upload = **16 bytes**
+* Used for buffer creation and `memcpy`
+
+---
+
+## 2️⃣ Create a staging buffer (CPU → GPU bridge)
+
+### Why a staging buffer?
+
+Because **DEVICE_LOCAL images are not CPU-mappable** on discrete GPUs.
+
+---
+
+```c
+VkBuffer staging;
+VkDeviceMemory stagingMem;
+```
+
+Handles for:
+
+* A temporary buffer
+* The memory backing it
+
+---
+
+### Create the buffer object
+
+```c
+VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+};
+vkCreateBuffer(device, &bufInfo, NULL, &staging);
+```
+
+This creates a **buffer object** that:
+
+* Holds `size` bytes
+* Can be used as a **transfer source**
+
+⚠️ No memory is allocated yet — this is just the *buffer description*.
+
+---
+
+### Query memory requirements
+
+```c
+VkMemoryRequirements memReq;
+vkGetBufferMemoryRequirements(device, staging, &memReq);
+```
+
+The driver tells you:
+
+* Required memory size
+* Required alignment
+* Which memory types are allowed (`memoryTypeBits`)
+
+---
+
+### Allocate HOST_VISIBLE + HOST_COHERENT memory
+
+```c
+VkMemoryAllocateInfo alloc = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+};
+```
+
+This means:
+
+| Flag | Meaning |
+| --------------- | ---------------------- |
+| `HOST_VISIBLE` | CPU can map it |
+| `HOST_COHERENT` | No manual flush needed |
+
+So:
+
+* CPU can write
+* GPU can read
+* Writes are automatically visible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &stagingMem);
+vkBindBufferMemory(device, staging, stagingMem, 0);
+```
+
+Now:
+
+* Physical memory is allocated
+* The buffer is bound to that memory
+
+---
+
+### Map and copy pixels
+
+```c
+void* data;
+vkMapMemory(device, stagingMem, 0, size, 0, &data);
+memcpy(data, pixels, size);
+vkUnmapMemory(device, stagingMem);
+```
+
+What happens:
+
+* CPU gets a pointer
+* Copies 16 bytes
+* Unmaps (flush is automatic because COHERENT)
+
+📌 **At this point, pixel data is in GPU-visible memory**
+
+---
+
+## 3️⃣ Create the GPU image (the actual texture)
+
+---
+
+```c
+VkImageCreateInfo img = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {2, 2, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage =
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+};
+vkCreateImage(device, &img, NULL, &textureImage);
+```
+
+This describes:
+
+* A **2D image**
+* 2×2 pixels
+* Optimal tiling (GPU-friendly layout)
+* Can be:
+
+ * Copied **into**
+ * Sampled by shaders
+
+Still **no memory allocated** yet.
+
+---
+
+### Allocate DEVICE_LOCAL memory
+
+```c
+vkGetImageMemoryRequirements(device, textureImage, &memReq);
+
+alloc.allocationSize = memReq.size;
+alloc.memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+```
+
+This means:
+
+* Memory physically close to the GPU
+* Fast for sampling
+* Not CPU-accessible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &textureMemory);
+vkBindImageMemory(device, textureImage, textureMemory, 0);
+```
+
+Now:
+
+* Image storage exists
+* GPU can access it efficiently
+
+---
+
+## 4️⃣ Record a one-time command buffer
+
+### Why?
+
+Because **copies and layout transitions happen on the GPU**
+
+---
+
+```c
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1
+};
+vkAllocateCommandBuffers(device, &cbAlloc, &cb);
+```
+
+---
+
+```c
+VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+};
+vkBeginCommandBuffer(cb, &begin);
+```
+
+---
+
+## 5️⃣ Layout transition: UNDEFINED → TRANSFER_DST
+
+```c
+VkImageMemoryBarrier toTransfer = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .image = textureImage,
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+};
+```
+
+Why?
+
+* Vulkan images start in **UNDEFINED**
+* Copying requires **TRANSFER_DST**
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toTransfer
+);
+```
+
+This tells Vulkan:
+
+> “Before transfer writes, make sure the image is ready.”
+
+---
+
+## 6️⃣ Copy buffer → image
+
+```c
+VkBufferImageCopy copy = {
+ .imageSubresource = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1
+ },
+ .imageExtent = {2, 2, 1}
+};
+```
+
+---
+
+```c
+vkCmdCopyBufferToImage(
+ cb,
+ staging,
+ textureImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1, ©
+);
+```
+
+GPU copies the data.
+
+📌 This is **very fast** — no CPU involvement.
+
+---
+
+## 7️⃣ Layout transition: TRANSFER_DST → SHADER_READ
+
+```c
+VkImageMemoryBarrier toSample = toTransfer;
+toSample.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+toSample.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+toSample.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+toSample.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+```
+
+Now the image becomes:
+
+* Readable by fragment shaders
+* Cache-safe
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toSample
+);
+```
+
+---
+
+## 8️⃣ Submit and wait (safely)
+
+```c
+vkEndCommandBuffer(cb);
+```
+
+---
+
+```c
+VkFence fence;
+VkFenceCreateInfo fenceInfo = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
+};
+vkCreateFence(device, &fenceInfo, NULL, &fence);
+```
+
+Fence lets CPU know when GPU is done.
+
+---
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, fence);
+vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+vkDestroyFence(device, fence, NULL);
+```
+
+CPU blocks until:
+
+* Copy completes
+* Image is ready for sampling
+
+✅ **Much better than `vkQueueWaitIdle`**
+
+---
+
+## 9️⃣ Cleanup staging resources
+
+```c
+vkFreeCommandBuffers(device, commandPool, 1, &cb);
+vkDestroyBuffer(device, staging, NULL);
+vkFreeMemory(device, stagingMem, NULL);
+```
+
+Staging buffer is no longer needed.
+
+---
+
+# Final mental model (important)
+
+```
+CPU RAM
+ ↓ memcpy
+HOST_VISIBLE buffer
+ ↓ GPU copy
+DEVICE_LOCAL image
+ ↓ shader sampling
+fragment shader
+```
+
+---
+
+## You did this function **correctly**
+
+* Correct memory types
+* Correct layout transitions
+* Correct synchronization
+* Correct use of fences
+* Correct staging pattern
+
+This function is **textbook Vulkan**.
+
+If you want next, I can:
+
+* Annotate it with **timeline diagrams**
+* Show how to batch uploads
+* Explain how this changes with BAR memory
+* Explain why this does *not* cause stutter
+
+Just tell me 👍
+
--- /dev/null
+In this context, **“depth” means the per-pixel (or per-sample) distance from the camera used to decide what is in front of what**.
+
+I’ll explain it from the ground up, then tie it directly to **render passes and subpasses**.
+
+---
+
+## 1. What “depth” is (fundamental idea)
+
+**Depth** is a numerical value that represents **how far a fragment is from the camera**.
+
+For every pixel the GPU draws, it computes a depth value:
+
+```
+closer to camera → smaller depth
+farther away → larger depth
+```
+
+This value is stored in a **depth buffer** (also called a **Z buffer**).
+
+---
+
+## 2. Why depth exists at all
+
+Without depth, triangles would be drawn **in submission order**:
+
+```
+draw triangle A
+draw triangle B
+```
+
+If B is behind A but drawn later → it appears in front ❌
+
+Depth fixes this by answering:
+
+> “Is this fragment closer than what was drawn before?”
+
+---
+
+## 3. How depth testing works
+
+For each fragment:
+
+1. Compute its depth (from vertex positions → clip space)
+2. Compare it against the stored depth value
+3. If it passes → draw and update depth
+4. If it fails → discard fragment
+
+Typical comparison:
+
+```c
+VK_COMPARE_OP_LESS
+```
+
+Meaning:
+
+> “Only draw if this fragment is closer.”
+
+---
+
+## 4. Where depth comes from (pipeline view)
+
+### Vertex shader
+
+Outputs:
+
+```glsl
+gl_Position
+```
+
+This contains **clip-space coordinates**.
+
+### After projection
+
+The GPU computes:
+
+```
+depth = z / w
+```
+
+Mapped into:
+
+```
+[0, 1] (Vulkan default)
+```
+
+This is what ends up in the depth buffer.
+
+---
+
+## 5. The depth buffer itself
+
+Depth is stored in a **depth attachment**, just like color is stored in a color attachment.
+
+Common formats:
+
+* `VK_FORMAT_D32_SFLOAT`
+* `VK_FORMAT_D24_UNORM_S8_UINT`
+
+Depth attachments:
+
+* Are not visible on screen
+* Exist only for visibility testing (and sometimes reading later)
+
+---
+
+## 6. Depth + subpasses (this is what you’re asking about)
+
+When you see:
+
+```
+Subpass 0: write color + depth
+Subpass 1: read depth, write color
+```
+
+It means:
+
+### Subpass 0
+
+* Draw geometry
+* Write:
+
+ * Color → color attachment
+ * Depth → depth attachment
+
+This establishes **visibility information**.
+
+---
+
+### Subpass 1
+
+* Does **not redraw geometry**
+* Reads depth to know:
+
+ * Which pixels belong to visible surfaces
+ * How far they are from the camera
+
+Used for:
+
+* Deferred lighting
+* Screen-space effects
+* Depth-aware post-processing
+
+---
+
+## 7. Reading depth in a later subpass
+
+Depth can be read as:
+
+### A) Input attachment (subpass-local, fast)
+
+```glsl
+subpassInput depthInput;
+```
+
+### B) Sampled image (general, slower)
+
+```glsl
+sampler2D depthTex;
+```
+
+This allows effects like:
+
+* Screen-space ambient occlusion (SSAO)
+* Volumetric fog
+* Soft particles
+* Deferred lighting
+
+---
+
+## 8. Depth vs color (key difference)
+
+| Color | Depth |
+| ------------ | ---------------------- |
+| What you see | What is visible |
+| RGB(A) | Single scalar value |
+| Displayed | Used for testing |
+| Final output | Internal decision data |
+
+Depth controls **visibility**, not appearance.
+
+---
+
+## 9. Depth and multisampling (important)
+
+With MSAA:
+
+* Each pixel has **multiple depth samples**
+* Each sample can pass/fail independently
+* This is why MSAA fixes edge aliasing correctly
+
+Depth must use the **same sample count** as color.
+
+---
+
+## 10. What depth is NOT
+
+❌ Not “distance in meters”
+❌ Not linear by default
+❌ Not screen-space Z
+❌ Not optional for 3D rendering
+
+Depth is **non-linear**, optimized for precision near the camera.
+
+---
+
+## 11. Mental model (best way to remember)
+
+> **Depth is a hidden image that stores “how close” each pixel is, and the GPU uses it to decide what is allowed to be drawn.**
+
+---
+
+## 12. One-sentence summary
+
+> **Depth is a per-pixel value stored in a depth buffer that allows the GPU to determine which fragments are visible and to reuse that visibility information across subpasses.**
+
+---
+
+If you want, I can next:
+
+* Show **exact Vulkan structs for a depth attachment**
+* Explain **why depth is non-linear**
+* Walk through **depth testing step-by-step**
+* Explain **stencil vs depth** (they’re often confused)
+
+Just tell me.
--- /dev/null
+Got it — you’re asking **why depth is “not optional” for 3D rendering**.
+
+This is a *fundamental* reason tied to how projection and visibility work, not just a Vulkan convention.
+
+---
+
+## Short answer (intuition)
+
+**Because in 3D, multiple surfaces project onto the same pixel, and without depth there is no correct way to know which one is visible.**
+
+2D doesn’t have this problem.
+3D does.
+
+---
+
+## What actually goes wrong without depth
+
+In 3D, after projection, **everything becomes 2D**:
+
+```
+3D scene → projection → screen pixels
+```
+
+Multiple triangles can map to the **same pixel**:
+
+```
+Camera
+ ↓
+[Triangle A] ← closer
+[Triangle B] ← farther
+```
+
+On screen:
+
+```
+Pixel (x, y)
+```
+
+Without depth, the GPU only knows **draw order**.
+
+---
+
+## Case 1: No depth buffer (painter’s algorithm)
+
+If you don’t use depth:
+
+```c
+vkCmdDraw(triangleA);
+vkCmdDraw(triangleB);
+```
+
+Whichever triangle is drawn **last** wins.
+
+This causes:
+
+* Objects popping in front of each other
+* Incorrect overlap
+* Massive CPU-side sorting requirements
+* Failure in cases like:
+
+ * Intersecting geometry
+ * Self-overlapping meshes
+ * Transparent + opaque mixes
+
+This is **not solvable in general**.
+
+---
+
+## Case 2: With depth buffer (correct solution)
+
+With depth testing:
+
+1. GPU stores depth per pixel
+2. For every fragment:
+
+ * Compare new depth vs stored depth
+ * Keep the closest one
+3. Order of draw calls **no longer matters**
+
+This gives:
+
+✔ Correct visibility
+✔ Intersecting geometry works
+✔ Self-occlusion works
+✔ Parallel GPU execution works
+
+---
+
+## Why the GPU *must* do this, not the CPU
+
+### You cannot sort triangles correctly in 3D
+
+There is **no total ordering** for triangles in 3D space.
+
+Example:
+
+* Triangle A overlaps B
+* B overlaps C
+* C overlaps A
+
+This is called a **cyclic overlap**.
+
+No draw order can fix this.
+
+Depth testing resolves it **per pixel**, which is the only correct level.
+
+---
+
+## Why this is fundamentally required by projection
+
+Perspective projection collapses depth:
+
+```
+(x, y, z) → (x / z, y / z)
+```
+
+That division by `z` destroys ordering information.
+
+Depth restores it.
+
+---
+
+## Can you “fake” 3D without depth?
+
+Yes, but with **severe limitations**:
+
+### When depth is *not* required:
+
+* Skyboxes
+* Fullscreen quads
+* Billboards drawn in strict order
+* 2.5D games (fixed layers)
+
+These avoid the general 3D visibility problem.
+
+---
+
+## Why Vulkan treats depth as a first-class concept
+
+Vulkan gives depth:
+
+* Its own attachment
+* Its own load/store ops
+* Its own layouts
+* Its own tests
+* Its own sampling path
+
+Because **the entire rasterization pipeline assumes depth exists**.
+
+---
+
+## The key mental model
+
+> **3D rendering is fundamentally a visibility problem.
+> Depth is the data structure that solves it.**
+
+Without depth, you don’t have 3D — you have a fragile illusion.
+
+---
+
+## One-sentence answer
+
+> **Depth is not optional in 3D rendering because projection causes multiple surfaces to map to the same pixel, and only per-pixel depth testing can correctly determine which surface is visible.**
+
+If you want, next we can:
+
+* Prove why triangle sorting is impossible in general
+* Walk through depth testing in Vulkan line-by-line
+* Explain why depth precision breaks at far distances
+* Show how deferred renderers rely entirely on depth
+
+Just say where you want to go.
--- /dev/null
+This is a **fundamental Vulkan question**, and the confusion is very normal. The short answer is:
+
+> **`malloc` / `calloc` allocate CPU memory.
+> `vkAllocateMemory` allocates GPU-accessible device memory.
+> They live in completely different worlds.**
+
+Let’s break it down cleanly and precisely.
+
+---
+
+## 1️⃣ `malloc` / `calloc` vs `vkAllocateMemory`
+
+### `malloc` / `calloc`
+
+* Allocates **process virtual memory**
+* Owned by the **CPU / OS**
+* Only the CPU can access it
+* The GPU **cannot see or use it**
+* Vulkan has **no idea** this memory exists
+
+```c
+void* data = malloc(1024); // CPU RAM only
+```
+
+You **cannot** bind this memory to:
+
+* `VkBuffer`
+* `VkImage`
+* Any Vulkan object
+
+---
+
+### `vkAllocateMemory`
+
+* Allocates memory from the **physical device**
+* Managed by **Vulkan + the GPU driver**
+* Can be:
+
+ * GPU-only (VRAM)
+ * CPU-visible (mapped system memory)
+* Can be **bound** to buffers and images
+
+```c
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+vkBindBufferMemory(device, buffer, memory, 0);
+```
+
+This is the **only way** Vulkan objects get usable memory.
+
+---
+
+## 2️⃣ Why Vulkan can’t use `malloc`
+
+The GPU:
+
+* Does not understand your process’s virtual address space
+* Does not participate in `malloc`
+* Cannot dereference CPU pointers
+
+Even if memory is physically the same (iGPU / UMA):
+
+* Vulkan still needs **explicit ownership, caching, and visibility rules**
+* The driver must know how the memory is used
+
+So Vulkan **must** control allocation.
+
+---
+
+## 3️⃣ Why Vulkan separates allocation from objects
+
+In OpenGL:
+
+* `glBufferData` silently allocates memory for you
+* You don’t control where or how
+
+In Vulkan:
+
+* You control:
+
+ * Memory location
+ * Cache behavior
+ * Sharing
+ * Lifetime
+
+This enables:
+
+* Zero-copy paths
+* Predictable performance
+* No hidden stalls
+
+---
+
+## 4️⃣ What Vulkan memory really is
+
+Think of Vulkan memory as:
+
+> A **handle to a chunk of physical device memory**,
+> not a pointer.
+
+```c
+VkDeviceMemory mem; // not a pointer!
+```
+
+The GPU accesses it through:
+
+* Bindings
+* Descriptors
+* Command buffers
+
+The CPU accesses it **only if HOST_VISIBLE**, via `vkMapMemory`.
+
+---
+
+## 5️⃣ Why staging buffers exist
+
+Because:
+
+* GPU VRAM is **fast but CPU-inaccessible**
+* CPU RAM is **slow but CPU-accessible**
+
+So Vulkan uses a two-step approach:
+
+```text
+CPU RAM (malloc / staging)
+ ↓ copy
+GPU VRAM (vkAllocateMemory DEVICE_LOCAL)
+```
+
+Except the staging buffer is still allocated via Vulkan,
+because the GPU must be able to read from it.
+
+---
+
+## 6️⃣ Common mistake (you avoided it)
+
+❌ This does NOT work:
+
+```c
+float* verts = malloc(sizeof(vertices));
+vkBindBufferMemory(buffer, (VkDeviceMemory)verts, 0); // invalid
+```
+
+Because:
+
+* That memory is not tracked by Vulkan
+* The GPU cannot see it
+
+---
+
+## 7️⃣ Why Vulkan memory feels “manual”
+
+Because it is designed for:
+
+* Engines
+* Drivers
+* Explicit control
+* Zero hidden sync
+
+Vulkan trades convenience for **absolute predictability**.
+
+---
+
+## TL;DR
+
+* `malloc` / `calloc` → CPU memory only
+* `vkAllocateMemory` → GPU-accessible device memory
+* Vulkan objects **must** use `vkAllocateMemory`
+* The GPU **cannot see malloc’d memory**
+* Explicit control = performance + no hidden stalls
+
+---
+
+If you want, next we can:
+
+* Walk through a **full buffer allocation lifecycle**
+* Explain **BAR memory vs VRAM** in more depth
+* Show how Vulkan allocators (VMA) work internally
+* Tie this back directly to your stutter issue
+
+Just tell me 👍
--- /dev/null
+Yes — this is a *really important* Vulkan concept, and you’re already **using it correctly** in your code. I’ll explain it from the ground up and tie it directly to what you wrote.
+
+---
+
+# 1️⃣ What “dynamic rendering” is (high-level)
+
+**Dynamic rendering** is a newer Vulkan feature that lets you:
+
+> **Render without creating a `VkRenderPass` or `VkFramebuffer`.**
+
+Instead of baking attachments and subpasses ahead of time, you:
+
+* Describe the attachments **at draw time**
+* Begin rendering **directly in the command buffer**
+* Bind pipelines that declare what formats they expect
+
+It removes a *huge* amount of boilerplate.
+
+---
+
+# 2️⃣ What Vulkan looked like *before* dynamic rendering
+
+### Old model (classic Vulkan)
+
+You had to create:
+
+1. `VkRenderPass`
+
+ * Attachments
+ * Subpasses
+ * Load/store ops
+2. `VkFramebuffer`
+
+ * One per swapchain image
+3. Pipeline tied **per render pass**
+
+```text
+RenderPass → Framebuffer → Pipeline → Draw
+```
+
+Problems:
+
+* Render passes were **compile-time objects**
+* Changing formats or attachments required rebuilding pipelines
+* Framebuffers had to match exactly
+
+This was powerful but **extremely rigid**.
+
+---
+
+# 3️⃣ What dynamic rendering replaces
+
+Dynamic rendering **removes**:
+
+| Old object | Replaced by |
+| -------------------- | ----------- |
+| `VkRenderPass` | ❌ Gone |
+| `VkFramebuffer` | ❌ Gone |
+| Subpass descriptions | ❌ Gone |
+
+Instead, Vulkan introduces:
+
+* `vkCmdBeginRendering`
+* `VkRenderingInfo`
+* `VkRenderingAttachmentInfo`
+* `VkPipelineRenderingCreateInfo`
+
+---
+
+# 4️⃣ What *you* are doing with dynamic rendering
+
+Let’s map your code to the concept.
+
+---
+
+## A) Enabling dynamic rendering on the device
+
+```c
+VkPhysicalDeviceDynamicRenderingFeatures dyn = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+};
+```
+
+This says:
+
+> “This device is allowed to use `vkCmdBeginRendering`.”
+
+Without this → **validation error / undefined behavior**.
+
+---
+
+## B) Declaring attachment formats in the pipeline
+
+```c
+VkPipelineRenderingCreateInfo rendering = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+};
+```
+
+This replaces:
+
+* `VkRenderPass`
+* Subpass attachment format matching
+
+It tells Vulkan:
+
+> “This pipeline expects **one color attachment** with this format.”
+
+Pipelines still need to know formats **up-front**, but not layouts, load ops, or framebuffers.
+
+---
+
+## C) Beginning rendering (the new render pass)
+
+```c
+vkCmdBeginRendering(cb, &ri);
+```
+
+This is the **dynamic equivalent** of:
+
+```c
+vkCmdBeginRenderPass(...)
+```
+
+Except:
+
+* No framebuffer
+* No render pass object
+* No subpass index
+
+---
+
+## D) Describing attachments at draw time
+
+```c
+VkRenderingAttachmentInfo color = {
+ .imageView = swapchainViews[img],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue.color = {{0,0,0,1}}
+};
+```
+
+This replaces:
+
+```text
+VkAttachmentDescription
+VkAttachmentReference
+VkSubpassDescription
+```
+
+All **combined**, but now:
+
+* Fully dynamic
+* Chosen per frame
+* Chosen per command buffer
+
+---
+
+## E) Ending rendering
+
+```c
+vkCmdEndRendering(cb);
+```
+
+Equivalent to:
+
+```c
+vkCmdEndRenderPass(cb);
+```
+
+---
+
+# 5️⃣ What dynamic rendering does **NOT** change
+
+Dynamic rendering does **not** change:
+
+| Thing | Still required |
+| ------------------ | -------------- |
+| Pipelines | ✅ |
+| Descriptor sets | ✅ |
+| Layout transitions | ✅ |
+| Synchronization | ✅ |
+| Image views | ✅ |
+
+This is why you still need:
+
+```c
+VkImageMemoryBarrier
+```
+
+Dynamic rendering does **not** manage layouts for you.
+
+---
+
+# 6️⃣ Why dynamic rendering exists
+
+### Goals:
+
+* Reduce boilerplate
+* Make Vulkan usable for small engines
+* Allow more flexible rendering graphs
+* Match modern APIs (DX12 / Metal)
+
+### Typical wins:
+
+* No framebuffer creation
+* No render pass combinatorics
+* Easier swapchain recreation
+* Much easier multipass rendering
+
+---
+
+# 7️⃣ How dynamic rendering maps conceptually
+
+Think of it like this:
+
+### Classic Vulkan
+
+```text
+RenderPass (static)
+ └─ Subpass (static)
+ └─ Attachments (static)
+```
+
+### Dynamic Rendering
+
+```text
+Command Buffer
+ └─ Begin Rendering
+ └─ Attachments (chosen now)
+```
+
+You move **render-graph decisions** from *initialization time* to *record time*.
+
+---
+
+# 8️⃣ Common misconceptions (important)
+
+### ❌ “Dynamic rendering is slower”
+
+No. Drivers internally build render passes anyway.
+
+### ❌ “Dynamic rendering removes the need for barriers”
+
+Absolutely not. You still must manage layouts and sync.
+
+### ❌ “Dynamic rendering is optional”
+
+Yes — but it’s becoming the **preferred path**.
+
+---
+
+# 9️⃣ When NOT to use dynamic rendering
+
+You might still want classic render passes if:
+
+* You need **subpass input attachments**
+* You rely on **tile-based GPU subpass optimizations**
+* You want strict render-graph control
+
+Most beginner/intermediate engines should **use dynamic rendering**.
+
+---
+
+# 🔑 One-sentence summary
+
+> **Dynamic rendering lets you render directly to images without pre-declaring render passes or framebuffers, by describing attachments dynamically inside the command buffer.**
+
+---
+
+If you want next, I can:
+
+* Rewrite your code in **classic render-pass style** to compare
+* Explain **why subpasses exist** and when they outperform dynamic rendering
+* Show **depth + MSAA** with dynamic rendering
+* Show how dynamic rendering maps to **Vulkan synchronization2**
+
+Just tell me.
--- /dev/null
+Absolutely — this is a **core Vulkan helper**, and understanding it deeply will make a lot of other Vulkan code “click”. I’ll go **line by line**, then give you a **mental model** at the end.
+
+---
+
+## Function signature
+
+```c
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props)
+```
+
+### What this function does (high level)
+
+It finds **which GPU memory type index** you should use when allocating memory for a buffer or image.
+
+Vulkan does **not** let you just say “give me GPU memory”.
+Instead, you must:
+
+1. Ask Vulkan **what kinds of memory are allowed** (`typeBits`)
+2. Ask for **what properties you want** (`props`)
+3. Pick a memory **type index** that satisfies both
+
+This function does exactly that.
+
+---
+
+## Get physical device memory info
+
+```c
+VkPhysicalDeviceMemoryProperties memProps;
+vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+```
+
+### What’s happening
+
+* Vulkan exposes **all memory types supported by the GPU**
+* This includes:
+
+ * Device-local VRAM
+ * Host-visible memory
+ * Coherent / cached memory
+ * Unified memory (on iGPUs)
+
+`memProps` contains:
+
+```c
+memProps.memoryTypeCount
+memProps.memoryTypes[i].propertyFlags
+```
+
+Typical GPU: **8–32 memory types**
+
+---
+
+## Loop over every memory type
+
+```c
+for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+```
+
+You are checking **each possible memory type index**.
+
+Important:
+
+* The return value of this function is **this index (`i`)**
+* This index is what you pass to `VkMemoryAllocateInfo.memoryTypeIndex`
+
+---
+
+## Check if this memory type is allowed
+
+```c
+if ((typeBits & (1 << i)) &&
+```
+
+### What `typeBits` means
+
+`typeBits` comes from:
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+```
+
+* `req.memoryTypeBits` is a **bitmask**
+* Each bit corresponds to a memory type index
+* If bit `i` is set → that memory type **can be used** for this resource
+
+### This line checks:
+
+> “Is memory type `i` compatible with this buffer/image?”
+
+If not, skip it.
+
+---
+
+## Check if it has the properties you want
+
+```c
+(memProps.memoryTypes[i].propertyFlags & props) == props)
+```
+
+### `propertyFlags` examples
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+VK_MEMORY_PROPERTY_HOST_CACHED_BIT
+```
+
+### This line means:
+
+> “Does this memory type include **all** the properties I requested?”
+
+Why bitwise AND?
+
+* `propertyFlags` may have **extra bits**
+* You only care that **your requested bits are present**
+
+Example:
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+```
+
+This is valid.
+
+---
+
+## If both conditions pass → success
+
+```c
+return i;
+```
+
+You’ve found:
+
+* A memory type that **works for this resource**
+* With **the properties you want**
+
+You’re done.
+
+---
+
+## If no memory type matches
+
+```c
+fprintf(stderr, "No suitable memory type\n");
+exit(1);
+```
+
+This should **never happen** in a correct Vulkan program.
+
+If it does:
+
+* You requested impossible memory properties
+* Or passed the wrong `typeBits`
+* Or you misunderstood host vs device memory
+
+---
+
+## Mental model (this is the key)
+
+Think of Vulkan memory like this:
+
+> “I have a list of memory pools.
+> Each pool has certain capabilities.
+> Each resource tells me which pools it is allowed to use.
+> I must pick one pool that satisfies both.”
+
+This function answers:
+
+> **“Which pool index should I use?”**
+
+---
+
+## Example usage (buffer allocation)
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+
+uint32_t memoryType = find_memory_type(
+ req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+);
+```
+
+This means:
+
+> “Find a memory type that
+> ✔ the buffer supports
+> ✔ I can map from CPU
+> ✔ doesn’t require manual flush/invalidate”
+
+---
+
+## Why this function is critical
+
+Every Vulkan program needs it for:
+
+* Vertex buffers
+* Index buffers
+* Uniform buffers
+* Staging buffers
+* Images
+* Depth buffers
+* Textures
+
+If you understand **this function**, you understand:
+
+* Why Vulkan memory is explicit
+* Why performance is predictable
+* Why Vulkan avoids hidden stalls
+
+---
+
+If you want next, I can:
+
+* Draw a **diagram of Vulkan memory types**
+* Show **which memory properties to use for each buffer**
+* Explain **why HOST_VISIBLE is slower**
+* Explain **why staging buffers exist**
+
+Just tell me which one.
--- /dev/null
+Ah, now we’re getting into a subtle Vulkan topic: **swapchain image count vs performance**. Let’s break it down carefully.
+
+---
+
+## 1️⃣ How the swapchain works
+
+A Vulkan swapchain is basically a **queue of images** (framebuffers) the GPU can render into and the presentation engine (display) can show.
+
+* `vkCreateSwapchainKHR` lets you specify `minImageCount` — the number of images.
+* Typical values: **2–3** (double or triple buffering).
+
+Each image can be in one of three states:
+
+1. **Available** — ready to render to
+2. **Rendering** — GPU is drawing into it
+3. **Presenting** — waiting to be shown on screen
+
+---
+
+## 2️⃣ Double vs Triple Buffering
+
+| # Images | Behavior | Pros | Cons |
+| ---------- | ------------------------------------------------ | ---------------------------------------- | ---------------------------------------- |
+| 2 (double) | GPU renders to 1 image, display shows the other | Less memory usage, lower latency | GPU may stall if display is slow |
+| 3 (triple) | GPU has 1 image ready, 1 rendering, 1 displaying | GPU never waits for display → higher FPS | More memory usage, slightly more latency |
+
+✅ Triple buffering can **improve throughput** because the GPU always has an image to render to.
+❌ But adding **more than 3–4 images rarely helps** — the GPU already pipelines well.
+
+---
+
+## 3️⃣ Latency vs performance tradeoff
+
+* **More images → higher FPS potential** (GPU never blocks)
+* **More images → higher input/display latency** (you’re rendering frames that are “ahead” of the screen)
+
+For games:
+
+* Triple buffering is common for smooth FPS
+* Double buffering is often enough for low-latency apps
+
+---
+
+## 4️⃣ Vulkan-specific notes
+
+* Some GPU drivers **require at least 2 images**.
+
+* The swapchain must have **at least `surfaceCapabilities.minImageCount`** images.
+
+* If you request more than `surfaceCapabilities.maxImageCount` → creation fails.
+
+* Vulkan allows you to acquire the **next available image** via `vkAcquireNextImageKHR`. If all images are in use, the GPU may block until one becomes free. More images = less chance of blocking.
+
+---
+
+### ✅ TL;DR
+
+* **2 images:** safe, low memory, slightly higher chance GPU waits
+* **3 images:** smoother, less chance of GPU stall, slightly more latency
+* **4+ images:** rarely improves FPS, just uses more VRAM
+
+So yes, **more images *can* improve throughput**, but only up to a point (usually 3). Beyond that, it’s mostly wasted memory.
+
+---
+
+If you want, I can draw a **diagram showing the swapchain states over time** — it makes it super clear why 3 images can be smoother than 2.
+
+Do you want me to do that?
--- /dev/null
+Feature,Pattern Rule (%),Wildcard (*)
+Purpose,Defining a transformation rule.,Listing existing files.
+Usage,%.o: %.c,rm *.o
+Timing,Evaluated when make decides what to do.,Evaluated by the shell when the
+command runs. Example,"""How to turn any X into Y.""","""Do something to
+everything that ends in .X."""
+
+Symbol,What it does,Resulting Logic %.spv: %.vert,Links the names.,"""To get
+player.spv, look for player.vert.""" *.spv: *.vert,Literal expansion.,"""To get
+all spv files, look for all vert files."""
--- /dev/null
+Ah, yes — this is a subtle but very important distinction. It’s easy to get confused because Vulkan separates **allocation** from **binding**, and the wording can make it seem like they both “give memory” to the buffer. Let’s clarify carefully.
+
+---
+
+### Step 1: `vkAllocateMemory`
+
+When you call:
+
+```c
+VkDeviceMemory memory;
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+```
+
+You are **reserving a chunk of memory from the GPU (or a heap)**.
+
+Think of it like **booking a block of storage in a warehouse**:
+
+* `VkDeviceMemory` is just a handle that represents that chunk.
+* The memory **exists**, but nothing is stored in it yet.
+* The GPU doesn’t yet know which buffer or image will use it.
+
+It’s untyped, unbound storage — just “here are these bytes in GPU-accessible memory.”
+
+---
+
+### Step 2: `vkBindBufferMemory`
+
+When you call:
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+```
+
+You are saying:
+
+> “Buffer X will **live starting at this offset in the allocated memory block**.”
+
+Now:
+
+* The buffer has a **valid GPU address**.
+* The GPU can access it when drawing or copying.
+* Alignment and memory type are enforced.
+
+It’s like taking that chunk of warehouse space and **assigning it to a specific shelf (the buffer)**.
+
+---
+
+### Analogy
+
+| Vulkan Step | Analogy |
+| -------------------- | --------------------------------------------------------------------------------------- |
+| `vkAllocateMemory` | Renting a warehouse block of storage (size, type, location) |
+| `vkBindBufferMemory` | Putting a specific item (buffer or image) into a specific shelf in that warehouse block |
+
+---
+
+### Why not do it all in one call?
+
+Vulkan separates the two because:
+
+1. **Memory suballocation:**
+ One `VkDeviceMemory` block can hold multiple buffers/images.
+
+ ```c
+ vkBindBufferMemory(vertexBuffer, memory, 0);
+ vkBindBufferMemory(indexBuffer, memory, 65536);
+ ```
+2. **Explicit control:**
+ You choose **memory type** (device local, host visible, cached, BAR-mapped) and **alignment**.
+3. **Performance predictability:**
+ Drivers don’t do hidden allocations or copies.
+
+---
+
+✅ **Summary**
+
+* `vkAllocateMemory`: “Here is a raw block of memory on the GPU.”
+* `vkBindBufferMemory`: “This buffer will live inside that block at this offset.”
+
+Without `vkAllocateMemory`, `vkBindBufferMemory` has nothing to bind.
+Without `vkBindBufferMemory`, the buffer exists but has **no storage**.
+
+---
+
+If you want, I can draw a little diagram showing **buffers, images, memory blocks, and GPU access** — it makes this super clear.
+
+Do you want me to do that?
--- /dev/null
+Great question — this is one of the **most important Vulkan concepts**, and the wording is easy to gloss over.
+
+When I said:
+
+> **“I can map from CPU”**
+
+this refers **very specifically** to Vulkan’s ability to let your program access GPU memory **as a normal pointer** on the CPU.
+
+Let’s break that down precisely.
+
+---
+
+## What “map from CPU” means in Vulkan
+
+In Vulkan, memory lives in different places:
+
+* Some memory lives **only on the GPU** (fast, but CPU can’t touch it)
+* Some memory lives in **system RAM** or unified memory (CPU can touch it)
+* Some memory is shared / cached / coherent
+
+When memory has the flag:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+```
+
+it means:
+
+> **The CPU is allowed to directly access this memory**
+
+Concretely, this allows you to call:
+
+```c
+void* data;
+vkMapMemory(device, memory, 0, size, 0, &data);
+```
+
+And then do:
+
+```c
+memcpy(data, myVertices, size);
+```
+
+That’s what **“map from CPU”** means.
+
+---
+
+## What happens without HOST_VISIBLE
+
+If memory does **not** have `HOST_VISIBLE`:
+
+* `vkMapMemory` is **illegal**
+* You cannot get a pointer to it
+* The CPU cannot read or write it directly
+
+This is typical for:
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+which usually means:
+
+* Memory physically located on the GPU (VRAM)
+* Very fast for the GPU
+* Completely inaccessible to the CPU
+
+---
+
+## Why you care
+
+### Example: Vertex buffer
+
+#### Option A — HOST_VISIBLE memory
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+```
+
+✔ Easy: `memcpy` vertices directly
+❌ Slower for GPU access
+❌ PCIe / cache penalties
+
+#### Option B — DEVICE_LOCAL memory
+
+```c
+props = DEVICE_LOCAL
+```
+
+✔ Very fast for GPU
+❌ CPU cannot write directly
+
+So how do you upload data in Option B?
+
+➡ **Staging buffer**
+
+---
+
+## The staging buffer pattern (this is key)
+
+1. Create a **CPU-visible staging buffer**
+
+ ```c
+ HOST_VISIBLE | HOST_COHERENT
+ ```
+
+2. Write data via `memcpy`
+
+3. Copy staging buffer → device-local buffer using a command buffer
+
+4. Free staging buffer
+
+This is why Vulkan makes you think explicitly.
+
+---
+
+## HOST_COHERENT (important related detail)
+
+Often you see:
+
+```c
+HOST_VISIBLE | HOST_COHERENT
+```
+
+### What HOST_COHERENT means
+
+It means:
+
+> “When I write to mapped memory, the GPU sees it automatically”
+
+Without it, you must manually do:
+
+```c
+vkFlushMappedMemoryRanges(...)
+vkInvalidateMappedMemoryRanges(...)
+```
+
+So:
+
+* **HOST_VISIBLE** = CPU *can* access
+* **HOST_COHERENT** = CPU/GPU memory stays in sync automatically
+
+---
+
+## Concrete analogy
+
+Think of GPU memory like rooms:
+
+| Room type | CPU can enter? | GPU speed |
+| ----------------------- | -------------- | ------------ |
+| Device local (VRAM) | ❌ No | 🚀 Fast |
+| Host visible | ✅ Yes | 🐢 Slower |
+| Host visible + coherent | ✅ Yes | 🐢🐢 Slowest |
+
+You’re choosing **which room** to put your data in.
+
+---
+
+## In your program specifically
+
+When you call:
+
+```c
+find_memory_type(req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+```
+
+You are explicitly saying:
+
+> “Give me memory that my CPU can map with a pointer
+> and write to using memcpy,
+> without manual cache management.”
+
+That’s exactly what “I can map from CPU” means.
+
+---
+
+## One sentence summary (very precise)
+
+> **“Map from CPU” means the CPU can obtain a pointer to GPU memory via `vkMapMemory` and directly read/write it like normal RAM.”**
+
+---
+
+If you want next, I can explain:
+
+* Why HOST_VISIBLE memory is often slower
+* Why some GPUs don’t have coherent memory
+* How unified memory GPUs behave differently
+* Exactly when you should *not* use HOST_VISIBLE
+
+Just tell me which one you want.
+
+Short answer: **sometimes yes, sometimes no — and Vulkan deliberately doesn’t promise which**.
+
+Let’s be very precise.
+
+---
+
+## Is mapped memory “still on the GPU”?
+
+### Vulkan’s honest answer:
+
+> **Mapped memory is always allocated by the Vulkan device, but its *physical location* depends on the hardware and memory type.**
+
+When you call:
+
+```c
+vkAllocateMemory(device, ...)
+vkMapMemory(device, memory, ...)
+```
+
+You are **not** allocating “CPU memory” or “GPU memory” explicitly.
+
+You are allocating **device memory**, and Vulkan exposes a **CPU mapping** *if the memory type allows it*.
+
+---
+
+## What actually happens under the hood
+
+### Case 1: Discrete GPU (most desktops)
+
+Typical example:
+
+* CPU has system RAM
+* GPU has its own VRAM
+* Connected via PCIe
+
+For these GPUs:
+
+| Memory type | Physical location |
+| -------------- | ------------------------------- |
+| `DEVICE_LOCAL` | GPU VRAM |
+| `HOST_VISIBLE` | System RAM (or BAR-mapped VRAM) |
+
+So if memory is:
+
+```c
+HOST_VISIBLE
+```
+
+then **physically** it is usually:
+
+* Allocated in **system RAM**
+* GPU accesses it over PCIe
+* CPU maps it directly
+
+✅ CPU pointer is real RAM
+❌ GPU access is slower
+
+This is why HOST_VISIBLE is slower for rendering.
+
+---
+
+### Case 2: Integrated / UMA GPU (laptops, APUs)
+
+Typical example:
+
+* AMD APU
+* Intel iGPU
+* Apple Silicon
+
+Here:
+
+* CPU and GPU share **the same physical memory**
+
+So:
+
+```c
+HOST_VISIBLE | DEVICE_LOCAL
+```
+
+can both be true.
+
+In this case:
+
+* Memory is physically one pool
+* CPU pointer == GPU memory
+* No PCIe penalty
+
+This is the **best-case scenario**, and Vulkan allows it.
+
+---
+
+### Case 3: BAR-mapped VRAM (advanced discrete GPUs)
+
+Some modern GPUs expose parts of VRAM to the CPU via PCIe BAR:
+
+* Memory is **physically in VRAM**
+* CPU sees a mapped window
+* Still slower than RAM (system ram)
+* Limited size
+
+Still counts as `HOST_VISIBLE`.
+
+---
+
+## The key Vulkan rule (this matters)
+
+> **Vulkan never guarantees where the memory physically lives.**
+
+It only guarantees:
+
+* Whether the CPU *may* access it
+* Whether caches are coherent
+* Whether the GPU prefers it
+
+This is why Vulkan forces you to ask:
+
+```c
+vkGetPhysicalDeviceMemoryProperties(...)
+```
+
+And choose explicitly.
+
+---
+
+## So is the memory “on the GPU”?
+
+Let’s answer your question exactly:
+
+> **If the CPU can obtain the pointer, is the memory still allocated on the GPU then?**
+
+### Correct, nuanced answer:
+
+* ✅ **Yes**, it is Vulkan *device memory*
+* ❌ **No**, it is not necessarily physically in GPU VRAM
+* 🔁 It may be:
+
+ * system RAM
+ * unified memory
+ * BAR-mapped VRAM
+
+The CPU pointer is a **mapping**, not proof of physical location.
+
+---
+
+## Why Vulkan phrases it this way
+
+Vulkan is designed so that:
+
+* The same code works on phones, consoles, desktops, servers
+* You never assume memory topology
+* Performance comes from choosing wisely, not guessing
+
+That’s why:
+
+* You describe *requirements*
+* The driver chooses *placement*
+
+---
+
+## Mental model (use this)
+
+Think of Vulkan memory like this:
+
+> “I allocate memory *owned by the device*.
+> Sometimes the device lets the CPU borrow a pointer to it.”
+
+Not:
+
+> “I allocated CPU memory that the GPU can see.”
+
+That mental model will keep you out of trouble.
+
+---
+
+## One-sentence takeaway
+
+> **Mapped memory is always Vulkan device memory; CPU access is a permission, not a statement about physical location.**
+
+If you want next, I can:
+
+* Show how to detect UMA vs discrete GPUs
+* Explain why HOST_VISIBLE can stall rendering
+* Walk through a perfect staging-buffer upload
+* Explain why mapping every frame can cause stutter (relevant to your issue)
+
+Just tell me.
+
+Great question — this is exactly where the confusion usually is.
+Let’s answer it **very concretely** and correct one small wording issue first.
+
+> What you meant by *“Case 3”* is **BAR-mapped VRAM on a discrete GPU**
+> (Resizable BAR / Smart Access Memory), **not unified memory**.
+
+---
+
+## Short answer (TL;DR)
+
+**BAR-mapped VRAM is *not faster* than normal system RAM for the CPU.**
+Its advantages are **not raw speed**, but **capability and reduced copies**.
+
+---
+
+## First: what “non BAR-mapped” means
+
+### Traditional (non-BAR) discrete GPU memory
+
+* GPU VRAM is **not directly addressable** by the CPU
+* CPU must:
+
+ 1. Write to system RAM (staging buffer)
+ 2. Ask GPU to copy over PCIe (`vkCmdCopyBuffer`)
+* CPU **cannot** dereference VRAM pointers
+
+This is how GPUs worked for decades.
+
+---
+
+## What BAR-mapped VRAM actually is
+
+With **Resizable BAR**:
+
+* GPU VRAM is mapped into the CPU’s address space
+* CPU can get a pointer to VRAM
+* CPU loads/stores go **over PCIe**
+
+Important:
+
+> **PCIe is much slower and higher-latency than DRAM**
+
+---
+
+## Is BAR-mapped VRAM faster than non-BAR?
+
+### For the CPU?
+
+❌ **No — it is slower than system RAM**
+
+| Memory type | CPU access speed |
+| ------------------ | ---------------- |
+| System RAM | ⭐⭐⭐⭐⭐ |
+| BAR-mapped VRAM | ⭐⭐ |
+| GPU VRAM (non-BAR) | ❌ impossible |
+
+BAR gives **access**, not speed.
+
+---
+
+### For the GPU?
+
+⚠️ **Sometimes indirectly**
+
+BAR can reduce:
+
+* Extra copies
+* Command buffer overhead
+* Synchronization latency
+
+But GPU **still prefers DEVICE_LOCAL VRAM accessed by itself**.
+
+---
+
+## So what are the actual advantages?
+
+### ✅ 1. Fewer copies (big deal)
+
+Without BAR:
+
+```
+CPU → RAM → GPU copy → VRAM
+```
+
+With BAR:
+
+```
+CPU → VRAM
+```
+
+This matters when:
+
+* Streaming assets
+* Uploading many small buffers
+* Doing frequent updates
+
+---
+
+### ✅ 2. Lower latency for small, dynamic updates
+
+Examples:
+
+* Per-frame constants
+* Dynamic vertex buffers
+* Sparse data updates
+
+Avoids:
+
+* Command submission
+* Transfer queues
+* Extra synchronization
+
+---
+
+### ✅ 3. Better CPU-driven streaming
+
+Used heavily in:
+
+* Modern engines (DX12 / Vulkan)
+* Texture streaming systems
+* Nanite-style workloads
+
+BAR helps **bandwidth utilization**, not single-access speed.
+
+---
+
+### ❌ What BAR does NOT give you
+
+* ❌ Faster CPU memory access
+* ❌ Replacement for staging buffers in large uploads
+* ❌ Free performance
+
+If you memcpy 100 MB per frame into BAR VRAM → **you will stall badly**
+
+---
+
+## Why BAR can *hurt* if misused
+
+BAR-mapped VRAM:
+
+* Bypasses CPU caches
+* Has high write-combining costs
+* Causes PCIe traffic saturation
+
+So this is **bad**:
+
+```c
+memcpy(barMappedVRAM, bigTexture, size);
+```
+
+This is **good**:
+
+```c
+memcpy(stagingRAM, data, size);
+vkCmdCopyBuffer(...); // GPU does the heavy lifting
+```
+
+---
+
+## Vulkan perspective (important)
+
+Vulkan exposes BAR like this:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+If you see **both** on a discrete GPU:
+
+* That’s BAR-mapped VRAM
+* Treat it as **special-purpose**, not general RAM
+
+---
+
+## Practical rule (tattoo this mentally)
+
+> **BAR is for reducing copies, not increasing bandwidth**
+
+Use BAR-mapped memory when:
+
+* Updates are small
+* Latency matters
+* You want to avoid extra transfers
+
+Use staging buffers when:
+
+* Data is large
+* GPU will read it often
+* You want peak throughput
+
+---
+
+## Relating this back to your stuttering issue
+
+You were right to suspect memory behavior.
+
+Your current pipeline is good because:
+
+* Texture is uploaded once
+* GPU samples DEVICE_LOCAL memory
+* No per-frame BAR writes
+
+So your stutter is **not** BAR-related — it was primarily:
+
+* Swapchain image / frame-in-flight mismatch
+* Fence/image ownership issues (which you already fixed)
+
+---
+
+If you want next, I can:
+
+* Show how engines *mix* BAR + staging safely
+* Explain why BAR helps DX12 more than Vulkan
+* Show how to detect BAR memory at runtime
+
+Just tell me 👍
--- /dev/null
+Ah, yes! This is a subtle but important Vulkan concept. Let’s break it down carefully.
+
+---
+
+### 1️⃣ What “opaque object” means
+
+In Vulkan, when you see something described as an **opaque object**, it means:
+
+* **You don’t know or need to know what’s inside it.**
+* The API treats it as a **handle** (essentially a pointer or an integer under the hood).
+* You cannot directly access its internal structure — you can only interact with it via Vulkan functions.
+
+For example:
+
+```c
+VkCommandPool commandPool;
+vkCreateCommandPool(device, &cpInfo, NULL, &commandPool);
+```
+
+* `commandPool` is a **handle** to the actual command pool data.
+* Internally, the driver stores GPU memory, thread info, buffers, etc., but **Vulkan doesn’t expose those details**.
+* You just pass `commandPool` to Vulkan functions like `vkAllocateCommandBuffers` or `vkResetCommandPool`.
+
+---
+
+### 2️⃣ Why Vulkan uses opaque objects
+
+1. **Driver flexibility:** Different GPU vendors can implement the object however they want.
+2. **Safety:** You can’t accidentally mess with internal GPU data.
+3. **Abstraction:** Vulkan provides a uniform interface regardless of the underlying hardware.
+
+Other examples of opaque objects:
+
+* `VkDevice`
+* `VkBuffer`
+* `VkImage`
+* `VkPipeline`
+* `VkShaderModule`
+
+All of these are **handles**, not structs you can poke inside.
+
+---
+
+### 3️⃣ How you interact with them
+
+You always pass the handle to Vulkan functions:
+
+```c
+vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers);
+vkDestroyCommandPool(device, commandPool, NULL);
+```
+
+You never dereference it or try to read its members, because **its memory layout is unknown**.
+
+---
+
+✅ In short:
+
+An **opaque object** in Vulkan is like a black box: you know **what it represents** and **how to use it**, but you **don’t see or modify its internals**. The driver takes care of the details.
+
+---
+
+If you want, I can make a **quick visual analogy showing Vulkan handles vs real GPU memory** — it helps a lot to internalize the concept.
+
+Do you want me to do that?
+
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
+
--- /dev/null
+Ah! I can see exactly why this is still stuttering, and it’s subtle but very common in Vulkan beginners’ loops. Let’s go through it carefully.
+
+---
+
+## 1️⃣ The root cause
+
+Right now, your code does this:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+vkResetCommandBuffer(cb, 0);
+...
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+* `MAX_FRAMES_IN_FLIGHT = 2` → you have **2 fences**, **2 semaphores**, and **2 command buffers**.
+* **Swapchain images = 4**.
+
+This works *sometimes*, but not reliably, because **you are recording one command buffer per frame in flight, but there are 4 swapchain images**.
+
+* If `vkAcquireNextImageKHR` returns image index 2 or 3 (which can happen randomly), you will record and submit **command buffer 0 or 1** to draw to that image.
+* If that image is still in use (GPU hasn’t finished writing to it yet), you can get **stutter or occasional tearing**.
+
+In short: **frames in flight and swapchain images are not aligned**, and your code assumes a fixed one-to-one mapping between them.
+
+---
+
+## 2️⃣ How Vulkan apps usually fix this
+
+There are **two approaches**:
+
+---
+
+### **Approach A — Command buffer per swapchain image**
+
+* Allocate **one command buffer per swapchain image**:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+* Record commands **per swapchain image** (or re-record each frame).
+* When rendering:
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* No matter which swapchain image is returned by `vkAcquireNextImageKHR`, you have a dedicated command buffer for it.
+* This is the cleanest solution if you’re recording per-frame commands dynamically.
+
+---
+
+### **Approach B — Keep frames in flight separate from swapchain images, but wait properly**
+
+* Keep `MAX_FRAMES_IN_FLIGHT` buffers/fences.
+* After acquiring the swapchain image, **check if it’s already in flight**:
+
+```c
+VkFence imageFence = imagesInFlight[imgIndex];
+if(imageFence != VK_NULL_HANDLE)
+ vkWaitForFences(device, 1, &imageFence, VK_TRUE, UINT64_MAX);
+imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+```
+
+* This ensures **you don’t overwrite a swapchain image that’s still being presented**.
+* `imagesInFlight` is an array of size `swapchainImageCount`, initialized to `VK_NULL_HANDLE`.
+
+---
+
+## 3️⃣ Other subtle stutter causes
+
+Even after fixing the above:
+
+1. **V-Sync mode (FIFO) + small floating-point inaccuracies**
+
+ * You are limiting rotation by `delta` each frame — good.
+ * But CPU/GPU timing can slightly drift → slight stutter.
+
+2. **`vkQueueWaitIdle()` or blocking calls**
+
+ * In your `create_texture_image()`, you do:
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, VK_NULL_HANDLE);
+vkQueueWaitIdle(graphicsQueue);
+```
+
+* That’s fine for setup, but make sure your **render loop doesn’t have extra waits** other than fences per frame.
+
+3. **Push constants and descriptor updates**
+
+ * Currently you only use **push constants** for rotation and a static texture → that’s fine.
+ * If you update descriptor sets per frame, that can stall if you don’t use separate sets per frame.
+
+---
+
+## ✅ Minimal fix for your current code
+
+I recommend **switching to one command buffer per swapchain image**.
+
+### Changes:
+
+```c
+// allocate command buffers per swapchain image
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+};
+vkAllocateCommandBuffers(device, &cbAlloc, commandBuffers);
+```
+
+Then, in your render loop:
+
+```c
+uint32_t imgIndex;
+vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE, &imgIndex);
+
+// use command buffer dedicated to this swapchain image
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* **No more mismatches**.
+* `currentFrame` is still used for fences/semaphores, not command buffers.
+
+---
+
+If you want, I can **rewrite your render loop fully corrected** with:
+
+* Multiple swapchain images
+* MAX_FRAMES_IN_FLIGHT fences/semaphores
+* Dynamic rendering + textures + push constant rotation
+
+so the stutter disappears completely.
+
+Do you want me to do that?
--- /dev/null
+Good catch — this is **not** a texturing problem anymore.
+What you’re seeing is **time-step stutter**, not GPU stutter.
+
+---
+
+## ✅ Root cause: frame-dependent rotation
+
+Right now you update rotation like this:
+
+```c
+angle += 0.01f;
+```
+
+That means:
+
+* Rotation speed depends on **how long the frame took**
+* If one frame takes longer (OS scheduling, compositor, swapchain wait)
+* The angle “jumps” more on that frame
+* Your eye interprets this as **texture stuttering**
+
+The texture is actually stable — the **motion is uneven**.
+
+---
+
+## Why Vulkan makes this obvious
+
+You are using:
+
+```c
+VK_PRESENT_MODE_FIFO_KHR
+```
+
+Which:
+
+* Syncs to v-blank (good)
+* Causes variable frame times when frames miss a refresh
+* Makes fixed-per-frame motion very noticeable
+
+This is **expected behavior**.
+
+---
+
+## ✅ Correct fix: time-based animation (delta time)
+
+You must rotate based on **elapsed time**, not frames.
+
+### Add these variables
+
+At the top of `main`:
+
+```c
+double lastTime = glfwGetTime();
+```
+
+### Replace your angle update with this
+
+Inside the loop:
+
+```c
+double now = glfwGetTime();
+double delta = now - lastTime;
+lastTime = now;
+
+angle += (float)(delta * 1.5); // radians per second
+```
+
+Now rotation speed is:
+
+* Smooth
+* Independent of frame rate
+* Independent of swapchain stalls
+
+---
+
+## 🎯 Why this fixes the “texture stutter”
+
+Perspective-correct interpolation is now correct
+✔ Texture is locked to geometry
+✔ Geometry moves smoothly in time
+✔ No visible swimming or jitter
+
+---
+
+## ❌ What is NOT the cause
+
+* ❌ Texture sampling
+* ❌ Descriptor sets
+* ❌ Dynamic rendering
+* ❌ Nearest vs linear filtering
+* ❌ Swapchain image count
+
+---
+
+## Optional improvements (not required)
+
+### Clamp large frame jumps (alt-tab, debugger pauses)
+
+```c
+if (delta > 0.1) delta = 0.1;
+```
+
+### Use radians explicitly (clarity)
+
+```c
+float rotationSpeed = M_PI / 2.0f; // 90°/sec
+angle += rotationSpeed * delta;
+```
+
+---
+
+## Final verdict
+
+✔ Your Vulkan pipeline is correct
+✔ Your texture mapping is correct
+✔ Your projection fix was correct
+❌ Motion was frame-rate dependent
+
+After switching to **delta-time animation**, the “texture stutter” should disappear entirely.
+
+If you want next, I can:
+
+* Add a depth buffer (very noticeable improvement)
+* Switch to indexed drawing
+* Add proper MVP matrices
+* Show how to decouple update & render timing
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
--- /dev/null
+Ah — now I see exactly what’s happening. The “stuttering” you’re seeing is **not a problem with your delta-time rotation**, but instead comes from the **way you’re submitting command buffers and waiting for fences** in your render loop.
+
+Right now, your loop looks like this (simplified):
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlight);
+
+vkAcquireNextImageKHR(..., imageAvailable, VK_NULL_HANDLE, &imgIndex);
+
+vkResetCommandBuffer(cb, 0);
+vkBeginCommandBuffer(cb, ...);
+
+// record commands
+vkCmdBeginRendering(cb, &renderingInfo);
+vkCmdBindPipeline(...);
+vkCmdDraw(...);
+vkCmdEndRendering(cb);
+
+vkEndCommandBuffer(cb);
+
+VkSubmitInfo submit = { ... };
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlight);
+
+VkPresentInfoKHR present = { ... };
+vkQueuePresentKHR(graphicsQueue, &present);
+```
+
+---
+
+## 🔴 The problem
+
+You are using **one fence (`inFlight`) for all frames**:
+
+```c
+VkFence inFlight;
+```
+
+And your loop does:
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+```
+
+This means:
+
+* Each frame **blocks until the previous frame finishes entirely** (GPU + present)
+* If your GPU is fast, this is okay
+* If there’s any fluctuation (swapchain wait, vsync), your CPU **stalls**
+* Result: **stuttery motion**, even if delta-time rotation is correct
+
+Also, you **don’t use multiple in-flight frames**, so you can’t submit the next frame until the GPU is done.
+
+---
+
+## ✅ How to fix it
+
+You need to implement **double-buffered or triple-buffered frames in flight**.
+
+### Step 1: create multiple fences
+
+```c
+#define MAX_FRAMES_IN_FLIGHT 2
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+```
+
+### Step 2: cycle through frames
+
+```c
+uint32_t currentFrame = 0;
+```
+
+Inside the loop:
+
+```c
+vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+);
+
+// record command buffer ...
+// submit:
+VkSubmitInfo submit = { ... };
+submit.waitSemaphoreCount = 1;
+submit.pWaitSemaphores = &imageAvailable[currentFrame];
+submit.signalSemaphoreCount = 1;
+submit.pSignalSemaphores = &renderFinished[currentFrame];
+
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+// present:
+VkPresentInfoKHR present = { ... };
+present.waitSemaphoreCount = 1;
+present.pWaitSemaphores = &renderFinished[currentFrame];
+
+vkQueuePresentKHR(graphicsQueue, &present);
+
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+---
+
+## Step 3: Why this fixes the stutter
+
+* Now **CPU doesn’t wait for GPU to finish the previous frame entirely**
+* You can have **two frames queued in flight**
+* Delta-time rotation is smooth
+* Swapchain image acquisition can happen asynchronously
+* Texture and cube rotation appear smooth
+
+---
+
+### Extra tip:
+
+Make sure your **swapchain present mode is FIFO (vsync)**:
+
+```c
+.preTransform = caps.currentTransform,
+.presentMode = VK_PRESENT_MODE_FIFO_KHR,
+```
+
+This ensures no tearing, but introduces a slight latency — which is fine with multiple frames in flight.
+
+---
+
+### ✅ TL;DR
+
+1. You currently block on a single fence every frame → causes stutter.
+2. Implement **multiple frames in flight** (double/triple buffering).
+3. Use **frame-indexed semaphores and fences** for acquire/submit/present.
+4. Keep delta-time rotation as you have — it will now appear smooth.
+
+---
+
+If you want, I can rewrite your **main render loop with double-buffered frames in flight** for your current code — fully Vulkan 1.3 + dynamic rendering ready.
+
+Do you want me to do that?
+
+Ah! I can see exactly why your program segfaults — it’s not a single one-liner; there are **multiple issues related to fences, semaphores, and memory allocation** in your current code. Let me break it down carefully and explain exactly what’s going wrong, and how to fix it.
+
+---
+
+## 1️⃣ Fences and semaphores creation
+
+You have:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+**Problem:**
+
+* `imageAvailable` and `renderFinished` are **arrays** of `MAX_FRAMES_IN_FLIGHT` elements.
+* `inFlightFences` is also an **array** of size `MAX_FRAMES_IN_FLIGHT`.
+* `vkCreateSemaphore` and `vkCreateFence` expect a **pointer to a single VkSemaphore/VkFence**, not an array.
+
+Right now, you’re passing `imageAvailable` (type `VkSemaphore[MAX_FRAMES_IN_FLIGHT]`) **without indexing**, so Vulkan interprets the pointer incorrectly → undefined behavior → **segfault**.
+
+**Fix:** create each semaphore/fence individually:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+}
+```
+
+✅ This is **mandatory**. Without it, your `vkWaitForFences` later will segfault because the fence handle is garbage.
+
+---
+
+## 2️⃣ Command buffers + swapchain images mismatch
+
+You allocated:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+and
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* `imgIndex` comes from `vkAcquireNextImageKHR`.
+* But `swapchainImageCount` may be **larger than MAX_FRAMES_IN_FLIGHT**.
+* Your `currentFrame` index is used for fences and semaphores.
+* This can cause **index mismatches** if you have more swapchain images than frames in flight.
+
+**Fix:** use **frame indexing consistently**:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+```
+
+Then record the command buffer for **that frame**.
+Or allocate **one command buffer per frame in flight**, not per swapchain image.
+
+---
+
+## 3️⃣ Push constants / pipeline layout
+
+You correctly defined a push constant of `sizeof(float)` for rotation.
+✅ This part is fine.
+
+---
+
+## 4️⃣ Descriptor sets
+
+You only have **1 descriptor set**, but you submit multiple frames in flight.
+
+* Technically okay since the texture is static, but if you ever update it dynamically, you’d need **one descriptor set per frame**.
+* For now, not a segfault cause.
+
+---
+
+## 5️⃣ Minor issues that can crash
+
+* `VkSurfaceFormatKHR formats[fmtCount];` — if `fmtCount` is zero, this will crash. Always check:
+
+```c
+if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+```
+
+* `vkCreateImageView` and `vkCreateBuffer` **do not check return values everywhere**. Make sure you check **every Vulkan call**.
+
+---
+
+## 6️⃣ Texture image staging
+
+* Your staging buffer is allocated, copied, and freed **synchronously**.
+* That part is fine.
+* Just make sure `vkQueueSubmit` waits for the copy to finish, which you do with `vkQueueWaitIdle`. ✅
+
+---
+
+## ✅ Corrected fence/semaphore creation
+
+Replace:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+with:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create synchronization objects\n");
+ exit(1);
+ }
+}
+```
+
+---
+
+## 7️⃣ Recommended changes
+
+1. Allocate **one command buffer per frame in flight**, not per swapchain image.
+2. Use `currentFrame` to index fences, semaphores, and command buffers.
+3. Always check Vulkan function returns.
+4. Use `VK_NULL_HANDLE` fences/semaphores carefully — do **not** pass arrays directly.
+
+---
+
+If you want, I can **rewrite your main render loop + fence/semaphore setup** fully corrected for **double-buffered frames in flight** — so it will **stop segfaulting** and run smoothly.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely! Let’s break it down carefully.
+
+---
+
+### 1️⃣ What `u` and `v` are
+
+In your struct:
+
+```c
+typedef struct {
+ float x, y, z; // 3D position in space
+ float u, v; // texture coordinates
+} CubeVertex;
+```
+
+* `x, y, z` → define the **vertex position in 3D space**.
+* `u, v` → define **where on a 2D texture image this vertex maps to**.
+
+These are often called **UV coordinates** or **texture coordinates**.
+
+---
+
+### 2️⃣ How `u` and `v` relate to the texture
+
+Textures are images. Imagine a 2D image like this:
+
+```
+(0,1) ┌─────────────┐ (1,1)
+ │ │
+ │ │
+ │ │
+(0,0) └─────────────┘ (1,0)
+```
+
+* `u` → horizontal position on the texture (0 = left edge, 1 = right edge)
+* `v` → vertical position on the texture (0 = bottom edge, 1 = top edge)
+
+> Note: In Vulkan (and OpenGL), **v=0 is usually bottom**, **v=1 is top**. Some image libraries flip this, so sometimes `v` may need to be inverted.
+
+---
+
+### 3️⃣ How this works in rendering
+
+When Vulkan draws a triangle:
+
+1. Each vertex has `(x, y, z)` for position and `(u, v)` for texture mapping.
+2. The GPU **interpolates** `u` and `v` across the triangle.
+3. For every pixel in the triangle, the GPU calculates a corresponding `(u, v)` coordinate.
+4. The pixel color is sampled from the texture using that `(u, v)` coordinate.
+
+---
+
+### 4️⃣ Example
+
+If you have a cube face made of two triangles and a 2x2 texture like this:
+
+```
+Texture:
+[white][pink]
+[pink][white]
+```
+
+And your vertices are:
+
+```c
+{-0.5f,-0.5f,-0.5f, 0,0}, // bottom-left maps to texture bottom-left
+{ 0.5f,-0.5f,-0.5f, 1,0}, // bottom-right maps to texture bottom-right
+{ 0.5f, 0.5f,-0.5f, 1,1}, // top-right maps to texture top-right
+{-0.5f, 0.5f,-0.5f, 0,1} // top-left maps to texture top-left
+```
+
+* Each vertex "grabs" the color from that corner of the texture.
+* The GPU fills the interior of the triangle with colors **interpolated** between those corners.
+
+---
+
+### ✅ TL;DR
+
+* `u` = horizontal coordinate on the texture
+* `v` = vertical coordinate on the texture
+* `(u, v)` tell Vulkan **which part of the 2D image** to apply to the 3D vertex.
+* Without `u, v`, Vulkan wouldn’t know how to map the texture to the cube.
+
+---
+
+If you want, I can make a **small visual diagram showing a cube face, its vertices, and how u,v map the texture onto it**, which usually makes it click instantly.
+
+Do you want me to do that?
+
+Absolutely! Let’s connect the dots between your **CubeVertex struct** `(x, y, z, u, v)` and how the **shader** uses it to texture your cube. I’ll break it down step by step.
+
+---
+
+## 1️⃣ Vertex Shader
+
+A typical vertex shader in Vulkan GLSL might look like this:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec3 inPos; // x, y, z from CubeVertex
+layout(location = 1) in vec2 inUV; // u, v from CubeVertex
+
+layout(location = 0) out vec2 fragUV; // pass to fragment shader
+
+layout(push_constant) uniform PushConstants {
+ float angle;
+} pc;
+
+void main() {
+ // Apply rotation around Y axis
+ float s = sin(pc.angle);
+ float c = cos(pc.angle);
+ vec3 pos = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // Convert to clip space (just simple identity for example)
+ gl_Position = vec4(pos, 1.0);
+
+ // Pass the UV coordinates to the fragment shader
+ fragUV = inUV;
+}
+```
+
+### 🔑 Notes:
+
+* `layout(location = 0) in vec3 inPos` ← gets **x, y, z** from `CubeVertex`.
+* `layout(location = 1) in vec2 inUV` ← gets **u, v** from `CubeVertex`.
+* `fragUV = inUV;` → we pass the texture coordinates **to the fragment shader**, interpolated across the triangle.
+
+So every pixel in the triangle will have a `fragUV` coordinate that is **interpolated** between the vertices.
+
+---
+
+## 2️⃣ Fragment Shader
+
+Then, in the fragment shader:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec2 fragUV; // interpolated UV
+layout(location = 0) out vec4 outColor; // final pixel color
+
+layout(set = 0, binding = 0) uniform sampler2D tex; // texture sampler
+
+void main() {
+ // Sample the texture at fragUV
+ outColor = texture(tex, fragUV);
+}
+```
+
+### 🔑 Notes:
+
+* `fragUV` = interpolated `(u,v)` coordinates from vertex shader.
+* `sampler2D tex` = your bound texture image + sampler.
+* `texture(tex, fragUV)` → returns the color of the texture at that `(u,v)` location.
+* The GPU writes `outColor` to the framebuffer.
+
+---
+
+## 3️⃣ How this maps from your CubeVertex:
+
+| CubeVertex field | Role in Shader |
+| ---------------- | ---------------------------------------------------------------------------------------- |
+| `x, y, z` | `inPos` in vertex shader → determines 3D position of vertex |
+| `u, v` | `inUV` in vertex shader → passed as `fragUV` to fragment shader → used to sample texture |
+
+**Flow:**
+
+```
+CubeVertex.u,v ---> vertex shader ---> fragUV (interpolated) ---> fragment shader ---> texture lookup ---> final pixel color
+```
+
+---
+
+## 4️⃣ Why this is important
+
+Without `u, v`:
+
+* Fragment shader wouldn’t know **which part of the texture** corresponds to each pixel.
+* Texture would be meaningless; all faces might just use the top-left pixel, for example.
+
+With `u, v`:
+
+* Each face of your cube can correctly map the texture, even after rotation.
+* The GPU interpolates `u, v` across triangles for smooth texturing.
+
+---
+
+If you want, I can also **draw a diagram showing the cube, a triangle, and how `(u,v)` coordinates are interpolated to map the texture properly**.
+
+Do you want me to make that visual?
--- /dev/null
+Ah, this is a **common misunderstanding in Vulkan** — the “3D” in `VkImageViewType` does **not mean a 3D object in the scene**, it’s about the **image resource itself** in GPU memory. Let me explain carefully.
+
+---
+
+## 1️⃣ What `viewType` really means
+
+`VkImageViewCreateInfo.viewType` can be:
+
+| Value | Meaning |
+| ------------------------- | -------------------------------------------------- |
+| `VK_IMAGE_VIEW_TYPE_1D` | The image is a 1D texture (width only) |
+| `VK_IMAGE_VIEW_TYPE_2D` | The image is a 2D texture (width × height) |
+| `VK_IMAGE_VIEW_TYPE_3D` | The image is a 3D texture (width × height × depth) |
+| `VK_IMAGE_VIEW_TYPE_CUBE` | Cube map texture (6 faces) |
+
+**Important:** This is about **how the GPU interprets the memory layout of the image**, not whether your scene is 3D.
+
+---
+
+## 2️⃣ Swapchain images are always 2D
+
+* Swapchain images are the **images that will be presented on your 2D screen**.
+* Even though your cube is a 3D object in the scene, the **swapchain image is a 2D framebuffer**.
+* Therefore, `viewType` **must remain `VK_IMAGE_VIEW_TYPE_2D`** for swapchain images.
+
+If you tried `VK_IMAGE_VIEW_TYPE_3D`:
+
+* Vulkan would expect a **3D image resource** in memory (with depth slices).
+* Swapchain images **aren’t 3D** — they only have width × height × 1.
+* `vkCreateImageView` would **fail** or behave incorrectly.
+
+---
+
+## 3️⃣ Summary
+
+* Your 3D cube **does not make the swapchain 3D**.
+* `VK_IMAGE_VIEW_TYPE_2D` is correct for **all normal onscreen rendering**.
+* `VK_IMAGE_VIEW_TYPE_3D` is only for **volumetric textures**, like 3D noise, 3D textures, or voxel data.
+
+---
+
+💡 **Analogy:**
+
+* The 3D cube is like a **3D model**.
+* The swapchain image is like the **photograph of that model** — a 2D picture.
+* The swapchain image view interprets the “photograph” — so it’s always 2D.
+
+---
+
+If you want, I can also explain **when you *would* use `VK_IMAGE_VIEW_TYPE_3D` in Vulkan** and how it differs from rendering a normal 3D scene. This is actually a pretty common point of confusion for newcomers.
+
+Do you want me to go into that?
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_cube
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+parser: objParser.c
+ gcc objParser.c -o objParser
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+Great question — this gets to **one of the core ideas Vulkan is built on**, and it’s very different from OpenGL / D3D11.
+
+I’ll explain **what binding memory means**, **why Vulkan requires it**, and **what actually happens inside the driver/GPU**.
+
+---
+
+## The short answer (intuition first)
+
+> **Creating a buffer or image does *not* allocate memory.
+> Binding memory is what gives that object actual storage.**
+
+Until you bind memory:
+
+* A `VkBuffer` or `VkImage` is just a **description**
+* It has **no backing bytes**
+* The GPU cannot read or write it
+
+Binding memory:
+
+* Assigns a **physical region of GPU-accessible memory**
+* Tells Vulkan *where* the object’s data lives
+* Fixes alignment and offset requirements
+
+---
+
+## What Vulkan objects really are
+
+### 1️⃣ `VkBuffer` / `VkImage` (no memory yet)
+
+When you do:
+
+```c
+vkCreateBuffer(...)
+vkCreateImage(...)
+```
+
+You are creating:
+
+* A **metadata object**
+* Size, format, usage flags
+* No storage
+
+Think of it like:
+
+> “I want a 2D image, 512×512, RGBA, sampled and copied into”
+
+But **no memory is reserved yet**.
+
+---
+
+### 2️⃣ `VkDeviceMemory` (raw memory block)
+
+When you do:
+
+```c
+vkAllocateMemory(...)
+```
+
+You get:
+
+* A chunk of memory
+* From a specific **heap**
+* With specific properties (HOST_VISIBLE, DEVICE_LOCAL, etc.)
+
+This memory:
+
+* Is **untyped**
+* Has no idea if it will store vertices, textures, or uniforms
+
+It’s just **bytes**.
+
+---
+
+### 3️⃣ Binding connects the two
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+vkBindImageMemory(device, image, memory, offset);
+```
+
+This says:
+
+> “This buffer/image *lives* starting at this byte offset in that memory block.”
+
+After binding:
+
+* The buffer/image has **real storage**
+* GPU accesses to that object resolve to actual memory addresses
+* Alignment requirements are guaranteed
+
+---
+
+## What binding does internally
+
+When you bind memory, the driver:
+
+1. Verifies alignment and size requirements
+2. Computes GPU virtual addresses
+3. Programs internal page tables / mappings
+4. Associates resource metadata with physical memory
+
+On modern GPUs this involves:
+
+* IOMMU / GPU MMU
+* Virtual address translation
+* Cache policy setup
+
+📌 This is why Vulkan **cannot auto-bind** memory — it would hide too many important decisions.
+
+---
+
+## Why Vulkan separates creation and binding
+
+### 🔹 Explicit memory control
+
+Vulkan allows things like:
+
+* Sub-allocating **multiple buffers in one allocation**
+* Packing resources tightly
+* Choosing different heaps per resource
+* Avoiding fragmentation
+
+Example:
+
+```c
+VkDeviceMemory bigBlock;
+vkAllocateMemory(..., &bigBlock);
+
+vkBindBufferMemory(device, vertexBuffer, bigBlock, 0);
+vkBindBufferMemory(device, indexBuffer, bigBlock, 65536);
+vkBindImageMemory (device, textureImage, bigBlock, 131072);
+```
+
+This is **impossible** in OpenGL.
+
+---
+
+### 🔹 Performance predictability
+
+The driver:
+
+* Does **zero hidden allocations**
+* Does **no implicit migrations**
+* Never guesses where to put memory
+
+You choose:
+
+* Fast GPU memory
+* CPU-visible memory
+* BAR-mapped memory
+* Unified memory
+
+---
+
+### 🔹 Multi-heap GPUs
+
+GPUs often have:
+
+* VRAM
+* System RAM
+* BAR-mapped regions
+* Cached vs uncached heaps
+
+Binding ensures:
+
+* The resource lives in a compatible heap
+* Usage flags match memory properties
+
+---
+
+## What happens if you don’t bind?
+
+If you try to use an unbound resource:
+
+* Validation layers scream
+* Behavior is **undefined**
+* GPU may fault or crash
+
+Because:
+
+> The resource literally has no address.
+
+---
+
+## Mental model (very important)
+
+```
+VkBuffer / VkImage
+ ↓ describes usage & format
+VkDeviceMemory
+ ↓ raw storage
+vkBind*Memory
+ ↓ connects them
+GPU can now access bytes
+```
+
+---
+
+## Why other APIs hide this
+
+| API | Memory model |
+| ------ | --------------------------- |
+| OpenGL | Driver decides everything |
+| D3D11 | Driver allocates implicitly |
+| Vulkan | You control everything |
+| D3D12 | Same as Vulkan |
+
+Vulkan trades **verbosity** for:
+
+* Zero surprises
+* Maximum performance
+* Precise memory control
+
+---
+
+## Key takeaway
+
+> **Binding memory is what turns a “resource description” into a “real GPU resource.”**
+
+Without binding:
+
+* No storage
+* No address
+* No GPU access
+
+---
+
+If you want next, I can:
+
+* Show a diagram of heaps → memory → buffers
+* Explain why images have stricter alignment
+* Explain aliasing & memory reuse
+* Explain how bindless / sparse resources change this
+
+Just say the word.
--- /dev/null
+Ah, that sentence is a bit dense. Let’s unpack it carefully.
+
+---
+
+### 1️⃣ Context
+
+Vulkan is a low-level API. When you create resources (like images, buffers, pipelines, etc.), it can be **expensive** in terms of time or GPU/driver work. For example, creating a pipeline involves compiling shaders, setting up internal GPU structures, etc.
+
+The sentence usually appears in **documentation about command pools or resource creation**:
+
+> *“Allow the implementation to amortize the cost of resource creation across multiple command buffers.”*
+
+---
+
+### 2️⃣ Breaking it down
+
+* **Amortize the cost:**
+ In computer science, “amortize” means **spread out a one-time expensive operation over multiple uses** so that each individual use appears cheaper.
+
+* **Resource creation:**
+ This refers to creating Vulkan objects (buffers, images, pipelines, etc.) or allocating memory for them.
+
+* **Across multiple command buffers:**
+ Instead of paying the full cost every time you create a command buffer or resource, the driver can **share internal data or precompute things**, so that subsequent command buffers don’t have to redo all the work.
+
+---
+
+### 3️⃣ In practical Vulkan terms
+
+* If a command pool or pipeline is created with the right flags (like `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`), the driver might:
+
+ * Reuse internal memory for command buffers
+ * Cache GPU-side structures
+ * Avoid redundant validations
+* This **reduces the overhead per command buffer**, especially when creating many of them.
+
+---
+
+### 4️⃣ Analogy
+
+Think of it like **printing tickets at a theme park**:
+
+* Printing one ticket from scratch each time is slow.
+* If the printer can **preprint some templates or share ink across tickets**, then each ticket after the first is faster.
+
+That’s what “amortizing the cost” means in Vulkan: the driver can share the work of resource creation across multiple command buffers or operations, instead of starting from scratch every time.
+
+---
+
+If you want, I can make a **tiny diagram showing how Vulkan command buffers and resource creation share internal data** — it makes “amortization” very visual.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely — this is a **great** function to walk through, because it touches almost every core Vulkan concept: memory types, staging, layout transitions, and synchronization.
+
+I’ll go **top → bottom**, explaining **what each block does and *why***.
+I’ll avoid hand-wavy language and stick to concrete GPU/CPU behavior.
+
+---
+
+# `create_texture_image()` — line-by-line walkthrough
+
+---
+
+## 1️⃣ CPU-side pixel data
+
+```c
+uint32_t pixels[] = {
+ 0xFFFFFFFF, 0xFFFF69B4,
+ 0xFFFF69B4, 0xFFFFFFFF
+};
+```
+
+* This is a **2×2 RGBA texture** stored in **CPU memory**
+* Each `uint32_t` is `0xAABBGGRR` (little-endian)
+* Vulkan does **not** care where this comes from (file, procedural, etc.)
+
+---
+
+```c
+VkDeviceSize size = sizeof(pixels);
+```
+
+* Size of the upload = **16 bytes**
+* Used for buffer creation and `memcpy`
+
+---
+
+## 2️⃣ Create a staging buffer (CPU → GPU bridge)
+
+### Why a staging buffer?
+
+Because **DEVICE_LOCAL images are not CPU-mappable** on discrete GPUs.
+
+---
+
+```c
+VkBuffer staging;
+VkDeviceMemory stagingMem;
+```
+
+Handles for:
+
+* A temporary buffer
+* The memory backing it
+
+---
+
+### Create the buffer object
+
+```c
+VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+};
+vkCreateBuffer(device, &bufInfo, NULL, &staging);
+```
+
+This creates a **buffer object** that:
+
+* Holds `size` bytes
+* Can be used as a **transfer source**
+
+⚠️ No memory is allocated yet — this is just the *buffer description*.
+
+---
+
+### Query memory requirements
+
+```c
+VkMemoryRequirements memReq;
+vkGetBufferMemoryRequirements(device, staging, &memReq);
+```
+
+The driver tells you:
+
+* Required memory size
+* Required alignment
+* Which memory types are allowed (`memoryTypeBits`)
+
+---
+
+### Allocate HOST_VISIBLE + HOST_COHERENT memory
+
+```c
+VkMemoryAllocateInfo alloc = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+};
+```
+
+This means:
+
+| Flag | Meaning |
+| --------------- | ---------------------- |
+| `HOST_VISIBLE` | CPU can map it |
+| `HOST_COHERENT` | No manual flush needed |
+
+So:
+
+* CPU can write
+* GPU can read
+* Writes are automatically visible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &stagingMem);
+vkBindBufferMemory(device, staging, stagingMem, 0);
+```
+
+Now:
+
+* Physical memory is allocated
+* The buffer is bound to that memory
+
+---
+
+### Map and copy pixels
+
+```c
+void* data;
+vkMapMemory(device, stagingMem, 0, size, 0, &data);
+memcpy(data, pixels, size);
+vkUnmapMemory(device, stagingMem);
+```
+
+What happens:
+
+* CPU gets a pointer
+* Copies 16 bytes
+* Unmaps (flush is automatic because COHERENT)
+
+📌 **At this point, pixel data is in GPU-visible memory**
+
+---
+
+## 3️⃣ Create the GPU image (the actual texture)
+
+---
+
+```c
+VkImageCreateInfo img = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {2, 2, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage =
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+};
+vkCreateImage(device, &img, NULL, &textureImage);
+```
+
+This describes:
+
+* A **2D image**
+* 2×2 pixels
+* Optimal tiling (GPU-friendly layout)
+* Can be:
+
+ * Copied **into**
+ * Sampled by shaders
+
+Still **no memory allocated** yet.
+
+---
+
+### Allocate DEVICE_LOCAL memory
+
+```c
+vkGetImageMemoryRequirements(device, textureImage, &memReq);
+
+alloc.allocationSize = memReq.size;
+alloc.memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+```
+
+This means:
+
+* Memory physically close to the GPU
+* Fast for sampling
+* Not CPU-accessible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &textureMemory);
+vkBindImageMemory(device, textureImage, textureMemory, 0);
+```
+
+Now:
+
+* Image storage exists
+* GPU can access it efficiently
+
+---
+
+## 4️⃣ Record a one-time command buffer
+
+### Why?
+
+Because **copies and layout transitions happen on the GPU**
+
+---
+
+```c
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1
+};
+vkAllocateCommandBuffers(device, &cbAlloc, &cb);
+```
+
+---
+
+```c
+VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+};
+vkBeginCommandBuffer(cb, &begin);
+```
+
+---
+
+## 5️⃣ Layout transition: UNDEFINED → TRANSFER_DST
+
+```c
+VkImageMemoryBarrier toTransfer = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .image = textureImage,
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+};
+```
+
+Why?
+
+* Vulkan images start in **UNDEFINED**
+* Copying requires **TRANSFER_DST**
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toTransfer
+);
+```
+
+This tells Vulkan:
+
+> “Before transfer writes, make sure the image is ready.”
+
+---
+
+## 6️⃣ Copy buffer → image
+
+```c
+VkBufferImageCopy copy = {
+ .imageSubresource = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1
+ },
+ .imageExtent = {2, 2, 1}
+};
+```
+
+---
+
+```c
+vkCmdCopyBufferToImage(
+ cb,
+ staging,
+ textureImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1, ©
+);
+```
+
+GPU copies the data.
+
+📌 This is **very fast** — no CPU involvement.
+
+---
+
+## 7️⃣ Layout transition: TRANSFER_DST → SHADER_READ
+
+```c
+VkImageMemoryBarrier toSample = toTransfer;
+toSample.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+toSample.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+toSample.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+toSample.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+```
+
+Now the image becomes:
+
+* Readable by fragment shaders
+* Cache-safe
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toSample
+);
+```
+
+---
+
+## 8️⃣ Submit and wait (safely)
+
+```c
+vkEndCommandBuffer(cb);
+```
+
+---
+
+```c
+VkFence fence;
+VkFenceCreateInfo fenceInfo = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
+};
+vkCreateFence(device, &fenceInfo, NULL, &fence);
+```
+
+Fence lets CPU know when GPU is done.
+
+---
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, fence);
+vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+vkDestroyFence(device, fence, NULL);
+```
+
+CPU blocks until:
+
+* Copy completes
+* Image is ready for sampling
+
+✅ **Much better than `vkQueueWaitIdle`**
+
+---
+
+## 9️⃣ Cleanup staging resources
+
+```c
+vkFreeCommandBuffers(device, commandPool, 1, &cb);
+vkDestroyBuffer(device, staging, NULL);
+vkFreeMemory(device, stagingMem, NULL);
+```
+
+Staging buffer is no longer needed.
+
+---
+
+# Final mental model (important)
+
+```
+CPU RAM
+ ↓ memcpy
+HOST_VISIBLE buffer
+ ↓ GPU copy
+DEVICE_LOCAL image
+ ↓ shader sampling
+fragment shader
+```
+
+---
+
+## You did this function **correctly**
+
+* Correct memory types
+* Correct layout transitions
+* Correct synchronization
+* Correct use of fences
+* Correct staging pattern
+
+This function is **textbook Vulkan**.
+
+If you want next, I can:
+
+* Annotate it with **timeline diagrams**
+* Show how to batch uploads
+* Explain how this changes with BAR memory
+* Explain why this does *not* cause stutter
+
+Just tell me 👍
+
--- /dev/null
+In this context, **“depth” means the per-pixel (or per-sample) distance from the camera used to decide what is in front of what**.
+
+I’ll explain it from the ground up, then tie it directly to **render passes and subpasses**.
+
+---
+
+## 1. What “depth” is (fundamental idea)
+
+**Depth** is a numerical value that represents **how far a fragment is from the camera**.
+
+For every pixel the GPU draws, it computes a depth value:
+
+```
+closer to camera → smaller depth
+farther away → larger depth
+```
+
+This value is stored in a **depth buffer** (also called a **Z buffer**).
+
+---
+
+## 2. Why depth exists at all
+
+Without depth, triangles would be drawn **in submission order**:
+
+```
+draw triangle A
+draw triangle B
+```
+
+If B is behind A but drawn later → it appears in front ❌
+
+Depth fixes this by answering:
+
+> “Is this fragment closer than what was drawn before?”
+
+---
+
+## 3. How depth testing works
+
+For each fragment:
+
+1. Compute its depth (from vertex positions → clip space)
+2. Compare it against the stored depth value
+3. If it passes → draw and update depth
+4. If it fails → discard fragment
+
+Typical comparison:
+
+```c
+VK_COMPARE_OP_LESS
+```
+
+Meaning:
+
+> “Only draw if this fragment is closer.”
+
+---
+
+## 4. Where depth comes from (pipeline view)
+
+### Vertex shader
+
+Outputs:
+
+```glsl
+gl_Position
+```
+
+This contains **clip-space coordinates**.
+
+### After projection
+
+The GPU computes:
+
+```
+depth = z / w
+```
+
+Mapped into:
+
+```
+[0, 1] (Vulkan default)
+```
+
+This is what ends up in the depth buffer.
+
+---
+
+## 5. The depth buffer itself
+
+Depth is stored in a **depth attachment**, just like color is stored in a color attachment.
+
+Common formats:
+
+* `VK_FORMAT_D32_SFLOAT`
+* `VK_FORMAT_D24_UNORM_S8_UINT`
+
+Depth attachments:
+
+* Are not visible on screen
+* Exist only for visibility testing (and sometimes reading later)
+
+---
+
+## 6. Depth + subpasses (this is what you’re asking about)
+
+When you see:
+
+```
+Subpass 0: write color + depth
+Subpass 1: read depth, write color
+```
+
+It means:
+
+### Subpass 0
+
+* Draw geometry
+* Write:
+
+ * Color → color attachment
+ * Depth → depth attachment
+
+This establishes **visibility information**.
+
+---
+
+### Subpass 1
+
+* Does **not redraw geometry**
+* Reads depth to know:
+
+ * Which pixels belong to visible surfaces
+ * How far they are from the camera
+
+Used for:
+
+* Deferred lighting
+* Screen-space effects
+* Depth-aware post-processing
+
+---
+
+## 7. Reading depth in a later subpass
+
+Depth can be read as:
+
+### A) Input attachment (subpass-local, fast)
+
+```glsl
+subpassInput depthInput;
+```
+
+### B) Sampled image (general, slower)
+
+```glsl
+sampler2D depthTex;
+```
+
+This allows effects like:
+
+* Screen-space ambient occlusion (SSAO)
+* Volumetric fog
+* Soft particles
+* Deferred lighting
+
+---
+
+## 8. Depth vs color (key difference)
+
+| Color | Depth |
+| ------------ | ---------------------- |
+| What you see | What is visible |
+| RGB(A) | Single scalar value |
+| Displayed | Used for testing |
+| Final output | Internal decision data |
+
+Depth controls **visibility**, not appearance.
+
+---
+
+## 9. Depth and multisampling (important)
+
+With MSAA:
+
+* Each pixel has **multiple depth samples**
+* Each sample can pass/fail independently
+* This is why MSAA fixes edge aliasing correctly
+
+Depth must use the **same sample count** as color.
+
+---
+
+## 10. What depth is NOT
+
+❌ Not “distance in meters”
+❌ Not linear by default
+❌ Not screen-space Z
+❌ Not optional for 3D rendering
+
+Depth is **non-linear**, optimized for precision near the camera.
+
+---
+
+## 11. Mental model (best way to remember)
+
+> **Depth is a hidden image that stores “how close” each pixel is, and the GPU uses it to decide what is allowed to be drawn.**
+
+---
+
+## 12. One-sentence summary
+
+> **Depth is a per-pixel value stored in a depth buffer that allows the GPU to determine which fragments are visible and to reuse that visibility information across subpasses.**
+
+---
+
+If you want, I can next:
+
+* Show **exact Vulkan structs for a depth attachment**
+* Explain **why depth is non-linear**
+* Walk through **depth testing step-by-step**
+* Explain **stencil vs depth** (they’re often confused)
+
+Just tell me.
--- /dev/null
+Got it — you’re asking **why depth is “not optional” for 3D rendering**.
+
+This is a *fundamental* reason tied to how projection and visibility work, not just a Vulkan convention.
+
+---
+
+## Short answer (intuition)
+
+**Because in 3D, multiple surfaces project onto the same pixel, and without depth there is no correct way to know which one is visible.**
+
+2D doesn’t have this problem.
+3D does.
+
+---
+
+## What actually goes wrong without depth
+
+In 3D, after projection, **everything becomes 2D**:
+
+```
+3D scene → projection → screen pixels
+```
+
+Multiple triangles can map to the **same pixel**:
+
+```
+Camera
+ ↓
+[Triangle A] ← closer
+[Triangle B] ← farther
+```
+
+On screen:
+
+```
+Pixel (x, y)
+```
+
+Without depth, the GPU only knows **draw order**.
+
+---
+
+## Case 1: No depth buffer (painter’s algorithm)
+
+If you don’t use depth:
+
+```c
+vkCmdDraw(triangleA);
+vkCmdDraw(triangleB);
+```
+
+Whichever triangle is drawn **last** wins.
+
+This causes:
+
+* Objects popping in front of each other
+* Incorrect overlap
+* Massive CPU-side sorting requirements
+* Failure in cases like:
+
+ * Intersecting geometry
+ * Self-overlapping meshes
+ * Transparent + opaque mixes
+
+This is **not solvable in general**.
+
+---
+
+## Case 2: With depth buffer (correct solution)
+
+With depth testing:
+
+1. GPU stores depth per pixel
+2. For every fragment:
+
+ * Compare new depth vs stored depth
+ * Keep the closest one
+3. Order of draw calls **no longer matters**
+
+This gives:
+
+✔ Correct visibility
+✔ Intersecting geometry works
+✔ Self-occlusion works
+✔ Parallel GPU execution works
+
+---
+
+## Why the GPU *must* do this, not the CPU
+
+### You cannot sort triangles correctly in 3D
+
+There is **no total ordering** for triangles in 3D space.
+
+Example:
+
+* Triangle A overlaps B
+* B overlaps C
+* C overlaps A
+
+This is called a **cyclic overlap**.
+
+No draw order can fix this.
+
+Depth testing resolves it **per pixel**, which is the only correct level.
+
+---
+
+## Why this is fundamentally required by projection
+
+Perspective projection collapses depth:
+
+```
+(x, y, z) → (x / z, y / z)
+```
+
+That division by `z` destroys ordering information.
+
+Depth restores it.
+
+---
+
+## Can you “fake” 3D without depth?
+
+Yes, but with **severe limitations**:
+
+### When depth is *not* required:
+
+* Skyboxes
+* Fullscreen quads
+* Billboards drawn in strict order
+* 2.5D games (fixed layers)
+
+These avoid the general 3D visibility problem.
+
+---
+
+## Why Vulkan treats depth as a first-class concept
+
+Vulkan gives depth:
+
+* Its own attachment
+* Its own load/store ops
+* Its own layouts
+* Its own tests
+* Its own sampling path
+
+Because **the entire rasterization pipeline assumes depth exists**.
+
+---
+
+## The key mental model
+
+> **3D rendering is fundamentally a visibility problem.
+> Depth is the data structure that solves it.**
+
+Without depth, you don’t have 3D — you have a fragile illusion.
+
+---
+
+## One-sentence answer
+
+> **Depth is not optional in 3D rendering because projection causes multiple surfaces to map to the same pixel, and only per-pixel depth testing can correctly determine which surface is visible.**
+
+If you want, next we can:
+
+* Prove why triangle sorting is impossible in general
+* Walk through depth testing in Vulkan line-by-line
+* Explain why depth precision breaks at far distances
+* Show how deferred renderers rely entirely on depth
+
+Just say where you want to go.
--- /dev/null
+This is a **fundamental Vulkan question**, and the confusion is very normal. The short answer is:
+
+> **`malloc` / `calloc` allocate CPU memory.
+> `vkAllocateMemory` allocates GPU-accessible device memory.
+> They live in completely different worlds.**
+
+Let’s break it down cleanly and precisely.
+
+---
+
+## 1️⃣ `malloc` / `calloc` vs `vkAllocateMemory`
+
+### `malloc` / `calloc`
+
+* Allocates **process virtual memory**
+* Owned by the **CPU / OS**
+* Only the CPU can access it
+* The GPU **cannot see or use it**
+* Vulkan has **no idea** this memory exists
+
+```c
+void* data = malloc(1024); // CPU RAM only
+```
+
+You **cannot** bind this memory to:
+
+* `VkBuffer`
+* `VkImage`
+* Any Vulkan object
+
+---
+
+### `vkAllocateMemory`
+
+* Allocates memory from the **physical device**
+* Managed by **Vulkan + the GPU driver**
+* Can be:
+
+ * GPU-only (VRAM)
+ * CPU-visible (mapped system memory)
+* Can be **bound** to buffers and images
+
+```c
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+vkBindBufferMemory(device, buffer, memory, 0);
+```
+
+This is the **only way** Vulkan objects get usable memory.
+
+---
+
+## 2️⃣ Why Vulkan can’t use `malloc`
+
+The GPU:
+
+* Does not understand your process’s virtual address space
+* Does not participate in `malloc`
+* Cannot dereference CPU pointers
+
+Even if memory is physically the same (iGPU / UMA):
+
+* Vulkan still needs **explicit ownership, caching, and visibility rules**
+* The driver must know how the memory is used
+
+So Vulkan **must** control allocation.
+
+---
+
+## 3️⃣ Why Vulkan separates allocation from objects
+
+In OpenGL:
+
+* `glBufferData` silently allocates memory for you
+* You don’t control where or how
+
+In Vulkan:
+
+* You control:
+
+ * Memory location
+ * Cache behavior
+ * Sharing
+ * Lifetime
+
+This enables:
+
+* Zero-copy paths
+* Predictable performance
+* No hidden stalls
+
+---
+
+## 4️⃣ What Vulkan memory really is
+
+Think of Vulkan memory as:
+
+> A **handle to a chunk of physical device memory**,
+> not a pointer.
+
+```c
+VkDeviceMemory mem; // not a pointer!
+```
+
+The GPU accesses it through:
+
+* Bindings
+* Descriptors
+* Command buffers
+
+The CPU accesses it **only if HOST_VISIBLE**, via `vkMapMemory`.
+
+---
+
+## 5️⃣ Why staging buffers exist
+
+Because:
+
+* GPU VRAM is **fast but CPU-inaccessible**
+* CPU RAM is **slow but CPU-accessible**
+
+So Vulkan uses a two-step approach:
+
+```text
+CPU RAM (malloc / staging)
+ ↓ copy
+GPU VRAM (vkAllocateMemory DEVICE_LOCAL)
+```
+
+Except the staging buffer is still allocated via Vulkan,
+because the GPU must be able to read from it.
+
+---
+
+## 6️⃣ Common mistake (you avoided it)
+
+❌ This does NOT work:
+
+```c
+float* verts = malloc(sizeof(vertices));
+vkBindBufferMemory(buffer, (VkDeviceMemory)verts, 0); // invalid
+```
+
+Because:
+
+* That memory is not tracked by Vulkan
+* The GPU cannot see it
+
+---
+
+## 7️⃣ Why Vulkan memory feels “manual”
+
+Because it is designed for:
+
+* Engines
+* Drivers
+* Explicit control
+* Zero hidden sync
+
+Vulkan trades convenience for **absolute predictability**.
+
+---
+
+## TL;DR
+
+* `malloc` / `calloc` → CPU memory only
+* `vkAllocateMemory` → GPU-accessible device memory
+* Vulkan objects **must** use `vkAllocateMemory`
+* The GPU **cannot see malloc’d memory**
+* Explicit control = performance + no hidden stalls
+
+---
+
+If you want, next we can:
+
+* Walk through a **full buffer allocation lifecycle**
+* Explain **BAR memory vs VRAM** in more depth
+* Show how Vulkan allocators (VMA) work internally
+* Tie this back directly to your stutter issue
+
+Just tell me 👍
--- /dev/null
+Yes — this is a *really important* Vulkan concept, and you’re already **using it correctly** in your code. I’ll explain it from the ground up and tie it directly to what you wrote.
+
+---
+
+# 1️⃣ What “dynamic rendering” is (high-level)
+
+**Dynamic rendering** is a newer Vulkan feature that lets you:
+
+> **Render without creating a `VkRenderPass` or `VkFramebuffer`.**
+
+Instead of baking attachments and subpasses ahead of time, you:
+
+* Describe the attachments **at draw time**
+* Begin rendering **directly in the command buffer**
+* Bind pipelines that declare what formats they expect
+
+It removes a *huge* amount of boilerplate.
+
+---
+
+# 2️⃣ What Vulkan looked like *before* dynamic rendering
+
+### Old model (classic Vulkan)
+
+You had to create:
+
+1. `VkRenderPass`
+
+ * Attachments
+ * Subpasses
+ * Load/store ops
+2. `VkFramebuffer`
+
+ * One per swapchain image
+3. Pipeline tied **per render pass**
+
+```text
+RenderPass → Framebuffer → Pipeline → Draw
+```
+
+Problems:
+
+* Render passes were **compile-time objects**
+* Changing formats or attachments required rebuilding pipelines
+* Framebuffers had to match exactly
+
+This was powerful but **extremely rigid**.
+
+---
+
+# 3️⃣ What dynamic rendering replaces
+
+Dynamic rendering **removes**:
+
+| Old object | Replaced by |
+| -------------------- | ----------- |
+| `VkRenderPass` | ❌ Gone |
+| `VkFramebuffer` | ❌ Gone |
+| Subpass descriptions | ❌ Gone |
+
+Instead, Vulkan introduces:
+
+* `vkCmdBeginRendering`
+* `VkRenderingInfo`
+* `VkRenderingAttachmentInfo`
+* `VkPipelineRenderingCreateInfo`
+
+---
+
+# 4️⃣ What *you* are doing with dynamic rendering
+
+Let’s map your code to the concept.
+
+---
+
+## A) Enabling dynamic rendering on the device
+
+```c
+VkPhysicalDeviceDynamicRenderingFeatures dyn = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+};
+```
+
+This says:
+
+> “This device is allowed to use `vkCmdBeginRendering`.”
+
+Without this → **validation error / undefined behavior**.
+
+---
+
+## B) Declaring attachment formats in the pipeline
+
+```c
+VkPipelineRenderingCreateInfo rendering = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+};
+```
+
+This replaces:
+
+* `VkRenderPass`
+* Subpass attachment format matching
+
+It tells Vulkan:
+
+> “This pipeline expects **one color attachment** with this format.”
+
+Pipelines still need to know formats **up-front**, but not layouts, load ops, or framebuffers.
+
+---
+
+## C) Beginning rendering (the new render pass)
+
+```c
+vkCmdBeginRendering(cb, &ri);
+```
+
+This is the **dynamic equivalent** of:
+
+```c
+vkCmdBeginRenderPass(...)
+```
+
+Except:
+
+* No framebuffer
+* No render pass object
+* No subpass index
+
+---
+
+## D) Describing attachments at draw time
+
+```c
+VkRenderingAttachmentInfo color = {
+ .imageView = swapchainViews[img],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue.color = {{0,0,0,1}}
+};
+```
+
+This replaces:
+
+```text
+VkAttachmentDescription
+VkAttachmentReference
+VkSubpassDescription
+```
+
+All **combined**, but now:
+
+* Fully dynamic
+* Chosen per frame
+* Chosen per command buffer
+
+---
+
+## E) Ending rendering
+
+```c
+vkCmdEndRendering(cb);
+```
+
+Equivalent to:
+
+```c
+vkCmdEndRenderPass(cb);
+```
+
+---
+
+# 5️⃣ What dynamic rendering does **NOT** change
+
+Dynamic rendering does **not** change:
+
+| Thing | Still required |
+| ------------------ | -------------- |
+| Pipelines | ✅ |
+| Descriptor sets | ✅ |
+| Layout transitions | ✅ |
+| Synchronization | ✅ |
+| Image views | ✅ |
+
+This is why you still need:
+
+```c
+VkImageMemoryBarrier
+```
+
+Dynamic rendering does **not** manage layouts for you.
+
+---
+
+# 6️⃣ Why dynamic rendering exists
+
+### Goals:
+
+* Reduce boilerplate
+* Make Vulkan usable for small engines
+* Allow more flexible rendering graphs
+* Match modern APIs (DX12 / Metal)
+
+### Typical wins:
+
+* No framebuffer creation
+* No render pass combinatorics
+* Easier swapchain recreation
+* Much easier multipass rendering
+
+---
+
+# 7️⃣ How dynamic rendering maps conceptually
+
+Think of it like this:
+
+### Classic Vulkan
+
+```text
+RenderPass (static)
+ └─ Subpass (static)
+ └─ Attachments (static)
+```
+
+### Dynamic Rendering
+
+```text
+Command Buffer
+ └─ Begin Rendering
+ └─ Attachments (chosen now)
+```
+
+You move **render-graph decisions** from *initialization time* to *record time*.
+
+---
+
+# 8️⃣ Common misconceptions (important)
+
+### ❌ “Dynamic rendering is slower”
+
+No. Drivers internally build render passes anyway.
+
+### ❌ “Dynamic rendering removes the need for barriers”
+
+Absolutely not. You still must manage layouts and sync.
+
+### ❌ “Dynamic rendering is optional”
+
+Yes — but it’s becoming the **preferred path**.
+
+---
+
+# 9️⃣ When NOT to use dynamic rendering
+
+You might still want classic render passes if:
+
+* You need **subpass input attachments**
+* You rely on **tile-based GPU subpass optimizations**
+* You want strict render-graph control
+
+Most beginner/intermediate engines should **use dynamic rendering**.
+
+---
+
+# 🔑 One-sentence summary
+
+> **Dynamic rendering lets you render directly to images without pre-declaring render passes or framebuffers, by describing attachments dynamically inside the command buffer.**
+
+---
+
+If you want next, I can:
+
+* Rewrite your code in **classic render-pass style** to compare
+* Explain **why subpasses exist** and when they outperform dynamic rendering
+* Show **depth + MSAA** with dynamic rendering
+* Show how dynamic rendering maps to **Vulkan synchronization2**
+
+Just tell me.
--- /dev/null
+Absolutely — this is a **core Vulkan helper**, and understanding it deeply will make a lot of other Vulkan code “click”. I’ll go **line by line**, then give you a **mental model** at the end.
+
+---
+
+## Function signature
+
+```c
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props)
+```
+
+### What this function does (high level)
+
+It finds **which GPU memory type index** you should use when allocating memory for a buffer or image.
+
+Vulkan does **not** let you just say “give me GPU memory”.
+Instead, you must:
+
+1. Ask Vulkan **what kinds of memory are allowed** (`typeBits`)
+2. Ask for **what properties you want** (`props`)
+3. Pick a memory **type index** that satisfies both
+
+This function does exactly that.
+
+---
+
+## Get physical device memory info
+
+```c
+VkPhysicalDeviceMemoryProperties memProps;
+vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+```
+
+### What’s happening
+
+* Vulkan exposes **all memory types supported by the GPU**
+* This includes:
+
+ * Device-local VRAM
+ * Host-visible memory
+ * Coherent / cached memory
+ * Unified memory (on iGPUs)
+
+`memProps` contains:
+
+```c
+memProps.memoryTypeCount
+memProps.memoryTypes[i].propertyFlags
+```
+
+Typical GPU: **8–32 memory types**
+
+---
+
+## Loop over every memory type
+
+```c
+for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+```
+
+You are checking **each possible memory type index**.
+
+Important:
+
+* The return value of this function is **this index (`i`)**
+* This index is what you pass to `VkMemoryAllocateInfo.memoryTypeIndex`
+
+---
+
+## Check if this memory type is allowed
+
+```c
+if ((typeBits & (1 << i)) &&
+```
+
+### What `typeBits` means
+
+`typeBits` comes from:
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+```
+
+* `req.memoryTypeBits` is a **bitmask**
+* Each bit corresponds to a memory type index
+* If bit `i` is set → that memory type **can be used** for this resource
+
+### This line checks:
+
+> “Is memory type `i` compatible with this buffer/image?”
+
+If not, skip it.
+
+---
+
+## Check if it has the properties you want
+
+```c
+(memProps.memoryTypes[i].propertyFlags & props) == props)
+```
+
+### `propertyFlags` examples
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+VK_MEMORY_PROPERTY_HOST_CACHED_BIT
+```
+
+### This line means:
+
+> “Does this memory type include **all** the properties I requested?”
+
+Why bitwise AND?
+
+* `propertyFlags` may have **extra bits**
+* You only care that **your requested bits are present**
+
+Example:
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+```
+
+This is valid.
+
+---
+
+## If both conditions pass → success
+
+```c
+return i;
+```
+
+You’ve found:
+
+* A memory type that **works for this resource**
+* With **the properties you want**
+
+You’re done.
+
+---
+
+## If no memory type matches
+
+```c
+fprintf(stderr, "No suitable memory type\n");
+exit(1);
+```
+
+This should **never happen** in a correct Vulkan program.
+
+If it does:
+
+* You requested impossible memory properties
+* Or passed the wrong `typeBits`
+* Or you misunderstood host vs device memory
+
+---
+
+## Mental model (this is the key)
+
+Think of Vulkan memory like this:
+
+> “I have a list of memory pools.
+> Each pool has certain capabilities.
+> Each resource tells me which pools it is allowed to use.
+> I must pick one pool that satisfies both.”
+
+This function answers:
+
+> **“Which pool index should I use?”**
+
+---
+
+## Example usage (buffer allocation)
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+
+uint32_t memoryType = find_memory_type(
+ req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+);
+```
+
+This means:
+
+> “Find a memory type that
+> ✔ the buffer supports
+> ✔ I can map from CPU
+> ✔ doesn’t require manual flush/invalidate”
+
+---
+
+## Why this function is critical
+
+Every Vulkan program needs it for:
+
+* Vertex buffers
+* Index buffers
+* Uniform buffers
+* Staging buffers
+* Images
+* Depth buffers
+* Textures
+
+If you understand **this function**, you understand:
+
+* Why Vulkan memory is explicit
+* Why performance is predictable
+* Why Vulkan avoids hidden stalls
+
+---
+
+If you want next, I can:
+
+* Draw a **diagram of Vulkan memory types**
+* Show **which memory properties to use for each buffer**
+* Explain **why HOST_VISIBLE is slower**
+* Explain **why staging buffers exist**
+
+Just tell me which one.
--- /dev/null
+Ah, now we’re getting into a subtle Vulkan topic: **swapchain image count vs performance**. Let’s break it down carefully.
+
+---
+
+## 1️⃣ How the swapchain works
+
+A Vulkan swapchain is basically a **queue of images** (framebuffers) the GPU can render into and the presentation engine (display) can show.
+
+* `vkCreateSwapchainKHR` lets you specify `minImageCount` — the number of images.
+* Typical values: **2–3** (double or triple buffering).
+
+Each image can be in one of three states:
+
+1. **Available** — ready to render to
+2. **Rendering** — GPU is drawing into it
+3. **Presenting** — waiting to be shown on screen
+
+---
+
+## 2️⃣ Double vs Triple Buffering
+
+| # Images | Behavior | Pros | Cons |
+| ---------- | ------------------------------------------------ | ---------------------------------------- | ---------------------------------------- |
+| 2 (double) | GPU renders to 1 image, display shows the other | Less memory usage, lower latency | GPU may stall if display is slow |
+| 3 (triple) | GPU has 1 image ready, 1 rendering, 1 displaying | GPU never waits for display → higher FPS | More memory usage, slightly more latency |
+
+✅ Triple buffering can **improve throughput** because the GPU always has an image to render to.
+❌ But adding **more than 3–4 images rarely helps** — the GPU already pipelines well.
+
+---
+
+## 3️⃣ Latency vs performance tradeoff
+
+* **More images → higher FPS potential** (GPU never blocks)
+* **More images → higher input/display latency** (you’re rendering frames that are “ahead” of the screen)
+
+For games:
+
+* Triple buffering is common for smooth FPS
+* Double buffering is often enough for low-latency apps
+
+---
+
+## 4️⃣ Vulkan-specific notes
+
+* Some GPU drivers **require at least 2 images**.
+
+* The swapchain must have **at least `surfaceCapabilities.minImageCount`** images.
+
+* If you request more than `surfaceCapabilities.maxImageCount` → creation fails.
+
+* Vulkan allows you to acquire the **next available image** via `vkAcquireNextImageKHR`. If all images are in use, the GPU may block until one becomes free. More images = less chance of blocking.
+
+---
+
+### ✅ TL;DR
+
+* **2 images:** safe, low memory, slightly higher chance GPU waits
+* **3 images:** smoother, less chance of GPU stall, slightly more latency
+* **4+ images:** rarely improves FPS, just uses more VRAM
+
+So yes, **more images *can* improve throughput**, but only up to a point (usually 3). Beyond that, it’s mostly wasted memory.
+
+---
+
+If you want, I can draw a **diagram showing the swapchain states over time** — it makes it super clear why 3 images can be smoother than 2.
+
+Do you want me to do that?
--- /dev/null
+Feature,Pattern Rule (%),Wildcard (*)
+Purpose,Defining a transformation rule.,Listing existing files.
+Usage,%.o: %.c,rm *.o
+Timing,Evaluated when make decides what to do.,Evaluated by the shell when the
+command runs. Example,"""How to turn any X into Y.""","""Do something to
+everything that ends in .X."""
+
+Symbol,What it does,Resulting Logic %.spv: %.vert,Links the names.,"""To get
+player.spv, look for player.vert.""" *.spv: *.vert,Literal expansion.,"""To get
+all spv files, look for all vert files."""
--- /dev/null
+Ah, yes — this is a subtle but very important distinction. It’s easy to get confused because Vulkan separates **allocation** from **binding**, and the wording can make it seem like they both “give memory” to the buffer. Let’s clarify carefully.
+
+---
+
+### Step 1: `vkAllocateMemory`
+
+When you call:
+
+```c
+VkDeviceMemory memory;
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+```
+
+You are **reserving a chunk of memory from the GPU (or a heap)**.
+
+Think of it like **booking a block of storage in a warehouse**:
+
+* `VkDeviceMemory` is just a handle that represents that chunk.
+* The memory **exists**, but nothing is stored in it yet.
+* The GPU doesn’t yet know which buffer or image will use it.
+
+It’s untyped, unbound storage — just “here are these bytes in GPU-accessible memory.”
+
+---
+
+### Step 2: `vkBindBufferMemory`
+
+When you call:
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+```
+
+You are saying:
+
+> “Buffer X will **live starting at this offset in the allocated memory block**.”
+
+Now:
+
+* The buffer has a **valid GPU address**.
+* The GPU can access it when drawing or copying.
+* Alignment and memory type are enforced.
+
+It’s like taking that chunk of warehouse space and **assigning it to a specific shelf (the buffer)**.
+
+---
+
+### Analogy
+
+| Vulkan Step | Analogy |
+| -------------------- | --------------------------------------------------------------------------------------- |
+| `vkAllocateMemory` | Renting a warehouse block of storage (size, type, location) |
+| `vkBindBufferMemory` | Putting a specific item (buffer or image) into a specific shelf in that warehouse block |
+
+---
+
+### Why not do it all in one call?
+
+Vulkan separates the two because:
+
+1. **Memory suballocation:**
+ One `VkDeviceMemory` block can hold multiple buffers/images.
+
+ ```c
+ vkBindBufferMemory(vertexBuffer, memory, 0);
+ vkBindBufferMemory(indexBuffer, memory, 65536);
+ ```
+2. **Explicit control:**
+ You choose **memory type** (device local, host visible, cached, BAR-mapped) and **alignment**.
+3. **Performance predictability:**
+ Drivers don’t do hidden allocations or copies.
+
+---
+
+✅ **Summary**
+
+* `vkAllocateMemory`: “Here is a raw block of memory on the GPU.”
+* `vkBindBufferMemory`: “This buffer will live inside that block at this offset.”
+
+Without `vkAllocateMemory`, `vkBindBufferMemory` has nothing to bind.
+Without `vkBindBufferMemory`, the buffer exists but has **no storage**.
+
+---
+
+If you want, I can draw a little diagram showing **buffers, images, memory blocks, and GPU access** — it makes this super clear.
+
+Do you want me to do that?
--- /dev/null
+Great question — this is one of the **most important Vulkan concepts**, and the wording is easy to gloss over.
+
+When I said:
+
+> **“I can map from CPU”**
+
+this refers **very specifically** to Vulkan’s ability to let your program access GPU memory **as a normal pointer** on the CPU.
+
+Let’s break that down precisely.
+
+---
+
+## What “map from CPU” means in Vulkan
+
+In Vulkan, memory lives in different places:
+
+* Some memory lives **only on the GPU** (fast, but CPU can’t touch it)
+* Some memory lives in **system RAM** or unified memory (CPU can touch it)
+* Some memory is shared / cached / coherent
+
+When memory has the flag:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+```
+
+it means:
+
+> **The CPU is allowed to directly access this memory**
+
+Concretely, this allows you to call:
+
+```c
+void* data;
+vkMapMemory(device, memory, 0, size, 0, &data);
+```
+
+And then do:
+
+```c
+memcpy(data, myVertices, size);
+```
+
+That’s what **“map from CPU”** means.
+
+---
+
+## What happens without HOST_VISIBLE
+
+If memory does **not** have `HOST_VISIBLE`:
+
+* `vkMapMemory` is **illegal**
+* You cannot get a pointer to it
+* The CPU cannot read or write it directly
+
+This is typical for:
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+which usually means:
+
+* Memory physically located on the GPU (VRAM)
+* Very fast for the GPU
+* Completely inaccessible to the CPU
+
+---
+
+## Why you care
+
+### Example: Vertex buffer
+
+#### Option A — HOST_VISIBLE memory
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+```
+
+✔ Easy: `memcpy` vertices directly
+❌ Slower for GPU access
+❌ PCIe / cache penalties
+
+#### Option B — DEVICE_LOCAL memory
+
+```c
+props = DEVICE_LOCAL
+```
+
+✔ Very fast for GPU
+❌ CPU cannot write directly
+
+So how do you upload data in Option B?
+
+➡ **Staging buffer**
+
+---
+
+## The staging buffer pattern (this is key)
+
+1. Create a **CPU-visible staging buffer**
+
+ ```c
+ HOST_VISIBLE | HOST_COHERENT
+ ```
+
+2. Write data via `memcpy`
+
+3. Copy staging buffer → device-local buffer using a command buffer
+
+4. Free staging buffer
+
+This is why Vulkan makes you think explicitly.
+
+---
+
+## HOST_COHERENT (important related detail)
+
+Often you see:
+
+```c
+HOST_VISIBLE | HOST_COHERENT
+```
+
+### What HOST_COHERENT means
+
+It means:
+
+> “When I write to mapped memory, the GPU sees it automatically”
+
+Without it, you must manually do:
+
+```c
+vkFlushMappedMemoryRanges(...)
+vkInvalidateMappedMemoryRanges(...)
+```
+
+So:
+
+* **HOST_VISIBLE** = CPU *can* access
+* **HOST_COHERENT** = CPU/GPU memory stays in sync automatically
+
+---
+
+## Concrete analogy
+
+Think of GPU memory like rooms:
+
+| Room type | CPU can enter? | GPU speed |
+| ----------------------- | -------------- | ------------ |
+| Device local (VRAM) | ❌ No | 🚀 Fast |
+| Host visible | ✅ Yes | 🐢 Slower |
+| Host visible + coherent | ✅ Yes | 🐢🐢 Slowest |
+
+You’re choosing **which room** to put your data in.
+
+---
+
+## In your program specifically
+
+When you call:
+
+```c
+find_memory_type(req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+```
+
+You are explicitly saying:
+
+> “Give me memory that my CPU can map with a pointer
+> and write to using memcpy,
+> without manual cache management.”
+
+That’s exactly what “I can map from CPU” means.
+
+---
+
+## One sentence summary (very precise)
+
+> **“Map from CPU” means the CPU can obtain a pointer to GPU memory via `vkMapMemory` and directly read/write it like normal RAM.”**
+
+---
+
+If you want next, I can explain:
+
+* Why HOST_VISIBLE memory is often slower
+* Why some GPUs don’t have coherent memory
+* How unified memory GPUs behave differently
+* Exactly when you should *not* use HOST_VISIBLE
+
+Just tell me which one you want.
+
+Short answer: **sometimes yes, sometimes no — and Vulkan deliberately doesn’t promise which**.
+
+Let’s be very precise.
+
+---
+
+## Is mapped memory “still on the GPU”?
+
+### Vulkan’s honest answer:
+
+> **Mapped memory is always allocated by the Vulkan device, but its *physical location* depends on the hardware and memory type.**
+
+When you call:
+
+```c
+vkAllocateMemory(device, ...)
+vkMapMemory(device, memory, ...)
+```
+
+You are **not** allocating “CPU memory” or “GPU memory” explicitly.
+
+You are allocating **device memory**, and Vulkan exposes a **CPU mapping** *if the memory type allows it*.
+
+---
+
+## What actually happens under the hood
+
+### Case 1: Discrete GPU (most desktops)
+
+Typical example:
+
+* CPU has system RAM
+* GPU has its own VRAM
+* Connected via PCIe
+
+For these GPUs:
+
+| Memory type | Physical location |
+| -------------- | ------------------------------- |
+| `DEVICE_LOCAL` | GPU VRAM |
+| `HOST_VISIBLE` | System RAM (or BAR-mapped VRAM) |
+
+So if memory is:
+
+```c
+HOST_VISIBLE
+```
+
+then **physically** it is usually:
+
+* Allocated in **system RAM**
+* GPU accesses it over PCIe
+* CPU maps it directly
+
+✅ CPU pointer is real RAM
+❌ GPU access is slower
+
+This is why HOST_VISIBLE is slower for rendering.
+
+---
+
+### Case 2: Integrated / UMA GPU (laptops, APUs)
+
+Typical example:
+
+* AMD APU
+* Intel iGPU
+* Apple Silicon
+
+Here:
+
+* CPU and GPU share **the same physical memory**
+
+So:
+
+```c
+HOST_VISIBLE | DEVICE_LOCAL
+```
+
+can both be true.
+
+In this case:
+
+* Memory is physically one pool
+* CPU pointer == GPU memory
+* No PCIe penalty
+
+This is the **best-case scenario**, and Vulkan allows it.
+
+---
+
+### Case 3: BAR-mapped VRAM (advanced discrete GPUs)
+
+Some modern GPUs expose parts of VRAM to the CPU via PCIe BAR:
+
+* Memory is **physically in VRAM**
+* CPU sees a mapped window
+* Still slower than RAM (system ram)
+* Limited size
+
+Still counts as `HOST_VISIBLE`.
+
+---
+
+## The key Vulkan rule (this matters)
+
+> **Vulkan never guarantees where the memory physically lives.**
+
+It only guarantees:
+
+* Whether the CPU *may* access it
+* Whether caches are coherent
+* Whether the GPU prefers it
+
+This is why Vulkan forces you to ask:
+
+```c
+vkGetPhysicalDeviceMemoryProperties(...)
+```
+
+And choose explicitly.
+
+---
+
+## So is the memory “on the GPU”?
+
+Let’s answer your question exactly:
+
+> **If the CPU can obtain the pointer, is the memory still allocated on the GPU then?**
+
+### Correct, nuanced answer:
+
+* ✅ **Yes**, it is Vulkan *device memory*
+* ❌ **No**, it is not necessarily physically in GPU VRAM
+* 🔁 It may be:
+
+ * system RAM
+ * unified memory
+ * BAR-mapped VRAM
+
+The CPU pointer is a **mapping**, not proof of physical location.
+
+---
+
+## Why Vulkan phrases it this way
+
+Vulkan is designed so that:
+
+* The same code works on phones, consoles, desktops, servers
+* You never assume memory topology
+* Performance comes from choosing wisely, not guessing
+
+That’s why:
+
+* You describe *requirements*
+* The driver chooses *placement*
+
+---
+
+## Mental model (use this)
+
+Think of Vulkan memory like this:
+
+> “I allocate memory *owned by the device*.
+> Sometimes the device lets the CPU borrow a pointer to it.”
+
+Not:
+
+> “I allocated CPU memory that the GPU can see.”
+
+That mental model will keep you out of trouble.
+
+---
+
+## One-sentence takeaway
+
+> **Mapped memory is always Vulkan device memory; CPU access is a permission, not a statement about physical location.**
+
+If you want next, I can:
+
+* Show how to detect UMA vs discrete GPUs
+* Explain why HOST_VISIBLE can stall rendering
+* Walk through a perfect staging-buffer upload
+* Explain why mapping every frame can cause stutter (relevant to your issue)
+
+Just tell me.
+
+Great question — this is exactly where the confusion usually is.
+Let’s answer it **very concretely** and correct one small wording issue first.
+
+> What you meant by *“Case 3”* is **BAR-mapped VRAM on a discrete GPU**
+> (Resizable BAR / Smart Access Memory), **not unified memory**.
+
+---
+
+## Short answer (TL;DR)
+
+**BAR-mapped VRAM is *not faster* than normal system RAM for the CPU.**
+Its advantages are **not raw speed**, but **capability and reduced copies**.
+
+---
+
+## First: what “non BAR-mapped” means
+
+### Traditional (non-BAR) discrete GPU memory
+
+* GPU VRAM is **not directly addressable** by the CPU
+* CPU must:
+
+ 1. Write to system RAM (staging buffer)
+ 2. Ask GPU to copy over PCIe (`vkCmdCopyBuffer`)
+* CPU **cannot** dereference VRAM pointers
+
+This is how GPUs worked for decades.
+
+---
+
+## What BAR-mapped VRAM actually is
+
+With **Resizable BAR**:
+
+* GPU VRAM is mapped into the CPU’s address space
+* CPU can get a pointer to VRAM
+* CPU loads/stores go **over PCIe**
+
+Important:
+
+> **PCIe is much slower and higher-latency than DRAM**
+
+---
+
+## Is BAR-mapped VRAM faster than non-BAR?
+
+### For the CPU?
+
+❌ **No — it is slower than system RAM**
+
+| Memory type | CPU access speed |
+| ------------------ | ---------------- |
+| System RAM | ⭐⭐⭐⭐⭐ |
+| BAR-mapped VRAM | ⭐⭐ |
+| GPU VRAM (non-BAR) | ❌ impossible |
+
+BAR gives **access**, not speed.
+
+---
+
+### For the GPU?
+
+⚠️ **Sometimes indirectly**
+
+BAR can reduce:
+
+* Extra copies
+* Command buffer overhead
+* Synchronization latency
+
+But GPU **still prefers DEVICE_LOCAL VRAM accessed by itself**.
+
+---
+
+## So what are the actual advantages?
+
+### ✅ 1. Fewer copies (big deal)
+
+Without BAR:
+
+```
+CPU → RAM → GPU copy → VRAM
+```
+
+With BAR:
+
+```
+CPU → VRAM
+```
+
+This matters when:
+
+* Streaming assets
+* Uploading many small buffers
+* Doing frequent updates
+
+---
+
+### ✅ 2. Lower latency for small, dynamic updates
+
+Examples:
+
+* Per-frame constants
+* Dynamic vertex buffers
+* Sparse data updates
+
+Avoids:
+
+* Command submission
+* Transfer queues
+* Extra synchronization
+
+---
+
+### ✅ 3. Better CPU-driven streaming
+
+Used heavily in:
+
+* Modern engines (DX12 / Vulkan)
+* Texture streaming systems
+* Nanite-style workloads
+
+BAR helps **bandwidth utilization**, not single-access speed.
+
+---
+
+### ❌ What BAR does NOT give you
+
+* ❌ Faster CPU memory access
+* ❌ Replacement for staging buffers in large uploads
+* ❌ Free performance
+
+If you memcpy 100 MB per frame into BAR VRAM → **you will stall badly**
+
+---
+
+## Why BAR can *hurt* if misused
+
+BAR-mapped VRAM:
+
+* Bypasses CPU caches
+* Has high write-combining costs
+* Causes PCIe traffic saturation
+
+So this is **bad**:
+
+```c
+memcpy(barMappedVRAM, bigTexture, size);
+```
+
+This is **good**:
+
+```c
+memcpy(stagingRAM, data, size);
+vkCmdCopyBuffer(...); // GPU does the heavy lifting
+```
+
+---
+
+## Vulkan perspective (important)
+
+Vulkan exposes BAR like this:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+If you see **both** on a discrete GPU:
+
+* That’s BAR-mapped VRAM
+* Treat it as **special-purpose**, not general RAM
+
+---
+
+## Practical rule (tattoo this mentally)
+
+> **BAR is for reducing copies, not increasing bandwidth**
+
+Use BAR-mapped memory when:
+
+* Updates are small
+* Latency matters
+* You want to avoid extra transfers
+
+Use staging buffers when:
+
+* Data is large
+* GPU will read it often
+* You want peak throughput
+
+---
+
+## Relating this back to your stuttering issue
+
+You were right to suspect memory behavior.
+
+Your current pipeline is good because:
+
+* Texture is uploaded once
+* GPU samples DEVICE_LOCAL memory
+* No per-frame BAR writes
+
+So your stutter is **not** BAR-related — it was primarily:
+
+* Swapchain image / frame-in-flight mismatch
+* Fence/image ownership issues (which you already fixed)
+
+---
+
+If you want next, I can:
+
+* Show how engines *mix* BAR + staging safely
+* Explain why BAR helps DX12 more than Vulkan
+* Show how to detect BAR memory at runtime
+
+Just tell me 👍
--- /dev/null
+Ah, yes! This is a subtle but important Vulkan concept. Let’s break it down carefully.
+
+---
+
+### 1️⃣ What “opaque object” means
+
+In Vulkan, when you see something described as an **opaque object**, it means:
+
+* **You don’t know or need to know what’s inside it.**
+* The API treats it as a **handle** (essentially a pointer or an integer under the hood).
+* You cannot directly access its internal structure — you can only interact with it via Vulkan functions.
+
+For example:
+
+```c
+VkCommandPool commandPool;
+vkCreateCommandPool(device, &cpInfo, NULL, &commandPool);
+```
+
+* `commandPool` is a **handle** to the actual command pool data.
+* Internally, the driver stores GPU memory, thread info, buffers, etc., but **Vulkan doesn’t expose those details**.
+* You just pass `commandPool` to Vulkan functions like `vkAllocateCommandBuffers` or `vkResetCommandPool`.
+
+---
+
+### 2️⃣ Why Vulkan uses opaque objects
+
+1. **Driver flexibility:** Different GPU vendors can implement the object however they want.
+2. **Safety:** You can’t accidentally mess with internal GPU data.
+3. **Abstraction:** Vulkan provides a uniform interface regardless of the underlying hardware.
+
+Other examples of opaque objects:
+
+* `VkDevice`
+* `VkBuffer`
+* `VkImage`
+* `VkPipeline`
+* `VkShaderModule`
+
+All of these are **handles**, not structs you can poke inside.
+
+---
+
+### 3️⃣ How you interact with them
+
+You always pass the handle to Vulkan functions:
+
+```c
+vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers);
+vkDestroyCommandPool(device, commandPool, NULL);
+```
+
+You never dereference it or try to read its members, because **its memory layout is unknown**.
+
+---
+
+✅ In short:
+
+An **opaque object** in Vulkan is like a black box: you know **what it represents** and **how to use it**, but you **don’t see or modify its internals**. The driver takes care of the details.
+
+---
+
+If you want, I can make a **quick visual analogy showing Vulkan handles vs real GPU memory** — it helps a lot to internalize the concept.
+
+Do you want me to do that?
+
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
+
--- /dev/null
+Ah! I can see exactly why this is still stuttering, and it’s subtle but very common in Vulkan beginners’ loops. Let’s go through it carefully.
+
+---
+
+## 1️⃣ The root cause
+
+Right now, your code does this:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+vkResetCommandBuffer(cb, 0);
+...
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+* `MAX_FRAMES_IN_FLIGHT = 2` → you have **2 fences**, **2 semaphores**, and **2 command buffers**.
+* **Swapchain images = 4**.
+
+This works *sometimes*, but not reliably, because **you are recording one command buffer per frame in flight, but there are 4 swapchain images**.
+
+* If `vkAcquireNextImageKHR` returns image index 2 or 3 (which can happen randomly), you will record and submit **command buffer 0 or 1** to draw to that image.
+* If that image is still in use (GPU hasn’t finished writing to it yet), you can get **stutter or occasional tearing**.
+
+In short: **frames in flight and swapchain images are not aligned**, and your code assumes a fixed one-to-one mapping between them.
+
+---
+
+## 2️⃣ How Vulkan apps usually fix this
+
+There are **two approaches**:
+
+---
+
+### **Approach A — Command buffer per swapchain image**
+
+* Allocate **one command buffer per swapchain image**:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+* Record commands **per swapchain image** (or re-record each frame).
+* When rendering:
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* No matter which swapchain image is returned by `vkAcquireNextImageKHR`, you have a dedicated command buffer for it.
+* This is the cleanest solution if you’re recording per-frame commands dynamically.
+
+---
+
+### **Approach B — Keep frames in flight separate from swapchain images, but wait properly**
+
+* Keep `MAX_FRAMES_IN_FLIGHT` buffers/fences.
+* After acquiring the swapchain image, **check if it’s already in flight**:
+
+```c
+VkFence imageFence = imagesInFlight[imgIndex];
+if(imageFence != VK_NULL_HANDLE)
+ vkWaitForFences(device, 1, &imageFence, VK_TRUE, UINT64_MAX);
+imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+```
+
+* This ensures **you don’t overwrite a swapchain image that’s still being presented**.
+* `imagesInFlight` is an array of size `swapchainImageCount`, initialized to `VK_NULL_HANDLE`.
+
+---
+
+## 3️⃣ Other subtle stutter causes
+
+Even after fixing the above:
+
+1. **V-Sync mode (FIFO) + small floating-point inaccuracies**
+
+ * You are limiting rotation by `delta` each frame — good.
+ * But CPU/GPU timing can slightly drift → slight stutter.
+
+2. **`vkQueueWaitIdle()` or blocking calls**
+
+ * In your `create_texture_image()`, you do:
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, VK_NULL_HANDLE);
+vkQueueWaitIdle(graphicsQueue);
+```
+
+* That’s fine for setup, but make sure your **render loop doesn’t have extra waits** other than fences per frame.
+
+3. **Push constants and descriptor updates**
+
+ * Currently you only use **push constants** for rotation and a static texture → that’s fine.
+ * If you update descriptor sets per frame, that can stall if you don’t use separate sets per frame.
+
+---
+
+## ✅ Minimal fix for your current code
+
+I recommend **switching to one command buffer per swapchain image**.
+
+### Changes:
+
+```c
+// allocate command buffers per swapchain image
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+};
+vkAllocateCommandBuffers(device, &cbAlloc, commandBuffers);
+```
+
+Then, in your render loop:
+
+```c
+uint32_t imgIndex;
+vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE, &imgIndex);
+
+// use command buffer dedicated to this swapchain image
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* **No more mismatches**.
+* `currentFrame` is still used for fences/semaphores, not command buffers.
+
+---
+
+If you want, I can **rewrite your render loop fully corrected** with:
+
+* Multiple swapchain images
+* MAX_FRAMES_IN_FLIGHT fences/semaphores
+* Dynamic rendering + textures + push constant rotation
+
+so the stutter disappears completely.
+
+Do you want me to do that?
--- /dev/null
+Good catch — this is **not** a texturing problem anymore.
+What you’re seeing is **time-step stutter**, not GPU stutter.
+
+---
+
+## ✅ Root cause: frame-dependent rotation
+
+Right now you update rotation like this:
+
+```c
+angle += 0.01f;
+```
+
+That means:
+
+* Rotation speed depends on **how long the frame took**
+* If one frame takes longer (OS scheduling, compositor, swapchain wait)
+* The angle “jumps” more on that frame
+* Your eye interprets this as **texture stuttering**
+
+The texture is actually stable — the **motion is uneven**.
+
+---
+
+## Why Vulkan makes this obvious
+
+You are using:
+
+```c
+VK_PRESENT_MODE_FIFO_KHR
+```
+
+Which:
+
+* Syncs to v-blank (good)
+* Causes variable frame times when frames miss a refresh
+* Makes fixed-per-frame motion very noticeable
+
+This is **expected behavior**.
+
+---
+
+## ✅ Correct fix: time-based animation (delta time)
+
+You must rotate based on **elapsed time**, not frames.
+
+### Add these variables
+
+At the top of `main`:
+
+```c
+double lastTime = glfwGetTime();
+```
+
+### Replace your angle update with this
+
+Inside the loop:
+
+```c
+double now = glfwGetTime();
+double delta = now - lastTime;
+lastTime = now;
+
+angle += (float)(delta * 1.5); // radians per second
+```
+
+Now rotation speed is:
+
+* Smooth
+* Independent of frame rate
+* Independent of swapchain stalls
+
+---
+
+## 🎯 Why this fixes the “texture stutter”
+
+Perspective-correct interpolation is now correct
+✔ Texture is locked to geometry
+✔ Geometry moves smoothly in time
+✔ No visible swimming or jitter
+
+---
+
+## ❌ What is NOT the cause
+
+* ❌ Texture sampling
+* ❌ Descriptor sets
+* ❌ Dynamic rendering
+* ❌ Nearest vs linear filtering
+* ❌ Swapchain image count
+
+---
+
+## Optional improvements (not required)
+
+### Clamp large frame jumps (alt-tab, debugger pauses)
+
+```c
+if (delta > 0.1) delta = 0.1;
+```
+
+### Use radians explicitly (clarity)
+
+```c
+float rotationSpeed = M_PI / 2.0f; // 90°/sec
+angle += rotationSpeed * delta;
+```
+
+---
+
+## Final verdict
+
+✔ Your Vulkan pipeline is correct
+✔ Your texture mapping is correct
+✔ Your projection fix was correct
+❌ Motion was frame-rate dependent
+
+After switching to **delta-time animation**, the “texture stutter” should disappear entirely.
+
+If you want next, I can:
+
+* Add a depth buffer (very noticeable improvement)
+* Switch to indexed drawing
+* Add proper MVP matrices
+* Show how to decouple update & render timing
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
--- /dev/null
+Ah — now I see exactly what’s happening. The “stuttering” you’re seeing is **not a problem with your delta-time rotation**, but instead comes from the **way you’re submitting command buffers and waiting for fences** in your render loop.
+
+Right now, your loop looks like this (simplified):
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlight);
+
+vkAcquireNextImageKHR(..., imageAvailable, VK_NULL_HANDLE, &imgIndex);
+
+vkResetCommandBuffer(cb, 0);
+vkBeginCommandBuffer(cb, ...);
+
+// record commands
+vkCmdBeginRendering(cb, &renderingInfo);
+vkCmdBindPipeline(...);
+vkCmdDraw(...);
+vkCmdEndRendering(cb);
+
+vkEndCommandBuffer(cb);
+
+VkSubmitInfo submit = { ... };
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlight);
+
+VkPresentInfoKHR present = { ... };
+vkQueuePresentKHR(graphicsQueue, &present);
+```
+
+---
+
+## 🔴 The problem
+
+You are using **one fence (`inFlight`) for all frames**:
+
+```c
+VkFence inFlight;
+```
+
+And your loop does:
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+```
+
+This means:
+
+* Each frame **blocks until the previous frame finishes entirely** (GPU + present)
+* If your GPU is fast, this is okay
+* If there’s any fluctuation (swapchain wait, vsync), your CPU **stalls**
+* Result: **stuttery motion**, even if delta-time rotation is correct
+
+Also, you **don’t use multiple in-flight frames**, so you can’t submit the next frame until the GPU is done.
+
+---
+
+## ✅ How to fix it
+
+You need to implement **double-buffered or triple-buffered frames in flight**.
+
+### Step 1: create multiple fences
+
+```c
+#define MAX_FRAMES_IN_FLIGHT 2
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+```
+
+### Step 2: cycle through frames
+
+```c
+uint32_t currentFrame = 0;
+```
+
+Inside the loop:
+
+```c
+vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+);
+
+// record command buffer ...
+// submit:
+VkSubmitInfo submit = { ... };
+submit.waitSemaphoreCount = 1;
+submit.pWaitSemaphores = &imageAvailable[currentFrame];
+submit.signalSemaphoreCount = 1;
+submit.pSignalSemaphores = &renderFinished[currentFrame];
+
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+// present:
+VkPresentInfoKHR present = { ... };
+present.waitSemaphoreCount = 1;
+present.pWaitSemaphores = &renderFinished[currentFrame];
+
+vkQueuePresentKHR(graphicsQueue, &present);
+
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+---
+
+## Step 3: Why this fixes the stutter
+
+* Now **CPU doesn’t wait for GPU to finish the previous frame entirely**
+* You can have **two frames queued in flight**
+* Delta-time rotation is smooth
+* Swapchain image acquisition can happen asynchronously
+* Texture and cube rotation appear smooth
+
+---
+
+### Extra tip:
+
+Make sure your **swapchain present mode is FIFO (vsync)**:
+
+```c
+.preTransform = caps.currentTransform,
+.presentMode = VK_PRESENT_MODE_FIFO_KHR,
+```
+
+This ensures no tearing, but introduces a slight latency — which is fine with multiple frames in flight.
+
+---
+
+### ✅ TL;DR
+
+1. You currently block on a single fence every frame → causes stutter.
+2. Implement **multiple frames in flight** (double/triple buffering).
+3. Use **frame-indexed semaphores and fences** for acquire/submit/present.
+4. Keep delta-time rotation as you have — it will now appear smooth.
+
+---
+
+If you want, I can rewrite your **main render loop with double-buffered frames in flight** for your current code — fully Vulkan 1.3 + dynamic rendering ready.
+
+Do you want me to do that?
+
+Ah! I can see exactly why your program segfaults — it’s not a single one-liner; there are **multiple issues related to fences, semaphores, and memory allocation** in your current code. Let me break it down carefully and explain exactly what’s going wrong, and how to fix it.
+
+---
+
+## 1️⃣ Fences and semaphores creation
+
+You have:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+**Problem:**
+
+* `imageAvailable` and `renderFinished` are **arrays** of `MAX_FRAMES_IN_FLIGHT` elements.
+* `inFlightFences` is also an **array** of size `MAX_FRAMES_IN_FLIGHT`.
+* `vkCreateSemaphore` and `vkCreateFence` expect a **pointer to a single VkSemaphore/VkFence**, not an array.
+
+Right now, you’re passing `imageAvailable` (type `VkSemaphore[MAX_FRAMES_IN_FLIGHT]`) **without indexing**, so Vulkan interprets the pointer incorrectly → undefined behavior → **segfault**.
+
+**Fix:** create each semaphore/fence individually:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+}
+```
+
+✅ This is **mandatory**. Without it, your `vkWaitForFences` later will segfault because the fence handle is garbage.
+
+---
+
+## 2️⃣ Command buffers + swapchain images mismatch
+
+You allocated:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+and
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* `imgIndex` comes from `vkAcquireNextImageKHR`.
+* But `swapchainImageCount` may be **larger than MAX_FRAMES_IN_FLIGHT**.
+* Your `currentFrame` index is used for fences and semaphores.
+* This can cause **index mismatches** if you have more swapchain images than frames in flight.
+
+**Fix:** use **frame indexing consistently**:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+```
+
+Then record the command buffer for **that frame**.
+Or allocate **one command buffer per frame in flight**, not per swapchain image.
+
+---
+
+## 3️⃣ Push constants / pipeline layout
+
+You correctly defined a push constant of `sizeof(float)` for rotation.
+✅ This part is fine.
+
+---
+
+## 4️⃣ Descriptor sets
+
+You only have **1 descriptor set**, but you submit multiple frames in flight.
+
+* Technically okay since the texture is static, but if you ever update it dynamically, you’d need **one descriptor set per frame**.
+* For now, not a segfault cause.
+
+---
+
+## 5️⃣ Minor issues that can crash
+
+* `VkSurfaceFormatKHR formats[fmtCount];` — if `fmtCount` is zero, this will crash. Always check:
+
+```c
+if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+```
+
+* `vkCreateImageView` and `vkCreateBuffer` **do not check return values everywhere**. Make sure you check **every Vulkan call**.
+
+---
+
+## 6️⃣ Texture image staging
+
+* Your staging buffer is allocated, copied, and freed **synchronously**.
+* That part is fine.
+* Just make sure `vkQueueSubmit` waits for the copy to finish, which you do with `vkQueueWaitIdle`. ✅
+
+---
+
+## ✅ Corrected fence/semaphore creation
+
+Replace:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+with:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create synchronization objects\n");
+ exit(1);
+ }
+}
+```
+
+---
+
+## 7️⃣ Recommended changes
+
+1. Allocate **one command buffer per frame in flight**, not per swapchain image.
+2. Use `currentFrame` to index fences, semaphores, and command buffers.
+3. Always check Vulkan function returns.
+4. Use `VK_NULL_HANDLE` fences/semaphores carefully — do **not** pass arrays directly.
+
+---
+
+If you want, I can **rewrite your main render loop + fence/semaphore setup** fully corrected for **double-buffered frames in flight** — so it will **stop segfaulting** and run smoothly.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely! Let’s break it down carefully.
+
+---
+
+### 1️⃣ What `u` and `v` are
+
+In your struct:
+
+```c
+typedef struct {
+ float x, y, z; // 3D position in space
+ float u, v; // texture coordinates
+} CubeVertex;
+```
+
+* `x, y, z` → define the **vertex position in 3D space**.
+* `u, v` → define **where on a 2D texture image this vertex maps to**.
+
+These are often called **UV coordinates** or **texture coordinates**.
+
+---
+
+### 2️⃣ How `u` and `v` relate to the texture
+
+Textures are images. Imagine a 2D image like this:
+
+```
+(0,1) ┌─────────────┐ (1,1)
+ │ │
+ │ │
+ │ │
+(0,0) └─────────────┘ (1,0)
+```
+
+* `u` → horizontal position on the texture (0 = left edge, 1 = right edge)
+* `v` → vertical position on the texture (0 = bottom edge, 1 = top edge)
+
+> Note: In Vulkan (and OpenGL), **v=0 is usually bottom**, **v=1 is top**. Some image libraries flip this, so sometimes `v` may need to be inverted.
+
+---
+
+### 3️⃣ How this works in rendering
+
+When Vulkan draws a triangle:
+
+1. Each vertex has `(x, y, z)` for position and `(u, v)` for texture mapping.
+2. The GPU **interpolates** `u` and `v` across the triangle.
+3. For every pixel in the triangle, the GPU calculates a corresponding `(u, v)` coordinate.
+4. The pixel color is sampled from the texture using that `(u, v)` coordinate.
+
+---
+
+### 4️⃣ Example
+
+If you have a cube face made of two triangles and a 2x2 texture like this:
+
+```
+Texture:
+[white][pink]
+[pink][white]
+```
+
+And your vertices are:
+
+```c
+{-0.5f,-0.5f,-0.5f, 0,0}, // bottom-left maps to texture bottom-left
+{ 0.5f,-0.5f,-0.5f, 1,0}, // bottom-right maps to texture bottom-right
+{ 0.5f, 0.5f,-0.5f, 1,1}, // top-right maps to texture top-right
+{-0.5f, 0.5f,-0.5f, 0,1} // top-left maps to texture top-left
+```
+
+* Each vertex "grabs" the color from that corner of the texture.
+* The GPU fills the interior of the triangle with colors **interpolated** between those corners.
+
+---
+
+### ✅ TL;DR
+
+* `u` = horizontal coordinate on the texture
+* `v` = vertical coordinate on the texture
+* `(u, v)` tell Vulkan **which part of the 2D image** to apply to the 3D vertex.
+* Without `u, v`, Vulkan wouldn’t know how to map the texture to the cube.
+
+---
+
+If you want, I can make a **small visual diagram showing a cube face, its vertices, and how u,v map the texture onto it**, which usually makes it click instantly.
+
+Do you want me to do that?
+
+Absolutely! Let’s connect the dots between your **CubeVertex struct** `(x, y, z, u, v)` and how the **shader** uses it to texture your cube. I’ll break it down step by step.
+
+---
+
+## 1️⃣ Vertex Shader
+
+A typical vertex shader in Vulkan GLSL might look like this:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec3 inPos; // x, y, z from CubeVertex
+layout(location = 1) in vec2 inUV; // u, v from CubeVertex
+
+layout(location = 0) out vec2 fragUV; // pass to fragment shader
+
+layout(push_constant) uniform PushConstants {
+ float angle;
+} pc;
+
+void main() {
+ // Apply rotation around Y axis
+ float s = sin(pc.angle);
+ float c = cos(pc.angle);
+ vec3 pos = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // Convert to clip space (just simple identity for example)
+ gl_Position = vec4(pos, 1.0);
+
+ // Pass the UV coordinates to the fragment shader
+ fragUV = inUV;
+}
+```
+
+### 🔑 Notes:
+
+* `layout(location = 0) in vec3 inPos` ← gets **x, y, z** from `CubeVertex`.
+* `layout(location = 1) in vec2 inUV` ← gets **u, v** from `CubeVertex`.
+* `fragUV = inUV;` → we pass the texture coordinates **to the fragment shader**, interpolated across the triangle.
+
+So every pixel in the triangle will have a `fragUV` coordinate that is **interpolated** between the vertices.
+
+---
+
+## 2️⃣ Fragment Shader
+
+Then, in the fragment shader:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec2 fragUV; // interpolated UV
+layout(location = 0) out vec4 outColor; // final pixel color
+
+layout(set = 0, binding = 0) uniform sampler2D tex; // texture sampler
+
+void main() {
+ // Sample the texture at fragUV
+ outColor = texture(tex, fragUV);
+}
+```
+
+### 🔑 Notes:
+
+* `fragUV` = interpolated `(u,v)` coordinates from vertex shader.
+* `sampler2D tex` = your bound texture image + sampler.
+* `texture(tex, fragUV)` → returns the color of the texture at that `(u,v)` location.
+* The GPU writes `outColor` to the framebuffer.
+
+---
+
+## 3️⃣ How this maps from your CubeVertex:
+
+| CubeVertex field | Role in Shader |
+| ---------------- | ---------------------------------------------------------------------------------------- |
+| `x, y, z` | `inPos` in vertex shader → determines 3D position of vertex |
+| `u, v` | `inUV` in vertex shader → passed as `fragUV` to fragment shader → used to sample texture |
+
+**Flow:**
+
+```
+CubeVertex.u,v ---> vertex shader ---> fragUV (interpolated) ---> fragment shader ---> texture lookup ---> final pixel color
+```
+
+---
+
+## 4️⃣ Why this is important
+
+Without `u, v`:
+
+* Fragment shader wouldn’t know **which part of the texture** corresponds to each pixel.
+* Texture would be meaningless; all faces might just use the top-left pixel, for example.
+
+With `u, v`:
+
+* Each face of your cube can correctly map the texture, even after rotation.
+* The GPU interpolates `u, v` across triangles for smooth texturing.
+
+---
+
+If you want, I can also **draw a diagram showing the cube, a triangle, and how `(u,v)` coordinates are interpolated to map the texture properly**.
+
+Do you want me to make that visual?
--- /dev/null
+Ah, this is a **common misunderstanding in Vulkan** — the “3D” in `VkImageViewType` does **not mean a 3D object in the scene**, it’s about the **image resource itself** in GPU memory. Let me explain carefully.
+
+---
+
+## 1️⃣ What `viewType` really means
+
+`VkImageViewCreateInfo.viewType` can be:
+
+| Value | Meaning |
+| ------------------------- | -------------------------------------------------- |
+| `VK_IMAGE_VIEW_TYPE_1D` | The image is a 1D texture (width only) |
+| `VK_IMAGE_VIEW_TYPE_2D` | The image is a 2D texture (width × height) |
+| `VK_IMAGE_VIEW_TYPE_3D` | The image is a 3D texture (width × height × depth) |
+| `VK_IMAGE_VIEW_TYPE_CUBE` | Cube map texture (6 faces) |
+
+**Important:** This is about **how the GPU interprets the memory layout of the image**, not whether your scene is 3D.
+
+---
+
+## 2️⃣ Swapchain images are always 2D
+
+* Swapchain images are the **images that will be presented on your 2D screen**.
+* Even though your cube is a 3D object in the scene, the **swapchain image is a 2D framebuffer**.
+* Therefore, `viewType` **must remain `VK_IMAGE_VIEW_TYPE_2D`** for swapchain images.
+
+If you tried `VK_IMAGE_VIEW_TYPE_3D`:
+
+* Vulkan would expect a **3D image resource** in memory (with depth slices).
+* Swapchain images **aren’t 3D** — they only have width × height × 1.
+* `vkCreateImageView` would **fail** or behave incorrectly.
+
+---
+
+## 3️⃣ Summary
+
+* Your 3D cube **does not make the swapchain 3D**.
+* `VK_IMAGE_VIEW_TYPE_2D` is correct for **all normal onscreen rendering**.
+* `VK_IMAGE_VIEW_TYPE_3D` is only for **volumetric textures**, like 3D noise, 3D textures, or voxel data.
+
+---
+
+💡 **Analogy:**
+
+* The 3D cube is like a **3D model**.
+* The swapchain image is like the **photograph of that model** — a 2D picture.
+* The swapchain image view interprets the “photograph” — so it’s always 2D.
+
+---
+
+If you want, I can also explain **when you *would* use `VK_IMAGE_VIEW_TYPE_3D` in Vulkan** and how it differs from rendering a normal 3D scene. This is actually a pretty common point of confusion for newcomers.
+
+Do you want me to go into that?
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+#version 450
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+
--- /dev/null
+# Blender 5.0.1 MTL File: 'None'
+# www.blender.org
+
+newmtl Material
+Ns 250.000000
+Ka 1.000000 1.000000 1.000000
+Kd 0.800000 0.800000 0.800000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 2
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.875000 0.500000
+vt 0.625000 0.750000
+vt 0.625000 0.500000
+vt 0.375000 1.000000
+vt 0.375000 0.750000
+vt 0.625000 0.000000
+vt 0.375000 0.250000
+vt 0.375000 0.000000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+vt 0.125000 0.500000
+vt 0.625000 0.250000
+vt 0.875000 0.750000
+vt 0.625000 1.000000
+s 0
+usemtl Material
+f 5/1/1 3/2/1 1/3/1
+f 3/2/2 8/4/2 4/5/2
+f 7/6/3 6/7/3 8/8/3
+f 2/9/4 8/10/4 6/11/4
+f 1/3/5 4/5/5 2/9/5
+f 5/12/6 2/9/6 6/7/6
+f 1/3/5 3/2/5 4/5/5
+f 5/12/6 1/3/6 2/9/6
+f 2/9/4 4/5/4 8/10/4
+f 7/6/3 5/12/3 6/7/3
+f 5/1/1 7/13/1 3/2/1
+f 3/2/2 7/14/2 8/4/2
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 inPos;
+
+
+layout(push_constant) uniform Push {
+ float angle;
+} pc;
+
+void main() {
+ float c = cos(pc.angle);
+ float s = sin(pc.angle);
+
+ vec3 p = vec3(
+ inPos.x * c - inPos.z * s,
+ -inPos.y, // added negative sign to flip the model correctly
+ inPos.x * s + inPos.z * c
+ );
+
+ // simple perspective
+ float z = p.z + 2.5;
+
+ gl_Position = vec4(p.xy, z, z);
+}
--- /dev/null
+# Blender 5.0.1 MTL File: 'None'
+# www.blender.org
+
+newmtl Material
+Ns 250.000000
+Ka 1.000000 1.000000 1.000000
+Kd 0.800000 0.800000 0.800000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 2
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube2.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 1.732058
+v -1.000000 1.000000 1.732058
+v 1.000000 1.000000 1.732058
+v 1.000000 -1.000000 1.732058
+vn -0.0000 1.0000 -0.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vn -0.0000 -0.0000 1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.375000 0.750000
+vt 0.125000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 3/4/1 7/3/1 10/3/1 11/4/1
+f 8/5/2 7/6/2 5/7/2 6/8/2
+f 6/9/3 2/10/3 4/11/3 8/12/3
+f 2/10/4 1/1/4 3/4/4 4/11/4
+f 6/8/5 5/7/5 1/1/5 2/10/5
+f 12/11/6 11/4/6 10/13/6 9/14/6
+f 4/11/4 3/4/4 11/4/4 12/11/4
+f 7/6/2 8/5/2 9/5/2 10/6/2
+f 8/12/3 4/11/3 12/11/3 9/12/3
--- /dev/null
+# Blender 5.0.1 MTL File: 'None'
+# www.blender.org
+
+newmtl Material
+Ns 250.000000
+Ka 1.000000 1.000000 1.000000
+Kd 0.800000 0.800000 0.800000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 2
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube3.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 1.732058
+v -1.000000 1.000000 1.732058
+v 1.000000 1.000000 1.732058
+v 1.000000 -1.000000 1.732058
+v -1.420365 -1.000000 -1.000000
+v -1.420365 -1.000000 1.000000
+v -1.420365 1.000000 1.000000
+v -1.420365 1.000000 -1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.125000 0.500000
+vt 0.125000 0.750000
+vt 0.375000 0.500000
+vt 0.375000 0.750000
+vt 0.375000 0.250000
+vt 0.625000 0.250000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.625000 0.000000
+vt 0.375000 0.000000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 3/4/1 7/3/1 10/3/1 11/4/1
+f 6/5/2 8/6/2 14/6/2 13/5/2
+f 6/5/2 2/7/2 4/8/2 8/6/2
+f 2/7/3 1/1/3 3/4/3 4/8/3
+f 6/9/4 5/10/4 1/1/4 2/7/4
+f 12/8/5 11/4/5 10/11/5 9/12/5
+f 4/8/3 3/4/3 11/4/3 12/8/3
+f 7/13/6 8/14/6 9/14/6 10/13/6
+f 8/6/2 4/8/2 12/8/2 9/6/2
+f 14/14/6 15/13/6 16/10/6 13/9/6
+f 7/3/1 5/2/1 16/2/1 15/3/1
+f 5/10/4 6/9/4 13/9/4 16/10/4
+f 8/14/5 7/13/5 15/13/5 14/14/5
--- /dev/null
+== NDC (Normalized Device Coordinates)
+
+Based on the math inside your `main()` function and the way you are passing your
+`inPos` data, we can "reverse engineer" exactly how your 3D world is oriented.
+
+Because you are using *Vulkan's default NDC* (Normalized Device Coordinates),
+your coordinate system is currently *Y-Down* and *Right-Handed*.
+
+== 1. Blender: The "Architect's" System
+
+Blender uses a *Right-Handed,Z-Up* system.
+
+- *X*: Left / Right
+
+- *Y*: Forward / Backword
+
+- *Z*: Up / Down (Imagine a blueprint lying on a table; Z is the height of the
+ walls)
+
+== 2. Vulkan: The "Screen" System
+
+Vulkan (by default) uses a *Right-Handed*, *Y-Down* system in it's Normalized
+Device Coordinates (NDC)
+
+- *X*: Right is positive (1.0), Left is negative (-1.0)
+
+- *Y*: Down is positive (1.0), Up is negative (-1.0).
+
+- *Z*: Forward (into the screen) is 0.0 to 1.0.
+
+Why Y-Down?::
+ Historically, computer graphics started with 2D window coordinates where the
+ top-left corner of your monitor was (0,0). Vulkan stays true to this legacy.
+
+.Summary Table
+|===
+| Axis | Blender | Vulkan (NDC)
+//---------------------------------
+| Up | +Z | +Y
+| Down | -Z | -Y
+| Right | +X | +X
+| Left | -X | -X
+| forward | +Y | +Z
+| Back | -Y | -Z
+|===
--- /dev/null
+== Overview
+
+* malloc /calloc allocate CPU memory
+* vkAllocate allocates GPU-accessible device memory
+* THey live in completely different worlds
+
+== Malloc / calloc
+
+* Allocates process virtual memory
+* owned by the CPU / OS
+* ONly the CPU can access it
+* the GPU cannot see or use it
+* VUlkan has no ide this memory exists
+
+== VkALlocateMemory
+
+* Allocates memory from the *physical device*
+* Managed by *Vulkan + the GPU driver*
+
+* can be:
+ - GPU-only (VRAM)
+ - CPU-visible (mapped system memory)
+ - Can be *bound* to buffers and images
+
+[source, c]
+----
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+vkBindBufferMemory(device, buffer, memory, 0);
+----
+
+== vkAlloacteMemory
+
+- VkDevice device
+- VkMemoryAllocateInfo pAllocateInfo
+- VkAllocateCallbacks* pAllocator
+- VkDeviceMemory* pMemory
+
+device::
+ is the logical device that owns the memory
+
+pAllocateInfo::
+ is a pointer to a `VkMemoryAllocateInfo` structure describing parameteres of
+ the allocation. A successfully returned allocation must use the requested
+ parameters -- no substitution is permited by the implementation.
+
+pAllocator::
+ controls host memory allocation as described in the Memory Allocation
+ chapter
+
+pMemory::
+ is a pointer to a `vkDeviceMemory` handle in which information about the
+ allocated memory is returned.
+
+== vkBindBufferMemory
+- VkDevice device
+- VkBuffer buffer
+- VkDeviceMemory memory
+- VkDeviceSize memoryOffset
+
+device::
+ Your logical device
+
+buffer::
+ the `VkBuffer` handle you created
+
+memory::
+ The `VkDeviceMemory` you allocated
+
+memoryOffset::
+ This is crucial. If you are using one big memory block for multiple buffers,
+ this tell Vulkan: "Start this specific buffer 1024 bytes into the memory
+ block." Note: This offset must be a multiple of the `alignment` requirment
+ returned by `vkGetBufferMemoryRequirements`
+
+== `VkPhysicalDeviceMemoryProperties`
+The `VkPhysicalDeviceMemoryProperties` structure describes a number of memory
+heaps as well as a number of memory types that can be used to access memory
+allocated in those heaps. Each heap descibes a memory resourece of a particular
+size, and each memory type describes a set of memory properties (e.g host cached
+vs. uncached) that can be used with a given memory heap. Allocations using a
+particular memory type will consume resources from teh heap indicated by that
+memory types heap index. More than one memory type may share each heap, and the
+heaps and memory types provide a mechanism to advertise an accurate size of the
+phsysical memory resources while allowing the memory to be used with a variety
+of different properties.
+
+The number of memory heaps is given by `memoryHeapCOunt` and is less than or
+equal to `VK_MAX_MEMORY_HEAPS`. Each heap is descibed by an elemtn of the
+`memoryHeaps` array as a `vkMemoryHeap` structure.
+
+At least one heap must include `VK_MEMORY_HEAP_DEVICE_LOCAL_BIT` in
+`VkMemoryHeap::flags`. If ther are multiple heaps that all have similar
+performance characteristics, they may all include
+`VK_MEMORY_HEAP_DEVICE_LOCAL_BIT`. In a unified memory architecture (UMA) system
+there is often a single memory heap which is considered to be equally "local" to
+the host and to the device, and such an implementation must advertise the heap
+as device-local.
+
+
+== HOST_VISIBLE and HOST_COHERENT
+
+=== 1. `HOST_VISIBLE_BIT`: "Can I talk to it?"
+This flag is all about *access*.
+
+If memory is `HOST_VISIBLE`, it means the CPU is physically wired to be able to
+"see" that memory address. If a memory type *does not* have this bit, you cannot
+use `vkMapMemory`.
+
+Analogy::
+ `HOST_VISIBLE` is like having a *window* into the GPU. Without the window,
+ you can't reach inside to put data (like your vertices) onto the shelf.
+
+When you makr memory as `HOST_VISIBLE`, you are triggering a hardare feature
+called Memory Mapped I/O (MMIO)
+
+To the CPU, the GPU is just another device on the PCIe bus. Normally, the CPU
+can't just "reach out" and touch the VRAM on you RX660. `HOST_VISIBLE` tells the
+system to take a portion of the GPU's memory and map it inot the CPU's own
+address space.
+
+== The Virtuasl Address Space
+When you call `vkMapMemory`, the operating system and the GPU driver work
+together to take a chunk of the GPU's physical memory and give it a "fake"
+address in teh CPU's list.
+
+== The PCIe Bus: The Highway
+WHen you perform
+
+== links
+* https://www.khronos.org/assets/uploads/developers/library/2018-vulkan-devday/03-Memory.pdf
--- /dev/null
+= Index Buffer
+
+== Introduction
+
+The 3D meshes you'll be rendering in a real world application will often share
+vertices between multiple triangles. This already happens even with something
+simple like drawing a rectangle:
+
+----
+ v0
+v5 +----------+ v1
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ v4 +----------+ v2
+ v3
+
+ v0 +----------+ v1
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ v3 +----------+ v2
+ Indices
+ {0, 1, 2, 2, 3, 0}
+----
+
+Drawing a rectangle takes two triangles, which means that we need a vertex
+buffer with 5 vertices. The problem is that the data of two vertices needs to be
+duplicated resulting in 50% redundancy. It only gets worse with more complex
+meshes, where vertices are reused in an average number of 3 traingles. The
+solution is to use an _index_ buffer.
+
+AN index buffer is essentially an array of pointers into the vertex buffer. It
+allows you to reorder the vertex data, and resuse existing data for multiple
+vertices.
+
+[source, c]
+----
+const std::vector<Vertex> vertices = {
+ {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
+ {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
+ {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
+ {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
+};
+
+const std::vector<uint16_t> indices = {
+ 0, 1, 2, 2, 3, 0
+};
+
+void initVulkan() {
+ ...
+ createVertexBuffer();
+ createIndexBuffer();
+ ...
+}
+
+void createIndexBuffer() {
+ VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();
+
+ VkBuffer stagingBuffer;
+ VkDeviceMemory stagingBufferMemory;
+ createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
+
+ void* data;
+ vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
+ memcpy(data, indices.data(), (size_t) bufferSize);
+ vkUnmapMemory(device, stagingBufferMemory);
+
+ createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);
+
+ copyBuffer(stagingBuffer, indexBuffer, bufferSize);
+
+ vkDestroyBuffer(device, stagingBuffer, nullptr);
+ vkFreeMemory(device, stagingBufferMemory, nullptr);
+}
+----
+
+== Links
+
+* https://vulkan-tutorial.com/Vertex_buffers/Index_buffer[index buffer]
--- /dev/null
+== NDC (Normalized Device Coordinates)
+
+Based on the math inside your `main()` function and the way you are passing your
+`inPos` data, we can "reverse engineer" exactly how your 3D world is oriented.
+
+Because you are using *Vulkan's default NDC* (Normalized Device Coordinates),
+your coordinate system is currently *Y-Down* and *Right-Handed*.
+
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
+* https://docs.vulkan.org/samples/latest/samples/extensions/dynamic_rendering/README.html
+* https://www.youtube.com/watch?v=m1RHLavNjKo
+* https://www.vulkan.org/learn#new-to-graphics-programming
+* https://paroj.github.io/gltut/
--- /dev/null
+= Summary
+
+The reason Blender shows *8 vertices* While your C code likely
+has 36 (6 faces x 2 triangles x 3 vertices) comes down to the difference between
+*Geometric Vertices* and *GPU Vertices*.
+
+== 1. Geometric vs GPU Vertices
+
+In Blender's viewport, a "vertex" is just a point in space. Since a cube only
+has 8 corners, Blender tells you there are 8 vertices.
+
+However, a GPU Vertex is a unique combination of Position + UV + Normal. If two
+faces share a corner but have different texture coordinates (UVs) or point in
+different directions (Normals), the GPU cannot treat them as the same vertex,
+They must be duplicated.
+
+== Links
+* https://vulkan-tutorial.com/Vertex_buffers/Index_buffer[Index buffers]
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+#include <vulkan/vulkan.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#include "./objParser.h"
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+#define MAX_FRAMES_IN_FLIGHT 3
+
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+
+VkSampler sampler;
+VkDescriptorSetLayout descriptorSetLayout;
+VkDescriptorPool descriptorPool;
+VkDescriptorSet descriptorSet;
+
+VkImage textureImage;
+VkDeviceMemory textureMemory;
+VkImageView textureImageView;
+
+VkFence* imagesInFlight = NULL; // tracks which fence is using each image
+
+GLFWwindow* window;
+
+float angle = 0.0f;
+
+typedef struct {
+ float x, y, z;
+} CubeVertex;
+
+uint32_t vertexCount = 0;
+
+// this finds which GPU memory type index you should use when allocating memory
+// for a buffer or image.
+// https://docs.vulkan.org/spec/latest/chapters/memory.html#VkMemoryPropertyFlagBits
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props) {
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+ // check each bit and see if it matches typeBits
+ // - `typeBits` is a bitmask
+ // - Each bit corresponds to a memory type index
+ // - If bit i is set -> that memory type can be used for this resource
+ if ((typeBits & (1 << i)) &&
+ // `propertyFlags` may have extra bits
+ // You only care that your requested bits are present
+ //
+ // exmaple:
+ // props = HOST_VISIBLE | HOST_COHERENT
+ // propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+ //
+ // The above would still return true
+
+ (memProps.memoryTypes[i].propertyFlags & props) == props) {
+ return i;
+ }
+ }
+
+ fprintf(stderr, "No suitable memory type\n");
+ exit(1);
+}
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+
+ for (uint32_t i = 0; i < fmtCount; i++) {
+ // Look for a format that is 8-bit BGRA and uses the sRGB color space
+ if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
+ formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ surf_fmt = formats[i];
+ break;
+ }
+ }
+ swapchainFormat = surf_fmt.format;
+
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount + 1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace, // sRBG, HDR10 etc..
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ // .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ // should check if GPU supports this presentMode
+ .presentMode = VK_PRESENT_MODE_MAILBOX_KHR,
+ .clipped = VK_TRUE
+ };
+ // pAllocator is set to NULL
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+ printf("image count: %d\n", swapchainImageCount);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .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
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+ // 1. Calculate the exact size of your cube array
+ // Since cube is defined as CubeVertex cube[], this is exactly 36 * 12 bytes
+ // VkDeviceSize bufferSize = sizeof(cube);
+ // vertexCount = sizeof(cube) / sizeof(cube[0]);
+
+ float *cube = NULL;
+ uint16_t size = 0;
+ // getObjInfo("cube.obj", &cube, &size);
+ getObjInfo("./monkey.obj", &cube, &size);
+ for (int i = 0; i < size; ++i) {
+ printf("%f\n", cube[i]);
+ }
+
+ VkDeviceSize bufferSize = sizeof(float) * size;
+ vertexCount = size;
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = bufferSize,
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+
+ if (vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create vertex buffer\n");
+ exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ // 2. Find memory that is both Visible (can map it) and Coherent (no flush needed)
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+ };
+
+ if (vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to allocate vertex buffer memory\n");
+ exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ // 3. Map and copy DIRECTLY from your 'cube' array
+ // This removes the risk of struct-mismatch bugs
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data);
+ memcpy(data, cube, (size_t)bufferSize);
+ vkUnmapMemory(device, vertexBufferMemory);
+ if (cube) free(cube);
+}
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("cube.vert.spv");
+ VkShaderModule fragModule = load_shader("cube.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ // One Vertex per vertex
+ // Move forward sizeof(Vertex) each vertex
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(CubeVertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+
+ VkVertexInputAttributeDescription attrDesc[] = {
+ {
+ .location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = 0
+ }
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=1,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ // .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_LINE, .cullMode=VK_CULL_MODE_NONE,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ // .rasterizationSamples = VK_SAMPLE_COUNT_4_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPushConstantRange pushRange = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(float)
+ };
+
+ VkDescriptorSetLayoutBinding texBinding = {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
+ };
+
+ VkDescriptorSetLayoutCreateInfo layoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .bindingCount = 1,
+ .pBindings = &texBinding
+ };
+
+ vkCreateDescriptorSetLayout(device, &layoutInfo, NULL, &descriptorSetLayout);
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pushRange,
+ .setLayoutCount = 0,
+ .pSetLayouts = NULL,
+ };
+
+ vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout);
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+
+ VkPipelineRenderingCreateInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+ };
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = {
+ .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &renderingInfo,
+ .stageCount = 2,
+ .pStages = stages,
+ .pVertexInputState = &vertexInput,
+ .pInputAssemblyState = &inputAssembly,
+ .pViewportState = &vpState,
+ .pRasterizationState = &rasterizer,
+ .pMultisampleState = &multisampling,
+ .pColorBlendState = &colorBlending,
+ .layout = pipelineLayout,
+ .renderPass = VK_NULL_HANDLE,
+ .subpass = 0
+ };
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,
+ &graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+/* --- Main --- */
+int main(void) {
+ double lastTime = glfwGetTime();
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+
+ uint32_t instanceVersion;
+ vkEnumerateInstanceVersion(&instanceVersion);
+ printf("Vulkan loader supports version %d.%d.%d\n",
+ VK_API_VERSION_MAJOR(instanceVersion),
+ VK_API_VERSION_MINOR(instanceVersion),
+ VK_API_VERSION_PATCH(instanceVersion)
+ );
+
+ if (!(VK_API_VERSION_MAJOR(instanceVersion) >= 1 &&
+ VK_API_VERSION_MINOR(instanceVersion) >= 3)) {
+ fprintf(stderr, "need at least vulkan 1.3");
+ exit(1);
+ }
+
+ uint32_t ext_count=0;
+ VkApplicationInfo appInfo = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = "cube",
+ .applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0),
+ .apiVersion = VK_API_VERSION_1_3
+ };
+
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &appInfo,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+
+ vkCreateInstance(&ici,NULL,&instance);
+
+ // You get instance-level pointers this way
+ volkLoadInstance(instance);
+
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8];
+ vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ printf("device count: %d\n", physCount);
+
+ // getting the name of the devices
+ for (int i = 0; i < physCount; ++i) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(devices[i], &properties);
+ printf("GPU Name: %s\n", properties.deviceName);
+
+ if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ physical = devices[i];
+ }
+
+ }
+
+ uint32_t qcount=0;
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ VkQueueFamilyProperties qprops[qcount];
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }
+
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ // .pNext = NULL,
+ .dynamicRendering = VK_TRUE
+ };
+
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &dynamicRenderingFeature,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ // You get Device-level pointers this way
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ imagesInFlight = malloc(sizeof(VkFence) * swapchainImageCount);
+ for (uint32_t i = 0; i < swapchainImageCount; i++)
+ imagesInFlight[i] = VK_NULL_HANDLE;
+
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+
+
+ create_vertex_buffer();
+
+ VkSemaphoreCreateInfo sem = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
+ };
+
+ VkFenceCreateInfo fence = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT
+ };
+
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+ }
+
+ uint32_t currentFrame = 0;
+
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ double now = glfwGetTime();
+ double delta = now - lastTime;
+ if (delta > 0.03) delta = 0.03; // clamp to max ~33ms
+ lastTime = now;
+ float rotationSpeed = M_PI / 2.0f; // 90°/sec
+ angle += rotationSpeed * delta;
+
+ uint32_t imgIndex;
+
+ // Acquire next swapchain image
+ vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+ );
+
+ // Wait for previous frame using this image
+ if (imagesInFlight[imgIndex] != VK_NULL_HANDLE) {
+ vkWaitForFences(device, 1, &imagesInFlight[imgIndex], VK_TRUE, UINT64_MAX);
+ }
+
+ // Mark image as now in use by current frame's fence
+ imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+
+ // Reset current frame fence
+ vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+ // Get command buffer for this image
+ VkCommandBuffer cb = commandBuffers[imgIndex];
+ vkResetCommandBuffer(cb, 0);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ // Transition image: PRESENT -> COLOR_ATTACHMENT_OPTIMAL
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .image = swapchainImages[imgIndex],
+ .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }
+ };
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+ // Begin dynamic rendering
+ VkRenderingAttachmentInfo colorAttachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = swapchainImageViews[imgIndex],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = {{0.0f, 0.0f, 0.0f, 1.0f}}
+ };
+ VkRenderingInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {{0, 0}, swapchainExtent},
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorAttachment
+ };
+ vkCmdBeginRendering(cb, &renderingInfo);
+
+ // Bind pipeline, viewport, scissor
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+ VkViewport vp = {0, 0, (float)swapchainExtent.width, (float)swapchainExtent.height, 0.0f, 1.0f};
+ VkRect2D scissor = {{0,0}, swapchainExtent};
+ vkCmdSetViewport(cb, 0, 1, &vp);
+ vkCmdSetScissor(cb, 0, 1, &scissor);
+
+ // Bind vertex buffer
+ VkDeviceSize offset = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vertexBuffer, &offset);
+
+ // Push rotation angle
+ vkCmdPushConstants(cb, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float), &angle);
+
+ // Bind descriptor set
+ vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+ // Draw
+ vkCmdDraw(cb, vertexCount, 1, 0, 0);
+
+ vkCmdEndRendering(cb);
+
+ // Transition image: COLOR_ATTACHMENT_OPTIMAL -> PRESENT
+ barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &barrier);
+
+ vkEndCommandBuffer(cb);
+
+ // Submit
+ VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &imageAvailable[currentFrame],
+ .pWaitDstStageMask = &waitStage,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &renderFinished[currentFrame],
+ };
+ vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+ // Present
+ VkPresentInfoKHR present = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &renderFinished[currentFrame],
+ .swapchainCount = 1,
+ .pSwapchains = &swapchain,
+ .pImageIndices = &imgIndex
+ };
+ vkQueuePresentKHR(graphicsQueue, &present);
+
+ // Advance frame
+ currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+ }
+
+
+ // Wait for GPU to finish all work
+ vkDeviceWaitIdle(device);
+
+ // Destroy semaphores and fences
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (imageAvailable[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, imageAvailable[i], NULL);
+ if (renderFinished[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, renderFinished[i], NULL);
+ if (inFlightFences[i] != VK_NULL_HANDLE)
+ vkDestroyFence(device, inFlightFences[i], NULL);
+ }
+
+ // Free imagesInFlight array
+ free(imagesInFlight);
+
+ // Destroy vertex buffer
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+
+ // Destroy texture resources
+ vkDestroySampler(device, sampler, NULL);
+ vkDestroyImageView(device, textureImageView, NULL);
+ vkDestroyImage(device, textureImage, NULL);
+ vkFreeMemory(device, textureMemory, NULL);
+
+ // Destroy descriptor sets/pools/layout
+ vkDestroyDescriptorPool(device, descriptorPool, NULL);
+ vkDestroyDescriptorSetLayout(device, descriptorSetLayout, NULL);
+
+ // Destroy pipeline and pipeline layout
+ vkDestroyPipeline(device, graphicsPipeline, NULL);
+ vkDestroyPipelineLayout(device, pipelineLayout, NULL);
+
+ // Destroy command pool
+ vkDestroyCommandPool(device, commandPool, NULL);
+
+ // Destroy swapchain image views
+ for (uint32_t i = 0; i < swapchainImageCount; i++) {
+ vkDestroyImageView(device, swapchainImageViews[i], NULL);
+ }
+ free(swapchainImageViews);
+ free(swapchainImages);
+
+ // Destroy swapchain
+ vkDestroySwapchainKHR(device, swapchain, NULL);
+
+ // Destroy device, surface, and instance
+ vkDestroyDevice(device, NULL);
+ vkDestroySurfaceKHR(instance, surface, NULL);
+ vkDestroyInstance(instance, NULL);
+
+ // Destroy GLFW window
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}
--- /dev/null
+# Blender 5.0.1 MTL File: 'None'
+# www.blender.org
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib monkey.mtl
+o Suzanne
+v 0.437500 0.164062 0.765625
+v -0.437500 0.164062 0.765625
+v 0.500000 0.093750 0.687500
+v -0.500000 0.093750 0.687500
+v 0.546875 0.054688 0.578125
+v -0.546875 0.054688 0.578125
+v 0.351562 -0.023438 0.617188
+v -0.351562 -0.023438 0.617188
+v 0.351562 0.031250 0.718750
+v -0.351562 0.031250 0.718750
+v 0.351562 0.132812 0.781250
+v -0.351562 0.132812 0.781250
+v 0.273438 0.164062 0.796875
+v -0.273438 0.164062 0.796875
+v 0.203125 0.093750 0.742188
+v -0.203125 0.093750 0.742188
+v 0.156250 0.054688 0.648438
+v -0.156250 0.054688 0.648438
+v 0.078125 0.242188 0.656250
+v -0.078125 0.242188 0.656250
+v 0.140625 0.242188 0.742188
+v -0.140625 0.242188 0.742188
+v 0.242188 0.242188 0.796875
+v -0.242188 0.242188 0.796875
+v 0.273438 0.328125 0.796875
+v -0.273438 0.328125 0.796875
+v 0.203125 0.390625 0.742188
+v -0.203125 0.390625 0.742188
+v 0.156250 0.437500 0.648438
+v -0.156250 0.437500 0.648438
+v 0.351562 0.515625 0.617188
+v -0.351562 0.515625 0.617188
+v 0.351562 0.453125 0.718750
+v -0.351562 0.453125 0.718750
+v 0.351562 0.359375 0.781250
+v -0.351562 0.359375 0.781250
+v 0.437500 0.328125 0.765625
+v -0.437500 0.328125 0.765625
+v 0.500000 0.390625 0.687500
+v -0.500000 0.390625 0.687500
+v 0.546875 0.437500 0.578125
+v -0.546875 0.437500 0.578125
+v 0.625000 0.242188 0.562500
+v -0.625000 0.242188 0.562500
+v 0.562500 0.242188 0.671875
+v -0.562500 0.242188 0.671875
+v 0.468750 0.242188 0.757812
+v -0.468750 0.242188 0.757812
+v 0.476562 0.242188 0.773438
+v -0.476562 0.242188 0.773438
+v 0.445312 0.335938 0.781250
+v -0.445312 0.335938 0.781250
+v 0.351562 0.375000 0.804688
+v -0.351562 0.375000 0.804688
+v 0.265625 0.335938 0.820312
+v -0.265625 0.335938 0.820312
+v 0.226562 0.242188 0.820312
+v -0.226562 0.242188 0.820312
+v 0.265625 0.156250 0.820312
+v -0.265625 0.156250 0.820312
+v 0.351562 0.242188 0.828125
+v -0.351562 0.242188 0.828125
+v 0.351562 0.117188 0.804688
+v -0.351562 0.117188 0.804688
+v 0.445312 0.156250 0.781250
+v -0.445312 0.156250 0.781250
+v 0.000000 0.429688 0.742188
+v 0.000000 0.351562 0.820312
+v 0.000000 -0.679688 0.734375
+v 0.000000 -0.320312 0.781250
+v 0.000000 -0.187500 0.796875
+v 0.000000 -0.773438 0.718750
+v 0.000000 0.406250 0.601562
+v 0.000000 0.570312 0.570312
+v 0.000000 0.898438 -0.546875
+v 0.000000 0.562500 -0.851562
+v 0.000000 0.070312 -0.828125
+v 0.000000 -0.382812 -0.351562
+v 0.203125 -0.187500 0.562500
+v -0.203125 -0.187500 0.562500
+v 0.312500 -0.437500 0.570312
+v -0.312500 -0.437500 0.570312
+v 0.351562 -0.695312 0.570312
+v -0.351562 -0.695312 0.570312
+v 0.367188 -0.890625 0.531250
+v -0.367188 -0.890625 0.531250
+v 0.328125 -0.945312 0.523438
+v -0.328125 -0.945312 0.523438
+v 0.179688 -0.968750 0.554688
+v -0.179688 -0.968750 0.554688
+v 0.000000 -0.984375 0.578125
+v 0.437500 -0.140625 0.531250
+v -0.437500 -0.140625 0.531250
+v 0.632812 -0.039062 0.539062
+v -0.632812 -0.039062 0.539062
+v 0.828125 0.148438 0.445312
+v -0.828125 0.148438 0.445312
+v 0.859375 0.429688 0.593750
+v -0.859375 0.429688 0.593750
+v 0.710938 0.484375 0.625000
+v -0.710938 0.484375 0.625000
+v 0.492188 0.601562 0.687500
+v -0.492188 0.601562 0.687500
+v 0.320312 0.757812 0.734375
+v -0.320312 0.757812 0.734375
+v 0.156250 0.718750 0.757812
+v -0.156250 0.718750 0.757812
+v 0.062500 0.492188 0.750000
+v -0.062500 0.492188 0.750000
+v 0.164062 0.414062 0.773438
+v -0.164062 0.414062 0.773438
+v 0.125000 0.304688 0.765625
+v -0.125000 0.304688 0.765625
+v 0.203125 0.093750 0.742188
+v -0.203125 0.093750 0.742188
+v 0.375000 0.015625 0.703125
+v -0.375000 0.015625 0.703125
+v 0.492188 0.062500 0.671875
+v -0.492188 0.062500 0.671875
+v 0.625000 0.187500 0.648438
+v -0.625000 0.187500 0.648438
+v 0.640625 0.296875 0.648438
+v -0.640625 0.296875 0.648438
+v 0.601562 0.375000 0.664062
+v -0.601562 0.375000 0.664062
+v 0.429688 0.437500 0.718750
+v -0.429688 0.437500 0.718750
+v 0.250000 0.468750 0.757812
+v -0.250000 0.468750 0.757812
+v 0.000000 -0.765625 0.734375
+v 0.109375 -0.718750 0.734375
+v -0.109375 -0.718750 0.734375
+v 0.117188 -0.835938 0.710938
+v -0.117188 -0.835938 0.710938
+v 0.062500 -0.882812 0.695312
+v -0.062500 -0.882812 0.695312
+v 0.000000 -0.890625 0.687500
+v 0.000000 -0.195312 0.750000
+v 0.000000 -0.140625 0.742188
+v 0.101562 -0.148438 0.742188
+v -0.101562 -0.148438 0.742188
+v 0.125000 -0.226562 0.750000
+v -0.125000 -0.226562 0.750000
+v 0.085938 -0.289062 0.742188
+v -0.085938 -0.289062 0.742188
+v 0.398438 -0.046875 0.671875
+v -0.398438 -0.046875 0.671875
+v 0.617188 0.054688 0.625000
+v -0.617188 0.054688 0.625000
+v 0.726562 0.203125 0.601562
+v -0.726562 0.203125 0.601562
+v 0.742188 0.375000 0.656250
+v -0.742188 0.375000 0.656250
+v 0.687500 0.414062 0.726562
+v -0.687500 0.414062 0.726562
+v 0.437500 0.546875 0.796875
+v -0.437500 0.546875 0.796875
+v 0.312500 0.640625 0.835938
+v -0.312500 0.640625 0.835938
+v 0.203125 0.617188 0.851562
+v -0.203125 0.617188 0.851562
+v 0.101562 0.429688 0.843750
+v -0.101562 0.429688 0.843750
+v 0.125000 -0.101562 0.812500
+v -0.125000 -0.101562 0.812500
+v 0.210938 -0.445312 0.710938
+v -0.210938 -0.445312 0.710938
+v 0.250000 -0.703125 0.687500
+v -0.250000 -0.703125 0.687500
+v 0.265625 -0.820312 0.664062
+v -0.265625 -0.820312 0.664062
+v 0.234375 -0.914062 0.632812
+v -0.234375 -0.914062 0.632812
+v 0.164062 -0.929688 0.632812
+v -0.164062 -0.929688 0.632812
+v 0.000000 -0.945312 0.640625
+v 0.000000 0.046875 0.726562
+v 0.000000 0.210938 0.765625
+v 0.328125 0.476562 0.742188
+v -0.328125 0.476562 0.742188
+v 0.164062 0.140625 0.750000
+v -0.164062 0.140625 0.750000
+v 0.132812 0.210938 0.757812
+v -0.132812 0.210938 0.757812
+v 0.117188 -0.687500 0.734375
+v -0.117188 -0.687500 0.734375
+v 0.078125 -0.445312 0.750000
+v -0.078125 -0.445312 0.750000
+v 0.000000 -0.445312 0.750000
+v 0.000000 -0.328125 0.742188
+v 0.093750 -0.273438 0.781250
+v -0.093750 -0.273438 0.781250
+v 0.132812 -0.226562 0.796875
+v -0.132812 -0.226562 0.796875
+v 0.109375 -0.132812 0.781250
+v -0.109375 -0.132812 0.781250
+v 0.039062 -0.125000 0.781250
+v -0.039062 -0.125000 0.781250
+v 0.000000 -0.203125 0.828125
+v 0.046875 -0.148438 0.812500
+v -0.046875 -0.148438 0.812500
+v 0.093750 -0.156250 0.812500
+v -0.093750 -0.156250 0.812500
+v 0.109375 -0.226562 0.828125
+v -0.109375 -0.226562 0.828125
+v 0.078125 -0.250000 0.804688
+v -0.078125 -0.250000 0.804688
+v 0.000000 -0.289062 0.804688
+v 0.257812 -0.312500 0.554688
+v -0.257812 -0.312500 0.554688
+v 0.164062 -0.242188 0.710938
+v -0.164062 -0.242188 0.710938
+v 0.179688 -0.312500 0.710938
+v -0.179688 -0.312500 0.710938
+v 0.234375 -0.250000 0.554688
+v -0.234375 -0.250000 0.554688
+v 0.000000 -0.875000 0.687500
+v 0.046875 -0.867188 0.687500
+v -0.046875 -0.867188 0.687500
+v 0.093750 -0.820312 0.710938
+v -0.093750 -0.820312 0.710938
+v 0.093750 -0.742188 0.726562
+v -0.093750 -0.742188 0.726562
+v 0.000000 -0.781250 0.656250
+v 0.093750 -0.750000 0.664062
+v -0.093750 -0.750000 0.664062
+v 0.093750 -0.812500 0.640625
+v -0.093750 -0.812500 0.640625
+v 0.046875 -0.851562 0.632812
+v -0.046875 -0.851562 0.632812
+v 0.000000 -0.859375 0.632812
+v 0.171875 0.218750 0.781250
+v -0.171875 0.218750 0.781250
+v 0.187500 0.156250 0.773438
+v -0.187500 0.156250 0.773438
+v 0.335938 0.429688 0.757812
+v -0.335938 0.429688 0.757812
+v 0.273438 0.421875 0.773438
+v -0.273438 0.421875 0.773438
+v 0.421875 0.398438 0.773438
+v -0.421875 0.398438 0.773438
+v 0.562500 0.351562 0.695312
+v -0.562500 0.351562 0.695312
+v 0.585938 0.289062 0.687500
+v -0.585938 0.289062 0.687500
+v 0.578125 0.195312 0.679688
+v -0.578125 0.195312 0.679688
+v 0.476562 0.101562 0.718750
+v -0.476562 0.101562 0.718750
+v 0.375000 0.062500 0.742188
+v -0.375000 0.062500 0.742188
+v 0.226562 0.109375 0.781250
+v -0.226562 0.109375 0.781250
+v 0.179688 0.296875 0.781250
+v -0.179688 0.296875 0.781250
+v 0.210938 0.375000 0.781250
+v -0.210938 0.375000 0.781250
+v 0.234375 0.359375 0.757812
+v -0.234375 0.359375 0.757812
+v 0.195312 0.296875 0.757812
+v -0.195312 0.296875 0.757812
+v 0.242188 0.125000 0.757812
+v -0.242188 0.125000 0.757812
+v 0.375000 0.085938 0.726562
+v -0.375000 0.085938 0.726562
+v 0.460938 0.117188 0.703125
+v -0.460938 0.117188 0.703125
+v 0.546875 0.210938 0.671875
+v -0.546875 0.210938 0.671875
+v 0.554688 0.281250 0.671875
+v -0.554688 0.281250 0.671875
+v 0.531250 0.335938 0.679688
+v -0.531250 0.335938 0.679688
+v 0.414062 0.390625 0.750000
+v -0.414062 0.390625 0.750000
+v 0.281250 0.398438 0.765625
+v -0.281250 0.398438 0.765625
+v 0.335938 0.406250 0.750000
+v -0.335938 0.406250 0.750000
+v 0.203125 0.171875 0.750000
+v -0.203125 0.171875 0.750000
+v 0.195312 0.226562 0.750000
+v -0.195312 0.226562 0.750000
+v 0.109375 0.460938 0.609375
+v -0.109375 0.460938 0.609375
+v 0.195312 0.664062 0.617188
+v -0.195312 0.664062 0.617188
+v 0.335938 0.687500 0.593750
+v -0.335938 0.687500 0.593750
+v 0.484375 0.554688 0.554688
+v -0.484375 0.554688 0.554688
+v 0.679688 0.453125 0.492188
+v -0.679688 0.453125 0.492188
+v 0.796875 0.406250 0.460938
+v -0.796875 0.406250 0.460938
+v 0.773438 0.164062 0.375000
+v -0.773438 0.164062 0.375000
+v 0.601562 0.000000 0.414062
+v -0.601562 0.000000 0.414062
+v 0.437500 -0.093750 0.468750
+v -0.437500 -0.093750 0.468750
+v 0.000000 0.898438 0.289062
+v 0.000000 0.984375 -0.078125
+v 0.000000 -0.195312 -0.671875
+v 0.000000 -0.460938 0.187500
+v 0.000000 -0.976562 0.460938
+v 0.000000 -0.804688 0.343750
+v 0.000000 -0.570312 0.320312
+v 0.000000 -0.484375 0.281250
+v 0.851562 0.234375 0.054688
+v -0.851562 0.234375 0.054688
+v 0.859375 0.320312 -0.046875
+v -0.859375 0.320312 -0.046875
+v 0.773438 0.265625 -0.437500
+v -0.773438 0.265625 -0.437500
+v 0.460938 0.437500 -0.703125
+v -0.460938 0.437500 -0.703125
+v 0.734375 -0.046875 0.070312
+v -0.734375 -0.046875 0.070312
+v 0.593750 -0.125000 -0.164062
+v -0.593750 -0.125000 -0.164062
+v 0.640625 -0.007812 -0.429688
+v -0.640625 -0.007812 -0.429688
+v 0.335938 0.054688 -0.664062
+v -0.335938 0.054688 -0.664062
+v 0.234375 -0.351562 0.406250
+v -0.234375 -0.351562 0.406250
+v 0.179688 -0.414062 0.257812
+v -0.179688 -0.414062 0.257812
+v 0.289062 -0.710938 0.382812
+v -0.289062 -0.710938 0.382812
+v 0.250000 -0.500000 0.390625
+v -0.250000 -0.500000 0.390625
+v 0.328125 -0.914062 0.398438
+v -0.328125 -0.914062 0.398438
+v 0.140625 -0.757812 0.367188
+v -0.140625 -0.757812 0.367188
+v 0.125000 -0.539062 0.359375
+v -0.125000 -0.539062 0.359375
+v 0.164062 -0.945312 0.437500
+v -0.164062 -0.945312 0.437500
+v 0.218750 -0.281250 0.429688
+v -0.218750 -0.281250 0.429688
+v 0.210938 -0.226562 0.468750
+v -0.210938 -0.226562 0.468750
+v 0.203125 -0.171875 0.500000
+v -0.203125 -0.171875 0.500000
+v 0.210938 -0.390625 0.164062
+v -0.210938 -0.390625 0.164062
+v 0.296875 -0.312500 -0.265625
+v -0.296875 -0.312500 -0.265625
+v 0.343750 -0.148438 -0.539062
+v -0.343750 -0.148438 -0.539062
+v 0.453125 0.867188 -0.382812
+v -0.453125 0.867188 -0.382812
+v 0.453125 0.929688 -0.070312
+v -0.453125 0.929688 -0.070312
+v 0.453125 0.851562 0.234375
+v -0.453125 0.851562 0.234375
+v 0.460938 0.523438 0.429688
+v -0.460938 0.523438 0.429688
+v 0.726562 0.406250 0.335938
+v -0.726562 0.406250 0.335938
+v 0.632812 0.453125 0.281250
+v -0.632812 0.453125 0.281250
+v 0.640625 0.703125 0.054688
+v -0.640625 0.703125 0.054688
+v 0.796875 0.562500 0.125000
+v -0.796875 0.562500 0.125000
+v 0.796875 0.617188 -0.117188
+v -0.796875 0.617188 -0.117188
+v 0.640625 0.750000 -0.195312
+v -0.640625 0.750000 -0.195312
+v 0.640625 0.679688 -0.445312
+v -0.640625 0.679688 -0.445312
+v 0.796875 0.539062 -0.359375
+v -0.796875 0.539062 -0.359375
+v 0.617188 0.328125 -0.585938
+v -0.617188 0.328125 -0.585938
+v 0.484375 0.023438 -0.546875
+v -0.484375 0.023438 -0.546875
+v 0.820312 0.328125 -0.203125
+v -0.820312 0.328125 -0.203125
+v 0.406250 -0.171875 0.148438
+v -0.406250 -0.171875 0.148438
+v 0.429688 -0.195312 -0.210938
+v -0.429688 -0.195312 -0.210938
+v 0.890625 0.406250 -0.234375
+v -0.890625 0.406250 -0.234375
+v 0.773438 -0.140625 -0.125000
+v -0.773438 -0.140625 -0.125000
+v 1.039062 -0.101562 -0.328125
+v -1.039062 -0.101562 -0.328125
+v 1.281250 0.054688 -0.429688
+v -1.281250 0.054688 -0.429688
+v 1.351562 0.320312 -0.421875
+v -1.351562 0.320312 -0.421875
+v 1.234375 0.507812 -0.421875
+v -1.234375 0.507812 -0.421875
+v 1.023438 0.476562 -0.312500
+v -1.023438 0.476562 -0.312500
+v 1.015625 0.414062 -0.289062
+v -1.015625 0.414062 -0.289062
+v 1.187500 0.437500 -0.390625
+v -1.187500 0.437500 -0.390625
+v 1.265625 0.289062 -0.406250
+v -1.265625 0.289062 -0.406250
+v 1.210938 0.078125 -0.406250
+v -1.210938 0.078125 -0.406250
+v 1.031250 -0.039062 -0.304688
+v -1.031250 -0.039062 -0.304688
+v 0.828125 -0.070312 -0.132812
+v -0.828125 -0.070312 -0.132812
+v 0.921875 0.359375 -0.218750
+v -0.921875 0.359375 -0.218750
+v 0.945312 0.304688 -0.289062
+v -0.945312 0.304688 -0.289062
+v 0.882812 -0.023438 -0.210938
+v -0.882812 -0.023438 -0.210938
+v 1.039062 0.000000 -0.367188
+v -1.039062 0.000000 -0.367188
+v 1.187500 0.093750 -0.445312
+v -1.187500 0.093750 -0.445312
+v 1.234375 0.250000 -0.445312
+v -1.234375 0.250000 -0.445312
+v 1.171875 0.359375 -0.437500
+v -1.171875 0.359375 -0.437500
+v 1.023438 0.343750 -0.359375
+v -1.023438 0.343750 -0.359375
+v 0.843750 0.289062 -0.210938
+v -0.843750 0.289062 -0.210938
+v 0.835938 0.171875 -0.273438
+v -0.835938 0.171875 -0.273438
+v 0.757812 0.093750 -0.273438
+v -0.757812 0.093750 -0.273438
+v 0.820312 0.085938 -0.273438
+v -0.820312 0.085938 -0.273438
+v 0.843750 0.015625 -0.273438
+v -0.843750 0.015625 -0.273438
+v 0.812500 -0.015625 -0.273438
+v -0.812500 -0.015625 -0.273438
+v 0.726562 0.000000 -0.070312
+v -0.726562 0.000000 -0.070312
+v 0.718750 -0.023438 -0.171875
+v -0.718750 -0.023438 -0.171875
+v 0.718750 0.039062 -0.187500
+v -0.718750 0.039062 -0.187500
+v 0.796875 0.203125 -0.210938
+v -0.796875 0.203125 -0.210938
+v 0.890625 0.242188 -0.265625
+v -0.890625 0.242188 -0.265625
+v 0.890625 0.234375 -0.320312
+v -0.890625 0.234375 -0.320312
+v 0.812500 -0.015625 -0.320312
+v -0.812500 -0.015625 -0.320312
+v 0.851562 0.015625 -0.320312
+v -0.851562 0.015625 -0.320312
+v 0.828125 0.078125 -0.320312
+v -0.828125 0.078125 -0.320312
+v 0.765625 0.093750 -0.320312
+v -0.765625 0.093750 -0.320312
+v 0.843750 0.171875 -0.320312
+v -0.843750 0.171875 -0.320312
+v 1.039062 0.328125 -0.414062
+v -1.039062 0.328125 -0.414062
+v 1.187500 0.343750 -0.484375
+v -1.187500 0.343750 -0.484375
+v 1.257812 0.242188 -0.492188
+v -1.257812 0.242188 -0.492188
+v 1.210938 0.085938 -0.484375
+v -1.210938 0.085938 -0.484375
+v 1.046875 0.000000 -0.421875
+v -1.046875 0.000000 -0.421875
+v 0.882812 -0.015625 -0.265625
+v -0.882812 -0.015625 -0.265625
+v 0.953125 0.289062 -0.343750
+v -0.953125 0.289062 -0.343750
+v 0.890625 0.109375 -0.328125
+v -0.890625 0.109375 -0.328125
+v 0.937500 0.062500 -0.335938
+v -0.937500 0.062500 -0.335938
+v 1.000000 0.125000 -0.367188
+v -1.000000 0.125000 -0.367188
+v 0.960938 0.171875 -0.351562
+v -0.960938 0.171875 -0.351562
+v 1.015625 0.234375 -0.375000
+v -1.015625 0.234375 -0.375000
+v 1.054688 0.187500 -0.382812
+v -1.054688 0.187500 -0.382812
+v 1.109375 0.210938 -0.390625
+v -1.109375 0.210938 -0.390625
+v 1.085938 0.273438 -0.390625
+v -1.085938 0.273438 -0.390625
+v 1.023438 0.437500 -0.484375
+v -1.023438 0.437500 -0.484375
+v 1.250000 0.468750 -0.546875
+v -1.250000 0.468750 -0.546875
+v 1.367188 0.296875 -0.500000
+v -1.367188 0.296875 -0.500000
+v 1.312500 0.054688 -0.531250
+v -1.312500 0.054688 -0.531250
+v 1.039062 -0.085938 -0.492188
+v -1.039062 -0.085938 -0.492188
+v 0.789062 -0.125000 -0.328125
+v -0.789062 -0.125000 -0.328125
+v 0.859375 0.382812 -0.382812
+v -0.859375 0.382812 -0.382812
+vn 0.6650 -0.2008 0.7194
+vn -0.6650 -0.2008 0.7194
+vn 0.8294 -0.3036 0.4689
+vn -0.8294 -0.3036 0.4689
+vn 0.4155 -0.7933 0.4449
+vn -0.4155 -0.7933 0.4449
+vn 0.3600 -0.5089 0.7820
+vn -0.3600 -0.5089 0.7820
+vn -0.0787 -0.5394 0.8384
+vn 0.0787 -0.5394 0.8384
+vn -0.2696 -0.8413 0.4685
+vn 0.2696 -0.8413 0.4685
+vn -0.7707 -0.3352 0.5420
+vn 0.7707 -0.3352 0.5420
+vn -0.4689 -0.1940 0.8617
+vn 0.4689 -0.1940 0.8617
+vn -0.4767 0.1907 0.8581
+vn 0.4767 0.1907 0.8581
+vn -0.7672 0.3264 0.5521
+vn 0.7672 0.3264 0.5521
+vn -0.2519 0.8173 0.5182
+vn 0.2519 0.8173 0.5182
+vn -0.0949 0.5696 0.8164
+vn 0.0949 0.5696 0.8164
+vn 0.3667 0.5370 0.7597
+vn -0.3667 0.5370 0.7597
+vn 0.4141 0.7672 0.4898
+vn -0.4141 0.7672 0.4898
+vn 0.8277 0.2952 0.4771
+vn -0.8277 0.2952 0.4771
+vn 0.6713 0.1971 0.7145
+vn -0.6713 0.1971 0.7145
+vn 0.8111 0.3244 -0.4867
+vn -0.8111 0.3244 -0.4867
+vn 0.2052 0.8206 -0.5334
+vn -0.2052 0.8206 -0.5334
+vn -0.4223 0.7806 -0.4607
+vn 0.4223 0.7806 -0.4607
+vn -0.8241 0.3225 -0.4658
+vn 0.8241 0.3225 -0.4658
+vn -0.8137 -0.3487 -0.4650
+vn 0.8137 -0.3487 -0.4650
+vn -0.4223 -0.7806 -0.4607
+vn 0.4223 -0.7806 -0.4607
+vn 0.2052 -0.8206 -0.5334
+vn -0.2052 -0.8206 -0.5334
+vn 0.7995 -0.3510 -0.4875
+vn -0.7995 -0.3510 -0.4875
+vn 0.4000 -0.0623 0.9144
+vn -0.4000 -0.0623 0.9144
+vn 0.3069 -0.1754 0.9354
+vn -0.3069 -0.1754 0.9354
+vn 0.0945 -0.1835 0.9785
+vn -0.0945 -0.1835 0.9785
+vn -0.0624 -0.0283 0.9977
+vn 0.0624 -0.0283 0.9977
+vn -0.0624 0.0260 0.9977
+vn 0.0624 0.0260 0.9977
+vn 0.0996 0.1729 0.9799
+vn -0.0996 0.1729 0.9799
+vn 0.3036 0.1656 0.9383
+vn -0.3036 0.1656 0.9383
+vn 0.4002 0.0572 0.9147
+vn -0.4002 0.0572 0.9147
+vn 0.1231 -0.8616 0.4924
+vn -0.1231 -0.8616 0.4924
+vn 0.2190 -0.8647 0.4520
+vn -0.2190 -0.8647 0.4520
+vn 0.5902 -0.4550 0.6668
+vn -0.5902 -0.4550 0.6668
+vn 0.7689 -0.0506 0.6374
+vn -0.7689 -0.0506 0.6374
+vn 0.7796 0.0900 0.6197
+vn -0.7796 0.0900 0.6197
+vn 0.3241 -0.8188 0.4739
+vn -0.3241 -0.8188 0.4739
+vn 0.3857 -0.6629 0.6417
+vn -0.3857 -0.6629 0.6417
+vn 0.6895 -0.4193 0.5906
+vn -0.6895 -0.4193 0.5906
+vn 0.6588 -0.3634 0.6588
+vn -0.6588 -0.3634 0.6588
+vn 0.5465 0.3707 0.7509
+vn -0.5465 0.3707 0.7509
+vn 0.5064 0.6464 0.5706
+vn -0.5064 0.6464 0.5706
+vn 0.6092 0.5167 0.6015
+vn -0.6092 0.5167 0.6015
+vn -0.0441 0.6610 0.7491
+vn 0.0441 0.6610 0.7491
+vn -0.7246 0.3187 0.6110
+vn 0.7246 0.3187 0.6110
+vn -0.5880 0.5554 0.5880
+vn 0.5880 0.5554 0.5880
+vn 0.5361 -0.3909 0.7482
+vn -0.5361 -0.3909 0.7482
+vn 0.2207 -0.4690 0.8552
+vn -0.2207 -0.4690 0.8552
+vn -0.0794 -0.5321 0.8429
+vn 0.0794 -0.5321 0.8429
+vn -0.0825 -0.6575 0.7490
+vn 0.0825 -0.6575 0.7490
+vn 0.0457 -0.5667 0.8226
+vn -0.0457 -0.5667 0.8226
+vn 0.2784 -0.2130 0.9365
+vn -0.2784 -0.2130 0.9365
+vn 0.3813 -0.1824 0.9063
+vn -0.3813 -0.1824 0.9063
+vn 0.3357 -0.2878 0.8969
+vn -0.3357 -0.2878 0.8969
+vn 0.3762 0.0603 0.9246
+vn -0.3762 0.0603 0.9246
+vn -0.1352 0.2680 0.9539
+vn 0.1352 0.2680 0.9539
+vn 0.3961 -0.4321 0.8102
+vn -0.3961 -0.4321 0.8102
+vn 0.1856 -0.2474 0.9510
+vn -0.1856 -0.2474 0.9510
+vn 0.0099 -0.1948 0.9808
+vn -0.0099 -0.1948 0.9808
+vn 0.0721 -0.6966 0.7138
+vn -0.0721 -0.6966 0.7138
+vn 0.1863 -0.5723 0.7986
+vn -0.1863 -0.5723 0.7986
+vn 0.3157 -0.2708 0.9094
+vn -0.3157 -0.2708 0.9094
+vn 0.3063 -0.0265 0.9516
+vn -0.3063 -0.0265 0.9516
+vn 0.3266 -0.1306 0.9361
+vn -0.3266 -0.1306 0.9361
+vn -0.0137 0.0574 0.9983
+vn 0.0137 0.0574 0.9983
+vn -0.0026 -0.0656 0.9978
+vn 0.0026 -0.0656 0.9978
+vn -0.0000 -0.0000 1.0000
+vn 0.8174 -0.5744 -0.0442
+vn -0.8174 -0.5744 -0.0442
+vn 0.9494 0.2297 -0.2144
+vn -0.9494 0.2297 -0.2144
+vn 0.0825 0.9073 -0.4124
+vn -0.0825 0.9073 -0.4124
+vn -0.8836 0.3555 0.3047
+vn 0.8836 0.3555 0.3047
+vn 0.4207 -0.8797 0.2218
+vn -0.4207 -0.8797 0.2218
+vn 0.2873 -0.5747 0.7663
+vn -0.2873 -0.5747 0.7663
+vn -0.6542 0.6019 0.4580
+vn 0.6542 0.6019 0.4580
+vn 0.1052 0.7892 0.6051
+vn -0.1052 0.7892 0.6051
+vn 0.7582 0.2916 0.5832
+vn -0.7582 0.2916 0.5832
+vn 0.3889 -0.7130 0.5834
+vn -0.3889 -0.7130 0.5834
+vn 0.0463 0.2314 0.9718
+vn -0.0463 0.2314 0.9718
+vn 0.0335 -0.4018 0.9151
+vn -0.0335 -0.4018 0.9151
+vn -0.4452 -0.1610 0.8809
+vn 0.4452 -0.1610 0.8809
+vn -0.2182 -0.4364 0.8729
+vn 0.2182 -0.4364 0.8729
+vn 0.4341 -0.1290 0.8916
+vn -0.4341 -0.1290 0.8916
+vn 0.3008 0.0501 0.9524
+vn -0.3008 0.0501 0.9524
+vn 0.8123 0.3010 0.4996
+vn -0.8123 0.3010 0.4996
+vn 0.8753 0.2574 0.4093
+vn -0.8753 0.2574 0.4093
+vn 0.9385 0.1601 0.3060
+vn -0.9385 0.1601 0.3060
+vn 0.2237 -0.6539 0.7227
+vn -0.2237 -0.6539 0.7227
+vn -0.1536 -0.1997 0.9677
+vn 0.1536 -0.1997 0.9677
+vn -0.2733 -0.1025 0.9565
+vn 0.2733 -0.1025 0.9565
+vn -0.0976 0.1952 0.9759
+vn 0.0976 0.1952 0.9759
+vn -0.1582 0.9494 0.2713
+vn 0.1582 0.9494 0.2713
+vn -0.6934 0.7082 0.1328
+vn 0.6934 0.7082 0.1328
+vn -1.0000 -0.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn 0.3051 -0.9450 0.1181
+vn -0.3051 -0.9450 0.1181
+vn 0.0298 -0.2981 0.9541
+vn -0.0298 -0.2981 0.9541
+vn 0.1353 -0.3479 0.9277
+vn -0.1353 -0.3479 0.9277
+vn -0.5085 -0.2755 0.8158
+vn 0.5085 -0.2755 0.8158
+vn -0.3843 -0.0419 0.9223
+vn 0.3843 -0.0419 0.9223
+vn -0.2083 0.0374 0.9774
+vn 0.2083 0.0374 0.9774
+vn -0.5721 -0.4767 0.6674
+vn 0.5721 -0.4767 0.6674
+vn -0.1369 -0.7531 0.6435
+vn 0.1369 -0.7531 0.6435
+vn 0.4088 -0.6071 0.6814
+vn -0.4088 -0.6071 0.6814
+vn 0.5740 -0.4130 0.7070
+vn -0.5740 -0.4130 0.7070
+vn 0.5665 -0.0968 0.8183
+vn -0.5665 -0.0968 0.8183
+vn 0.5703 0.1180 0.8129
+vn -0.5703 0.1180 0.8129
+vn 0.4823 0.5621 0.6719
+vn -0.4823 0.5621 0.6719
+vn 0.2604 0.6114 0.7473
+vn -0.2604 0.6114 0.7473
+vn 0.1640 0.3607 0.9182
+vn -0.1640 0.3607 0.9182
+vn -0.0178 0.2495 0.9682
+vn 0.0178 0.2495 0.9682
+vn 0.3273 -0.4166 0.8481
+vn -0.3273 -0.4166 0.8481
+vn 0.2811 -0.2610 0.9235
+vn -0.2811 -0.2610 0.9235
+vn -0.2542 -0.6514 0.7149
+vn 0.2542 -0.6514 0.7149
+vn -0.0260 -0.8455 0.5333
+vn 0.0260 -0.8455 0.5333
+vn -0.3518 -0.2606 0.8991
+vn 0.3518 -0.2606 0.8991
+vn -0.3523 -0.0110 0.9358
+vn 0.3523 -0.0110 0.9358
+vn -0.1317 0.4608 0.8777
+vn 0.1317 0.4608 0.8777
+vn -0.0342 0.6159 0.7870
+vn 0.0342 0.6159 0.7870
+vn 0.3603 0.5836 0.7277
+vn -0.3603 0.5836 0.7277
+vn 0.4988 0.5300 0.6858
+vn -0.4988 0.5300 0.6858
+vn 0.6667 -0.3333 0.6667
+vn -0.6667 -0.3333 0.6667
+vn 0.8165 -0.0731 0.5727
+vn -0.8165 -0.0731 0.5727
+vn 0.7840 0.1161 0.6098
+vn -0.7840 0.1161 0.6098
+vn -0.5306 0.8111 -0.2461
+vn 0.5306 0.8111 -0.2461
+vn -0.8511 0.3695 -0.3730
+vn 0.8511 0.3695 -0.3730
+vn -0.2446 0.8675 -0.4331
+vn 0.2446 0.8675 -0.4331
+vn 0.5924 0.7465 -0.3030
+vn -0.5924 0.7465 -0.3030
+vn 0.3685 0.8758 -0.3118
+vn -0.3685 0.8758 -0.3118
+vn 0.2821 0.9151 -0.2880
+vn -0.2821 0.9151 -0.2880
+vn 0.8561 0.1340 -0.4991
+vn -0.8561 0.1340 -0.4991
+vn 0.5342 -0.7233 -0.4376
+vn -0.5342 -0.7233 -0.4376
+vn 0.3849 -0.8131 -0.4368
+vn -0.3849 -0.8131 -0.4368
+vn 0.2335 -0.5806 -0.7800
+vn -0.2335 -0.5806 -0.7800
+vn 0.2449 -0.0583 -0.9678
+vn -0.2449 -0.0583 -0.9678
+vn 0.1163 -0.4535 -0.8837
+vn -0.1163 -0.4535 -0.8837
+vn 0.1152 -0.9836 -0.1388
+vn -0.1152 -0.9836 -0.1388
+vn 0.1184 -0.9669 -0.2260
+vn -0.1184 -0.9669 -0.2260
+vn 0.9597 -0.0085 -0.2808
+vn -0.9597 -0.0085 -0.2808
+vn 0.9319 0.1629 -0.3242
+vn -0.9319 0.1629 -0.3242
+vn 0.1626 0.0207 -0.9865
+vn -0.1626 0.0207 -0.9865
+vn -0.0188 -0.2177 -0.9758
+vn 0.0188 -0.2177 -0.9758
+vn 0.7538 -0.2926 -0.5884
+vn -0.7538 -0.2926 -0.5884
+vn 0.9196 0.1379 -0.3678
+vn -0.9196 0.1379 -0.3678
+vn 0.9297 0.3127 -0.1944
+vn -0.9297 0.3127 -0.1944
+vn 0.9120 0.3376 -0.2329
+vn -0.9120 0.3376 -0.2329
+vn 0.9407 0.3338 -0.0607
+vn -0.9407 0.3338 -0.0607
+vn 0.1761 -0.8805 -0.4402
+vn -0.1761 -0.8805 -0.4402
+vn 0.3708 -0.4733 -0.7991
+vn -0.3708 -0.4733 -0.7991
+vn 0.3107 -0.8284 -0.4660
+vn -0.3107 -0.8284 -0.4660
+vn 0.2793 -0.9515 -0.1287
+vn -0.2793 -0.9515 -0.1287
+vn 0.3139 -0.9321 -0.1807
+vn -0.3139 -0.9321 -0.1807
+vn 0.9762 -0.2083 -0.0609
+vn -0.9762 -0.2083 -0.0609
+vn 0.8267 -0.5066 0.2447
+vn -0.8267 -0.5066 0.2447
+vn 0.3449 -0.1158 -0.9315
+vn -0.3449 -0.1158 -0.9315
+vn 0.1203 0.9644 0.2355
+vn -0.1203 0.9644 0.2355
+vn 0.1275 0.9744 -0.1851
+vn -0.1275 0.9744 -0.1851
+vn 0.3492 0.5947 -0.7241
+vn -0.3492 0.5947 -0.7241
+vn 0.4153 0.8981 -0.1449
+vn -0.4153 0.8981 -0.1449
+vn 0.1845 0.7036 0.6863
+vn -0.1845 0.7036 0.6863
+vn 0.6056 0.7794 0.1608
+vn -0.6056 0.7794 0.1608
+vn 0.7033 0.6806 -0.2053
+vn -0.7033 0.6806 -0.2053
+vn 0.6679 0.2007 -0.7166
+vn -0.6679 0.2007 -0.7166
+vn 0.4948 0.4342 -0.7528
+vn -0.4948 0.4342 -0.7528
+vn 0.6423 0.7459 -0.1761
+vn -0.6423 0.7459 -0.1761
+vn 0.7182 0.6788 0.1530
+vn -0.7182 0.6788 0.1530
+vn 0.7388 0.3972 0.5444
+vn -0.7388 0.3972 0.5444
+vn 0.3428 0.9261 -0.1579
+vn -0.3428 0.9261 -0.1579
+vn 0.2270 0.5740 0.7867
+vn -0.2270 0.5740 0.7867
+vn -0.1722 0.1046 -0.9795
+vn 0.1722 0.1046 -0.9795
+vn 0.0425 0.9150 0.4013
+vn -0.0425 0.9150 0.4013
+vn -0.1616 0.1847 0.9694
+vn 0.1616 0.1847 0.9694
+vn 0.9791 0.1973 0.0483
+vn -0.9791 0.1973 0.0483
+vn 0.9470 0.0918 0.3079
+vn -0.9470 0.0918 0.3079
+vn 0.9794 0.1905 -0.0661
+vn -0.9794 0.1905 -0.0661
+vn 0.9938 0.0312 -0.1070
+vn -0.9938 0.0312 -0.1070
+vn 0.7116 -0.7008 0.0501
+vn -0.7116 -0.7008 0.0501
+vn 0.3722 -0.9243 0.0847
+vn -0.3722 -0.9243 0.0847
+vn 0.4465 -0.8644 0.2310
+vn -0.4465 -0.8644 0.2310
+vn 0.6066 -0.7578 0.2405
+vn -0.6066 -0.7578 0.2405
+vn 0.7325 -0.6368 0.2407
+vn -0.7325 -0.6368 0.2407
+vn 0.2637 -0.4499 0.8533
+vn -0.2637 -0.4499 0.8533
+vn 0.5568 -0.3181 -0.7673
+vn -0.5568 -0.3181 -0.7673
+vn 0.5004 -0.2807 -0.8190
+vn -0.5004 -0.2807 -0.8190
+vn 0.3190 -0.8494 -0.4205
+vn -0.3190 -0.8494 -0.4205
+vn 0.7198 -0.6356 -0.2793
+vn -0.7198 -0.6356 -0.2793
+vn 0.4972 -0.4408 -0.7473
+vn -0.4972 -0.4408 -0.7473
+vn 0.3506 0.3807 0.8557
+vn -0.3506 0.3807 0.8557
+vn 0.4566 0.1715 0.8730
+vn -0.4566 0.1715 0.8730
+vn 0.2583 0.1055 0.9603
+vn -0.2583 0.1055 0.9603
+vn 0.2455 -0.0802 0.9661
+vn -0.2455 -0.0802 0.9661
+vn 0.4643 -0.0599 0.8837
+vn -0.4643 -0.0599 0.8837
+vn 0.6225 -0.3045 0.7210
+vn -0.6225 -0.3045 0.7210
+vn 0.4500 0.6590 0.6027
+vn -0.4500 0.6590 0.6027
+vn -0.2667 0.8309 0.4884
+vn 0.2667 0.8309 0.4884
+vn -0.8284 0.2291 0.5111
+vn 0.8284 0.2291 0.5111
+vn -0.5251 -0.3566 0.7727
+vn 0.5251 -0.3566 0.7727
+vn 0.4546 -0.5665 0.6873
+vn -0.4546 -0.5665 0.6873
+vn 0.6996 -0.4497 0.5552
+vn -0.6996 -0.4497 0.5552
+vn 0.7220 -0.6827 -0.1126
+vn -0.7220 -0.6827 -0.1126
+vn -0.1919 0.2860 0.9388
+vn 0.1919 0.2860 0.9388
+vn 0.9048 -0.3734 -0.2047
+vn -0.9048 -0.3734 -0.2047
+vn 0.1034 0.1551 0.9825
+vn -0.1034 0.1551 0.9825
+vn 0.0841 0.9318 0.3530
+vn -0.0841 0.9318 0.3530
+vn 0.6446 -0.0883 0.7594
+vn -0.6446 -0.0883 0.7594
+vn 0.4309 0.4740 0.7678
+vn -0.4309 0.4740 0.7678
+vn 0.8032 -0.4847 0.3462
+vn -0.8032 -0.4847 0.3462
+vn 0.5811 -0.4128 0.7014
+vn -0.5811 -0.4128 0.7014
+vn 0.5910 -0.4305 0.6822
+vn -0.5910 -0.4305 0.6822
+vn 0.9818 -0.1804 -0.0591
+vn -0.9818 -0.1804 -0.0591
+vn 0.9105 -0.3965 -0.1175
+vn -0.9105 -0.3965 -0.1175
+vn 0.9972 -0.0181 -0.0725
+vn -0.9972 -0.0181 -0.0725
+vn 0.7313 -0.6543 0.1925
+vn -0.7313 -0.6543 0.1925
+vn 0.7867 -0.6079 0.1073
+vn -0.7867 -0.6079 0.1073
+vn 0.7022 -0.7022 0.1170
+vn -0.7022 -0.7022 0.1170
+vn 0.1840 0.9816 -0.0511
+vn -0.1840 0.9816 -0.0511
+vn 0.9352 0.3301 0.1284
+vn -0.9352 0.3301 0.1284
+vn 0.6633 -0.7463 0.0553
+vn -0.6633 -0.7463 0.0553
+vn -0.0085 0.9970 0.0767
+vn 0.0085 0.9970 0.0767
+vn 0.6237 -0.7061 0.3354
+vn -0.6237 -0.7061 0.3354
+vn 0.2733 -0.8925 0.3587
+vn -0.2733 -0.8925 0.3587
+vn -0.8328 -0.5080 -0.2200
+vn 0.8328 -0.5080 -0.2200
+vn -0.8339 0.2377 -0.4981
+vn 0.8339 0.2377 -0.4981
+vn -0.5655 0.7847 -0.2539
+vn 0.5655 0.7847 -0.2539
+vn -0.0560 0.9962 0.0672
+vn 0.0560 0.9962 0.0672
+vn 0.1445 0.0222 0.9893
+vn -0.1445 0.0222 0.9893
+vn 0.3275 0.0645 0.9427
+vn -0.3275 0.0645 0.9427
+vn 0.3127 0.0232 0.9496
+vn -0.3127 0.0232 0.9496
+vn 0.1710 0.0274 0.9849
+vn -0.1710 0.0274 0.9849
+vn 0.3487 0.2849 0.8929
+vn -0.3487 0.2849 0.8929
+vn 0.4006 -0.0343 0.9156
+vn -0.4006 -0.0343 0.9156
+vn 0.2572 -0.0603 0.9645
+vn -0.2572 -0.0603 0.9645
+vn 0.0637 -0.0106 0.9979
+vn -0.0637 -0.0106 0.9979
+vn -0.3637 0.7039 0.6101
+vn 0.3637 0.7039 0.6101
+vn 0.6299 0.0355 0.7759
+vn -0.6299 0.0355 0.7759
+vn 0.4472 -0.2002 0.8717
+vn -0.4472 -0.2002 0.8717
+vn 0.5072 -0.2141 0.8348
+vn -0.5072 -0.2141 0.8348
+vn 0.5258 0.2619 0.8093
+vn -0.5258 0.2619 0.8093
+vn 0.2980 0.5802 0.7580
+vn -0.2980 0.5802 0.7580
+vn 0.0930 -0.9924 -0.0805
+vn -0.0930 -0.9924 -0.0805
+vn 0.5006 -0.8657 0.0080
+vn -0.5006 -0.8657 0.0080
+vn 0.9285 -0.2497 0.2748
+vn -0.9285 -0.2497 0.2748
+vn 0.8393 0.5424 -0.0378
+vn -0.8393 0.5424 -0.0378
+vn -0.2355 0.9367 -0.2589
+vn 0.2355 0.9367 -0.2589
+vn -0.4499 0.8838 -0.1285
+vn 0.4499 0.8838 -0.1285
+vn -0.5384 -0.0098 -0.8427
+vn 0.5384 -0.0098 -0.8427
+vn -0.1910 -0.0241 -0.9813
+vn 0.1910 -0.0241 -0.9813
+vn 0.4046 0.0266 -0.9141
+vn -0.4046 0.0266 -0.9141
+vn -0.7819 0.6231 0.0197
+vn 0.7819 0.6231 0.0197
+vn 0.5428 -0.2063 -0.8142
+vn -0.5428 -0.2063 -0.8142
+vn -0.2474 -0.9231 -0.2945
+vn 0.2474 -0.9231 -0.2945
+vt 0.843250 0.588863
+vt 0.833697 0.611927
+vt 0.794218 0.611927
+vt 0.815334 0.560948
+vt 0.866314 0.937441
+vt 0.866314 0.897962
+vt 0.889378 0.888409
+vt 0.917293 0.916324
+vt 0.746541 0.611927
+vt 0.781622 0.527235
+vt 0.866314 0.985118
+vt 0.951006 0.950037
+vt 0.815334 0.662907
+vt 0.781622 0.696619
+vt 0.781622 0.950037
+vt 0.815334 0.916325
+vt 0.843250 0.634991
+vt 0.843250 0.888409
+vt 0.866314 0.644544
+vt 0.866314 0.684023
+vt 0.794218 0.865345
+vt 0.833697 0.865345
+vt 0.866314 0.731700
+vt 0.746541 0.865345
+vt 0.917293 0.662907
+vt 0.951006 0.696619
+vt 0.781622 0.780653
+vt 0.815334 0.814366
+vt 0.889378 0.634991
+vt 0.843250 0.842281
+vt 0.898931 0.611927
+vt 0.938410 0.611927
+vt 0.866314 0.793249
+vt 0.866314 0.832728
+vt 0.986087 0.611927
+vt 0.866314 0.745572
+vt 0.917294 0.560947
+vt 0.951006 0.527235
+vt 0.951006 0.780653
+vt 0.917294 0.814365
+vt 0.889378 0.588863
+vt 0.889378 0.842281
+vt 0.866314 0.579310
+vt 0.866314 0.539831
+vt 0.938410 0.865345
+vt 0.898931 0.865345
+vt 0.866314 0.492154
+vt 0.986087 0.865345
+vt 0.866314 0.585461
+vt 0.847600 0.593213
+vt 0.892780 0.865345
+vt 0.885028 0.884059
+vt 0.885028 0.593213
+vt 0.885028 0.846631
+vt 0.892780 0.611927
+vt 0.866314 0.838879
+vt 0.885028 0.630641
+vt 0.847600 0.846631
+vt 0.866314 0.638393
+vt 0.839848 0.865345
+vt 0.847600 0.630641
+vt 0.847600 0.884059
+vt 0.839848 0.611927
+vt 0.866314 0.891811
+vt 0.866314 0.611927
+vt 0.866314 0.865345
+vt 0.520711 0.060503
+vt 0.517028 0.068490
+vt 0.500000 0.064527
+vt 0.500000 0.054411
+vt 0.482972 0.068490
+vt 0.479289 0.060503
+vt 0.530457 0.065062
+vt 0.522612 0.070678
+vt 0.477388 0.070678
+vt 0.469543 0.065062
+vt 0.532878 0.067822
+vt 0.526301 0.079422
+vt 0.473699 0.079422
+vt 0.467122 0.067822
+vt 0.544695 0.083023
+vt 0.529228 0.091098
+vt 0.470772 0.091098
+vt 0.455305 0.083023
+vt 0.565647 0.115495
+vt 0.537536 0.127779
+vt 0.462464 0.127779
+vt 0.434353 0.115495
+vt 0.586916 0.172042
+vt 0.626605 0.208159
+vt 0.600470 0.238288
+vt 0.535885 0.215568
+vt 0.399530 0.238288
+vt 0.373395 0.208159
+vt 0.413084 0.172042
+vt 0.464115 0.215568
+vt 0.650908 0.240294
+vt 0.636962 0.261221
+vt 0.363038 0.261221
+vt 0.349092 0.240294
+vt 0.689501 0.268805
+vt 0.655896 0.284413
+vt 0.344104 0.284413
+vt 0.310499 0.268805
+vt 0.684517 0.317795
+vt 0.659507 0.313152
+vt 0.340493 0.313152
+vt 0.315483 0.317795
+vt 0.671357 0.334694
+vt 0.649107 0.322437
+vt 0.350893 0.322437
+vt 0.328643 0.334694
+vt 0.636775 0.370732
+vt 0.613636 0.353108
+vt 0.386364 0.353108
+vt 0.363225 0.370732
+vt 0.602261 0.395588
+vt 0.594178 0.371277
+vt 0.405822 0.371277
+vt 0.397739 0.395588
+vt 0.581044 0.398468
+vt 0.577351 0.373341
+vt 0.422649 0.373341
+vt 0.418956 0.398468
+vt 0.532016 0.387365
+vt 0.539817 0.354339
+vt 0.460183 0.354339
+vt 0.467984 0.387365
+vt 0.500000 0.382860
+vt 0.500000 0.340417
+vt 0.559135 0.334326
+vt 0.579057 0.335646
+vt 0.420943 0.335646
+vt 0.440865 0.334326
+vt 0.592090 0.332885
+vt 0.407910 0.332885
+vt 0.606163 0.325516
+vt 0.393837 0.325516
+vt 0.628251 0.311636
+vt 0.371749 0.311636
+vt 0.635551 0.300507
+vt 0.364449 0.300507
+vt 0.631484 0.283889
+vt 0.368516 0.283889
+vt 0.611331 0.264411
+vt 0.388669 0.264411
+vt 0.592372 0.254897
+vt 0.407628 0.254897
+vt 0.559669 0.267717
+vt 0.440331 0.267717
+vt 0.550878 0.277832
+vt 0.500000 0.252445
+vt 0.449122 0.277832
+vt 0.544001 0.314523
+vt 0.455999 0.314523
+vt 0.500000 0.295691
+vt 0.543637 0.293371
+vt 0.456363 0.293371
+vt 0.506819 0.075382
+vt 0.500000 0.073497
+vt 0.493181 0.075382
+vt 0.511782 0.080633
+vt 0.488218 0.080633
+vt 0.512172 0.093457
+vt 0.487828 0.093457
+vt 0.514257 0.133422
+vt 0.513874 0.097191
+vt 0.486126 0.097191
+vt 0.485743 0.133422
+vt 0.520226 0.164615
+vt 0.500000 0.157864
+vt 0.500000 0.134352
+vt 0.479774 0.164615
+vt 0.500000 0.100063
+vt 0.500000 0.091346
+vt 0.530258 0.180239
+vt 0.521901 0.181560
+vt 0.517666 0.172353
+vt 0.482334 0.172353
+vt 0.478099 0.181560
+vt 0.469742 0.180239
+vt 0.523673 0.198817
+vt 0.518957 0.194728
+vt 0.481043 0.194728
+vt 0.476327 0.198817
+vt 0.500000 0.203867
+vt 0.508211 0.197295
+vt 0.491789 0.197295
+vt 0.500000 0.197278
+vt 0.500000 0.192167
+vt 0.500000 0.165170
+vt 0.513538 0.176949
+vt 0.500000 0.171718
+vt 0.486462 0.176949
+vt 0.500000 0.186182
+vt 0.508818 0.192407
+vt 0.491182 0.192407
+vt 0.515232 0.190709
+vt 0.484768 0.190709
+vt 0.516908 0.181873
+vt 0.483092 0.181873
+vt 0.544209 0.172814
+vt 0.455791 0.172814
+vt 0.541327 0.154899
+vt 0.458673 0.154899
+vt 0.578661 0.139176
+vt 0.421339 0.139176
+vt 0.583245 0.154155
+vt 0.416755 0.154155
+vt 0.500000 0.089977
+vt 0.509911 0.090724
+vt 0.490089 0.090724
+vt 0.509713 0.082236
+vt 0.490287 0.082236
+vt 0.505196 0.077083
+vt 0.494804 0.077083
+vt 0.500000 0.075473
+vt 0.503445 0.080840
+vt 0.500000 0.080320
+vt 0.496555 0.080840
+vt 0.506660 0.083289
+vt 0.493340 0.083289
+vt 0.506822 0.086791
+vt 0.493178 0.086791
+vt 0.500000 0.085637
+vt 0.562361 0.284338
+vt 0.559190 0.294882
+vt 0.437639 0.284338
+vt 0.440810 0.294882
+vt 0.563180 0.307151
+vt 0.436820 0.307151
+vt 0.570458 0.318697
+vt 0.429542 0.318697
+vt 0.569243 0.278245
+vt 0.430757 0.278245
+vt 0.589914 0.271772
+vt 0.410086 0.271772
+vt 0.602190 0.277180
+vt 0.397810 0.277180
+vt 0.617110 0.287175
+vt 0.382890 0.287175
+vt 0.618074 0.299197
+vt 0.381926 0.299197
+vt 0.615871 0.307438
+vt 0.384129 0.307438
+vt 0.598424 0.313725
+vt 0.401576 0.313725
+vt 0.590828 0.319797
+vt 0.409172 0.319797
+vt 0.582130 0.320991
+vt 0.417870 0.320991
+vt 0.578615 0.309394
+vt 0.583998 0.312892
+vt 0.421385 0.309394
+vt 0.416002 0.312892
+vt 0.589656 0.312336
+vt 0.410344 0.312336
+vt 0.596347 0.309249
+vt 0.403653 0.309249
+vt 0.606014 0.302517
+vt 0.393986 0.302517
+vt 0.607735 0.297520
+vt 0.392265 0.297520
+vt 0.606985 0.291216
+vt 0.393015 0.291216
+vt 0.598275 0.283083
+vt 0.401725 0.283083
+vt 0.589866 0.280167
+vt 0.410134 0.280167
+vt 0.576352 0.284624
+vt 0.423648 0.284624
+vt 0.572831 0.289915
+vt 0.427169 0.289915
+vt 0.573307 0.303342
+vt 0.426693 0.303342
+vt 0.572447 0.295716
+vt 0.427553 0.295716
+vt 0.529647 0.428636
+vt 0.500000 0.428636
+vt 0.470353 0.428636
+vt 0.584616 0.428636
+vt 0.415384 0.428636
+vt 0.622301 0.417889
+vt 0.377699 0.417889
+vt 0.666214 0.388954
+vt 0.333786 0.388954
+vt 0.701481 0.344467
+vt 0.298519 0.344467
+vt 0.712163 0.315527
+vt 0.287837 0.315527
+vt 0.704305 0.260225
+vt 0.295695 0.260225
+vt 0.672947 0.224880
+vt 0.327053 0.224880
+vt 0.640497 0.198841
+vt 0.359503 0.198841
+vt 0.603553 0.023452
+vt 0.625128 0.029461
+vt 0.635753 0.082198
+vt 0.598617 0.056841
+vt 0.364247 0.082198
+vt 0.374872 0.029461
+vt 0.396447 0.023452
+vt 0.401383 0.056841
+vt 0.546781 0.009442
+vt 0.556354 0.040906
+vt 0.453219 0.009442
+vt 0.443646 0.040906
+vt 0.500000 0.033223
+vt 0.527159 0.049514
+vt 0.472841 0.049514
+vt 0.537222 0.060794
+vt 0.462778 0.060794
+vt 0.558499 0.065137
+vt 0.441501 0.065137
+vt 0.588127 0.085177
+vt 0.411873 0.085177
+vt 0.607239 0.113940
+vt 0.392761 0.113940
+vt 0.612663 0.133675
+vt 0.605467 0.151503
+vt 0.394533 0.151503
+vt 0.387337 0.133675
+vt 0.600708 0.162985
+vt 0.399292 0.162985
+vt 0.922374 0.064228
+vt 0.859647 0.117994
+vt 0.823287 0.101559
+vt 0.861342 0.027230
+vt 0.176713 0.101559
+vt 0.140353 0.117994
+vt 0.077626 0.064228
+vt 0.138658 0.027230
+vt 0.765863 0.087001
+vt 0.774604 0.010837
+vt 0.234137 0.087001
+vt 0.225396 0.010837
+vt 0.661952 0.087561
+vt 0.647010 0.028716
+vt 0.338048 0.087561
+vt 0.352990 0.028716
+vt 0.743592 0.198572
+vt 0.766410 0.233065
+vt 0.256408 0.198572
+vt 0.233590 0.233065
+vt 0.987633 0.154243
+vt 0.889478 0.177602
+vt 0.110522 0.177602
+vt 0.012367 0.154243
+vt 0.899807 0.420886
+vt 0.844317 0.347370
+vt 0.881169 0.309153
+vt 0.958963 0.357385
+vt 0.118831 0.309153
+vt 0.155683 0.347370
+vt 0.100193 0.420886
+vt 0.041037 0.357385
+vt 0.903302 0.261367
+vt 0.999860 0.253531
+vt 0.096698 0.261367
+vt 0.000140 0.253531
+vt 0.724763 0.340212
+vt 0.744725 0.304629
+vt 0.766113 0.319494
+vt 0.255275 0.304629
+vt 0.275237 0.340212
+vt 0.233887 0.319494
+vt 0.793427 0.280495
+vt 0.826663 0.297810
+vt 0.206573 0.280495
+vt 0.173337 0.297810
+vt 0.823620 0.254612
+vt 0.855331 0.267956
+vt 0.176380 0.254612
+vt 0.144669 0.267956
+vt 0.835860 0.225225
+vt 0.868309 0.233525
+vt 0.164140 0.225225
+vt 0.131691 0.233525
+vt 0.813499 0.190705
+vt 0.851288 0.181145
+vt 0.148712 0.181145
+vt 0.186501 0.190705
+vt 0.772263 0.374010
+vt 0.227737 0.374010
+vt 0.744519 0.390057
+vt 0.255481 0.390057
+vt 0.824765 0.470987
+vt 0.175235 0.470987
+vt 0.792141 0.464714
+vt 0.742181 0.461543
+vt 0.726855 0.432574
+vt 0.273145 0.432574
+vt 0.257819 0.461543
+vt 0.207859 0.464714
+vt 0.795638 0.489991
+vt 0.204362 0.489991
+vt 0.786137 0.227788
+vt 0.213863 0.227788
+vt 0.802950 0.203415
+vt 0.197050 0.203415
+vt 0.769334 0.128771
+vt 0.691484 0.146686
+vt 0.230666 0.128771
+vt 0.308516 0.146686
+vt 0.780113 0.169386
+vt 0.219887 0.169386
+vt 0.803789 0.171181
+vt 0.830917 0.139518
+vt 0.169083 0.139518
+vt 0.196211 0.171181
+vt 0.574628 0.959188
+vt 0.464516 0.959188
+vt 0.477326 0.891852
+vt 0.545439 0.884582
+vt 0.272900 0.891852
+vt 0.285710 0.959188
+vt 0.175598 0.959188
+vt 0.204787 0.884582
+vt 0.605134 0.846978
+vt 0.715894 0.897069
+vt 0.145092 0.846978
+vt 0.034332 0.897069
+vt 0.605360 0.784410
+vt 0.683874 0.757011
+vt 0.144866 0.784410
+vt 0.066352 0.757011
+vt 0.574835 0.744212
+vt 0.617867 0.719156
+vt 0.175391 0.744212
+vt 0.132359 0.719156
+vt 0.534996 0.712528
+vt 0.568728 0.682132
+vt 0.215230 0.712528
+vt 0.181498 0.682132
+vt 0.483006 0.686791
+vt 0.508539 0.641024
+vt 0.267220 0.686791
+vt 0.241687 0.641024
+vt 0.513415 0.736977
+vt 0.478880 0.719593
+vt 0.271346 0.719593
+vt 0.236811 0.736977
+vt 0.541117 0.760754
+vt 0.209109 0.760754
+vt 0.557599 0.789442
+vt 0.192627 0.789442
+vt 0.553841 0.821849
+vt 0.196385 0.821849
+vt 0.524671 0.842380
+vt 0.225555 0.842380
+vt 0.485193 0.845321
+vt 0.265033 0.845321
+vt 0.786150 0.181127
+vt 0.770208 0.191225
+vt 0.213850 0.181127
+vt 0.229792 0.191225
+vt 0.408153 0.587507
+vt 0.409610 0.674022
+vt 0.342073 0.587507
+vt 0.340616 0.674022
+vt 0.400301 0.910556
+vt 0.414155 0.878318
+vt 0.336071 0.878318
+vt 0.349925 0.910556
+vt 0.443630 0.713731
+vt 0.306596 0.713731
+vt 0.429725 0.728622
+vt 0.392812 0.710774
+vt 0.357414 0.710774
+vt 0.320501 0.728622
+vt 0.422979 0.754609
+vt 0.432640 0.746527
+vt 0.327247 0.754609
+vt 0.317586 0.746527
+vt 0.427719 0.796173
+vt 0.391578 0.815187
+vt 0.358648 0.815187
+vt 0.322507 0.796173
+vt 0.448277 0.830976
+vt 0.301949 0.830976
+vt 0.797710 0.195652
+vt 0.802580 0.199758
+vt 0.197420 0.199758
+vt 0.202290 0.195652
+vt 0.789609 0.184671
+vt 0.210391 0.184671
+vt 0.466042 0.804091
+vt 0.490498 0.816075
+vt 0.284184 0.804091
+vt 0.259728 0.816075
+vt 0.448042 0.783921
+vt 0.302184 0.783921
+vt 0.438745 0.764960
+vt 0.311481 0.764960
+vt 0.445216 0.756351
+vt 0.305010 0.756351
+vt 0.449819 0.743458
+vt 0.300407 0.743458
+vt 0.456307 0.732687
+vt 0.293919 0.732687
+vt 0.475471 0.740871
+vt 0.274755 0.740871
+vt 0.514211 0.817769
+vt 0.236015 0.817769
+vt 0.527961 0.808598
+vt 0.222265 0.808598
+vt 0.530079 0.790754
+vt 0.220147 0.790754
+vt 0.518620 0.772385
+vt 0.231606 0.772385
+vt 0.498668 0.755033
+vt 0.251558 0.755033
+vt 0.468029 0.756171
+vt 0.459660 0.770459
+vt 0.282197 0.756171
+vt 0.290566 0.770459
+vt 0.487167 0.769899
+vt 0.477354 0.785393
+vt 0.263059 0.769899
+vt 0.272872 0.785393
+vt 0.504617 0.782999
+vt 0.496545 0.797122
+vt 0.245609 0.782999
+vt 0.253681 0.797122
+vt 0.516137 0.792591
+vt 0.513713 0.804019
+vt 0.234089 0.792591
+vt 0.236513 0.804019
+vt 0.574269 0.607668
+vt 0.613510 0.655816
+vt 0.175957 0.607668
+vt 0.136716 0.655816
+vt 0.644031 0.693270
+vt 0.106195 0.693270
+vt 0.668163 0.712097
+vt 0.082063 0.712097
+vt 0.727131 0.698475
+vt 0.676584 0.686010
+vt 0.023095 0.698475
+vt 0.073642 0.686010
+vt 0.729804 0.624370
+vt 0.667013 0.640198
+vt 0.020422 0.624370
+vt 0.083213 0.640198
+vt 0.721292 0.572934
+vt 0.648152 0.594596
+vt 0.028934 0.572934
+vt 0.102074 0.594596
+vt 0.630034 0.557583
+vt 0.717709 0.538143
+vt 0.032517 0.538143
+vt 0.120192 0.557583
+vt 0.526877 0.539235
+vt 0.223349 0.539235
+s 0
+f 47/1/1 1/2/1 3/3/1 45/4/1
+f 4/5/2 2/6/2 48/7/2 46/8/2
+f 45/4/3 3/3/3 5/9/3 43/10/3
+f 6/11/4 4/5/4 46/8/4 44/12/4
+f 3/3/5 9/13/5 7/14/5 5/9/5
+f 8/15/6 10/16/6 4/5/6 6/11/6
+f 1/2/7 11/17/7 9/13/7 3/3/7
+f 10/16/8 12/18/8 2/6/8 4/5/8
+f 11/17/9 13/19/9 15/20/9 9/13/9
+f 16/21/10 14/22/10 12/18/10 10/16/10
+f 9/13/11 15/20/11 17/23/11 7/14/11
+f 18/24/12 16/21/12 10/16/12 8/15/12
+f 15/20/13 21/25/13 19/26/13 17/23/13
+f 20/27/14 22/28/14 16/21/14 18/24/14
+f 13/19/15 23/29/15 21/25/15 15/20/15
+f 22/28/16 24/30/16 14/22/16 16/21/16
+f 23/29/17 25/31/17 27/32/17 21/25/17
+f 28/33/18 26/34/18 24/30/18 22/28/18
+f 21/25/19 27/32/19 29/35/19 19/26/19
+f 30/36/20 28/33/20 22/28/20 20/27/20
+f 27/32/21 33/37/21 31/38/21 29/35/21
+f 32/39/22 34/40/22 28/33/22 30/36/22
+f 25/31/23 35/41/23 33/37/23 27/32/23
+f 34/40/24 36/42/24 26/34/24 28/33/24
+f 35/41/25 37/43/25 39/44/25 33/37/25
+f 40/45/26 38/46/26 36/42/26 34/40/26
+f 33/37/27 39/44/27 41/47/27 31/38/27
+f 42/48/28 40/45/28 34/40/28 32/39/28
+f 39/44/29 45/4/29 43/10/29 41/47/29
+f 44/12/30 46/8/30 40/45/30 42/48/30
+f 37/43/31 47/1/31 45/4/31 39/44/31
+f 46/8/32 48/7/32 38/46/32 40/45/32
+f 47/1/33 37/43/33 51/49/33 49/50/33
+f 52/51/34 38/46/34 48/7/34 50/52/34
+f 37/43/35 35/41/35 53/53/35 51/49/35
+f 54/54/36 36/42/36 38/46/36 52/51/36
+f 35/41/37 25/31/37 55/55/37 53/53/37
+f 56/56/38 26/34/38 36/42/38 54/54/38
+f 25/31/39 23/29/39 57/57/39 55/55/39
+f 58/58/40 24/30/40 26/34/40 56/56/40
+f 23/29/41 13/19/41 59/59/41 57/57/41
+f 60/60/42 14/22/42 24/30/42 58/58/42
+f 13/19/43 11/17/43 63/61/43 59/59/43
+f 64/62/44 12/18/44 14/22/44 60/60/44
+f 11/17/45 1/2/45 65/63/45 63/61/45
+f 66/64/46 2/6/46 12/18/46 64/62/46
+f 1/2/47 47/1/47 49/50/47 65/63/47
+f 50/52/48 48/7/48 2/6/48 66/64/48
+f 61/65/49 65/63/49 49/50/49
+f 50/52/50 66/64/50 62/66/50
+f 63/61/51 65/63/51 61/65/51
+f 62/66/52 66/64/52 64/62/52
+f 61/65/53 59/59/53 63/61/53
+f 64/62/54 60/60/54 62/66/54
+f 61/65/55 57/57/55 59/59/55
+f 60/60/56 58/58/56 62/66/56
+f 61/65/57 55/55/57 57/57/57
+f 58/58/58 56/56/58 62/66/58
+f 61/65/59 53/53/59 55/55/59
+f 56/56/60 54/54/60 62/66/60
+f 61/65/61 51/49/61 53/53/61
+f 54/54/62 52/51/62 62/66/62
+f 61/65/63 49/50/63 51/49/63
+f 52/51/64 50/52/64 62/66/64
+f 89/67/65 174/68/65 176/69/65 91/70/65
+f 176/69/66 175/71/66 90/72/66 91/70/66
+f 87/73/67 172/74/67 174/68/67 89/67/67
+f 175/71/68 173/75/68 88/76/68 90/72/68
+f 85/77/69 170/78/69 172/74/69 87/73/69
+f 173/75/70 171/79/70 86/80/70 88/76/70
+f 83/81/71 168/82/71 170/78/71 85/77/71
+f 171/79/72 169/83/72 84/84/72 86/80/72
+f 81/85/73 166/86/73 168/82/73 83/81/73
+f 169/83/74 167/87/74 82/88/74 84/84/74
+f 79/89/75 92/90/75 146/91/75 164/92/75
+f 147/93/76 93/94/76 80/95/76 165/96/76
+f 92/90/77 94/97/77 148/98/77 146/91/77
+f 149/99/78 95/100/78 93/94/78 147/93/78
+f 94/97/79 96/101/79 150/102/79 148/98/79
+f 151/103/80 97/104/80 95/100/80 149/99/80
+f 96/101/81 98/105/81 152/106/81 150/102/81
+f 153/107/82 99/108/82 97/104/82 151/103/82
+f 98/105/83 100/109/83 154/110/83 152/106/83
+f 155/111/84 101/112/84 99/108/84 153/107/84
+f 100/109/85 102/113/85 156/114/85 154/110/85
+f 157/115/86 103/116/86 101/112/86 155/111/86
+f 102/113/87 104/117/87 158/118/87 156/114/87
+f 159/119/88 105/120/88 103/116/88 157/115/88
+f 104/117/89 106/121/89 160/122/89 158/118/89
+f 161/123/90 107/124/90 105/120/90 159/119/90
+f 106/121/91 108/125/91 162/126/91 160/122/91
+f 163/127/92 109/128/92 107/124/92 161/123/92
+f 108/125/93 67/129/93 68/130/93 162/126/93
+f 68/130/94 67/129/94 109/128/94 163/127/94
+f 110/131/95 128/132/95 160/122/95 162/126/95
+f 161/123/96 129/133/96 111/134/96 163/127/96
+f 128/132/97 179/135/97 158/118/97 160/122/97
+f 159/119/98 180/136/98 129/133/98 161/123/98
+f 126/137/99 156/114/99 158/118/99 179/135/99
+f 159/119/100 157/115/100 127/138/100 180/136/100
+f 124/139/101 154/110/101 156/114/101 126/137/101
+f 157/115/102 155/111/102 125/140/102 127/138/102
+f 122/141/103 152/106/103 154/110/103 124/139/103
+f 155/111/104 153/107/104 123/142/104 125/140/104
+f 120/143/105 150/102/105 152/106/105 122/141/105
+f 153/107/106 151/103/106 121/144/106 123/142/106
+f 118/145/107 148/98/107 150/102/107 120/143/107
+f 151/103/108 149/99/108 119/146/108 121/144/108
+f 116/147/109 146/91/109 148/98/109 118/145/109
+f 149/99/110 147/93/110 117/148/110 119/146/110
+f 114/149/111 164/92/111 146/91/111 116/147/111
+f 147/93/112 165/96/112 115/150/112 117/148/112
+f 114/149/113 181/151/113 177/152/113 164/92/113
+f 177/152/114 182/153/114 115/150/114 165/96/114
+f 110/131/115 162/126/115 68/130/115 112/154/115
+f 68/130/116 163/127/116 111/134/116 113/155/116
+f 112/154/117 68/130/117 178/156/117 183/157/117
+f 178/156/118 68/130/118 113/155/118 184/158/118
+f 177/152/119 181/151/119 183/157/119 178/156/119
+f 184/158/120 182/153/120 177/152/120 178/156/120
+f 135/159/121 137/160/121 176/69/121 174/68/121
+f 176/69/122 137/160/122 136/161/122 175/71/122
+f 133/162/123 135/159/123 174/68/123 172/74/123
+f 175/71/124 136/161/124 134/163/124 173/75/124
+f 131/164/125 133/162/125 172/74/125 170/78/125
+f 173/75/126 134/163/126 132/165/126 171/79/126
+f 166/86/127 187/166/127 185/167/127 168/82/127
+f 186/168/128 188/169/128 167/87/128 169/83/128
+f 131/164/129 170/78/129 168/82/129 185/167/129
+f 169/83/130 171/79/130 132/165/130 186/168/130
+f 144/170/131 190/171/131 189/172/131 187/166/131
+f 189/172/132 190/171/132 145/173/132 188/169/132
+f 185/167/133 187/166/133 189/172/133 69/174/133
+f 189/172/134 188/169/134 186/168/134 69/174/134
+f 130/175/135 131/164/135 185/167/135 69/174/135
+f 186/168/135 132/165/135 130/175/135 69/174/135
+f 142/176/136 193/177/136 191/178/136 144/170/136
+f 192/179/137 194/180/137 143/181/137 145/173/137
+f 140/182/138 195/183/138 193/177/138 142/176/138
+f 194/180/139 196/184/139 141/185/139 143/181/139
+f 139/186/140 197/187/140 195/183/140 140/182/140
+f 196/184/141 198/188/141 139/186/141 141/185/141
+f 138/189/142 71/190/142 197/187/142 139/186/142
+f 198/188/143 71/190/143 138/189/143 139/186/143
+f 190/171/144 144/170/144 191/178/144 70/191/144
+f 192/179/145 145/173/145 190/171/145 70/191/145
+f 70/191/146 191/178/146 206/192/146 208/193/146
+f 207/194/147 192/179/147 70/191/147 208/193/147
+f 71/190/148 199/195/148 200/196/148 197/187/148
+f 201/197/149 199/195/149 71/190/149 198/188/149
+f 197/187/150 200/196/150 202/198/150 195/183/150
+f 203/199/151 201/197/151 198/188/151 196/184/151
+f 195/183/152 202/198/152 204/200/152 193/177/152
+f 205/201/153 203/199/153 196/184/153 194/180/153
+f 193/177/154 204/200/154 206/192/154 191/178/154
+f 207/194/155 205/201/155 194/180/155 192/179/155
+f 199/195/156 204/200/156 202/198/156 200/196/156
+f 203/199/157 205/201/157 199/195/157 201/197/157
+f 199/195/158 208/193/158 206/192/158 204/200/158
+f 207/194/159 208/193/159 199/195/159 205/201/159
+f 139/186/160 140/182/160 164/92/160 177/152/160
+f 165/96/161 141/185/161 139/186/161 177/152/161
+f 140/182/162 142/176/162 211/202/162 164/92/162
+f 212/203/163 143/181/163 141/185/163 165/96/163
+f 142/176/164 144/170/164 213/204/164 211/202/164
+f 214/205/165 145/173/165 143/181/165 212/203/165
+f 144/170/166 187/166/166 166/86/166 213/204/166
+f 167/87/167 188/169/167 145/173/167 214/205/167
+f 81/85/168 209/206/168 213/204/168 166/86/168
+f 214/205/169 210/207/169 82/88/169 167/87/169
+f 209/206/170 215/208/170 211/202/170 213/204/170
+f 212/203/171 216/209/171 210/207/171 214/205/171
+f 79/89/172 164/92/172 211/202/172 215/208/172
+f 212/203/173 165/96/173 80/95/173 216/209/173
+f 131/164/174 130/175/174 72/210/174 222/211/174
+f 72/210/175 130/175/175 132/165/175 223/212/175
+f 133/162/176 131/164/176 222/211/176 220/213/176
+f 223/212/177 132/165/177 134/163/177 221/214/177
+f 135/159/178 133/162/178 220/213/178 218/215/178
+f 221/214/179 134/163/179 136/161/179 219/216/179
+f 137/160/180 135/159/180 218/215/180 217/217/180
+f 219/216/181 136/161/181 137/160/181 217/217/181
+f 217/217/182 218/215/182 229/218/182 231/219/182
+f 230/220/183 219/216/183 217/217/183 231/219/183
+f 218/215/184 220/213/184 227/221/184 229/218/184
+f 228/222/185 221/214/185 219/216/185 230/220/185
+f 220/213/186 222/211/186 225/223/186 227/221/186
+f 226/224/187 223/212/187 221/214/187 228/222/187
+f 222/211/188 72/210/188 224/225/188 225/223/188
+f 224/225/189 72/210/189 223/212/189 226/224/189
+f 224/225/190 231/219/190 229/218/190 225/223/190
+f 230/220/191 231/219/191 224/225/191 226/224/191
+f 225/223/192 229/218/192 227/221/192
+f 228/222/193 230/220/193 226/224/193
+f 183/157/194 181/151/194 234/226/194 232/227/194
+f 235/228/195 182/153/195 184/158/195 233/229/195
+f 112/154/196 183/157/196 232/227/196 254/230/196
+f 233/229/197 184/158/197 113/155/197 255/231/197
+f 110/131/198 112/154/198 254/230/198 256/232/198
+f 255/231/199 113/155/199 111/134/199 257/233/199
+f 181/151/200 114/149/200 252/234/200 234/226/200
+f 253/235/201 115/150/201 182/153/201 235/228/201
+f 114/149/202 116/147/202 250/236/202 252/234/202
+f 251/237/203 117/148/203 115/150/203 253/235/203
+f 116/147/204 118/145/204 248/238/204 250/236/204
+f 249/239/205 119/146/205 117/148/205 251/237/205
+f 118/145/206 120/143/206 246/240/206 248/238/206
+f 247/241/207 121/144/207 119/146/207 249/239/207
+f 120/143/208 122/141/208 244/242/208 246/240/208
+f 245/243/209 123/142/209 121/144/209 247/241/209
+f 122/141/210 124/139/210 242/244/210 244/242/210
+f 243/245/211 125/140/211 123/142/211 245/243/211
+f 124/139/212 126/137/212 240/246/212 242/244/212
+f 241/247/213 127/138/213 125/140/213 243/245/213
+f 126/137/214 179/135/214 236/248/214 240/246/214
+f 237/249/215 180/136/215 127/138/215 241/247/215
+f 179/135/216 128/132/216 238/250/216 236/248/216
+f 239/251/217 129/133/217 180/136/217 237/249/217
+f 128/132/218 110/131/218 256/232/218 238/250/218
+f 257/233/219 111/134/219 129/133/219 239/251/219
+f 238/250/220 256/232/220 258/252/220 276/253/220
+f 259/254/221 257/233/221 239/251/221 277/255/221
+f 236/248/222 238/250/222 276/253/222 278/256/222
+f 277/255/223 239/251/223 237/249/223 279/257/223
+f 240/246/224 236/248/224 278/256/224 274/258/224
+f 279/257/225 237/249/225 241/247/225 275/259/225
+f 242/244/226 240/246/226 274/258/226 272/260/226
+f 275/259/227 241/247/227 243/245/227 273/261/227
+f 244/242/228 242/244/228 272/260/228 270/262/228
+f 273/261/229 243/245/229 245/243/229 271/263/229
+f 246/240/230 244/242/230 270/262/230 268/264/230
+f 271/263/231 245/243/231 247/241/231 269/265/231
+f 248/238/232 246/240/232 268/264/232 266/266/232
+f 269/265/233 247/241/233 249/239/233 267/267/233
+f 250/236/234 248/238/234 266/266/234 264/268/234
+f 267/267/235 249/239/235 251/237/235 265/269/235
+f 252/234/236 250/236/236 264/268/236 262/270/236
+f 265/269/237 251/237/237 253/235/237 263/271/237
+f 234/226/238 252/234/238 262/270/238 280/272/238
+f 263/271/239 253/235/239 235/228/239 281/273/239
+f 256/232/240 254/230/240 260/274/240 258/252/240
+f 261/275/241 255/231/241 257/233/241 259/254/241
+f 254/230/242 232/227/242 282/276/242 260/274/242
+f 283/277/243 233/229/243 255/231/243 261/275/243
+f 232/227/244 234/226/244 280/272/244 282/276/244
+f 281/273/245 235/228/245 233/229/245 283/277/245
+f 67/129/246 108/125/246 284/278/246 73/279/246
+f 285/280/247 109/128/247 67/129/247 73/279/247
+f 108/125/248 106/121/248 286/281/248 284/278/248
+f 287/282/249 107/124/249 109/128/249 285/280/249
+f 106/121/250 104/117/250 288/283/250 286/281/250
+f 289/284/251 105/120/251 107/124/251 287/282/251
+f 104/117/252 102/113/252 290/285/252 288/283/252
+f 291/286/253 103/116/253 105/120/253 289/284/253
+f 102/113/254 100/109/254 292/287/254 290/285/254
+f 293/288/255 101/112/255 103/116/255 291/286/255
+f 100/109/256 98/105/256 294/289/256 292/287/256
+f 295/290/257 99/108/257 101/112/257 293/288/257
+f 98/105/258 96/101/258 296/291/258 294/289/258
+f 297/292/259 97/104/259 99/108/259 295/290/259
+f 96/101/260 94/97/260 298/293/260 296/291/260
+f 299/294/261 95/100/261 97/104/261 297/292/261
+f 94/97/262 92/90/262 300/295/262 298/293/262
+f 301/296/263 93/94/263 95/100/263 299/294/263
+f 308/297/264 309/298/264 328/299/264 338/300/264
+f 329/301/265 309/302/265 308/303/265 339/304/265
+f 307/305/266 308/297/266 338/300/266 336/306/266
+f 339/304/267 308/303/267 307/307/267 337/308/267
+f 306/309/268 307/305/268 336/306/268 340/310/268
+f 337/308/269 307/307/269 306/309/269 341/311/269
+f 89/67/270 91/70/270 306/309/270 340/310/270
+f 306/309/271 91/70/271 90/72/271 341/311/271
+f 87/73/272 89/67/272 340/310/272 334/312/272
+f 341/311/273 90/72/273 88/76/273 335/313/273
+f 85/77/274 87/73/274 334/312/274 330/314/274
+f 335/313/275 88/76/275 86/80/275 331/315/275
+f 83/81/276 85/77/276 330/314/276 332/316/276
+f 331/315/277 86/80/277 84/84/277 333/317/277
+f 330/314/278 336/306/278 338/300/278 332/316/278
+f 339/304/279 337/308/279 331/315/279 333/317/279
+f 330/314/280 334/312/280 340/310/280 336/306/280
+f 341/311/281 335/313/281 331/315/281 337/308/281
+f 326/318/282 332/316/282 338/300/282 328/299/282
+f 339/304/283 333/317/283 327/319/283 329/301/283
+f 81/85/284 83/81/284 332/316/284 326/318/284
+f 333/317/285 84/84/285 82/88/285 327/319/285
+f 209/206/286 342/320/286 344/321/286 215/208/286
+f 345/322/287 343/323/287 210/207/287 216/209/287
+f 81/85/288 326/318/288 342/320/288 209/206/288
+f 343/323/289 327/319/289 82/88/289 210/207/289
+f 79/89/290 215/208/290 344/321/290 346/324/290
+f 345/322/291 216/209/291 80/95/291 347/325/291
+f 79/89/292 346/324/292 300/295/292 92/90/292
+f 301/296/293 347/325/293 80/95/293 93/94/293
+f 77/326/294 324/327/294 352/328/294 304/329/294
+f 353/330/295 325/331/295 77/332/295 304/333/295
+f 304/329/296 352/328/296 350/334/296 78/335/296
+f 351/336/297 353/330/297 304/333/297 78/337/297
+f 78/335/298 350/334/298 348/338/298 305/339/298
+f 349/340/299 351/336/299 78/337/299 305/341/299
+f 305/339/300 348/338/300 328/299/300 309/298/300
+f 329/301/301 349/340/301 305/341/301 309/302/301
+f 326/318/302 328/299/302 348/338/302 342/320/302
+f 349/340/303 329/301/303 327/319/303 343/323/303
+f 296/291/304 298/293/304 318/342/304 310/343/304
+f 319/344/305 299/294/305 297/292/305 311/345/305
+f 76/346/306 316/347/306 324/327/306 77/326/306
+f 325/331/307 317/348/307 76/349/307 77/332/307
+f 302/350/308 358/351/308 356/352/308 303/353/308
+f 357/354/309 359/355/309 302/356/309 303/357/309
+f 303/353/310 356/352/310 354/358/310 75/359/310
+f 355/360/311 357/354/311 303/357/311 75/361/311
+f 75/359/312 354/358/312 316/347/312 76/346/312
+f 317/348/313 355/360/313 75/361/313 76/349/313
+f 292/362/314 294/289/314 362/363/314 364/364/314
+f 363/365/315 295/290/315 293/366/315 365/367/315
+f 364/364/316 362/363/316 368/368/316 366/369/316
+f 369/370/317 363/365/317 365/367/317 367/371/317
+f 366/369/318 368/368/318 370/372/318 372/373/318
+f 371/374/319 369/370/319 367/371/319 373/375/319
+f 372/373/320 370/372/320 376/376/320 374/377/320
+f 377/378/321 371/374/321 373/375/321 375/379/321
+f 314/380/322 378/381/322 374/377/322 376/376/322
+f 375/379/323 379/382/323 315/383/323 377/378/323
+f 316/347/324 354/358/324 374/377/324 378/381/324
+f 375/379/325 355/360/325 317/348/325 379/382/325
+f 354/358/326 356/352/326 372/373/326 374/377/326
+f 373/375/327 357/354/327 355/360/327 375/379/327
+f 356/352/328 358/351/328 366/369/328 372/373/328
+f 367/371/329 359/355/329 357/354/329 373/375/329
+f 358/351/330 360/384/330 364/364/330 366/369/330
+f 365/367/331 361/385/331 359/355/331 367/371/331
+f 290/386/332 292/362/332 364/364/332 360/384/332
+f 365/367/333 293/366/333 291/387/333 361/385/333
+f 74/388/334 360/384/334 358/351/334 302/350/334
+f 359/355/335 361/385/335 74/389/335 302/356/335
+f 284/390/336 286/391/336 288/392/336 290/386/336
+f 289/393/337 287/394/337 285/395/337 291/387/337
+f 284/390/338 290/386/338 360/384/338 74/388/338
+f 361/385/339 291/387/339 285/395/339 74/389/339
+f 73/396/340 284/390/340 74/388/340
+f 74/389/341 285/395/341 73/397/341
+f 294/289/342 296/291/342 310/343/342 362/363/342
+f 311/345/343 297/292/343 295/290/343 363/365/343
+f 310/343/344 312/398/344 368/368/344 362/363/344
+f 369/370/345 313/399/345 311/345/345 363/365/345
+f 312/398/346 382/400/346 370/372/346 368/368/346
+f 371/374/347 383/401/347 313/399/347 369/370/347
+f 314/380/348 376/376/348 370/372/348 382/400/348
+f 371/374/349 377/378/349 315/383/349 383/401/349
+f 348/338/350 350/334/350 386/402/350 384/403/350
+f 387/404/351 351/336/351 349/340/351 385/405/351
+f 318/342/352 384/403/352 386/402/352 320/406/352
+f 387/404/353 385/405/353 319/344/353 321/407/353
+f 298/293/354 300/295/354 384/403/354 318/342/354
+f 385/405/355 301/296/355 299/294/355 319/344/355
+f 300/295/356 344/321/356 342/320/356 384/403/356
+f 343/323/357 345/322/357 301/296/357 385/405/357
+f 342/320/358 348/338/358 384/403/358
+f 385/405/359 349/340/359 343/323/359
+f 300/295/360 346/324/360 344/321/360
+f 345/322/361 347/325/361 301/296/361
+f 314/380/362 322/408/362 380/409/362 378/381/362
+f 381/410/363 323/411/363 315/383/363 379/382/363
+f 316/347/364 378/381/364 380/409/364 324/327/364
+f 381/410/365 379/382/365 317/348/365 325/331/365
+f 320/406/366 386/402/366 380/409/366 322/408/366
+f 381/410/367 387/404/367 321/407/367 323/411/367
+f 350/334/368 352/328/368 380/409/368 386/402/368
+f 381/410/369 353/330/369 351/336/369 387/404/369
+f 324/327/370 380/409/370 352/328/370
+f 353/330/371 381/410/371 325/331/371
+f 400/412/372 388/413/372 414/414/372 402/415/372
+f 415/416/373 389/417/373 401/418/373 403/419/373
+f 400/412/374 402/415/374 404/420/374 398/421/374
+f 405/422/375 403/419/375 401/418/375 399/423/375
+f 398/421/376 404/420/376 406/424/376 396/425/376
+f 407/426/377 405/422/377 399/423/377 397/427/377
+f 396/425/378 406/424/378 408/428/378 394/429/378
+f 409/430/379 407/426/379 397/427/379 395/431/379
+f 394/429/380 408/428/380 410/432/380 392/433/380
+f 411/434/381 409/430/381 395/431/381 393/435/381
+f 392/433/382 410/432/382 412/436/382 390/437/382
+f 413/438/383 411/434/383 393/435/383 391/439/383
+f 410/432/384 420/440/384 418/441/384 412/436/384
+f 419/442/385 421/443/385 411/434/385 413/438/385
+f 408/428/386 422/444/386 420/440/386 410/432/386
+f 421/443/387 423/445/387 409/430/387 411/434/387
+f 406/424/388 424/446/388 422/444/388 408/428/388
+f 423/445/389 425/447/389 407/426/389 409/430/389
+f 404/420/390 426/448/390 424/446/390 406/424/390
+f 425/447/391 427/449/391 405/422/391 407/426/391
+f 402/415/392 428/450/392 426/448/392 404/420/392
+f 427/449/393 429/451/393 403/419/393 405/422/393
+f 402/415/394 414/414/394 416/452/394 428/450/394
+f 417/453/395 415/416/395 403/419/395 429/451/395
+f 318/342/396 320/406/396 444/454/396 442/455/396
+f 445/456/397 321/407/397 319/344/397 443/457/397
+f 320/458/398 390/437/398 412/436/398 444/459/398
+f 413/438/399 391/439/399 321/460/399 445/461/399
+f 310/343/400 318/342/400 442/455/400 312/398/400
+f 443/457/401 319/344/401 311/345/401 313/399/401
+f 382/462/402 430/463/402 414/414/402 388/413/402
+f 415/416/403 431/464/403 383/465/403 389/417/403
+f 412/436/404 418/441/404 440/466/404 444/459/404
+f 441/467/405 419/442/405 413/438/405 445/461/405
+f 438/468/406 446/469/406 444/459/406 440/466/406
+f 445/461/407 447/470/407 439/471/407 441/467/407
+f 434/472/408 446/469/408 438/468/408 436/473/408
+f 439/471/409 447/470/409 435/474/409 437/475/409
+f 432/476/410 448/477/410 446/469/410 434/472/410
+f 447/470/411 449/478/411 433/479/411 435/474/411
+f 430/463/412 448/477/412 432/476/412 450/480/412
+f 433/479/413 449/478/413 431/464/413 451/481/413
+f 414/414/414 430/463/414 450/480/414 416/452/414
+f 451/481/415 431/464/415 415/416/415 417/453/415
+f 312/398/416 448/482/416 430/483/416 382/400/416
+f 431/484/417 449/485/417 313/399/417 383/401/417
+f 312/398/418 442/455/418 446/486/418 448/482/418
+f 447/487/419 443/457/419 313/399/419 449/485/419
+f 442/455/420 444/454/420 446/486/420
+f 447/487/421 445/456/421 443/457/421
+f 416/452/422 450/480/422 452/488/422 476/489/422
+f 453/490/423 451/481/423 417/453/423 477/491/423
+f 450/480/424 432/476/424 462/492/424 452/488/424
+f 463/493/425 433/479/425 451/481/425 453/490/425
+f 432/476/426 434/472/426 460/494/426 462/492/426
+f 461/495/427 435/474/427 433/479/427 463/493/427
+f 434/472/428 436/473/428 458/496/428 460/494/428
+f 459/497/429 437/475/429 435/474/429 461/495/429
+f 436/473/430 438/468/430 456/498/430 458/496/430
+f 457/499/431 439/471/431 437/475/431 459/497/431
+f 438/468/432 440/466/432 454/500/432 456/498/432
+f 455/501/433 441/467/433 439/471/433 457/499/433
+f 440/466/434 418/441/434 474/502/434 454/500/434
+f 475/503/435 419/442/435 441/467/435 455/501/435
+f 428/450/436 416/452/436 476/489/436 464/504/436
+f 477/491/437 417/453/437 429/451/437 465/505/437
+f 426/448/438 428/450/438 464/504/438 466/506/438
+f 465/505/439 429/451/439 427/449/439 467/507/439
+f 424/446/440 426/448/440 466/506/440 468/508/440
+f 467/507/441 427/449/441 425/447/441 469/509/441
+f 422/444/442 424/446/442 468/508/442 470/510/442
+f 469/509/443 425/447/443 423/445/443 471/511/443
+f 420/440/444 422/444/444 470/510/444 472/512/444
+f 471/511/445 423/445/445 421/443/445 473/513/445
+f 418/441/446 420/440/446 472/512/446 474/502/446
+f 473/513/447 421/443/447 419/442/447 475/503/447
+f 458/496/448 456/498/448 480/514/448 478/515/448
+f 481/516/449 457/499/449 459/497/449 479/517/449
+f 478/515/450 480/514/450 482/518/450 484/519/450
+f 483/520/451 481/516/451 479/517/451 485/521/451
+f 484/519/452 482/518/452 488/522/452 486/523/452
+f 489/524/453 483/520/453 485/521/453 487/525/453
+f 486/523/454 488/522/454 490/526/454 492/527/454
+f 491/528/455 489/524/455 487/525/455 493/529/455
+f 464/504/456 476/489/456 486/523/456 492/527/456
+f 487/525/457 477/491/457 465/505/457 493/529/457
+f 452/488/458 484/519/458 486/523/458 476/489/458
+f 487/525/459 485/521/459 453/490/459 477/491/459
+f 452/488/460 462/492/460 478/515/460 484/519/460
+f 479/517/461 463/493/461 453/490/461 485/521/461
+f 458/496/462 478/515/462 462/492/462 460/494/462
+f 463/493/463 479/517/463 459/497/463 461/495/463
+f 454/500/464 474/502/464 480/514/464 456/498/464
+f 481/516/465 475/503/465 455/501/465 457/499/465
+f 472/512/466 482/518/466 480/514/466 474/502/466
+f 481/516/467 483/520/467 473/513/467 475/503/467
+f 470/510/468 488/522/468 482/518/468 472/512/468
+f 483/520/469 489/524/469 471/511/469 473/513/469
+f 468/508/470 490/526/470 488/522/470 470/510/470
+f 489/524/471 491/528/471 469/509/471 471/511/471
+f 466/506/472 492/527/472 490/526/472 468/508/472
+f 491/528/473 493/529/473 467/507/473 469/509/473
+f 464/504/474 492/527/474 466/506/474
+f 467/507/475 493/529/475 465/505/475
+f 392/433/476 390/437/476 504/530/476 502/531/476
+f 505/532/477 391/439/477 393/435/477 503/533/477
+f 394/429/478 392/433/478 502/531/478 500/534/478
+f 503/533/479 393/435/479 395/431/479 501/535/479
+f 396/425/480 394/429/480 500/534/480 498/536/480
+f 501/535/481 395/431/481 397/427/481 499/537/481
+f 398/538/482 396/425/482 498/536/482 496/539/482
+f 499/537/483 397/427/483 399/540/483 497/541/483
+f 400/542/484 398/538/484 496/539/484 494/543/484
+f 497/541/485 399/540/485 401/544/485 495/545/485
+f 388/546/486 400/542/486 494/543/486 506/547/486
+f 495/545/487 401/544/487 389/548/487 507/549/487
+f 494/543/488 502/531/488 504/530/488 506/547/488
+f 505/532/489 503/533/489 495/545/489 507/549/489
+f 494/543/490 496/539/490 500/534/490 502/531/490
+f 501/535/491 497/541/491 495/545/491 503/533/491
+f 496/539/492 498/536/492 500/534/492
+f 501/535/493 499/537/493 497/541/493
+f 314/550/494 382/551/494 388/546/494 506/547/494
+f 389/548/495 383/552/495 315/553/495 507/549/495
+f 314/550/496 506/547/496 504/530/496 322/554/496
+f 505/532/497 507/549/497 315/553/497 323/555/497
+f 320/458/498 322/554/498 504/530/498 390/437/498
+f 505/532/499 323/555/499 321/460/499 391/439/499
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <regex.h>
+#include <string.h>
+#include <stdint.h>
+
+#define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
+
+void getObjInfo(char *file, float **verts, uint16_t *vertCount) {
+ // printf("nope\n");
+ // return;
+ FILE *fp = fopen(file, "r");
+
+ if (fp == NULL)
+ fprintf(stderr, "cannot locate file\n");
+
+ size_t len = 0;
+ char *line = NULL;
+
+ // just for better readability
+ // float *pverts = (*verts);
+
+ (*vertCount) = 0;
+ regex_t regex;
+ if (regcomp(®ex, "^v ", REG_NEWLINE)) {
+ fprintf(stderr, "could not comp regular expression\n");
+ exit(1);
+ }
+
+ while (getline(&line, &len, fp) != -1) {
+ regmatch_t pmatch[1];
+ if (regexec(®ex, line, ARRAY_SIZE(pmatch), pmatch, 0) == REG_NOMATCH)
+ continue;
+
+ char *str = line + pmatch[0].rm_eo;
+
+ char *token = strtok(str, " ");
+
+ for (int i = 0; i < 3; ++i) {
+ float a = strtof(token, NULL);
+ (*verts) = (float *)reallocarray((*verts), (*vertCount) + 1, sizeof(float));
+ (*verts)[(*vertCount)] = a;
+ (*vertCount) += 1;
+ token = strtok(NULL, " ");
+ }
+ }
+ regfree(®ex);
+ free(line);
+ fclose(fp);
+}
+
+int main(void)
+{
+ float *verts = NULL;
+ uint16_t size = 0;
+ getObjInfo("cube.obj", &verts, &size);
+ for (int i = 0; i < size; ++i) {
+ printf("%f\n", verts[i]);
+ }
+ if (verts) free(verts);
+}
--- /dev/null
+#ifndef objparser_h
+#define objparser_h
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <regex.h>
+#include <string.h>
+#include <stdint.h>
+
+#define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
+
+void getObjInfo(char *file, float **verts, uint16_t *vertCount) {
+ FILE *fp = fopen(file, "r");
+ if (!fp) return;
+
+ float *vPool = NULL;
+ uint32_t poolSize = 0;
+ char line[256];
+
+ *vertCount = 0;
+ *verts = NULL;
+
+ while (fgets(line, sizeof(line), fp)) {
+ if (strncmp(line, "v ", 2) == 0) {
+ vPool = (float *)realloc(vPool, (poolSize + 3) * sizeof(float));
+ sscanf(line, "v %f %f %f", &vPool[poolSize], &vPool[poolSize+1], &vPool[poolSize+2]);
+ poolSize += 3;
+ }
+ else if (strncmp(line, "f ", 2) == 0) {
+ int vIdx[4];
+ int count = 0;
+ char *ptr = line + 2;
+
+ // Parse up to 4 indices per face
+ while (ptr && count < 4) {
+ while (*ptr == ' ') ptr++; // skip spaces
+ if (*ptr == '\n' || *ptr == '\0') break;
+
+ vIdx[count++] = atoi(ptr);
+ ptr = strpbrk(ptr, " \n\0"); // find next space or end
+ }
+
+ // Triangle 1: 0, 1, 2
+ for (int i = 0; i < 3; i++) {
+ int actualIdx = (vIdx[i] - 1) * 3;
+ *verts = (float *)realloc(*verts, (*vertCount + 3) * sizeof(float));
+ memcpy(&(*verts)[*vertCount], &vPool[actualIdx], 3 * sizeof(float));
+ *vertCount += 3;
+ }
+
+ // If it's a Quad, add Triangle 2: 0, 2, 3
+ if (count == 4) {
+ int tri2Indices[3] = {0, 2, 3};
+ for (int i = 0; i < 3; i++) {
+ int actualIdx = (vIdx[tri2Indices[i]] - 1) * 3;
+ *verts = (float *)realloc(*verts, (*vertCount + 3) * sizeof(float));
+ memcpy(&(*verts)[*vertCount], &vPool[actualIdx], 3 * sizeof(float));
+ *vertCount += 3;
+ }
+ }
+ }
+ }
+ if (vPool) free(vPool);
+ fclose(fp);
+}
+
+#endif
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+# Blender 5.0.1 MTL File: 'None'
+# www.blender.org
+
+newmtl Material
+Ns 250.000000
+Ka 1.000000 1.000000 1.000000
+Kd 0.800000 0.800000 0.800000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 2
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib wired-shape.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v 1.319707 1.570849 0.481071
+v -0.680293 1.570849 0.481071
+v 1.319707 1.570849 -1.518929
+v -0.680293 1.570849 -1.518929
+vn -0.0000 0.6727 0.7400
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vn -0.0000 1.0000 -0.0000
+vn -0.8725 0.4886 -0.0000
+vn 0.8725 -0.4886 -0.0000
+vn -0.0000 -0.6727 -0.7400
+vt 0.625000 1.000000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+s 0
+usemtl Material
+f 7/1/1 3/2/1 9/2/1 10/1/1
+f 4/3/2 3/2/2 7/1/2 8/4/2
+f 8/5/3 7/6/3 5/7/3 6/8/3
+f 6/9/4 2/10/4 4/3/4 8/11/4
+f 2/10/5 1/12/5 3/2/5 4/3/5
+f 6/8/6 5/7/6 1/12/6 2/10/6
+f 11/12/7 12/13/7 10/14/7 9/2/7
+f 5/7/8 7/6/8 10/6/8 12/7/8
+f 3/2/9 1/12/9 11/12/9 9/2/9
+f 1/12/10 5/7/10 12/7/10 11/12/10
--- /dev/null
+# Blender 5.0.1 MTL File: 'None'
+# www.blender.org
+
+newmtl Material
+Ns 250.000000
+Ka 1.000000 1.000000 1.000000
+Kd 0.800000 0.800000 0.800000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 2
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib wired-shape2.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v 1.319707 1.570849 0.481071
+v -0.680293 1.570849 0.481071
+v 1.319707 1.570849 -1.518929
+v -0.680293 1.570849 -1.518929
+v -1.000000 -1.000000 1.327740
+v -1.000000 1.000000 1.327740
+v 1.000000 1.000000 1.327740
+v 1.000000 -1.000000 1.327740
+vn -0.0000 0.6727 0.7400
+vn -0.0000 -1.0000 -0.0000
+vn -1.0000 -0.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vn -0.0000 1.0000 -0.0000
+vn -0.8725 0.4886 -0.0000
+vn 0.8725 -0.4886 -0.0000
+vn -0.0000 -0.6727 -0.7400
+vn -0.0000 -0.0000 1.0000
+vt 0.625000 1.000000
+vt 0.625000 0.750000
+vt 0.125000 0.750000
+vt 0.375000 0.750000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.375000 1.000000
+s 0
+usemtl Material
+f 7/1/1 3/2/1 9/2/1 10/1/1
+f 8/3/2 4/4/2 16/4/2 13/3/2
+f 8/5/3 7/6/3 5/7/3 6/8/3
+f 6/9/2 2/10/2 4/4/2 8/3/2
+f 2/10/4 1/11/4 3/2/4 4/4/4
+f 6/8/5 5/7/5 1/11/5 2/10/5
+f 11/11/6 12/12/6 10/13/6 9/2/6
+f 5/7/7 7/6/7 10/6/7 12/7/7
+f 3/2/8 1/11/8 11/11/8 9/2/8
+f 1/11/9 5/7/9 12/7/9 11/11/9
+f 16/4/10 15/2/10 14/1/10 13/14/10
+f 3/2/6 7/1/6 14/1/6 15/2/6
+f 4/4/4 3/2/4 15/2/4 16/4/4
+f 7/6/3 8/5/3 13/5/3 14/6/3
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+#version 450
+
+layout(set = 0, binding = 0) uniform sampler2D tex;
+layout(location = 0) in vec2 uv;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = texture(tex, uv);
+}
+
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec2 inUV;
+
+layout(location = 0) out vec2 fragUV;
+
+layout(push_constant) uniform Push {
+ float angle;
+} pc;
+
+void main() {
+ float c = cos(pc.angle);
+ float s = sin(pc.angle);
+
+ vec3 p = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // simple perspective
+ float z = p.z + 2.5;
+
+ gl_Position = vec4(p.xy, z, z); // ← KEY FIX
+ fragUV = inUV;
+}
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = triangle.vert triangle.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_triangle
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+
+/* --- Vulkan handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkRenderPass renderPass;
+VkFramebuffer* framebuffers;
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+VkBuffer indexBuffer;
+VkDeviceMemory indexBufferMemory;
+
+GLFWwindow* window;
+// Vertex vertices[] = {
+// // front face
+// {{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// {{ 0.5f -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// // backface
+// {{-0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// {{ 0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+
+
+// // left face
+// {{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// {{-0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// // rigth face
+// {{ 0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// {{ 0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// // top face
+// {{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, 0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+
+// // bottom face
+// {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{ 0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f} },
+// {{-0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f} },
+
+
+// };
+// Vertex vertices[] = {
+// {{ 0.0f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f }},
+// {{ 0.5f, 0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f }},
+// {{-0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f }},
+// };
+// Vertex vertices[] = {
+// {{ 0.25f, 0.25f, 0.25f}, {1.0f, 0.0f, 0.0f}},
+// {{-0.25f, 0.25f, 0.25f}, {1.0f, 0.0f, 0.0f}},
+// {{-0.25f, -0.25f, 0.25f}, {1.0f, 0.0f, 0.0f}},
+ // {{ 0.25, -0.25, 0.25}, {1.0f, 0.0f, 0.0f}},
+ // {{ 0.25, 0.25, -0.25}, {1.0f, 0.0f, 0.0f}},
+ // {{-0.25, 0.25, -0.25}, {1.0f, 0.0f, 0.0f}},
+ // {{-0.25, -0.25, -0.25}, {1.0f, 0.0f, 0.0f}},
+ // {{ 0.25, -0.25, -0.25}, {1.0f, 0.0f, 0.0f}},
+
+// };
+
+/* --- Vertex + Index data (cube) --- */
+
+// #include "cube.c"
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+Vertex vertices[]={
+ {{-0.5f,-0.5f, 0.25f},{1,0,0}},
+ {{ 0.5f,-0.5f, 0.25f},{0,1,0}},
+ {{ 0.5f, 0.5f, 0.25f},{0,0,1}},
+ {{-0.5f, 0.5f, 0.25f},{1,1,0}},
+ {{-0.5f,-0.5f,-0.25f},{1,0,1}},
+ {{ 0.5f,-0.5f,-0.25f},{0,1,1}},
+ {{ 0.5f, 0.5f,-0.25f},{1,1,1}},
+ {{-0.5f, 0.5f,-0.25f},{0,0,0}}
+};
+uint16_t indices[]={
+ 0,1,2, 2,3,0,
+ 1,5,6, 6,2,1,
+ 5,4,7, 7,6,5,
+ 4,0,3, 3,7,4,
+ 3,2,6, 6,7,3,
+ 4,5,1, 1,0,4
+};
+uint32_t vertexCount=sizeof(vertices)/sizeof(vertices[0]);
+uint32_t indexCount=sizeof(indices)/sizeof(indices[0]);
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size){
+ FILE* f=fopen(path,"rb"); if(!f){fprintf(stderr,"Cannot open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END); *size=ftell(f); rewind(f);
+ char* data=malloc(*size); fread(data,1,*size,f); fclose(f); return data;
+}
+
+VkShaderModule load_shader(const char* path){
+ size_t size; char* code=read_file(path,&size);
+ VkShaderModuleCreateInfo info={.sType=VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,.codeSize=size,.pCode=(uint32_t*)code};
+ VkShaderModule module;
+ if(vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){fprintf(stderr,"Failed shader\n"); exit(1);}
+ free(code); return module;
+}
+
+/* --- Swapchain --- */
+void create_swapchain(){
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0; vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[16]; vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ swapchainFormat=formats[0].format; swapchainExtent=caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR sc={0};
+ sc.sType=VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ sc.surface=surface;
+ sc.minImageCount=caps.minImageCount+1;
+ sc.imageFormat=swapchainFormat;
+ sc.imageColorSpace=formats[0].colorSpace;
+ sc.imageExtent=swapchainExtent;
+ sc.imageArrayLayers=1;
+ sc.imageUsage=VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ sc.imageSharingMode=VK_SHARING_MODE_EXCLUSIVE;
+ sc.preTransform=caps.currentTransform;
+ sc.compositeAlpha=VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ sc.presentMode=VK_PRESENT_MODE_FIFO_KHR;
+ sc.clipped=VK_TRUE;
+
+ if(vkCreateSwapchainKHR(device,&sc,NULL,&swapchain)!=VK_SUCCESS){fprintf(stderr,"Swapchain fail\n"); exit(1);}
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages=malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+
+ swapchainImageViews=malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo iv={0};
+ iv.sType=VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ iv.image=swapchainImages[i];
+ iv.viewType=VK_IMAGE_VIEW_TYPE_2D;
+ iv.format=swapchainFormat;
+
+ iv.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ iv.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ iv.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ iv.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+
+ iv.subresourceRange.aspectMask=VK_IMAGE_ASPECT_COLOR_BIT;
+ iv.subresourceRange.baseMipLevel=0;
+ iv.subresourceRange.levelCount=1;
+ iv.subresourceRange.baseArrayLayer=0;
+ iv.subresourceRange.layerCount=1;
+ if(vkCreateImageView(device,&iv,NULL,&swapchainImageViews[i])!=VK_SUCCESS){fprintf(stderr,"ImageView fail\n"); exit(1);}
+ }
+}
+
+/* --- Render pass --- */
+void create_render_pass(){
+ VkAttachmentDescription att={0};
+ att.format=swapchainFormat;
+ att.samples=VK_SAMPLE_COUNT_1_BIT;
+ att.loadOp=VK_ATTACHMENT_LOAD_OP_CLEAR;
+ att.storeOp=VK_ATTACHMENT_STORE_OP_STORE;
+ att.stencilLoadOp=VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ att.stencilStoreOp=VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ att.initialLayout=VK_IMAGE_LAYOUT_UNDEFINED;
+ att.finalLayout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+ VkAttachmentReference colorRef={0,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
+
+ VkSubpassDescription subpass={0};
+ subpass.pipelineBindPoint=VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.colorAttachmentCount=1;
+ subpass.pColorAttachments=&colorRef;
+
+ VkRenderPassCreateInfo rp={0};
+ rp.sType=VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ rp.attachmentCount=1;
+ rp.pAttachments=&att;
+ rp.subpassCount=1;
+ rp.pSubpasses=&subpass;
+
+ if(vkCreateRenderPass(device,&rp,NULL,&renderPass)!=VK_SUCCESS){fprintf(stderr,"RenderPass fail\n"); exit(1);}
+}
+
+/* --- Framebuffers --- */
+void create_framebuffers(){
+ framebuffers=malloc(sizeof(VkFramebuffer)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkFramebufferCreateInfo fb={0};
+ fb.sType=VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ fb.renderPass=renderPass;
+ fb.attachmentCount=1;
+ fb.pAttachments=&swapchainImageViews[i];
+ fb.width=swapchainExtent.width;
+ fb.height=swapchainExtent.height;
+ fb.layers=1;
+ if(vkCreateFramebuffer(device,&fb,NULL,&framebuffers[i])!=VK_SUCCESS){fprintf(stderr,"Framebuffer fail\n"); exit(1);}
+ }
+}
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers(){
+ VkCommandPoolCreateInfo cp={0};
+ cp.sType=VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ cp.queueFamilyIndex=graphicsQueueFamily;
+ if(vkCreateCommandPool(device,&cp,NULL,&commandPool)!=VK_SUCCESS){fprintf(stderr,"CommandPool fail\n"); exit(1);}
+
+ commandBuffers=malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cb={0};
+ cb.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ cb.commandPool=commandPool;
+ cb.level=VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ cb.commandBufferCount=swapchainImageCount;
+ if(vkAllocateCommandBuffers(device,&cb,commandBuffers)!=VK_SUCCESS){fprintf(stderr,"CommandBuffer alloc fail\n"); exit(1);}
+}
+
+/* --- Vertex/Index buffer --- */
+void create_buffer(VkBuffer* buf,VkDeviceMemory* mem,void* data,VkDeviceSize size,VkBufferUsageFlags usage){
+ VkBufferCreateInfo bci={0};
+ bci.sType=VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bci.size=size;
+ bci.usage=usage;
+ bci.sharingMode=VK_SHARING_MODE_EXCLUSIVE;
+ vkCreateBuffer(device,&bci,NULL,buf);
+
+ VkMemoryRequirements memReq; vkGetBufferMemoryRequirements(device,*buf,&memReq);
+ VkPhysicalDeviceMemoryProperties memProps; vkGetPhysicalDeviceMemoryProperties(physical,&memProps);
+
+ VkMemoryAllocateInfo alloc={0};
+ alloc.sType=VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ alloc.allocationSize=memReq.size;
+ for(uint32_t i=0;i<memProps.memoryTypeCount;i++){
+ if((memReq.memoryTypeBits&(1<<i)) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)){
+ alloc.memoryTypeIndex=i; break;
+ }
+ }
+
+ vkAllocateMemory(device,&alloc,NULL,mem);
+ vkBindBufferMemory(device,*buf,*mem,0);
+
+ void* ptr; vkMapMemory(device,*mem,0,size,0,&ptr); memcpy(ptr,data,size); vkUnmapMemory(device,*mem);
+}
+
+void create_vertex_index_buffers(){
+ create_buffer(&vertexBuffer,&vertexBufferMemory,vertices,sizeof(vertices),VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
+ create_buffer(&indexBuffer,&indexBufferMemory,indices,sizeof(indices),VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("triangle.vert.spv");
+ VkShaderModule fragModule = load_shader("triangle.frag.spv");
+
+ VkPipelineShaderStageCreateInfo shaderStages[2] = {0};
+ shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ shaderStages[0].module = vertModule;
+ shaderStages[0].pName = "main";
+
+ shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ shaderStages[1].module = fragModule;
+ shaderStages[1].pName = "main";
+
+ VkVertexInputBindingDescription bindingDescription = {
+ .binding = 0,
+ .stride = sizeof(Vertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+
+ VkVertexInputAttributeDescription attributeDescriptions[2] = {
+ { .location = 0, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, pos) },
+ { .location = 1, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, color) }
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertexInputInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 1,
+ .pVertexBindingDescriptions = &bindingDescription,
+ .vertexAttributeDescriptionCount = 2,
+ .pVertexAttributeDescriptions = attributeDescriptions
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+
+ VkViewport viewport = {
+ .x = 0.0f,
+ .y = 0.0f,
+ .width = (float)swapchainExtent.width,
+ .height = (float)swapchainExtent.height,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f
+ };
+
+ VkRect2D scissor = { .offset = {0,0}, .extent = swapchainExtent };
+
+ VkPipelineViewportStateCreateInfo viewportState = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount = 1,
+ .pViewports = &viewport,
+ .scissorCount = 1,
+ .pScissors = &scissor
+ };
+
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable = VK_FALSE,
+ .rasterizerDiscardEnable = VK_FALSE,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .cullMode = VK_CULL_MODE_BACK_BIT,
+ .frontFace = VK_FRONT_FACE_CLOCKWISE,
+ .depthBiasEnable = VK_FALSE,
+ .lineWidth = 1.0f
+ };
+
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ .sampleShadingEnable = VK_FALSE
+ };
+
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+
+ VkPipelineColorBlendStateCreateInfo colorBlending = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .logicOpEnable = VK_FALSE,
+ .logicOp = VK_LOGIC_OP_COPY,
+ .attachmentCount = 1,
+ .pAttachments = &colorBlendAttachment,
+ .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}
+ };
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
+ };
+
+ if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create pipeline layout\n");
+ exit(1);
+ }
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = {0};
+ pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = shaderStages;
+ pipelineInfo.pVertexInputState = &vertexInputInfo;
+ pipelineInfo.pInputAssemblyState = &inputAssembly;
+ pipelineInfo.pViewportState = &viewportState;
+ pipelineInfo.pRasterizationState = &rasterizer;
+ pipelineInfo.pMultisampleState = &multisampling;
+ pipelineInfo.pColorBlendState = &colorBlending;
+ pipelineInfo.layout = pipelineLayout;
+ pipelineInfo.renderPass = renderPass;
+ pipelineInfo.subpass = 0;
+
+ if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &graphicsPipeline) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create graphics pipeline\n");
+ exit(1);
+ }
+
+ vkDestroyShaderModule(device, vertModule, NULL);
+ vkDestroyShaderModule(device, fragModule, NULL);
+}
+
+/* --- Record command buffers --- */
+void record_command_buffers(){
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkCommandBufferBeginInfo begin={0};
+ begin.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ vkBeginCommandBuffer(commandBuffers[i],&begin);
+
+ VkClearValue clr={{0.1f,0.1f,0.1f,1.0f}};
+ VkRenderPassBeginInfo rp={0};
+ rp.sType=VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ rp.renderPass=renderPass;
+ rp.framebuffer=framebuffers[i];
+ rp.renderArea.offset.x=0; rp.renderArea.offset.y=0;
+ rp.renderArea.extent=swapchainExtent;
+ rp.clearValueCount=1; rp.pClearValues=&clr;
+
+ vkCmdBeginRenderPass(commandBuffers[i],&rp,VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(commandBuffers[i],VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline);
+ VkDeviceSize offsets[]={0};
+ vkCmdBindVertexBuffers(commandBuffers[i],0,1,&vertexBuffer,offsets);
+ vkCmdBindIndexBuffer(commandBuffers[i],indexBuffer,0,VK_INDEX_TYPE_UINT16);
+ vkCmdDrawIndexed(commandBuffers[i],indexCount,1,0,0,0);
+ vkCmdEndRenderPass(commandBuffers[i]);
+ vkEndCommandBuffer(commandBuffers[i]);
+ }
+}
+
+/* --- Draw frame --- */
+void draw_frame(){
+ uint32_t img;
+ vkAcquireNextImageKHR(device,swapchain,UINT64_MAX,VK_NULL_HANDLE,VK_NULL_HANDLE,&img);
+
+ VkSubmitInfo submit={0};
+ submit.sType=VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submit.commandBufferCount=1;
+ submit.pCommandBuffers=&commandBuffers[img];
+
+ vkQueueSubmit(graphicsQueue,1,&submit,VK_NULL_HANDLE);
+ vkQueueWaitIdle(graphicsQueue);
+
+ VkPresentInfoKHR present={0};
+ present.sType=VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+ present.swapchainCount=1;
+ present.pSwapchains=&swapchain;
+ present.pImageIndices=&img;
+
+ vkQueuePresentKHR(graphicsQueue,&present);
+}
+
+/* --- Main --- */
+int main(void){
+ if(!glfwInit()){fprintf(stderr,"GLFW init fail\n"); return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window=glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Cube",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"Volk init fail\n"); return 1;}
+ uint32_t ext_count=0; const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ici,NULL,&instance);
+ volkLoadInstance(instance);
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){fprintf(stderr,"Surface fail\n"); return 1;}
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8]; vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ physical=devices[0];
+
+ uint32_t qcount=0; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ VkQueueFamilyProperties qprops[16]; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres){graphicsQueueFamily=i;found=1;break;}
+ }
+ if(!found){fprintf(stderr,"No suitable queue family\n"); return 1;}
+
+ const char* devExts[]={VK_KHR_SWAPCHAIN_EXTENSION_NAME};
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,.queueFamilyIndex=graphicsQueueFamily,.queueCount=1,.pQueuePriorities=&pri};
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,.queueCreateInfoCount=1,.pQueueCreateInfos=&qci,.enabledExtensionCount=1,.ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ create_render_pass();
+ create_framebuffers();
+ create_command_pool_buffers();
+ create_vertex_index_buffers();
+ create_graphics_pipeline();
+ record_command_buffers();
+
+ while(!glfwWindowShouldClose(window)){
+ glfwPollEvents();
+ draw_frame();
+ }
+
+ vkDeviceWaitIdle(device);
+
+ for(uint32_t i=0;i<swapchainImageCount;i++) vkDestroyFramebuffer(device,framebuffers[i],NULL);
+ free(framebuffers); free(commandBuffers); free(swapchainImageViews); free(swapchainImages);
+ vkDestroyPipeline(device,graphicsPipeline,NULL);
+ vkDestroyPipelineLayout(device,pipelineLayout,NULL);
+ vkDestroyRenderPass(device,renderPass,NULL);
+ vkDestroySwapchainKHR(device,swapchain,NULL);
+ vkDestroyBuffer(device,vertexBuffer,NULL); vkFreeMemory(device,vertexBufferMemory,NULL);
+ vkDestroyBuffer(device,indexBuffer,NULL); vkFreeMemory(device,indexBufferMemory,NULL);
+ vkDestroyDevice(device,NULL);
+ vkDestroySurfaceKHR(instance,surface,NULL);
+ vkDestroyInstance(instance,NULL);
+ glfwDestroyWindow(window); glfwTerminate();
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+#version 450
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(fragColor, 1.0);
+}
--- /dev/null
+#version 450
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+void main() {
+ gl_Position = vec4(inPos, 1.0);
+ fragColor = inColor;
+}
--- /dev/null
+#!/usr/bin/env bash
+HOSTNAME=nyquistdev.net
+DOCINFODIR=/usr/local/share/readme-to-html
--- /dev/null
+Yes — in **modern Vulkan (1.3+)**, the *“newer”* approach is to **stop using render passes entirely** and use **dynamic rendering** instead (`VK_KHR_dynamic_rendering`, promoted to core in Vulkan 1.3).
+
+That means:
+
+* ❌ No `VkRenderPass`
+* ❌ No `VkFramebuffer`
+* ✅ You specify attachments directly when you begin rendering
+
+Below I’ll:
+
+1. Explain *what changes conceptually*
+2. Show the **updated version** using dynamic rendering
+3. Explain what you **remove elsewhere** in your code
+
+---
+
+## 1. What changed in modern Vulkan
+
+### Old model (what you have now)
+
+```
+RenderPass
+ └─ Subpass
+ └─ Attachments
+```
+
+You had to:
+
+* Predefine attachment formats
+* Predefine load/store ops
+* Create framebuffers per swapchain image
+
+This was **very rigid**.
+
+---
+
+### New model: Dynamic Rendering
+
+With dynamic rendering:
+
+* Attachments are specified **at draw time**
+* No render pass object
+* No framebuffer object
+* Much simpler and more flexible
+
+This is now the **recommended approach** unless you need:
+
+* Subpasses with input attachments
+* Very complex tile-based optimizations
+
+---
+
+## 2. Updated version using dynamic rendering
+
+### There is **no `create_render_pass()` anymore**
+
+Instead, you do rendering setup **when recording command buffers**.
+
+Here is the **modern replacement logic**.
+
+---
+
+### 2.1 Enable dynamic rendering (once, during device creation)
+
+You must enable this feature:
+
+```c
+VkPhysicalDeviceDynamicRenderingFeatures dynamicRendering = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+};
+
+VkDeviceCreateInfo deviceInfo = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &dynamicRendering,
+ // ...
+};
+```
+
+(If you already use Vulkan 1.3 features, you can chain it there instead.)
+
+---
+
+## 3. Replacement for render pass: `vkCmdBeginRendering`
+
+### This replaces your entire render pass logic
+
+```c
+void begin_rendering(VkCommandBuffer cmd, VkImageView swapchainImageView) {
+ VkRenderingAttachmentInfo colorAttachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = swapchainImageView,
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+
+ // Clear at start (equivalent to LOAD_OP_CLEAR)
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+
+ .clearValue = {
+ .color = {{0.0f, 0.0f, 0.0f, 1.0f}}
+ }
+ };
+
+ VkRenderingInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {
+ .offset = {0, 0},
+ .extent = swapchainExtent
+ },
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorAttachment
+ };
+
+ vkCmdBeginRendering(cmd, &renderingInfo);
+}
+```
+
+And later:
+
+```c
+vkCmdEndRendering(cmd);
+```
+
+---
+
+## 4. What this replaces in your old code
+
+### ❌ Remove completely
+
+```c
+vkCreateRenderPass(...)
+vkCreateFramebuffer(...)
+VkRenderPass
+VkFramebuffer
+```
+
+### ❌ No more subpasses
+
+Dynamic rendering is always **single-pass**.
+
+---
+
+## 5. Pipeline changes (IMPORTANT)
+
+Since there is no render pass, your graphics pipeline must declare formats explicitly:
+
+```c
+VkPipelineRenderingCreateInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+};
+
+VkGraphicsPipelineCreateInfo pipelineInfo = {
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &renderingInfo,
+ .renderPass = VK_NULL_HANDLE,
+ // ...
+};
+```
+
+This replaces:
+
+```c
+pipelineInfo.renderPass = renderPass;
+```
+
+---
+
+## 6. Layout transitions (still required!)
+
+You still must transition swapchain images:
+
+```c
+VK_IMAGE_LAYOUT_UNDEFINED
+→ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+→ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
+```
+
+Dynamic rendering **does not remove synchronization** — it only removes render pass objects.
+
+---
+
+## 7. Why this is “newer” and better
+
+✅ Less boilerplate
+✅ No framebuffer creation
+✅ Easier swapchain recreation
+✅ More flexible attachment usage
+✅ Officially recommended in Vulkan 1.3
+
+---
+
+## 8. Mental mapping from old → new
+
+| Old Vulkan | New Vulkan |
+| ---------------------- | --------------------- |
+| `VkRenderPass` | ❌ Gone |
+| `VkFramebuffer` | ❌ Gone |
+| `VkSubpassDescription` | ❌ Gone |
+| `vkCmdBeginRenderPass` | `vkCmdBeginRendering` |
+| `vkCmdEndRenderPass` | `vkCmdEndRendering` |
+
+---
+
+## 9. Summary
+
+> **Modern Vulkan replaces render passes with dynamic rendering, where attachments are defined at command-record time instead of upfront.**
+
+---
+
+If you want, next I can:
+
+* Convert your **entire render loop** to dynamic rendering
+* Explain **image layout transitions** in this model
+* Show **depth attachment dynamic rendering**
+* Explain **why subpasses existed in the first place**
+
+Just tell me 👍
--- /dev/null
+A **fragment** is:
+
+> **A potential pixel generated by the rasterizer that still needs to be tested and possibly written to the framebuffer.**
+
+It is **not yet a pixel** — it’s a *candidate*.
+
+---
+
+## 1. Where a fragment comes from (pipeline context)
+
+In the graphics pipeline:
+
+```
+Vertices
+ ↓
+Primitives (triangles, lines)
+ ↓
+Rasterization
+ ↓
+Fragments ← here
+ ↓
+Fragment shader
+ ↓
+Tests (depth, stencil)
+ ↓
+Pixels written to framebuffer
+```
+
+So a fragment is created **when geometry is rasterized**.
+
+---
+
+## 2. Fragment vs pixel (this is crucial)
+
+| Term | Meaning |
+| -------- | --------------------------------- |
+| Fragment | A potential pixel |
+| Pixel | A value stored in the framebuffer |
+
+A fragment:
+
+* Has position
+* Has interpolated attributes
+* Might be discarded
+* Might overwrite an existing pixel
+* Might blend with an existing pixel
+
+Only after all tests pass does it become a pixel.
+
+---
+
+## 3. What data a fragment contains
+
+A fragment carries:
+
+* **Window-space position** `(x, y, z)`
+* **Interpolated vertex outputs** (color, UVs, normals, etc.)
+* **Derivatives** (for mipmapping)
+* **Coverage info** (MSAA)
+* **Sample index** (if MSAA)
+
+This data is handed to the **fragment shader**.
+
+---
+
+## 4. Fragment shader’s job
+
+The fragment shader:
+
+* Computes final color
+* May sample textures
+* May write depth
+* May discard the fragment
+
+Example:
+
+```glsl
+if (alpha < 0.5)
+ discard;
+```
+
+Discarded fragments never reach the framebuffer.
+
+---
+
+## 5. Depth and fragments (why depth matters)
+
+Multiple fragments can map to the **same pixel**:
+
+* Overlapping triangles
+* Transparency
+* MSAA
+
+The **depth test** decides which fragment wins.
+
+This is why depth buffers exist.
+
+---
+
+## 6. Fragments and MSAA
+
+With multisampling:
+
+* Each pixel has multiple samples
+* Rasterization generates **one fragment per covered sample**
+* Each fragment has a `sampleID`
+
+So fragments are even finer-grained than pixels.
+
+---
+
+## 7. Fragments vs texels
+
+Important distinction:
+
+| Concept | Space |
+| -------- | ------------- |
+| Fragment | Screen space |
+| Texel | Texture space |
+
+Fragments *sample* texels, but they are not texels.
+
+---
+
+## 8. Mental model
+
+> **A fragment is a “would-be pixel” created by rasterization, carrying interpolated data, waiting to pass tests before becoming a real pixel.**
+
+---
+
+## 9. Why Vulkan uses “fragment”
+
+Because Vulkan:
+
+* Exposes pipeline stages precisely
+* Distinguishes between *generation* and *storage*
+
+OpenGL historically blurred this distinction; Vulkan does not.
+
+---
+
+## 10. One-sentence summary
+
+> **A fragment is a rasterized piece of a primitive that corresponds to a pixel location but may be discarded or modified before becoming an actual pixel in the framebuffer.**
+
+If you want, I can:
+
+* Show how fragments are generated from triangles
+* Explain early-Z vs late-Z
+* Explain fragment derivatives (`dFdx`, `dFdy`)
+* Explain how blending works at the fragment level
+
+Just say the word.
+
--- /dev/null
+A **framebuffer** is:
+
+> **The collection of images that receive the final output of rendering for a frame.**
+
+It is where fragments **end up** after all tests pass.
+
+---
+
+## 1. Framebuffer in the pipeline (big picture)
+
+```
+Vertices
+ ↓
+Rasterization
+ ↓
+Fragments
+ ↓
+Fragment Shader
+ ↓
+Depth / Stencil Tests
+ ↓
+Blending
+ ↓
+Framebuffer ← here
+```
+
+The framebuffer is the **destination** of rendering.
+
+---
+
+## 2. What a framebuffer actually contains
+
+A framebuffer is **not a single image**.
+
+It is a **set of attachments**, typically:
+
+* **Color attachment(s)** → final color(s)
+* **Depth attachment** → depth values
+* **Stencil attachment** → stencil values
+
+In Vulkan, these are all **VkImage views**.
+
+---
+
+## 3. Vulkan’s framebuffer is an object, not memory
+
+In Vulkan:
+
+* A **VkFramebuffer** does **not own memory**
+* It **binds image views** to a render pass
+* It defines *which images are written during rendering*
+
+Think of it as a **configuration object**.
+
+---
+
+## 4. Framebuffer vs render pass (important distinction)
+
+| Concept | Role |
+| ----------- | ----------------------------------------- |
+| Render pass | Describes *how* attachments are used |
+| Framebuffer | Binds *actual images* to that description |
+
+Example:
+
+```text
+Render pass: "I need 1 color + 1 depth attachment"
+Framebuffer: "Here are the actual images"
+```
+
+---
+
+## 5. Swapchain framebuffers
+
+In your Vulkan code:
+
+* Each swapchain image gets its own framebuffer
+* Same render pass, different images
+
+Visually:
+
+```
+Swapchain Images: [A] [B] [C]
+Framebuffers: [FA] [FB] [FC]
+```
+
+This allows:
+
+* Rendering one frame
+* Presenting another
+* Avoiding stalls
+
+---
+
+## 6. How fragments write to the framebuffer
+
+For each fragment:
+
+1. Depth test passes
+2. Stencil test passes
+3. Color is written to a color attachment
+4. Depth is written to depth attachment (if enabled)
+5. Blending may combine values
+
+Only then does the fragment become a **pixel in the framebuffer**.
+
+---
+
+## 7. Framebuffer coordinates
+
+Fragments map to framebuffer locations:
+
+* `(x, y)` → pixel location
+* `sample` → MSAA sample index
+
+This mapping is what makes the framebuffer a **screen-space structure**.
+
+---
+
+## 8. Framebuffer vs texture
+
+| Framebuffer | Texture |
+| -------------- | --------------- |
+| Render target | Sampling source |
+| Written by GPU | Read by shaders |
+| Screen-space | Texture-space |
+
+In Vulkan, **both are images** — the difference is *usage*.
+
+---
+
+## 9. Why Vulkan separates everything
+
+Vulkan makes these explicit so you can:
+
+* Reuse render passes
+* Control memory layouts
+* Optimize tile-based GPUs
+* Avoid hidden state
+
+---
+
+## 10. Mental model
+
+> **A framebuffer is a set of images that act as the destination for rendering during a render pass.**
+
+---
+
+## 11. One-sentence summary
+
+> **In Vulkan, a framebuffer binds concrete image views to a render pass so fragments can be written into color, depth, and stencil attachments.**
+
+If you want, I can:
+
+* Walk through a frame step-by-step
+* Explain offscreen framebuffers
+* Explain MRT (multiple render targets)
+* Show how depth-only framebuffers work
+
+Just say where you want to go next.
--- /dev/null
+Great question — **“level” is doing a lot of work here**, and Vulkan’s wording assumes you already know mipmapping. Let’s slow it down and rebuild the idea from first principles.
+
+---
+
+## 1. What “level” means (plain English)
+
+In Vulkan, **`level` means *mip level***.
+
+A **mip level** is:
+
+> A precomputed, smaller version of the same image, used when the image appears smaller on screen.
+
+So instead of one image, a texture actually contains **a pyramid of images**.
+
+---
+
+## 2. Why mip levels exist (the motivation)
+
+Imagine a 1024×1024 texture on a wall:
+
+* Close to the camera → many pixels → high detail needed
+* Far from the camera → few pixels → high detail is wasted
+
+Without mipmaps:
+
+* The GPU samples many texels for one pixel
+* Causes aliasing and shimmering
+* Slower
+
+With mipmaps:
+
+* The GPU samples from a **smaller version** of the texture
+* Better cache usage
+* Less aliasing
+* Faster
+
+---
+
+## 3. What “levels” actually are (concretely)
+
+Suppose your base image (level 0) is:
+
+```
+Level 0: 1024 × 1024
+```
+
+Each **next level halves the resolution**:
+
+```
+Level 1: 512 × 512
+Level 2: 256 × 256
+Level 3: 128 × 128
+Level 4: 64 × 64
+Level 5: 32 × 32
+Level 6: 16 × 16
+Level 7: 8 × 8
+Level 8: 4 × 4
+Level 9: 2 × 2
+Level 10: 1 × 1
+```
+
+That’s what the spec means by “levels”.
+
+---
+
+## 4. Why the formula looks scary
+
+The Vulkan spec gives this formula:
+
+```
+width_level = max(1, floor(width / 2^level))
+```
+
+This just says:
+
+* Divide the width by 2 for each level
+* Stop shrinking at 1
+
+Same for height and depth.
+
+### Example
+
+Base image:
+
+```
+width = 10
+```
+
+Levels:
+
+| Level | width |
+| ----- | -------------------------- |
+| 0 | floor(10 / 1) = 10 |
+| 1 | floor(10 / 2) = 5 |
+| 2 | floor(10 / 4) = 2 |
+| 3 | floor(10 / 8) = 1 |
+| 4 | max(1, floor(10 / 16)) = 1 |
+
+You never get 0 — Vulkan clamps at 1.
+
+---
+
+## 5. Why Vulkan says “no more than one level where all dimensions are 1”
+
+For a 2D image:
+
+* Width and height both shrink
+* Eventually both reach 1×1
+
+After that:
+
+* Further halving would still be 1×1
+* Those extra levels would be pointless
+
+So Vulkan allows **only one such level**.
+
+---
+
+## 6. What about depth (3D images)?
+
+For a 3D texture:
+
+```
+Level 0: 64 × 64 × 64
+Level 1: 32 × 32 × 32
+Level 2: 16 × 16 × 16
+...
+Level 6: 1 × 1 × 1
+```
+
+Same logic, just with `z`.
+
+---
+
+## 7. What “level” is NOT
+
+Very important:
+
+| Not a level | Why |
+| -------------- | -------------------------- |
+| Layer | Layers are separate images |
+| Sample | MSAA samples |
+| Framebuffer | Rendering target |
+| LOD in shaders | LOD *selects* a mip level |
+
+---
+
+## 8. Relationship between level and layers
+
+Think of it as:
+
+```
+Texture
+ ├── Level 0
+ │ ├── Layer 0
+ │ ├── Layer 1
+ │ └── ...
+ ├── Level 1
+ │ ├── Layer 0
+ │ ├── Layer 1
+ │ └── ...
+ └── Level 2
+ ├── Layer 0
+ └── ...
+```
+
+So:
+
+* **Levels** = resolution pyramid
+* **Layers** = array slices
+
+They are orthogonal.
+
+---
+
+## 9. What the last paragraph means (texel buffers)
+
+> “Resources with fewer dimensions … can be considered equivalent to an image with missing dimensions of size 1”
+
+This is just a **mental model**.
+
+Example: a texel buffer (1D):
+
+```text
+Buffer: [ x ]
+```
+
+Vulkan says:
+
+> Treat it like an image:
+
+```
+[x][y=0][z=0][layer=0][sample=0][level=0]
+```
+
+Nothing magical — it’s just explaining how the addressing rules stay consistent.
+
+---
+
+## 10. Mental shortcut (this helps a LOT)
+
+### Remember this:
+
+> **Level = how many times the texture has been downscaled by 2**
+
+Level 0 → original
+Level 1 → half
+Level 2 → quarter
+Level 3 → eighth
+…
+
+---
+
+## 11. One-sentence summary
+
+> **In Vulkan, “level” means mipmap level: each level is a smaller, downscaled version of the image, halving width/height/depth each time until they reach 1.**
+
+---
+
+If you want, I can:
+
+* Show how LOD chooses a mip level
+* Explain how mipmaps are generated in Vulkan
+* Explain how sampling from different levels works
+* Connect this directly to cache behavior on GPUs
+
+Just tell me what clicks next.
--- /dev/null
+This is a **really good catch**, and the confusion is 100% understandable. The key issue is that **“spatial index” is being used in two *very different* senses**.
+
+Vulkan **does** provide *spatial indices for texels*, but **that is not the same thing as a spatial index data structure** (BVH, quadtree, etc.).
+
+Let’s untangle this carefully.
+
+---
+
+## 1. What Vulkan means by “spatial index” for texels
+
+In the Vulkan spec, a **spatial index** simply means:
+
+> **An integer coordinate used to address a position in an image**
+
+So when the spec says:
+
+```
+x, y, z – spatial indices
+```
+
+it means:
+
+* `x` → column
+* `y` → row
+* `z` → depth slice (for 3D images)
+
+These are **array indices**, not acceleration structures.
+
+Think of it as:
+
+```text
+texel = image[x][y][z][layer][sample][level]
+```
+
+This is no different in spirit from:
+
+```c
+int value = array[x][y];
+```
+
+The word *spatial* here only means **position in image space**.
+
+---
+
+## 2. Why Vulkan uses the term “spatial index”
+
+Because images are:
+
+* 1D
+* 2D
+* 3D
+
+and the coordinates correspond to **spatial dimensions** of the image.
+
+So Vulkan distinguishes:
+
+| Index | Meaning |
+| ------- | --------------------------------- |
+| x, y, z | Spatial position inside the image |
+| layer | Which array layer |
+| sample | Which MSAA sample |
+| level | Which mip level |
+
+All six together uniquely identify **one texel sample**.
+
+---
+
+## 3. This is NOT a spatial index (data structure)
+
+When graphics / CS people say **“spatial index”**, they usually mean:
+
+* BVH
+* KD-tree
+* Octree
+* Grid
+* R-tree
+
+Those are **data structures that accelerate spatial queries**.
+
+Texel coordinates do **none** of the following:
+
+* No hierarchy
+* No pruning
+* No acceleration
+* No query optimization
+
+They are **direct addressing**, not indexing *space itself*.
+
+---
+
+## 4. Why the terminology collides (unfortunately)
+
+You’ve hit a classic terminology collision:
+
+### In Vulkan:
+
+> *Spatial index* = **coordinate**
+
+### In algorithms / graphics engines:
+
+> *Spatial index* = **data structure for spatial queries**
+
+Both are technically valid English — but refer to totally different concepts.
+
+---
+
+## 5. A precise mental model
+
+### Vulkan image addressing
+
+```text
+Spatial indexing = "Where is the texel?"
+```
+
+### Spatial index data structure
+
+```text
+Spatial indexing = "What objects are near here?"
+```
+
+Very different questions.
+
+---
+
+## 6. Why Vulkan images don’t need spatial acceleration
+
+Images already provide:
+
+* O(1) random access
+* Dense, regular memory layout
+* Hardware-supported addressing
+
+So there’s **no need** for a BVH-like structure.
+
+In contrast, geometry is:
+
+* Sparse
+* Irregular
+* Expensive to test individually
+
+→ needs spatial indexing.
+
+---
+
+## 7. One-sentence resolution
+
+> **Vulkan provides spatial *coordinates* for texels, but it does not provide spatial *index data structures*; the term “spatial index” in the image documentation refers only to positional addressing, not acceleration.**
+
+---
+
+## 8. Why your intuition was correct
+
+You were right to be suspicious because:
+
+* The same term is used in two domains
+* The Vulkan spec assumes mathematical precision
+* CS/graphics communities overload terminology heavily
+
+This is one of those cases where **context matters more than the word itself**.
+
+If you want next, I can:
+
+* Walk through a concrete texel address example
+* Explain how normalized UVs map to spatial indices
+* Compare texel addressing vs buffer indexing
+* Explain why textures are not sparse spatial structures
+
+Just say the word.
--- /dev/null
+Short answer first, then the nuance:
+
+> **No, in modern graphics they are the same thing.**
+> “Z-buffer” is the historical name; “depth buffer” is the modern, API-neutral term.
+
+But there *are* a few subtle distinctions worth understanding.
+
+---
+
+## 1. Where the names come from
+
+### Z-buffer (historical term)
+
+* Comes from early rasterization papers (1970s–80s)
+* The value compared was literally the **Z coordinate**
+* Fixed-function pipelines
+* OpenGL era terminology
+
+### Depth buffer (modern term)
+
+* Used by Vulkan, modern OpenGL, D3D
+* Refers to the **stored depth value after projection**
+* Not necessarily equal to world-space or view-space Z
+
+So the terminology changed as pipelines became programmable.
+
+---
+
+## 2. What is actually stored today
+
+In modern GPUs:
+
+* The stored value is **depth**, not raw Z
+* Depth is derived from:
+
+ ```
+ depth = z_clip / w_clip
+ ```
+* Then mapped to `[0, 1]` (Vulkan default)
+
+This value:
+
+* Is **non-linear**
+* Is optimized for precision near the camera
+* Depends on the projection matrix
+
+So calling it a “Z buffer” is technically inaccurate — but still common.
+
+---
+
+## 3. Why people still say “Z buffer”
+
+Because conceptually:
+
+* It answers: *“How far along the view direction is this fragment?”*
+* Historically that axis was called **Z**
+* The mental model stuck
+
+You’ll see:
+
+* “Z fighting”
+* “Early Z”
+* “Z test”
+
+All of these mean **depth**, not literal Z.
+
+---
+
+## 4. When the distinction actually matters
+
+### Case 1: Reversed-Z (important modern technique)
+
+Many engines use **reversed depth**:
+
+* Near plane → depth = 1.0
+* Far plane → depth = 0.0
+* Comparison becomes `GREATER` instead of `LESS`
+
+Here:
+
+* Stored depth has **no intuitive relation to Z**
+* Calling it a “Z buffer” is clearly wrong
+* But it’s still a depth buffer
+
+---
+
+### Case 2: Linearized depth in shaders
+
+When you sample depth in a shader:
+
+```glsl
+float d = texture(depthTex, uv).r;
+```
+
+That value is:
+
+* Not linear
+* Not world-space Z
+* Not view-space distance
+
+You often have to **reconstruct** linear depth.
+
+Again: depth ≠ Z.
+
+---
+
+## 5. Vulkan terminology (official)
+
+Vulkan **never** uses the term “Z buffer”.
+
+It uses:
+
+* `VkImageAspectDepthBit`
+* `VkFormat_D32_SFLOAT`
+* `pDepthStencilAttachment`
+
+This is deliberate — Vulkan is precise about what the value represents.
+
+---
+
+## 6. Practical takeaway
+
+| Term | Meaning |
+| ------------- | --------------------------------- |
+| Z buffer | Historical / colloquial name |
+| Depth buffer | Correct modern term |
+| Stored value | Post-projection depth |
+| Axis relation | Often related to Z, but not equal |
+| Vulkan usage | Always “depth” |
+
+---
+
+## 7. One-sentence summary
+
+> **A Z buffer and a depth buffer refer to the same GPU resource, but “Z buffer” is a legacy term, while “depth buffer” correctly reflects that the stored value is post-projection depth, not raw Z.**
+
+If you want next, I can:
+
+* Show why depth is non-linear mathematically
+* Explain reversed-Z in Vulkan
+* Explain Z-fighting and how to fix it
+* Show how depth is packed into different formats
+
+Just tell me.
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+#include <vulkan/vulkan.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h> #include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#include "cube.c"
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+#define MAX_FRAMES_IN_FLIGHT 3
+
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+
+VkSampler sampler;
+VkDescriptorSetLayout descriptorSetLayout;
+VkDescriptorPool descriptorPool;
+VkDescriptorSet descriptorSet;
+
+VkImage textureImage;
+VkDeviceMemory textureMemory;
+VkImageView textureImageView;
+
+VkFence* imagesInFlight = NULL; // tracks which fence is using each image
+
+GLFWwindow* window;
+
+float angle = 0.0f;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[3];
+ float uv[2];
+} Vertex;
+
+uint32_t vertexCount = 0;
+typedef struct _Vec3 { float x; float y; float z;} Vec3;
+typedef struct _Vec2 { float x; float y;} Vec2;
+
+typedef struct {
+ float x, y, z;
+ float u, v;
+} CubeVertex;
+
+CubeVertex cube[] = {
+ // front
+ {-0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f,-0.5f,-0.5f, 1,0},
+ { 0.5f, 0.5f,-0.5f, 1,1},
+
+ {-0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f,-0.5f, 1,1},
+ {-0.5f, 0.5f,-0.5f, 0,1},
+
+ // back
+ {-0.5f,-0.5f, 0.5f, 1,0},
+ {-0.5f, 0.5f, 0.5f, 1,1},
+ { 0.5f, 0.5f, 0.5f, 0,1},
+
+ {-0.5f,-0.5f, 0.5f, 1,0},
+ { 0.5f, 0.5f, 0.5f, 0,1},
+ { 0.5f,-0.5f, 0.5f, 0,0},
+
+ // left
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ {-0.5f,-0.5f,-0.5f, 1,0},
+ {-0.5f, 0.5f,-0.5f, 1,1},
+
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ {-0.5f, 0.5f,-0.5f, 1,1},
+ {-0.5f, 0.5f, 0.5f, 0,1},
+
+ // right
+ { 0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f,-0.5f, 0.5f, 1,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+
+ { 0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+ { 0.5f, 0.5f,-0.5f, 0,1},
+
+ // top
+ {-0.5f, 0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f,-0.5f, 1,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+
+ {-0.5f, 0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+ {-0.5f, 0.5f, 0.5f, 0,1},
+
+ // bottom
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ { 0.5f,-0.5f, 0.5f, 1,0},
+ { 0.5f,-0.5f,-0.5f, 1,1},
+
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ { 0.5f,-0.5f,-0.5f, 1,1},
+ {-0.5f,-0.5f,-0.5f, 0,1},
+};
+
+// this finds which GPU memory type index you should use when allocating memory
+// for a buffer or image.
+// https://docs.vulkan.org/spec/latest/chapters/memory.html#VkMemoryPropertyFlagBits
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props) {
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+ // check each bit and see if it matches typeBits
+ // - `typeBits` is a bitmask
+ // - Each bit corresponds to a memory type index
+ // - If bit i is set -> that memory type can be used for this resource
+ if ((typeBits & (1 << i)) &&
+ // `propertyFlags` may have extra bits
+ // You only care that your requested bits are present
+ //
+ // exmaple:
+ // props = HOST_VISIBLE | HOST_COHERENT
+ // propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+ //
+ // The above would still return true
+
+ (memProps.memoryTypes[i].propertyFlags & props) == props) {
+ return i;
+ }
+ }
+
+ fprintf(stderr, "No suitable memory type\n");
+ exit(1);
+}
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+
+ for (uint32_t i = 0; i < fmtCount; i++) {
+ // Look for a format that is 8-bit BGRA and uses the sRGB color space
+ if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
+ formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ surf_fmt = formats[i];
+ break;
+ }
+ }
+ swapchainFormat = surf_fmt.format;
+
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount + 1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace, // sRBG, HDR10 etc..
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ // .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ // should check if GPU supports this presentMode
+ .presentMode = VK_PRESENT_MODE_MAILBOX_KHR,
+ .clipped = VK_TRUE
+ };
+ // pAllocator is set to NULL
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+ printf("image count: %d\n", swapchainImageCount);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .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
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+
+ vertexCount = SIZE(cube);
+ Vertex vertices[vertexCount];
+
+ for (uint32_t i = 0; i < vertexCount; ++i) {
+ vertices[i].pos[0] = cube[i].x;
+ vertices[i].pos[1] = cube[i].y;
+ vertices[i].pos[2] = cube[i].z;
+
+ vertices[i].uv[0] = cube[i].u;
+ vertices[i].uv[1] = cube[i].v;
+ }
+
+
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = sizeof(Vertex) * vertexCount,
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+ if(vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create vertex buffer\n"); exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = 0
+ };
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+ for(uint32_t i=0;i<memProps.memoryTypeCount;i++){
+ if((memReq.memoryTypeBits & (1<<i)) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)){
+ allocInfo.memoryTypeIndex = i;
+ break;
+ }
+ }
+
+ if(vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate vertex buffer memory\n"); exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufInfo.size, 0, &data);
+ memcpy(data, vertices, bufInfo.size);
+ // memcpy(data, vertices, sizeof(Vertex) * vertexCount);
+ vkUnmapMemory(device, vertexBufferMemory);
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("cube.vert.spv");
+ VkShaderModule fragModule = load_shader("cube.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ // One Vertex per vertex
+ // Move forward sizeof(Vertex) each vertex
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(CubeVertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+
+ VkVertexInputAttributeDescription attrDesc[] = {
+ {
+ .location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = offsetof(Vertex, pos)
+ },
+ {
+ .location = 1,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32_SFLOAT,
+ .offset = offsetof(Vertex, uv)
+ }
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=2,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_BACK_BIT,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ // .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ .rasterizationSamples = VK_SAMPLE_COUNT_4_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPushConstantRange pushRange = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(float)
+ };
+
+ VkDescriptorSetLayoutBinding texBinding = {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
+ };
+
+ VkDescriptorSetLayoutCreateInfo layoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .bindingCount = 1,
+ .pBindings = &texBinding
+ };
+
+ vkCreateDescriptorSetLayout(device, &layoutInfo, NULL, &descriptorSetLayout);
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pushRange,
+ .setLayoutCount = 1,
+ .pSetLayouts = &descriptorSetLayout,
+ };
+
+ vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout);
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+
+ VkPipelineRenderingCreateInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+ };
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = {
+ .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &renderingInfo,
+ .stageCount = 2,
+ .pStages = stages,
+ .pVertexInputState = &vertexInput,
+ .pInputAssemblyState = &inputAssembly,
+ .pViewportState = &vpState,
+ .pRasterizationState = &rasterizer,
+ .pMultisampleState = &multisampling,
+ .pColorBlendState = &colorBlending,
+ .layout = pipelineLayout,
+ .renderPass = VK_NULL_HANDLE,
+ .subpass = 0
+ };
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,
+ &graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+void create_texture_sampler() {
+ VkSamplerCreateInfo samplerInfo = {
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ // this creates to black stars from an artifact
+ // .magFilter = VK_FILTER_LINEAR,
+ // .minFilter = VK_FILTER_LINEAR,
+ .magFilter = VK_FILTER_NEAREST,
+ .minFilter = VK_FILTER_NEAREST,
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ };
+
+ vkCreateSampler(device, &samplerInfo, NULL, &sampler);
+}
+
+void create_descriptor_pool() {
+ VkDescriptorPoolSize poolSize = {
+ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1
+ };
+
+ VkDescriptorPoolCreateInfo poolInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .poolSizeCount = 1,
+ .pPoolSizes = &poolSize,
+ .maxSets = 1
+ };
+
+ vkCreateDescriptorPool(device, &poolInfo, NULL, &descriptorPool);
+}
+
+void create_descriptor_set() {
+ VkDescriptorSetAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .descriptorPool = descriptorPool,
+ .descriptorSetCount = 1,
+ .pSetLayouts = &descriptorSetLayout
+ };
+
+ vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet);
+}
+
+void create_texture_image() {
+ // CPU RAM
+ // ↓ memcpy
+ // HOST_VISIBLE buffer
+ // ↓ GPU copy
+ // DEVICE_LOCAL image
+ // ↓ shader sampling
+ // fragment shader
+ // 2x2 RGBA checkerboard
+ uint32_t pixels[] = {
+ 0xFFFFFFFF, 0xFFFF69B4, // top row: white, pink
+ 0xFFFF69B4, 0xFFFFFFFF // bottom row: pink, white
+ };
+
+ VkDeviceSize size = sizeof(pixels);
+
+ /* --- staging buffer --- */
+ VkBuffer staging;
+ VkDeviceMemory stagingMem;
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+ };
+ // this creates a buffer object that
+ // - Holds `size` bytes
+ // - Can be used as a transfer source
+ // WARNING: no memory is allocated yet - this is just the buffer
+ // description
+ vkCreateBuffer(device, &bufInfo, NULL, &staging);
+
+ // The driver tell you:
+ // - required memory size
+ // - required alignment
+ // - Which memory types are allows (memoryTypeBits)
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, staging, &memReq);
+
+ VkMemoryAllocateInfo alloc = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex =
+ // https://docs.vulkan.org/spec/latest/chapters/memory.html#VkMemoryPropertyFlagBits
+ // This means:
+ // “Find a memory type that
+ // ✔ the buffer supports
+ // ✔ I can map from CPU
+ // ✔ doesn’t require manual flush/invalidate”
+ find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+ // `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`
+ // bit specifies that memory
+ // allocated with this type can be mapped for host access using
+ // vkMapMemory.
+ //
+ // `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`
+ // "When I write to mapped memory, the GPU sees it automatically"
+ //
+ // This is slower than flushing.
+ // Writes are automatically visible
+ //
+ // without it, you must manually do:
+ //
+ // vkFlushMappedMemoryRanges(...)
+ // vkInvalidateMappedMemoryRanges(...)
+
+ };
+
+ // - Physical memory is allocated
+ // - The buffer is bound to that memory
+ //
+ // when allocating you are reserving a chunk of memroy from the GPU (or a
+ // heap).
+ //
+ // - `VkDeviceMemory` is just a handle that represents that chunk.
+ // - The memory exists, but nothing is stored in it yet
+ // - The GPU doesn't know which buffer or image will use it.
+ vkAllocateMemory(device, &alloc, NULL, &stagingMem);
+ // Binding memory:
+ // - Assigns a physical region of GPU-accessible memory
+ // - Tells Vulkan where the object’s data lives
+ // - Fixes alignment and offset requirements
+ vkBindBufferMemory(device, staging, stagingMem, 0);
+
+ // - CPU gets a pointer
+ // - Copies `size` of data
+ // - Unmaps (flush is automatic because of COHERENT)
+ // At this point, pixel data is in GPU-visable memory
+ void* data;
+ vkMapMemory(device, stagingMem, 0, size, 0, &data);
+ memcpy(data, pixels, size);
+ vkUnmapMemory(device, stagingMem);
+
+ /* --- image --- */
+ // Create the GPU image (the actual texture)
+ // This describes
+ // - A 2D image
+ // - 2x2 pixels
+ // - Optimal tiling (GPU-friendly layout)
+ // - can be:
+ // - copied into
+ // - sampled by shaders
+ //
+ VkImageCreateInfo img = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {2, 2, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+ };
+ // still no memory allocated yet for textureImage
+ vkCreateImage(device, &img, NULL, &textureImage);
+
+ vkGetImageMemoryRequirements(device, textureImage, &memReq);
+ // modify the alloc struct so we don't have to create another struct.
+ // for simplicity
+ alloc.allocationSize = memReq.size;
+ alloc.memoryTypeIndex =
+ find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ // `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`
+ // specifies that memory allocated
+ // with this type is the most efficient for device access. This property
+ // will be set if and only if the memory type belongs to a heap with the
+ //
+ // This memory cannot be accessed by the CPU, and is faster than
+ // `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, because there is not memory being
+ // mapped between the CPU and GPU.
+
+ // bind and allocated the textureMemory
+ // GPU can access it efficently
+ vkAllocateMemory(device, &alloc, NULL, &textureMemory);
+ vkBindImageMemory(device, textureImage, textureMemory, 0);
+
+ /* --- command buffer for copy --- */
+ // Redner a one-time command buffer
+ // Why?
+ // Because copies and layout transitions happen on the GPU
+ // https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1
+ };
+
+ VkCommandBuffer cb;
+ vkAllocateCommandBuffers(device, &cbAlloc, &cb);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ /* layout: undefined → transfer dst */
+ // - Vulkan images start in UNDEFINED
+ // - Copying requires TRANSFER_DST
+ VkImageMemoryBarrier toTransfer = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ // The layout the image is currently in.
+ // Here it is `UNDEFINED` because we just created the image
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ // The layout we want for the next operation.
+ // `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` is optimized for copying data
+ // into the image
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ // VK_ACCESS_TRANSFER_WRITE_BIT tells Vulkan:
+ // "we're going to write this image in a transfer operation"
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ // The image we're transitioning
+ .image = textureImage,
+
+ .subresourceRange = {
+ // only the color aspect (not depth or stencil)
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ // noly the first mip level
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ // only the first layer (no cubemaps/arrays here)
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+
+ // This tells Vulkan:
+ // “Before transfer writes, make sure the image is ready.”
+ // https://docs.vulkan.org/spec/latest/chapters/synchronization.html#vkCmdPipelineBarrier
+ // https://docs.vulkan.org/spec/latest/chapters/synchronization.html#VkPipelineStageFlagBits
+ //
+ // vkCmdPipelineBarrier(
+ // VkCommandBuffer commandBuffer,
+ // VkPipelineStageFlags srcStageMask,
+ // VkPipelineStageFlags dstStageMask,
+ // ...
+ // );
+ // srcStageMask → which pipeline stage(s) must finish before the barrier
+ // dstStageMask → which pipeline stage(s) must wait until after the barrier
+ //
+ // TOP_OF_PIPE → Vertex → Fragment → Color Attachment → Bottom of Pipe
+ //
+ // so the the function below happens before the pipeline
+
+ // "Pipeline barriers also provide synchronization control within a command
+ // buffer, but at a single point, rather than with separate signal and wait
+ // operations. Pipeline barriers can be used to control resource access
+ // within a single queue"
+ // https://docs.vulkan.org/spec/latest/chapters/synchronization.html#vkCmdPipelineBarrier
+ vkCmdPipelineBarrier(
+ cb, // commandBufer
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask
+ VK_PIPELINE_STAGE_TRANSFER_BIT, // DstStageMask
+ // a bit mask of VkPipelineStageFlagBits specifying the destination
+ // stages
+ 0, // dependabctFlags
+ 0, // memoryBarrierCount
+ // A pointer to an array of `VkMemoryBarrier` structures.
+ NULL, // pMemoryBarriers
+ 0, // bufferMemoryBarrierCount
+ // a pointer array of `VkBufferMemoryBarrier`
+ NULL, // pBuferMemoryBarriers
+ 1, // imageMemoryBarrierCount
+ // a pointer to an array of VkImageMemoryBarrier
+ &toTransfer // pImageMemoryBarriers
+ );
+
+ /* copy buffer → image */
+ VkBufferImageCopy copy = {
+ .imageSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ },
+ .imageExtent = {.width = 2, .height = 2, .depth = 1}
+ };
+
+ // Copy data from a buffer object to an image object
+ vkCmdCopyBufferToImage(
+ cb,
+ staging,
+ textureImage,
+ // `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`
+
+ // must only be used as a destination image of a transfer command.
+ // This layout is valid only for image subresources of images
+ // created with the VK_IMAGE_USAGE_TRANSFER_DST_BIT usage flag set.
+
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1,
+ ©);
+
+ /* layout: transfer → shader read */
+ VkImageMemoryBarrier toSample = toTransfer;
+ toSample.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ toSample.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ toSample.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ toSample.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toSample
+ );
+
+ vkEndCommandBuffer(cb);
+
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb
+ };
+ // vkQueueSubmit(graphicsQueue, 1, &submit, VK_NULL_HANDLE);
+ // use fence instead of wait idle
+ // vkQueueWaitIdle(graphicsQueue);
+ // Fence lets CPU know when GPU is done
+ // CPU blocks until:
+ // - Copy completes
+ // - Image is ready for sampling
+ VkFence fence;
+ VkFenceCreateInfo fenceInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
+ vkCreateFence(device, &fenceInfo, NULL, &fence);
+ vkQueueSubmit(graphicsQueue, 1, &submit, fence);
+ vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+ vkDestroyFence(device, fence, NULL);
+
+
+ // cleanup staging resources
+ vkFreeCommandBuffers(device, commandPool, 1, &cb);
+ vkDestroyBuffer(device, staging, NULL);
+ vkFreeMemory(device, stagingMem, NULL);
+}
+
+void create_texture_image_view() {
+ VkImageViewCreateInfo view = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = textureImage,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+ };
+ vkCreateImageView(device, &view, NULL, &textureImageView);
+}
+
+void update_descriptor_set() {
+ VkDescriptorImageInfo imageInfo = {
+ .sampler = sampler,
+ .imageView = textureImageView,
+ .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+ };
+
+ VkWriteDescriptorSet write = {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = descriptorSet,
+ .dstBinding = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &imageInfo
+ };
+
+ vkUpdateDescriptorSets(device, 1, &write, 0, NULL);
+}
+
+
+/* --- Main --- */
+int main(void) {
+ double lastTime = glfwGetTime();
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+
+ uint32_t instanceVersion;
+ vkEnumerateInstanceVersion(&instanceVersion);
+ printf("Vulkan loader supports version %d.%d.%d\n",
+ VK_API_VERSION_MAJOR(instanceVersion),
+ VK_API_VERSION_MINOR(instanceVersion),
+ VK_API_VERSION_PATCH(instanceVersion)
+ );
+
+ if (!(VK_API_VERSION_MAJOR(instanceVersion) >= 1 &&
+ VK_API_VERSION_MINOR(instanceVersion) >= 3)) {
+ // you need at least vulkan 1.3 to use dynamic rendering
+ fprintf(stderr, "need at least vulkan 1.3");
+ exit(1);
+ }
+ uint32_t ext_count=0;
+ VkApplicationInfo appInfo = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = "cube",
+
+ // Developer supplied version number of the application
+ .applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0),
+
+ // must be the highest version of VUlkan that the application is
+ // designed to use.
+ .apiVersion = VK_API_VERSION_1_3
+ };
+
+ // This function returns an array of names of Vulkan instance extensions
+ // required by GLFW for creating Vulkan surfaces for GLFW windows. If
+ // successful, the list will always contain `VK_KHR_surface`, so if you
+ // don't require any additional extensions you can pass this list directly
+ // to the `vkInstanceCreateInfo` struct
+
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &appInfo,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+
+ vkCreateInstance(&ici,NULL,&instance);
+
+ // You get instance-level pointers this way
+ volkLoadInstance(instance);
+
+ // This function creates a Vulkan surface of the specific window.
+
+ // If the Vulkan loader or at least one minimally function ICD were not
+ // fuond, this returns `VK_ERROR_INITIALIZATION_FAILED` and generates a
+ // `GLFW_API_UNAVAILABLE` error. call `glfwVUlkanSupported` to check whether
+ // Vulkan is at least minimally available
+
+ // Parameteres
+ //
+ // [in] *instance* The Vulkan instance to create the surface in.
+
+ // [in] *window* The window to create the surface for.
+
+ // [in] *allocator* The allocator to use, or `NULL` to use the default
+ // allocator.
+
+ // [out] *surface* Where to store the handle of the surface. This is set
+ // to `VK_NULL_HANDLE` if an error occurred.
+
+ // Returns
+ // `VK_SUCCESS` if successful, or a Vulkan error code if an *error*
+ // occured
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8];
+ vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ printf("device count: %d\n", physCount);
+
+ // getting the name of the devices
+ for (int i = 0; i < physCount; ++i) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(devices[i], &properties);
+ printf("GPU Name: %s\n", properties.deviceName);
+
+ // if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
+
+ // }
+ if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ physical = devices[i];
+ }
+
+ }
+ // physical=devices[0];
+
+ uint32_t qcount=0;
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ // VkQueueFamilyProperties qprops[16]; // just a arbitrary number..
+ VkQueueFamilyProperties qprops[qcount];
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+
+ // checking wheather a queue family of a physical device supports
+ // presentation to a given surface
+ //
+ // While most modern GPUs (especially on Windows/Linux) use the same
+ // queue family for both graphics and presentation, it is not guaranteed
+ // by the Vulkan spec.
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+
+ // we only grab one que because we are going to be using
+ // `VK_SHARING_MODE_EXCLUSIVE`
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }
+
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ // .pNext = NULL,
+ .dynamicRendering = VK_TRUE
+ };
+
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &dynamicRenderingFeature,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ // You get Device-level pointers this way
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ imagesInFlight = malloc(sizeof(VkFence) * swapchainImageCount);
+ for (uint32_t i = 0; i < swapchainImageCount; i++)
+ imagesInFlight[i] = VK_NULL_HANDLE;
+
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+
+ create_texture_image();
+ create_texture_image_view();
+ create_texture_sampler();
+
+ create_descriptor_pool();
+ create_descriptor_set();
+ update_descriptor_set();
+
+ create_vertex_buffer();
+
+ VkSemaphoreCreateInfo sem = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
+ };
+
+ VkFenceCreateInfo fence = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT
+ };
+
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+ }
+
+ uint32_t currentFrame = 0;
+
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ double now = glfwGetTime();
+ double delta = now - lastTime;
+ if (delta > 0.03) delta = 0.03; // clamp to max ~33ms
+ lastTime = now;
+ float rotationSpeed = M_PI / 2.0f; // 90°/sec
+ angle += rotationSpeed * delta;
+
+ uint32_t imgIndex;
+
+ // Acquire next swapchain image
+ vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+ );
+
+ // Wait for previous frame using this image
+ if (imagesInFlight[imgIndex] != VK_NULL_HANDLE) {
+ vkWaitForFences(device, 1, &imagesInFlight[imgIndex], VK_TRUE, UINT64_MAX);
+ }
+
+ // Mark image as now in use by current frame's fence
+ imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+
+ // Reset current frame fence
+ vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+ // Get command buffer for this image
+ VkCommandBuffer cb = commandBuffers[imgIndex];
+ vkResetCommandBuffer(cb, 0);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ // Transition image: PRESENT -> COLOR_ATTACHMENT_OPTIMAL
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .image = swapchainImages[imgIndex],
+ .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }
+ };
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+ // Begin dynamic rendering
+ VkRenderingAttachmentInfo colorAttachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = swapchainImageViews[imgIndex],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = {{0.0f, 0.0f, 0.0f, 1.0f}}
+ };
+ VkRenderingInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {{0, 0}, swapchainExtent},
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorAttachment
+ };
+ vkCmdBeginRendering(cb, &renderingInfo);
+
+ // Bind pipeline, viewport, scissor
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+ VkViewport vp = {0, 0, (float)swapchainExtent.width, (float)swapchainExtent.height, 0.0f, 1.0f};
+ VkRect2D scissor = {{0,0}, swapchainExtent};
+ vkCmdSetViewport(cb, 0, 1, &vp);
+ vkCmdSetScissor(cb, 0, 1, &scissor);
+
+ // Bind vertex buffer
+ VkDeviceSize offset = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vertexBuffer, &offset);
+
+ // Push rotation angle
+ vkCmdPushConstants(cb, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float), &angle);
+
+ // Bind descriptor set
+ vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+ // Draw
+ vkCmdDraw(cb, vertexCount, 1, 0, 0);
+
+ vkCmdEndRendering(cb);
+
+ // Transition image: COLOR_ATTACHMENT_OPTIMAL -> PRESENT
+ barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &barrier);
+
+ vkEndCommandBuffer(cb);
+
+ // Submit
+ VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &imageAvailable[currentFrame],
+ .pWaitDstStageMask = &waitStage,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &renderFinished[currentFrame],
+ };
+ vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+ // Present
+ VkPresentInfoKHR present = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &renderFinished[currentFrame],
+ .swapchainCount = 1,
+ .pSwapchains = &swapchain,
+ .pImageIndices = &imgIndex
+ };
+ vkQueuePresentKHR(graphicsQueue, &present);
+
+ // Advance frame
+ currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+ }
+
+
+ // Wait for GPU to finish all work
+ vkDeviceWaitIdle(device);
+
+ // Destroy semaphores and fences
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (imageAvailable[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, imageAvailable[i], NULL);
+ if (renderFinished[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, renderFinished[i], NULL);
+ if (inFlightFences[i] != VK_NULL_HANDLE)
+ vkDestroyFence(device, inFlightFences[i], NULL);
+ }
+
+ // Free imagesInFlight array
+ free(imagesInFlight);
+
+ // Destroy vertex buffer
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+
+ // Destroy texture resources
+ vkDestroySampler(device, sampler, NULL);
+ vkDestroyImageView(device, textureImageView, NULL);
+ vkDestroyImage(device, textureImage, NULL);
+ vkFreeMemory(device, textureMemory, NULL);
+
+ // Destroy descriptor sets/pools/layout
+ vkDestroyDescriptorPool(device, descriptorPool, NULL);
+ vkDestroyDescriptorSetLayout(device, descriptorSetLayout, NULL);
+
+ // Destroy pipeline and pipeline layout
+ vkDestroyPipeline(device, graphicsPipeline, NULL);
+ vkDestroyPipelineLayout(device, pipelineLayout, NULL);
+
+ // Destroy command pool
+ vkDestroyCommandPool(device, commandPool, NULL);
+
+ // Destroy swapchain image views
+ for (uint32_t i = 0; i < swapchainImageCount; i++) {
+ vkDestroyImageView(device, swapchainImageViews[i], NULL);
+ }
+ free(swapchainImageViews);
+ free(swapchainImages);
+
+ // Destroy swapchain
+ vkDestroySwapchainKHR(device, swapchain, NULL);
+
+ // Destroy device, surface, and instance
+ vkDestroyDevice(device, NULL);
+ vkDestroySurfaceKHR(instance, surface, NULL);
+ vkDestroyInstance(instance, NULL);
+
+ // Destroy GLFW window
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
+* https://docs.vulkan.org/samples/latest/samples/extensions/dynamic_rendering/README.html
+* https://www.youtube.com/watch?v=m1RHLavNjKo
+* https://pilex1.github.io/computer-graphics/vertex-shader-coordinates
+* https://learnopengl.com/Advanced-OpenGL/Face-culling
--- /dev/null
+build: *.adoc
+ asciidoctor -b html5 *.adoc
--- /dev/null
+= Memory
+:source-highlighter: pygments
+:pygments-style: monokai
+:docinfo: shared
+:docinfodir: /usr/local/share/readme-to-html/
+:stem: latexmath
+
+== Memory Heaps and types
+
+Each memory type has a flag associated with it. for example `HOST_COHERENT`,
+`HOST_CACHED`, `HOST_VISIBLE`.
+
+=== `DEVICE_LOCAL`
+
+* Video memory. Fast access from GPU.
+
+* No direct acces from CPU - mapping not possible
+
+* Good for resources written and read frequently by GPU.
+
+* Good for resources uploaded once (immutable) or infrequently by CPU, read
+ frequently by GPU.
+
+* You need transfer command to get memory here
+
+=== `HOST_VISIBLE`
+
+* System memory. Accessible to CPU - mapping possible.
+
+* Access from GPU possible but slow.
+
+* Across PCIe bus, reads caches on GPU.
+
+* Good for CPU-side (staging) copy of your resources - used as source of
+ transfer.
+
+* Data written by CPU, read once by GPU (e.g. constant buffer) may work.
+ (always measure!)
+
+* Large data read by GPU - place here as last resort.
+
+* Large data written and read by GPU - shouldn't ever be here.
+
+* Hazard: Uncached. Writes may be write combined.
+
+typical usage for this is setting up your stagging buffers before you send them
+over to device local.
+
+== `DEVICE_LOCAL` + `HOST_VISIBLE`
+
+* Special pool of video memory
+
+* Exposed on AMD only. 256 MiB.
+
+* Fast access from GPU.
+
+* Direct access by both CPU and GPU.
+ - You don't need to do expolicit transfer.
+ - Mapping possible.
+
+* Good for resources updated frequently by CPU (dynamic), read by GPU.
+
+* Use as fallback if `DEVICE_LOCAL` is small and oversubscribed.
+
+* Hazard: Driver will use this memory tool
+
+* Hazard: Uncached. Writes may be write-combined.
+
+A good use for this is buffers that are constantly updating
+
+== `HOST_VISIBLE` + `HOST_CACHED`
+
+* System memory.
+
+* CPU reads and writes cached (write-back).
+
+* GPU access though PCIe.
+
+* GPU reads snoop CPU cache.
+
+* Good for resources written by GPU, read by CPU.
+ - Result of computations
+
+* Direct access by both CPU and GPU.
+ - No need to do explicit transfer
+
+* Use for any resources read or accessed randomly on CPU.
+
+
+== Snooping
+
+In the world of computer architecture, *snooping* isd the process where one
+component (like the GPU) monitors the cache of another component (the CPU) to
+make sure it is seeing the most up-to-date version of the data.
+
+=== The Problem: The "Two Versions" Conflict
+
+Caches create a synchonization problem. When the CPU changes a value in its
+`HOST_CACHED` memory, it doesn't immediately send that change to the main RAM --
+it keeps it in its local L1/L2/L3 cache because that's faster.
+
+If the GPU then tries to read that same dat afrom the main RAM. it will see the
+*old, stale version* because the "new" version is still sitting in the CPU's
+cache.
+
+=== How "snooping" Works
+
+When the text says "GPU reads snoop CPU cache," it means the hardware is
+designed to handle this automicatlly:
+
+1. *The Request*: The GPU send a requrest for a specific piece of data across
+ the PCIe bux.
+
+2. *The Snoop*: Before the data is pulled from RAM, the system "asks" or
+ "snoops" the CPU's chache: "_Hey, do you have a modified version of this_
+ _address?_"
+
+3. *The Handshake*:
+ - If teh CPU *does* have a new version, it provides that data directly (or
+ flushes it to RAM first).
+
+ - If the CPU *doesn't*, the GPU proceeds to read from RAM as usual.
+
+== "always measure"
+
+They are tallking about *Unified Memory Architecture (UMA)* vs *Discrete GPUs*.
+Depending on the user's hardware, putting "read-once" data (like a constant
+buffer) in `HOST_VISIBLE` memory might be fast on one computer and a massive
+bottleneck on another.
+
+Here is why you need to measure:
+
+=== 1. The PCIe Bottleneck vs. Integrated Speed
+ On a Descrete GPU (NVIDIA/AMD card)::
+ `HOST_VISIBLE` memory usually lives in your system RAM. For the GPU to
+ read it, the data must travel across the PCIe bus. This is like trying
+ to drink a milkshake through a very long, thin straw.
+
+ On a Integrated GPU (Intel/Apple/Mobile)::
+ The CPU and GPU share the exact same physical RAM. In this case,
+ `HOST_VISIBLE` is "local" to both. There is no "straw" -- they are both
+ sitting at the same table.
+
+=== 2. The "Read Once Strategy"
+
+The text says: "Data written by CPU, read once by GPU... may work. (always
+measure!)"
+
+The Theory::
+ If the GPU only needs to look at a piece of dat one time (like a
+ transformation matrix for a single frame), it might be faster to let the GPU
+ reach across the PCIe bus and grab it once, rather than spending time
+ copying it into "Fast" GPU memory first.
+
+The Reality::
+ If the GPU's "reach-across-the-bus" latency is too high, your frame rate
+ will tank. You have to use a profiler (like NVIDIA Nsight or RenderDoc) to
+ see which path is actually faster on that specific hardware.
+
+=== 3. What does "Measurig" look like?
+
+When a Vulkan developer "measures," they aren't just looking at the FPS counter.
+They are looking at:
+
+1. *Frame Time*: Does using `HOST_VISIBLE` memory add 2ms to my frame?
+
+2. *Bus Utilization*: Is the PCIe bus 100% saturated?
+
+3. *Cache Hits*: Is the GPU's internal cache helping hide the slowness of the
+ system RAM?
+
+== Cache Hits and Cache Misses
+In computing, a *cache* is a small, utra-fast storage area located very close to
+the processor (CPU or GPU). Because main memory (RAM) is relatively slow and far
+away, the processor keeps copies of data it recently used -- or thinks it will
+use soon -- in the cache.
+
+Thank of it like this: *Main Memory* is a massive warehouse across town. The
+*Cache* is the small toolbox istting right next to you on your workbench.
+
+=== 1. Cache Hit
+
+A *Cache Hit* occurs when the processor looks for a piece of data and finds it
+immediately in the cache.
+
+The Result::
+ The procoessor can continue working at full speed.
+
+=== 2. Cache Miss
+A *Cache Miss* occurs when the data the processor needs in _not_ in the cache.
+The processor must then "stall" (wait) while the system fetches that dasta from
+the much slower main memory (RAM).
+
+The Result::
+ A massive performance penalty. Modern processors can execute hundreds of
+ instructions in the time it takes to perform one "fetch" from RAM.
+
+=== 3. Why this matters for your Vulkan Mesh
+When you were reading about `HOST_VISIBLE` memory, the text mentioned that it is
+often *Uncached*
+
+If you put your mesh data in uncached memory:
+
+1. Every single time the GPU needs a vertex, it has to go all the way to the
+ "warehouse" (System RAM).
+
+2. It can't store the vertex in its "toolbox" (GPU Cache) for quick access if it
+ needs it again (like for a neighboring triangle).
+
+3. This results in *100% Cache Misses*, which makes your rendering significantly
+ slower.
+
+=== 4. How to improve "Hit Rates"
+
+Programmers use a concept called "Data Locality" to maximize hits:
+
+Temporal Locality::
+ If you used a piece of data once, you'll probably use it again soon.
+
+Spatial Locality::
+ If you used a piece of data at one address, you'll probably need the data
+ stored right next to it very soon.
+
+In Vulkan, this is why we try to keep data in `DEVICE_LOCAL` memory. It allows
+the GPU to use its internal cache systems effectively that the "toolbox" is
+always full of the right tools before the work begins.
+
+== Memory blocks
+* In old API's the resource owns the memory
+ - Memory is allocated when creating the resource.
+ - No control of where that memory is placed.
+
+* In Vulkan the memory is allocated by itself.
+ - Memory is allocated in blocks.
+ - A single block can have many resoureces.
+
+* Allows many new optimization techniques (memory alignment)
+ - Pool similar resourece for tighter packing.
+ - Keep object resources together to simpilify streaming of that objects
+ required resources.
+
+== Memory differences OpenGL vs Vulkan
+=== 1. OpenGL: Implicit Allocation (The Black Box)
+
+When you create a texture or a buffer in OpenGL, the driver does two things at
+once:
+
+1. It creates the *object handle* (the metadata like width, height, format).
+
+2. It *automatically allocated* the memory for that object.
+
+==== The Downside
+
+Lack of Controll::
+ You don't know exactly _where_ in the VRAM that texture is.
+
+Fragmentation::
+ If you create and delete many small textures, the driver's memory can
+ because a "swiss cheese" of holes, leading to crashes even if you
+ technically have free space.
+
+Overhead::
+ Every `glGenTextures` might trigger a slow system call to the OS to find
+ more memory.
+
+=== 2. Vulkan: Explicit Allocation (The Memory Block)
+
+In Vulkan, objects and memory are completely decoupled.
+
+1. You create a "Resourece" (e.g., a `VkImage`). This is just a "logical"
+ object; it has no memory and cannot store data yet.
+
+2. You ask the device how much memory that object needs
+ (`vkGetImageMemoryRequirements`A.)
+
+3. You allocate a large block of raw memory (`vkDeviceMemory`)
+
+4. You bind the resource to a specific spot (offset) inside that block.
+
+=== 3. key differences and Optimization Techniques
+
+==== A. Sub-Allocation (Tighter Packing)
+
+Instead of asking the GPU for 100 small chunks of memory (which is very slow),
+you ask for *one giant 256MB block*. You then "slice" that block up yourself.
+
+Analogy::
+ Buying a giant loaf of bread and slicing it yourself rather then buying 100
+ individual slices. It's faster and results in less "packaging" (overhead).
+
+==== B. Memory Alignment
+
+GPUs require data to start at specific intervals (e.g. every 256 bytes).
+
+* In *OpenGL*, the driver handles this, often wasting space to stay safe.
+
+* In *Vulkan*, you see the allignment requirements. YOu can pack a small buffer
+ immediately after a large one to ensure there is zero wasted space, as long as
+ you respect the alilgnment the GPU asked for.
+
+==== C. Data Streaming and Locality
+
+Because you control the blocks, you can put all the resources for a specific
+"Game Level" or "Character" in the *same physical block of memory*.
+
+- When the character enters the scene, you can "stream" (upload) that entire
+ block to the GPU in one go.
+
+- This keeps related data close together, which significantly improves your
+ *Cache Hit* rate (as we discussed earlier).
+
+== Memory Alignment in C
+
+In C, *memory alignment* is teh way the compiler arranges data in RAM to ensure
+the CPU can read it efficiently. CPUs generally don't read memory byte-by-byte;
+they read in larger "chunks" (usually 4 or 8 bytes).
+
+If a variable is "misaligned" (straddling the boundary between two chunks), the
+CPU has to perform two reads and some bit-shifting math to get the data, which
+slows everything down.
+
+=== 1. The Power of Two Rule
+
+Every data type has an alignment requirement. Usually, a type must start at a
+memory address that is a multiple of its size:
+
+* `char` (1 byte): Can start at any address.
+* `short` (2 bytes): Must start on an even address (multiple of 2).
+* `int` / `float` (4 bytes): Must start on an address divisible by 4.
+* `double` / `long long` (8 bytes): Must start on an address divisibel by 8.
+
+=== 2. Padding: The "Ghost" Bytes
+
+To satisfy these requirements, teh compiler inserts "padding" -- empty, wasted
+bytes -- between struct members.
+
+Look at this struct:
+
+[source, c]
+----
+struct Example {
+ char a; /* 1 byte */
+ /* 3 bytes of hidden padding inserted here */
+ int b; /* 4 bytes */
+ char c; /* 1 byte */
+ /* 3 bytes of hidden padding inserted here */
+};
+----
+
+Even thought the data only totals *6 bytes*, `sizeof(struct Example)` will
+return *12 bytes*.
+
+3. Why the padding at the end?
+
+The compiler adds teh padding at the end of a struct so that if you create an
+*array* of that struct, the second element will also be correctly aligned.
+Without that final padding, the `int b` in the second element of an array would
+start at an odd address and crash or slow down the system.
+
+4. Optimization: The "Big to small" Strategy
+
+You can minimize wasted space by ordering your struct members from *largest*
+*alignment to smallest*.
+
+Bad Packing (12 bytes):
+
+[source, c]
+----
+struct Bad {
+ char a; // 1
+ // 3 padding
+ int b; // 4
+ char c; // 1
+ // 3 padding
+};
+----
+
+Good Packing (8 bytes):
+
+[source, c]
+----
+struct Good {
+ int b; // 4
+ char a; // 1
+ char c; // 1
+ // 2 padding at the end to make the total 8 (multiple of 4)
+};
+----
+
+5. WHy this is CRITICAL for Vulkan
+
+VUlkan is extremely strit about alignment. WHen you send a C stuct (like your
+UniformBufferObject) to a shader:
+
+1. *GLSL (shaders)* follows a standard `std140` or `std430`.
+
+2. *C* follows the rules of your compiler (GCC/MSVC)
+
+If your C struct alignment doesn't perfectly match the Shader's alignment rules,
+the Shader will look for "Variable B" at address 8, but your C code put it at
+address 4. *Your green cube will flicker, stretch or simply disappear*.
+
+==== A Tip for you Project
+
+In you Vulkan code, you will often see poepl use `alignas(16)` or specific
+compiler attributes to force C structs to match the 16-byte aligment that
+Vulkan's "Uniform Buffers" often require.
+
+== Suballocation
+
+* Don't allocate a separate memory block for each resourece.
+ - Small limit on maximum number of allocations (e.g. 4096).
+
+* Allocate bigger blocks and sub-allocate ranges for your resuorces.
+ - 256 MiB is good default block size.
+
+ - For heaps <= 1 GiB use smaller blocks. (e.g. heap size / 8).
+
+* Allocations are slow.
+ - Prefer not to allocate or free memory blocks during gameplay to avoid
+ hitching.
+
+ - If you need to, you can do it on background thread.
+
+== Allocating Memory in Vulkan
+
+To Allocate resources and memory in Vulkan, you use a "split-level" approach.
+You don't just call one function to get a texture; you call one set of function
+to create the *Logical Object* and another set to handle the *Physical Memory*.
+
+Here is a step-by-step workflow and the specific functions involved.
+
+=== Phase 1: Creating the Resource (The "Empty Shell")
+
+First, you create the handle that defines _what_ the resource is (size, format,
+usage). At this stage, the resource has no memory backed to it.
+
+* For Buffers (Vertex/Index Data): `vkCreateBuffer`
+
+* For Images (Textures/Depth buffers): `vkCreateImage`
+
+=== Phase 2: Determining Requirements
+
+Since different GPUs have different rules (e.g., an NVIDIA GPU might need a
+texture to be aligned to 1024 bytes), while an AMD one needs 256), you must ask
+the driver for the "bill."
+
+* *Function*: `vkGetBufferMemoryRequirements` or `vkGetImageMemoryRequirements`
+
+* *Result*: A `vkMemoryRequirements` struct telling you:
+
+ - *Size*: Total bytes needed (often larger than the raw data due to
+ padding).
+
+ - *Alignment*: The "start address" requirement (e.g., must be a multiple of
+ 64).
+
+ - *MemoryTypeBits*: Which "heaps" on the GPU are compatible with this
+ resource.
+
+=== Phase 3: Allocating the Block (The "Land Purchase")
+
+As your text mentions, you don't want to do this for every single item. You
+allocate a large chunk of memory.
+
+* *function*: `vkAllocateMemory`
+
+* *Input*: `vkMemoryAllocateInfo` (specifying the size -- e.g., 256 MiB) -- and
+ the memory type index).
+
+* *Limit*: Remember, you can only call this ~4096 times total in many drivers,
+ so use it sparingly
+
+=== Phase 4: Sub-allocation (The "Slicing")
+
+This is a manual step. You take the offset (the starting byte) within your 256
+MiB block where you want this specific buffer or image to live.
+
+* *Function*: `vkBindBufferMemory` or `vkBindImageMemory`
+
+* *Argument*: You provide the *Resource*, the *Memory Block*, and the *Offset*.
+
+[cols="1,2,2", options="header"]
+|===
+| Step | Function Call | Purpose
+
+| *1* | `vkCreateBuffer(...)`
+| Create a 1 MB Vertex Buffer object (the "Logical" handle).
+
+| *2* | `vkGetBufferMemoryRequirements(...)`
+| Query the GPU for the "bill": Actual size needed plus alignment padding.
+
+| *3* | `vkAllocateMemory(...)`
+| Allocate a **256 MiB** giant block of physical VRAM from the OS.
+
+| *4* | `vkBindBufferMemory(buffer, block, 0)`
+| Link the first buffer to the start (offset 0) of that 256 MiB block.
+
+| *5* | `vkBindBufferMemory(nextBuf, block, 1048576)`
+| Link the next buffer at the next available aligned offset in the same block.
+|===
+
+== Over-commitment
+
+What happens when you exceed the maximum amount of physical video memroy?
+
+* It depends on the driver
+ ** Allocation may fail (VK_ERROR_OUT_OF_DEVICE_MEMORY).
+ ** Allocation may succeed (VK_SUCCESS).
+ *** Some blocks are silently migrated to system memory
+
+* Blocks may be migrated to sytem memory anyway.
+ ** You are not alone - other applications can use video memroy.
+ ** Using blocks migrated to system memory on GPU degrades performace
+
+* Don't use 100% of the heap
+ ** Leave room fro implicit resources
+ ** (e.g. 20% of DEVICE_LOCAL, 33% of DEVICE_LOCAL + HOST_VISIBLE).
+
+== Mapping
+
+In Vulkan, "mapping" is the process of obtaining a CPU-accessible pointer to a
+`VkDeviceMemory` object. This allows you to copy data from your application
+(like vertex data or textures) directly into GPU-accessible memory
+
+=== Persistant Mapping Example
+
+In modern VUlkan, it is common practice to map memroy once after allocation and
+keep it mapped until the memory is freed, rather than mapping and unmapping
+every frame.
+
+[source, c]
+----
+// 1. Allocate the memory (must have VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
+VkDeviceMemory deviceMemory;
+vkAllocateMemory(device, &allocInfo, NULL, &deviceMemory);
+
+// 2. Map the memory
+void* pData;
+VkResult result = vkMapMemory(
+ device,
+ deviceMemory,
+ 0, // Offset
+ VK_WHOLE_SIZE, // Size (or specific range)
+ 0, // Flags (reserved for future use)
+ &pData // Pointer to the mapped range
+);
+
+if (result == VK_SUCCESS) {
+ // 3. You can now use pData like a regular C pointer
+ // This is "Persistent Mapping" — pData remains valid until unmapped
+ memcpy(pData, myVertexData, dataSize);
+
+ // 4. Handle Coherency
+ // If memory is NOT VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, you must flush:
+ VkMappedMemoryRange range = {
+ .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+ .memory = deviceMemory,
+ .offset = 0,
+ .size = VK_WHOLE_SIZE
+ };
+ vkFlushMappedMemoryRanges(device, 1, &range);
+}
+
+// ... Use the memory in Command Buffers ...
+
+// 5. Cleanup (usually at application shutdown)
+vkUnmapMemory(device, deviceMemory);
+vkFreeMemory(device, deviceMemory, NULL);
+----
+
+=== Things to keep in mind about mapping
+
+[source, c]
+----
+void * ptr;
+----
+
+* No more versioning of resources by the API.
+
+* Having entire memory block persistently mapped is generally OK.
+ ** You don't need to unmap before using on GPU.
+
+* Exceptions
+ ** AMD, Windows version < 10: Blocks of DEVICE_LOCAL + HOST_VISIBLE memory
+ that stay mapped for the time of any call to Submit to Present are
+ migrated to system memory
+
+ ** Keeping many large memory blocks mapped may impact stability or
+ performace of debugging tools.
+
+== Transfer
+
+Another way of getting data to your ques is using transfer commands. You can use
+any que that supports the transfer bit. There is a speical bit that you'll find
+on dedicated GPUs that only supports the transfer bit, and that is the transfer
+queue. It is the most underutilized que in vulkan, very few people use it, but
+it is very powerfull. You can use this to defragment you memory.
+
+* Transfer queue is designed for efficent transfer via PCIe.
+ ** Heavily underutilized queue.
+ ** Use it in parallel with 3D rendering, even asynchronously to rendering
+ frames.
+ *** Good for texture streaming
+
+ ** Fastest way to copy across the PCIe bus.
+ ** Does not use any compute or graphics hardware.
+ ** Do your transfers long before the data is needed no graphics queue.
+
+* Transfer queue can be used for defragmenting
+ ** Copy a resource to a new address asynchronously to rendering.
+ ** When done update next frames descriptor.
+
+* Some hardware supports more than one queue.
+ ** e.g. AMD RX 580 has 2 transfer queues.
+
+ ** Can use one queue to defrag while teh other one updates frame resoureces.
+ (constant buffer, etc)
+
+* GPU to (the same) GPU copies are much faster on graphics queue.
+ ** Use it if graphics queue needs to wait for transfer result anyway
+
+* Be careful with granualarity
+ ** Always respect queue granualarly for copies otherwise you may have
+ corruption, crach, or even deadlock.
+
+ ** Don't need to worry when doing full sub resoure copies.
+
+== Defragmenting
+
+Defragmenting with the transfer queue is essentially a "background move"
+operation. Since Vulkan memory is often managed by the application in large
+`VkDeviceMemory` blocks, holes (fragmentation) appear when you free smaller
+sub-allocations.
+
+To defragment, you move a live resource from its fragmented location to a new,
+tightly packed location using a *Transfer Queue* so that the *Graphics Queue*
+can keep rendering the current frame uninterupted.
+
+=== The Defragmentation Workflow
+
+1. *Allocate New Space*: Find a contiguous gap in your memory heap or allocate a
+ new `VkDeviceMemory` block.
+
+2. *Record Transfer Command*: On a command buffer intended for the *Transfer*
+ *Queue*, record `vkCmdCopyBuffer` or `vkCmdCopyImage`.
+
+3. *Asynchronous Execution*: Sumbit this to the transfer queue. The GPU handles
+ the copy via the DMA (Direct Memory Access) engine without touching the
+ shaders or ROPs.
+
+4. *Synchronization and Handover*: Use a *Semaphore* or *Fence* to signal when
+ the copy is done.
+
+5. *Update Descriptors*: In the next frame's command buffer recording, point
+ your Descriptor Sets to the _new_ offset/buffer.
+
+5. *Free Old Space*: Once the GPU is no longer using the old memory location,
+ mark it as free.
+
+Direct Memory Access (DMA)::
+ A feature in the computer systems that allows hardware components to access
+ the main system memory independently of the CPU, improving data transfer
+ efficiently and reducing CPU workload. This enables faster data processing
+ and enhances multitasking capabilities in various applications.
+
+=== C Example: Background Buffer Move
+
+This example demonstrates moving a vertex bufer to a new location to close a
+memory gap.
+
+[source, c]
+----
+// Assume we have:
+// 1. A transfer queue (transferQueue) and a command pool (transferPool).
+// 2. The 'oldBuffer' currently used by the graphics queue.
+// 3. A 'newBuffer' allocated in a packed memory region.
+
+VkCommandBufferAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandPool = transferPool,
+ .commandBufferCount = 1
+};
+
+VkCommandBuffer transferCmd;
+vkAllocateCommandBuffers(device, &allocInfo, &transferCmd);
+
+// Begin recording
+VkCommandBufferBeginInfo beginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
+vkBeginCommandBuffer(transferCmd, &beginInfo);
+
+// The actual move command
+VkBufferCopy copyRegion = {
+ .srcOffset = 0,
+ .dstOffset = 0,
+ .size = bufferSize
+};
+
+vkCmdCopyBuffer(transferCmd, oldBuffer, newBuffer, 1, ©Region);
+
+vkEndCommandBuffer(transferCmd);
+
+// Submit to the transfer queue
+VkSubmitInfo submitInfo = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &transferCmd,
+ // Use a fence or semaphore so the Graphics Queue knows when to switch pointers
+ .pSignalSemaphores = ©FinishedSemaphore
+};
+
+vkQueueSubmit(transferQueue, 1, &submitInfo, VK_NULL_HANDLE);
+----
+
+=== Important Considerations
+
+Ownership Transfer::
+ If your transfer Queue and Graphics Queue belong to different *Queue*
+ *Families*, you must perform a "Buffer Memory Barrier" to transfer ownership
+ of the resource. If they are in the same family, a simple execution barrier
+ is enough
+
+The "Next Frame" Switch::
+ You cannot delete the old memory immediately. You must wait until the
+ Transfer Queue is finished _and_ any Graphics Queue frames that were still
+ using the old address have retired.
+
+Granularity::
+ As you noted in your text, ensure your copy offsets and size respect
+ `vkQueueFamilyProperties::minImageTransferGranularity`. While usually 1 x 1
+ x 1 for buffers, it matters for partial image copies.
+
+
+== What is "fragmentation"
+
+In Vulkan, you are the memory manager. Unlike higher-level APIs (like OpenGL)
+where the driver handles the "tetris" of memory for you, Vulkan requires you to
+allocate large blocks of `VkDeviceMemory` and sub-allocate small buffers or
+images within them.
+
+Fragementation occurs when the layout of these sub-allocations becomes
+inefficient, leaving gaps that are unstable.
+
+=== 1. External Fragmentation
+
+This is the most common type in Vulkan. It happens when you allocate and free
+objects of different sizes over time.
+
+* *The Problem*: You might have 500MB of total free memory, but it is split into
+ 50 tiny gaps of 10MB each.
+
+* *The Result*: If you try to allocate a new 50MB texture, the allocation will
+ fail with `VK_ERROR_OUT_OF_DEVICE_MEMORY`, even though you technically have
+ enough total spce. The memory must be _contiguous_.
+
+contiguous::
+ "touching" or "connected in an unbroken sequence". If memory is contiguous,
+ it exists as one single, solid block in your RAM or VRAM without any gaps,
+ other data, or "jumps" between the start and the end.
+
+=== 2. Internal Frangmentation
+
+This occurs due to *Alignment Requiremnts*. Vulkan hardware often requires that
+a buffer starts at an address that is a multiple of a certain value (e.g. 256
+bytes).
+
+The Problem::
+ If you place a 64-byte uniform buffer at an offset of 0, the next "valid"
+ spot for another buffer might not be a 64, but at 256.
+
+The Result::
+ You "waste" 192 bytes of padding. Over thousands of small allocations, this
+ adds up to significant wasted VRAM.
+
+=== How to Combat Fragmentation
+
+To keep you rmemory "healthy", you generally use three strategies:
+
+[%header, cols="1,2"]
+|===
+| Strategy | Description
+// -------------------------------------------------------------------------
+| Fixed-size Buckets | Allocate memory in fixed blocks (e.g., all 2k textures go
+ in one heap). This prevents small holes.
+
+| TLSF Allocator | Use a "Two-Level Segregated Fit" algorithm (like the
+ Vulkan Memory Allocator/VMA library) to manage
+ sub-allocations efficiently.
+
+| Defragmentation | As discussed earlier, use the *Transfer Queue* to "slide"
+ active allocation next to each other, merging small gaps
+ into one large usable block.
+|===
+
+=== Why the Transfer Queue is the "Janitor"
+
+As your documentation notes, the Transfer Queue is perfect for defragmenting
+because it can move these blocks in the background.
+
+NOTE: Think of it like moving furniture in a room while a party (Rendering) is
+ happening in the center. As long as you don't move the chair someone is
+ currently sitting on (a buffer being used by a shader), the party
+ continues uninterrupted.
+
+== Links
+
+* https://github.com/KhronosGroup/Vulkan-Guide/tree/main/chapters
+* https://www.youtube.com/watch?v=rXSdDE7NWmA[video memory alloaction]
+* https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/memory_allocation.adoc[github document | memory allocation]
+* https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
+* https://www.khronos.org/assets/uploads/developers/library/2018-vulkan-devday/03-Memory.pdf
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Memory</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<style>
+pre.pygments .hll { background-color: #49483e }
+pre.pygments { background: #272822; color: #F8F8F2 }
+pre.pygments .tok-c { color: #959077 } /* Comment */
+pre.pygments .tok-err { color: #ED007E; background-color: #1E0010 } /* Error */
+pre.pygments .tok-esc { color: #F8F8F2 } /* Escape */
+pre.pygments .tok-g { color: #F8F8F2 } /* Generic */
+pre.pygments .tok-k { color: #66D9EF } /* Keyword */
+pre.pygments .tok-l { color: #AE81FF } /* Literal */
+pre.pygments .tok-n { color: #F8F8F2 } /* Name */
+pre.pygments .tok-o { color: #FF4689 } /* Operator */
+pre.pygments .tok-x { color: #F8F8F2 } /* Other */
+pre.pygments .tok-p { color: #F8F8F2 } /* Punctuation */
+pre.pygments .tok-ch { color: #959077 } /* Comment.Hashbang */
+pre.pygments .tok-cm { color: #959077 } /* Comment.Multiline */
+pre.pygments .tok-cp { color: #959077 } /* Comment.Preproc */
+pre.pygments .tok-cpf { color: #959077 } /* Comment.PreprocFile */
+pre.pygments .tok-c1 { color: #959077 } /* Comment.Single */
+pre.pygments .tok-cs { color: #959077 } /* Comment.Special */
+pre.pygments .tok-gd { color: #FF4689 } /* Generic.Deleted */
+pre.pygments .tok-ge { color: #F8F8F2; font-style: italic } /* Generic.Emph */
+pre.pygments .tok-ges { color: #F8F8F2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+pre.pygments .tok-gr { color: #F8F8F2 } /* Generic.Error */
+pre.pygments .tok-gh { color: #F8F8F2 } /* Generic.Heading */
+pre.pygments .tok-gi { color: #A6E22E } /* Generic.Inserted */
+pre.pygments .tok-go { color: #66D9EF } /* Generic.Output */
+pre.pygments .tok-gp { color: #FF4689; font-weight: bold } /* Generic.Prompt */
+pre.pygments .tok-gs { color: #F8F8F2; font-weight: bold } /* Generic.Strong */
+pre.pygments .tok-gu { color: #959077 } /* Generic.Subheading */
+pre.pygments .tok-gt { color: #F8F8F2 } /* Generic.Traceback */
+pre.pygments .tok-kc { color: #66D9EF } /* Keyword.Constant */
+pre.pygments .tok-kd { color: #66D9EF } /* Keyword.Declaration */
+pre.pygments .tok-kn { color: #FF4689 } /* Keyword.Namespace */
+pre.pygments .tok-kp { color: #66D9EF } /* Keyword.Pseudo */
+pre.pygments .tok-kr { color: #66D9EF } /* Keyword.Reserved */
+pre.pygments .tok-kt { color: #66D9EF } /* Keyword.Type */
+pre.pygments .tok-ld { color: #E6DB74 } /* Literal.Date */
+pre.pygments .tok-m { color: #AE81FF } /* Literal.Number */
+pre.pygments .tok-s { color: #E6DB74 } /* Literal.String */
+pre.pygments .tok-na { color: #A6E22E } /* Name.Attribute */
+pre.pygments .tok-nb { color: #F8F8F2 } /* Name.Builtin */
+pre.pygments .tok-nc { color: #A6E22E } /* Name.Class */
+pre.pygments .tok-no { color: #66D9EF } /* Name.Constant */
+pre.pygments .tok-nd { color: #A6E22E } /* Name.Decorator */
+pre.pygments .tok-ni { color: #F8F8F2 } /* Name.Entity */
+pre.pygments .tok-ne { color: #A6E22E } /* Name.Exception */
+pre.pygments .tok-nf { color: #A6E22E } /* Name.Function */
+pre.pygments .tok-nl { color: #F8F8F2 } /* Name.Label */
+pre.pygments .tok-nn { color: #F8F8F2 } /* Name.Namespace */
+pre.pygments .tok-nx { color: #A6E22E } /* Name.Other */
+pre.pygments .tok-py { color: #F8F8F2 } /* Name.Property */
+pre.pygments .tok-nt { color: #FF4689 } /* Name.Tag */
+pre.pygments .tok-nv { color: #F8F8F2 } /* Name.Variable */
+pre.pygments .tok-ow { color: #FF4689 } /* Operator.Word */
+pre.pygments .tok-pm { color: #F8F8F2 } /* Punctuation.Marker */
+pre.pygments .tok-w { color: #F8F8F2 } /* Text.Whitespace */
+pre.pygments .tok-mb { color: #AE81FF } /* Literal.Number.Bin */
+pre.pygments .tok-mf { color: #AE81FF } /* Literal.Number.Float */
+pre.pygments .tok-mh { color: #AE81FF } /* Literal.Number.Hex */
+pre.pygments .tok-mi { color: #AE81FF } /* Literal.Number.Integer */
+pre.pygments .tok-mo { color: #AE81FF } /* Literal.Number.Oct */
+pre.pygments .tok-sa { color: #E6DB74 } /* Literal.String.Affix */
+pre.pygments .tok-sb { color: #E6DB74 } /* Literal.String.Backtick */
+pre.pygments .tok-sc { color: #E6DB74 } /* Literal.String.Char */
+pre.pygments .tok-dl { color: #E6DB74 } /* Literal.String.Delimiter */
+pre.pygments .tok-sd { color: #E6DB74 } /* Literal.String.Doc */
+pre.pygments .tok-s2 { color: #E6DB74 } /* Literal.String.Double */
+pre.pygments .tok-se { color: #AE81FF } /* Literal.String.Escape */
+pre.pygments .tok-sh { color: #E6DB74 } /* Literal.String.Heredoc */
+pre.pygments .tok-si { color: #E6DB74 } /* Literal.String.Interpol */
+pre.pygments .tok-sx { color: #E6DB74 } /* Literal.String.Other */
+pre.pygments .tok-sr { color: #E6DB74 } /* Literal.String.Regex */
+pre.pygments .tok-s1 { color: #E6DB74 } /* Literal.String.Single */
+pre.pygments .tok-ss { color: #E6DB74 } /* Literal.String.Symbol */
+pre.pygments .tok-bp { color: #F8F8F2 } /* Name.Builtin.Pseudo */
+pre.pygments .tok-fm { color: #A6E22E } /* Name.Function.Magic */
+pre.pygments .tok-vc { color: #F8F8F2 } /* Name.Variable.Class */
+pre.pygments .tok-vg { color: #F8F8F2 } /* Name.Variable.Global */
+pre.pygments .tok-vi { color: #F8F8F2 } /* Name.Variable.Instance */
+pre.pygments .tok-vm { color: #F8F8F2 } /* Name.Variable.Magic */
+pre.pygments .tok-il { color: #AE81FF } /* Literal.Number.Integer.Long */
+</style>
+<head>
+<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=DotGothic16"> -->
+<!-- <link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/A-Larsen/math-notes/refs/heads/main/favicon.png"> -->
+<!-- <link rel="icon" type="image/png" href="N2DE_favicon.png"/> -->
+<!-- <link rel="preconnect" href="https://fonts.googleapis.com"> -->
+<!-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
+<!-- <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap" rel="stylesheet"> -->
+<style>
+
+/*--------------------------------------------------------------*/
+/*use '!important' to override default css used by asciidoctor*/
+/*--------------------------------------------------------------*/
+
+:root {
+ --gruvbox-black: #282828;
+ --gruvbox-black2: #1d2021;
+ --gruvbox-black3: #463f39;
+ --gruvbox-red: #fc7869;
+ --gruvbox-yellow: #fabd2f;
+ --gruvbox-white: #ebdbb2;
+ --gruvbox-white2: #a7a187;
+ --gruvbox-blue: #83a598;
+ --gruvbox-green: #b8bb26;
+ --gruvbox-bg1: #7c6f64;
+ --gruvbox-bg2: #665c54;
+
+}
+
+.title {
+ color: var(--gruvbox-red) !important;
+}
+
+blockquote {
+ color: var(--gruvbox-white) !important;
+}
+
+table {
+ margin: 0 auto !important;
+}
+
+.hdlist1 {
+ font-size: 1.3em;
+}
+
+cite {
+ color: var(--gruvbox-green) !important;
+}
+
+.tableblock {
+ background-color: inherit;
+ color: var(--gruvbox-white);
+ max-width: 95%;
+}
+
+.tableblock th {
+ background-color: var(--gruvbox-black3);
+ color: var(--gruvbox-white);
+ max-width: 97%;
+}
+
+code:not([data-lang]) {
+ background-color: var(--gruvbox-bg2) !important;
+ color: var(--gruvbox-white);
+ text-shadow: none;
+}
+
+.checklist {
+ text-align: left;
+ font-size: .8em;
+}
+
+.checklist li ul { margin-left: 3%;
+}
+
+#content, h1, pre {
+ border-radius: 8px !important;
+}
+
+#header, #content, #footnotes, #footer {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.api-type a:hover {
+ text-decoration: underline;
+}
+
+.api-type, .api-type a {
+ color: var(--gruvbox-green);
+ text-decoration: none;
+}
+
+pre {
+ border-style: solid !important;
+ border-color: white !important;
+ border-width: 2px !important;
+}
+
+h1 {
+ /* font-family: "DotGothic16", sans-serif !important;*/
+
+
+ background-color: var(--gruvbox-white2) !important;
+ color: var(--gruvbox-black) !important;
+}
+
+.admonitionblock > table td.content {
+ color: var(--gruvbox-white) !important;
+}
+
+/*
+html {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+}
+
+
+*/
+/*html {*/
+
+body {
+ /*background: black url("background-image-space.jpeg") no-repeat fixed center;*/
+ /*background-color: black;*/
+ background-color: var(--gruvbox-black);
+ background-attachment: fixed;
+ background-size: cover;
+ overflow-y: scroll;
+ /*
+ margin: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ */
+}
+
+
+/*
+body {
+ overflow-y: scroll;
+ background: rgba(0,0,0,0);
+ height: 100%;
+ margin: 2%;
+}
+*/
+
+#toc {
+ background-color: var(--gruvbox-black2) !important;
+ /*opacity: .9;*/
+}
+
+#toctitle {
+ color: var(--gruvbox-yellow);
+}
+
+a {
+ color: var(--gruvbox-bg1);
+}
+
+#content {
+ color: #ebdbb2;
+ background: var(--gruvbox-black);
+ /*opacity: .9;*/
+}
+
+#header > h1:nth-last-child(2) {
+ border-bottom: none !important;
+ background-color: rgba(0, 0, 0, .1);
+ padding-left: 2%;
+}
+
+.sect1 + .sect1 {
+ border-top: none !important;
+}
+
+.sect1 h2 {
+ background-size: cover;
+ background-attachment: fixed;
+ /*
+ border-bottom: 1px solid var(--gruvbox-white);
+ border-top: 1px solid var(--gruvbox-white);
+ */
+ padding: 10px;
+ text-align: left;
+ color: var(--gruvbox-red) !important;
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-black);
+ text-shadow: 3px 3px 3px var(--gruvbox-black);
+ text-shadow: 5px 5px 5px var(--gruvbox-blue);
+ */
+}
+
+h3, h4, h5 {
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-white);
+ */
+}
+
+h3 {
+ color: var(--gruvbox-red) !important;
+}
+
+h4 {
+ color: var(--gruvbox-yellow) !important;
+}
+
+h5 {
+ color: #83a598 !important;
+}
+
+h2 ~ *:not(h1) {
+ margin-left: .5em;
+}
+
+.pygments {
+ max-width: 97% !important;
+ white-space: pre;
+ overflow-x: scroll !important;
+}
+
+::before {
+ /*makes sure that the programming language that show up on hover is within
+ pre tag*/
+ padding-right: 3%;
+}
+
+@media screen and (max-width: 800px) {
+ .checklist li ul {
+ margin-left: 6% !important;
+ }
+ #toc {
+ padding-left: 10px;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+@media screen and (max-width: 535px) {
+ h3 ~ *:not(h2):not(h3) {
+ margin-left: 0 !important;
+ }
+
+ h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 0 !important;
+ }
+ h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 0 !important;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+h3 ~ *:not(h2):not(h3) {
+ margin-left: 1.5em;
+}
+
+h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 2em;
+}
+h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 2.5em;
+}
+
+
+.sect1 h2 {
+
+ /*
+ background: linear-gradient(
+ to bottom,
+ rgba(251, 189, 47, 0.2), /* yellow */
+ rgba(0, 0, 0, 0.4) /* black */
+ );
+ */
+ /*opacity: .9;*/
+ }
+
+#preamble p{
+ padding-left: 10px;
+ color: var(--gruvbox-white) !important;
+}
+
+#footer {
+ max-width: 62.5em !important;
+ padding-left: 10px !important;
+ text-align: center;
+}
+
+.logo img {
+ display: block !important;
+ margin-left: auto !important;
+ margin-right: auto !important;
+}
+
+#header > h1:first-child {
+ margin-top: .3em !important;
+ padding-top: .2em !important;
+ text-align: center;
+}
+
+footer {
+ max-width: 62.5em;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+
+}
+
+details[open] > summary::before {
+ /* display: none;*/
+ border-top: none;
+ content: "▼";
+ margin-top: -12px;
+}
+
+.example.stemblock {
+ border: solid;
+ font-size: 2vw;
+ max-width: 40%;
+ margin: auto;
+}
+
+.center {
+ max-width: 500px;
+ display: flex;
+ justify-content: center;
+}
+
+.highlight {
+ color: var(--gruvbox-yellow)
+}
+
+.border {
+ padding: 4px;
+ border: 2px solid rgba(255, 0, 0, .3);
+}
+
+.dull {
+ opacity: 40%;
+}
+
+.inline {
+ margin-right: 5%;
+ display: inline-block;
+}
+
+img {
+ margin: auto;
+ width: 100%;
+}
+
+
+</style>
+
+</head>
+
+<script>
+function sendHeight() {
+ const height = document.documentElement.scrollHeight;
+ parent.postMessage({ type: "HEIGHT", height }, "*");
+}
+
+window.addEventListener("load", sendHeight);
+window.addEventListener("resize", sendHeight);
+</script>
+
+
+</head>
+<body class="article">
+<div id="header">
+<h1>Memory</h1>
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_memory_heaps_and_types">Memory Heaps and types</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Each memory type has a flag associated with it. for example <code>HOST_COHERENT</code>,
+<code>HOST_CACHED</code>, <code>HOST_VISIBLE</code>.</p>
+</div>
+<div class="sect2">
+<h3 id="_device_local"><code>DEVICE_LOCAL</code></h3>
+<div class="ulist">
+<ul>
+<li>
+<p>Video memory. Fast access from GPU.</p>
+</li>
+<li>
+<p>No direct acces from CPU - mapping not possible</p>
+</li>
+<li>
+<p>Good for resources written and read frequently by GPU.</p>
+</li>
+<li>
+<p>Good for resources uploaded once (immutable) or infrequently by CPU, read
+frequently by GPU.</p>
+</li>
+<li>
+<p>You need transfer command to get memory here</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_host_visible"><code>HOST_VISIBLE</code></h3>
+<div class="ulist">
+<ul>
+<li>
+<p>System memory. Accessible to CPU - mapping possible.</p>
+</li>
+<li>
+<p>Access from GPU possible but slow.</p>
+</li>
+<li>
+<p>Across PCIe bus, reads caches on GPU.</p>
+</li>
+<li>
+<p>Good for CPU-side (staging) copy of your resources - used as source of
+transfer.</p>
+</li>
+<li>
+<p>Data written by CPU, read once by GPU (e.g. constant buffer) may work.
+(always measure!)</p>
+</li>
+<li>
+<p>Large data read by GPU - place here as last resort.</p>
+</li>
+<li>
+<p>Large data written and read by GPU - shouldn’t ever be here.</p>
+</li>
+<li>
+<p>Hazard: Uncached. Writes may be write combined.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>typical usage for this is setting up your stagging buffers before you send them
+over to device local.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_device_local_host_visible"><code>DEVICE_LOCAL</code> + <code>HOST_VISIBLE</code></h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p>Special pool of video memory</p>
+</li>
+<li>
+<p>Exposed on AMD only. 256 MiB.</p>
+</li>
+<li>
+<p>Fast access from GPU.</p>
+</li>
+<li>
+<p>Direct access by both CPU and GPU.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>You don’t need to do expolicit transfer.</p>
+</li>
+<li>
+<p>Mapping possible.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Good for resources updated frequently by CPU (dynamic), read by GPU.</p>
+</li>
+<li>
+<p>Use as fallback if <code>DEVICE_LOCAL</code> is small and oversubscribed.</p>
+</li>
+<li>
+<p>Hazard: Driver will use this memory tool</p>
+</li>
+<li>
+<p>Hazard: Uncached. Writes may be write-combined.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>A good use for this is buffers that are constantly updating</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_host_visible_host_cached"><code>HOST_VISIBLE</code> + <code>HOST_CACHED</code></h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p>System memory.</p>
+</li>
+<li>
+<p>CPU reads and writes cached (write-back).</p>
+</li>
+<li>
+<p>GPU access though PCIe.</p>
+</li>
+<li>
+<p>GPU reads snoop CPU cache.</p>
+</li>
+<li>
+<p>Good for resources written by GPU, read by CPU.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Result of computations</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Direct access by both CPU and GPU.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>No need to do explicit transfer</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Use for any resources read or accessed randomly on CPU.</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_snooping">Snooping</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In the world of computer architecture, <strong>snooping</strong> isd the process where one
+component (like the GPU) monitors the cache of another component (the CPU) to
+make sure it is seeing the most up-to-date version of the data.</p>
+</div>
+<div class="sect2">
+<h3 id="_the_problem_the_two_versions_conflict">The Problem: The "Two Versions" Conflict</h3>
+<div class="paragraph">
+<p>Caches create a synchonization problem. When the CPU changes a value in its
+<code>HOST_CACHED</code> memory, it doesn’t immediately send that change to the main RAM — it keeps it in its local L1/L2/L3 cache because that’s faster.</p>
+</div>
+<div class="paragraph">
+<p>If the GPU then tries to read that same dat afrom the main RAM. it will see the
+<strong>old, stale version</strong> because the "new" version is still sitting in the CPU’s
+cache.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_how_snooping_works">How "snooping" Works</h3>
+<div class="paragraph">
+<p>When the text says "GPU reads snoop CPU cache," it means the hardware is
+designed to handle this automicatlly:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><strong>The Request</strong>: The GPU send a requrest for a specific piece of data across
+the PCIe bux.</p>
+</li>
+<li>
+<p><strong>The Snoop</strong>: Before the data is pulled from RAM, the system "asks" or
+"snoops" the CPU’s chache: "<em>Hey, do you have a modified version of this</em>
+<em>address?</em>"</p>
+</li>
+<li>
+<p><strong>The Handshake</strong>:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>If teh CPU <strong>does</strong> have a new version, it provides that data directly (or
+flushes it to RAM first).</p>
+</li>
+<li>
+<p>If the CPU <strong>doesn’t</strong>, the GPU proceeds to read from RAM as usual.</p>
+</li>
+</ul>
+</div>
+</li>
+</ol>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_always_measure">"always measure"</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>They are tallking about <strong>Unified Memory Architecture (UMA)</strong> vs <strong>Discrete GPUs</strong>.
+Depending on the user’s hardware, putting "read-once" data (like a constant
+buffer) in <code>HOST_VISIBLE</code> memory might be fast on one computer and a massive
+bottleneck on another.</p>
+</div>
+<div class="paragraph">
+<p>Here is why you need to measure:</p>
+</div>
+<div class="sect2">
+<h3 id="_1_the_pcie_bottleneck_vs_integrated_speed">1. The PCIe Bottleneck vs. Integrated Speed</h3>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">On a Descrete GPU (NVIDIA/AMD card)</dt>
+<dd>
+<p><code>HOST_VISIBLE</code> memory usually lives in your system RAM. For the GPU to
+read it, the data must travel across the PCIe bus. This is like trying
+to drink a milkshake through a very long, thin straw.</p>
+</dd>
+<dt class="hdlist1">On a Integrated GPU (Intel/Apple/Mobile)</dt>
+<dd>
+<p>The CPU and GPU share the exact same physical RAM. In this case,
+<code>HOST_VISIBLE</code> is "local" to both. There is no "straw" — they are both
+sitting at the same table.</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_2_the_read_once_strategy">2. The "Read Once Strategy"</h3>
+<div class="paragraph">
+<p>The text says: "Data written by CPU, read once by GPU…​ may work. (always
+measure!)"</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">The Theory</dt>
+<dd>
+<p>If the GPU only needs to look at a piece of dat one time (like a
+transformation matrix for a single frame), it might be faster to let the GPU
+reach across the PCIe bus and grab it once, rather than spending time
+copying it into "Fast" GPU memory first.</p>
+</dd>
+<dt class="hdlist1">The Reality</dt>
+<dd>
+<p>If the GPU’s "reach-across-the-bus" latency is too high, your frame rate
+will tank. You have to use a profiler (like NVIDIA Nsight or RenderDoc) to
+see which path is actually faster on that specific hardware.</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_3_what_does_measurig_look_like">3. What does "Measurig" look like?</h3>
+<div class="paragraph">
+<p>When a Vulkan developer "measures," they aren’t just looking at the FPS counter.
+They are looking at:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><strong>Frame Time</strong>: Does using <code>HOST_VISIBLE</code> memory add 2ms to my frame?</p>
+</li>
+<li>
+<p><strong>Bus Utilization</strong>: Is the PCIe bus 100% saturated?</p>
+</li>
+<li>
+<p><strong>Cache Hits</strong>: Is the GPU’s internal cache helping hide the slowness of the
+system RAM?</p>
+</li>
+</ol>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_cache_hits_and_cache_misses">Cache Hits and Cache Misses</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In computing, a <strong>cache</strong> is a small, utra-fast storage area located very close to
+the processor (CPU or GPU). Because main memory (RAM) is relatively slow and far
+away, the processor keeps copies of data it recently used — or thinks it will
+use soon — in the cache.</p>
+</div>
+<div class="paragraph">
+<p>Thank of it like this: <strong>Main Memory</strong> is a massive warehouse across town. The
+<strong>Cache</strong> is the small toolbox istting right next to you on your workbench.</p>
+</div>
+<div class="sect2">
+<h3 id="_1_cache_hit">1. Cache Hit</h3>
+<div class="paragraph">
+<p>A <strong>Cache Hit</strong> occurs when the processor looks for a piece of data and finds it
+immediately in the cache.</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">The Result</dt>
+<dd>
+<p>The procoessor can continue working at full speed.</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_2_cache_miss">2. Cache Miss</h3>
+<div class="paragraph">
+<p>A <strong>Cache Miss</strong> occurs when the data the processor needs in <em>not</em> in the cache.
+The processor must then "stall" (wait) while the system fetches that dasta from
+the much slower main memory (RAM).</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">The Result</dt>
+<dd>
+<p>A massive performance penalty. Modern processors can execute hundreds of
+instructions in the time it takes to perform one "fetch" from RAM.</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_3_why_this_matters_for_your_vulkan_mesh">3. Why this matters for your Vulkan Mesh</h3>
+<div class="paragraph">
+<p>When you were reading about <code>HOST_VISIBLE</code> memory, the text mentioned that it is
+often <strong>Uncached</strong></p>
+</div>
+<div class="paragraph">
+<p>If you put your mesh data in uncached memory:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Every single time the GPU needs a vertex, it has to go all the way to the
+"warehouse" (System RAM).</p>
+</li>
+<li>
+<p>It can’t store the vertex in its "toolbox" (GPU Cache) for quick access if it
+needs it again (like for a neighboring triangle).</p>
+</li>
+<li>
+<p>This results in <strong>100% Cache Misses</strong>, which makes your rendering significantly
+slower.</p>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_4_how_to_improve_hit_rates">4. How to improve "Hit Rates"</h3>
+<div class="paragraph">
+<p>Programmers use a concept called "Data Locality" to maximize hits:</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Temporal Locality</dt>
+<dd>
+<p>If you used a piece of data once, you’ll probably use it again soon.</p>
+</dd>
+<dt class="hdlist1">Spatial Locality</dt>
+<dd>
+<p>If you used a piece of data at one address, you’ll probably need the data
+stored right next to it very soon.</p>
+</dd>
+</dl>
+</div>
+<div class="paragraph">
+<p>In Vulkan, this is why we try to keep data in <code>DEVICE_LOCAL</code> memory. It allows
+the GPU to use its internal cache systems effectively that the "toolbox" is
+always full of the right tools before the work begins.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_memory_blocks">Memory blocks</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p>In old API’s the resource owns the memory</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Memory is allocated when creating the resource.</p>
+</li>
+<li>
+<p>No control of where that memory is placed.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>In Vulkan the memory is allocated by itself.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Memory is allocated in blocks.</p>
+</li>
+<li>
+<p>A single block can have many resoureces.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Allows many new optimization techniques (memory alignment)</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Pool similar resourece for tighter packing.</p>
+</li>
+<li>
+<p>Keep object resources together to simpilify streaming of that objects
+required resources.</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_memory_differences_opengl_vs_vulkan">Memory differences OpenGL vs Vulkan</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_1_opengl_implicit_allocation_the_black_box">1. OpenGL: Implicit Allocation (The Black Box)</h3>
+<div class="paragraph">
+<p>When you create a texture or a buffer in OpenGL, the driver does two things at
+once:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>It creates the <strong>object handle</strong> (the metadata like width, height, format).</p>
+</li>
+<li>
+<p>It <strong>automatically allocated</strong> the memory for that object.</p>
+</li>
+</ol>
+</div>
+<div class="sect3">
+<h4 id="_the_downside">The Downside</h4>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Lack of Controll</dt>
+<dd>
+<p>You don’t know exactly <em>where</em> in the VRAM that texture is.</p>
+</dd>
+<dt class="hdlist1">Fragmentation</dt>
+<dd>
+<p>If you create and delete many small textures, the driver’s memory can
+because a "swiss cheese" of holes, leading to crashes even if you
+technically have free space.</p>
+</dd>
+<dt class="hdlist1">Overhead</dt>
+<dd>
+<p>Every <code>glGenTextures</code> might trigger a slow system call to the OS to find
+more memory.</p>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_2_vulkan_explicit_allocation_the_memory_block">2. Vulkan: Explicit Allocation (The Memory Block)</h3>
+<div class="paragraph">
+<p>In Vulkan, objects and memory are completely decoupled.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>You create a "Resourece" (e.g., a <code>VkImage</code>). This is just a "logical"
+object; it has no memory and cannot store data yet.</p>
+</li>
+<li>
+<p>You ask the device how much memory that object needs
+(`vkGetImageMemoryRequirements`A.)</p>
+</li>
+<li>
+<p>You allocate a large block of raw memory (<code>vkDeviceMemory</code>)</p>
+</li>
+<li>
+<p>You bind the resource to a specific spot (offset) inside that block.</p>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_3_key_differences_and_optimization_techniques">3. key differences and Optimization Techniques</h3>
+<div class="sect3">
+<h4 id="_a_sub_allocation_tighter_packing">A. Sub-Allocation (Tighter Packing)</h4>
+<div class="paragraph">
+<p>Instead of asking the GPU for 100 small chunks of memory (which is very slow),
+you ask for <strong>one giant 256MB block</strong>. You then "slice" that block up yourself.</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Analogy</dt>
+<dd>
+<p>Buying a giant loaf of bread and slicing it yourself rather then buying 100
+individual slices. It’s faster and results in less "packaging" (overhead).</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_b_memory_alignment">B. Memory Alignment</h4>
+<div class="paragraph">
+<p>GPUs require data to start at specific intervals (e.g. every 256 bytes).</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>In <strong>OpenGL</strong>, the driver handles this, often wasting space to stay safe.</p>
+</li>
+<li>
+<p>In <strong>Vulkan</strong>, you see the allignment requirements. YOu can pack a small buffer
+immediately after a large one to ensure there is zero wasted space, as long as
+you respect the alilgnment the GPU asked for.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_c_data_streaming_and_locality">C. Data Streaming and Locality</h4>
+<div class="paragraph">
+<p>Because you control the blocks, you can put all the resources for a specific
+"Game Level" or "Character" in the <strong>same physical block of memory</strong>.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>When the character enters the scene, you can "stream" (upload) that entire
+block to the GPU in one go.</p>
+</li>
+<li>
+<p>This keeps related data close together, which significantly improves your
+<strong>Cache Hit</strong> rate (as we discussed earlier).</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_memory_alignment_in_c">Memory Alignment in C</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In C, <strong>memory alignment</strong> is teh way the compiler arranges data in RAM to ensure
+the CPU can read it efficiently. CPUs generally don’t read memory byte-by-byte;
+they read in larger "chunks" (usually 4 or 8 bytes).</p>
+</div>
+<div class="paragraph">
+<p>If a variable is "misaligned" (straddling the boundary between two chunks), the
+CPU has to perform two reads and some bit-shifting math to get the data, which
+slows everything down.</p>
+</div>
+<div class="sect2">
+<h3 id="_1_the_power_of_two_rule">1. The Power of Two Rule</h3>
+<div class="paragraph">
+<p>Every data type has an alignment requirement. Usually, a type must start at a
+memory address that is a multiple of its size:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>char</code> (1 byte): Can start at any address.</p>
+</li>
+<li>
+<p><code>short</code> (2 bytes): Must start on an even address (multiple of 2).</p>
+</li>
+<li>
+<p><code>int</code> / <code>float</code> (4 bytes): Must start on an address divisible by 4.</p>
+</li>
+<li>
+<p><code>double</code> / <code>long long</code> (8 bytes): Must start on an address divisibel by 8.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_2_padding_the_ghost_bytes">2. Padding: The "Ghost" Bytes</h3>
+<div class="paragraph">
+<p>To satisfy these requirements, teh compiler inserts "padding" — empty, wasted
+bytes — between struct members.</p>
+</div>
+<div class="paragraph">
+<p>Look at this struct:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-k">struct</span><span class="tok-w"> </span><span class="tok-nc">Example</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-kt">char</span><span class="tok-w"> </span><span class="tok-n">a</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-cm">/* 1 byte */</span>
+<span class="tok-w"> </span><span class="tok-cm">/* 3 bytes of hidden padding inserted here */</span>
+<span class="tok-w"> </span><span class="tok-kt">int</span><span class="tok-w"> </span><span class="tok-n">b</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-cm">/* 4 bytes */</span>
+<span class="tok-w"> </span><span class="tok-kt">char</span><span class="tok-w"> </span><span class="tok-n">c</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-cm">/* 1 byte */</span>
+<span class="tok-w"> </span><span class="tok-cm">/* 3 bytes of hidden padding inserted here */</span>
+<span class="tok-p">};</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Even thought the data only totals <strong>6 bytes</strong>, <code>sizeof(struct Example)</code> will
+return <strong>12 bytes</strong>.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Why the padding at the end?</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>The compiler adds teh padding at the end of a struct so that if you create an
+<strong>array</strong> of that struct, the second element will also be correctly aligned.
+Without that final padding, the <code>int b</code> in the second element of an array would
+start at an odd address and crash or slow down the system.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Optimization: The "Big to small" Strategy</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>You can minimize wasted space by ordering your struct members from <strong>largest</strong>
+<strong>alignment to smallest</strong>.</p>
+</div>
+<div class="paragraph">
+<p>Bad Packing (12 bytes):</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-k">struct</span><span class="tok-w"> </span><span class="tok-nc">Bad</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-kt">char</span><span class="tok-w"> </span><span class="tok-n">a</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-c1">// 1</span>
+<span class="tok-w"> </span><span class="tok-c1">// 3 padding</span>
+<span class="tok-w"> </span><span class="tok-kt">int</span><span class="tok-w"> </span><span class="tok-n">b</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-c1">// 4</span>
+<span class="tok-w"> </span><span class="tok-kt">char</span><span class="tok-w"> </span><span class="tok-n">c</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-c1">// 1</span>
+<span class="tok-w"> </span><span class="tok-c1">// 3 padding</span>
+<span class="tok-p">};</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Good Packing (8 bytes):</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-k">struct</span><span class="tok-w"> </span><span class="tok-nc">Good</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-kt">int</span><span class="tok-w"> </span><span class="tok-n">b</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-c1">// 4</span>
+<span class="tok-w"> </span><span class="tok-kt">char</span><span class="tok-w"> </span><span class="tok-n">a</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-c1">// 1</span>
+<span class="tok-w"> </span><span class="tok-kt">char</span><span class="tok-w"> </span><span class="tok-n">c</span><span class="tok-p">;</span><span class="tok-w"> </span><span class="tok-c1">// 1</span>
+<span class="tok-w"> </span><span class="tok-c1">// 2 padding at the end to make the total 8 (multiple of 4)</span>
+<span class="tok-p">};</span></code></pre>
+</div>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>WHy this is CRITICAL for Vulkan</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>VUlkan is extremely strit about alignment. WHen you send a C stuct (like your
+UniformBufferObject) to a shader:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><strong>GLSL (shaders)</strong> follows a standard <code>std140</code> or <code>std430</code>.</p>
+</li>
+<li>
+<p><strong>C</strong> follows the rules of your compiler (GCC/MSVC)</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>If your C struct alignment doesn’t perfectly match the Shader’s alignment rules,
+the Shader will look for "Variable B" at address 8, but your C code put it at
+address 4. <strong>Your green cube will flicker, stretch or simply disappear</strong>.</p>
+</div>
+<div class="sect3">
+<h4 id="_a_tip_for_you_project">A Tip for you Project</h4>
+<div class="paragraph">
+<p>In you Vulkan code, you will often see poepl use <code>alignas(16)</code> or specific
+compiler attributes to force C structs to match the 16-byte aligment that
+Vulkan’s "Uniform Buffers" often require.</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_suballocation">Suballocation</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p>Don’t allocate a separate memory block for each resourece.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Small limit on maximum number of allocations (e.g. 4096).</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Allocate bigger blocks and sub-allocate ranges for your resuorces.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>256 MiB is good default block size.</p>
+</li>
+<li>
+<p>For heaps ⇐ 1 GiB use smaller blocks. (e.g. heap size / 8).</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Allocations are slow.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Prefer not to allocate or free memory blocks during gameplay to avoid
+hitching.</p>
+</li>
+<li>
+<p>If you need to, you can do it on background thread.</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_allocating_memory_in_vulkan">Allocating Memory in Vulkan</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>To Allocate resources and memory in Vulkan, you use a "split-level" approach.
+You don’t just call one function to get a texture; you call one set of function
+to create the <strong>Logical Object</strong> and another set to handle the <strong>Physical Memory</strong>.</p>
+</div>
+<div class="paragraph">
+<p>Here is a step-by-step workflow and the specific functions involved.</p>
+</div>
+<div class="sect2">
+<h3 id="_phase_1_creating_the_resource_the_empty_shell">Phase 1: Creating the Resource (The "Empty Shell")</h3>
+<div class="paragraph">
+<p>First, you create the handle that defines <em>what</em> the resource is (size, format,
+usage). At this stage, the resource has no memory backed to it.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>For Buffers (Vertex/Index Data): <code>vkCreateBuffer</code></p>
+</li>
+<li>
+<p>For Images (Textures/Depth buffers): <code>vkCreateImage</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_phase_2_determining_requirements">Phase 2: Determining Requirements</h3>
+<div class="paragraph">
+<p>Since different GPUs have different rules (e.g., an NVIDIA GPU might need a
+texture to be aligned to 1024 bytes), while an AMD one needs 256), you must ask
+the driver for the "bill."</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Function</strong>: <code>vkGetBufferMemoryRequirements</code> or <code>vkGetImageMemoryRequirements</code></p>
+</li>
+<li>
+<p><strong>Result</strong>: A <code>vkMemoryRequirements</code> struct telling you:</p>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Size</strong>: Total bytes needed (often larger than the raw data due to
+padding).</p>
+</li>
+<li>
+<p><strong>Alignment</strong>: The "start address" requirement (e.g., must be a multiple of
+64).</p>
+</li>
+<li>
+<p><strong>MemoryTypeBits</strong>: Which "heaps" on the GPU are compatible with this
+resource.</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_phase_3_allocating_the_block_the_land_purchase">Phase 3: Allocating the Block (The "Land Purchase")</h3>
+<div class="paragraph">
+<p>As your text mentions, you don’t want to do this for every single item. You
+allocate a large chunk of memory.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>function</strong>: <code>vkAllocateMemory</code></p>
+</li>
+<li>
+<p><strong>Input</strong>: <code>vkMemoryAllocateInfo</code> (specifying the size — e.g., 256 MiB) — and
+the memory type index).</p>
+</li>
+<li>
+<p><strong>Limit</strong>: Remember, you can only call this ~4096 times total in many drivers,
+so use it sparingly</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_phase_4_sub_allocation_the_slicing">Phase 4: Sub-allocation (The "Slicing")</h3>
+<div class="paragraph">
+<p>This is a manual step. You take the offset (the starting byte) within your 256
+MiB block where you want this specific buffer or image to live.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Function</strong>: <code>vkBindBufferMemory</code> or <code>vkBindImageMemory</code></p>
+</li>
+<li>
+<p><strong>Argument</strong>: You provide the <strong>Resource</strong>, the <strong>Memory Block</strong>, and the <strong>Offset</strong>.</p>
+</li>
+</ul>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 20%;">
+<col style="width: 40%;">
+<col style="width: 40%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Step</th>
+<th class="tableblock halign-left valign-top">Function Call</th>
+<th class="tableblock halign-left valign-top">Purpose</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>1</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>vkCreateBuffer(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Create a 1 MB Vertex Buffer object (the "Logical" handle).</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>2</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>vkGetBufferMemoryRequirements(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Query the GPU for the "bill": Actual size needed plus alignment padding.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>3</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>vkAllocateMemory(…​)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Allocate a <strong>256 MiB</strong> giant block of physical VRAM from the OS.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>4</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>vkBindBufferMemory(buffer, block, 0)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Link the first buffer to the start (offset 0) of that 256 MiB block.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>5</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>vkBindBufferMemory(nextBuf, block, 1048576)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Link the next buffer at the next available aligned offset in the same block.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_over_commitment">Over-commitment</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>What happens when you exceed the maximum amount of physical video memroy?</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>It depends on the driver</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Allocation may fail (VK_ERROR_OUT_OF_DEVICE_MEMORY).</p>
+</li>
+<li>
+<p>Allocation may succeed (VK_SUCCESS).</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Some blocks are silently migrated to system memory</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Blocks may be migrated to sytem memory anyway.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>You are not alone - other applications can use video memroy.</p>
+</li>
+<li>
+<p>Using blocks migrated to system memory on GPU degrades performace</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Don’t use 100% of the heap</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Leave room fro implicit resources</p>
+</li>
+<li>
+<p>(e.g. 20% of DEVICE_LOCAL, 33% of DEVICE_LOCAL + HOST_VISIBLE).</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_mapping">Mapping</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In Vulkan, "mapping" is the process of obtaining a CPU-accessible pointer to a
+<code>VkDeviceMemory</code> object. This allows you to copy data from your application
+(like vertex data or textures) directly into GPU-accessible memory</p>
+</div>
+<div class="sect2">
+<h3 id="_persistant_mapping_example">Persistant Mapping Example</h3>
+<div class="paragraph">
+<p>In modern VUlkan, it is common practice to map memroy once after allocation and
+keep it mapped until the memory is freed, rather than mapping and unmapping
+every frame.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-c1">// 1. Allocate the memory (must have VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)</span>
+<span class="tok-n">VkDeviceMemory</span><span class="tok-w"> </span><span class="tok-n">deviceMemory</span><span class="tok-p">;</span>
+<span class="tok-n">vkAllocateMemory</span><span class="tok-p">(</span><span class="tok-n">device</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">allocInfo</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-nb">NULL</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">deviceMemory</span><span class="tok-p">);</span>
+
+<span class="tok-c1">// 2. Map the memory</span>
+<span class="tok-kt">void</span><span class="tok-o">*</span><span class="tok-w"> </span><span class="tok-n">pData</span><span class="tok-p">;</span>
+<span class="tok-n">VkResult</span><span class="tok-w"> </span><span class="tok-n">result</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">vkMapMemory</span><span class="tok-p">(</span>
+<span class="tok-w"> </span><span class="tok-n">device</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-n">deviceMemory</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-mi">0</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-c1">// Offset</span>
+<span class="tok-w"> </span><span class="tok-n">VK_WHOLE_SIZE</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-c1">// Size (or specific range)</span>
+<span class="tok-w"> </span><span class="tok-mi">0</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-c1">// Flags (reserved for future use)</span>
+<span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">pData</span><span class="tok-w"> </span><span class="tok-c1">// Pointer to the mapped range</span>
+<span class="tok-p">);</span>
+
+<span class="tok-k">if</span><span class="tok-w"> </span><span class="tok-p">(</span><span class="tok-n">result</span><span class="tok-w"> </span><span class="tok-o">==</span><span class="tok-w"> </span><span class="tok-n">VK_SUCCESS</span><span class="tok-p">)</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-c1">// 3. You can now use pData like a regular C pointer</span>
+<span class="tok-w"> </span><span class="tok-c1">// This is "Persistent Mapping" — pData remains valid until unmapped</span>
+<span class="tok-w"> </span><span class="tok-n">memcpy</span><span class="tok-p">(</span><span class="tok-n">pData</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-n">myVertexData</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-n">dataSize</span><span class="tok-p">);</span>
+
+<span class="tok-w"> </span><span class="tok-c1">// 4. Handle Coherency</span>
+<span class="tok-w"> </span><span class="tok-c1">// If memory is NOT VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, you must flush:</span>
+<span class="tok-w"> </span><span class="tok-n">VkMappedMemoryRange</span><span class="tok-w"> </span><span class="tok-n">range</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">sType</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">memory</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">deviceMemory</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">offset</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">0</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">size</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_WHOLE_SIZE</span>
+<span class="tok-w"> </span><span class="tok-p">};</span>
+<span class="tok-w"> </span><span class="tok-n">vkFlushMappedMemoryRanges</span><span class="tok-p">(</span><span class="tok-n">device</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-mi">1</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">range</span><span class="tok-p">);</span>
+<span class="tok-p">}</span>
+
+<span class="tok-c1">// ... Use the memory in Command Buffers ...</span>
+
+<span class="tok-c1">// 5. Cleanup (usually at application shutdown)</span>
+<span class="tok-n">vkUnmapMemory</span><span class="tok-p">(</span><span class="tok-n">device</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-n">deviceMemory</span><span class="tok-p">);</span>
+<span class="tok-n">vkFreeMemory</span><span class="tok-p">(</span><span class="tok-n">device</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-n">deviceMemory</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-nb">NULL</span><span class="tok-p">);</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_things_to_keep_in_mind_about_mapping">Things to keep in mind about mapping</h3>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-kt">void</span><span class="tok-w"> </span><span class="tok-o">*</span><span class="tok-w"> </span><span class="tok-n">ptr</span><span class="tok-p">;</span></code></pre>
+</div>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>No more versioning of resources by the API.</p>
+</li>
+<li>
+<p>Having entire memory block persistently mapped is generally OK.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>You don’t need to unmap before using on GPU.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Exceptions</p>
+<div class="ulist">
+<ul>
+<li>
+<p>AMD, Windows version < 10: Blocks of DEVICE_LOCAL + HOST_VISIBLE memory
+that stay mapped for the time of any call to Submit to Present are
+migrated to system memory</p>
+</li>
+<li>
+<p>Keeping many large memory blocks mapped may impact stability or
+performace of debugging tools.</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_transfer">Transfer</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Another way of getting data to your ques is using transfer commands. You can use
+any que that supports the transfer bit. There is a speical bit that you’ll find
+on dedicated GPUs that only supports the transfer bit, and that is the transfer
+queue. It is the most underutilized que in vulkan, very few people use it, but
+it is very powerfull. You can use this to defragment you memory.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Transfer queue is designed for efficent transfer via PCIe.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Heavily underutilized queue.</p>
+</li>
+<li>
+<p>Use it in parallel with 3D rendering, even asynchronously to rendering
+frames.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Good for texture streaming</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Fastest way to copy across the PCIe bus.</p>
+</li>
+<li>
+<p>Does not use any compute or graphics hardware.</p>
+</li>
+<li>
+<p>Do your transfers long before the data is needed no graphics queue.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Transfer queue can be used for defragmenting</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Copy a resource to a new address asynchronously to rendering.</p>
+</li>
+<li>
+<p>When done update next frames descriptor.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Some hardware supports more than one queue.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>e.g. AMD RX 580 has 2 transfer queues.</p>
+</li>
+<li>
+<p>Can use one queue to defrag while teh other one updates frame resoureces.
+(constant buffer, etc)</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>GPU to (the same) GPU copies are much faster on graphics queue.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Use it if graphics queue needs to wait for transfer result anyway</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Be careful with granualarity</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Always respect queue granualarly for copies otherwise you may have
+corruption, crach, or even deadlock.</p>
+</li>
+<li>
+<p>Don’t need to worry when doing full sub resoure copies.</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_defragmenting">Defragmenting</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Defragmenting with the transfer queue is essentially a "background move"
+operation. Since Vulkan memory is often managed by the application in large
+<code>VkDeviceMemory</code> blocks, holes (fragmentation) appear when you free smaller
+sub-allocations.</p>
+</div>
+<div class="paragraph">
+<p>To defragment, you move a live resource from its fragmented location to a new,
+tightly packed location using a <strong>Transfer Queue</strong> so that the <strong>Graphics Queue</strong>
+can keep rendering the current frame uninterupted.</p>
+</div>
+<div class="sect2">
+<h3 id="_the_defragmentation_workflow">The Defragmentation Workflow</h3>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><strong>Allocate New Space</strong>: Find a contiguous gap in your memory heap or allocate a
+new <code>VkDeviceMemory</code> block.</p>
+</li>
+<li>
+<p><strong>Record Transfer Command</strong>: On a command buffer intended for the <strong>Transfer</strong>
+<strong>Queue</strong>, record <code>vkCmdCopyBuffer</code> or <code>vkCmdCopyImage</code>.</p>
+</li>
+<li>
+<p><strong>Asynchronous Execution</strong>: Sumbit this to the transfer queue. The GPU handles
+the copy via the DMA (Direct Memory Access) engine without touching the
+shaders or ROPs.</p>
+</li>
+<li>
+<p><strong>Synchronization and Handover</strong>: Use a <strong>Semaphore</strong> or <strong>Fence</strong> to signal when
+the copy is done.</p>
+</li>
+<li>
+<p><strong>Update Descriptors</strong>: In the next frame’s command buffer recording, point
+your Descriptor Sets to the <em>new</em> offset/buffer.</p>
+</li>
+<li>
+<p><strong>Free Old Space</strong>: Once the GPU is no longer using the old memory location,
+mark it as free.</p>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Direct Memory Access (DMA)</dt>
+<dd>
+<p>A feature in the computer systems that allows hardware components to access
+the main system memory independently of the CPU, improving data transfer
+efficiently and reducing CPU workload. This enables faster data processing
+and enhances multitasking capabilities in various applications.</p>
+</dd>
+</dl>
+</div>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_c_example_background_buffer_move">C Example: Background Buffer Move</h3>
+<div class="paragraph">
+<p>This example demonstrates moving a vertex bufer to a new location to close a
+memory gap.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-c1">// Assume we have:</span>
+<span class="tok-c1">// 1. A transfer queue (transferQueue) and a command pool (transferPool).</span>
+<span class="tok-c1">// 2. The 'oldBuffer' currently used by the graphics queue.</span>
+<span class="tok-c1">// 3. A 'newBuffer' allocated in a packed memory region.</span>
+
+<span class="tok-n">VkCommandBufferAllocateInfo</span><span class="tok-w"> </span><span class="tok-n">allocInfo</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">sType</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">level</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_COMMAND_BUFFER_LEVEL_PRIMARY</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">commandPool</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">transferPool</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">commandBufferCount</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">1</span>
+<span class="tok-p">};</span>
+
+<span class="tok-n">VkCommandBuffer</span><span class="tok-w"> </span><span class="tok-n">transferCmd</span><span class="tok-p">;</span>
+<span class="tok-n">vkAllocateCommandBuffers</span><span class="tok-p">(</span><span class="tok-n">device</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">allocInfo</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">transferCmd</span><span class="tok-p">);</span>
+
+<span class="tok-c1">// Begin recording</span>
+<span class="tok-n">VkCommandBufferBeginInfo</span><span class="tok-w"> </span><span class="tok-n">beginInfo</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span><span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">sType</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO</span><span class="tok-w"> </span><span class="tok-p">};</span>
+<span class="tok-n">vkBeginCommandBuffer</span><span class="tok-p">(</span><span class="tok-n">transferCmd</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">beginInfo</span><span class="tok-p">);</span>
+
+<span class="tok-c1">// The actual move command</span>
+<span class="tok-n">VkBufferCopy</span><span class="tok-w"> </span><span class="tok-n">copyRegion</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">srcOffset</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">0</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">dstOffset</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">0</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">size</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">bufferSize</span>
+<span class="tok-p">};</span>
+
+<span class="tok-n">vkCmdCopyBuffer</span><span class="tok-p">(</span><span class="tok-n">transferCmd</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-n">oldBuffer</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-n">newBuffer</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-mi">1</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">copyRegion</span><span class="tok-p">);</span>
+
+<span class="tok-n">vkEndCommandBuffer</span><span class="tok-p">(</span><span class="tok-n">transferCmd</span><span class="tok-p">);</span>
+
+<span class="tok-c1">// Submit to the transfer queue</span>
+<span class="tok-n">VkSubmitInfo</span><span class="tok-w"> </span><span class="tok-n">submitInfo</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">sType</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_STRUCTURE_TYPE_SUBMIT_INFO</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">commandBufferCount</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">1</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">pCommandBuffers</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">transferCmd</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-c1">// Use a fence or semaphore so the Graphics Queue knows when to switch pointers</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">pSignalSemaphores</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">copyFinishedSemaphore</span>
+<span class="tok-p">};</span>
+
+<span class="tok-n">vkQueueSubmit</span><span class="tok-p">(</span><span class="tok-n">transferQueue</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-mi">1</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-o">&</span><span class="tok-n">submitInfo</span><span class="tok-p">,</span><span class="tok-w"> </span><span class="tok-n">VK_NULL_HANDLE</span><span class="tok-p">);</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_important_considerations">Important Considerations</h3>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Ownership Transfer</dt>
+<dd>
+<p>If your transfer Queue and Graphics Queue belong to different <strong>Queue</strong>
+<strong>Families</strong>, you must perform a "Buffer Memory Barrier" to transfer ownership
+of the resource. If they are in the same family, a simple execution barrier
+is enough</p>
+</dd>
+<dt class="hdlist1">The "Next Frame" Switch</dt>
+<dd>
+<p>You cannot delete the old memory immediately. You must wait until the
+Transfer Queue is finished <em>and</em> any Graphics Queue frames that were still
+using the old address have retired.</p>
+</dd>
+<dt class="hdlist1">Granularity</dt>
+<dd>
+<p>As you noted in your text, ensure your copy offsets and size respect
+<code>vkQueueFamilyProperties::minImageTransferGranularity</code>. While usually 1 x 1
+x 1 for buffers, it matters for partial image copies.</p>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_what_is_fragmentation">What is "fragmentation"</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In Vulkan, you are the memory manager. Unlike higher-level APIs (like OpenGL)
+where the driver handles the "tetris" of memory for you, Vulkan requires you to
+allocate large blocks of <code>VkDeviceMemory</code> and sub-allocate small buffers or
+images within them.</p>
+</div>
+<div class="paragraph">
+<p>Fragementation occurs when the layout of these sub-allocations becomes
+inefficient, leaving gaps that are unstable.</p>
+</div>
+<div class="sect2">
+<h3 id="_1_external_fragmentation">1. External Fragmentation</h3>
+<div class="paragraph">
+<p>This is the most common type in Vulkan. It happens when you allocate and free
+objects of different sizes over time.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>The Problem</strong>: You might have 500MB of total free memory, but it is split into
+50 tiny gaps of 10MB each.</p>
+</li>
+<li>
+<p><strong>The Result</strong>: If you try to allocate a new 50MB texture, the allocation will
+fail with <code>VK_ERROR_OUT_OF_DEVICE_MEMORY</code>, even though you technically have
+enough total spce. The memory must be <em>contiguous</em>.</p>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">contiguous</dt>
+<dd>
+<p>"touching" or "connected in an unbroken sequence". If memory is contiguous,
+it exists as one single, solid block in your RAM or VRAM without any gaps,
+other data, or "jumps" between the start and the end.</p>
+</dd>
+</dl>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_2_internal_frangmentation">2. Internal Frangmentation</h3>
+<div class="paragraph">
+<p>This occurs due to <strong>Alignment Requiremnts</strong>. Vulkan hardware often requires that
+a buffer starts at an address that is a multiple of a certain value (e.g. 256
+bytes).</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">The Problem</dt>
+<dd>
+<p>If you place a 64-byte uniform buffer at an offset of 0, the next "valid"
+spot for another buffer might not be a 64, but at 256.</p>
+</dd>
+<dt class="hdlist1">The Result</dt>
+<dd>
+<p>You "waste" 192 bytes of padding. Over thousands of small allocations, this
+adds up to significant wasted VRAM.</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_how_to_combat_fragmentation">How to Combat Fragmentation</h3>
+<div class="paragraph">
+<p>To keep you rmemory "healthy", you generally use three strategies:</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 66.6667%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Strategy</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Fixed-size Buckets</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Allocate memory in fixed blocks (e.g., all 2k textures go
+ in one heap). This prevents small holes.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">TLSF Allocator</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Use a "Two-Level Segregated Fit" algorithm (like the
+ Vulkan Memory Allocator/VMA library) to manage
+ sub-allocations efficiently.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Defragmentation</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">As discussed earlier, use the <strong>Transfer Queue</strong> to "slide"
+ active allocation next to each other, merging small gaps
+ into one large usable block.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect2">
+<h3 id="_why_the_transfer_queue_is_the_janitor">Why the Transfer Queue is the "Janitor"</h3>
+<div class="paragraph">
+<p>As your documentation notes, the Transfer Queue is perfect for defragmenting
+because it can move these blocks in the background.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<div class="title">Note</div>
+</td>
+<td class="content">
+Think of it like moving furniture in a room while a party (Rendering) is
+ happening in the center. As long as you don’t move the chair someone is
+ currently sitting on (a buffer being used by a shader), the party
+ continues uninterrupted.
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_links">Links</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p><a href="https://github.com/KhronosGroup/Vulkan-Guide/tree/main/chapters" class="bare">https://github.com/KhronosGroup/Vulkan-Guide/tree/main/chapters</a></p>
+</li>
+<li>
+<p><a href="https://www.youtube.com/watch?v=rXSdDE7NWmA">video memory alloaction</a></p>
+</li>
+<li>
+<p><a href="https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/memory_allocation.adoc">github document | memory allocation</a></p>
+</li>
+<li>
+<p><a href="https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator" class="bare">https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator</a></p>
+</li>
+<li>
+<p><a href="https://www.khronos.org/assets/uploads/developers/library/2018-vulkan-devday/03-Memory.pdf" class="bare">https://www.khronos.org/assets/uploads/developers/library/2018-vulkan-devday/03-Memory.pdf</a></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-23 15:38:15 -0500
+</div>
+</div>
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+ messageStyle: "none",
+ tex2jax: {
+ inlineMath: [["\\(", "\\)"]],
+ displayMath: [["\\[", "\\]"]],
+ ignoreClass: "nostem|nolatexmath"
+ },
+ asciimath2jax: {
+ delimiters: [["\\$", "\\$"]],
+ ignoreClass: "nostem|noasciimath"
+ },
+ TeX: { equationNumbers: { autoNumber: "none" } }
+})
+MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
+ MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
+ data.math.root.display = "block"
+ }
+ return data
+ })
+})
+</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Learning Vulkan in c</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
+<head>
+<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=DotGothic16"> -->
+<!-- <link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/A-Larsen/math-notes/refs/heads/main/favicon.png"> -->
+<!-- <link rel="icon" type="image/png" href="N2DE_favicon.png"/> -->
+<!-- <link rel="preconnect" href="https://fonts.googleapis.com"> -->
+<!-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
+<!-- <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap" rel="stylesheet"> -->
+<style>
+
+/*--------------------------------------------------------------*/
+/*use '!important' to override default css used by asciidoctor*/
+/*--------------------------------------------------------------*/
+
+:root {
+ --gruvbox-black: #282828;
+ --gruvbox-black2: #1d2021;
+ --gruvbox-black3: #463f39;
+ --gruvbox-red: #fc7869;
+ --gruvbox-yellow: #fabd2f;
+ --gruvbox-white: #ebdbb2;
+ --gruvbox-white2: #a7a187;
+ --gruvbox-blue: #83a598;
+ --gruvbox-green: #b8bb26;
+ --gruvbox-bg1: #7c6f64;
+ --gruvbox-bg2: #665c54;
+
+}
+
+.title {
+ color: var(--gruvbox-red) !important;
+}
+
+blockquote {
+ color: var(--gruvbox-white) !important;
+}
+
+table {
+ margin: 0 auto !important;
+}
+
+.hdlist1 {
+ font-size: 1.3em;
+}
+
+cite {
+ color: var(--gruvbox-green) !important;
+}
+
+.tableblock {
+ background-color: inherit;
+ color: var(--gruvbox-white);
+ max-width: 95%;
+}
+
+.tableblock th {
+ background-color: var(--gruvbox-black3);
+ color: var(--gruvbox-white);
+ max-width: 97%;
+}
+
+code:not([data-lang]) {
+ background-color: var(--gruvbox-bg2) !important;
+ color: var(--gruvbox-white);
+ text-shadow: none;
+}
+
+.checklist {
+ text-align: left;
+ font-size: .8em;
+}
+
+.checklist li ul { margin-left: 3%;
+}
+
+#content, h1, pre {
+ border-radius: 8px !important;
+}
+
+#header, #content, #footnotes, #footer {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.api-type a:hover {
+ text-decoration: underline;
+}
+
+.api-type, .api-type a {
+ color: var(--gruvbox-green);
+ text-decoration: none;
+}
+
+pre {
+ border-style: solid !important;
+ border-color: white !important;
+ border-width: 2px !important;
+}
+
+h1 {
+ /* font-family: "DotGothic16", sans-serif !important;*/
+
+
+ background-color: var(--gruvbox-white2) !important;
+ color: var(--gruvbox-black) !important;
+}
+
+.admonitionblock > table td.content {
+ color: var(--gruvbox-white) !important;
+}
+
+/*
+html {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+}
+
+
+*/
+/*html {*/
+
+body {
+ /*background: black url("background-image-space.jpeg") no-repeat fixed center;*/
+ /*background-color: black;*/
+ background-color: var(--gruvbox-black);
+ background-attachment: fixed;
+ background-size: cover;
+ overflow-y: scroll;
+ /*
+ margin: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ */
+}
+
+
+/*
+body {
+ overflow-y: scroll;
+ background: rgba(0,0,0,0);
+ height: 100%;
+ margin: 2%;
+}
+*/
+
+#toc {
+ background-color: var(--gruvbox-black2) !important;
+ /*opacity: .9;*/
+}
+
+#toctitle {
+ color: var(--gruvbox-yellow);
+}
+
+a {
+ color: var(--gruvbox-bg1);
+}
+
+#content {
+ color: #ebdbb2;
+ background: var(--gruvbox-black);
+ /*opacity: .9;*/
+}
+
+#header > h1:nth-last-child(2) {
+ border-bottom: none !important;
+ background-color: rgba(0, 0, 0, .1);
+ padding-left: 2%;
+}
+
+.sect1 + .sect1 {
+ border-top: none !important;
+}
+
+.sect1 h2 {
+ background-size: cover;
+ background-attachment: fixed;
+ /*
+ border-bottom: 1px solid var(--gruvbox-white);
+ border-top: 1px solid var(--gruvbox-white);
+ */
+ padding: 10px;
+ text-align: left;
+ color: var(--gruvbox-red) !important;
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-black);
+ text-shadow: 3px 3px 3px var(--gruvbox-black);
+ text-shadow: 5px 5px 5px var(--gruvbox-blue);
+ */
+}
+
+h3, h4, h5 {
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-white);
+ */
+}
+
+h3 {
+ color: var(--gruvbox-red) !important;
+}
+
+h4 {
+ color: var(--gruvbox-yellow) !important;
+}
+
+h5 {
+ color: #83a598 !important;
+}
+
+h2 ~ *:not(h1) {
+ margin-left: .5em;
+}
+
+.pygments {
+ max-width: 97% !important;
+ white-space: pre;
+ overflow-x: scroll !important;
+}
+
+::before {
+ /*makes sure that the programming language that show up on hover is within
+ pre tag*/
+ padding-right: 3%;
+}
+
+@media screen and (max-width: 800px) {
+ .checklist li ul {
+ margin-left: 6% !important;
+ }
+ #toc {
+ padding-left: 10px;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+@media screen and (max-width: 535px) {
+ h3 ~ *:not(h2):not(h3) {
+ margin-left: 0 !important;
+ }
+
+ h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 0 !important;
+ }
+ h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 0 !important;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+h3 ~ *:not(h2):not(h3) {
+ margin-left: 1.5em;
+}
+
+h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 2em;
+}
+h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 2.5em;
+}
+
+
+.sect1 h2 {
+
+ /*
+ background: linear-gradient(
+ to bottom,
+ rgba(251, 189, 47, 0.2), /* yellow */
+ rgba(0, 0, 0, 0.4) /* black */
+ );
+ */
+ /*opacity: .9;*/
+ }
+
+#preamble p{
+ padding-left: 10px;
+ color: var(--gruvbox-white) !important;
+}
+
+#footer {
+ max-width: 62.5em !important;
+ padding-left: 10px !important;
+ text-align: center;
+}
+
+.logo img {
+ display: block !important;
+ margin-left: auto !important;
+ margin-right: auto !important;
+}
+
+#header > h1:first-child {
+ margin-top: .3em !important;
+ padding-top: .2em !important;
+ text-align: center;
+}
+
+footer {
+ max-width: 62.5em;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+
+}
+
+details[open] > summary::before {
+ /* display: none;*/
+ border-top: none;
+ content: "▼";
+ margin-top: -12px;
+}
+
+.example.stemblock {
+ border: solid;
+ font-size: 2vw;
+ max-width: 40%;
+ margin: auto;
+}
+
+.center {
+ max-width: 500px;
+ display: flex;
+ justify-content: center;
+}
+
+.highlight {
+ color: var(--gruvbox-yellow)
+}
+
+.border {
+ padding: 4px;
+ border: 2px solid rgba(255, 0, 0, .3);
+}
+
+.dull {
+ opacity: 40%;
+}
+
+.inline {
+ margin-right: 5%;
+ display: inline-block;
+}
+
+img {
+ margin: auto;
+ width: 100%;
+}
+
+
+</style>
+
+</head>
+
+<script>
+function sendHeight() {
+ const height = document.documentElement.scrollHeight;
+ parent.postMessage({ type: "HEIGHT", height }, "*");
+}
+
+window.addEventListener("load", sendHeight);
+window.addEventListener("resize", sendHeight);
+</script>
+
+
+</head>
+<body class="article">
+<div id="header">
+<h1>Learning Vulkan in c</h1>
+</div>
+<div id="content">
+<div id="preamble">
+<div class="sectionbody">
+<div class="paragraph">
+<p>Some simple example code for vulkan</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_links">Links</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p><a href="https://www.kdab.com/projection-matrices-with-vulkan-part-1/">projection matrices</a></p>
+</li>
+<li>
+<p><a href="https://matthias-research.github.io/pages/tenMinutePhysics/index.html" class="bare">https://matthias-research.github.io/pages/tenMinutePhysics/index.html</a></p>
+</li>
+<li>
+<p><a href="https://youtu.be/bGe-d09Nc_M?si=BG0fisehhE5G8IB2" class="bare">https://youtu.be/bGe-d09Nc_M?si=BG0fisehhE5G8IB2</a></p>
+</li>
+<li>
+<p><a href="https://www.youtube.com/watch?v=bGe-d09Nc_M" class="bare">https://www.youtube.com/watch?v=bGe-d09Nc_M</a></p>
+</li>
+<li>
+<p><a href="https://www.youtube.com/watch?v=P6UKhR0T6cs" class="bare">https://www.youtube.com/watch?v=P6UKhR0T6cs</a></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+ messageStyle: "none",
+ tex2jax: {
+ inlineMath: [["\\(", "\\)"]],
+ displayMath: [["\\[", "\\]"]],
+ ignoreClass: "nostem|nolatexmath"
+ },
+ asciimath2jax: {
+ delimiters: [["\\$", "\\$"]],
+ ignoreClass: "nostem|noasciimath"
+ },
+ TeX: { equationNumbers: { autoNumber: "none" } }
+})
+MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
+ MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
+ data.math.root.display = "block"
+ }
+ return data
+ })
+})
+</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_cube
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+In this context, **“depth” means the per-pixel (or per-sample) distance from the camera used to decide what is in front of what**.
+
+I’ll explain it from the ground up, then tie it directly to **render passes and subpasses**.
+
+---
+
+## 1. What “depth” is (fundamental idea)
+
+**Depth** is a numerical value that represents **how far a fragment is from the camera**.
+
+For every pixel the GPU draws, it computes a depth value:
+
+```
+closer to camera → smaller depth
+farther away → larger depth
+```
+
+This value is stored in a **depth buffer** (also called a **Z buffer**).
+
+---
+
+## 2. Why depth exists at all
+
+Without depth, triangles would be drawn **in submission order**:
+
+```
+draw triangle A
+draw triangle B
+```
+
+If B is behind A but drawn later → it appears in front ❌
+
+Depth fixes this by answering:
+
+> “Is this fragment closer than what was drawn before?”
+
+---
+
+## 3. How depth testing works
+
+For each fragment:
+
+1. Compute its depth (from vertex positions → clip space)
+2. Compare it against the stored depth value
+3. If it passes → draw and update depth
+4. If it fails → discard fragment
+
+Typical comparison:
+
+```c
+VK_COMPARE_OP_LESS
+```
+
+Meaning:
+
+> “Only draw if this fragment is closer.”
+
+---
+
+## 4. Where depth comes from (pipeline view)
+
+### Vertex shader
+
+Outputs:
+
+```glsl
+gl_Position
+```
+
+This contains **clip-space coordinates**.
+
+### After projection
+
+The GPU computes:
+
+```
+depth = z / w
+```
+
+Mapped into:
+
+```
+[0, 1] (Vulkan default)
+```
+
+This is what ends up in the depth buffer.
+
+---
+
+## 5. The depth buffer itself
+
+Depth is stored in a **depth attachment**, just like color is stored in a color attachment.
+
+Common formats:
+
+* `VK_FORMAT_D32_SFLOAT`
+* `VK_FORMAT_D24_UNORM_S8_UINT`
+
+Depth attachments:
+
+* Are not visible on screen
+* Exist only for visibility testing (and sometimes reading later)
+
+---
+
+## 6. Depth + subpasses (this is what you’re asking about)
+
+When you see:
+
+```
+Subpass 0: write color + depth
+Subpass 1: read depth, write color
+```
+
+It means:
+
+### Subpass 0
+
+* Draw geometry
+* Write:
+
+ * Color → color attachment
+ * Depth → depth attachment
+
+This establishes **visibility information**.
+
+---
+
+### Subpass 1
+
+* Does **not redraw geometry**
+* Reads depth to know:
+
+ * Which pixels belong to visible surfaces
+ * How far they are from the camera
+
+Used for:
+
+* Deferred lighting
+* Screen-space effects
+* Depth-aware post-processing
+
+---
+
+## 7. Reading depth in a later subpass
+
+Depth can be read as:
+
+### A) Input attachment (subpass-local, fast)
+
+```glsl
+subpassInput depthInput;
+```
+
+### B) Sampled image (general, slower)
+
+```glsl
+sampler2D depthTex;
+```
+
+This allows effects like:
+
+* Screen-space ambient occlusion (SSAO)
+* Volumetric fog
+* Soft particles
+* Deferred lighting
+
+---
+
+## 8. Depth vs color (key difference)
+
+| Color | Depth |
+| ------------ | ---------------------- |
+| What you see | What is visible |
+| RGB(A) | Single scalar value |
+| Displayed | Used for testing |
+| Final output | Internal decision data |
+
+Depth controls **visibility**, not appearance.
+
+---
+
+## 9. Depth and multisampling (important)
+
+With MSAA:
+
+* Each pixel has **multiple depth samples**
+* Each sample can pass/fail independently
+* This is why MSAA fixes edge aliasing correctly
+
+Depth must use the **same sample count** as color.
+
+---
+
+## 10. What depth is NOT
+
+❌ Not “distance in meters”
+❌ Not linear by default
+❌ Not screen-space Z
+❌ Not optional for 3D rendering
+
+Depth is **non-linear**, optimized for precision near the camera.
+
+---
+
+## 11. Mental model (best way to remember)
+
+> **Depth is a hidden image that stores “how close” each pixel is, and the GPU uses it to decide what is allowed to be drawn.**
+
+---
+
+## 12. One-sentence summary
+
+> **Depth is a per-pixel value stored in a depth buffer that allows the GPU to determine which fragments are visible and to reuse that visibility information across subpasses.**
+
+---
+
+If you want, I can next:
+
+* Show **exact Vulkan structs for a depth attachment**
+* Explain **why depth is non-linear**
+* Walk through **depth testing step-by-step**
+* Explain **stencil vs depth** (they’re often confused)
+
+Just tell me.
--- /dev/null
+Got it — you’re asking **why depth is “not optional” for 3D rendering**.
+
+This is a *fundamental* reason tied to how projection and visibility work, not just a Vulkan convention.
+
+---
+
+## Short answer (intuition)
+
+**Because in 3D, multiple surfaces project onto the same pixel, and without depth there is no correct way to know which one is visible.**
+
+2D doesn’t have this problem.
+3D does.
+
+---
+
+## What actually goes wrong without depth
+
+In 3D, after projection, **everything becomes 2D**:
+
+```
+3D scene → projection → screen pixels
+```
+
+Multiple triangles can map to the **same pixel**:
+
+```
+Camera
+ ↓
+[Triangle A] ← closer
+[Triangle B] ← farther
+```
+
+On screen:
+
+```
+Pixel (x, y)
+```
+
+Without depth, the GPU only knows **draw order**.
+
+---
+
+## Case 1: No depth buffer (painter’s algorithm)
+
+If you don’t use depth:
+
+```c
+vkCmdDraw(triangleA);
+vkCmdDraw(triangleB);
+```
+
+Whichever triangle is drawn **last** wins.
+
+This causes:
+
+* Objects popping in front of each other
+* Incorrect overlap
+* Massive CPU-side sorting requirements
+* Failure in cases like:
+
+ * Intersecting geometry
+ * Self-overlapping meshes
+ * Transparent + opaque mixes
+
+This is **not solvable in general**.
+
+---
+
+## Case 2: With depth buffer (correct solution)
+
+With depth testing:
+
+1. GPU stores depth per pixel
+2. For every fragment:
+
+ * Compare new depth vs stored depth
+ * Keep the closest one
+3. Order of draw calls **no longer matters**
+
+This gives:
+
+✔ Correct visibility
+✔ Intersecting geometry works
+✔ Self-occlusion works
+✔ Parallel GPU execution works
+
+---
+
+## Why the GPU *must* do this, not the CPU
+
+### You cannot sort triangles correctly in 3D
+
+There is **no total ordering** for triangles in 3D space.
+
+Example:
+
+* Triangle A overlaps B
+* B overlaps C
+* C overlaps A
+
+This is called a **cyclic overlap**.
+
+No draw order can fix this.
+
+Depth testing resolves it **per pixel**, which is the only correct level.
+
+---
+
+## Why this is fundamentally required by projection
+
+Perspective projection collapses depth:
+
+```
+(x, y, z) → (x / z, y / z)
+```
+
+That division by `z` destroys ordering information.
+
+Depth restores it.
+
+---
+
+## Can you “fake” 3D without depth?
+
+Yes, but with **severe limitations**:
+
+### When depth is *not* required:
+
+* Skyboxes
+* Fullscreen quads
+* Billboards drawn in strict order
+* 2.5D games (fixed layers)
+
+These avoid the general 3D visibility problem.
+
+---
+
+## Why Vulkan treats depth as a first-class concept
+
+Vulkan gives depth:
+
+* Its own attachment
+* Its own load/store ops
+* Its own layouts
+* Its own tests
+* Its own sampling path
+
+Because **the entire rasterization pipeline assumes depth exists**.
+
+---
+
+## The key mental model
+
+> **3D rendering is fundamentally a visibility problem.
+> Depth is the data structure that solves it.**
+
+Without depth, you don’t have 3D — you have a fragile illusion.
+
+---
+
+## One-sentence answer
+
+> **Depth is not optional in 3D rendering because projection causes multiple surfaces to map to the same pixel, and only per-pixel depth testing can correctly determine which surface is visible.**
+
+If you want, next we can:
+
+* Prove why triangle sorting is impossible in general
+* Walk through depth testing in Vulkan line-by-line
+* Explain why depth precision breaks at far distances
+* Show how deferred renderers rely entirely on depth
+
+Just say where you want to go.
--- /dev/null
+Yes — this is a *really important* Vulkan concept, and you’re already **using it correctly** in your code. I’ll explain it from the ground up and tie it directly to what you wrote.
+
+---
+
+# 1️⃣ What “dynamic rendering” is (high-level)
+
+**Dynamic rendering** is a newer Vulkan feature that lets you:
+
+> **Render without creating a `VkRenderPass` or `VkFramebuffer`.**
+
+Instead of baking attachments and subpasses ahead of time, you:
+
+* Describe the attachments **at draw time**
+* Begin rendering **directly in the command buffer**
+* Bind pipelines that declare what formats they expect
+
+It removes a *huge* amount of boilerplate.
+
+---
+
+# 2️⃣ What Vulkan looked like *before* dynamic rendering
+
+### Old model (classic Vulkan)
+
+You had to create:
+
+1. `VkRenderPass`
+
+ * Attachments
+ * Subpasses
+ * Load/store ops
+2. `VkFramebuffer`
+
+ * One per swapchain image
+3. Pipeline tied **per render pass**
+
+```text
+RenderPass → Framebuffer → Pipeline → Draw
+```
+
+Problems:
+
+* Render passes were **compile-time objects**
+* Changing formats or attachments required rebuilding pipelines
+* Framebuffers had to match exactly
+
+This was powerful but **extremely rigid**.
+
+---
+
+# 3️⃣ What dynamic rendering replaces
+
+Dynamic rendering **removes**:
+
+| Old object | Replaced by |
+| -------------------- | ----------- |
+| `VkRenderPass` | ❌ Gone |
+| `VkFramebuffer` | ❌ Gone |
+| Subpass descriptions | ❌ Gone |
+
+Instead, Vulkan introduces:
+
+* `vkCmdBeginRendering`
+* `VkRenderingInfo`
+* `VkRenderingAttachmentInfo`
+* `VkPipelineRenderingCreateInfo`
+
+---
+
+# 4️⃣ What *you* are doing with dynamic rendering
+
+Let’s map your code to the concept.
+
+---
+
+## A) Enabling dynamic rendering on the device
+
+```c
+VkPhysicalDeviceDynamicRenderingFeatures dyn = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+};
+```
+
+This says:
+
+> “This device is allowed to use `vkCmdBeginRendering`.”
+
+Without this → **validation error / undefined behavior**.
+
+---
+
+## B) Declaring attachment formats in the pipeline
+
+```c
+VkPipelineRenderingCreateInfo rendering = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+};
+```
+
+This replaces:
+
+* `VkRenderPass`
+* Subpass attachment format matching
+
+It tells Vulkan:
+
+> “This pipeline expects **one color attachment** with this format.”
+
+Pipelines still need to know formats **up-front**, but not layouts, load ops, or framebuffers.
+
+---
+
+## C) Beginning rendering (the new render pass)
+
+```c
+vkCmdBeginRendering(cb, &ri);
+```
+
+This is the **dynamic equivalent** of:
+
+```c
+vkCmdBeginRenderPass(...)
+```
+
+Except:
+
+* No framebuffer
+* No render pass object
+* No subpass index
+
+---
+
+## D) Describing attachments at draw time
+
+```c
+VkRenderingAttachmentInfo color = {
+ .imageView = swapchainViews[img],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue.color = {{0,0,0,1}}
+};
+```
+
+This replaces:
+
+```text
+VkAttachmentDescription
+VkAttachmentReference
+VkSubpassDescription
+```
+
+All **combined**, but now:
+
+* Fully dynamic
+* Chosen per frame
+* Chosen per command buffer
+
+---
+
+## E) Ending rendering
+
+```c
+vkCmdEndRendering(cb);
+```
+
+Equivalent to:
+
+```c
+vkCmdEndRenderPass(cb);
+```
+
+---
+
+# 5️⃣ What dynamic rendering does **NOT** change
+
+Dynamic rendering does **not** change:
+
+| Thing | Still required |
+| ------------------ | -------------- |
+| Pipelines | ✅ |
+| Descriptor sets | ✅ |
+| Layout transitions | ✅ |
+| Synchronization | ✅ |
+| Image views | ✅ |
+
+This is why you still need:
+
+```c
+VkImageMemoryBarrier
+```
+
+Dynamic rendering does **not** manage layouts for you.
+
+---
+
+# 6️⃣ Why dynamic rendering exists
+
+### Goals:
+
+* Reduce boilerplate
+* Make Vulkan usable for small engines
+* Allow more flexible rendering graphs
+* Match modern APIs (DX12 / Metal)
+
+### Typical wins:
+
+* No framebuffer creation
+* No render pass combinatorics
+* Easier swapchain recreation
+* Much easier multipass rendering
+
+---
+
+# 7️⃣ How dynamic rendering maps conceptually
+
+Think of it like this:
+
+### Classic Vulkan
+
+```text
+RenderPass (static)
+ └─ Subpass (static)
+ └─ Attachments (static)
+```
+
+### Dynamic Rendering
+
+```text
+Command Buffer
+ └─ Begin Rendering
+ └─ Attachments (chosen now)
+```
+
+You move **render-graph decisions** from *initialization time* to *record time*.
+
+---
+
+# 8️⃣ Common misconceptions (important)
+
+### ❌ “Dynamic rendering is slower”
+
+No. Drivers internally build render passes anyway.
+
+### ❌ “Dynamic rendering removes the need for barriers”
+
+Absolutely not. You still must manage layouts and sync.
+
+### ❌ “Dynamic rendering is optional”
+
+Yes — but it’s becoming the **preferred path**.
+
+---
+
+# 9️⃣ When NOT to use dynamic rendering
+
+You might still want classic render passes if:
+
+* You need **subpass input attachments**
+* You rely on **tile-based GPU subpass optimizations**
+* You want strict render-graph control
+
+Most beginner/intermediate engines should **use dynamic rendering**.
+
+---
+
+# 🔑 One-sentence summary
+
+> **Dynamic rendering lets you render directly to images without pre-declaring render passes or framebuffers, by describing attachments dynamically inside the command buffer.**
+
+---
+
+If you want next, I can:
+
+* Rewrite your code in **classic render-pass style** to compare
+* Explain **why subpasses exist** and when they outperform dynamic rendering
+* Show **depth + MSAA** with dynamic rendering
+* Show how dynamic rendering maps to **Vulkan synchronization2**
+
+Just tell me.
--- /dev/null
+Ah, now we’re getting into a subtle Vulkan topic: **swapchain image count vs performance**. Let’s break it down carefully.
+
+---
+
+## 1️⃣ How the swapchain works
+
+A Vulkan swapchain is basically a **queue of images** (framebuffers) the GPU can render into and the presentation engine (display) can show.
+
+* `vkCreateSwapchainKHR` lets you specify `minImageCount` — the number of images.
+* Typical values: **2–3** (double or triple buffering).
+
+Each image can be in one of three states:
+
+1. **Available** — ready to render to
+2. **Rendering** — GPU is drawing into it
+3. **Presenting** — waiting to be shown on screen
+
+---
+
+## 2️⃣ Double vs Triple Buffering
+
+| # Images | Behavior | Pros | Cons |
+| ---------- | ------------------------------------------------ | ---------------------------------------- | ---------------------------------------- |
+| 2 (double) | GPU renders to 1 image, display shows the other | Less memory usage, lower latency | GPU may stall if display is slow |
+| 3 (triple) | GPU has 1 image ready, 1 rendering, 1 displaying | GPU never waits for display → higher FPS | More memory usage, slightly more latency |
+
+✅ Triple buffering can **improve throughput** because the GPU always has an image to render to.
+❌ But adding **more than 3–4 images rarely helps** — the GPU already pipelines well.
+
+---
+
+## 3️⃣ Latency vs performance tradeoff
+
+* **More images → higher FPS potential** (GPU never blocks)
+* **More images → higher input/display latency** (you’re rendering frames that are “ahead” of the screen)
+
+For games:
+
+* Triple buffering is common for smooth FPS
+* Double buffering is often enough for low-latency apps
+
+---
+
+## 4️⃣ Vulkan-specific notes
+
+* Some GPU drivers **require at least 2 images**.
+
+* The swapchain must have **at least `surfaceCapabilities.minImageCount`** images.
+
+* If you request more than `surfaceCapabilities.maxImageCount` → creation fails.
+
+* Vulkan allows you to acquire the **next available image** via `vkAcquireNextImageKHR`. If all images are in use, the GPU may block until one becomes free. More images = less chance of blocking.
+
+---
+
+### ✅ TL;DR
+
+* **2 images:** safe, low memory, slightly higher chance GPU waits
+* **3 images:** smoother, less chance of GPU stall, slightly more latency
+* **4+ images:** rarely improves FPS, just uses more VRAM
+
+So yes, **more images *can* improve throughput**, but only up to a point (usually 3). Beyond that, it’s mostly wasted memory.
+
+---
+
+If you want, I can draw a **diagram showing the swapchain states over time** — it makes it super clear why 3 images can be smoother than 2.
+
+Do you want me to do that?
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
+
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
--- /dev/null
+Ah, this is a **common misunderstanding in Vulkan** — the “3D” in `VkImageViewType` does **not mean a 3D object in the scene**, it’s about the **image resource itself** in GPU memory. Let me explain carefully.
+
+---
+
+## 1️⃣ What `viewType` really means
+
+`VkImageViewCreateInfo.viewType` can be:
+
+| Value | Meaning |
+| ------------------------- | -------------------------------------------------- |
+| `VK_IMAGE_VIEW_TYPE_1D` | The image is a 1D texture (width only) |
+| `VK_IMAGE_VIEW_TYPE_2D` | The image is a 2D texture (width × height) |
+| `VK_IMAGE_VIEW_TYPE_3D` | The image is a 3D texture (width × height × depth) |
+| `VK_IMAGE_VIEW_TYPE_CUBE` | Cube map texture (6 faces) |
+
+**Important:** This is about **how the GPU interprets the memory layout of the image**, not whether your scene is 3D.
+
+---
+
+## 2️⃣ Swapchain images are always 2D
+
+* Swapchain images are the **images that will be presented on your 2D screen**.
+* Even though your cube is a 3D object in the scene, the **swapchain image is a 2D framebuffer**.
+* Therefore, `viewType` **must remain `VK_IMAGE_VIEW_TYPE_2D`** for swapchain images.
+
+If you tried `VK_IMAGE_VIEW_TYPE_3D`:
+
+* Vulkan would expect a **3D image resource** in memory (with depth slices).
+* Swapchain images **aren’t 3D** — they only have width × height × 1.
+* `vkCreateImageView` would **fail** or behave incorrectly.
+
+---
+
+## 3️⃣ Summary
+
+* Your 3D cube **does not make the swapchain 3D**.
+* `VK_IMAGE_VIEW_TYPE_2D` is correct for **all normal onscreen rendering**.
+* `VK_IMAGE_VIEW_TYPE_3D` is only for **volumetric textures**, like 3D noise, 3D textures, or voxel data.
+
+---
+
+💡 **Analogy:**
+
+* The 3D cube is like a **3D model**.
+* The swapchain image is like the **photograph of that model** — a 2D picture.
+* The swapchain image view interprets the “photograph” — so it’s always 2D.
+
+---
+
+If you want, I can also explain **when you *would* use `VK_IMAGE_VIEW_TYPE_3D` in Vulkan** and how it differs from rendering a normal 3D scene. This is actually a pretty common point of confusion for newcomers.
+
+Do you want me to go into that?
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(fragColor, 1.0);
+}
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+layout(push_constant) uniform Push {
+ float angle;
+} pc;
+
+void main() {
+ float c = cos(pc.angle);
+ float s = sin(pc.angle);
+
+ // Rotate around Y axis
+ vec3 rotated = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // Simple perspective
+ float z = rotated.z + 2.5;
+ gl_Position = vec4(rotated.xy / z, 0.0, 1.0);
+
+ fragColor = inColor;
+}
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+#include <vulkan/vulkan.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+VkSemaphore imageAvailable;
+VkSemaphore renderFinished;
+VkFence inFlight;
+
+GLFWwindow* window;
+
+float angle = 0.0f;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[3];
+ float color[3];
+} Vertex;
+
+uint32_t vertexCount = 0;
+typedef struct _Vec3 { float x; float y; float z;} Vec3;
+typedef struct _Vec2 { float x; float y;} Vec2;
+
+Vec3 cube[] = {
+
+ // bottom
+ { 0.5f, 0.5f, -0.5f},
+ {-0.5f, 0.5f, -0.5f},
+
+ { 0.5f, 0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ { 0.5f, 0.5f, -0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ {-0.5f, 0.5f, -0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+
+ // top
+ { 0.5f, -0.5f, -0.5f},
+ {-0.5f, -0.5f, -0.5f},
+
+ { 0.5f, -0.5f, 0.5f},
+ {-0.5f, -0.5f, 0.5f},
+
+ { 0.5f, -0.5f, -0.5f},
+ { 0.5f, -0.5f, 0.5f},
+
+ {-0.5f, -0.5f, -0.5f},
+ {-0.5f, -0.5f, 0.5f},
+
+ // front
+ {-0.5f, -0.5f, -0.5f},
+ {-0.5f, 0.5f, -0.5f},
+
+ {0.5f, -0.5f, -0.5f},
+ {0.5f, 0.5f, -0.5f},
+
+ // back
+ {-0.5f, -0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ {0.5f, -0.5f, 0.5f},
+ {0.5f, 0.5f, 0.5f},
+};
+
+
+// vertices don't need a z value. you only can write z and y to screen we can
+// use the projecion formula to change this
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+// * https://docs.vulkan.org/spec/latest/chapters/images.html
+// In every swap chain there are at least two buffers. The first framebuffer,
+// the screenbuffer, is the buffer that is rendered to the output of the video
+// card. The remaining buffers are known as backbuffers, Each time a new frame
+// is displayed, the first backbuffer in the swapchain takes the place fo the
+// screen buffer, thi sis called presentation or swapping.
+void create_swapchain() {
+ // `caps` will contain things such as
+ //
+ // - max and minimum window height
+ //
+ // - alapha channel modes
+ //
+ // - options for how images update, and wheather or not there is screen
+ // tearing
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+ swapchainFormat = surf_fmt.format;
+
+ // extent means the width and height of the window
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount + 1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace, // sRBG, HDR10 etc..
+
+ // size (in pixels) of the swapchain image(s).
+ .imageExtent = swapchainExtent,
+
+ // number of views in a multiview/stereo surface. For
+ // non-stereoscopic-3D applications, this value is 1
+ //
+ // sterio rendering is used for VR
+ .imageArrayLayers = 1,
+
+ // bitmask of VkImageUsageFlagBits
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ // VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+ // Image cna be rendered into as a color target
+ // VK_IMAGE_USAGE_SAMPLED_BIT
+ // Image can be read by a shader using a sampler
+ // Typical texures, shadow maps, etc
+ // VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT
+ // Texture loaded from disk
+ // VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT
+ // Render-to-texture (post-processing)
+ // VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
+ // Depth buffer
+ // VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT
+ // Screenshot-capable framebuffer
+
+ // Sharing mode tells Vulkan whether a resource (buffer or image) will
+ // be accessed by one queue family or by multiple queue families.
+ // `VK_SHARING_MODE_EXCLUSIVE` means only one queue family will access
+ // this resource at a time.
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ // preTransformation tells vulkan if the image should be rotated
+ .preTransform = caps.currentTransform,
+
+ // a VkCompositeAlphaFlagBitsKHR value indicating the alpha compositing
+ // mode to use when this surface is composited together with other
+ // surfaces on certain window systems.
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+
+ // the presentation mode the swapchain will use. A swapchain’s present
+ // mode determines how incoming present requests will be processed and
+ // queued internally.
+ .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+
+ // specifies whether the Vulkan implementation is allowed to discard
+ // rendering operations that affect regions of the surface that are not
+ // visible.
+ .clipped = VK_TRUE
+ // If clipped is VK_TRUE, the presentable images associated with the
+ // swapchain may not own all of their pixels. Pixels in the presentable
+ // images that correspond to regions of the target surface obscured by
+ // another window on the desktop, or subject to some other clipping
+ // mechanism will have undefined content when read back. Fragment
+ // shaders may not execute for these pixels, and thus any side effects
+ // they would have had will not occur. Setting VK_TRUE does not
+ // guarantee any clipping will occur, but allows more efficient
+ // presentation methods to be used on some platform
+ //
+ // If clipped is VK_FALSE, presentable images associated with the
+ // swapchain will own all of the pixels they cont
+ };
+ // pAllocator is set to NULL
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+ printf("image count: %d\n", swapchainImageCount);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .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
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+
+ vertexCount = SIZE(cube);
+ Vertex vertices[vertexCount];
+
+ for (int i = 0; i < vertexCount; ++i) {
+ vertices[i].pos[0] = cube[i].x;
+ vertices[i].pos[1] = cube[i].y;
+ vertices[i].pos[2] = cube[i].z;
+
+ vertices[i].color[0] = 1.0f;
+ vertices[i].color[1] = 1.0f;
+ vertices[i].color[2] = 1.0f;
+ }
+
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = sizeof(Vertex) * vertexCount,
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+ if(vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create vertex buffer\n"); exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = 0
+ };
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+ for(uint32_t i=0;i<memProps.memoryTypeCount;i++){
+ if((memReq.memoryTypeBits & (1<<i)) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)){
+ allocInfo.memoryTypeIndex = i;
+ break;
+ }
+ }
+
+ if(vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate vertex buffer memory\n"); exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufInfo.size, 0, &data);
+ memcpy(data, vertices, bufInfo.size);
+ // memcpy(data, vertices, sizeof(Vertex) * vertexCount);
+ vkUnmapMemory(device, vertexBufferMemory);
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("cube.vert.spv");
+ VkShaderModule fragModule = load_shader("cube.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ // One Vertex per vertex
+ // Move forward sizeof(Vertex) each vertex
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(Vertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+
+ // the location variable is the same location variable
+ // in the shader program
+ VkVertexInputAttributeDescription attrDesc[2] = {
+ {
+ .location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = offsetof(Vertex, pos)
+ },
+
+ {
+ .location = 1,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = offsetof(Vertex, color)
+ }
+ };
+
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=2,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ /// topology is how to draw the mesh
+ // .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_BACK_BIT,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPushConstantRange pushRange = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(float)
+ };
+
+ VkPipelineLayoutCreateInfo layoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pushRange
+ };
+
+ vkCreatePipelineLayout(device, &layoutInfo, NULL, &pipelineLayout);
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+
+ VkPipelineRenderingCreateInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .pNext = NULL,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &formats[0].format,
+ .stencilAttachmentFormat = VK_FORMAT_UNDEFINED
+ };
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = {
+ .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &renderingInfo,
+ .stageCount = 2,
+ .pStages = stages,
+ .pVertexInputState = &vertexInput,
+ .pInputAssemblyState = &inputAssembly,
+ .pViewportState = &vpState,
+ .pRasterizationState = &rasterizer,
+ .pMultisampleState = &multisampling,
+ .pColorBlendState = &colorBlending,
+ .layout = pipelineLayout,
+ .renderPass = VK_NULL_HANDLE,
+ .subpass = 0
+ };
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,
+ &graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+
+/* --- Main --- */
+int main(void) {
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+
+ uint32_t instanceVersion;
+ vkEnumerateInstanceVersion(&instanceVersion);
+ printf("Vulkan loader supports version %d.%d.%d\n",
+ VK_API_VERSION_MAJOR(instanceVersion),
+ VK_API_VERSION_MINOR(instanceVersion),
+ VK_API_VERSION_PATCH(instanceVersion)
+ );
+
+ uint32_t ext_count=0;
+ VkApplicationInfo appInfo = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = "cube",
+ .applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0),
+ // Dynamic rendering needs version 1.3
+ // .apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0)
+ .apiVersion = VK_API_VERSION_1_3
+ };
+
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &appInfo,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ici,NULL,&instance);
+
+ volkLoadInstance(instance);
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8];
+ vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ physical=devices[0];
+
+ uint32_t qcount=0;
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ VkQueueFamilyProperties qprops[16];
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ // check devices to see if dynamic rendering exists
+ // NOTE: this does not need to be done if you are using VULKAN 1.3 or
+ // greater
+
+ // uint32_t extensionCount;
+ // vkEnumerateDeviceExtensionProperties(physical, NULL, &extensionCount, NULL);
+ // VkExtensionProperties extensionProperties[extensionCount];
+ // vkEnumerateDeviceExtensionProperties(physical, NULL, &extensionCount, extensionProperties);
+ // bool supportDynamicRendering = false;
+
+ // for (int i = 0; i < extensionCount; ++i) {
+ // if (strcmp(extensionProperties[i].extensionName,
+ // VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) == 0) {
+ // supportDynamicRendering = true;
+ // }
+ // }
+ // if (!supportDynamicRendering) {
+ // fprintf(stderr, "cannot continue, device does not support dynamic rendering");
+ // exit(1);
+ // }
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ // .pNext = NULL,
+ .dynamicRendering = VK_TRUE
+ };
+
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &dynamicRenderingFeature,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ // defines
+ // - swapchainExtent
+ // - swapchainFormat
+ // - swapchain
+ // - swapchainImages
+ // - swapchainImageCount
+ // - swapchainImageViews
+ create_swapchain();
+ // defines
+ // - renderPass
+ // create_render_pass();
+ // defines
+ // - framebuffers
+ // create_framebuffers();
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+ create_vertex_buffer();
+ // record_command_buffers();
+ VkSemaphoreCreateInfo sem = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
+ };
+
+ VkFenceCreateInfo fence = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT
+ };
+
+ vkCreateSemaphore(device, &sem, NULL, &imageAvailable);
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished);
+ vkCreateFence(device, &fence, NULL, &inFlight);
+
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ angle += 0.01f;
+
+ uint32_t imgIndex;
+ vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+ vkResetFences(device, 1, &inFlight);
+
+ vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable,
+ VK_NULL_HANDLE,
+ &imgIndex
+ );
+
+ VkCommandBuffer cb = commandBuffers[imgIndex];
+
+ vkResetCommandBuffer(cb, 0);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+
+ // vkCmdBeginRenderPass(cb, &rp, VK_SUBPASS_CONTENTS_INLINE);
+ VkRenderingAttachmentInfo colorAttachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = swapchainImageViews[imgIndex],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = {{0.0f, 0.0f, 0.0f, 1.0f}}
+ };
+
+ VkRenderingInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {{0, 0}, swapchainExtent},
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorAttachment
+ };
+
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .image = swapchainImages[imgIndex],
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+ };
+
+
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+
+ vkCmdBeginRendering(cb, &renderingInfo);
+
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+ VkViewport vp = {
+ 0, 0,
+ (float)swapchainExtent.width,
+ (float)swapchainExtent.height,
+ 0.0f, 1.0f
+ };
+
+ VkRect2D scissor = {
+ {0, 0},
+ swapchainExtent
+ };
+
+ vkCmdSetViewport(cb, 0, 1, &vp);
+ vkCmdSetScissor(cb, 0, 1, &scissor);
+
+
+ VkDeviceSize offset = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vertexBuffer, &offset);
+
+ // 🔥 Push rotation angle
+ vkCmdPushConstants(
+ cb,
+ pipelineLayout,
+ VK_SHADER_STAGE_VERTEX_BIT,
+ 0,
+ sizeof(float),
+ &angle
+ );
+
+ vkCmdDraw(cb, vertexCount, 1, 0, 0);
+
+ vkCmdEndRendering(cb);
+
+
+ barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+ vkEndCommandBuffer(cb);
+
+
+ VkPipelineStageFlags waitStage =
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &imageAvailable,
+ .pWaitDstStageMask = &waitStage,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &renderFinished
+ };
+
+ vkQueueSubmit(graphicsQueue, 1, &submit, inFlight);
+
+ VkPresentInfoKHR present = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &renderFinished,
+ .swapchainCount = 1,
+ .pSwapchains = &swapchain,
+ .pImageIndices = &imgIndex
+ };
+
+ vkQueuePresentKHR(graphicsQueue, &present);
+
+ }
+
+ vkDeviceWaitIdle(device);
+
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+ vkDestroyPipeline(device,graphicsPipeline,NULL);
+ vkDestroyPipelineLayout(device,pipelineLayout,NULL);
+ vkDestroySwapchainKHR(device,swapchain,NULL);
+ vkDestroyDevice(device,NULL);
+ vkDestroySurfaceKHR(instance,surface,NULL);
+ vkDestroyInstance(instance,NULL);
+ glfwDestroyWindow(window); glfwTerminate();
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
+* https://docs.vulkan.org/samples/latest/samples/extensions/dynamic_rendering/README.html
+* https://www.youtube.com/watch?v=m1RHLavNjKo
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_cube
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+In this context, **“depth” means the per-pixel (or per-sample) distance from the camera used to decide what is in front of what**.
+
+I’ll explain it from the ground up, then tie it directly to **render passes and subpasses**.
+
+---
+
+## 1. What “depth” is (fundamental idea)
+
+**Depth** is a numerical value that represents **how far a fragment is from the camera**.
+
+For every pixel the GPU draws, it computes a depth value:
+
+```
+closer to camera → smaller depth
+farther away → larger depth
+```
+
+This value is stored in a **depth buffer** (also called a **Z buffer**).
+
+---
+
+## 2. Why depth exists at all
+
+Without depth, triangles would be drawn **in submission order**:
+
+```
+draw triangle A
+draw triangle B
+```
+
+If B is behind A but drawn later → it appears in front ❌
+
+Depth fixes this by answering:
+
+> “Is this fragment closer than what was drawn before?”
+
+---
+
+## 3. How depth testing works
+
+For each fragment:
+
+1. Compute its depth (from vertex positions → clip space)
+2. Compare it against the stored depth value
+3. If it passes → draw and update depth
+4. If it fails → discard fragment
+
+Typical comparison:
+
+```c
+VK_COMPARE_OP_LESS
+```
+
+Meaning:
+
+> “Only draw if this fragment is closer.”
+
+---
+
+## 4. Where depth comes from (pipeline view)
+
+### Vertex shader
+
+Outputs:
+
+```glsl
+gl_Position
+```
+
+This contains **clip-space coordinates**.
+
+### After projection
+
+The GPU computes:
+
+```
+depth = z / w
+```
+
+Mapped into:
+
+```
+[0, 1] (Vulkan default)
+```
+
+This is what ends up in the depth buffer.
+
+---
+
+## 5. The depth buffer itself
+
+Depth is stored in a **depth attachment**, just like color is stored in a color attachment.
+
+Common formats:
+
+* `VK_FORMAT_D32_SFLOAT`
+* `VK_FORMAT_D24_UNORM_S8_UINT`
+
+Depth attachments:
+
+* Are not visible on screen
+* Exist only for visibility testing (and sometimes reading later)
+
+---
+
+## 6. Depth + subpasses (this is what you’re asking about)
+
+When you see:
+
+```
+Subpass 0: write color + depth
+Subpass 1: read depth, write color
+```
+
+It means:
+
+### Subpass 0
+
+* Draw geometry
+* Write:
+
+ * Color → color attachment
+ * Depth → depth attachment
+
+This establishes **visibility information**.
+
+---
+
+### Subpass 1
+
+* Does **not redraw geometry**
+* Reads depth to know:
+
+ * Which pixels belong to visible surfaces
+ * How far they are from the camera
+
+Used for:
+
+* Deferred lighting
+* Screen-space effects
+* Depth-aware post-processing
+
+---
+
+## 7. Reading depth in a later subpass
+
+Depth can be read as:
+
+### A) Input attachment (subpass-local, fast)
+
+```glsl
+subpassInput depthInput;
+```
+
+### B) Sampled image (general, slower)
+
+```glsl
+sampler2D depthTex;
+```
+
+This allows effects like:
+
+* Screen-space ambient occlusion (SSAO)
+* Volumetric fog
+* Soft particles
+* Deferred lighting
+
+---
+
+## 8. Depth vs color (key difference)
+
+| Color | Depth |
+| ------------ | ---------------------- |
+| What you see | What is visible |
+| RGB(A) | Single scalar value |
+| Displayed | Used for testing |
+| Final output | Internal decision data |
+
+Depth controls **visibility**, not appearance.
+
+---
+
+## 9. Depth and multisampling (important)
+
+With MSAA:
+
+* Each pixel has **multiple depth samples**
+* Each sample can pass/fail independently
+* This is why MSAA fixes edge aliasing correctly
+
+Depth must use the **same sample count** as color.
+
+---
+
+## 10. What depth is NOT
+
+❌ Not “distance in meters”
+❌ Not linear by default
+❌ Not screen-space Z
+❌ Not optional for 3D rendering
+
+Depth is **non-linear**, optimized for precision near the camera.
+
+---
+
+## 11. Mental model (best way to remember)
+
+> **Depth is a hidden image that stores “how close” each pixel is, and the GPU uses it to decide what is allowed to be drawn.**
+
+---
+
+## 12. One-sentence summary
+
+> **Depth is a per-pixel value stored in a depth buffer that allows the GPU to determine which fragments are visible and to reuse that visibility information across subpasses.**
+
+---
+
+If you want, I can next:
+
+* Show **exact Vulkan structs for a depth attachment**
+* Explain **why depth is non-linear**
+* Walk through **depth testing step-by-step**
+* Explain **stencil vs depth** (they’re often confused)
+
+Just tell me.
--- /dev/null
+Got it — you’re asking **why depth is “not optional” for 3D rendering**.
+
+This is a *fundamental* reason tied to how projection and visibility work, not just a Vulkan convention.
+
+---
+
+## Short answer (intuition)
+
+**Because in 3D, multiple surfaces project onto the same pixel, and without depth there is no correct way to know which one is visible.**
+
+2D doesn’t have this problem.
+3D does.
+
+---
+
+## What actually goes wrong without depth
+
+In 3D, after projection, **everything becomes 2D**:
+
+```
+3D scene → projection → screen pixels
+```
+
+Multiple triangles can map to the **same pixel**:
+
+```
+Camera
+ ↓
+[Triangle A] ← closer
+[Triangle B] ← farther
+```
+
+On screen:
+
+```
+Pixel (x, y)
+```
+
+Without depth, the GPU only knows **draw order**.
+
+---
+
+## Case 1: No depth buffer (painter’s algorithm)
+
+If you don’t use depth:
+
+```c
+vkCmdDraw(triangleA);
+vkCmdDraw(triangleB);
+```
+
+Whichever triangle is drawn **last** wins.
+
+This causes:
+
+* Objects popping in front of each other
+* Incorrect overlap
+* Massive CPU-side sorting requirements
+* Failure in cases like:
+
+ * Intersecting geometry
+ * Self-overlapping meshes
+ * Transparent + opaque mixes
+
+This is **not solvable in general**.
+
+---
+
+## Case 2: With depth buffer (correct solution)
+
+With depth testing:
+
+1. GPU stores depth per pixel
+2. For every fragment:
+
+ * Compare new depth vs stored depth
+ * Keep the closest one
+3. Order of draw calls **no longer matters**
+
+This gives:
+
+✔ Correct visibility
+✔ Intersecting geometry works
+✔ Self-occlusion works
+✔ Parallel GPU execution works
+
+---
+
+## Why the GPU *must* do this, not the CPU
+
+### You cannot sort triangles correctly in 3D
+
+There is **no total ordering** for triangles in 3D space.
+
+Example:
+
+* Triangle A overlaps B
+* B overlaps C
+* C overlaps A
+
+This is called a **cyclic overlap**.
+
+No draw order can fix this.
+
+Depth testing resolves it **per pixel**, which is the only correct level.
+
+---
+
+## Why this is fundamentally required by projection
+
+Perspective projection collapses depth:
+
+```
+(x, y, z) → (x / z, y / z)
+```
+
+That division by `z` destroys ordering information.
+
+Depth restores it.
+
+---
+
+## Can you “fake” 3D without depth?
+
+Yes, but with **severe limitations**:
+
+### When depth is *not* required:
+
+* Skyboxes
+* Fullscreen quads
+* Billboards drawn in strict order
+* 2.5D games (fixed layers)
+
+These avoid the general 3D visibility problem.
+
+---
+
+## Why Vulkan treats depth as a first-class concept
+
+Vulkan gives depth:
+
+* Its own attachment
+* Its own load/store ops
+* Its own layouts
+* Its own tests
+* Its own sampling path
+
+Because **the entire rasterization pipeline assumes depth exists**.
+
+---
+
+## The key mental model
+
+> **3D rendering is fundamentally a visibility problem.
+> Depth is the data structure that solves it.**
+
+Without depth, you don’t have 3D — you have a fragile illusion.
+
+---
+
+## One-sentence answer
+
+> **Depth is not optional in 3D rendering because projection causes multiple surfaces to map to the same pixel, and only per-pixel depth testing can correctly determine which surface is visible.**
+
+If you want, next we can:
+
+* Prove why triangle sorting is impossible in general
+* Walk through depth testing in Vulkan line-by-line
+* Explain why depth precision breaks at far distances
+* Show how deferred renderers rely entirely on depth
+
+Just say where you want to go.
--- /dev/null
+Yes — this is a *really important* Vulkan concept, and you’re already **using it correctly** in your code. I’ll explain it from the ground up and tie it directly to what you wrote.
+
+---
+
+# 1️⃣ What “dynamic rendering” is (high-level)
+
+**Dynamic rendering** is a newer Vulkan feature that lets you:
+
+> **Render without creating a `VkRenderPass` or `VkFramebuffer`.**
+
+Instead of baking attachments and subpasses ahead of time, you:
+
+* Describe the attachments **at draw time**
+* Begin rendering **directly in the command buffer**
+* Bind pipelines that declare what formats they expect
+
+It removes a *huge* amount of boilerplate.
+
+---
+
+# 2️⃣ What Vulkan looked like *before* dynamic rendering
+
+### Old model (classic Vulkan)
+
+You had to create:
+
+1. `VkRenderPass`
+
+ * Attachments
+ * Subpasses
+ * Load/store ops
+2. `VkFramebuffer`
+
+ * One per swapchain image
+3. Pipeline tied **per render pass**
+
+```text
+RenderPass → Framebuffer → Pipeline → Draw
+```
+
+Problems:
+
+* Render passes were **compile-time objects**
+* Changing formats or attachments required rebuilding pipelines
+* Framebuffers had to match exactly
+
+This was powerful but **extremely rigid**.
+
+---
+
+# 3️⃣ What dynamic rendering replaces
+
+Dynamic rendering **removes**:
+
+| Old object | Replaced by |
+| -------------------- | ----------- |
+| `VkRenderPass` | ❌ Gone |
+| `VkFramebuffer` | ❌ Gone |
+| Subpass descriptions | ❌ Gone |
+
+Instead, Vulkan introduces:
+
+* `vkCmdBeginRendering`
+* `VkRenderingInfo`
+* `VkRenderingAttachmentInfo`
+* `VkPipelineRenderingCreateInfo`
+
+---
+
+# 4️⃣ What *you* are doing with dynamic rendering
+
+Let’s map your code to the concept.
+
+---
+
+## A) Enabling dynamic rendering on the device
+
+```c
+VkPhysicalDeviceDynamicRenderingFeatures dyn = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+};
+```
+
+This says:
+
+> “This device is allowed to use `vkCmdBeginRendering`.”
+
+Without this → **validation error / undefined behavior**.
+
+---
+
+## B) Declaring attachment formats in the pipeline
+
+```c
+VkPipelineRenderingCreateInfo rendering = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+};
+```
+
+This replaces:
+
+* `VkRenderPass`
+* Subpass attachment format matching
+
+It tells Vulkan:
+
+> “This pipeline expects **one color attachment** with this format.”
+
+Pipelines still need to know formats **up-front**, but not layouts, load ops, or framebuffers.
+
+---
+
+## C) Beginning rendering (the new render pass)
+
+```c
+vkCmdBeginRendering(cb, &ri);
+```
+
+This is the **dynamic equivalent** of:
+
+```c
+vkCmdBeginRenderPass(...)
+```
+
+Except:
+
+* No framebuffer
+* No render pass object
+* No subpass index
+
+---
+
+## D) Describing attachments at draw time
+
+```c
+VkRenderingAttachmentInfo color = {
+ .imageView = swapchainViews[img],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue.color = {{0,0,0,1}}
+};
+```
+
+This replaces:
+
+```text
+VkAttachmentDescription
+VkAttachmentReference
+VkSubpassDescription
+```
+
+All **combined**, but now:
+
+* Fully dynamic
+* Chosen per frame
+* Chosen per command buffer
+
+---
+
+## E) Ending rendering
+
+```c
+vkCmdEndRendering(cb);
+```
+
+Equivalent to:
+
+```c
+vkCmdEndRenderPass(cb);
+```
+
+---
+
+# 5️⃣ What dynamic rendering does **NOT** change
+
+Dynamic rendering does **not** change:
+
+| Thing | Still required |
+| ------------------ | -------------- |
+| Pipelines | ✅ |
+| Descriptor sets | ✅ |
+| Layout transitions | ✅ |
+| Synchronization | ✅ |
+| Image views | ✅ |
+
+This is why you still need:
+
+```c
+VkImageMemoryBarrier
+```
+
+Dynamic rendering does **not** manage layouts for you.
+
+---
+
+# 6️⃣ Why dynamic rendering exists
+
+### Goals:
+
+* Reduce boilerplate
+* Make Vulkan usable for small engines
+* Allow more flexible rendering graphs
+* Match modern APIs (DX12 / Metal)
+
+### Typical wins:
+
+* No framebuffer creation
+* No render pass combinatorics
+* Easier swapchain recreation
+* Much easier multipass rendering
+
+---
+
+# 7️⃣ How dynamic rendering maps conceptually
+
+Think of it like this:
+
+### Classic Vulkan
+
+```text
+RenderPass (static)
+ └─ Subpass (static)
+ └─ Attachments (static)
+```
+
+### Dynamic Rendering
+
+```text
+Command Buffer
+ └─ Begin Rendering
+ └─ Attachments (chosen now)
+```
+
+You move **render-graph decisions** from *initialization time* to *record time*.
+
+---
+
+# 8️⃣ Common misconceptions (important)
+
+### ❌ “Dynamic rendering is slower”
+
+No. Drivers internally build render passes anyway.
+
+### ❌ “Dynamic rendering removes the need for barriers”
+
+Absolutely not. You still must manage layouts and sync.
+
+### ❌ “Dynamic rendering is optional”
+
+Yes — but it’s becoming the **preferred path**.
+
+---
+
+# 9️⃣ When NOT to use dynamic rendering
+
+You might still want classic render passes if:
+
+* You need **subpass input attachments**
+* You rely on **tile-based GPU subpass optimizations**
+* You want strict render-graph control
+
+Most beginner/intermediate engines should **use dynamic rendering**.
+
+---
+
+# 🔑 One-sentence summary
+
+> **Dynamic rendering lets you render directly to images without pre-declaring render passes or framebuffers, by describing attachments dynamically inside the command buffer.**
+
+---
+
+If you want next, I can:
+
+* Rewrite your code in **classic render-pass style** to compare
+* Explain **why subpasses exist** and when they outperform dynamic rendering
+* Show **depth + MSAA** with dynamic rendering
+* Show how dynamic rendering maps to **Vulkan synchronization2**
+
+Just tell me.
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
+
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(fragColor, 1.0);
+}
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+layout(push_constant) uniform Push {
+ float angle;
+} pc;
+
+void main() {
+ float c = cos(pc.angle);
+ float s = sin(pc.angle);
+
+ // Rotate around Y axis
+ vec3 rotated = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // Simple perspective
+ float z = rotated.z + 2.5;
+ gl_Position = vec4(rotated.xy / z, 0.0, 1.0);
+
+ fragColor = inColor;
+}
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkRenderPass renderPass;
+VkFramebuffer* framebuffers;
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+GLFWwindow* window;
+
+float angle = 0.0f;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[3];
+ float color[3];
+} Vertex;
+
+uint32_t vertexCount = 0;
+typedef struct _Vec3 { float x; float y; float z;} Vec3;
+typedef struct _Vec2 { float x; float y;} Vec2;
+
+Vec3 cube[] = {
+
+ // bottom
+ { 0.5f, 0.5f, -0.5f},
+ {-0.5f, 0.5f, -0.5f},
+
+ { 0.5f, 0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ { 0.5f, 0.5f, -0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ {-0.5f, 0.5f, -0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+
+ // top
+ { 0.5f, -0.5f, -0.5f},
+ {-0.5f, -0.5f, -0.5f},
+
+ { 0.5f, -0.5f, 0.5f},
+ {-0.5f, -0.5f, 0.5f},
+
+ { 0.5f, -0.5f, -0.5f},
+ { 0.5f, -0.5f, 0.5f},
+
+ {-0.5f, -0.5f, -0.5f},
+ {-0.5f, -0.5f, 0.5f},
+
+ // front
+ {-0.5f, -0.5f, -0.5f},
+ {-0.5f, 0.5f, -0.5f},
+
+ {0.5f, -0.5f, -0.5f},
+ {0.5f, 0.5f, -0.5f},
+
+ // back
+ {-0.5f, -0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ {0.5f, -0.5f, 0.5f},
+ {0.5f, 0.5f, 0.5f},
+};
+
+
+// vertices don't need a z value. you only can write z and y to screen we can
+// use the projecion formula to change this
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+// * https://docs.vulkan.org/spec/latest/chapters/images.html
+// In every swap chain there are at least two buffers. The first framebuffer,
+// the screenbuffer, is the buffer that is rendered to the output of the video
+// card. The remaining buffers are known as backbuffers, Each time a new frame
+// is displayed, the first backbuffer in the swapchain takes the place fo the
+// screen buffer, thi sis called presentation or swapping.
+void create_swapchain() {
+ // `caps` will contain things such as
+ //
+ // - max and minimum window height
+ //
+ // - alapha channel modes
+ //
+ // - options for how images update, and wheather or not there is screen
+ // tearing
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ // I believe 16 is another arbitrary number
+ // VkSurfaceFormatKHR formats[16];
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+ swapchainFormat = surf_fmt.format;
+
+ // extent means the width and height of the window
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount + 1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace, // sRBG, HDR10 etc..
+
+ // size (in pixels) of the swapchain image(s).
+ .imageExtent = swapchainExtent,
+
+ // number of views in a multiview/stereo surface. For
+ // non-stereoscopic-3D applications, this value is 1
+ //
+ // sterio rendering is used for VR
+ .imageArrayLayers = 1,
+
+ // bitmask of VkImageUsageFlagBits
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ // VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+ // Image cna be rendered into as a color target
+ // VK_IMAGE_USAGE_SAMPLED_BIT
+ // Image can be read by a shader using a sampler
+ // Typical texures, shadow maps, etc
+ // VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT
+ // Texture loaded from disk
+ // VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT
+ // Render-to-texture (post-processing)
+ // VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
+ // Depth buffer
+ // VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT
+ // Screenshot-capable framebuffer
+
+ // Sharing mode tells Vulkan whether a resource (buffer or image) will
+ // be accessed by one queue family or by multiple queue families.
+ // `VK_SHARING_MODE_EXCLUSIVE` means only one queue family will access
+ // this resource at a time.
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ // preTransformation tells vulkan if the image should be rotated
+ .preTransform = caps.currentTransform,
+
+ // a VkCompositeAlphaFlagBitsKHR value indicating the alpha compositing
+ // mode to use when this surface is composited together with other
+ // surfaces on certain window systems.
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+
+ // the presentation mode the swapchain will use. A swapchain’s present
+ // mode determines how incoming present requests will be processed and
+ // queued internally.
+ .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+
+ // specifies whether the Vulkan implementation is allowed to discard
+ // rendering operations that affect regions of the surface that are not
+ // visible.
+ .clipped = VK_TRUE
+ // If clipped is VK_TRUE, the presentable images associated with the
+ // swapchain may not own all of their pixels. Pixels in the presentable
+ // images that correspond to regions of the target surface obscured by
+ // another window on the desktop, or subject to some other clipping
+ // mechanism will have undefined content when read back. Fragment
+ // shaders may not execute for these pixels, and thus any side effects
+ // they would have had will not occur. Setting VK_TRUE does not
+ // guarantee any clipping will occur, but allows more efficient
+ // presentation methods to be used on some platform
+ //
+ // If clipped is VK_FALSE, presentable images associated with the
+ // swapchain will own all of the pixels they cont
+ };
+ // pAllocator is set to NULL
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .components = {VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY},
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+/* --- Render pass --- */
+// https://docs.vulkan.org/spec/latest/chapters/renderpass.html
+// https://docs.vulkan.org/spec/latest/chapters/drawing.html#drawing
+// Draw commands must be recorded within a render pass instance. Each render
+// pass instance defines a set of image resources
+// Render pass is a contract how images are used over time, not a draw call
+//
+// This code creates a simple Vulkan render pass with one color attachment that
+// is cleared, rendered to, and then transitioned to a presentable layout for
+// display on the screen.
+void create_render_pass() {
+ // This function creates a rener pass that:
+ // - Has one color attachment (the swapchain image)
+ // - Clears it at the start of rendering
+ // - Renders into it in one subpass
+ // - Presents the result to the screen
+
+ // An attachment is an image the GPU reads from or writes to during a render
+ // pass.
+ VkAttachmentDescription att = {
+ // Must match the swapchain image format
+ .format = swapchainFormat,
+
+ // Multisampling, no MSAA, One sample per pixel
+ // A pixel is an area that contains multiple samples. this is saying to
+ // have one sample per pixel
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+
+ // What happens at the start of the render pass
+ // - The image is cleared
+ // - Clear color is provided in vkCmdBeginRenderPass
+ // Alternative options
+ // LOAD -> preserver previous contents
+ // DONT_CARE -> contents undefined
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+
+
+ // What happens at the end of the render pass
+ // - the rendered image is kept
+ // - Required for presentation
+ // if you used VK_ATTACHMENT_STORE_OP_DONT_CARE
+ // then the screen would show garbage
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+
+ // - Relevant only for depth/stencil formats
+ // - safe to ignore for color images
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+
+ // - Vulkan does not preserve previous contents
+ // - Allows driver to discard old memory
+ // - Faster than transitioning from a known layour
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+
+ // - Required for swapchan presentation
+ // - Vulkan automatically transitions the image to this layout
+ .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
+ };
+ VkAttachmentReference colorRef = {
+ // which attachment index to use (0)
+ .attachment = 0,
+ // which layout it should be during the subpass
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
+ };
+
+ // A subpass is a phase of rendering inside a render pass
+ VkSubpassDescription subpass = {
+ // a graphics subpass
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorRef
+ };
+ VkRenderPassCreateInfo rpInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &att,
+ .subpassCount = 1,
+ .pSubpasses = &subpass
+ };
+ if(vkCreateRenderPass(device,&rpInfo,NULL,&renderPass)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create render pass\n"); exit(1);
+ }
+}
+
+/* --- Framebuffers --- */
+// https://docs.vulkan.org/spec/latest/chapters/framebuffer.html
+// a framebuffer is
+// The collection of images that receive the final output of rendering for a
+// frame.
+void create_framebuffers() {
+ framebuffers = malloc(sizeof(VkFramebuffer)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkFramebufferCreateInfo fbInfo = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = renderPass,
+ .attachmentCount = 1,
+ .pAttachments = &swapchainImageViews[i],
+ .width = swapchainExtent.width,
+ .height = swapchainExtent.height,
+ .layers = 1
+ };
+ if(vkCreateFramebuffer(device,&fbInfo,NULL,&framebuffers[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create framebuffer\n"); exit(1);
+ }
+ }
+}
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+
+ vertexCount = SIZE(cube);
+ Vertex vertices[vertexCount];
+
+ for (int i = 0; i < vertexCount; ++i) {
+ vertices[i].pos[0] = cube[i].x;
+ vertices[i].pos[1] = cube[i].y;
+ vertices[i].pos[2] = cube[i].z;
+
+ vertices[i].color[0] = 1.0f;
+ vertices[i].color[1] = 1.0f;
+ vertices[i].color[2] = 1.0f;
+ }
+
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = sizeof(Vertex) * vertexCount,
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+ if(vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create vertex buffer\n"); exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = 0
+ };
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+ for(uint32_t i=0;i<memProps.memoryTypeCount;i++){
+ if((memReq.memoryTypeBits & (1<<i)) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)){
+ allocInfo.memoryTypeIndex = i;
+ break;
+ }
+ }
+
+ if(vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate vertex buffer memory\n"); exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufInfo.size, 0, &data);
+ memcpy(data, vertices, bufInfo.size);
+ // memcpy(data, vertices, sizeof(Vertex) * vertexCount);
+ vkUnmapMemory(device, vertexBufferMemory);
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("cube.vert.spv");
+ VkShaderModule fragModule = load_shader("cube.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ // One Vertex per vertex
+ // Move forward sizeof(Vertex) each vertex
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(Vertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+
+ // the location variable is the same location variable
+ // in the shader program
+ VkVertexInputAttributeDescription attrDesc[2] = {
+ {
+ .location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = offsetof(Vertex, pos)
+ },
+
+ {
+ .location = 1,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = offsetof(Vertex, color)
+ }
+ };
+
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=2,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ /// topology is how to draw the mesh
+ // .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_BACK_BIT,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPushConstantRange pushRange = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(float)
+ };
+
+ VkPipelineLayoutCreateInfo layoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pushRange
+ };
+
+ vkCreatePipelineLayout(device, &layoutInfo, NULL, &pipelineLayout);
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = { .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = stages;
+ pipelineInfo.pVertexInputState = &vertexInput;
+ pipelineInfo.pInputAssemblyState = &inputAssembly;
+ pipelineInfo.pViewportState = &vpState;
+ pipelineInfo.pRasterizationState = &rasterizer;
+ pipelineInfo.pMultisampleState = &multisampling;
+ pipelineInfo.pColorBlendState = &colorBlending;
+ pipelineInfo.layout = pipelineLayout;
+ pipelineInfo.renderPass = renderPass;
+ pipelineInfo.subpass = 0;
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,&graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+/* --- Record command buffers --- */
+// https://docs.vulkan.org/spec/latest/chapters/renderpass.html
+// https://docs.vulkan.org/spec/latest/chapters/drawing.html#drawing
+void record_command_buffers() {
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkCommandBufferBeginInfo begin={.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
+ vkBeginCommandBuffer(commandBuffers[i],&begin);
+
+ VkClearValue clearColor = {{0.0f,0.0f,0.0f,1.0f}};
+ VkRenderPassBeginInfo rpBegin = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = renderPass,
+ .framebuffer = framebuffers[i],
+ .renderArea = {.offset={0,0},.extent=swapchainExtent},
+ .clearValueCount = 1,
+ .pClearValues = &clearColor
+ };
+
+ vkCmdBeginRenderPass(commandBuffers[i],&rpBegin,VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(commandBuffers[i],VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline);
+
+ VkDeviceSize offsets[] = {0};
+ vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, &vertexBuffer, offsets);
+ vkCmdDraw(commandBuffers[i], vertexCount, 1, 0, 0);
+
+ vkCmdEndRenderPass(commandBuffers[i]);
+ vkEndCommandBuffer(commandBuffers[i]);
+ }
+}
+
+/* --- Draw frame --- */
+void draw_frame() {
+ uint32_t imgIndex;
+ vkAcquireNextImageKHR(device,swapchain,UINT64_MAX,VK_NULL_HANDLE,VK_NULL_HANDLE,&imgIndex);
+
+ VkSubmitInfo submit={.sType=VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount=1,
+ .pCommandBuffers=&commandBuffers[imgIndex]};
+ vkQueueSubmit(graphicsQueue,1,&submit,VK_NULL_HANDLE);
+ vkQueueWaitIdle(graphicsQueue);
+
+ VkPresentInfoKHR present={.sType=VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .swapchainCount=1,
+ .pSwapchains=&swapchain,
+ .pImageIndices=&imgIndex};
+ vkQueuePresentKHR(graphicsQueue,&present);
+}
+
+/* --- Main --- */
+int main(void) {
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+ uint32_t ext_count=0;
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ici,NULL,&instance);
+ volkLoadInstance(instance);
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8]; vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ physical=devices[0];
+
+ uint32_t qcount=0; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ VkQueueFamilyProperties qprops[16]; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres){graphicsQueueFamily=i;found=1;break;}
+ }
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ // defines
+ // - swapchainExtent
+ // - swapchainFormat
+ // - swapchain
+ // - swapchainImages
+ // - swapchainImageCount
+ // - swapchainImageViews
+ create_swapchain();
+ // defines
+ // - renderPass
+ create_render_pass();
+ // defines
+ // - framebuffers
+ create_framebuffers();
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+ create_vertex_buffer();
+ record_command_buffers();
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ angle += 0.01f;
+
+ uint32_t imgIndex;
+ vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
+ VK_NULL_HANDLE, VK_NULL_HANDLE, &imgIndex);
+
+ VkCommandBuffer cb = commandBuffers[imgIndex];
+
+ vkResetCommandBuffer(cb, 0);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ VkClearValue clear = {{0, 0, 0, 1}};
+ VkRenderPassBeginInfo rp = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = renderPass,
+ .framebuffer = framebuffers[imgIndex],
+ .renderArea = {{0,0}, swapchainExtent},
+ .clearValueCount = 1,
+ .pClearValues = &clear
+ };
+
+ vkCmdBeginRenderPass(cb, &rp, VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+
+ VkDeviceSize offset = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vertexBuffer, &offset);
+
+ // 🔥 Push rotation angle
+ vkCmdPushConstants(
+ cb,
+ pipelineLayout,
+ VK_SHADER_STAGE_VERTEX_BIT,
+ 0,
+ sizeof(float),
+ &angle
+ );
+
+ vkCmdDraw(cb, vertexCount, 1, 0, 0);
+
+ vkCmdEndRenderPass(cb);
+ vkEndCommandBuffer(cb);
+
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb
+ };
+ vkQueueSubmit(graphicsQueue, 1, &submit, VK_NULL_HANDLE);
+
+ VkPresentInfoKHR present = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .swapchainCount = 1,
+ .pSwapchains = &swapchain,
+ .pImageIndices = &imgIndex
+ };
+ vkQueuePresentKHR(graphicsQueue, &present);
+ }
+
+ vkDeviceWaitIdle(device);
+
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+ for(uint32_t i=0;i<swapchainImageCount;i++) vkDestroyFramebuffer(device,framebuffers[i],NULL);
+ free(framebuffers); free(commandBuffers); free(swapchainImageViews); free(swapchainImages);
+ vkDestroyPipeline(device,graphicsPipeline,NULL);
+ vkDestroyPipelineLayout(device,pipelineLayout,NULL);
+ vkDestroyRenderPass(device,renderPass,NULL);
+ vkDestroySwapchainKHR(device,swapchain,NULL);
+ vkDestroyDevice(device,NULL);
+ vkDestroySurfaceKHR(instance,surface,NULL);
+ vkDestroyInstance(instance,NULL);
+ glfwDestroyWindow(window); glfwTerminate();
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
+* https://docs.vulkan.org/samples/latest/samples/extensions/dynamic_rendering/README.html
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= /home/nyquist/Development/tools/linux/vulkan-1.4.328.1/x86_64/include/
+
+CFLAGS += -I$(VOLK_PATH)
+
+LIBSDL = `pkg-config --static --libs sdl2 SDL2_image`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBSDL) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag skybox.vert skybox.frag
+SPV = cube.vert.spv cube.frag.spv skybox.vert.spv skybox.frag.spv
+TARGET = vk_plane
+SRC = main.c
+
+all: $(SPV) $(TARGET)
+
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+#version 450
+layout(location = 0) in vec2 inUV;
+layout(location = 0) out vec4 outColor;
+layout(binding = 0) uniform sampler2D texSampler;
+void main() {
+ outColor = texture(texSampler, inUV);
+}
--- /dev/null
+#version 450
+layout(push_constant) uniform Push { mat4 mvp; } pc;
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec2 inUV;
+layout(location = 0) out vec2 outUV;
+void main() {
+ gl_Position = pc.mvp * vec4(inPos, 1.0);
+ outUV = inUV;
+}
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_vulkan.h>
+#include <SDL2/SDL_image.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <math.h>
+
+#ifndef M_PI
+ #define M_PI 3.14159265358979323846
+#endif
+
+// --- MATH ---
+typedef struct { float x, y, z; } Vec3;
+typedef struct { float m[4][4]; } Mat4;
+
+Vec3 v3_sub(Vec3 a, Vec3 b) { return (Vec3){a.x-b.x, a.y-b.y, a.z-b.z}; }
+Vec3 v3_add(Vec3 a, Vec3 b) { return (Vec3){a.x+b.x, a.y+b.y, a.z+b.z}; }
+Vec3 v3_mul(Vec3 a, float s) { return (Vec3){a.x*s, a.y*s, a.z*s}; }
+float v3_dot(Vec3 a, Vec3 b) { return a.x*b.x + a.y*b.y + a.z*b.z; }
+Vec3 v3_cross(Vec3 a, Vec3 b) { return (Vec3){a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z, a.x*b.y-a.y*b.x}; }
+Vec3 v3_norm(Vec3 v) { float l = sqrtf(v3_dot(v, v)); return (l>0)?v3_mul(v, 1.0f/l):v; }
+
+Mat4 m4_id() { Mat4 r={0}; for(int i=0;i<4;i++) r.m[i][i]=1; return r; }
+Mat4 m4_mul(Mat4 a, Mat4 b) {
+ Mat4 r={0};
+ for(int i=0;i<4;i++) for(int j=0;j<4;j++) for(int k=0;k<4;k++)
+ r.m[i][j] += a.m[i][k] * b.m[k][j];
+ return r;
+}
+Mat4 m4_persp(float fov, float asp, float n, float f) {
+ float t = tanf(fov/2); Mat4 r={0};
+ r.m[0][0]=1/(asp*t); r.m[1][1]=-1/t; r.m[2][2]=f/(n-f); r.m[2][3]=-1; r.m[3][2]=(n*f)/(n-f);
+ return r;
+}
+Mat4 m4_lookat(Vec3 eye, Vec3 ctr, Vec3 up) {
+ Vec3 f=v3_norm(v3_sub(ctr, eye)), s=v3_norm(v3_cross(f, up)), u=v3_cross(s, f);
+ Mat4 r=m4_id();
+ r.m[0][0]=s.x; r.m[1][0]=s.y; r.m[2][0]=s.z;
+ r.m[0][1]=u.x; r.m[1][1]=u.y; r.m[2][1]=u.z;
+ r.m[0][2]=-f.x; r.m[1][2]=-f.y; r.m[2][2]=-f.z;
+ r.m[3][0]=-v3_dot(s, eye); r.m[3][1]=-v3_dot(u, eye); r.m[3][2]=v3_dot(f, eye);
+ return r;
+}
+
+// --- VULKAN HELPERS ---
+typedef struct { float pos[3], uv[2]; } Vertex;
+
+uint32_t find_mem(VkPhysicalDevice p, uint32_t f, VkMemoryPropertyFlags pr) {
+ VkPhysicalDeviceMemoryProperties m; vkGetPhysicalDeviceMemoryProperties(p, &m);
+ for(uint32_t i=0; i<m.memoryTypeCount; i++) if((f&(1<<i)) && (m.memoryTypes[i].propertyFlags&pr)==pr) return i;
+ return 0;
+}
+
+void create_buf(VkDevice d, VkPhysicalDevice p, VkDeviceSize size, VkBufferUsageFlags u, VkMemoryPropertyFlags pr, VkBuffer* b, VkDeviceMemory* m) {
+ VkBufferCreateInfo bi = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size=size, .usage=u, .sharingMode=VK_SHARING_MODE_EXCLUSIVE};
+ if(vkCreateBuffer(d, &bi, NULL, b) != VK_SUCCESS) { printf("Failed buffer\n"); exit(1); }
+ VkMemoryRequirements req; vkGetBufferMemoryRequirements(d, *b, &req);
+ VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize=req.size, .memoryTypeIndex=find_mem(p, req.memoryTypeBits, pr)};
+ vkAllocateMemory(d, &ai, NULL, m); vkBindBufferMemory(d, *b, *m, 0);
+}
+
+// Shader Loader
+VkShaderModule load_shader(VkDevice dev, const char* path) {
+ FILE* f = fopen(path, "rb"); if(!f) { printf("Failed to load %s\n", path); exit(1); }
+ fseek(f, 0, 2); long size = ftell(f); rewind(f);
+ char* buf = malloc(size); fread(buf, 1, size, f); fclose(f);
+ VkShaderModuleCreateInfo ci = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize=size, .pCode=(uint32_t*)buf};
+ VkShaderModule sm; vkCreateShaderModule(dev, &ci, NULL, &sm);
+ free(buf); return sm;
+}
+
+// --- APP STATE ---
+Vec3 camPos = {0, 0, 3}, camFwd = {0, 0, -1};
+float yaw = -90.0f, pitch = 0.0f;
+
+int main(int argc, char** argv) {
+ float scale = 1.0f;
+
+ volkInitialize();
+ SDL_Init(SDL_INIT_VIDEO);
+ IMG_Init(IMG_INIT_PNG);
+ SDL_Window* win = SDL_CreateWindow("Vulkan Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_VULKAN);
+ SDL_SetRelativeMouseMode(SDL_TRUE);
+
+ uint32_t extC=0; SDL_Vulkan_GetInstanceExtensions(win, &extC, NULL);
+ const char** exts = malloc(sizeof(char*)*extC); SDL_Vulkan_GetInstanceExtensions(win, &extC, exts);
+ VkInstance inst; VkApplicationInfo app={VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion=VK_API_VERSION_1_3};
+ VkInstanceCreateInfo ic={VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo=&app, .enabledExtensionCount=extC, .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ic, NULL, &inst); volkLoadInstance(inst);
+ VkSurfaceKHR surf; SDL_Vulkan_CreateSurface(win, inst, &surf);
+
+ VkPhysicalDevice phys; uint32_t pc=1; vkEnumeratePhysicalDevices(inst, &pc, &phys);
+ float pri=1.0f; VkDeviceQueueCreateInfo qc={VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueCount=1, .pQueuePriorities=&pri};
+ VkPhysicalDeviceDynamicRenderingFeatures df={VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, .dynamicRendering=VK_TRUE};
+ const char* de[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME};
+ VkDevice dev; VkDeviceCreateInfo dc={VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext=&df, .queueCreateInfoCount=1, .pQueueCreateInfos=&qc, .enabledExtensionCount=2, .ppEnabledExtensionNames=de};
+ vkCreateDevice(phys, &dc, NULL, &dev); volkLoadDevice(dev);
+ VkQueue q; vkGetDeviceQueue(dev, 0, 0, &q);
+
+ VkSwapchainKHR swp; VkExtent2D ext={1280,720};
+ VkSwapchainCreateInfoKHR sc={VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface=surf, .minImageCount=2, .imageFormat=VK_FORMAT_B8G8R8A8_UNORM, .imageColorSpace=VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, .imageExtent=ext, .imageArrayLayers=1, .imageUsage=VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .preTransform=VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, .compositeAlpha=VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode=VK_PRESENT_MODE_FIFO_KHR};
+ vkCreateSwapchainKHR(dev, &sc, NULL, &swp);
+ uint32_t icount; vkGetSwapchainImagesKHR(dev, swp, &icount, NULL);
+ VkImage simg[icount]; vkGetSwapchainImagesKHR(dev, swp, &icount, simg);
+ VkImageView siv[icount];
+ for(int i=0; i<icount; i++){
+ VkImageViewCreateInfo iv={VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image=simg[i], .viewType=VK_IMAGE_VIEW_TYPE_2D, .format=VK_FORMAT_B8G8R8A8_UNORM, .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}};
+ vkCreateImageView(dev, &iv, NULL, &siv[i]);
+ }
+
+ VkCommandPool cp; VkCommandPoolCreateInfo cpi={VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags=VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex=0};
+ vkCreateCommandPool(dev, &cpi, NULL, &cp);
+ VkCommandBuffer cb; VkCommandBufferAllocateInfo cai={VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool=cp, .level=VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount=1};
+ vkAllocateCommandBuffers(dev, &cai, &cb);
+
+ // --- LOAD PLANE TEXTURE ---
+ SDL_Surface* tmpPlane = IMG_Load("smile.png");
+ if(!tmpPlane){ printf("Missing smile.png\n"); exit(1); }
+ SDL_Surface* sPlane = SDL_ConvertSurfaceFormat(tmpPlane, SDL_PIXELFORMAT_ABGR8888, 0);
+ SDL_FreeSurface(tmpPlane);
+ float asp = (float)sPlane->w / sPlane->h;
+ VkDeviceSize planeSize = (VkDeviceSize)sPlane->w * sPlane->h * 4;
+
+ VkBuffer planeStg; VkDeviceMemory planeStgMem;
+ create_buf(dev, phys, planeSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &planeStg, &planeStgMem);
+ void* data; vkMapMemory(dev, planeStgMem, 0, planeSize, 0, &data); memcpy(data, sPlane->pixels, planeSize); vkUnmapMemory(dev, planeStgMem);
+
+ VkImage planeImg; VkDeviceMemory planeMem;
+ VkImageCreateInfo pti={VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType=VK_IMAGE_TYPE_2D, .format=VK_FORMAT_R8G8B8A8_UNORM, .extent={sPlane->w,sPlane->h,1}, .mipLevels=1, .arrayLayers=1, .samples=VK_SAMPLE_COUNT_1_BIT, .tiling=VK_IMAGE_TILING_OPTIMAL, .usage=VK_IMAGE_USAGE_TRANSFER_DST_BIT|VK_IMAGE_USAGE_SAMPLED_BIT};
+ vkCreateImage(dev, &pti, NULL, &planeImg);
+ VkMemoryRequirements ptr; vkGetImageMemoryRequirements(dev, planeImg, &ptr);
+ VkMemoryAllocateInfo pta={VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize=ptr.size, .memoryTypeIndex=find_mem(phys, ptr.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)};
+ vkAllocateMemory(dev, &pta, NULL, &planeMem); vkBindImageMemory(dev, planeImg, planeMem, 0);
+
+ // --- LOAD SKYBOX TEXTURE ---
+ const char* faces[] = {"posx.png", "negx.png", "posy.png", "negy.png", "posz.png", "negz.png"};
+ SDL_Surface* skySurfs[6];
+ for(int i=0; i<6; i++) {
+ SDL_Surface* tmp = IMG_Load(faces[i]);
+ if(!tmp){ printf("Missing %s\n", faces[i]); exit(1); }
+ skySurfs[i] = SDL_ConvertSurfaceFormat(tmp, SDL_PIXELFORMAT_ABGR8888, 0);
+ SDL_FreeSurface(tmp);
+ }
+ VkDeviceSize faceSize = (VkDeviceSize)skySurfs[0]->w * skySurfs[0]->h * 4;
+ VkDeviceSize skySize = faceSize * 6;
+
+ VkBuffer skyStg; VkDeviceMemory skyStgMem;
+ create_buf(dev, phys, skySize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &skyStg, &skyStgMem);
+ vkMapMemory(dev, skyStgMem, 0, skySize, 0, &data);
+ for(int i=0; i<6; i++) { memcpy((char*)data + (i * faceSize), skySurfs[i]->pixels, faceSize); }
+ vkUnmapMemory(dev, skyStgMem);
+
+ VkImage skyImg; VkDeviceMemory skyMem;
+ VkImageCreateInfo sti={VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .flags=VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT, .imageType=VK_IMAGE_TYPE_2D, .format=VK_FORMAT_R8G8B8A8_UNORM, .extent={skySurfs[0]->w,skySurfs[0]->h,1}, .mipLevels=1, .arrayLayers=6, .samples=VK_SAMPLE_COUNT_1_BIT, .tiling=VK_IMAGE_TILING_OPTIMAL, .usage=VK_IMAGE_USAGE_TRANSFER_DST_BIT|VK_IMAGE_USAGE_SAMPLED_BIT};
+ vkCreateImage(dev, &sti, NULL, &skyImg);
+ VkMemoryRequirements str; vkGetImageMemoryRequirements(dev, skyImg, &str);
+ VkMemoryAllocateInfo sta={VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize=str.size, .memoryTypeIndex=find_mem(phys, str.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)};
+ vkAllocateMemory(dev, &sta, NULL, &skyMem); vkBindImageMemory(dev, skyImg, skyMem, 0);
+
+ // --- UPLOAD COMMANDS ---
+ VkCommandBufferBeginInfo cbi={VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags=VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
+ vkBeginCommandBuffer(cb, &cbi);
+
+ // Plane Barrier 1
+ VkImageMemoryBarrier pb1={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .dstAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout=VK_IMAGE_LAYOUT_UNDEFINED, .newLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .image=planeImg, .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &pb1);
+ // Skybox Barrier 1
+ VkImageMemoryBarrier sb1={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .dstAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout=VK_IMAGE_LAYOUT_UNDEFINED, .newLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .image=skyImg, .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,6}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &sb1);
+
+ // Copies
+ VkBufferImageCopy pcpy={.imageSubresource={VK_IMAGE_ASPECT_COLOR_BIT,0,0,1}, .imageExtent={sPlane->w,sPlane->h,1}};
+ vkCmdCopyBufferToImage(cb, planeStg, planeImg, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &pcpy);
+
+ VkBufferImageCopy scpy={.imageSubresource={VK_IMAGE_ASPECT_COLOR_BIT,0,0,6}, .imageExtent={skySurfs[0]->w,skySurfs[0]->h,1}};
+ vkCmdCopyBufferToImage(cb, skyStg, skyImg, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &scpy);
+
+ // Plane Barrier 2
+ VkImageMemoryBarrier pb2={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask=VK_ACCESS_SHADER_READ_BIT, .oldLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .newLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .image=planeImg, .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &pb2);
+ // Skybox Barrier 2
+ VkImageMemoryBarrier sb2={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask=VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask=VK_ACCESS_SHADER_READ_BIT, .oldLayout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .newLayout=VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .image=skyImg, .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,6}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &sb2);
+ vkEndCommandBuffer(cb);
+
+ VkSubmitInfo si={VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount=1, .pCommandBuffers=&cb};
+ vkQueueSubmit(q, 1, &si, VK_NULL_HANDLE); vkQueueWaitIdle(q);
+
+ // Views & Sampler
+ VkImageView pView; VkImageViewCreateInfo pvc={VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image=planeImg, .viewType=VK_IMAGE_VIEW_TYPE_2D, .format=VK_FORMAT_R8G8B8A8_UNORM, .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}};
+ vkCreateImageView(dev, &pvc, NULL, &pView);
+ VkImageView sView; VkImageViewCreateInfo svc={VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image=skyImg, .viewType=VK_IMAGE_VIEW_TYPE_CUBE, .format=VK_FORMAT_R8G8B8A8_UNORM, .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,6}};
+ vkCreateImageView(dev, &svc, NULL, &sView);
+
+ VkSampler smp; VkSamplerCreateInfo smpi={VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter=VK_FILTER_LINEAR, .minFilter=VK_FILTER_LINEAR, .addressModeU=VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV=VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW=VK_SAMPLER_ADDRESS_MODE_REPEAT};
+ vkCreateSampler(dev, &smpi, NULL, &smp);
+
+ // --- PLANE GEOMETRY (Upside down fix applied) ---
+ float w=asp*scale, h=1.0f*scale;
+ Vertex vtx[] = {{{-w,-h,0},{0,1}}, {{w,-h,0},{1,1}}, {{w,h,0},{1,0}}, {{-w,h,0},{0,0}}};
+ uint16_t idx[] = {0,1,2, 2,3,0};
+ VkBuffer vb, ib; VkDeviceMemory vbm, ibm;
+ create_buf(dev, phys, sizeof(vtx), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &vb, &vbm);
+ create_buf(dev, phys, sizeof(idx), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &ib, &ibm);
+ vkMapMemory(dev, vbm, 0, sizeof(vtx), 0, &data); memcpy(data, vtx, sizeof(vtx)); vkUnmapMemory(dev, vbm);
+ vkMapMemory(dev, ibm, 0, sizeof(idx), 0, &data); memcpy(data, idx, sizeof(idx)); vkUnmapMemory(dev, ibm);
+
+ // --- DESCRIPTORS (2 Bindings) ---
+ VkDescriptorSetLayoutBinding lb[]={
+ {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, NULL},
+ {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, NULL}
+ };
+ VkDescriptorSetLayoutCreateInfo dlc={VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount=2, .pBindings=lb};
+ VkDescriptorSetLayout dsl; vkCreateDescriptorSetLayout(dev, &dlc, NULL, &dsl);
+
+ VkPushConstantRange pcr={VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(Mat4)};
+ VkPipelineLayoutCreateInfo plc={VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount=1, .pSetLayouts=&dsl, .pushConstantRangeCount=1, .pPushConstantRanges=&pcr};
+ VkPipelineLayout play; vkCreatePipelineLayout(dev, &plc, NULL, &play);
+
+ VkDescriptorPoolSize ps={VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2};
+ VkDescriptorPool dp; VkDescriptorPoolCreateInfo dpc={VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets=1, .poolSizeCount=1, .pPoolSizes=&ps};
+ vkCreateDescriptorPool(dev, &dpc, NULL, &dp);
+ VkDescriptorSet dset; VkDescriptorSetAllocateInfo dsa={VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool=dp, .descriptorSetCount=1, .pSetLayouts=&dsl};
+ vkAllocateDescriptorSets(dev, &dsa, &dset);
+
+ VkDescriptorImageInfo pdi={smp, pView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
+ VkDescriptorImageInfo sdi={smp, sView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
+ VkWriteDescriptorSet wds[]={
+ {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet=dset, .dstBinding=0, .descriptorCount=1, .descriptorType=VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo=&pdi},
+ {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet=dset, .dstBinding=1, .descriptorCount=1, .descriptorType=VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo=&sdi}
+ };
+ vkUpdateDescriptorSets(dev, 2, wds, 0, NULL);
+
+ // --- PIPELINES ---
+ VkShaderModule mv_p = load_shader(dev, "cube.vert.spv");
+ VkShaderModule mf_p = load_shader(dev, "cube.frag.spv");
+ VkShaderModule mv_s = load_shader(dev, "skybox.vert.spv");
+ VkShaderModule mf_s = load_shader(dev, "skybox.frag.spv");
+
+ VkPipelineShaderStageCreateInfo ssP[] = {{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage=VK_SHADER_STAGE_VERTEX_BIT, .module=mv_p, .pName="main"}, {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage=VK_SHADER_STAGE_FRAGMENT_BIT, .module=mf_p, .pName="main"}};
+ VkPipelineShaderStageCreateInfo ssS[] = {{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage=VK_SHADER_STAGE_VERTEX_BIT, .module=mv_s, .pName="main"}, {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage=VK_SHADER_STAGE_FRAGMENT_BIT, .module=mf_s, .pName="main"}};
+
+ VkVertexInputBindingDescription bd={0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX};
+ VkVertexInputAttributeDescription ad[] = {{0,0,VK_FORMAT_R32G32B32_SFLOAT,0}, {1,0,VK_FORMAT_R32G32_SFLOAT,sizeof(float)*3}};
+
+ VkPipelineVertexInputStateCreateInfo viP={VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount=1, .pVertexBindingDescriptions=&bd, .vertexAttributeDescriptionCount=2, .pVertexAttributeDescriptions=ad};
+ VkPipelineVertexInputStateCreateInfo viS={VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; // Skybox needs no vertex input
+
+ VkPipelineInputAssemblyStateCreateInfo ia={VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology=VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
+ VkPipelineViewportStateCreateInfo vp={VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount=1, .scissorCount=1};
+ VkPipelineRasterizationStateCreateInfo rs={VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .lineWidth=1.0f, .cullMode=VK_CULL_MODE_NONE, .frontFace=VK_FRONT_FACE_COUNTER_CLOCKWISE};
+ VkPipelineMultisampleStateCreateInfo ms={VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples=VK_SAMPLE_COUNT_1_BIT};
+ VkPipelineColorBlendAttachmentState cba={.colorWriteMask=0xF, .blendEnable=VK_FALSE};
+ VkPipelineColorBlendStateCreateInfo cbsc={VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .attachmentCount=1, .pAttachments=&cba};
+ VkDynamicState dyns[]={VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+ VkPipelineDynamicStateCreateInfo dy={VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .dynamicStateCount=2, .pDynamicStates=dyns};
+ VkPipelineRenderingCreateInfo prc={VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, .colorAttachmentCount=1, .pColorAttachmentFormats=&sc.imageFormat};
+
+ VkGraphicsPipelineCreateInfo gpc={VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext=&prc, .stageCount=2, .pStages=ssP, .pVertexInputState=&viP, .pInputAssemblyState=&ia, .pViewportState=&vp, .pRasterizationState=&rs, .pMultisampleState=&ms, .pColorBlendState=&cbsc, .pDynamicState=&dy, .layout=play};
+ VkPipeline pipePlane, pipeSkybox;
+ vkCreateGraphicsPipelines(dev, NULL, 1, &gpc, NULL, &pipePlane);
+
+ gpc.pStages = ssS;
+ gpc.pVertexInputState = &viS;
+ vkCreateGraphicsPipelines(dev, NULL, 1, &gpc, NULL, &pipeSkybox);
+
+ VkSemaphore sem_img, sem_rn; VkSemaphoreCreateInfo smi={VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
+ vkCreateSemaphore(dev, &smi, NULL, &sem_img); vkCreateSemaphore(dev, &smi, NULL, &sem_rn);
+ VkFence fn; VkFenceCreateInfo fci={VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags=VK_FENCE_CREATE_SIGNALED_BIT};
+ vkCreateFence(dev, &fci, NULL, &fn);
+
+ bool run=1; SDL_Event e;
+ while(run){
+ while(SDL_PollEvent(&e)){ if(e.type==SDL_QUIT) run=0;
+ if(e.type==SDL_MOUSEMOTION){ yaw+=e.motion.xrel*0.1f; pitch-=e.motion.yrel*0.1f; pitch=fmaxf(-89,fminf(89,pitch)); }
+ }
+ const uint8_t* k=SDL_GetKeyboardState(NULL); float spd=0.05f;
+ camFwd.x=cosf(yaw*M_PI/180)*cosf(pitch*M_PI/180); camFwd.y=sinf(pitch*M_PI/180); camFwd.z=sinf(yaw*M_PI/180)*cosf(pitch*M_PI/180); camFwd=v3_norm(camFwd);
+ Vec3 rgt=v3_norm(v3_cross(camFwd,(Vec3){0,1,0}));
+ if(k[SDL_SCANCODE_W]) camPos=v3_add(camPos,v3_mul(camFwd,spd)); if(k[SDL_SCANCODE_S]) camPos=v3_sub(camPos,v3_mul(camFwd,spd));
+ if(k[SDL_SCANCODE_A]) camPos=v3_sub(camPos,v3_mul(rgt,spd)); if(k[SDL_SCANCODE_D]) camPos=v3_add(camPos,v3_mul(rgt,spd));
+
+ vkWaitForFences(dev,1,&fn,1,UINT64_MAX); vkResetFences(dev,1,&fn);
+ uint32_t idx; vkAcquireNextImageKHR(dev,swp,UINT64_MAX,sem_img,0,&idx);
+ vkResetCommandPool(dev,cp,0); vkBeginCommandBuffer(cb,&cbi);
+
+ VkImageMemoryBarrier ibr={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .dstAccessMask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .oldLayout=VK_IMAGE_LAYOUT_UNDEFINED, .newLayout=VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .image=simg[idx], .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, NULL, 0, NULL, 1, &ibr);
+
+ VkRenderingAttachmentInfo rai={.imageView=siv[idx], .imageLayout=VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .loadOp=VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp=VK_ATTACHMENT_STORE_OP_STORE, .clearValue={0,0,0,1}};
+ VkRenderingInfo ri={VK_STRUCTURE_TYPE_RENDERING_INFO, .renderArea={{0,0},ext}, .layerCount=1, .colorAttachmentCount=1, .pColorAttachments=&rai};
+ vkCmdBeginRendering(cb, &ri);
+
+ VkViewport v={0,0,ext.width,ext.height,0,1}; vkCmdSetViewport(cb,0,1,&v);
+ VkRect2D sc={0,0,ext.width,ext.height}; vkCmdSetScissor(cb,0,1,&sc);
+
+ // Matrices
+ Mat4 proj=m4_persp(M_PI/4, (float)ext.width/ext.height, 0.1f, 100.0f);
+ Mat4 view=m4_lookat(camPos, v3_add(camPos,camFwd), (Vec3){0,1,0});
+
+ // 1. Draw Skybox (Remove translation from view)
+ Mat4 viewSky = view;
+ viewSky.m[3][0] = 0; viewSky.m[3][1] = 0; viewSky.m[3][2] = 0;
+ Mat4 mvpSky = m4_mul(viewSky, proj);
+
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeSkybox);
+ vkCmdPushConstants(cb, play, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(Mat4), &mvpSky);
+ vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, play, 0, 1, &dset, 0, NULL);
+ vkCmdDraw(cb, 36, 1, 0, 0); // 36 vertices generated in shader
+
+ // 2. Draw Plane
+ Mat4 mvpPlane = m4_mul(view, proj);
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipePlane);
+ vkCmdPushConstants(cb, play, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(Mat4), &mvpPlane);
+ VkDeviceSize off=0; vkCmdBindVertexBuffers(cb,0,1,&vb,&off); vkCmdBindIndexBuffer(cb,ib,0,VK_INDEX_TYPE_UINT16);
+ vkCmdDrawIndexed(cb,6,1,0,0,0);
+
+ vkCmdEndRendering(cb);
+
+ VkImageMemoryBarrier pbr={VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .oldLayout=VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .newLayout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .image=simg[idx], .subresourceRange={VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &pbr);
+ vkEndCommandBuffer(cb);
+
+ VkPipelineStageFlags wait=VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo sub={VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount=1, .pWaitSemaphores=&sem_img, .pWaitDstStageMask=&wait, .commandBufferCount=1, .pCommandBuffers=&cb, .signalSemaphoreCount=1, .pSignalSemaphores=&sem_rn};
+ vkQueueSubmit(q,1,&sub,fn);
+ VkPresentInfoKHR pres={VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount=1, .pWaitSemaphores=&sem_rn, .swapchainCount=1, .pSwapchains=&swp, .pImageIndices=&idx};
+ vkQueuePresentKHR(q, &pres);
+ }
+ vkDeviceWaitIdle(dev); return 0;
+}
--- /dev/null
+#version 450
+layout(location = 0) in vec3 inUVW;
+layout(location = 0) out vec4 outColor;
+layout(binding = 1) uniform samplerCube skySampler;
+void main() {
+ outColor = texture(skySampler, inUVW);
+}
--- /dev/null
+#version 450
+layout(push_constant) uniform Push { mat4 mvp; } pc;
+layout(location = 0) out vec3 outUVW;
+
+// Hardcoded cube vertices
+vec3 pos[8] = vec3[](
+ vec3(-1,-1,-1), vec3(1,-1,-1), vec3(1,1,-1), vec3(-1,1,-1),
+ vec3(-1,-1,1), vec3(1,-1,1), vec3(1,1,1), vec3(-1,1,1)
+);
+int idx[36] = int[](
+ 0,1,2, 2,3,0, 4,5,6, 6,7,4, 1,5,6, 6,2,1, 0,4,7, 7,3,0, 3,2,6, 6,7,3, 0,1,5, 5,4,0
+);
+
+void main() {
+ vec3 vPos = pos[idx[gl_VertexIndex]];
+ // Force Z to W so it always renders at maximum depth (behind everything)
+ vec4 clipPos = pc.mvp * vec4(vPos, 1.0);
+ gl_Position = clipPos.xyww;
+ outUVW = vPos;
+}
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= /home/nyquist/Development/tools/linux/vulkan-1.4.328.1/x86_64/include/
+
+CFLAGS += -I$(VOLK_PATH)
+
+LIBSDL = `pkg-config --static --libs sdl2 SDL2_image`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBSDL) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag
+SPV = cube.vert.spv cube.frag.spv
+TARGET = vk_plane
+SRC = main.c
+
+all: $(SPV) $(TARGET)
+
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+[cite_start]The `VkImageMemoryBarrier` is a synchronization tool used to transition an image between different **layouts** and to define which **memory operations** must finish before others begin[cite: 78, 102]. [cite_start]In your code, `b1` is preparing the image to receive the pixel data from the staging buffer[cite: 78, 79].
+
+### **Member-by-Member Breakdown**
+
+* [cite_start]**`sType`**: Set to `VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER`[cite: 78]. [cite_start]This is a standard Vulkan requirement so the driver knows which structure is being passed to the function[cite: 78].
+* [cite_start]**`.dstAccessMask`**: Set to `VK_ACCESS_TRANSFER_WRITE_BIT`[cite: 78].
+ * [cite_start]This tells the GPU that the *destination* of this barrier (what happens after) involves a **write operation** during a data transfer[cite: 78, 79].
+ * [cite_start]Since you are about to use `vkCmdCopyBufferToImage`, the GPU needs to "unlock" the image for writing[cite: 79].
+* [cite_start]**`.oldLayout`**: Set to `VK_IMAGE_LAYOUT_UNDEFINED`[cite: 78].
+ * [cite_start]This indicates the layout the image is currently in[cite: 78].
+ * [cite_start]Using `UNDEFINED` tells Vulkan you don't care about the previous contents of the memory (which is true, since it's brand new and empty)[cite: 78].
+* [cite_start]**`.newLayout`**: Set to `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`[cite: 78].
+ * [cite_start]This is the target layout[cite: 78].
+ * [cite_start]`TRANSFER_DST_OPTIMAL` is the specific memory arrangement the GPU hardware needs to efficiently receive a stream of bytes from a buffer[cite: 79].
+* [cite_start]**`.image`**: Set to `timg`[cite: 78].
+ * [cite_start]This specifies which specific image handle this barrier applies to[cite: 75, 78].
+* [cite_start]**`.subresourceRange`**: Defines exactly which part of the image is being transitioned[cite: 78].
+ * [cite_start]**`aspectMask`**: `VK_IMAGE_ASPECT_COLOR_BIT` (you are treating this as color data, not depth)[cite: 78].
+ * [cite_start]**`baseMipLevel` / `levelCount`**: `0, 1` (applies to the first mipmap level only)[cite: 78].
+ * [cite_start]**`baseArrayLayer` / `layerCount`**: `0, 1` (applies to the first layer of the image)[cite: 78].
+
+
+
+### **Why is this necessary?**
+[cite_start]GPUs are highly parallel and often perform operations out of order[cite: 67, 107]. [cite_start]Without this barrier, the GPU might try to start copying the PNG pixels while the image memory is still being initialized or in a "discard" state, leading to corruption or crashes[cite: 78, 102]. [cite_start]The barrier forces the GPU to "pause" that specific image until the layout is correctly formatted for writing[cite: 79, 80].
--- /dev/null
+In this section of your **fascia** engine, you are implementing a **First-Person
+Camera** (often called a "Free Look" camera). Since you are using a 3D
+coordinate system, you are converting 2D mouse movements (horizontal and
+vertical) into a 3D direction vector that your `m4_lookat` function can use.
+
+Here is the breakdown of the trigonometry involved:
+
+### 1. Accumulating Rotation (Euler Angles)
+The `yaw` and `pitch` variables represent your rotation around the axes.
+* **Yaw:** Rotation around the Y-axis (looking left/right).
+* **Pitch:** Rotation around the X-axis (looking up/down).
+
+```c
+yaw += e.motion.xrel * 0.1f;
+pitch -= e.motion.yrel * 0.1f;
+pitch = fmaxf(-89, fminf(89, pitch));
+```
+
+The `fmaxf`/`fminf` part is a **clamping** operation. It prevents the "Gimbal
+Lock" effect or the camera flipping upside down by stopping you from looking
+further than 89° straight up or down.
+
+---
+
+### 2. Converting Degrees to Radians
+C math functions like `cosf` and `sinf` expect **radians**, not degrees. Your code performs this conversion in-line:
+$$\text{radians} = \text{degrees} \times \frac{\pi}{180}$$
+
+---
+
+### 3. Spherical to Cartesian Coordinates
+This is the core of the camera logic. You are calculating the `camFwd` (Forward
+Vector). Imagine your camera is at the center of a sphere with a radius of 1.
+You need to find the $(x, y, z)$ point on that sphere's surface that corresponds
+to your rotation.
+
+
+
+The formulas derived from spherical trigonometry are:
+
+$$x = \cos(\text{yaw}) \cdot \cos(\text{pitch})$$
+$$y = \sin(\text{pitch})$$
+$$z = \sin(\text{yaw}) \cdot \cos(\text{pitch})$$
+
+#### Why the $\cos(\text{pitch})$ multiplier for $x$ and $z$?
+As you look further up or down (increasing pitch), the horizontal circle you are
+looking "around" gets smaller. If you didn't multiply $x$ and $z$ by
+$\cos(\text{pitch})$, the camera would move faster horizontally when looking at
+the horizon and slower when looking at the sky. This multiplier keeps the
+"length" of the vector consistent.
+
+---
+
+### 4. Vector Normalization
+```c
+camFwd = v3_norm(camFwd);
+```
+Even though the trigonometry theoretically produces a unit vector (length of 1),
+floating-point errors can cause the vector to grow or shrink slightly over time.
+Normalizing ensures the length is exactly 1, which is critical for consistent
+movement speed when you later add `camPos` to it:
+
+$$\hat{V} = \frac{V}{\|V\|}$$
+
+### Summary of the Math
+| Variable | Represents | Formula Component |
+| :--- | :--- | :--- |
+| **`e.motion.xrel`** | Mouse Horizontal | $\Delta \text{Yaw}$ |
+| **`e.motion.yrel`** | Mouse Vertical | $\Delta \text{Pitch}$ |
+| **`camFwd.y`** | Height | $\sin(\phi)$ |
+| **`camFwd.xz`** | Depth/Breadth | $\cos(\phi)$ components |
+
+Now that you have the `camFwd` vector, your `m4_lookat` function uses it to
+build the View Matrix, effectively rotating the entire world around the camera
+so the plane appears in the correct spot on your ultra-wide monitor. Are the
+Long Sword-style quick-turns next on the feature list, or is this just for
+static inspection?
--- /dev/null
+In this block, you are creating a **Descriptor Pool**. If the *Descriptor Set Layout* (from your previous snippet) is the "blueprint" for how data should look, the **Descriptor Pool** is the actual "storage locker" where the memory for those sets is allocated.
+
+Vulkan does not allow you to just "create" a descriptor set out of thin air; you must pre-allocate a chunk of memory specifically for them to live in.
+
+### 1. Defining the Pool Size
+```c
+VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1};
+```
+Here, you are telling the GPU exactly what kind of "slots" you need inside this pool.
+* **Type:** `VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`. You are saying this pool will store descriptors for textures (images + samplers).
+* **Count (1):** You are only asking for enough memory to hold **one** texture descriptor.
+
+### 2. The Pool's Capacity
+```c
+VkDescriptorPoolCreateInfo dpc = {
+ .maxSets = 1,
+ .poolSizeCount = 1,
+ .pPoolSizes = &ps
+};
+```
+This structure defines the overall limits of the pool:
+* **`.maxSets = 1`**: This is the total number of "Descriptor Sets" (groups of bindings) that can be allocated from this pool at once. Since your **fascia** engine currently only renders one plane with one texture, a limit of 1 is perfect.
+* **`.pPoolSizes = &ps`**: This links back to the size you defined above, confirming that the memory in this pool is specifically formatted for image samplers.
+
+
+
+### 3. Creation
+```c
+vkCreateDescriptorPool(dev, &dpc, NULL, &dp);
+```
+This final call talks to the driver and carves out that tiny bit of memory on the GPU. Once this `dp` (Descriptor Pool) is created, you can then proceed to "allocate" your actual `VkDescriptorSet` from it.
+
+---
+
+### Why is this necessary?
+Vulkan requires this because high-performance GPUs handle memory differently than a standard CPU. By pre-declaring that you only need **one set** and **one sampler**, the driver can optimize the memory layout for maximum speed during your render loop.
+
+**What's next in your monolith?** Usually, right after this, you'll see a call to `vkAllocateDescriptorSets` which actually reaches into this pool and grabs the "handle" you'll use to bind your texture. Are you planning to add more textures later, or is this strictly a single-image viewer for now?
--- /dev/null
+In 3D computer graphics, **W** is the fourth component of a 4D coordinate, known
+as a **Homogeneous Coordinate** $(x, y, z, w)$.
+
+While we live in a 3D world $(x, y, z)$, the math used by GPUs (and your
+**fascia** engine) uses 4D vectors because it makes complex transformations like
+translation and perspective possible with simple matrix multiplication.
+
+Here is what **W** specifically represents in your project:
+
+### 1. The "Perspective" Switch The most important job of $W$ in your `m4_persp`
+function is to store the distance from the camera.
+* In your matrix, you have the line `r.m[2][3] = -1;`.
+* When a 3D point is multiplied by this matrix, its original $Z$ value (how far
+away it is) gets moved into the $W$ slot.
+* The GPU then performs the **Perspective Divide**: it divides $x, y, \text{ and
+} z$ by $w$.
+
+If $W$ is large (the object is far away), the resulting $x$ and $y$ become very
+small, moving them toward the center of the screen. This is exactly how the
+"vanishing point" effect is created.
+
+
+
+---
+
+### 2. Differentiating Points vs. Directions In a 4x4 matrix system, $W$ tells
+the math whether it is dealing with a physical location or just a direction:
+* **$W = 1$ (A Point):** Represents a specific location in space. If you
+translate (move) a point, its position changes.
+* **$W = 0$ (A Direction):** Represents a vector with no fixed position (like
+your `camFwd`). If you translate a direction, it stays the same (e.g., "North"
+is still "North" no matter where you are standing).
+
+---
+
+### 3. The "Normalized Device Coordinate" (NDC) Vulkan expects coordinates to be
+in a specific range ($0.0$ to $1.0$ for depth, and $-1.0$ to $1.0$ for screen
+space). $W$ acts as the scaling factor that forces every 3D coordinate into this
+tiny "unit cube" regardless of how large your actual game world is.
+
+
+
+### Why your code needs it In your `main.c`, when you define your vertices or
+your camera, you are working in 3D. But as soon as you pass that data to your
+`mvp` (Model-View-Projection) matrix, the math "promotes" those points to 4D.
+
+Without that $W$ component, the matrix multiplication wouldn't be able to handle
+**translation** (moving the camera) and **perspective** (making things smaller
+in the distance) at the same time.
+
+Does that help clarify why we use 4x4 matrices for 3D points, or would you like
+to see how a single point $P(x, y, z, 1)$ changes after it hits that `r.m[2][3]
+= -1` line?
--- /dev/null
+In Vulkan, having an **Image** and **Memory** isn't enough to actually use a texture in a shader. You need two more objects: an **Image View** (which acts as a "window" into the data) and a **Sampler** (which acts as the "lens" that reads the data).
+
+### **1. The Image View (`tiv`)**
+A `VkImage` is just a raw block of memory. The **Image View** tells Vulkan *how* to interpret that memory.
+* **Why we need it:** It specifies the format (`R8G8B8A8_UNORM`) and which parts of the image to look at (the `subresourceRange`).
+* **Analogy:** If the `VkImage` is a book, the **Image View** is a pair of reading glasses that ensures you are reading the correct language and page.
+
+---
+
+### **2. The Sampler (`smp`)**
+The **Sampler** is one of the most important objects in graphics. It is a configuration object that tells the GPU hardware exactly how to extract a color value from a texture when a shader asks for it.
+
+#### **Why do we need a Sampler?**
+When you render your plane, it will rarely be exactly $1:1$ with the pixels on your screen. The plane might be far away (shrinking the texture) or tilted (distorting it). The Sampler decides what color a screen pixel should be when it falls "between" the texels of your PNG.
+
+
+
+#### **Key Members of the Sampler:**
+* **`magFilter` / `minFilter` = `VK_FILTER_LINEAR`**:
+ * This enables **Linear Filtering**. Instead of picking the single closest
+ pixel (which looks "blocky"), the GPU will take a weighted average of the
+ four surrounding texels. This makes the texture look smooth even when you
+ get very close to the plane.
+* **`addressModeU` / `addressModeV` = `VK_IMAGE_ADDRESS_MODE_REPEAT`**:
+ * This defines what happens if your UV coordinates go outside the $0.0$ to $1.0$ range.
+ * **Repeat**: The texture tiles infinitely (like wallpaper).
+ * **Alternative (Clamp)**: The edge pixels of the image would just stretch out forever.
+
+
+
+### **The "Binding" Relationship**
+In your code, these two objects work together in a **Combined Image Sampler**:
+1. The **Shader** asks for a color at a specific UV coordinate.
+2. The **Sampler** calculates *where* in the image to look and *how* to blend the nearby colors.
+3. The **Image View** provides the actual raw pixel data from the `VkImage`.
+
+Without these two objects, your `cube.frag` shader would have no way to "read" the PNG data you worked so hard to upload to the GPU.
--- /dev/null
+[cite_start]In Vulkan, a **layout** is a specific memory organization for an image that is optimized for a particular type of task (like reading, writing, or presenting to a screen). [cite: 78, 80, 102] [cite_start]While we often think of an image as a simple 2D grid of pixels, GPUs can rearrange those pixels internally to make memory access much faster depending on what the hardware is doing. [cite: 75, 103]
+
+### **Why Layouts Exist**
+[cite_start]Standard "Linear" memory (row-by-row) is easy for the CPU to understand, but it is inefficient for a GPU. [cite: 73, 74] [cite_start]When a GPU "samples" a texture, it often needs to look at a pixel and its neighbors simultaneously. [cite: 123, 124] [cite_start]If the image is "tiled" (stored in small blocks), those neighboring pixels are physically closer together in the hardware cache, which drastically improves performance. [cite: 75, 82]
+
+
+
+### **Common Layouts in Your Code**
+[cite_start]Your `main.c` transitions your texture through several layouts to ensure the hardware is always using the most efficient "shape" for the current command: [cite: 40, 78, 80]
+
+* [cite_start]**`VK_IMAGE_LAYOUT_UNDEFINED`**: The initial state. [cite: 75, 78] [cite_start]This tells Vulkan the current contents of the memory are garbage and don't need to be preserved during a transition. [cite: 102, 106]
+* [cite_start]**`VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`**: Optimized for receiving data. [cite: 78, 79] [cite_start]You move your image into this layout before calling `vkCmdCopyBufferToImage` so the GPU can write the incoming bytes as fast as possible. [cite: 79]
+* [cite_start]**`VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL`**: Optimized for the fragment shader. [cite: 80, 96] [cite_start]This tells the hardware to "tile" the data so that your `texSampler` can read it efficiently without lag. [cite: 123, 124]
+* [cite_start]**`VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`**: Used for the swapchain images while you are actually drawing to them. [cite: 69, 102, 103]
+* [cite_start]**`VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`**: A special layout that hands the image off to the monitor's display engine to be shown on your screen. [cite: 106, 107]
+
+
+
+### **How Layouts are Switched**
+[cite_start]Layouts are changed using an **Image Memory Barrier**. [cite: 78, 80, 102] [cite_start]This barrier acts as a "reformat" command that tells the GPU: *"Stop what you're doing, rearrange these pixels from Format A to Format B, and don't let the next task start until the transformation is finished."* [cite: 78, 79, 102, 106]
--- /dev/null
+This block of code is the "Initialization and Staging" phase of texture loading in Vulkan. You are moving raw image data from a file into a high-performance memory format that the GPU can use.
+
+### **Step 1: Load Image to CPU RAM**
+```c
+SDL_Surface *s = IMG_Load(argv[1]);
+float asp = (float)s->w / s->h;
+```
+* [cite_start]**Action**: `IMG_Load` reads the PNG file from your disk into a standard `SDL_Surface`[cite: 72].
+* [cite_start]**Result**: The pixels now live in your **System RAM** (CPU memory) in a linear row-by-row format[cite: 73, 74].
+* [cite_start]**Math**: You calculate the aspect ratio here so the plane can be scaled to match the image dimensions later[cite: 73, 83].
+
+### **Step 2: Create a Staging Buffer**
+```c
+create_buf(dev, phys, s->w * s->h * 4, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ &stg, &smem);
+```
+* [cite_start]**Action**: You create a temporary buffer (`stg`) to act as a "waiting room" for the data[cite: 73].
+* [cite_start]**Purpose**: You use `HOST_VISIBLE` memory because the CPU cannot directly "talk" to high-speed VRAM; it needs a shared space to drop off the data[cite: 73].
+* [cite_start]**Coherency**: `HOST_COHERENT` ensures the GPU sees your `memcpy` changes immediately without you having to manually flush the cache[cite: 73].
+
+### **Step 3: Copy Pixels to the Staging Area**
+```c
+vkMapMemory(dev, smem, 0, s->w * s->h * 4, 0, &data);
+memcpy(data, s->pixels, s->w * s->h * 4);
+vkUnmapMemory(dev, smem);
+```
+* [cite_start]**Mapping**: `vkMapMemory` gets a CPU pointer (`data`) that points to the GPU-accessible staging memory[cite: 74].
+* [cite_start]**Copying**: `memcpy` physically moves the bytes from the `SDL_Surface` to that staging memory[cite: 74].
+* [cite_start]**Unmapping**: You invalidate the pointer because the CPU is finished with its part of the job[cite: 74].
+
+### **Step 4: Define the GPU Image Handle**
+```c
+VkImageCreateInfo ti = { ... .tiling = VK_IMAGE_TILING_OPTIMAL, ... };
+vkCreateImage(dev, &ti, NULL, &timg);
+```
+* [cite_start]**Action**: You create the metadata for the actual texture (`timg`) that the shader will use[cite: 75].
+* **Optimal Tiling**: Unlike the staging buffer (which is linear), you specify `TILING_OPTIMAL`. [cite_start]This allows the GPU to rearrange pixels into a "tiled" layout (like a Z-order curve) for lightning-fast cache access during sampling[cite: 75].
+
+
+### **Step 5: Allocate "Device Local" VRAM**
+```c
+vkGetImageMemoryRequirements(dev, timg, &tr);
+VkMemoryAllocateInfo ta = { ... VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT };
+vkAllocateMemory(dev, &ta, NULL, &tmem);
+vkBindImageMemory(dev, timg, tmem, 0);
+```
+* [cite_start]**Requirements**: You ask the GPU, "How much actual memory do you need for a $W \times H$ image with optimal tiling?"[cite: 75].
+* [cite_start]**Allocation**: You allocate `DEVICE_LOCAL` memory—this is the **high-speed VRAM** physically located on your graphics card[cite: 76]. The CPU cannot touch this memory directly.
+* [cite_start]**Binding**: You link the image handle (`timg`) to the raw memory block (`tmem`) you just allocated[cite: 76].
+
+---
+
+**Summary of the Flow:**
+1. [cite_start]**Disk** $\rightarrow$ **System RAM** (`SDL_Surface`)[cite: 72, 74].
+2. [cite_start]**System RAM** $\rightarrow$ **Staging Buffer** (CPU-visible GPU memory) via `memcpy`[cite: 74].
+3. [cite_start]**Staging Buffer** $\rightarrow$ **Final Image** (High-speed VRAM) via `vkCmdCopyBufferToImage` (which happens in the next section of your code)[cite: 79].
--- /dev/null
+In this block of the **fascia** engine, you are defining the **"Interface"** between your C code and your GPU shaders.
+
+Vulkan requires you to explicitly declare how data (like your texture and your MVP matrix) will be passed into the pipeline before you even create the pipeline itself. You are setting up two different communication channels: **Descriptors** for the texture and **Push Constants** for the math.
+
+---
+
+### 1. Descriptor Set Layout (The Texture Slot)
+```c
+VkDescriptorSetLayoutBinding lb = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT};
+```
+Think of a **Descriptor Set Layout** as a template or a "function signature" for your fragment shader.
+* **Binding 0:** You are reserving "Slot 0" for a resource.
+* **COMBINED_IMAGE_SAMPLER:** You are telling Vulkan that this slot will hold both the texture data and the instructions on how to read it (the sampler).
+* **FRAGMENT_BIT:** You are specifying that only the Fragment Shader needs access to this image.
+
+### 2. Push Constant Range (The Fast Path for Matrices)
+```c
+VkPushConstantRange pcr = { .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(Mat4) };
+```
+**Push Constants** are the fastest way to send small amounts of data to the GPU. Instead of creating a buffer, mapping memory, and unmapping it, you "push" the data directly into the command buffer.
+* **VERTEX_BIT:** Unlike the texture, your `Mat4` (the Model-View-Projection matrix) is only needed by the Vertex Shader to position the points of your plane.
+* **Size:** You are telling Vulkan to set aside exactly 64 bytes (the size of a 4x4 float matrix).
+
+
+
+---
+
+### 3. Pipeline Layout (The Master Plan)
+```c
+VkPipelineLayoutCreateInfo plc = {
+ .setLayoutCount = 1,
+ .pSetLayouts = &dsl,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pcr
+};
+```
+The **Pipeline Layout** is the final object that glues everything together. It tells the GPU: *"This graphics pipeline will use exactly one Descriptor Set (for the texture) and one Push Constant range (for the matrix)."*
+
+### Summary: Why this matters
+When you eventually call your draw commands, Vulkan uses this `play` (Pipeline Layout) handle to verify that the data you are providing matches what the shaders expect.
+
+| Feature | Data Type | Destination | Speed |
+| :--- | :--- | :--- | :--- |
+| **Descriptor** | `SDL_image` Texture | Fragment Shader | Standard |
+| **Push Constant** | `Mat4` (MVP) | Vertex Shader | Ultra-Fast |
+
+Since you've moved to a **monolithic C structure**, keeping these layouts simple (one set, one push range) is a great way to keep your `vkCmd` calls clean and avoid the headache of managing complex descriptor pools. Are you planning to keep the matrix as a Push Constant, or will you eventually move to Uniform Buffers if the engine grows?
--- /dev/null
+#version 450
+layout(location = 0) in vec2 inUV;
+layout(location = 0) out vec4 outColor;
+layout(binding = 0) uniform sampler2D texSampler;
+
+void main() {
+ // Flip the y-coordinate before sampling
+ outColor = texture(texSampler, vec2(inUV.x, 1.0 - inUV.y));
+}
--- /dev/null
+#version 450
+layout(push_constant) uniform Push { mat4 mvp; } pc;
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec2 inUV;
+layout(location = 0) out vec2 outUV;
+void main() {
+ gl_Position = pc.mvp * vec4(inPos, 1.0);
+ outUV = inUV;
+}
--- /dev/null
+== Links
+* https://learnopengl.com/Getting-started/Camera
+* https://mathworld.wolfram.com/SphericalCoordinates.html[mathword | spherical coordinates]
+* https://www.youtube.com/watch?v=Ex_g2w4E5lQ[mathworld | spherical coordinates]
+* https://www.youtube.com/watch?v=w79nZGzWMyI[youtube | deriving spherical coordinates]
+* https://www.youtube.com/watch?v=jBcNOkwiS6k[youtube | integrating spherical coordinates]
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=eu6i7WJeinw[3Blue 1Brown | Cross Product]
--- /dev/null
+== 1. Save image to CPU
+
+[source, c]
+----
+// Texture Load
+SDL_Surface *s = IMG_Load(argv[1]);
+----
+
+== 2. Move the CPU memory to the GPU
+
+[source, c]
+----
+ float asp = (float)s->w / s->h; // aspect ratio
+ VkBuffer stg; // used in command buffers
+ VkDeviceMemory smem; // used for writing data to buffer from CPU
+
+ // create buffer allocates and binds the buffer memory
+ // we mulitple by 4 because we need RGBA for each color channel
+ create_buf(dev, phys, s->w * s->h * 4, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ // ^^ The CPU is allows to directly access this memory
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ // ^^ No manual flush needed
+ &stg, &smem);
+
+ // You use HOST_VISIBLE memory because the CPU cannot directly "talk" to
+ // high-speed VRAM; it needs a shared space to drop off the data
+ //
+ void *data;
+ vkMapMemory(dev, smem, 0, s->w * s->h * 4, 0, &data);
+ memcpy(data, s->pixels, s->w * s->h * 4);
+ vkUnmapMemory(dev, smem);
+----
+
+== 3. create image for texture
+
+[source, c]
+----
+ VkImage timg;
+ VkDeviceMemory tmem;
+ VkImageCreateInfo ti = {
+ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {s->w, s->h, 1},
+ // ^^^ 3d extent
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ // ^^^ value speciying the number of samples per texel
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+ };
+ // you specify TILING_OPTIMAL. This allows the GPU to rearrange pixels into a
+ // "tiled" layout (like a Z-order curve) for lightning-fast cache access
+ // during sampling
+
+ // VK_IMAGE_TILING_LINEAR
+ // This is the way a standard C array or a .bmp file is organized. Texels
+ // are laid out row by row (horizontally).
+ // * Memory Map: [Row 0, Pixel 0][Row 0, Pixel 1] ... [Row 1, Pixel 0]
+ // * Pros: The CPU can easily understand it. You can memcpy directly into it
+ // and read it like a normal buffer.
+ //
+ // Cons: It is terrible for GPU performance. Because of how GPU caches work,
+ // jumping from the end of one row to the start of the next is a long
+ // "stride" in memory, which causes cache misses.
+ vkCreateImage(dev, &ti, NULL, &timg); // returns timg
+ VkMemoryRequirements tr;
+ vkGetImageMemoryRequirements(dev, timg, &tr);
+ VkMemoryAllocateInfo ta = {
+ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = tr.size,
+ .memoryTypeIndex = find_mem(phys, tr.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)};
+ vkAllocateMemory(dev, &ta, NULL, &tmem);
+ vkBindImageMemory(dev, timg, tmem, 0);
+
+
+----
+
+== 4. use a command buffer to move the GPU memroy to the texture
+
+[source, c]
+----
+vkBeginCommandBuffer(cb, &bi);
+// --------------------------------------------------------------------------
+// This block of code is a Pipeline Barrier. In the fascia engine, this acts
+// as a "State Transition." You are telling the GPU to prepare the memory of
+// your texture (timg) for a specific operation.
+
+// In Vulkan, images don't just "exist" as pixels; they exist in specific
+// Layouts optimized for different tasks. Before you can copy your PNG data
+// into the image, you have to tell the GPU to change that image's internal
+// "mode."
+// --------------------------------------------------------------------------
+VkImageMemoryBarrier b1 = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ // The layout members here are not related to the layout variables in the
+ // shader
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .image = timg,
+ .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseArrayLayer = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1}};
+vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ // ^^
+ // This is the destination.
+ // this tells the GPU "you are not allowed to start
+ // the `TRANSFER` stage (the actual copying of pixels)
+ // until this transition is finished"
+ 0, 0, NULL, 0, NULL, 1,
+ &b1);
+// --------------------------------------------------------------------------
+VkBufferImageCopy copy = {
+ .imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
+ .imageExtent = {s->w, s->h, 1}};
+vkCmdCopyBufferToImage(cb, stg, timg, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
+ ©);
+
+VkImageMemoryBarrier b2 = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ .image = timg,
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0,
+ NULL, 1, &b2);
+vkEndCommandBuffer(cb);
+----
+
+=== vkCmdPipelineBarrier (The "Stoplight")
+
+This command is purely about synchronization and state. It does not move a
+single pixel from your staging buffer.
+
+* *State Change*: It tells the GPU to stop treating the memory at timg as a raw,
+ unknown block of data (UNDEFINED) and reshuffle its internal logic to handle
+ incoming data (TRANSFER_DST_OPTIMAL).
+
+* *The "Wait" Signal*: It tells the GPU's internal scheduler: "Don't let the
+ Transfer Engine touch this image until the memory controller has finished the
+ transition."
+
+=== vkCmdCopyBufferToImage (The "Freight Train")
+
+This is the command that does the heavy lifting.
+
+* *The Action*: It reads the raw, linear pixel data (the RGBA bytes) from your
+ staging buffer (stg) and writes them into the texture (timg).
+
+* *The Conversion*: Because you set the image to OPTIMAL tiling earlier, this
+ command is where the GPU actually "swizzles" the pixels into its proprietary
+ high-speed layout.
--- /dev/null
+This image perfectly captures the core logic of your `m4_persp` function. It
+shows how we project a 3D point in the "world" (eye space) onto the 2D plane of
+your screen (projection space).
+
+The formula used here is the **Intercept Theorem** (or Thales's Theorem), which
+states that the ratios of corresponding sides of similar triangles are equal.
+
+---
+
+### 1. Breaking Down the Top View ($x$ and $z$) In the "Top View" diagram, you
+can see two right-angled triangles that share a vertex at the camera origin
+$(0,0,0)$:
+1. **The Large Triangle:** Extends to the real object at $(x_e, z_e)$. Its
+ width is $x_e$ and its depth is $z_e$.
+2. **The Small Triangle:** Extends only to the Near Plane at distance $-n$. Its
+ width is $x_p$ (the projected position on your screen).
+
+
+**The Ratio:** $$\frac{\text{Width of small}}{\text{Width of large}} =
+\frac{\text{Depth of small}}{\text{Depth of large}}$$
+
+Substituting the variables from your image: $$\frac{x_p}{x_e} = \frac{-n}{z_e}$$
+
+To find where the pixel $x_p$ should be drawn on your screen, you solve for
+$x_p$: $$x_p = \frac{-n \cdot x_e}{z_e}$$
+
+---
+
+### 2. Why the Negatives? You'll notice the image uses $-n$ and $-z_e$.
+* In many graphics conventions (like OpenGL), the camera looks down the
+**negative Z-axis**.
+* Distance is always a positive concept, but the coordinate is negative.
+* To make the math easier to handle, we often flip it: $x_p = \frac{n \cdot
+x_e}{-z_e}$.
+
+---
+
+### 3. How this links to your Code Look back at your `m4_persp` matrix. You have
+$z$ values being moved into the $w$ slot: `r.m[2][3] = -1;`
+
+When the GPU performs the **Perspective Divide** ($x/w$), it is literally
+executing the final step of this similar triangle formula.
+* **$x_e$** is your vertex attribute.
+* **$w$** (which becomes $-z_e$ thanks to that `-1`) is the denominator.
+* **The Result** is $x_p$.
+
+
+### Summary of the Formula The "magic" of 3D perspective is simply scaling the
+horizontal and vertical positions by the inverse of the depth.
+* **Nearer objects (small $z_e$):** Result in a larger $x_p$ (they appear
+further from the center, thus "bigger").
+* **Farther objects (large $z_e$):** Result in a smaller $x_p$ (they cluster
+toward the center vanishing point).
+
+Does seeing the triangles laid out like this make the $x_p = \frac{n \cdot
+x_e}{-z_e}$ formula feel more like a physical measurement and less like abstract
+algebra?
--- /dev/null
+== Texels
+Texels:: pixels being used from a texture
+
+Somehow texels are decoupled from pixels since we are not necessarily using all
+of the pixels from the image we use as a texture.
+
+== Links
+* https://medium.com/@denisdee/texels-texel-density-and-pixels-bce6f474f0a2[texels and texel density]
+ ** This is important to keep in mind. Trying to keep a specific texel
+ density for objects
--- /dev/null
+In your **fascia** engine, the "sending" of data to the fragment shader happens in two distinct ways depending on what kind of data it is.
+
+Since you are currently working with a texture, the data is not "sent" once; it is **bound** to the pipeline, and then the GPU "pulls" the data as needed for every pixel on your 2560x1080 screen.
+
+---
+
+### 1. The Global "Look-up" (Descriptor Sets)
+For your `texSampler`, you aren't pushing the data directly into the shader. Instead, you are giving the shader a **pointer** (a Descriptor) that tells it where the texture lives in VRAM.
+
+This happens in your `while(run)` loop right before you draw:
+```c
+vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ play, 0, 1, &dset, 0, NULL);
+```
+* **When:** Every frame (or whenever you change textures).
+* **What is happening:** You are telling the Fragment Shader, "Whenever you call `texture()` in your code, use the image and sampler linked to this descriptor set (`dset`)."
+* **The Result:** The shader now knows where to "reach out" in memory to grab texels.
+
+
+
+---
+
+### 2. The Per-Pixel "Interpolation" (Varyings)
+For your `inUV`, the data actually comes from the **Vertex Shader**. This is a "relay race" that happens inside the GPU hardware.
+
+* **In the Vertex Shader:** You output the UV coordinate: `layout(location = 0) out vec2 outUV;`.
+* **In the Fragment Shader:** You receive it: `layout(location = 0) in vec2 inUV;`.
+* **The "Secret" Step:** Between the Vertex and Fragment stages, the GPU performs **Rasterization**. It looks at the UVs at the corners of your plane and mathematically calculates (interpolates) what the UV should be for every single pixel in between.
+
+
+
+---
+
+### 3. Summary of Data Flow
+
+| Data Type | C Code Action | When? | How it reaches the Fragment Shader |
+| :--- | :--- | :--- | :--- |
+| **Texture/Image** | `vkCmdBindDescriptorSets` | Before `vkCmdDraw` | The shader "looks it up" via the binding slot. |
+| **UV Coordinates** | `vkCmdBindVertexBuffers` | Before `vkCmdDraw` | Passed from Vertex shader and interpolated by the GPU hardware. |
+| **Uniforms (Colors/Timers)** | `vkCmdPushConstants` or UBOs | Before `vkCmdDraw` | Read directly from a small, high-speed constant buffer. |
+
+### Why this is efficient for Fascia
+By using **Descriptor Sets**, you aren't wasting CPU time copying the actual texture data every frame. You copy the image to the GPU **once** during your setup, and then every frame after that, you are just sending a tiny "memory address" to the fragment shader.
+
+On your **Radeon RX 6650M**, this is why your engine can run at hundreds of frames per second—the "heavy lifting" (the pixels) stays on the GPU, and the C code just acts as the air traffic controller.
+
+Are you planning to send any other data to the fragment shader soon, like a "Time" variable to make the texture scroll or pulse?
--- /dev/null
+This is a great observation. It feels lopsided because you are defining the **Vertex Input** in one place but needing a **Pipeline Layout** for the **Fragment Shader** resources.
+
+The reason for this distinction comes down to how the GPU hardware physically accesses data. There are two different "paths" for data to enter your shader: **Fixed-Function Input** and **Descriptor-Based Resources**.
+
+---
+
+### 1. Vertex Input: The "Automatic" Path
+The variables `inPos` and `inUV` use the **Vertex Input Interface**.
+* **How it works:** This is a high-speed, "hard-wired" part of the GPU. When you provide the `VkVertexInputAttributeDescription` array, you are configuring a piece of hardware called the **Vertex Fetch Unit**.
+* **The Difference:** The GPU knows exactly how to pull this data because it’s tied to the `vkCmdDraw` command. It just marches through your vertex buffer one by one. It doesn't need a "layout" because the attributes are considered part of the **internal pipeline state**, not an external resource.
+
+### 2. Fragment Buffers: The "Random Access" Path
+The variables like `texSampler` (or Uniform Buffers) use the **Descriptor Interface**.
+* **How it works:** Unlike vertex data, which is pulled automatically, a texture is a "resource" that stays in memory. The fragment shader needs to "reach out" and grab specific parts of it whenever it wants.
+* **Why the Pipeline Layout exists:** The `VkPipelineLayout` is the **Map** of all external resources. It tells the GPU: "This pipeline is going to need access to one Texture at Binding 0."
+* **The Reason for the Function Call:** The GPU needs to pre-allocate "ports" or "registers" to handle those incoming textures and buffers. `vkCreatePipelineLayout` reserves that space globally for the entire pipeline (Vertex + Fragment stages).
+
+---
+
+### The "Contract" Analogy
+Think of the **Graphics Pipeline** as a factory worker.
+
+* **Vertex Input (`VkVertexInputAttributeDescription`):** This is the **Conveyor Belt**. The worker doesn't have to look for the parts; they just arrive one after another in a fixed order. You just need to tell the worker, "The part on the left is a bolt, the part on the right is a washer."
+* **Pipeline Layout (`vkCreatePipelineLayout`):** This is the worker's **Tool Belt**. These are items the worker might need to reach for at any time (like a texture). You have to define how big the tool belt is and which pockets the tools are in before the worker starts the shift.
+
+
+
+---
+
+### Wait, can Vertex Shaders use the Layout too?
+Yes! If you wanted to move your `cube` in 3D space using a "Model Matrix," you would add a **Uniform Buffer**.
+1. You would define a `layout(binding = 1) uniform UBO ...` in your **Vertex Shader**.
+2. You would then have to add a second binding to your **Descriptor Set Layout**.
+3. Your `VkPipelineLayout` would then grow to accommodate both the Texture (for the Fragment stage) and the Matrix (for the Vertex stage).
+
+### Summary for "Fascia"
+| Data Type | Interface | Configured By... | Why? |
+| :--- | :--- | :--- | :--- |
+| `inPos`, `inUV` | Vertex Input | `VkPipelineVertexInputStateCreateInfo` | It's a "stream" of data handled by the Fetch hardware. |
+| `texSampler` | Descriptors | `VkPipelineLayout` | It's a "resource" that needs a mapped address to be accessed. |
+
+In your current monolithic setup, your `VkPipelineLayout` is very simple because you only have that one texture. But as **fascia** grows—perhaps when you add that 8-bit logic computer simulation as a texture or dynamic data—you'll see that "Tool Belt" (the layout) getting more complex!
+
+Does that distinction between the "Conveyor Belt" and the "Tool Belt" help clarify why they are handled by different Vulkan objects?
--- /dev/null
+## Question
+
+why do (Xclip/Wclip, Yclip / Wclip, Zclip/wclip) instead of (Xclip/Zclip, Yclip/Zclip) ?
+
+
+That is a great question. If the goal is just to make things smaller in the
+distance, you *could* technically just divide $x$ and $y$ by $z$ in your shader
+and call it a day.
+
+However, doing it this way (using $w$ and a projection matrix) solves three
+massive problems that a simple division cannot handle:
+
+### 1. The Matrix "Package" Deal In your **fascia** engine, you want to perform
+multiple transformations:
+* **Rotation** (Yaw/Pitch)
+* **Translation** (Moving the camera position)
+* **Scaling** (Changing the size of objects)
+* **Projection** (The similar triangle division)
+
+Linear algebra allows us to multiply all these different steps into **one single
+matrix**. If you did a manual division by $z$ in the middle of your code, you
+would break the mathematical chain. By using $w$, we keep the entire
+transformation process as a single, fast matrix multiplication.
+
+### 2. Linearity and Interpolation This is the "secret" reason GPUs work this
+way. GPUs are designed to draw triangles by interpolating values across their
+surface.
+* If you divide $x$ and $y$ by $z$ **before** sending them to the GPU, the
+relationship becomes non-linear.
+* This causes "texture warping" (perspective incorrect mapping).
+* By keeping the coordinates in **Clip Space** (where $w$ is separate), the GPU
+can interpolate your textures and colors linearly across the triangle, and then
+perform the division at the very last microsecond for every single pixel. This
+ensures your textures look "glued" to the plane correctly as you move.
+
+
+### 3. Handling the Z-Buffer (Depth) If you just divide $x$ and $y$ by $z$, what
+happens to $z$ itself?
+* We still need $z$ to figure out which object is in front of another (Depth
+Testing).
+* The projection matrix doesn't just divide; it re-maps your $z$ from a massive
+range (e.g., $0.1$ to $1000.0$) into a tiny range ($0.0$ to $1.0$).
+* This re-mapping is non-linear, meaning you get more precision for objects
+close to the camera and less for objects far away. This is exactly what you want
+to prevent "Z-fighting" (where two distant objects flicker over each other).
+
+
+### Summary: Why we use $w_{clip}$ The formula in your image shows that the
+matrix multiplication *prepares* the division, but the actual division happens
+at the **NDC (Normalized Device Coordinate)** stage.
+
+$$\begin{pmatrix} x_{ndc} \\ y_{ndc} \\ z_{ndc} \end{pmatrix} = \begin{pmatrix}
+x_{clip} / w_{clip} \\ y_{clip} / w_{clip} \\ z_{clip} / w_{clip}
+\end{pmatrix}$$
+
+By using $w_{clip}$ as the divisor, we get:
+1. **Correct Perspective** for textures and shapes.
+2. **Optimized Depth** for the Z-buffer.
+3. **Clean Code** where one matrix handles the entire 3D-to-2D journey.
+
+Think of $w$ as a "delay timer." It holds onto the depth information until the
+very last stage of the rendering pipeline so that everything else (like texture
+mapping) stays mathematically perfect until the division occurs.
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_vulkan.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <volk/volk.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+// --- MATH (math.h only) ---
+typedef struct {
+ float x, y, z;
+} Vec3;
+
+typedef struct {
+ float m[4][4];
+} Mat4;
+
+Vec3 v3_sub(Vec3 a, Vec3 b) { return (Vec3){a.x - b.x, a.y - b.y, a.z - b.z}; }
+Vec3 v3_add(Vec3 a, Vec3 b) { return (Vec3){a.x + b.x, a.y + b.y, a.z + b.z}; }
+Vec3 v3_mul(Vec3 a, float s) { return (Vec3){a.x * s, a.y * s, a.z * s}; }
+float v3_dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
+
+Vec3 v3_cross(Vec3 a, Vec3 b) {
+ return (Vec3){a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z,
+ a.x * b.y - a.y * b.x};
+}
+
+Vec3 v3_norm(Vec3 v) {
+ float l = sqrtf(v3_dot(v, v));
+ // v3_mul(v, 1.0f / l) is the same as v / |v |
+ // In vector math, it is impossible for the length (magnitude) to be less than
+ // zero
+ return (l > 0) ? v3_mul(v, 1.0f / l) : v;
+}
+
+// identity matrix
+Mat4 m4_id() {
+ Mat4 r = {0};
+ for (int i = 0; i < 4; i++)
+ r.m[i][i] = 1;
+ return r;
+}
+
+Mat4 m4_mul(Mat4 a, Mat4 b) {
+ Mat4 r = {0};
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ for (int k = 0; k < 4; k++)
+ //a.m[row][column]
+ r.m[i][j] += a.m[i][k] * b.m[k][j];
+ return r;
+
+}
+
+Mat4 m4_persp(float fov, float asp, float n, float f)
+/*
+This function transforms the 3D world into a 2D image while adding the
+illusion depth
+
+fov (Field of View):
+ The vertical angle of the camera lens. A larger FOV gives a "fish-eye"
+ effect; a smaller one acts like a zoom lens.
+
+asp (Aspect Ratio):
+ The width of your window divided by its height (e.g.,
+ $2560/1080$ for your ultrawide setup). This prevents the image from
+ looking stretched.
+
+n and f (Near and Far planes):
+ The minimum and maximum distances the camera
+ can see. Anything closer than n or further than f is clipped (not
+ rendered).
+
+
+To get a better feel of this 4x4 matrix you can take a look at the identity
+matrix multiplied by the homogeneous coordinates
+
+ | 1 0 0 0 | | x |
+ | 0 1 0 0 | * | y |
+ | 0 0 1 0 | | z |
+ | 0 0 0 1 | | w |
+
+*/
+{
+ float t = tanf(fov / 2);
+ Mat4 r = {0};
+ r.m[0][0] = 1 / (asp * t);
+ r.m[1][1] = -1 / t;
+ r.m[2][2] = f / (n - f);
+ r.m[2][3] = -1;
+ r.m[3][2] = (n * f) / (n - f);
+ return r;
+}
+
+Mat4 m4_lookat(Vec3 eye, Vec3 ctr, Vec3 up) {
+ Vec3 f = v3_norm(v3_sub(ctr, eye)), s = v3_norm(v3_cross(f, up)),
+ u = v3_cross(s, f);
+ Mat4 r = m4_id();
+ r.m[0][0] = s.x;
+ r.m[1][0] = s.y;
+ r.m[2][0] = s.z;
+ r.m[0][1] = u.x;
+ r.m[1][1] = u.y;
+ r.m[2][1] = u.z;
+ r.m[0][2] = -f.x;
+ r.m[1][2] = -f.y;
+ r.m[2][2] = -f.z;
+ r.m[3][0] = -v3_dot(s, eye);
+ r.m[3][1] = -v3_dot(u, eye);
+ r.m[3][2] = v3_dot(f, eye);
+ return r;
+}
+
+// --- VULKAN HELPERS ---
+typedef struct {
+ float pos[3], uv[2];
+} Vertex;
+
+uint32_t find_mem(VkPhysicalDevice p, uint32_t f, VkMemoryPropertyFlags pr) {
+ VkPhysicalDeviceMemoryProperties m;
+
+ // typedef struct VkPhysicalDeviceMemoryProperties {
+ // uint32_t memoryTypeCount;
+ // VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
+ // uint32_t memoryHeapCount;
+ // VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS];
+ // } VkPhysicalDeviceMemoryProperties;
+
+ vkGetPhysicalDeviceMemoryProperties(p, &m);
+
+ // This line queries you graphics card to find out what its memory
+ // architecture looks like. It fills the `m` struct with two main things:
+ //
+ // - Memory heaps
+ // - Memory Types
+
+ // typedef enum VkMemoryPropertyFlagBits {
+ // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001,
+ // VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002,
+ // VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004,
+ // VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008,
+ // VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010,
+ // // Provided by VK_VERSION_1_1
+ // VK_MEMORY_PROPERTY_PROTECTED_BIT = 0x00000020,
+ // // Provided by VK_AMD_device_coherent_memory
+ // VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD = 0x00000040,
+ // // Provided by VK_AMD_device_coherent_memory
+ // VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD = 0x00000080,
+ // // Provided by VK_NV_external_memory_rdma
+ // VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV = 0x00000100,
+ // } VkMemoryPropertyFlagBits;
+
+ // printf("memoryTypeBits: %d\n", f);
+
+ //`memoryTypeBits` (f) is a bitmask containing one bit set for every memory
+ //type which the specified open Harmony OS native buffer can be imported as
+ for (uint32_t i = 0; i < m.memoryTypeCount; i++)
+
+ // - The f bitmask is a map of all valid "slots" (indices) in the
+ // m.memoryTypes array
+
+ // - If f is 0b00001011, it means indices 0, 1, and 3 are
+ // hardware-compatible.
+
+ // - Each of those indices represents a completely different type of memory
+ // with different properties.
+
+ // Example
+ // - If Bit 3 is a 1: The GPU hardware can physically wire this buffer to the
+ // memory described at Index 3.
+
+ // - If Bit 3 is a 0: It is physically impossible (or prohibited by the
+ // driver) to put this specific buffer into Index 3.
+
+ // the left side of && just checks that the bits are the same in
+ // propertyFlags and pr. If there are extra bits it propertyFlags it's
+ // fine
+ if ((f & (1 << i)) && (m.memoryTypes[i].propertyFlags & pr) == pr)
+ return i;
+ return 0;
+}
+
+void create_buf(VkDevice d, VkPhysicalDevice p, VkDeviceSize size,
+ VkBufferUsageFlags u, VkMemoryPropertyFlags pr, VkBuffer *b,
+ VkDeviceMemory *m) {
+
+ VkBufferCreateInfo bi = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = u,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ // ^^^^
+ // resource is owned by exactly one queue family at a time
+ };
+
+ if (vkCreateBuffer(d, &bi, NULL, b) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create buffer\n");
+ exit(1);
+ }
+
+ VkMemoryRequirements req;
+ vkGetBufferMemoryRequirements(d, *b, &req);
+
+ VkMemoryAllocateInfo ai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = req.size,
+ .memoryTypeIndex =
+ find_mem(p, req.memoryTypeBits, pr)};
+
+ if (vkAllocateMemory(d, &ai, NULL, m) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to allocate buffer memory\n");
+ exit(1);
+ }
+
+ vkBindBufferMemory(d, *b, *m, 0);
+}
+
+// --- APP STATE ---
+Vec3 camPos = {0, 0, 3}, camFwd = {0, 0, -1};
+float yaw = -90.0f, pitch = 0.0f;
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ printf("Usage: %s <png> [scale]\n", argv[0]);
+ return 1;
+ }
+ float scale = (argc > 2) ? atof(argv[2]) : 1.0f;
+
+ volkInitialize();
+ SDL_Init(SDL_INIT_VIDEO);
+ IMG_Init(IMG_INIT_PNG);
+ SDL_Window *win =
+ SDL_CreateWindow("Vulkan Plane", SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_VULKAN);
+
+ SDL_SetRelativeMouseMode(SDL_TRUE);
+
+ // Instance & Surface
+ uint32_t extC = 0;
+ SDL_Vulkan_GetInstanceExtensions(win, &extC, NULL);
+ // const char **exts = malloc(sizeof(char *) * extC);
+ const char *exts[extC];
+ SDL_Vulkan_GetInstanceExtensions(win, &extC, exts);
+ VkInstance inst;
+ VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .apiVersion = VK_API_VERSION_1_3};
+ VkInstanceCreateInfo ic = {
+ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app,
+ .enabledExtensionCount = extC, .ppEnabledExtensionNames = exts};
+
+ // Load instance level pointers, they handle things like querying physical
+ // device properties, creating a surface, or picking a GPU
+ vkCreateInstance(&ic, NULL, &inst);
+ volkLoadInstance(inst);
+ VkSurfaceKHR surf;
+ SDL_Vulkan_CreateSurface(win, inst, &surf);
+
+ // Device
+ VkPhysicalDevice phys;
+ uint32_t pc = 1;
+ vkEnumeratePhysicalDevices(inst, &pc, &phys);
+ float pri = 1.0f;
+ // higher priority gets more "time slices" or execution unitson the chip.
+ VkDeviceQueueCreateInfo qc = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueCount = 1, .pQueuePriorities = &pri};
+ VkPhysicalDeviceDynamicRenderingFeatures df = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE};
+ const char *de[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME};
+ VkDevice dev;
+ VkDeviceCreateInfo dc = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &df,
+ .queueCreateInfoCount = 1,
+ .pQueueCreateInfos = &qc,
+ .enabledExtensionCount = 2,
+ .ppEnabledExtensionNames = de};
+
+ // Create device-level pointers. these are your "hot path" commands - drawing,
+ // dispatching cumpute shaders, and memory management
+ vkCreateDevice(phys, &dc, NULL, &dev);
+ volkLoadDevice(dev);
+ VkQueue q;
+
+ // this function retrives a VKQueue object
+ //
+ // NOTE: I dont think this is the best way to get a que. you aren't
+ // searching for ques available on you're device
+ //
+ // void vkGetDeviceQueue(
+ // VkDevice device,
+ // uint32_t queueFamilyIndex,
+ // uint32_t queueIndex,
+ // VkQueue* pQueue);
+ vkGetDeviceQueue(dev, 0, 0, &q);
+
+ // Swapchain
+ VkSwapchainKHR swp;
+ VkExtent2D ext = {1280, 720}; // windows resolutions
+
+ VkSwapchainCreateInfoKHR sc = {
+ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surf,
+ // we didn't check how many surfaces we support first!
+ .minImageCount = 2,
+ .imageFormat = VK_FORMAT_B8G8R8A8_UNORM,
+ // ^^^^
+ // This tells the GPU that each pixel consists of Blue, Green, Red, and
+ // Alpha components, in that specific order, each taking up 8 bits
+ //
+ // UNORM (Unsigned Normalized) means the values are stored as integers
+ // (0–255) but are treated as floating-point values (0.0–1.0) in your
+ // shaders.
+ .imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
+ // ^^^^^
+ // This tells teh GPU that each pixel consists of Blue, Green, Red, and
+ // Alpha components, in that specific order, each taking up 8 bits
+ .imageExtent = ext, .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ // ^^^^
+ // This is the most common setting. It means you will be rendering
+ // directly into these images (they will be the "target" of your graphics
+ // pipeline)
+ .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+ // This is saying to not do image transformation
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ // ^^^^
+ // This tells the OS that your window is fully opaque. The desktop
+ // compositor will not try to "look through" your game to see what is
+ // behind it.
+ .presentMode = VK_PRESENT_MODE_FIFO_KHR};
+
+ // VkResult vkCreateSwapchainKHR(
+ // VkDevice device,
+ // const VkSwapchainCreateInfoKHR* pCreateInfo,
+ // const VkAllocationCallbacks* pAllocator,
+ // VkSwapchainKHR* pSwapchain);
+
+ // `pAllocator` is the allocator used for host memory allocated for the
+ // swapchain object when there is no more specific allocator availabl
+ vkCreateSwapchainKHR(dev, &sc, NULL, &swp);
+ uint32_t icount;
+ vkGetSwapchainImagesKHR(dev, swp, &icount, NULL);
+ VkImage simg[icount];
+ vkGetSwapchainImagesKHR(dev, swp, &icount, simg);
+ // ^^ obtain the arry of presentable images associated with the swapchain
+
+ // create an image view for each swapchain image
+ // ----------------------------------------------
+ // A VkImage is just a collection of pixels in a specific memory layout.
+ // However, the GPU needs to know how to look at those pixels. The VkImageView
+ // provides the metadata that describes it
+ VkImageView siv[icount];
+
+ for (int i = 0; i < icount; i++) {
+
+ VkImageViewCreateInfo iv = {
+ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = simg[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = VK_FORMAT_B8G8R8A8_UNORM,
+
+ // Think of a VkImage as a container that can hold more than just one
+ // flat picture. It can hold a stack of images (Array Layers) or various
+ // resolutions of the same image (Mip Levels). This structure is the
+ // "address" that specifies the target area.
+
+ .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1}};
+ vkCreateImageView(dev, &iv, NULL, &siv[i]);
+ }
+
+ // Texture Load
+ SDL_Surface *s = IMG_Load(argv[1]);
+ float asp = (float)s->w / s->h; // aspect ratio
+ VkBuffer stg; // used in command buffers
+ VkDeviceMemory smem; // used for writing data to buffer form CPU
+
+ // create buffer allocates and binds the buffer memory
+ // we mulitple by 4 because we need RGBA for each color channel
+ create_buf(dev, phys, s->w * s->h * 4, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ // ^^ The CPU is allows to directly access this memory
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ // ^^ No manual flush needed
+ &stg, &smem);
+
+ // You use HOST_VISIBLE memory because the CPU cannot directly "talk" to
+ // high-speed VRAM; it needs a shared space to drop off the data
+ //
+ void *data;
+ vkMapMemory(dev, smem, 0, s->w * s->h * 4, 0, &data);
+ memcpy(data, s->pixels, s->w * s->h * 4);
+ vkUnmapMemory(dev, smem);
+ // Once you unmap you cannot write to the data variable. The bridge between
+ // the GPU and CPU is gone
+
+ // Mapping: vkMapMemory gets a CPU pointer (data) that points to the
+ // GPU-accessible staging memory.
+
+ // Copying: memcpy physically moves the bytes from the SDL_Surface to that
+ // staging memory
+
+ // Unmapping: You invalidate the pointer because the CPU is finished with its
+ // part of the job
+
+ VkImage timg;
+ VkDeviceMemory tmem;
+ VkImageCreateInfo ti = {
+ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {s->w, s->h, 1},
+ // ^^^ 3d extent
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ // ^^^ value speciying the number of samples per texel
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+ };
+ // you specify TILING_OPTIMAL. This allows the GPU to rearrange pixels into a
+ // "tiled" layout (like a Z-order curve) for lightning-fast cache access
+ // during sampling
+
+ // VK_IMAGE_TILING_LINEAR
+ // This is the way a standard C array or a .bmp file is organized. Texels
+ // are laid out row by row (horizontally).
+ // * Memory Map: [Row 0, Pixel 0][Row 0, Pixel 1] ... [Row 1, Pixel 0]
+ // * Pros: The CPU can easily understand it. You can memcpy directly into it
+ // and read it like a normal buffer.
+ //
+ // Cons: It is terrible for GPU performance. Because of how GPU caches work,
+ // jumping from the end of one row to the start of the next is a long
+ // "stride" in memory, which causes cache misses.
+ vkCreateImage(dev, &ti, NULL, &timg); // returns timg
+ VkMemoryRequirements tr;
+ vkGetImageMemoryRequirements(dev, timg, &tr);
+ VkMemoryAllocateInfo ta = {
+ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = tr.size,
+ .memoryTypeIndex = find_mem(phys, tr.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)};
+ vkAllocateMemory(dev, &ta, NULL, &tmem);
+ vkBindImageMemory(dev, timg, tmem, 0);
+
+ // Command Pool for setup
+ VkCommandPool cp;
+ // VkCommandPoolCreateInfo cpi={VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
+ VkCommandPoolCreateInfo cpi = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = 0, // Ensure this matches your graphics queue family
+ .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
+ // `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` flag is a memory
+ // management setting that determines how you are allowed to reuse your
+ // command buffers.
+
+ // In Vulkan, recording commands is an expensive process. To save time,
+ // you don't want to keep destroying and re-creating command buffers every
+ // frame. Instead, you want to reset them so you can record new
+ // instructions (like the updated camera position) into the same memory
+ // now you can do `vkResetCommandPool(dev, cp, 0)`
+ };
+
+ vkCreateCommandPool(dev, &cpi, NULL, &cp);
+ VkCommandBuffer cb;
+ VkCommandBufferAllocateInfo cai = {
+ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = cp,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1};
+ vkAllocateCommandBuffers(dev, &cai, &cb);
+
+ VkCommandBufferBeginInfo bi = {
+ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
+
+ vkBeginCommandBuffer(cb, &bi);
+
+ // --------------------------------------------------------------------------
+ // This block of code is a Pipeline Barrier. In the fascia engine, this acts
+ // as a "State Transition." You are telling the GPU to prepare the memory of
+ // your texture (timg) for a specific operation.
+
+ // In Vulkan, images don't just "exist" as pixels; they exist in specific
+ // Layouts optimized for different tasks. Before you can copy your PNG data
+ // into the image, you have to tell the GPU to change that image's internal
+ // "mode."
+ // --------------------------------------------------------------------------
+ VkImageMemoryBarrier b1 = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ // The layout members here are not related to the layout variables in the
+ // shader
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .image = timg,
+ .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseArrayLayer = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ // ^^
+ // This is the destination.
+ // this tells the GPU "you are not allowed to start
+ // the `TRANSFER` stage (the actual copying of pixels)
+ // until this transition is finished"
+ 0, 0, NULL, 0, NULL, 1,
+ &b1);
+ // --------------------------------------------------------------------------
+
+ VkBufferImageCopy copy = {
+ .imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
+ .imageExtent = {s->w, s->h, 1}};
+ vkCmdCopyBufferToImage(cb, stg, timg, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
+ ©);
+ // `vkCmdCopyBufferToImage`: This moves the data from the staging buffer to
+ // the final image. This function put the image on the high-speed VRAM
+
+ // now reformat the memory so the fragment shader can read it
+ VkImageMemoryBarrier b2 = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ .image = timg,
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0,
+ NULL, 1, &b2);
+ vkEndCommandBuffer(cb);
+ VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1,
+ .pCommandBuffers = &cb};
+ vkQueueSubmit(q, 1, &si, VK_NULL_HANDLE);
+ vkQueueWaitIdle(q);
+
+ // The image view acts as a window into the data VKImage is just a raw block
+ // of memory. The Image View tells Vulkan _how_ to interpret that memory
+ VkImageView tiv;
+ VkImageViewCreateInfo tivc = {
+ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = timg,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+ vkCreateImageView(dev, &tivc, NULL, &tiv);
+
+ // The sampler tells teh GPU hardware exactly how to extract color value from
+ // a texture when a shader asks for it. When you render the plane, it will
+ // rarely be exactly 1:1 with the pixels on your screen. The plane might be
+ // far away (shrinking the texture) or tilted (distorting it). The Sampler
+ // decides what color a pixel should be when it fills "between" the texels
+ // of the PNG.
+ VkSampler smp;
+ VkSamplerCreateInfo smpi = {
+ VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR,
+ .minFilter = VK_FILTER_LINEAR,
+ // magFiler / minFilter.
+ // Instead of picking the single closest pixel
+ // (which looks "blocky"), the GPU will take a
+ // weighted average of the four sorrounding
+ // texels. This makes the texture look smooth even
+ // when you get close to the plane
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT
+ // * This defines what happens if your UV coordinates go outside the 0.0
+ // to 1.0 range
+ };
+
+ vkCreateSampler(dev, &smpi, NULL, &smp);
+
+ // Plane Vertices
+ float w = asp * scale, h = 1.0f * scale;
+
+ Vertex vtx[] = {{{-w, -h, 0}, {0, 0}},
+ {{w, -h, 0}, {1, 0}},
+ {{w, h, 0}, {1, 1}},
+ {{-w, h, 0}, {0, 1}}};
+
+ // create two triangles from vtx
+ // Triangle 1: Connects vertices 0, 1, and 2.
+ // Triangle 2: Connects vertices 2, 3, and 0.
+ // By using an index buffer, you avoid redefining the same vertex coordinates
+ // multiple times, which saves memory and processing power.
+ uint16_t idx[] = {0, 1, 2, 2, 3, 0};
+ VkBuffer vb, ib;
+ VkDeviceMemory vbm, ibm;
+ // create buffer allocates and binds the buffer memory
+ create_buf(dev, phys, sizeof(vtx), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ &vb, &vbm);
+ create_buf(dev, phys, sizeof(idx), VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ &ib, &ibm);
+ vkMapMemory(dev, vbm, 0, sizeof(vtx), 0, &data);
+ memcpy(data, vtx, sizeof(vtx));
+ vkUnmapMemory(dev, vbm);
+ vkMapMemory(dev, ibm, 0, sizeof(idx), 0, &data);
+ memcpy(data, idx, sizeof(idx));
+ vkUnmapMemory(dev, ibm);
+
+ // Pipeline & Descriptors
+ // setting
+ // layout(binding = 0) uniform sampler2D texSampler;
+ // in fragment shader
+ VkDescriptorSetLayoutBinding lb = {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ // ^^ you are telling vulkan that this slot will hold both the texture
+ // data and the instructions on how to read it (the sampler).
+ // This must match the variable type in your shader.
+ // In GLSL, a sampler2D is technically a "Combined Image Sampler" because
+ // it represents both the Image data (the pixels) and the Sampler (the
+ // filtering rules).
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT};
+ VkDescriptorSetLayoutCreateInfo dlc = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1,
+ .pBindings = &lb};
+ VkDescriptorSetLayout dsl;
+ vkCreateDescriptorSetLayout(dev, &dlc, NULL, &dsl);
+ VkPushConstantRange pcr = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(Mat4)
+ };
+
+ VkPipelineLayoutCreateInfo plc = {
+ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1,
+ .pSetLayouts = &dsl, .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pcr};
+ VkPipelineLayout play;
+ vkCreatePipelineLayout(dev, &plc, NULL, &play);
+
+ // Shaders (Load file logic)
+ FILE *fv = fopen("cube.vert.spv", "rb");
+ fseek(fv, 0, 2);
+ long sv = ftell(fv);
+ rewind(fv);
+ char *bv = malloc(sv);
+ fread(bv, 1, sv, fv);
+ fclose(fv);
+ FILE *ff = fopen("cube.frag.spv", "rb");
+ fseek(ff, 0, 2);
+ long sf = ftell(ff);
+ rewind(ff);
+ char *bf = malloc(sf);
+ fread(bf, 1, sf, ff);
+ fclose(ff);
+ VkShaderModuleCreateInfo mvi = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = sv, .pCode = (uint32_t *)bv},
+ mfi = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = sf, .pCode = (uint32_t *)bf};
+ VkShaderModule mv, mf;
+ vkCreateShaderModule(dev, &mvi, NULL, &mv);
+ vkCreateShaderModule(dev, &mfi, NULL, &mf);
+
+ VkPipelineShaderStageCreateInfo ss[] = {
+ {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = mv, .pName = "main"},
+ {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = mf, .pName = "main"}};
+ VkVertexInputBindingDescription bd = {0, sizeof(Vertex),
+ VK_VERTEX_INPUT_RATE_VERTEX};
+ VkVertexInputAttributeDescription ad[] = {
+ // Matches: layout(location = 0) in vec3 inPos;
+ {.location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = 0},
+
+ // VK_FORMAT_R32G32B32_SFLOAT tells the GPU that inPos is 3 floats (X, Y,
+ // Z), which matches vec3.
+
+ // Matches: layout(location = 1) in vec2 inUV;
+ {.location = 1,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32_SFLOAT,
+ .offset = sizeof(float) * 3}
+ };
+
+ VkPipelineVertexInputStateCreateInfo vi = {
+ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bd,
+ .vertexAttributeDescriptionCount = 2, .pVertexAttributeDescriptions = ad};
+ VkPipelineInputAssemblyStateCreateInfo ia = {
+ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
+ VkPipelineViewportStateCreateInfo vp = {
+ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1,
+ .scissorCount = 1};
+ VkPipelineRasterizationStateCreateInfo rs = {
+ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .lineWidth = 1.0f};
+ VkPipelineMultisampleStateCreateInfo ms = {
+ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT};
+ VkPipelineColorBlendAttachmentState cba = {.colorWriteMask = 0xF};
+ VkPipelineColorBlendStateCreateInfo cbsc = {
+ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount = 1, .pAttachments = &cba};
+ VkDynamicState ds[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+ VkPipelineDynamicStateCreateInfo dy = {
+ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .dynamicStateCount = 2, .pDynamicStates = ds};
+ VkPipelineRenderingCreateInfo prc = {
+ VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1, .pColorAttachmentFormats = &sc.imageFormat};
+
+ VkGraphicsPipelineCreateInfo gpc = {
+ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &prc,
+ .stageCount = 2,
+ .pStages = ss,
+ .pVertexInputState = &vi,
+ .pInputAssemblyState = &ia,
+ .pViewportState = &vp,
+ .pRasterizationState = &rs,
+ .pMultisampleState = &ms,
+ .pColorBlendState = &cbsc,
+ .pDynamicState = &dy,
+ .layout = play};
+
+ VkPipeline pipe;
+ vkCreateGraphicsPipelines(dev, NULL, 1, &gpc, NULL, &pipe);
+
+ VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1};
+ VkDescriptorPool dp;
+ VkDescriptorPoolCreateInfo dpc = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets = 1,
+ .poolSizeCount = 1, .pPoolSizes = &ps};
+ vkCreateDescriptorPool(dev, &dpc, NULL, &dp);
+ VkDescriptorSetAllocateInfo dsa = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = dp,
+ .descriptorSetCount = 1, .pSetLayouts = &dsl};
+ VkDescriptorSet dset;
+ vkAllocateDescriptorSets(dev, &dsa, &dset);
+ VkDescriptorImageInfo dii = {smp, tiv,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
+ VkWriteDescriptorSet wds = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = dset, .descriptorCount = 1,
+ .descriptorType =
+ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &dii};
+ vkUpdateDescriptorSets(dev, 1, &wds, 0, NULL);
+
+ VkSemaphore sem_img, sem_rn;
+ VkSemaphoreCreateInfo smi = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
+ vkCreateSemaphore(dev, &smi, NULL, &sem_img);
+ vkCreateSemaphore(dev, &smi, NULL, &sem_rn);
+ VkFence fn;
+ VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT};
+ vkCreateFence(dev, &fci, NULL, &fn);
+
+ bool run = 1;
+ SDL_Event e;
+ while (run) {
+ while (SDL_PollEvent(&e)) {
+ if (e.type == SDL_QUIT)
+ run = 0;
+ if (e.type == SDL_MOUSEMOTION) {
+ printf("relative mouse: (x: %d, y: %d)\n", e.motion.xrel, e.motion.yrel);
+ yaw += e.motion.xrel * 0.1f;
+ pitch -= e.motion.yrel * 0.1f;
+ pitch = fmaxf(-89, fminf(89, pitch));
+ }
+ }
+
+ const uint8_t *k = SDL_GetKeyboardState(NULL);
+ float spd = 0.05f;
+ // Spherical to Cartesian Coordinates
+ // Here we are converting degrees to radians
+ // degree * PI / 180
+ // on the unit circle sin is y and cos is x
+ camFwd.x = cosf(yaw * M_PI / 180) * cosf(pitch * M_PI / 180);
+ camFwd.y = sinf(pitch * M_PI / 180);
+ camFwd.z = sinf(yaw * M_PI / 180) * cosf(pitch * M_PI / 180);
+ // Why the cos(pitch) multiplier for x and z?
+ // As you look further up or down (increasing pitch), the horizontal circle
+ // you are looking "around" gets smaller. If you didn't multiply x and z
+ // by cos(pitch, )the camera would move faster horizontally when
+ // looking at the horizon and slower when looking at the sky. This
+ // multiplier keeps the "length" of the vector consistent.
+
+ // MY NOTE:
+ // remeber, because cos() starts at one, that when the mouse is in the
+ // center in the screen there is no scaling by cos(pith)
+ camFwd = v3_norm(camFwd);
+ Vec3 rgt = v3_norm(v3_cross(camFwd, (Vec3){0, 1, 0}));
+ if (k[SDL_SCANCODE_W])
+ camPos = v3_add(camPos, v3_mul(camFwd, spd));
+ if (k[SDL_SCANCODE_S])
+ camPos = v3_sub(camPos, v3_mul(camFwd, spd));
+ if (k[SDL_SCANCODE_A])
+ camPos = v3_sub(camPos, v3_mul(rgt, spd));
+ if (k[SDL_SCANCODE_D])
+ camPos = v3_add(camPos, v3_mul(rgt, spd));
+
+ vkWaitForFences(dev, 1, &fn, 1, UINT64_MAX);
+ vkResetFences(dev, 1, &fn);
+ uint32_t idx;
+ vkAcquireNextImageKHR(dev, swp, UINT64_MAX, sem_img, 0, &idx);
+ vkResetCommandPool(dev, cp, 0);
+ vkBeginCommandBuffer(cb, &bi);
+ VkImageMemoryBarrier ibr = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .image = simg[idx],
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0,
+ NULL, 0, NULL, 1, &ibr);
+
+ VkRenderingAttachmentInfo rai = {
+ .imageView = siv[idx],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = {0, 0, 0, 1}};
+ VkRenderingInfo ri = {VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {{0, 0}, ext}, .layerCount = 1,
+ .colorAttachmentCount = 1, .pColorAttachments = &rai};
+ vkCmdBeginRendering(cb, &ri);
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
+ VkViewport v = {0, 0, ext.width, ext.height, 0, 1};
+ vkCmdSetViewport(cb, 0, 1, &v);
+ VkRect2D sc = {0, 0, ext.width, ext.height};
+ vkCmdSetScissor(cb, 0, 1, &sc);
+ Mat4 proj = m4_persp(M_PI / 4, (float)ext.width / ext.height, 0.1f, 100.0f);
+ Mat4 view = m4_lookat(camPos, v3_add(camPos, camFwd), (Vec3){0, 1, 0});
+ Mat4 mvp = m4_mul(view, proj);
+ vkCmdPushConstants(cb, play, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(Mat4),
+ &mvp);
+ vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, play, 0, 1,
+ &dset, 0, NULL);
+ VkDeviceSize off = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vb, &off);
+ vkCmdBindIndexBuffer(cb, ib, 0, VK_INDEX_TYPE_UINT16);
+ vkCmdDrawIndexed(cb, 6, 1, 0, 0, 0);
+ vkCmdEndRendering(cb);
+
+ VkImageMemoryBarrier pbr = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .image = simg[idx],
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0,
+ NULL, 1, &pbr);
+ vkEndCommandBuffer(cb);
+
+ VkPipelineStageFlags wait = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo sub = {
+ VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &sem_img, .pWaitDstStageMask = &wait,
+ .commandBufferCount = 1, .pCommandBuffers = &cb,
+ .signalSemaphoreCount = 1, .pSignalSemaphores = &sem_rn};
+ vkQueueSubmit(q, 1, &sub, fn);
+ VkPresentInfoKHR pres = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &sem_rn,
+ .swapchainCount = 1,
+ .pSwapchains = &swp,
+ .pImageIndices = &idx};
+ vkQueuePresentKHR(q, &pres);
+ }
+ vkDeviceWaitIdle(dev);
+ return 0;
+}
--- /dev/null
+== running
+
+----
+./vk_plane posz.png 1
+----
+
+== Terminology
+
+* *A frustum of a pyramid*: is a solid geometric figure created by cutting a
+ pyramid with a plane parallel to its base. This cut results in a new face,
+ smaller than the original base, and transforms the pyramid into a solid with
+ two parallel bases.
+
+== Ratio of similar triangles
+
+for stem:[(X_e,Y_e,Y_e)] and stem:[(X_p, Y_p, Z_p)] that are similar triangles
+
+[stem]
+++++
+\frac{X_e}{X_p} = \frac{Z_p}{Z_e}
+++++
+
+
+
+== Links
+* https://songho.ca/opengl/gl_projectionmatrix.html
+* https://www.andreaminini.net/math/frustum-of-a-pyramid[frustum-of-a-pyramid]
+* https://www.youtube.com/watch?v=Yc6q2w8Nt38[Projection Matrices Properties]
+* https://www.youtube.com/watch?v=U0_ONQQ5ZNM[Brendan Galea | The Math behind (most) 3D games]
+* https://www.youtube.com/watch?v=YO46x8fALzE[Brendan Galea | Projection Matrices]
+* https://morning-flow.com/2023/02/20/the-perspective-transformation-and-collinearity/
+* https://www.dgp.toronto.edu/~mtao/files/csc418/life_poly.pdf
+* https://johannesugb.github.io/gpu-programming/setting-up-a-proper-vulkan-projection-matrix/[Vulkan | clip Space]
+* https://en.wikipedia.org/wiki/Orthographic_projection[wikipedia | Orthographic Projection]
+* https://learnopengl.com/Getting-started/Camera[learnopengl.com | look at matrix]
--- /dev/null
+#version 450
+layout(location = 0) in vec2 inUV;
+layout(location = 0) out vec4 outColor;
+layout(binding = 0) uniform sampler2D texSampler;
+
+void main() {
+ // Flip the y-coordinate before sampling
+ outColor = texture(texSampler, vec2(inUV.x, 1.0 - inUV.y));
+}
--- /dev/null
+#version 450
+layout(push_constant) uniform Push { mat4 mvp; } pc;
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec2 inUV;
+layout(location = 0) out vec2 outUV;
+void main() {
+ gl_Position = pc.mvp * vec4(inPos, 1.0);
+ outUV = inUV;
+}
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_vulkan.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <volk/volk.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+// --- MATH (math.h only) ---
+typedef struct {
+ float x, y, z;
+} Vec3;
+
+typedef struct {
+ float m[4][4];
+} Mat4;
+
+Vec3 v3_sub(Vec3 a, Vec3 b) { return (Vec3){a.x - b.x, a.y - b.y, a.z - b.z}; }
+Vec3 v3_add(Vec3 a, Vec3 b) { return (Vec3){a.x + b.x, a.y + b.y, a.z + b.z}; }
+Vec3 v3_mul(Vec3 a, float s) { return (Vec3){a.x * s, a.y * s, a.z * s}; }
+float v3_dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
+
+Vec3 v3_cross(Vec3 a, Vec3 b) {
+ return (Vec3){a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z,
+ a.x * b.y - a.y * b.x};
+}
+Vec3 v3_norm(Vec3 v) {
+ float l = sqrtf(v3_dot(v, v));
+ return (l > 0) ? v3_mul(v, 1.0f / l) : v;
+}
+
+Mat4 m4_id() {
+ Mat4 r = {0};
+ for (int i = 0; i < 4; i++)
+ r.m[i][i] = 1;
+ return r;
+}
+
+Mat4 m4_mul(Mat4 a, Mat4 b) {
+ Mat4 r = {0};
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ for (int k = 0; k < 4; k++)
+ r.m[i][j] += a.m[i][k] * b.m[k][j];
+ return r;
+
+}
+Mat4 m4_persp(float fov, float asp, float n, float f) {
+ float t = tanf(fov / 2);
+ Mat4 r = {0};
+ r.m[0][0] = 1 / (asp * t);
+ r.m[1][1] = -1 / t;
+ r.m[2][2] = f / (n - f);
+ r.m[2][3] = -1;
+ r.m[3][2] = (n * f) / (n - f);
+ return r;
+}
+
+Mat4 m4_lookat(Vec3 eye, Vec3 ctr, Vec3 up) {
+ Vec3 f = v3_norm(v3_sub(ctr, eye)), s = v3_norm(v3_cross(f, up)),
+ u = v3_cross(s, f);
+ Mat4 r = m4_id();
+ r.m[0][0] = s.x;
+ r.m[1][0] = s.y;
+ r.m[2][0] = s.z;
+ r.m[0][1] = u.x;
+ r.m[1][1] = u.y;
+ r.m[2][1] = u.z;
+ r.m[0][2] = -f.x;
+ r.m[1][2] = -f.y;
+ r.m[2][2] = -f.z;
+ r.m[3][0] = -v3_dot(s, eye);
+ r.m[3][1] = -v3_dot(u, eye);
+ r.m[3][2] = v3_dot(f, eye);
+ return r;
+}
+
+// --- VULKAN HELPERS ---
+typedef struct {
+ float pos[3], uv[2];
+} Vertex;
+
+uint32_t find_mem(VkPhysicalDevice p, uint32_t f, VkMemoryPropertyFlags pr) {
+ VkPhysicalDeviceMemoryProperties m;
+
+ // typedef struct VkPhysicalDeviceMemoryProperties {
+ // uint32_t memoryTypeCount;
+ // VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
+ // uint32_t memoryHeapCount;
+ // VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS];
+ // } VkPhysicalDeviceMemoryProperties;
+
+ vkGetPhysicalDeviceMemoryProperties(p, &m);
+
+ // This line queries you graphics card to find out what its memory
+ // architecture looks like. It fills the `m` struct with two main things:
+ //
+ // - Memory heaps
+ // - Memory Types
+
+ // typedef enum VkMemoryPropertyFlagBits {
+ // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001,
+ // VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002,
+ // VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004,
+ // VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008,
+ // VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010,
+ // // Provided by VK_VERSION_1_1
+ // VK_MEMORY_PROPERTY_PROTECTED_BIT = 0x00000020,
+ // // Provided by VK_AMD_device_coherent_memory
+ // VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD = 0x00000040,
+ // // Provided by VK_AMD_device_coherent_memory
+ // VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD = 0x00000080,
+ // // Provided by VK_NV_external_memory_rdma
+ // VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV = 0x00000100,
+ // } VkMemoryPropertyFlagBits;
+
+ // printf("memoryTypeBits: %d\n", f);
+
+ //`memoryTypeBits` (f) is a bitmask containing one bit set for every memory
+ //type which the specified open Harmony OS native buffer can be imported as
+ for (uint32_t i = 0; i < m.memoryTypeCount; i++)
+
+ // - The f bitmask is a map of all valid "slots" (indices) in the
+ // m.memoryTypes array
+
+ // - If f is 0b00001011, it means indices 0, 1, and 3 are
+ // hardware-compatible.
+
+ // - Each of those indices represents a completely different type of memory
+ // with different properties.
+
+ // Example
+ // - If Bit 3 is a 1: The GPU hardware can physically wire this buffer to the
+ // memory described at Index 3.
+
+ // - If Bit 3 is a 0: It is physically impossible (or prohibited by the
+ // driver) to put this specific buffer into Index 3.
+
+ // the left side of && just checks that the bits are the same in
+ // propertyFlags and pr. If there are extra bits it propertyFlags it's
+ // fine
+ if ((f & (1 << i)) && (m.memoryTypes[i].propertyFlags & pr) == pr)
+ return i;
+ return 0;
+}
+
+void create_buf(VkDevice d, VkPhysicalDevice p, VkDeviceSize size,
+ VkBufferUsageFlags u, VkMemoryPropertyFlags pr, VkBuffer *b,
+ VkDeviceMemory *m) {
+
+ VkBufferCreateInfo bi = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = u,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ // ^^^^
+ // resource is owned by exactly one queue family at a time
+ };
+
+ if (vkCreateBuffer(d, &bi, NULL, b) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create buffer\n");
+ exit(1);
+ }
+
+ VkMemoryRequirements req;
+ vkGetBufferMemoryRequirements(d, *b, &req);
+
+ VkMemoryAllocateInfo ai = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = req.size,
+ .memoryTypeIndex =
+ find_mem(p, req.memoryTypeBits, pr)};
+
+ if (vkAllocateMemory(d, &ai, NULL, m) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to allocate buffer memory\n");
+ exit(1);
+ }
+
+ vkBindBufferMemory(d, *b, *m, 0);
+}
+
+// --- APP STATE ---
+Vec3 camPos = {0, 0, 3}, camFwd = {0, 0, -1};
+float yaw = -90.0f, pitch = 0.0f;
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ printf("Usage: %s <png> [scale]\n", argv[0]);
+ return 1;
+ }
+ float scale = (argc > 2) ? atof(argv[2]) : 1.0f;
+
+ volkInitialize();
+ SDL_Init(SDL_INIT_VIDEO);
+ IMG_Init(IMG_INIT_PNG);
+ SDL_Window *win =
+ SDL_CreateWindow("Vulkan Plane", SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_VULKAN);
+
+ SDL_SetRelativeMouseMode(SDL_TRUE);
+
+ // Instance & Surface
+ uint32_t extC = 0;
+ SDL_Vulkan_GetInstanceExtensions(win, &extC, NULL);
+ // const char **exts = malloc(sizeof(char *) * extC);
+ const char *exts[extC];
+ SDL_Vulkan_GetInstanceExtensions(win, &extC, exts);
+ VkInstance inst;
+ VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .apiVersion = VK_API_VERSION_1_3};
+ VkInstanceCreateInfo ic = {
+ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app,
+ .enabledExtensionCount = extC, .ppEnabledExtensionNames = exts};
+
+ // Load instance level pointers, they handle things like querying physical
+ // device properties, creating a surface, or picking a GPU
+ vkCreateInstance(&ic, NULL, &inst);
+ volkLoadInstance(inst);
+ VkSurfaceKHR surf;
+ SDL_Vulkan_CreateSurface(win, inst, &surf);
+
+ // Device
+ VkPhysicalDevice phys;
+ uint32_t pc = 1;
+ vkEnumeratePhysicalDevices(inst, &pc, &phys);
+ float pri = 1.0f;
+ // higher priority gets more "time slices" or execution unitson the chip.
+ VkDeviceQueueCreateInfo qc = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueCount = 1, .pQueuePriorities = &pri};
+ VkPhysicalDeviceDynamicRenderingFeatures df = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE};
+ const char *de[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME};
+ VkDevice dev;
+ VkDeviceCreateInfo dc = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &df,
+ .queueCreateInfoCount = 1,
+ .pQueueCreateInfos = &qc,
+ .enabledExtensionCount = 2,
+ .ppEnabledExtensionNames = de};
+
+ // Create device-level pointers. these are your "hot path" commands - drawing,
+ // dispatching cumpute shaders, and memory management
+ vkCreateDevice(phys, &dc, NULL, &dev);
+ volkLoadDevice(dev);
+ VkQueue q;
+
+ // this function retrives a VKQueue object
+ //
+ // NOTE: I dont think this is the best way to get a que. you aren't
+ // searching for ques available on you're device
+ //
+ // void vkGetDeviceQueue(
+ // VkDevice device,
+ // uint32_t queueFamilyIndex,
+ // uint32_t queueIndex,
+ // VkQueue* pQueue);
+ vkGetDeviceQueue(dev, 0, 0, &q);
+
+ // Swapchain
+ VkSwapchainKHR swp;
+ VkExtent2D ext = {1280, 720}; // windows resolutions
+
+ VkSwapchainCreateInfoKHR sc = {
+ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surf,
+ // we didn't check how many surfaces we support first!
+ .minImageCount = 2,
+ .imageFormat = VK_FORMAT_B8G8R8A8_UNORM,
+ // ^^^^
+ // This tells the GPU that each pixel consists of Blue, Green, Red, and
+ // Alpha components, in that specific order, each taking up 8 bits
+ //
+ // UNORM (Unsigned Normalized) means the values are stored as integers
+ // (0–255) but are treated as floating-point values (0.0–1.0) in your
+ // shaders.
+ .imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
+ // ^^^^^
+ // This tells teh GPU that each pixel consists of Blue, Green, Red, and
+ // Alpha components, in that specific order, each taking up 8 bits
+ .imageExtent = ext, .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ // ^^^^
+ // This is the most common setting. It means you will be rendering
+ // directly into these images (they will be the "target" of your graphics
+ // pipeline)
+ .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+ // This is saying to not do image transformation
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ // ^^^^
+ // This tells the OS that your window is fully opaque. The desktop
+ // compositor will not try to "look through" your game to see what is
+ // behind it.
+ .presentMode = VK_PRESENT_MODE_FIFO_KHR};
+
+ // VkResult vkCreateSwapchainKHR(
+ // VkDevice device,
+ // const VkSwapchainCreateInfoKHR* pCreateInfo,
+ // const VkAllocationCallbacks* pAllocator,
+ // VkSwapchainKHR* pSwapchain);
+
+ // `pAllocator` is the allocator used for host memory allocated for the
+ // swapchain object when there is no more specific allocator availabl
+ vkCreateSwapchainKHR(dev, &sc, NULL, &swp);
+ uint32_t icount;
+ vkGetSwapchainImagesKHR(dev, swp, &icount, NULL);
+ VkImage simg[icount];
+ vkGetSwapchainImagesKHR(dev, swp, &icount, simg);
+ // ^^ obtain the arry of presentable images associated with the swapchain
+
+ // create an image view for each swapchain image
+ // ----------------------------------------------
+ // A VkImage is just a collection of pixels in a specific memory layout.
+ // However, the GPU needs to know how to look at those pixels. The VkImageView
+ // provides the metadata that describes it
+ VkImageView siv[icount];
+
+ for (int i = 0; i < icount; i++) {
+
+ VkImageViewCreateInfo iv = {
+ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = simg[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = VK_FORMAT_B8G8R8A8_UNORM,
+
+ // Think of a VkImage as a container that can hold more than just one
+ // flat picture. It can hold a stack of images (Array Layers) or various
+ // resolutions of the same image (Mip Levels). This structure is the
+ // "address" that specifies the target area.
+
+ .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1}};
+ vkCreateImageView(dev, &iv, NULL, &siv[i]);
+ }
+
+ // Texture Load
+ SDL_Surface *s = IMG_Load(argv[1]);
+ float asp = (float)s->w / s->h; // aspect ratio
+ VkBuffer stg; // used in command buffers
+ VkDeviceMemory smem; // used for writing data to buffer form CPU
+
+ // create buffer allocates and binds the buffer memory
+ // we mulitple by 4 because we need RGBA for each color channel
+ create_buf(dev, phys, s->w * s->h * 4, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ // ^^ The CPU is allows to directly access this memory
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ // ^^ No manual flush needed
+ &stg, &smem);
+
+ // You use HOST_VISIBLE memory because the CPU cannot directly "talk" to
+ // high-speed VRAM; it needs a shared space to drop off the data
+ //
+ void *data;
+ vkMapMemory(dev, smem, 0, s->w * s->h * 4, 0, &data);
+ memcpy(data, s->pixels, s->w * s->h * 4);
+ vkUnmapMemory(dev, smem);
+ // Once you unmap you cannot write to the data variable. The bridge between
+ // the GPU and CPU is gone
+
+ // Mapping: vkMapMemory gets a CPU pointer (data) that points to the
+ // GPU-accessible staging memory.
+
+ // Copying: memcpy physically moves the bytes from the SDL_Surface to that
+ // staging memory
+
+ // Unmapping: You invalidate the pointer because the CPU is finished with its
+ // part of the job
+
+ VkImage timg;
+ VkDeviceMemory tmem;
+ VkImageCreateInfo ti = {
+ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {s->w, s->h, 1},
+ // ^^^ 3d extent
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ // ^^^ value speciying the number of samples per texel
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+ };
+ // you specify TILING_OPTIMAL. This allows the GPU to rearrange pixels into a
+ // "tiled" layout (like a Z-order curve) for lightning-fast cache access
+ // during sampling
+
+ // VK_IMAGE_TILING_LINEAR
+ // This is the way a standard C array or a .bmp file is organized. Texels
+ // are laid out row by row (horizontally).
+ // * Memory Map: [Row 0, Pixel 0][Row 0, Pixel 1] ... [Row 1, Pixel 0]
+ // * Pros: The CPU can easily understand it. You can memcpy directly into it
+ // and read it like a normal buffer.
+ //
+ // Cons: It is terrible for GPU performance. Because of how GPU caches work,
+ // jumping from the end of one row to the start of the next is a long
+ // "stride" in memory, which causes cache misses.
+ vkCreateImage(dev, &ti, NULL, &timg); // returns timg
+ VkMemoryRequirements tr;
+ vkGetImageMemoryRequirements(dev, timg, &tr);
+ VkMemoryAllocateInfo ta = {
+ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = tr.size,
+ .memoryTypeIndex = find_mem(phys, tr.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)};
+ vkAllocateMemory(dev, &ta, NULL, &tmem);
+ vkBindImageMemory(dev, timg, tmem, 0);
+
+ // Command Pool for setup
+ VkCommandPool cp;
+ // VkCommandPoolCreateInfo cpi={VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
+ VkCommandPoolCreateInfo cpi = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = 0, // Ensure this matches your graphics queue family
+ .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
+ // `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT` flag is a memory
+ // management setting that determines how you are allowed to reuse your
+ // command buffers.
+
+ // In Vulkan, recording commands is an expensive process. To save time,
+ // you don't want to keep destroying and re-creating command buffers every
+ // frame. Instead, you want to reset them so you can record new
+ // instructions (like the updated camera position) into the same memory
+ // now you can do `vkResetCommandPool(dev, cp, 0)`
+ };
+
+ vkCreateCommandPool(dev, &cpi, NULL, &cp);
+ VkCommandBuffer cb;
+ VkCommandBufferAllocateInfo cai = {
+ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = cp,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1};
+ vkAllocateCommandBuffers(dev, &cai, &cb);
+
+ VkCommandBufferBeginInfo bi = {
+ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
+
+ vkBeginCommandBuffer(cb, &bi);
+
+ // --------------------------------------------------------------------------
+ // This block of code is a Pipeline Barrier. In the fascia engine, this acts
+ // as a "State Transition." You are telling the GPU to prepare the memory of
+ // your texture (timg) for a specific operation.
+
+ // In Vulkan, images don't just "exist" as pixels; they exist in specific
+ // Layouts optimized for different tasks. Before you can copy your PNG data
+ // into the image, you have to tell the GPU to change that image's internal
+ // "mode."
+ // --------------------------------------------------------------------------
+ VkImageMemoryBarrier b1 = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ // The layout members here are not related to the layout variables in the
+ // shader
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .image = timg,
+ .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseArrayLayer = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ // ^^
+ // This is the destination.
+ // this tells the GPU "you are not allowed to start
+ // the `TRANSFER` stage (the actual copying of pixels)
+ // until this transition is finished"
+ 0, 0, NULL, 0, NULL, 1,
+ &b1);
+ // --------------------------------------------------------------------------
+
+ VkBufferImageCopy copy = {
+ .imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
+ .imageExtent = {s->w, s->h, 1}};
+ vkCmdCopyBufferToImage(cb, stg, timg, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
+ ©);
+ // `vkCmdCopyBufferToImage`: This moves the data from the staging buffer to
+ // the final image. This function put the image on the high-speed VRAM
+
+ // now reformat the memory so the fragment shader can read it
+ VkImageMemoryBarrier b2 = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ .image = timg,
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0,
+ NULL, 1, &b2);
+ vkEndCommandBuffer(cb);
+ VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1,
+ .pCommandBuffers = &cb};
+ vkQueueSubmit(q, 1, &si, VK_NULL_HANDLE);
+ vkQueueWaitIdle(q);
+
+ // The image view acts as a window into the data VKImage is just a raw block
+ // of memory. The Image View tells Vulkan _how_ to interpret that memory
+ VkImageView tiv;
+ VkImageViewCreateInfo tivc = {
+ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = timg,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+ vkCreateImageView(dev, &tivc, NULL, &tiv);
+
+ // The sampler tells teh GPU hardware exactly how to extract color value from
+ // a texture when a shader asks for it. When you render the plane, it will
+ // rarely be exactly 1:1 with the pixels on your screen. The plane might be
+ // far away (shrinking the texture) or tilted (distorting it). The Sampler
+ // decides what color a pixel should be when it fills "between" the texels
+ // of the PNG.
+ VkSampler smp;
+ VkSamplerCreateInfo smpi = {
+ VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR,
+ .minFilter = VK_FILTER_LINEAR,
+ // magFiler / minFilter.
+ // Instead of picking the single closest pixel
+ // (which looks "blocky"), the GPU will take a
+ // weighted average of the four sorrounding
+ // texels. This makes the texture look smooth even
+ // when you get close to the plane
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT
+ // * This defines what happens if your UV coordinates go outside the 0.0
+ // to 1.0 range
+ };
+
+ vkCreateSampler(dev, &smpi, NULL, &smp);
+
+ // Plane Vertices
+ float w = asp * scale, h = 1.0f * scale;
+
+ Vertex vtx[] = {{{-w, -h, 0}, {0, 0}},
+ {{w, -h, 0}, {1, 0}},
+ {{w, h, 0}, {1, 1}},
+ {{-w, h, 0}, {0, 1}}};
+
+ // create two triangles from vtx
+ // Triangle 1: Connects vertices 0, 1, and 2.
+ // Triangle 2: Connects vertices 2, 3, and 0.
+ // By using an index buffer, you avoid redefining the same vertex coordinates
+ // multiple times, which saves memory and processing power.
+ uint16_t idx[] = {0, 1, 2, 2, 3, 0};
+ VkBuffer vb, ib;
+ VkDeviceMemory vbm, ibm;
+ // create buffer allocates and binds the buffer memory
+ create_buf(dev, phys, sizeof(vtx), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ &vb, &vbm);
+ create_buf(dev, phys, sizeof(idx), VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ &ib, &ibm);
+ vkMapMemory(dev, vbm, 0, sizeof(vtx), 0, &data);
+ memcpy(data, vtx, sizeof(vtx));
+ vkUnmapMemory(dev, vbm);
+ vkMapMemory(dev, ibm, 0, sizeof(idx), 0, &data);
+ memcpy(data, idx, sizeof(idx));
+ vkUnmapMemory(dev, ibm);
+
+ // Pipeline & Descriptors
+ // setting
+ // layout(binding = 0) uniform sampler2D texSampler;
+ // in fragment shader
+ VkDescriptorSetLayoutBinding lb = {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ // ^^ you are telling vulkan that this slot will hold both the texture
+ // data and the instructions on how to read it (the sampler).
+ // This must match the variable type in your shader.
+ // In GLSL, a sampler2D is technically a "Combined Image Sampler" because
+ // it represents both the Image data (the pixels) and the Sampler (the
+ // filtering rules).
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT};
+ VkDescriptorSetLayoutCreateInfo dlc = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1,
+ .pBindings = &lb};
+ VkDescriptorSetLayout dsl;
+ vkCreateDescriptorSetLayout(dev, &dlc, NULL, &dsl);
+ VkPushConstantRange pcr = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(Mat4)
+ };
+
+ VkPipelineLayoutCreateInfo plc = {
+ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1,
+ .pSetLayouts = &dsl, .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pcr};
+ VkPipelineLayout play;
+ vkCreatePipelineLayout(dev, &plc, NULL, &play);
+
+ // Shaders (Load file logic)
+ FILE *fv = fopen("cube.vert.spv", "rb");
+ fseek(fv, 0, 2);
+ long sv = ftell(fv);
+ rewind(fv);
+ char *bv = malloc(sv);
+ fread(bv, 1, sv, fv);
+ fclose(fv);
+ FILE *ff = fopen("cube.frag.spv", "rb");
+ fseek(ff, 0, 2);
+ long sf = ftell(ff);
+ rewind(ff);
+ char *bf = malloc(sf);
+ fread(bf, 1, sf, ff);
+ fclose(ff);
+ VkShaderModuleCreateInfo mvi = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = sv, .pCode = (uint32_t *)bv},
+ mfi = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = sf, .pCode = (uint32_t *)bf};
+ VkShaderModule mv, mf;
+ vkCreateShaderModule(dev, &mvi, NULL, &mv);
+ vkCreateShaderModule(dev, &mfi, NULL, &mf);
+
+ VkPipelineShaderStageCreateInfo ss[] = {
+ {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = mv, .pName = "main"},
+ {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = mf, .pName = "main"}};
+ VkVertexInputBindingDescription bd = {0, sizeof(Vertex),
+ VK_VERTEX_INPUT_RATE_VERTEX};
+ VkVertexInputAttributeDescription ad[] = {
+ // Matches: layout(location = 0) in vec3 inPos;
+ {.location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = 0},
+
+ // VK_FORMAT_R32G32B32_SFLOAT tells the GPU that inPos is 3 floats (X, Y,
+ // Z), which matches vec3.
+
+ // Matches: layout(location = 1) in vec2 inUV;
+ {.location = 1,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32_SFLOAT,
+ .offset = sizeof(float) * 3}
+ };
+
+ VkPipelineVertexInputStateCreateInfo vi = {
+ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bd,
+ .vertexAttributeDescriptionCount = 2, .pVertexAttributeDescriptions = ad};
+ VkPipelineInputAssemblyStateCreateInfo ia = {
+ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
+ VkPipelineViewportStateCreateInfo vp = {
+ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1,
+ .scissorCount = 1};
+ VkPipelineRasterizationStateCreateInfo rs = {
+ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .lineWidth = 1.0f};
+ VkPipelineMultisampleStateCreateInfo ms = {
+ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT};
+ VkPipelineColorBlendAttachmentState cba = {.colorWriteMask = 0xF};
+ VkPipelineColorBlendStateCreateInfo cbsc = {
+ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount = 1, .pAttachments = &cba};
+ VkDynamicState ds[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+ VkPipelineDynamicStateCreateInfo dy = {
+ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .dynamicStateCount = 2, .pDynamicStates = ds};
+ VkPipelineRenderingCreateInfo prc = {
+ VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1, .pColorAttachmentFormats = &sc.imageFormat};
+
+ VkGraphicsPipelineCreateInfo gpc = {
+ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &prc,
+ .stageCount = 2,
+ .pStages = ss,
+ .pVertexInputState = &vi,
+ .pInputAssemblyState = &ia,
+ .pViewportState = &vp,
+ .pRasterizationState = &rs,
+ .pMultisampleState = &ms,
+ .pColorBlendState = &cbsc,
+ .pDynamicState = &dy,
+ .layout = play};
+
+ VkPipeline pipe;
+ vkCreateGraphicsPipelines(dev, NULL, 1, &gpc, NULL, &pipe);
+
+ VkDescriptorPoolSize ps = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1};
+ VkDescriptorPool dp;
+ VkDescriptorPoolCreateInfo dpc = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets = 1,
+ .poolSizeCount = 1, .pPoolSizes = &ps};
+ vkCreateDescriptorPool(dev, &dpc, NULL, &dp);
+ VkDescriptorSetAllocateInfo dsa = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = dp,
+ .descriptorSetCount = 1, .pSetLayouts = &dsl};
+ VkDescriptorSet dset;
+ vkAllocateDescriptorSets(dev, &dsa, &dset);
+ VkDescriptorImageInfo dii = {smp, tiv,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
+ VkWriteDescriptorSet wds = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = dset, .descriptorCount = 1,
+ .descriptorType =
+ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &dii};
+ vkUpdateDescriptorSets(dev, 1, &wds, 0, NULL);
+
+ VkSemaphore sem_img, sem_rn;
+ VkSemaphoreCreateInfo smi = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
+ vkCreateSemaphore(dev, &smi, NULL, &sem_img);
+ vkCreateSemaphore(dev, &smi, NULL, &sem_rn);
+ VkFence fn;
+ VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT};
+ vkCreateFence(dev, &fci, NULL, &fn);
+
+ bool run = 1;
+ SDL_Event e;
+ while (run) {
+ while (SDL_PollEvent(&e)) {
+ if (e.type == SDL_QUIT)
+ run = 0;
+ if (e.type == SDL_MOUSEMOTION) {
+ printf("relative mouse: (x: %d, y: %d)\n", e.motion.xrel, e.motion.yrel);
+ yaw += e.motion.xrel * 0.1f;
+ pitch -= e.motion.yrel * 0.1f;
+ pitch = fmaxf(-89, fminf(89, pitch));
+ }
+ }
+
+ const uint8_t *k = SDL_GetKeyboardState(NULL);
+ float spd = 0.05f;
+ // Spherical to Cartesian Coordinates
+ camFwd.x = cosf(yaw * M_PI / 180) * cosf(pitch * M_PI / 180);
+ camFwd.y = sinf(pitch * M_PI / 180);
+ camFwd.z = sinf(yaw * M_PI / 180) * cosf(pitch * M_PI / 180);
+ // Why the cos(pitch) multiplier for x and z?
+ // As you look further up or down (increasing pitch), the horizontal circle
+ // you are looking "around" gets smaller. If you didn't multiply x and z
+ // by cos(pitch, )the camera would move faster horizontally when
+ // looking at the horizon and slower when looking at the sky. This
+ // multiplier keeps the "length" of the vector consistent.
+
+ // MY NOTE:
+ // remeber, because cos() starts at one, that when the mouse is in the
+ // center in the screen there is no scaling by cos(pith)
+ camFwd = v3_norm(camFwd);
+ Vec3 rgt = v3_norm(v3_cross(camFwd, (Vec3){0, 1, 0}));
+ if (k[SDL_SCANCODE_W])
+ camPos = v3_add(camPos, v3_mul(camFwd, spd));
+ if (k[SDL_SCANCODE_S])
+ camPos = v3_sub(camPos, v3_mul(camFwd, spd));
+ if (k[SDL_SCANCODE_A])
+ camPos = v3_sub(camPos, v3_mul(rgt, spd));
+ if (k[SDL_SCANCODE_D])
+ camPos = v3_add(camPos, v3_mul(rgt, spd));
+
+ vkWaitForFences(dev, 1, &fn, 1, UINT64_MAX);
+ vkResetFences(dev, 1, &fn);
+ uint32_t idx;
+ vkAcquireNextImageKHR(dev, swp, UINT64_MAX, sem_img, 0, &idx);
+ vkResetCommandPool(dev, cp, 0);
+ vkBeginCommandBuffer(cb, &bi);
+ VkImageMemoryBarrier ibr = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .image = simg[idx],
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0,
+ NULL, 0, NULL, 1, &ibr);
+
+ VkRenderingAttachmentInfo rai = {
+ .imageView = siv[idx],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = {0, 0, 0, 1}};
+ VkRenderingInfo ri = {VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {{0, 0}, ext}, .layerCount = 1,
+ .colorAttachmentCount = 1, .pColorAttachments = &rai};
+ vkCmdBeginRendering(cb, &ri);
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
+ VkViewport v = {0, 0, ext.width, ext.height, 0, 1};
+ vkCmdSetViewport(cb, 0, 1, &v);
+ VkRect2D sc = {0, 0, ext.width, ext.height};
+ vkCmdSetScissor(cb, 0, 1, &sc);
+ Mat4 proj = m4_persp(M_PI / 4, (float)ext.width / ext.height, 0.1f, 100.0f);
+ Mat4 view = m4_lookat(camPos, v3_add(camPos, camFwd), (Vec3){0, 1, 0});
+ Mat4 mvp = m4_mul(view, proj);
+ vkCmdPushConstants(cb, play, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(Mat4),
+ &mvp);
+ vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, play, 0, 1,
+ &dset, 0, NULL);
+ VkDeviceSize off = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vb, &off);
+ vkCmdBindIndexBuffer(cb, ib, 0, VK_INDEX_TYPE_UINT16);
+ vkCmdDrawIndexed(cb, 6, 1, 0, 0, 0);
+ vkCmdEndRendering(cb);
+
+ VkImageMemoryBarrier pbr = {
+ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .image = simg[idx],
+ .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0,
+ NULL, 1, &pbr);
+ vkEndCommandBuffer(cb);
+
+ VkPipelineStageFlags wait = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo sub = {
+ VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &sem_img, .pWaitDstStageMask = &wait,
+ .commandBufferCount = 1, .pCommandBuffers = &cb,
+ .signalSemaphoreCount = 1, .pSignalSemaphores = &sem_rn};
+ vkQueueSubmit(q, 1, &sub, fn);
+ VkPresentInfoKHR pres = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &sem_rn,
+ .swapchainCount = 1,
+ .pSwapchains = &swp,
+ .pImageIndices = &idx};
+ vkQueuePresentKHR(q, &pres);
+ }
+ vkDeviceWaitIdle(dev);
+ return 0;
+}
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_cube
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+Great question — this gets to **one of the core ideas Vulkan is built on**, and it’s very different from OpenGL / D3D11.
+
+I’ll explain **what binding memory means**, **why Vulkan requires it**, and **what actually happens inside the driver/GPU**.
+
+---
+
+## The short answer (intuition first)
+
+> **Creating a buffer or image does *not* allocate memory.
+> Binding memory is what gives that object actual storage.**
+
+Until you bind memory:
+
+* A `VkBuffer` or `VkImage` is just a **description**
+* It has **no backing bytes**
+* The GPU cannot read or write it
+
+Binding memory:
+
+* Assigns a **physical region of GPU-accessible memory**
+* Tells Vulkan *where* the object’s data lives
+* Fixes alignment and offset requirements
+
+---
+
+## What Vulkan objects really are
+
+### 1️⃣ `VkBuffer` / `VkImage` (no memory yet)
+
+When you do:
+
+```c
+vkCreateBuffer(...)
+vkCreateImage(...)
+```
+
+You are creating:
+
+* A **metadata object**
+* Size, format, usage flags
+* No storage
+
+Think of it like:
+
+> “I want a 2D image, 512×512, RGBA, sampled and copied into”
+
+But **no memory is reserved yet**.
+
+---
+
+### 2️⃣ `VkDeviceMemory` (raw memory block)
+
+When you do:
+
+```c
+vkAllocateMemory(...)
+```
+
+You get:
+
+* A chunk of memory
+* From a specific **heap**
+* With specific properties (HOST_VISIBLE, DEVICE_LOCAL, etc.)
+
+This memory:
+
+* Is **untyped**
+* Has no idea if it will store vertices, textures, or uniforms
+
+It’s just **bytes**.
+
+---
+
+### 3️⃣ Binding connects the two
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+vkBindImageMemory(device, image, memory, offset);
+```
+
+This says:
+
+> “This buffer/image *lives* starting at this byte offset in that memory block.”
+
+After binding:
+
+* The buffer/image has **real storage**
+* GPU accesses to that object resolve to actual memory addresses
+* Alignment requirements are guaranteed
+
+---
+
+## What binding does internally
+
+When you bind memory, the driver:
+
+1. Verifies alignment and size requirements
+2. Computes GPU virtual addresses
+3. Programs internal page tables / mappings
+4. Associates resource metadata with physical memory
+
+On modern GPUs this involves:
+
+* IOMMU / GPU MMU
+* Virtual address translation
+* Cache policy setup
+
+📌 This is why Vulkan **cannot auto-bind** memory — it would hide too many important decisions.
+
+---
+
+## Why Vulkan separates creation and binding
+
+### 🔹 Explicit memory control
+
+Vulkan allows things like:
+
+* Sub-allocating **multiple buffers in one allocation**
+* Packing resources tightly
+* Choosing different heaps per resource
+* Avoiding fragmentation
+
+Example:
+
+```c
+VkDeviceMemory bigBlock;
+vkAllocateMemory(..., &bigBlock);
+
+vkBindBufferMemory(device, vertexBuffer, bigBlock, 0);
+vkBindBufferMemory(device, indexBuffer, bigBlock, 65536);
+vkBindImageMemory (device, textureImage, bigBlock, 131072);
+```
+
+This is **impossible** in OpenGL.
+
+---
+
+### 🔹 Performance predictability
+
+The driver:
+
+* Does **zero hidden allocations**
+* Does **no implicit migrations**
+* Never guesses where to put memory
+
+You choose:
+
+* Fast GPU memory
+* CPU-visible memory
+* BAR-mapped memory
+* Unified memory
+
+---
+
+### 🔹 Multi-heap GPUs
+
+GPUs often have:
+
+* VRAM
+* System RAM
+* BAR-mapped regions
+* Cached vs uncached heaps
+
+Binding ensures:
+
+* The resource lives in a compatible heap
+* Usage flags match memory properties
+
+---
+
+## What happens if you don’t bind?
+
+If you try to use an unbound resource:
+
+* Validation layers scream
+* Behavior is **undefined**
+* GPU may fault or crash
+
+Because:
+
+> The resource literally has no address.
+
+---
+
+## Mental model (very important)
+
+```
+VkBuffer / VkImage
+ ↓ describes usage & format
+VkDeviceMemory
+ ↓ raw storage
+vkBind*Memory
+ ↓ connects them
+GPU can now access bytes
+```
+
+---
+
+## Why other APIs hide this
+
+| API | Memory model |
+| ------ | --------------------------- |
+| OpenGL | Driver decides everything |
+| D3D11 | Driver allocates implicitly |
+| Vulkan | You control everything |
+| D3D12 | Same as Vulkan |
+
+Vulkan trades **verbosity** for:
+
+* Zero surprises
+* Maximum performance
+* Precise memory control
+
+---
+
+## Key takeaway
+
+> **Binding memory is what turns a “resource description” into a “real GPU resource.”**
+
+Without binding:
+
+* No storage
+* No address
+* No GPU access
+
+---
+
+If you want next, I can:
+
+* Show a diagram of heaps → memory → buffers
+* Explain why images have stricter alignment
+* Explain aliasing & memory reuse
+* Explain how bindless / sparse resources change this
+
+Just say the word.
--- /dev/null
+Ah, that sentence is a bit dense. Let’s unpack it carefully.
+
+---
+
+### 1️⃣ Context
+
+Vulkan is a low-level API. When you create resources (like images, buffers, pipelines, etc.), it can be **expensive** in terms of time or GPU/driver work. For example, creating a pipeline involves compiling shaders, setting up internal GPU structures, etc.
+
+The sentence usually appears in **documentation about command pools or resource creation**:
+
+> *“Allow the implementation to amortize the cost of resource creation across multiple command buffers.”*
+
+---
+
+### 2️⃣ Breaking it down
+
+* **Amortize the cost:**
+ In computer science, “amortize” means **spread out a one-time expensive operation over multiple uses** so that each individual use appears cheaper.
+
+* **Resource creation:**
+ This refers to creating Vulkan objects (buffers, images, pipelines, etc.) or allocating memory for them.
+
+* **Across multiple command buffers:**
+ Instead of paying the full cost every time you create a command buffer or resource, the driver can **share internal data or precompute things**, so that subsequent command buffers don’t have to redo all the work.
+
+---
+
+### 3️⃣ In practical Vulkan terms
+
+* If a command pool or pipeline is created with the right flags (like `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`), the driver might:
+
+ * Reuse internal memory for command buffers
+ * Cache GPU-side structures
+ * Avoid redundant validations
+* This **reduces the overhead per command buffer**, especially when creating many of them.
+
+---
+
+### 4️⃣ Analogy
+
+Think of it like **printing tickets at a theme park**:
+
+* Printing one ticket from scratch each time is slow.
+* If the printer can **preprint some templates or share ink across tickets**, then each ticket after the first is faster.
+
+That’s what “amortizing the cost” means in Vulkan: the driver can share the work of resource creation across multiple command buffers or operations, instead of starting from scratch every time.
+
+---
+
+If you want, I can make a **tiny diagram showing how Vulkan command buffers and resource creation share internal data** — it makes “amortization” very visual.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely — this is a **great** function to walk through, because it touches almost every core Vulkan concept: memory types, staging, layout transitions, and synchronization.
+
+I’ll go **top → bottom**, explaining **what each block does and *why***.
+I’ll avoid hand-wavy language and stick to concrete GPU/CPU behavior.
+
+---
+
+# `create_texture_image()` — line-by-line walkthrough
+
+---
+
+## 1️⃣ CPU-side pixel data
+
+```c
+uint32_t pixels[] = {
+ 0xFFFFFFFF, 0xFFFF69B4,
+ 0xFFFF69B4, 0xFFFFFFFF
+};
+```
+
+* This is a **2×2 RGBA texture** stored in **CPU memory**
+* Each `uint32_t` is `0xAABBGGRR` (little-endian)
+* Vulkan does **not** care where this comes from (file, procedural, etc.)
+
+---
+
+```c
+VkDeviceSize size = sizeof(pixels);
+```
+
+* Size of the upload = **16 bytes**
+* Used for buffer creation and `memcpy`
+
+---
+
+## 2️⃣ Create a staging buffer (CPU → GPU bridge)
+
+### Why a staging buffer?
+
+Because **DEVICE_LOCAL images are not CPU-mappable** on discrete GPUs.
+
+---
+
+```c
+VkBuffer staging;
+VkDeviceMemory stagingMem;
+```
+
+Handles for:
+
+* A temporary buffer
+* The memory backing it
+
+---
+
+### Create the buffer object
+
+```c
+VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+};
+vkCreateBuffer(device, &bufInfo, NULL, &staging);
+```
+
+This creates a **buffer object** that:
+
+* Holds `size` bytes
+* Can be used as a **transfer source**
+
+⚠️ No memory is allocated yet — this is just the *buffer description*.
+
+---
+
+### Query memory requirements
+
+```c
+VkMemoryRequirements memReq;
+vkGetBufferMemoryRequirements(device, staging, &memReq);
+```
+
+The driver tells you:
+
+* Required memory size
+* Required alignment
+* Which memory types are allowed (`memoryTypeBits`)
+
+---
+
+### Allocate HOST_VISIBLE + HOST_COHERENT memory
+
+```c
+VkMemoryAllocateInfo alloc = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+};
+```
+
+This means:
+
+| Flag | Meaning |
+| --------------- | ---------------------- |
+| `HOST_VISIBLE` | CPU can map it |
+| `HOST_COHERENT` | No manual flush needed |
+
+So:
+
+* CPU can write
+* GPU can read
+* Writes are automatically visible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &stagingMem);
+vkBindBufferMemory(device, staging, stagingMem, 0);
+```
+
+Now:
+
+* Physical memory is allocated
+* The buffer is bound to that memory
+
+---
+
+### Map and copy pixels
+
+```c
+void* data;
+vkMapMemory(device, stagingMem, 0, size, 0, &data);
+memcpy(data, pixels, size);
+vkUnmapMemory(device, stagingMem);
+```
+
+What happens:
+
+* CPU gets a pointer
+* Copies 16 bytes
+* Unmaps (flush is automatic because COHERENT)
+
+📌 **At this point, pixel data is in GPU-visible memory**
+
+---
+
+## 3️⃣ Create the GPU image (the actual texture)
+
+---
+
+```c
+VkImageCreateInfo img = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {2, 2, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage =
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+};
+vkCreateImage(device, &img, NULL, &textureImage);
+```
+
+This describes:
+
+* A **2D image**
+* 2×2 pixels
+* Optimal tiling (GPU-friendly layout)
+* Can be:
+
+ * Copied **into**
+ * Sampled by shaders
+
+Still **no memory allocated** yet.
+
+---
+
+### Allocate DEVICE_LOCAL memory
+
+```c
+vkGetImageMemoryRequirements(device, textureImage, &memReq);
+
+alloc.allocationSize = memReq.size;
+alloc.memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+```
+
+This means:
+
+* Memory physically close to the GPU
+* Fast for sampling
+* Not CPU-accessible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &textureMemory);
+vkBindImageMemory(device, textureImage, textureMemory, 0);
+```
+
+Now:
+
+* Image storage exists
+* GPU can access it efficiently
+
+---
+
+## 4️⃣ Record a one-time command buffer
+
+### Why?
+
+Because **copies and layout transitions happen on the GPU**
+
+---
+
+```c
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1
+};
+vkAllocateCommandBuffers(device, &cbAlloc, &cb);
+```
+
+---
+
+```c
+VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+};
+vkBeginCommandBuffer(cb, &begin);
+```
+
+---
+
+## 5️⃣ Layout transition: UNDEFINED → TRANSFER_DST
+
+```c
+VkImageMemoryBarrier toTransfer = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .image = textureImage,
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+};
+```
+
+Why?
+
+* Vulkan images start in **UNDEFINED**
+* Copying requires **TRANSFER_DST**
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toTransfer
+);
+```
+
+This tells Vulkan:
+
+> “Before transfer writes, make sure the image is ready.”
+
+---
+
+## 6️⃣ Copy buffer → image
+
+```c
+VkBufferImageCopy copy = {
+ .imageSubresource = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1
+ },
+ .imageExtent = {2, 2, 1}
+};
+```
+
+---
+
+```c
+vkCmdCopyBufferToImage(
+ cb,
+ staging,
+ textureImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1, ©
+);
+```
+
+GPU copies the data.
+
+📌 This is **very fast** — no CPU involvement.
+
+---
+
+## 7️⃣ Layout transition: TRANSFER_DST → SHADER_READ
+
+```c
+VkImageMemoryBarrier toSample = toTransfer;
+toSample.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+toSample.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+toSample.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+toSample.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+```
+
+Now the image becomes:
+
+* Readable by fragment shaders
+* Cache-safe
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toSample
+);
+```
+
+---
+
+## 8️⃣ Submit and wait (safely)
+
+```c
+vkEndCommandBuffer(cb);
+```
+
+---
+
+```c
+VkFence fence;
+VkFenceCreateInfo fenceInfo = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
+};
+vkCreateFence(device, &fenceInfo, NULL, &fence);
+```
+
+Fence lets CPU know when GPU is done.
+
+---
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, fence);
+vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+vkDestroyFence(device, fence, NULL);
+```
+
+CPU blocks until:
+
+* Copy completes
+* Image is ready for sampling
+
+✅ **Much better than `vkQueueWaitIdle`**
+
+---
+
+## 9️⃣ Cleanup staging resources
+
+```c
+vkFreeCommandBuffers(device, commandPool, 1, &cb);
+vkDestroyBuffer(device, staging, NULL);
+vkFreeMemory(device, stagingMem, NULL);
+```
+
+Staging buffer is no longer needed.
+
+---
+
+# Final mental model (important)
+
+```
+CPU RAM
+ ↓ memcpy
+HOST_VISIBLE buffer
+ ↓ GPU copy
+DEVICE_LOCAL image
+ ↓ shader sampling
+fragment shader
+```
+
+---
+
+## You did this function **correctly**
+
+* Correct memory types
+* Correct layout transitions
+* Correct synchronization
+* Correct use of fences
+* Correct staging pattern
+
+This function is **textbook Vulkan**.
+
+If you want next, I can:
+
+* Annotate it with **timeline diagrams**
+* Show how to batch uploads
+* Explain how this changes with BAR memory
+* Explain why this does *not* cause stutter
+
+Just tell me 👍
+
--- /dev/null
+In this context, **“depth” means the per-pixel (or per-sample) distance from the camera used to decide what is in front of what**.
+
+I’ll explain it from the ground up, then tie it directly to **render passes and subpasses**.
+
+---
+
+## 1. What “depth” is (fundamental idea)
+
+**Depth** is a numerical value that represents **how far a fragment is from the camera**.
+
+For every pixel the GPU draws, it computes a depth value:
+
+```
+closer to camera → smaller depth
+farther away → larger depth
+```
+
+This value is stored in a **depth buffer** (also called a **Z buffer**).
+
+---
+
+## 2. Why depth exists at all
+
+Without depth, triangles would be drawn **in submission order**:
+
+```
+draw triangle A
+draw triangle B
+```
+
+If B is behind A but drawn later → it appears in front ❌
+
+Depth fixes this by answering:
+
+> “Is this fragment closer than what was drawn before?”
+
+---
+
+## 3. How depth testing works
+
+For each fragment:
+
+1. Compute its depth (from vertex positions → clip space)
+2. Compare it against the stored depth value
+3. If it passes → draw and update depth
+4. If it fails → discard fragment
+
+Typical comparison:
+
+```c
+VK_COMPARE_OP_LESS
+```
+
+Meaning:
+
+> “Only draw if this fragment is closer.”
+
+---
+
+## 4. Where depth comes from (pipeline view)
+
+### Vertex shader
+
+Outputs:
+
+```glsl
+gl_Position
+```
+
+This contains **clip-space coordinates**.
+
+### After projection
+
+The GPU computes:
+
+```
+depth = z / w
+```
+
+Mapped into:
+
+```
+[0, 1] (Vulkan default)
+```
+
+This is what ends up in the depth buffer.
+
+---
+
+## 5. The depth buffer itself
+
+Depth is stored in a **depth attachment**, just like color is stored in a color attachment.
+
+Common formats:
+
+* `VK_FORMAT_D32_SFLOAT`
+* `VK_FORMAT_D24_UNORM_S8_UINT`
+
+Depth attachments:
+
+* Are not visible on screen
+* Exist only for visibility testing (and sometimes reading later)
+
+---
+
+## 6. Depth + subpasses (this is what you’re asking about)
+
+When you see:
+
+```
+Subpass 0: write color + depth
+Subpass 1: read depth, write color
+```
+
+It means:
+
+### Subpass 0
+
+* Draw geometry
+* Write:
+
+ * Color → color attachment
+ * Depth → depth attachment
+
+This establishes **visibility information**.
+
+---
+
+### Subpass 1
+
+* Does **not redraw geometry**
+* Reads depth to know:
+
+ * Which pixels belong to visible surfaces
+ * How far they are from the camera
+
+Used for:
+
+* Deferred lighting
+* Screen-space effects
+* Depth-aware post-processing
+
+---
+
+## 7. Reading depth in a later subpass
+
+Depth can be read as:
+
+### A) Input attachment (subpass-local, fast)
+
+```glsl
+subpassInput depthInput;
+```
+
+### B) Sampled image (general, slower)
+
+```glsl
+sampler2D depthTex;
+```
+
+This allows effects like:
+
+* Screen-space ambient occlusion (SSAO)
+* Volumetric fog
+* Soft particles
+* Deferred lighting
+
+---
+
+## 8. Depth vs color (key difference)
+
+| Color | Depth |
+| ------------ | ---------------------- |
+| What you see | What is visible |
+| RGB(A) | Single scalar value |
+| Displayed | Used for testing |
+| Final output | Internal decision data |
+
+Depth controls **visibility**, not appearance.
+
+---
+
+## 9. Depth and multisampling (important)
+
+With MSAA:
+
+* Each pixel has **multiple depth samples**
+* Each sample can pass/fail independently
+* This is why MSAA fixes edge aliasing correctly
+
+Depth must use the **same sample count** as color.
+
+---
+
+## 10. What depth is NOT
+
+❌ Not “distance in meters”
+❌ Not linear by default
+❌ Not screen-space Z
+❌ Not optional for 3D rendering
+
+Depth is **non-linear**, optimized for precision near the camera.
+
+---
+
+## 11. Mental model (best way to remember)
+
+> **Depth is a hidden image that stores “how close” each pixel is, and the GPU uses it to decide what is allowed to be drawn.**
+
+---
+
+## 12. One-sentence summary
+
+> **Depth is a per-pixel value stored in a depth buffer that allows the GPU to determine which fragments are visible and to reuse that visibility information across subpasses.**
+
+---
+
+If you want, I can next:
+
+* Show **exact Vulkan structs for a depth attachment**
+* Explain **why depth is non-linear**
+* Walk through **depth testing step-by-step**
+* Explain **stencil vs depth** (they’re often confused)
+
+Just tell me.
--- /dev/null
+Got it — you’re asking **why depth is “not optional” for 3D rendering**.
+
+This is a *fundamental* reason tied to how projection and visibility work, not just a Vulkan convention.
+
+---
+
+## Short answer (intuition)
+
+**Because in 3D, multiple surfaces project onto the same pixel, and without depth there is no correct way to know which one is visible.**
+
+2D doesn’t have this problem.
+3D does.
+
+---
+
+## What actually goes wrong without depth
+
+In 3D, after projection, **everything becomes 2D**:
+
+```
+3D scene → projection → screen pixels
+```
+
+Multiple triangles can map to the **same pixel**:
+
+```
+Camera
+ ↓
+[Triangle A] ← closer
+[Triangle B] ← farther
+```
+
+On screen:
+
+```
+Pixel (x, y)
+```
+
+Without depth, the GPU only knows **draw order**.
+
+---
+
+## Case 1: No depth buffer (painter’s algorithm)
+
+If you don’t use depth:
+
+```c
+vkCmdDraw(triangleA);
+vkCmdDraw(triangleB);
+```
+
+Whichever triangle is drawn **last** wins.
+
+This causes:
+
+* Objects popping in front of each other
+* Incorrect overlap
+* Massive CPU-side sorting requirements
+* Failure in cases like:
+
+ * Intersecting geometry
+ * Self-overlapping meshes
+ * Transparent + opaque mixes
+
+This is **not solvable in general**.
+
+---
+
+## Case 2: With depth buffer (correct solution)
+
+With depth testing:
+
+1. GPU stores depth per pixel
+2. For every fragment:
+
+ * Compare new depth vs stored depth
+ * Keep the closest one
+3. Order of draw calls **no longer matters**
+
+This gives:
+
+✔ Correct visibility
+✔ Intersecting geometry works
+✔ Self-occlusion works
+✔ Parallel GPU execution works
+
+---
+
+## Why the GPU *must* do this, not the CPU
+
+### You cannot sort triangles correctly in 3D
+
+There is **no total ordering** for triangles in 3D space.
+
+Example:
+
+* Triangle A overlaps B
+* B overlaps C
+* C overlaps A
+
+This is called a **cyclic overlap**.
+
+No draw order can fix this.
+
+Depth testing resolves it **per pixel**, which is the only correct level.
+
+---
+
+## Why this is fundamentally required by projection
+
+Perspective projection collapses depth:
+
+```
+(x, y, z) → (x / z, y / z)
+```
+
+That division by `z` destroys ordering information.
+
+Depth restores it.
+
+---
+
+## Can you “fake” 3D without depth?
+
+Yes, but with **severe limitations**:
+
+### When depth is *not* required:
+
+* Skyboxes
+* Fullscreen quads
+* Billboards drawn in strict order
+* 2.5D games (fixed layers)
+
+These avoid the general 3D visibility problem.
+
+---
+
+## Why Vulkan treats depth as a first-class concept
+
+Vulkan gives depth:
+
+* Its own attachment
+* Its own load/store ops
+* Its own layouts
+* Its own tests
+* Its own sampling path
+
+Because **the entire rasterization pipeline assumes depth exists**.
+
+---
+
+## The key mental model
+
+> **3D rendering is fundamentally a visibility problem.
+> Depth is the data structure that solves it.**
+
+Without depth, you don’t have 3D — you have a fragile illusion.
+
+---
+
+## One-sentence answer
+
+> **Depth is not optional in 3D rendering because projection causes multiple surfaces to map to the same pixel, and only per-pixel depth testing can correctly determine which surface is visible.**
+
+If you want, next we can:
+
+* Prove why triangle sorting is impossible in general
+* Walk through depth testing in Vulkan line-by-line
+* Explain why depth precision breaks at far distances
+* Show how deferred renderers rely entirely on depth
+
+Just say where you want to go.
--- /dev/null
+This is a **fundamental Vulkan question**, and the confusion is very normal. The short answer is:
+
+> **`malloc` / `calloc` allocate CPU memory.
+> `vkAllocateMemory` allocates GPU-accessible device memory.
+> They live in completely different worlds.**
+
+Let’s break it down cleanly and precisely.
+
+---
+
+## 1️⃣ `malloc` / `calloc` vs `vkAllocateMemory`
+
+### `malloc` / `calloc`
+
+* Allocates **process virtual memory**
+* Owned by the **CPU / OS**
+* Only the CPU can access it
+* The GPU **cannot see or use it**
+* Vulkan has **no idea** this memory exists
+
+```c
+void* data = malloc(1024); // CPU RAM only
+```
+
+You **cannot** bind this memory to:
+
+* `VkBuffer`
+* `VkImage`
+* Any Vulkan object
+
+---
+
+### `vkAllocateMemory`
+
+* Allocates memory from the **physical device**
+* Managed by **Vulkan + the GPU driver**
+* Can be:
+
+ * GPU-only (VRAM)
+ * CPU-visible (mapped system memory)
+* Can be **bound** to buffers and images
+
+```c
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+vkBindBufferMemory(device, buffer, memory, 0);
+```
+
+This is the **only way** Vulkan objects get usable memory.
+
+---
+
+## 2️⃣ Why Vulkan can’t use `malloc`
+
+The GPU:
+
+* Does not understand your process’s virtual address space
+* Does not participate in `malloc`
+* Cannot dereference CPU pointers
+
+Even if memory is physically the same (iGPU / UMA):
+
+* Vulkan still needs **explicit ownership, caching, and visibility rules**
+* The driver must know how the memory is used
+
+So Vulkan **must** control allocation.
+
+---
+
+## 3️⃣ Why Vulkan separates allocation from objects
+
+In OpenGL:
+
+* `glBufferData` silently allocates memory for you
+* You don’t control where or how
+
+In Vulkan:
+
+* You control:
+
+ * Memory location
+ * Cache behavior
+ * Sharing
+ * Lifetime
+
+This enables:
+
+* Zero-copy paths
+* Predictable performance
+* No hidden stalls
+
+---
+
+## 4️⃣ What Vulkan memory really is
+
+Think of Vulkan memory as:
+
+> A **handle to a chunk of physical device memory**,
+> not a pointer.
+
+```c
+VkDeviceMemory mem; // not a pointer!
+```
+
+The GPU accesses it through:
+
+* Bindings
+* Descriptors
+* Command buffers
+
+The CPU accesses it **only if HOST_VISIBLE**, via `vkMapMemory`.
+
+---
+
+## 5️⃣ Why staging buffers exist
+
+Because:
+
+* GPU VRAM is **fast but CPU-inaccessible**
+* CPU RAM is **slow but CPU-accessible**
+
+So Vulkan uses a two-step approach:
+
+```text
+CPU RAM (malloc / staging)
+ ↓ copy
+GPU VRAM (vkAllocateMemory DEVICE_LOCAL)
+```
+
+Except the staging buffer is still allocated via Vulkan,
+because the GPU must be able to read from it.
+
+---
+
+## 6️⃣ Common mistake (you avoided it)
+
+❌ This does NOT work:
+
+```c
+float* verts = malloc(sizeof(vertices));
+vkBindBufferMemory(buffer, (VkDeviceMemory)verts, 0); // invalid
+```
+
+Because:
+
+* That memory is not tracked by Vulkan
+* The GPU cannot see it
+
+---
+
+## 7️⃣ Why Vulkan memory feels “manual”
+
+Because it is designed for:
+
+* Engines
+* Drivers
+* Explicit control
+* Zero hidden sync
+
+Vulkan trades convenience for **absolute predictability**.
+
+---
+
+## TL;DR
+
+* `malloc` / `calloc` → CPU memory only
+* `vkAllocateMemory` → GPU-accessible device memory
+* Vulkan objects **must** use `vkAllocateMemory`
+* The GPU **cannot see malloc’d memory**
+* Explicit control = performance + no hidden stalls
+
+---
+
+If you want, next we can:
+
+* Walk through a **full buffer allocation lifecycle**
+* Explain **BAR memory vs VRAM** in more depth
+* Show how Vulkan allocators (VMA) work internally
+* Tie this back directly to your stutter issue
+
+Just tell me 👍
--- /dev/null
+Yes — this is a *really important* Vulkan concept, and you’re already **using it correctly** in your code. I’ll explain it from the ground up and tie it directly to what you wrote.
+
+---
+
+# 1️⃣ What “dynamic rendering” is (high-level)
+
+**Dynamic rendering** is a newer Vulkan feature that lets you:
+
+> **Render without creating a `VkRenderPass` or `VkFramebuffer`.**
+
+Instead of baking attachments and subpasses ahead of time, you:
+
+* Describe the attachments **at draw time**
+* Begin rendering **directly in the command buffer**
+* Bind pipelines that declare what formats they expect
+
+It removes a *huge* amount of boilerplate.
+
+---
+
+# 2️⃣ What Vulkan looked like *before* dynamic rendering
+
+### Old model (classic Vulkan)
+
+You had to create:
+
+1. `VkRenderPass`
+
+ * Attachments
+ * Subpasses
+ * Load/store ops
+2. `VkFramebuffer`
+
+ * One per swapchain image
+3. Pipeline tied **per render pass**
+
+```text
+RenderPass → Framebuffer → Pipeline → Draw
+```
+
+Problems:
+
+* Render passes were **compile-time objects**
+* Changing formats or attachments required rebuilding pipelines
+* Framebuffers had to match exactly
+
+This was powerful but **extremely rigid**.
+
+---
+
+# 3️⃣ What dynamic rendering replaces
+
+Dynamic rendering **removes**:
+
+| Old object | Replaced by |
+| -------------------- | ----------- |
+| `VkRenderPass` | ❌ Gone |
+| `VkFramebuffer` | ❌ Gone |
+| Subpass descriptions | ❌ Gone |
+
+Instead, Vulkan introduces:
+
+* `vkCmdBeginRendering`
+* `VkRenderingInfo`
+* `VkRenderingAttachmentInfo`
+* `VkPipelineRenderingCreateInfo`
+
+---
+
+# 4️⃣ What *you* are doing with dynamic rendering
+
+Let’s map your code to the concept.
+
+---
+
+## A) Enabling dynamic rendering on the device
+
+```c
+VkPhysicalDeviceDynamicRenderingFeatures dyn = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+};
+```
+
+This says:
+
+> “This device is allowed to use `vkCmdBeginRendering`.”
+
+Without this → **validation error / undefined behavior**.
+
+---
+
+## B) Declaring attachment formats in the pipeline
+
+```c
+VkPipelineRenderingCreateInfo rendering = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+};
+```
+
+This replaces:
+
+* `VkRenderPass`
+* Subpass attachment format matching
+
+It tells Vulkan:
+
+> “This pipeline expects **one color attachment** with this format.”
+
+Pipelines still need to know formats **up-front**, but not layouts, load ops, or framebuffers.
+
+---
+
+## C) Beginning rendering (the new render pass)
+
+```c
+vkCmdBeginRendering(cb, &ri);
+```
+
+This is the **dynamic equivalent** of:
+
+```c
+vkCmdBeginRenderPass(...)
+```
+
+Except:
+
+* No framebuffer
+* No render pass object
+* No subpass index
+
+---
+
+## D) Describing attachments at draw time
+
+```c
+VkRenderingAttachmentInfo color = {
+ .imageView = swapchainViews[img],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue.color = {{0,0,0,1}}
+};
+```
+
+This replaces:
+
+```text
+VkAttachmentDescription
+VkAttachmentReference
+VkSubpassDescription
+```
+
+All **combined**, but now:
+
+* Fully dynamic
+* Chosen per frame
+* Chosen per command buffer
+
+---
+
+## E) Ending rendering
+
+```c
+vkCmdEndRendering(cb);
+```
+
+Equivalent to:
+
+```c
+vkCmdEndRenderPass(cb);
+```
+
+---
+
+# 5️⃣ What dynamic rendering does **NOT** change
+
+Dynamic rendering does **not** change:
+
+| Thing | Still required |
+| ------------------ | -------------- |
+| Pipelines | ✅ |
+| Descriptor sets | ✅ |
+| Layout transitions | ✅ |
+| Synchronization | ✅ |
+| Image views | ✅ |
+
+This is why you still need:
+
+```c
+VkImageMemoryBarrier
+```
+
+Dynamic rendering does **not** manage layouts for you.
+
+---
+
+# 6️⃣ Why dynamic rendering exists
+
+### Goals:
+
+* Reduce boilerplate
+* Make Vulkan usable for small engines
+* Allow more flexible rendering graphs
+* Match modern APIs (DX12 / Metal)
+
+### Typical wins:
+
+* No framebuffer creation
+* No render pass combinatorics
+* Easier swapchain recreation
+* Much easier multipass rendering
+
+---
+
+# 7️⃣ How dynamic rendering maps conceptually
+
+Think of it like this:
+
+### Classic Vulkan
+
+```text
+RenderPass (static)
+ └─ Subpass (static)
+ └─ Attachments (static)
+```
+
+### Dynamic Rendering
+
+```text
+Command Buffer
+ └─ Begin Rendering
+ └─ Attachments (chosen now)
+```
+
+You move **render-graph decisions** from *initialization time* to *record time*.
+
+---
+
+# 8️⃣ Common misconceptions (important)
+
+### ❌ “Dynamic rendering is slower”
+
+No. Drivers internally build render passes anyway.
+
+### ❌ “Dynamic rendering removes the need for barriers”
+
+Absolutely not. You still must manage layouts and sync.
+
+### ❌ “Dynamic rendering is optional”
+
+Yes — but it’s becoming the **preferred path**.
+
+---
+
+# 9️⃣ When NOT to use dynamic rendering
+
+You might still want classic render passes if:
+
+* You need **subpass input attachments**
+* You rely on **tile-based GPU subpass optimizations**
+* You want strict render-graph control
+
+Most beginner/intermediate engines should **use dynamic rendering**.
+
+---
+
+# 🔑 One-sentence summary
+
+> **Dynamic rendering lets you render directly to images without pre-declaring render passes or framebuffers, by describing attachments dynamically inside the command buffer.**
+
+---
+
+If you want next, I can:
+
+* Rewrite your code in **classic render-pass style** to compare
+* Explain **why subpasses exist** and when they outperform dynamic rendering
+* Show **depth + MSAA** with dynamic rendering
+* Show how dynamic rendering maps to **Vulkan synchronization2**
+
+Just tell me.
--- /dev/null
+Absolutely — this is a **core Vulkan helper**, and understanding it deeply will make a lot of other Vulkan code “click”. I’ll go **line by line**, then give you a **mental model** at the end.
+
+---
+
+## Function signature
+
+```c
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props)
+```
+
+### What this function does (high level)
+
+It finds **which GPU memory type index** you should use when allocating memory for a buffer or image.
+
+Vulkan does **not** let you just say “give me GPU memory”.
+Instead, you must:
+
+1. Ask Vulkan **what kinds of memory are allowed** (`typeBits`)
+2. Ask for **what properties you want** (`props`)
+3. Pick a memory **type index** that satisfies both
+
+This function does exactly that.
+
+---
+
+## Get physical device memory info
+
+```c
+VkPhysicalDeviceMemoryProperties memProps;
+vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+```
+
+### What’s happening
+
+* Vulkan exposes **all memory types supported by the GPU**
+* This includes:
+
+ * Device-local VRAM
+ * Host-visible memory
+ * Coherent / cached memory
+ * Unified memory (on iGPUs)
+
+`memProps` contains:
+
+```c
+memProps.memoryTypeCount
+memProps.memoryTypes[i].propertyFlags
+```
+
+Typical GPU: **8–32 memory types**
+
+---
+
+## Loop over every memory type
+
+```c
+for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+```
+
+You are checking **each possible memory type index**.
+
+Important:
+
+* The return value of this function is **this index (`i`)**
+* This index is what you pass to `VkMemoryAllocateInfo.memoryTypeIndex`
+
+---
+
+## Check if this memory type is allowed
+
+```c
+if ((typeBits & (1 << i)) &&
+```
+
+### What `typeBits` means
+
+`typeBits` comes from:
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+```
+
+* `req.memoryTypeBits` is a **bitmask**
+* Each bit corresponds to a memory type index
+* If bit `i` is set → that memory type **can be used** for this resource
+
+### This line checks:
+
+> “Is memory type `i` compatible with this buffer/image?”
+
+If not, skip it.
+
+---
+
+## Check if it has the properties you want
+
+```c
+(memProps.memoryTypes[i].propertyFlags & props) == props)
+```
+
+### `propertyFlags` examples
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+VK_MEMORY_PROPERTY_HOST_CACHED_BIT
+```
+
+### This line means:
+
+> “Does this memory type include **all** the properties I requested?”
+
+Why bitwise AND?
+
+* `propertyFlags` may have **extra bits**
+* You only care that **your requested bits are present**
+
+Example:
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+```
+
+This is valid.
+
+---
+
+## If both conditions pass → success
+
+```c
+return i;
+```
+
+You’ve found:
+
+* A memory type that **works for this resource**
+* With **the properties you want**
+
+You’re done.
+
+---
+
+## If no memory type matches
+
+```c
+fprintf(stderr, "No suitable memory type\n");
+exit(1);
+```
+
+This should **never happen** in a correct Vulkan program.
+
+If it does:
+
+* You requested impossible memory properties
+* Or passed the wrong `typeBits`
+* Or you misunderstood host vs device memory
+
+---
+
+## Mental model (this is the key)
+
+Think of Vulkan memory like this:
+
+> “I have a list of memory pools.
+> Each pool has certain capabilities.
+> Each resource tells me which pools it is allowed to use.
+> I must pick one pool that satisfies both.”
+
+This function answers:
+
+> **“Which pool index should I use?”**
+
+---
+
+## Example usage (buffer allocation)
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+
+uint32_t memoryType = find_memory_type(
+ req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+);
+```
+
+This means:
+
+> “Find a memory type that
+> ✔ the buffer supports
+> ✔ I can map from CPU
+> ✔ doesn’t require manual flush/invalidate”
+
+---
+
+## Why this function is critical
+
+Every Vulkan program needs it for:
+
+* Vertex buffers
+* Index buffers
+* Uniform buffers
+* Staging buffers
+* Images
+* Depth buffers
+* Textures
+
+If you understand **this function**, you understand:
+
+* Why Vulkan memory is explicit
+* Why performance is predictable
+* Why Vulkan avoids hidden stalls
+
+---
+
+If you want next, I can:
+
+* Draw a **diagram of Vulkan memory types**
+* Show **which memory properties to use for each buffer**
+* Explain **why HOST_VISIBLE is slower**
+* Explain **why staging buffers exist**
+
+Just tell me which one.
--- /dev/null
+Ah, now we’re getting into a subtle Vulkan topic: **swapchain image count vs performance**. Let’s break it down carefully.
+
+---
+
+## 1️⃣ How the swapchain works
+
+A Vulkan swapchain is basically a **queue of images** (framebuffers) the GPU can render into and the presentation engine (display) can show.
+
+* `vkCreateSwapchainKHR` lets you specify `minImageCount` — the number of images.
+* Typical values: **2–3** (double or triple buffering).
+
+Each image can be in one of three states:
+
+1. **Available** — ready to render to
+2. **Rendering** — GPU is drawing into it
+3. **Presenting** — waiting to be shown on screen
+
+---
+
+## 2️⃣ Double vs Triple Buffering
+
+| # Images | Behavior | Pros | Cons |
+| ---------- | ------------------------------------------------ | ---------------------------------------- | ---------------------------------------- |
+| 2 (double) | GPU renders to 1 image, display shows the other | Less memory usage, lower latency | GPU may stall if display is slow |
+| 3 (triple) | GPU has 1 image ready, 1 rendering, 1 displaying | GPU never waits for display → higher FPS | More memory usage, slightly more latency |
+
+✅ Triple buffering can **improve throughput** because the GPU always has an image to render to.
+❌ But adding **more than 3–4 images rarely helps** — the GPU already pipelines well.
+
+---
+
+## 3️⃣ Latency vs performance tradeoff
+
+* **More images → higher FPS potential** (GPU never blocks)
+* **More images → higher input/display latency** (you’re rendering frames that are “ahead” of the screen)
+
+For games:
+
+* Triple buffering is common for smooth FPS
+* Double buffering is often enough for low-latency apps
+
+---
+
+## 4️⃣ Vulkan-specific notes
+
+* Some GPU drivers **require at least 2 images**.
+
+* The swapchain must have **at least `surfaceCapabilities.minImageCount`** images.
+
+* If you request more than `surfaceCapabilities.maxImageCount` → creation fails.
+
+* Vulkan allows you to acquire the **next available image** via `vkAcquireNextImageKHR`. If all images are in use, the GPU may block until one becomes free. More images = less chance of blocking.
+
+---
+
+### ✅ TL;DR
+
+* **2 images:** safe, low memory, slightly higher chance GPU waits
+* **3 images:** smoother, less chance of GPU stall, slightly more latency
+* **4+ images:** rarely improves FPS, just uses more VRAM
+
+So yes, **more images *can* improve throughput**, but only up to a point (usually 3). Beyond that, it’s mostly wasted memory.
+
+---
+
+If you want, I can draw a **diagram showing the swapchain states over time** — it makes it super clear why 3 images can be smoother than 2.
+
+Do you want me to do that?
--- /dev/null
+Feature,Pattern Rule (%),Wildcard (*)
+Purpose,Defining a transformation rule.,Listing existing files.
+Usage,%.o: %.c,rm *.o
+Timing,Evaluated when make decides what to do.,Evaluated by the shell when the
+command runs. Example,"""How to turn any X into Y.""","""Do something to
+everything that ends in .X."""
+
+Symbol,What it does,Resulting Logic %.spv: %.vert,Links the names.,"""To get
+player.spv, look for player.vert.""" *.spv: *.vert,Literal expansion.,"""To get
+all spv files, look for all vert files."""
--- /dev/null
+Ah, yes — this is a subtle but very important distinction. It’s easy to get confused because Vulkan separates **allocation** from **binding**, and the wording can make it seem like they both “give memory” to the buffer. Let’s clarify carefully.
+
+---
+
+### Step 1: `vkAllocateMemory`
+
+When you call:
+
+```c
+VkDeviceMemory memory;
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+```
+
+You are **reserving a chunk of memory from the GPU (or a heap)**.
+
+Think of it like **booking a block of storage in a warehouse**:
+
+* `VkDeviceMemory` is just a handle that represents that chunk.
+* The memory **exists**, but nothing is stored in it yet.
+* The GPU doesn’t yet know which buffer or image will use it.
+
+It’s untyped, unbound storage — just “here are these bytes in GPU-accessible memory.”
+
+---
+
+### Step 2: `vkBindBufferMemory`
+
+When you call:
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+```
+
+You are saying:
+
+> “Buffer X will **live starting at this offset in the allocated memory block**.”
+
+Now:
+
+* The buffer has a **valid GPU address**.
+* The GPU can access it when drawing or copying.
+* Alignment and memory type are enforced.
+
+It’s like taking that chunk of warehouse space and **assigning it to a specific shelf (the buffer)**.
+
+---
+
+### Analogy
+
+| Vulkan Step | Analogy |
+| -------------------- | --------------------------------------------------------------------------------------- |
+| `vkAllocateMemory` | Renting a warehouse block of storage (size, type, location) |
+| `vkBindBufferMemory` | Putting a specific item (buffer or image) into a specific shelf in that warehouse block |
+
+---
+
+### Why not do it all in one call?
+
+Vulkan separates the two because:
+
+1. **Memory suballocation:**
+ One `VkDeviceMemory` block can hold multiple buffers/images.
+
+ ```c
+ vkBindBufferMemory(vertexBuffer, memory, 0);
+ vkBindBufferMemory(indexBuffer, memory, 65536);
+ ```
+2. **Explicit control:**
+ You choose **memory type** (device local, host visible, cached, BAR-mapped) and **alignment**.
+3. **Performance predictability:**
+ Drivers don’t do hidden allocations or copies.
+
+---
+
+✅ **Summary**
+
+* `vkAllocateMemory`: “Here is a raw block of memory on the GPU.”
+* `vkBindBufferMemory`: “This buffer will live inside that block at this offset.”
+
+Without `vkAllocateMemory`, `vkBindBufferMemory` has nothing to bind.
+Without `vkBindBufferMemory`, the buffer exists but has **no storage**.
+
+---
+
+If you want, I can draw a little diagram showing **buffers, images, memory blocks, and GPU access** — it makes this super clear.
+
+Do you want me to do that?
--- /dev/null
+Great question — this is one of the **most important Vulkan concepts**, and the wording is easy to gloss over.
+
+When I said:
+
+> **“I can map from CPU”**
+
+this refers **very specifically** to Vulkan’s ability to let your program access GPU memory **as a normal pointer** on the CPU.
+
+Let’s break that down precisely.
+
+---
+
+## What “map from CPU” means in Vulkan
+
+In Vulkan, memory lives in different places:
+
+* Some memory lives **only on the GPU** (fast, but CPU can’t touch it)
+* Some memory lives in **system RAM** or unified memory (CPU can touch it)
+* Some memory is shared / cached / coherent
+
+When memory has the flag:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+```
+
+it means:
+
+> **The CPU is allowed to directly access this memory**
+
+Concretely, this allows you to call:
+
+```c
+void* data;
+vkMapMemory(device, memory, 0, size, 0, &data);
+```
+
+And then do:
+
+```c
+memcpy(data, myVertices, size);
+```
+
+That’s what **“map from CPU”** means.
+
+---
+
+## What happens without HOST_VISIBLE
+
+If memory does **not** have `HOST_VISIBLE`:
+
+* `vkMapMemory` is **illegal**
+* You cannot get a pointer to it
+* The CPU cannot read or write it directly
+
+This is typical for:
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+which usually means:
+
+* Memory physically located on the GPU (VRAM)
+* Very fast for the GPU
+* Completely inaccessible to the CPU
+
+---
+
+## Why you care
+
+### Example: Vertex buffer
+
+#### Option A — HOST_VISIBLE memory
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+```
+
+✔ Easy: `memcpy` vertices directly
+❌ Slower for GPU access
+❌ PCIe / cache penalties
+
+#### Option B — DEVICE_LOCAL memory
+
+```c
+props = DEVICE_LOCAL
+```
+
+✔ Very fast for GPU
+❌ CPU cannot write directly
+
+So how do you upload data in Option B?
+
+➡ **Staging buffer**
+
+---
+
+## The staging buffer pattern (this is key)
+
+1. Create a **CPU-visible staging buffer**
+
+ ```c
+ HOST_VISIBLE | HOST_COHERENT
+ ```
+
+2. Write data via `memcpy`
+
+3. Copy staging buffer → device-local buffer using a command buffer
+
+4. Free staging buffer
+
+This is why Vulkan makes you think explicitly.
+
+---
+
+## HOST_COHERENT (important related detail)
+
+Often you see:
+
+```c
+HOST_VISIBLE | HOST_COHERENT
+```
+
+### What HOST_COHERENT means
+
+It means:
+
+> “When I write to mapped memory, the GPU sees it automatically”
+
+Without it, you must manually do:
+
+```c
+vkFlushMappedMemoryRanges(...)
+vkInvalidateMappedMemoryRanges(...)
+```
+
+So:
+
+* **HOST_VISIBLE** = CPU *can* access
+* **HOST_COHERENT** = CPU/GPU memory stays in sync automatically
+
+---
+
+## Concrete analogy
+
+Think of GPU memory like rooms:
+
+| Room type | CPU can enter? | GPU speed |
+| ----------------------- | -------------- | ------------ |
+| Device local (VRAM) | ❌ No | 🚀 Fast |
+| Host visible | ✅ Yes | 🐢 Slower |
+| Host visible + coherent | ✅ Yes | 🐢🐢 Slowest |
+
+You’re choosing **which room** to put your data in.
+
+---
+
+## In your program specifically
+
+When you call:
+
+```c
+find_memory_type(req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+```
+
+You are explicitly saying:
+
+> “Give me memory that my CPU can map with a pointer
+> and write to using memcpy,
+> without manual cache management.”
+
+That’s exactly what “I can map from CPU” means.
+
+---
+
+## One sentence summary (very precise)
+
+> **“Map from CPU” means the CPU can obtain a pointer to GPU memory via `vkMapMemory` and directly read/write it like normal RAM.”**
+
+---
+
+If you want next, I can explain:
+
+* Why HOST_VISIBLE memory is often slower
+* Why some GPUs don’t have coherent memory
+* How unified memory GPUs behave differently
+* Exactly when you should *not* use HOST_VISIBLE
+
+Just tell me which one you want.
+
+Short answer: **sometimes yes, sometimes no — and Vulkan deliberately doesn’t promise which**.
+
+Let’s be very precise.
+
+---
+
+## Is mapped memory “still on the GPU”?
+
+### Vulkan’s honest answer:
+
+> **Mapped memory is always allocated by the Vulkan device, but its *physical location* depends on the hardware and memory type.**
+
+When you call:
+
+```c
+vkAllocateMemory(device, ...)
+vkMapMemory(device, memory, ...)
+```
+
+You are **not** allocating “CPU memory” or “GPU memory” explicitly.
+
+You are allocating **device memory**, and Vulkan exposes a **CPU mapping** *if the memory type allows it*.
+
+---
+
+## What actually happens under the hood
+
+### Case 1: Discrete GPU (most desktops)
+
+Typical example:
+
+* CPU has system RAM
+* GPU has its own VRAM
+* Connected via PCIe
+
+For these GPUs:
+
+| Memory type | Physical location |
+| -------------- | ------------------------------- |
+| `DEVICE_LOCAL` | GPU VRAM |
+| `HOST_VISIBLE` | System RAM (or BAR-mapped VRAM) |
+
+So if memory is:
+
+```c
+HOST_VISIBLE
+```
+
+then **physically** it is usually:
+
+* Allocated in **system RAM**
+* GPU accesses it over PCIe
+* CPU maps it directly
+
+✅ CPU pointer is real RAM
+❌ GPU access is slower
+
+This is why HOST_VISIBLE is slower for rendering.
+
+---
+
+### Case 2: Integrated / UMA GPU (laptops, APUs)
+
+Typical example:
+
+* AMD APU
+* Intel iGPU
+* Apple Silicon
+
+Here:
+
+* CPU and GPU share **the same physical memory**
+
+So:
+
+```c
+HOST_VISIBLE | DEVICE_LOCAL
+```
+
+can both be true.
+
+In this case:
+
+* Memory is physically one pool
+* CPU pointer == GPU memory
+* No PCIe penalty
+
+This is the **best-case scenario**, and Vulkan allows it.
+
+---
+
+### Case 3: BAR-mapped VRAM (advanced discrete GPUs)
+
+Some modern GPUs expose parts of VRAM to the CPU via PCIe BAR:
+
+* Memory is **physically in VRAM**
+* CPU sees a mapped window
+* Still slower than RAM (system ram)
+* Limited size
+
+Still counts as `HOST_VISIBLE`.
+
+---
+
+## The key Vulkan rule (this matters)
+
+> **Vulkan never guarantees where the memory physically lives.**
+
+It only guarantees:
+
+* Whether the CPU *may* access it
+* Whether caches are coherent
+* Whether the GPU prefers it
+
+This is why Vulkan forces you to ask:
+
+```c
+vkGetPhysicalDeviceMemoryProperties(...)
+```
+
+And choose explicitly.
+
+---
+
+## So is the memory “on the GPU”?
+
+Let’s answer your question exactly:
+
+> **If the CPU can obtain the pointer, is the memory still allocated on the GPU then?**
+
+### Correct, nuanced answer:
+
+* ✅ **Yes**, it is Vulkan *device memory*
+* ❌ **No**, it is not necessarily physically in GPU VRAM
+* 🔁 It may be:
+
+ * system RAM
+ * unified memory
+ * BAR-mapped VRAM
+
+The CPU pointer is a **mapping**, not proof of physical location.
+
+---
+
+## Why Vulkan phrases it this way
+
+Vulkan is designed so that:
+
+* The same code works on phones, consoles, desktops, servers
+* You never assume memory topology
+* Performance comes from choosing wisely, not guessing
+
+That’s why:
+
+* You describe *requirements*
+* The driver chooses *placement*
+
+---
+
+## Mental model (use this)
+
+Think of Vulkan memory like this:
+
+> “I allocate memory *owned by the device*.
+> Sometimes the device lets the CPU borrow a pointer to it.”
+
+Not:
+
+> “I allocated CPU memory that the GPU can see.”
+
+That mental model will keep you out of trouble.
+
+---
+
+## One-sentence takeaway
+
+> **Mapped memory is always Vulkan device memory; CPU access is a permission, not a statement about physical location.**
+
+If you want next, I can:
+
+* Show how to detect UMA vs discrete GPUs
+* Explain why HOST_VISIBLE can stall rendering
+* Walk through a perfect staging-buffer upload
+* Explain why mapping every frame can cause stutter (relevant to your issue)
+
+Just tell me.
+
+Great question — this is exactly where the confusion usually is.
+Let’s answer it **very concretely** and correct one small wording issue first.
+
+> What you meant by *“Case 3”* is **BAR-mapped VRAM on a discrete GPU**
+> (Resizable BAR / Smart Access Memory), **not unified memory**.
+
+---
+
+## Short answer (TL;DR)
+
+**BAR-mapped VRAM is *not faster* than normal system RAM for the CPU.**
+Its advantages are **not raw speed**, but **capability and reduced copies**.
+
+---
+
+## First: what “non BAR-mapped” means
+
+### Traditional (non-BAR) discrete GPU memory
+
+* GPU VRAM is **not directly addressable** by the CPU
+* CPU must:
+
+ 1. Write to system RAM (staging buffer)
+ 2. Ask GPU to copy over PCIe (`vkCmdCopyBuffer`)
+* CPU **cannot** dereference VRAM pointers
+
+This is how GPUs worked for decades.
+
+---
+
+## What BAR-mapped VRAM actually is
+
+With **Resizable BAR**:
+
+* GPU VRAM is mapped into the CPU’s address space
+* CPU can get a pointer to VRAM
+* CPU loads/stores go **over PCIe**
+
+Important:
+
+> **PCIe is much slower and higher-latency than DRAM**
+
+---
+
+## Is BAR-mapped VRAM faster than non-BAR?
+
+### For the CPU?
+
+❌ **No — it is slower than system RAM**
+
+| Memory type | CPU access speed |
+| ------------------ | ---------------- |
+| System RAM | ⭐⭐⭐⭐⭐ |
+| BAR-mapped VRAM | ⭐⭐ |
+| GPU VRAM (non-BAR) | ❌ impossible |
+
+BAR gives **access**, not speed.
+
+---
+
+### For the GPU?
+
+⚠️ **Sometimes indirectly**
+
+BAR can reduce:
+
+* Extra copies
+* Command buffer overhead
+* Synchronization latency
+
+But GPU **still prefers DEVICE_LOCAL VRAM accessed by itself**.
+
+---
+
+## So what are the actual advantages?
+
+### ✅ 1. Fewer copies (big deal)
+
+Without BAR:
+
+```
+CPU → RAM → GPU copy → VRAM
+```
+
+With BAR:
+
+```
+CPU → VRAM
+```
+
+This matters when:
+
+* Streaming assets
+* Uploading many small buffers
+* Doing frequent updates
+
+---
+
+### ✅ 2. Lower latency for small, dynamic updates
+
+Examples:
+
+* Per-frame constants
+* Dynamic vertex buffers
+* Sparse data updates
+
+Avoids:
+
+* Command submission
+* Transfer queues
+* Extra synchronization
+
+---
+
+### ✅ 3. Better CPU-driven streaming
+
+Used heavily in:
+
+* Modern engines (DX12 / Vulkan)
+* Texture streaming systems
+* Nanite-style workloads
+
+BAR helps **bandwidth utilization**, not single-access speed.
+
+---
+
+### ❌ What BAR does NOT give you
+
+* ❌ Faster CPU memory access
+* ❌ Replacement for staging buffers in large uploads
+* ❌ Free performance
+
+If you memcpy 100 MB per frame into BAR VRAM → **you will stall badly**
+
+---
+
+## Why BAR can *hurt* if misused
+
+BAR-mapped VRAM:
+
+* Bypasses CPU caches
+* Has high write-combining costs
+* Causes PCIe traffic saturation
+
+So this is **bad**:
+
+```c
+memcpy(barMappedVRAM, bigTexture, size);
+```
+
+This is **good**:
+
+```c
+memcpy(stagingRAM, data, size);
+vkCmdCopyBuffer(...); // GPU does the heavy lifting
+```
+
+---
+
+## Vulkan perspective (important)
+
+Vulkan exposes BAR like this:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+If you see **both** on a discrete GPU:
+
+* That’s BAR-mapped VRAM
+* Treat it as **special-purpose**, not general RAM
+
+---
+
+## Practical rule (tattoo this mentally)
+
+> **BAR is for reducing copies, not increasing bandwidth**
+
+Use BAR-mapped memory when:
+
+* Updates are small
+* Latency matters
+* You want to avoid extra transfers
+
+Use staging buffers when:
+
+* Data is large
+* GPU will read it often
+* You want peak throughput
+
+---
+
+## Relating this back to your stuttering issue
+
+You were right to suspect memory behavior.
+
+Your current pipeline is good because:
+
+* Texture is uploaded once
+* GPU samples DEVICE_LOCAL memory
+* No per-frame BAR writes
+
+So your stutter is **not** BAR-related — it was primarily:
+
+* Swapchain image / frame-in-flight mismatch
+* Fence/image ownership issues (which you already fixed)
+
+---
+
+If you want next, I can:
+
+* Show how engines *mix* BAR + staging safely
+* Explain why BAR helps DX12 more than Vulkan
+* Show how to detect BAR memory at runtime
+
+Just tell me 👍
--- /dev/null
+Ah, yes! This is a subtle but important Vulkan concept. Let’s break it down carefully.
+
+---
+
+### 1️⃣ What “opaque object” means
+
+In Vulkan, when you see something described as an **opaque object**, it means:
+
+* **You don’t know or need to know what’s inside it.**
+* The API treats it as a **handle** (essentially a pointer or an integer under the hood).
+* You cannot directly access its internal structure — you can only interact with it via Vulkan functions.
+
+For example:
+
+```c
+VkCommandPool commandPool;
+vkCreateCommandPool(device, &cpInfo, NULL, &commandPool);
+```
+
+* `commandPool` is a **handle** to the actual command pool data.
+* Internally, the driver stores GPU memory, thread info, buffers, etc., but **Vulkan doesn’t expose those details**.
+* You just pass `commandPool` to Vulkan functions like `vkAllocateCommandBuffers` or `vkResetCommandPool`.
+
+---
+
+### 2️⃣ Why Vulkan uses opaque objects
+
+1. **Driver flexibility:** Different GPU vendors can implement the object however they want.
+2. **Safety:** You can’t accidentally mess with internal GPU data.
+3. **Abstraction:** Vulkan provides a uniform interface regardless of the underlying hardware.
+
+Other examples of opaque objects:
+
+* `VkDevice`
+* `VkBuffer`
+* `VkImage`
+* `VkPipeline`
+* `VkShaderModule`
+
+All of these are **handles**, not structs you can poke inside.
+
+---
+
+### 3️⃣ How you interact with them
+
+You always pass the handle to Vulkan functions:
+
+```c
+vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers);
+vkDestroyCommandPool(device, commandPool, NULL);
+```
+
+You never dereference it or try to read its members, because **its memory layout is unknown**.
+
+---
+
+✅ In short:
+
+An **opaque object** in Vulkan is like a black box: you know **what it represents** and **how to use it**, but you **don’t see or modify its internals**. The driver takes care of the details.
+
+---
+
+If you want, I can make a **quick visual analogy showing Vulkan handles vs real GPU memory** — it helps a lot to internalize the concept.
+
+Do you want me to do that?
+
--- /dev/null
+In the context of your graphics project, these three terms represent different ways of looking at "points" of data as they move from a file to your monitor.
+
+### **1. Texel (Texture Element)**
+[cite_start]A **texel** is the fundamental unit of a texture map, such as the PNG image you are loading with `SDL_image`[cite: 40, 72].
+* [cite_start]**Origin**: Texels exist in **Texture Space** (also called UV space), ranging from $(0,0)$ to $(1,1)$[cite: 114].
+* [cite_start]**Role**: When your `cube.frag` shader calls `texture(texSampler, inUV)`, it is looking up data from the texels stored in the `VkImage` you created[cite: 75, 124].
+* [cite_start]**Nature**: Unlike pixels on a screen, texels don't have a fixed size; they are scaled, rotated, and stretched based on how you define the vertices of your plane[cite: 83].
+
+### **2. Pixel (Picture Element)**
+A **pixel** is a physical point of light on your 2560x1080 ultra-wide monitor.
+* **Origin**: Pixels exist in **Screen Space**.
+* [cite_start]**Role**: In your Vulkan swapchain, the `VkImage` images (the ones you see in `simg[idx]`) are buffers of pixels that will eventually be displayed by the hardware[cite: 70, 102].
+* [cite_start]**Nature**: A pixel is a discrete "bucket" on your screen that receives a color value once the GPU finishes processing a fragment[cite: 103, 106].
+
+### **3. Sample**
+A **sample** is a specific measurement of color or depth taken at a single point.
+* **The Bridge**: Sampling is the process of mapping a **Pixel** to a **Texel**.
+* **Role in Shaders**: When the GPU renders your plane, it "samples" the texture. Because your plane might be tilted or far away, a single screen pixel might correspond to half a texel or four texels at once.
+* [cite_start]**Filtering**: Your `VkSamplerCreateInfo` defines *how* this sampling happens[cite: 82].
+ * [cite_start]**Linear Filtering**: The sampler takes a weighted average of the 4 nearest texels to calculate a smooth color[cite: 82].
+ * **Nearest Filtering**: The sampler simply picks the single closest texel, which creates a "pixelated" or retro look.
+
+
+
+### **Summary Table**
+
+| Term | Context | Coordinate System |
+| :--- | :--- | :--- |
+| **Texel** | The source image (PNG) | UV Space $(0.0 \text{ to } 1.0)$ |
+| **Sample** | The act of reading data | Intersection of Screen/Texture Space |
+| **Pixel** | The final output (Monitor) | Screen Space $(0 \text{ to } 1280 \text{ px})$ |
+
+[cite_start]In your `main.c`, you bridge these by loading **texels** from a file into a `VkImage`, creating a `VkSampler` to define the **sampling** logic, and finally rendering them into the swapchain's **pixels**[cite: 74, 82, 107].
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
+
--- /dev/null
+Ah! I can see exactly why this is still stuttering, and it’s subtle but very common in Vulkan beginners’ loops. Let’s go through it carefully.
+
+---
+
+## 1️⃣ The root cause
+
+Right now, your code does this:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+vkResetCommandBuffer(cb, 0);
+...
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+* `MAX_FRAMES_IN_FLIGHT = 2` → you have **2 fences**, **2 semaphores**, and **2 command buffers**.
+* **Swapchain images = 4**.
+
+This works *sometimes*, but not reliably, because **you are recording one command buffer per frame in flight, but there are 4 swapchain images**.
+
+* If `vkAcquireNextImageKHR` returns image index 2 or 3 (which can happen randomly), you will record and submit **command buffer 0 or 1** to draw to that image.
+* If that image is still in use (GPU hasn’t finished writing to it yet), you can get **stutter or occasional tearing**.
+
+In short: **frames in flight and swapchain images are not aligned**, and your code assumes a fixed one-to-one mapping between them.
+
+---
+
+## 2️⃣ How Vulkan apps usually fix this
+
+There are **two approaches**:
+
+---
+
+### **Approach A — Command buffer per swapchain image**
+
+* Allocate **one command buffer per swapchain image**:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+* Record commands **per swapchain image** (or re-record each frame).
+* When rendering:
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* No matter which swapchain image is returned by `vkAcquireNextImageKHR`, you have a dedicated command buffer for it.
+* This is the cleanest solution if you’re recording per-frame commands dynamically.
+
+---
+
+### **Approach B — Keep frames in flight separate from swapchain images, but wait properly**
+
+* Keep `MAX_FRAMES_IN_FLIGHT` buffers/fences.
+* After acquiring the swapchain image, **check if it’s already in flight**:
+
+```c
+VkFence imageFence = imagesInFlight[imgIndex];
+if(imageFence != VK_NULL_HANDLE)
+ vkWaitForFences(device, 1, &imageFence, VK_TRUE, UINT64_MAX);
+imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+```
+
+* This ensures **you don’t overwrite a swapchain image that’s still being presented**.
+* `imagesInFlight` is an array of size `swapchainImageCount`, initialized to `VK_NULL_HANDLE`.
+
+---
+
+## 3️⃣ Other subtle stutter causes
+
+Even after fixing the above:
+
+1. **V-Sync mode (FIFO) + small floating-point inaccuracies**
+
+ * You are limiting rotation by `delta` each frame — good.
+ * But CPU/GPU timing can slightly drift → slight stutter.
+
+2. **`vkQueueWaitIdle()` or blocking calls**
+
+ * In your `create_texture_image()`, you do:
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, VK_NULL_HANDLE);
+vkQueueWaitIdle(graphicsQueue);
+```
+
+* That’s fine for setup, but make sure your **render loop doesn’t have extra waits** other than fences per frame.
+
+3. **Push constants and descriptor updates**
+
+ * Currently you only use **push constants** for rotation and a static texture → that’s fine.
+ * If you update descriptor sets per frame, that can stall if you don’t use separate sets per frame.
+
+---
+
+## ✅ Minimal fix for your current code
+
+I recommend **switching to one command buffer per swapchain image**.
+
+### Changes:
+
+```c
+// allocate command buffers per swapchain image
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+};
+vkAllocateCommandBuffers(device, &cbAlloc, commandBuffers);
+```
+
+Then, in your render loop:
+
+```c
+uint32_t imgIndex;
+vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE, &imgIndex);
+
+// use command buffer dedicated to this swapchain image
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* **No more mismatches**.
+* `currentFrame` is still used for fences/semaphores, not command buffers.
+
+---
+
+If you want, I can **rewrite your render loop fully corrected** with:
+
+* Multiple swapchain images
+* MAX_FRAMES_IN_FLIGHT fences/semaphores
+* Dynamic rendering + textures + push constant rotation
+
+so the stutter disappears completely.
+
+Do you want me to do that?
--- /dev/null
+Good catch — this is **not** a texturing problem anymore.
+What you’re seeing is **time-step stutter**, not GPU stutter.
+
+---
+
+## ✅ Root cause: frame-dependent rotation
+
+Right now you update rotation like this:
+
+```c
+angle += 0.01f;
+```
+
+That means:
+
+* Rotation speed depends on **how long the frame took**
+* If one frame takes longer (OS scheduling, compositor, swapchain wait)
+* The angle “jumps” more on that frame
+* Your eye interprets this as **texture stuttering**
+
+The texture is actually stable — the **motion is uneven**.
+
+---
+
+## Why Vulkan makes this obvious
+
+You are using:
+
+```c
+VK_PRESENT_MODE_FIFO_KHR
+```
+
+Which:
+
+* Syncs to v-blank (good)
+* Causes variable frame times when frames miss a refresh
+* Makes fixed-per-frame motion very noticeable
+
+This is **expected behavior**.
+
+---
+
+## ✅ Correct fix: time-based animation (delta time)
+
+You must rotate based on **elapsed time**, not frames.
+
+### Add these variables
+
+At the top of `main`:
+
+```c
+double lastTime = glfwGetTime();
+```
+
+### Replace your angle update with this
+
+Inside the loop:
+
+```c
+double now = glfwGetTime();
+double delta = now - lastTime;
+lastTime = now;
+
+angle += (float)(delta * 1.5); // radians per second
+```
+
+Now rotation speed is:
+
+* Smooth
+* Independent of frame rate
+* Independent of swapchain stalls
+
+---
+
+## 🎯 Why this fixes the “texture stutter”
+
+Perspective-correct interpolation is now correct
+✔ Texture is locked to geometry
+✔ Geometry moves smoothly in time
+✔ No visible swimming or jitter
+
+---
+
+## ❌ What is NOT the cause
+
+* ❌ Texture sampling
+* ❌ Descriptor sets
+* ❌ Dynamic rendering
+* ❌ Nearest vs linear filtering
+* ❌ Swapchain image count
+
+---
+
+## Optional improvements (not required)
+
+### Clamp large frame jumps (alt-tab, debugger pauses)
+
+```c
+if (delta > 0.1) delta = 0.1;
+```
+
+### Use radians explicitly (clarity)
+
+```c
+float rotationSpeed = M_PI / 2.0f; // 90°/sec
+angle += rotationSpeed * delta;
+```
+
+---
+
+## Final verdict
+
+✔ Your Vulkan pipeline is correct
+✔ Your texture mapping is correct
+✔ Your projection fix was correct
+❌ Motion was frame-rate dependent
+
+After switching to **delta-time animation**, the “texture stutter” should disappear entirely.
+
+If you want next, I can:
+
+* Add a depth buffer (very noticeable improvement)
+* Switch to indexed drawing
+* Add proper MVP matrices
+* Show how to decouple update & render timing
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
--- /dev/null
+Ah — now I see exactly what’s happening. The “stuttering” you’re seeing is **not a problem with your delta-time rotation**, but instead comes from the **way you’re submitting command buffers and waiting for fences** in your render loop.
+
+Right now, your loop looks like this (simplified):
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlight);
+
+vkAcquireNextImageKHR(..., imageAvailable, VK_NULL_HANDLE, &imgIndex);
+
+vkResetCommandBuffer(cb, 0);
+vkBeginCommandBuffer(cb, ...);
+
+// record commands
+vkCmdBeginRendering(cb, &renderingInfo);
+vkCmdBindPipeline(...);
+vkCmdDraw(...);
+vkCmdEndRendering(cb);
+
+vkEndCommandBuffer(cb);
+
+VkSubmitInfo submit = { ... };
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlight);
+
+VkPresentInfoKHR present = { ... };
+vkQueuePresentKHR(graphicsQueue, &present);
+```
+
+---
+
+## 🔴 The problem
+
+You are using **one fence (`inFlight`) for all frames**:
+
+```c
+VkFence inFlight;
+```
+
+And your loop does:
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+```
+
+This means:
+
+* Each frame **blocks until the previous frame finishes entirely** (GPU + present)
+* If your GPU is fast, this is okay
+* If there’s any fluctuation (swapchain wait, vsync), your CPU **stalls**
+* Result: **stuttery motion**, even if delta-time rotation is correct
+
+Also, you **don’t use multiple in-flight frames**, so you can’t submit the next frame until the GPU is done.
+
+---
+
+## ✅ How to fix it
+
+You need to implement **double-buffered or triple-buffered frames in flight**.
+
+### Step 1: create multiple fences
+
+```c
+#define MAX_FRAMES_IN_FLIGHT 2
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+```
+
+### Step 2: cycle through frames
+
+```c
+uint32_t currentFrame = 0;
+```
+
+Inside the loop:
+
+```c
+vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+);
+
+// record command buffer ...
+// submit:
+VkSubmitInfo submit = { ... };
+submit.waitSemaphoreCount = 1;
+submit.pWaitSemaphores = &imageAvailable[currentFrame];
+submit.signalSemaphoreCount = 1;
+submit.pSignalSemaphores = &renderFinished[currentFrame];
+
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+// present:
+VkPresentInfoKHR present = { ... };
+present.waitSemaphoreCount = 1;
+present.pWaitSemaphores = &renderFinished[currentFrame];
+
+vkQueuePresentKHR(graphicsQueue, &present);
+
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+---
+
+## Step 3: Why this fixes the stutter
+
+* Now **CPU doesn’t wait for GPU to finish the previous frame entirely**
+* You can have **two frames queued in flight**
+* Delta-time rotation is smooth
+* Swapchain image acquisition can happen asynchronously
+* Texture and cube rotation appear smooth
+
+---
+
+### Extra tip:
+
+Make sure your **swapchain present mode is FIFO (vsync)**:
+
+```c
+.preTransform = caps.currentTransform,
+.presentMode = VK_PRESENT_MODE_FIFO_KHR,
+```
+
+This ensures no tearing, but introduces a slight latency — which is fine with multiple frames in flight.
+
+---
+
+### ✅ TL;DR
+
+1. You currently block on a single fence every frame → causes stutter.
+2. Implement **multiple frames in flight** (double/triple buffering).
+3. Use **frame-indexed semaphores and fences** for acquire/submit/present.
+4. Keep delta-time rotation as you have — it will now appear smooth.
+
+---
+
+If you want, I can rewrite your **main render loop with double-buffered frames in flight** for your current code — fully Vulkan 1.3 + dynamic rendering ready.
+
+Do you want me to do that?
+
+Ah! I can see exactly why your program segfaults — it’s not a single one-liner; there are **multiple issues related to fences, semaphores, and memory allocation** in your current code. Let me break it down carefully and explain exactly what’s going wrong, and how to fix it.
+
+---
+
+## 1️⃣ Fences and semaphores creation
+
+You have:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+**Problem:**
+
+* `imageAvailable` and `renderFinished` are **arrays** of `MAX_FRAMES_IN_FLIGHT` elements.
+* `inFlightFences` is also an **array** of size `MAX_FRAMES_IN_FLIGHT`.
+* `vkCreateSemaphore` and `vkCreateFence` expect a **pointer to a single VkSemaphore/VkFence**, not an array.
+
+Right now, you’re passing `imageAvailable` (type `VkSemaphore[MAX_FRAMES_IN_FLIGHT]`) **without indexing**, so Vulkan interprets the pointer incorrectly → undefined behavior → **segfault**.
+
+**Fix:** create each semaphore/fence individually:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+}
+```
+
+✅ This is **mandatory**. Without it, your `vkWaitForFences` later will segfault because the fence handle is garbage.
+
+---
+
+## 2️⃣ Command buffers + swapchain images mismatch
+
+You allocated:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+and
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* `imgIndex` comes from `vkAcquireNextImageKHR`.
+* But `swapchainImageCount` may be **larger than MAX_FRAMES_IN_FLIGHT**.
+* Your `currentFrame` index is used for fences and semaphores.
+* This can cause **index mismatches** if you have more swapchain images than frames in flight.
+
+**Fix:** use **frame indexing consistently**:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+```
+
+Then record the command buffer for **that frame**.
+Or allocate **one command buffer per frame in flight**, not per swapchain image.
+
+---
+
+## 3️⃣ Push constants / pipeline layout
+
+You correctly defined a push constant of `sizeof(float)` for rotation.
+✅ This part is fine.
+
+---
+
+## 4️⃣ Descriptor sets
+
+You only have **1 descriptor set**, but you submit multiple frames in flight.
+
+* Technically okay since the texture is static, but if you ever update it dynamically, you’d need **one descriptor set per frame**.
+* For now, not a segfault cause.
+
+---
+
+## 5️⃣ Minor issues that can crash
+
+* `VkSurfaceFormatKHR formats[fmtCount];` — if `fmtCount` is zero, this will crash. Always check:
+
+```c
+if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+```
+
+* `vkCreateImageView` and `vkCreateBuffer` **do not check return values everywhere**. Make sure you check **every Vulkan call**.
+
+---
+
+## 6️⃣ Texture image staging
+
+* Your staging buffer is allocated, copied, and freed **synchronously**.
+* That part is fine.
+* Just make sure `vkQueueSubmit` waits for the copy to finish, which you do with `vkQueueWaitIdle`. ✅
+
+---
+
+## ✅ Corrected fence/semaphore creation
+
+Replace:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+with:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create synchronization objects\n");
+ exit(1);
+ }
+}
+```
+
+---
+
+## 7️⃣ Recommended changes
+
+1. Allocate **one command buffer per frame in flight**, not per swapchain image.
+2. Use `currentFrame` to index fences, semaphores, and command buffers.
+3. Always check Vulkan function returns.
+4. Use `VK_NULL_HANDLE` fences/semaphores carefully — do **not** pass arrays directly.
+
+---
+
+If you want, I can **rewrite your main render loop + fence/semaphore setup** fully corrected for **double-buffered frames in flight** — so it will **stop segfaulting** and run smoothly.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely! Let’s break it down carefully.
+
+---
+
+### 1️⃣ What `u` and `v` are
+
+In your struct:
+
+```c
+typedef struct {
+ float x, y, z; // 3D position in space
+ float u, v; // texture coordinates
+} CubeVertex;
+```
+
+* `x, y, z` → define the **vertex position in 3D space**.
+* `u, v` → define **where on a 2D texture image this vertex maps to**.
+
+These are often called **UV coordinates** or **texture coordinates**.
+
+---
+
+### 2️⃣ How `u` and `v` relate to the texture
+
+Textures are images. Imagine a 2D image like this:
+
+```
+(0,1) ┌─────────────┐ (1,1)
+ │ │
+ │ │
+ │ │
+(0,0) └─────────────┘ (1,0)
+```
+
+* `u` → horizontal position on the texture (0 = left edge, 1 = right edge)
+* `v` → vertical position on the texture (0 = bottom edge, 1 = top edge)
+
+> Note: In Vulkan (and OpenGL), **v=0 is usually bottom**, **v=1 is top**. Some image libraries flip this, so sometimes `v` may need to be inverted.
+
+---
+
+### 3️⃣ How this works in rendering
+
+When Vulkan draws a triangle:
+
+1. Each vertex has `(x, y, z)` for position and `(u, v)` for texture mapping.
+2. The GPU **interpolates** `u` and `v` across the triangle.
+3. For every pixel in the triangle, the GPU calculates a corresponding `(u, v)` coordinate.
+4. The pixel color is sampled from the texture using that `(u, v)` coordinate.
+
+---
+
+### 4️⃣ Example
+
+If you have a cube face made of two triangles and a 2x2 texture like this:
+
+```
+Texture:
+[white][pink]
+[pink][white]
+```
+
+And your vertices are:
+
+```c
+{-0.5f,-0.5f,-0.5f, 0,0}, // bottom-left maps to texture bottom-left
+{ 0.5f,-0.5f,-0.5f, 1,0}, // bottom-right maps to texture bottom-right
+{ 0.5f, 0.5f,-0.5f, 1,1}, // top-right maps to texture top-right
+{-0.5f, 0.5f,-0.5f, 0,1} // top-left maps to texture top-left
+```
+
+* Each vertex "grabs" the color from that corner of the texture.
+* The GPU fills the interior of the triangle with colors **interpolated** between those corners.
+
+---
+
+### ✅ TL;DR
+
+* `u` = horizontal coordinate on the texture
+* `v` = vertical coordinate on the texture
+* `(u, v)` tell Vulkan **which part of the 2D image** to apply to the 3D vertex.
+* Without `u, v`, Vulkan wouldn’t know how to map the texture to the cube.
+
+---
+
+If you want, I can make a **small visual diagram showing a cube face, its vertices, and how u,v map the texture onto it**, which usually makes it click instantly.
+
+Do you want me to do that?
+
+Absolutely! Let’s connect the dots between your **CubeVertex struct** `(x, y, z, u, v)` and how the **shader** uses it to texture your cube. I’ll break it down step by step.
+
+---
+
+## 1️⃣ Vertex Shader
+
+A typical vertex shader in Vulkan GLSL might look like this:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec3 inPos; // x, y, z from CubeVertex
+layout(location = 1) in vec2 inUV; // u, v from CubeVertex
+
+layout(location = 0) out vec2 fragUV; // pass to fragment shader
+
+layout(push_constant) uniform PushConstants {
+ float angle;
+} pc;
+
+void main() {
+ // Apply rotation around Y axis
+ float s = sin(pc.angle);
+ float c = cos(pc.angle);
+ vec3 pos = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // Convert to clip space (just simple identity for example)
+ gl_Position = vec4(pos, 1.0);
+
+ // Pass the UV coordinates to the fragment shader
+ fragUV = inUV;
+}
+```
+
+### 🔑 Notes:
+
+* `layout(location = 0) in vec3 inPos` ← gets **x, y, z** from `CubeVertex`.
+* `layout(location = 1) in vec2 inUV` ← gets **u, v** from `CubeVertex`.
+* `fragUV = inUV;` → we pass the texture coordinates **to the fragment shader**, interpolated across the triangle.
+
+So every pixel in the triangle will have a `fragUV` coordinate that is **interpolated** between the vertices.
+
+---
+
+## 2️⃣ Fragment Shader
+
+Then, in the fragment shader:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec2 fragUV; // interpolated UV
+layout(location = 0) out vec4 outColor; // final pixel color
+
+layout(set = 0, binding = 0) uniform sampler2D tex; // texture sampler
+
+void main() {
+ // Sample the texture at fragUV
+ outColor = texture(tex, fragUV);
+}
+```
+
+### 🔑 Notes:
+
+* `fragUV` = interpolated `(u,v)` coordinates from vertex shader.
+* `sampler2D tex` = your bound texture image + sampler.
+* `texture(tex, fragUV)` → returns the color of the texture at that `(u,v)` location.
+* The GPU writes `outColor` to the framebuffer.
+
+---
+
+## 3️⃣ How this maps from your CubeVertex:
+
+| CubeVertex field | Role in Shader |
+| ---------------- | ---------------------------------------------------------------------------------------- |
+| `x, y, z` | `inPos` in vertex shader → determines 3D position of vertex |
+| `u, v` | `inUV` in vertex shader → passed as `fragUV` to fragment shader → used to sample texture |
+
+**Flow:**
+
+```
+CubeVertex.u,v ---> vertex shader ---> fragUV (interpolated) ---> fragment shader ---> texture lookup ---> final pixel color
+```
+
+---
+
+## 4️⃣ Why this is important
+
+Without `u, v`:
+
+* Fragment shader wouldn’t know **which part of the texture** corresponds to each pixel.
+* Texture would be meaningless; all faces might just use the top-left pixel, for example.
+
+With `u, v`:
+
+* Each face of your cube can correctly map the texture, even after rotation.
+* The GPU interpolates `u, v` across triangles for smooth texturing.
+
+---
+
+If you want, I can also **draw a diagram showing the cube, a triangle, and how `(u,v)` coordinates are interpolated to map the texture properly**.
+
+Do you want me to make that visual?
--- /dev/null
+Ah, this is a **common misunderstanding in Vulkan** — the “3D” in `VkImageViewType` does **not mean a 3D object in the scene**, it’s about the **image resource itself** in GPU memory. Let me explain carefully.
+
+---
+
+## 1️⃣ What `viewType` really means
+
+`VkImageViewCreateInfo.viewType` can be:
+
+| Value | Meaning |
+| ------------------------- | -------------------------------------------------- |
+| `VK_IMAGE_VIEW_TYPE_1D` | The image is a 1D texture (width only) |
+| `VK_IMAGE_VIEW_TYPE_2D` | The image is a 2D texture (width × height) |
+| `VK_IMAGE_VIEW_TYPE_3D` | The image is a 3D texture (width × height × depth) |
+| `VK_IMAGE_VIEW_TYPE_CUBE` | Cube map texture (6 faces) |
+
+**Important:** This is about **how the GPU interprets the memory layout of the image**, not whether your scene is 3D.
+
+---
+
+## 2️⃣ Swapchain images are always 2D
+
+* Swapchain images are the **images that will be presented on your 2D screen**.
+* Even though your cube is a 3D object in the scene, the **swapchain image is a 2D framebuffer**.
+* Therefore, `viewType` **must remain `VK_IMAGE_VIEW_TYPE_2D`** for swapchain images.
+
+If you tried `VK_IMAGE_VIEW_TYPE_3D`:
+
+* Vulkan would expect a **3D image resource** in memory (with depth slices).
+* Swapchain images **aren’t 3D** — they only have width × height × 1.
+* `vkCreateImageView` would **fail** or behave incorrectly.
+
+---
+
+## 3️⃣ Summary
+
+* Your 3D cube **does not make the swapchain 3D**.
+* `VK_IMAGE_VIEW_TYPE_2D` is correct for **all normal onscreen rendering**.
+* `VK_IMAGE_VIEW_TYPE_3D` is only for **volumetric textures**, like 3D noise, 3D textures, or voxel data.
+
+---
+
+💡 **Analogy:**
+
+* The 3D cube is like a **3D model**.
+* The swapchain image is like the **photograph of that model** — a 2D picture.
+* The swapchain image view interprets the “photograph” — so it’s always 2D.
+
+---
+
+If you want, I can also explain **when you *would* use `VK_IMAGE_VIEW_TYPE_3D` in Vulkan** and how it differs from rendering a normal 3D scene. This is actually a pretty common point of confusion for newcomers.
+
+Do you want me to go into that?
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+#version 450
+
+layout(set = 0, binding = 0) uniform sampler2D tex;
+layout(location = 0) in vec2 uv;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = texture(tex, uv);
+}
+
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec2 inUV;
+
+layout(location = 0) out vec2 fragUV;
+
+layout(push_constant) uniform Push {
+ float angle;
+} pc;
+
+void main() {
+ float c = cos(pc.angle);
+ float s = sin(pc.angle);
+
+ vec3 p = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // simple perspective
+ float z = p.z + 2.5;
+
+ gl_Position = vec4(p.xy, z, z); // ← KEY FIX
+ fragUV = inUV;
+}
--- /dev/null
+build: *.adoc
+ asciidoctor -b html5 *.adoc
--- /dev/null
+:docinfo: shared
+:docinfodir: /usr/local/share/readme-to-html/
+:stem: latexmath
+
+== Fixed-Point Data Conversions
+
+* https://docs.vulkan.org/spec/latest/chapters/fundamentals.html#fundamentals-fixedconv
+
+This is what happens when you use a format like `VK_FORMAT_RG8B8A8_UNORM`. It
+maps an 8-bit integer (0 to 255) to a decimal (0.0 to 1.0).
+
+`b` is the bit count
+
+[stem]
+++++
+f = \frac{c}{2^{b-1}}
+++++
+
+
+== signed Normalized (SNORM)
+
+This is used for things like normals or offsets (`VK_FORMAT_R8G8_SNORM`), where
+you need negative numbers. It maps integers to the range [-1.0,1.0].
+
+- The "Weird" rule: in an 8-bit signed integer, the range is actually -128 to
+ 127 . However Vulkan ignores -128 to keep the math symmetrical. It only cares
+ about the range -127 to 127.
+
+[stem]
+++++
+f = \max(\frac{c}{2^{b-1}-1}, -1.0)
+++++
+
+== Conversion From Floating-Point to Normalized Fixed-Point
+
+The conversion from a floating-point value f to the corresponding unsigned
+normalized fixed-point value c is defined by first clamping f to the range
+[0,1], then computing
+
+c = convertFloatToUint(f × (2b - 1), b)
+
+where convertFloatToUint(r,b) returns one of the two unsigned binary integer
+values with exactly b bits which are closest to the floating-point value r.
+Implementations should round to nearest. If r is equal to an integer, then that
+integer value must be returned. In particular, if f is equal to 0.0 or 1.0, then
+c must be assigned 0 or 2b - 1, respectively.
+
+The conversion from a floating-point value f to the corresponding signed
+normalized fixed-point value c is performed by clamping f to the range [-1,1],
+then computing
+
+c = convertFloatToInt(f × (2b-1 - 1), b)
+
+where convertFloatToInt(r,b) returns one of the two signed two’s-complement
+binary integer values with exactly b bits which are closest to the
+floating-point value r. Implementations should round to nearest. If r is equal
+to an integer, then that integer value must be returned. In particular, if f is
+equal to -1.0, 0.0, or 1.0, then c must be assigned -(2b-1 - 1), 0, or 2b-1 - 1,
+respectively.
+
+This equation is used everywhere that floating-point values are converted to
+signed normalized fixed-poin
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Fixed-Point Data Conversions</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<head>
+<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=DotGothic16"> -->
+<!-- <link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/A-Larsen/math-notes/refs/heads/main/favicon.png"> -->
+<!-- <link rel="icon" type="image/png" href="N2DE_favicon.png"/> -->
+<!-- <link rel="preconnect" href="https://fonts.googleapis.com"> -->
+<!-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
+<!-- <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap" rel="stylesheet"> -->
+<style>
+
+/*--------------------------------------------------------------*/
+/*use '!important' to override default css used by asciidoctor*/
+/*--------------------------------------------------------------*/
+
+:root {
+ --gruvbox-black: #282828;
+ --gruvbox-black2: #1d2021;
+ --gruvbox-black3: #463f39;
+ --gruvbox-red: #fc7869;
+ --gruvbox-yellow: #fabd2f;
+ --gruvbox-white: #ebdbb2;
+ --gruvbox-white2: #a7a187;
+ --gruvbox-blue: #83a598;
+ --gruvbox-green: #b8bb26;
+ --gruvbox-bg1: #7c6f64;
+ --gruvbox-bg2: #665c54;
+
+}
+
+.title {
+ color: var(--gruvbox-red) !important;
+}
+
+blockquote {
+ color: var(--gruvbox-white) !important;
+}
+
+table {
+ margin: 0 auto !important;
+}
+
+.hdlist1 {
+ font-size: 1.3em;
+}
+
+cite {
+ color: var(--gruvbox-green) !important;
+}
+
+.tableblock {
+ background-color: inherit;
+ color: var(--gruvbox-white);
+ max-width: 95%;
+}
+
+.tableblock th {
+ background-color: var(--gruvbox-black3);
+ color: var(--gruvbox-white);
+ max-width: 97%;
+}
+
+code:not([data-lang]) {
+ background-color: var(--gruvbox-bg2) !important;
+ color: var(--gruvbox-white);
+ text-shadow: none;
+}
+
+.checklist {
+ text-align: left;
+ font-size: .8em;
+}
+
+.checklist li ul { margin-left: 3%;
+}
+
+#content, h1, pre {
+ border-radius: 8px !important;
+}
+
+#header, #content, #footnotes, #footer {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.api-type a:hover {
+ text-decoration: underline;
+}
+
+.api-type, .api-type a {
+ color: var(--gruvbox-green);
+ text-decoration: none;
+}
+
+pre {
+ border-style: solid !important;
+ border-color: white !important;
+ border-width: 2px !important;
+}
+
+h1 {
+ /* font-family: "DotGothic16", sans-serif !important;*/
+
+
+ background-color: var(--gruvbox-white2) !important;
+ color: var(--gruvbox-black) !important;
+}
+
+.admonitionblock > table td.content {
+ color: var(--gruvbox-white) !important;
+}
+
+/*
+html {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+}
+
+
+*/
+/*html {*/
+
+body {
+ /*background: black url("background-image-space.jpeg") no-repeat fixed center;*/
+ /*background-color: black;*/
+ background-color: var(--gruvbox-black);
+ background-attachment: fixed;
+ background-size: cover;
+ overflow-y: scroll;
+ /*
+ margin: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ */
+}
+
+
+/*
+body {
+ overflow-y: scroll;
+ background: rgba(0,0,0,0);
+ height: 100%;
+ margin: 2%;
+}
+*/
+
+#toc {
+ background-color: var(--gruvbox-black2) !important;
+ /*opacity: .9;*/
+}
+
+#toctitle {
+ color: var(--gruvbox-yellow);
+}
+
+a {
+ color: var(--gruvbox-bg1);
+}
+
+#content {
+ color: #ebdbb2;
+ background: var(--gruvbox-black);
+ /*opacity: .9;*/
+}
+
+#header > h1:nth-last-child(2) {
+ border-bottom: none !important;
+ background-color: rgba(0, 0, 0, .1);
+ padding-left: 2%;
+}
+
+.sect1 + .sect1 {
+ border-top: none !important;
+}
+
+.sect1 h2 {
+ background-size: cover;
+ background-attachment: fixed;
+ /*
+ border-bottom: 1px solid var(--gruvbox-white);
+ border-top: 1px solid var(--gruvbox-white);
+ */
+ padding: 10px;
+ text-align: left;
+ color: var(--gruvbox-red) !important;
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-black);
+ text-shadow: 3px 3px 3px var(--gruvbox-black);
+ text-shadow: 5px 5px 5px var(--gruvbox-blue);
+ */
+}
+
+h3, h4, h5 {
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-white);
+ */
+}
+
+h3 {
+ color: var(--gruvbox-red) !important;
+}
+
+h4 {
+ color: var(--gruvbox-yellow) !important;
+}
+
+h5 {
+ color: #83a598 !important;
+}
+
+h2 ~ *:not(h1) {
+ margin-left: .5em;
+}
+
+.pygments {
+ max-width: 97% !important;
+ white-space: pre;
+ overflow-x: scroll !important;
+}
+
+::before {
+ /*makes sure that the programming language that show up on hover is within
+ pre tag*/
+ padding-right: 3%;
+}
+
+@media screen and (max-width: 800px) {
+ .checklist li ul {
+ margin-left: 6% !important;
+ }
+ #toc {
+ padding-left: 10px;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+@media screen and (max-width: 535px) {
+ h3 ~ *:not(h2):not(h3) {
+ margin-left: 0 !important;
+ }
+
+ h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 0 !important;
+ }
+ h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 0 !important;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+h3 ~ *:not(h2):not(h3) {
+ margin-left: 1.5em;
+}
+
+h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 2em;
+}
+h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 2.5em;
+}
+
+
+.sect1 h2 {
+
+ /*
+ background: linear-gradient(
+ to bottom,
+ rgba(251, 189, 47, 0.2), /* yellow */
+ rgba(0, 0, 0, 0.4) /* black */
+ );
+ */
+ /*opacity: .9;*/
+ }
+
+#preamble p{
+ padding-left: 10px;
+ color: var(--gruvbox-white) !important;
+}
+
+#footer {
+ max-width: 62.5em !important;
+ padding-left: 10px !important;
+ text-align: center;
+}
+
+.logo img {
+ display: block !important;
+ margin-left: auto !important;
+ margin-right: auto !important;
+}
+
+#header > h1:first-child {
+ margin-top: .3em !important;
+ padding-top: .2em !important;
+ text-align: center;
+}
+
+footer {
+ max-width: 62.5em;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+
+}
+
+details[open] > summary::before {
+ /* display: none;*/
+ border-top: none;
+ content: "▼";
+ margin-top: -12px;
+}
+
+.example.stemblock {
+ border: solid;
+ font-size: 2vw;
+ max-width: 40%;
+ margin: auto;
+}
+
+.center {
+ max-width: 500px;
+ display: flex;
+ justify-content: center;
+}
+
+.highlight {
+ color: var(--gruvbox-yellow)
+}
+
+.border {
+ padding: 4px;
+ border: 2px solid rgba(255, 0, 0, .3);
+}
+
+.dull {
+ opacity: 40%;
+}
+
+.inline {
+ margin-right: 5%;
+ display: inline-block;
+}
+
+img {
+ margin: auto;
+ width: 100%;
+}
+
+
+</style>
+
+</head>
+
+<script>
+function sendHeight() {
+ const height = document.documentElement.scrollHeight;
+ parent.postMessage({ type: "HEIGHT", height }, "*");
+}
+
+window.addEventListener("load", sendHeight);
+window.addEventListener("resize", sendHeight);
+</script>
+
+
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_fixed_point_data_conversions">Fixed-Point Data Conversions</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p><a href="https://docs.vulkan.org/spec/latest/chapters/fundamentals.html#fundamentals-fixedconv" class="bare">https://docs.vulkan.org/spec/latest/chapters/fundamentals.html#fundamentals-fixedconv</a></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This is what happens when you use a format like <code>VK_FORMAT_RG8B8A8_UNORM</code>. It
+maps an 8-bit integer (0 to 255) to a decimal (0.0 to 1.0).</p>
+</div>
+<div class="paragraph">
+<p><code>b</code> is the bit count</p>
+</div>
+<div class="stemblock">
+<div class="content">
+\[f = \frac{c}{2^{b-1}}\]
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_signed_normalized_snorm">signed Normalized (SNORM)</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>This is used for things like normals or offsets (<code>VK_FORMAT_R8G8_SNORM</code>), where
+you need negative numbers. It maps integers to the range [-1.0,1.0].</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>The "Weird" rule: in an 8-bit signed integer, the range is actually -128 to
+127 . However Vulkan ignores -128 to keep the math symmetrical. It only cares
+about the range -127 to 127.</p>
+</li>
+</ul>
+</div>
+<div class="stemblock">
+<div class="content">
+\[f = \max(\frac{c}{2^{b-1}-1}, -1.0)\]
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_conversion_from_floating_point_to_normalized_fixed_point">Conversion From Floating-Point to Normalized Fixed-Point</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The conversion from a floating-point value f to the corresponding unsigned
+normalized fixed-point value c is defined by first clamping f to the range
+[0,1], then computing</p>
+</div>
+<div class="paragraph">
+<p>c = convertFloatToUint(f × (2b - 1), b)</p>
+</div>
+<div class="paragraph">
+<p>where convertFloatToUint(r,b) returns one of the two unsigned binary integer
+values with exactly b bits which are closest to the floating-point value r.
+Implementations should round to nearest. If r is equal to an integer, then that
+integer value must be returned. In particular, if f is equal to 0.0 or 1.0, then
+c must be assigned 0 or 2b - 1, respectively.</p>
+</div>
+<div class="paragraph">
+<p>The conversion from a floating-point value f to the corresponding signed
+normalized fixed-point value c is performed by clamping f to the range [-1,1],
+then computing</p>
+</div>
+<div class="paragraph">
+<p>c = convertFloatToInt(f × (2b-1 - 1), b)</p>
+</div>
+<div class="paragraph">
+<p>where convertFloatToInt(r,b) returns one of the two signed two’s-complement
+binary integer values with exactly b bits which are closest to the
+floating-point value r. Implementations should round to nearest. If r is equal
+to an integer, then that integer value must be returned. In particular, if f is
+equal to -1.0, 0.0, or 1.0, then c must be assigned -(2b-1 - 1), 0, or 2b-1 - 1,
+respectively.</p>
+</div>
+<div class="paragraph">
+<p>This equation is used everywhere that floating-point values are converted to
+signed normalized fixed-poin</p>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+ messageStyle: "none",
+ tex2jax: {
+ inlineMath: [["\\(", "\\)"]],
+ displayMath: [["\\[", "\\]"]],
+ ignoreClass: "nostem|nolatexmath"
+ },
+ asciimath2jax: {
+ delimiters: [["\\$", "\\$"]],
+ ignoreClass: "nostem|noasciimath"
+ },
+ TeX: { equationNumbers: { autoNumber: "none" } }
+})
+MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
+ MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
+ data.math.root.display = "block"
+ }
+ return data
+ })
+})
+</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Untitled</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+== Devices
+Once Vulkan is initialized, devices and queues are the primary objects used to
+interact with a Vulkan implementation.
+
+Vulkan seperates the concept of _physical_ and _logical_ devices. A physical
+device usually represents a single implementation of Vulkan (excluding
+instance-level functionality) available to the host, of which there are a finite
+number. A logical device represents an instance of the implementation with its
+own state and resources independent of other logical devices.
+
+Physical devices cannot be independently destroyed, and are instead destroyed
+with the vkInstance that they were retrieved from.
+
+Physical devices are represented by `VkPhysicalDevice` handles:
+
+----
+// Provided by VK_VERSION_1_0
+VK_DEFINE_HANDLE(VkPhysicalDevice)
+----
+
+
+== My devices
+
+----
+$ ./vk_cube
+Vulkan loader supports version 1.3.275
+device count: 2
+GPU Name: AMD Radeon RX 6600 (RADV NAVI23)
+GPU Name: llvmpipe (LLVM 20.1.2, 256 bits)
+image count: 4
+----
+
+In this output, you're seeing two very different "engines." The first is your
+actual hardware (the AMD card), but *llvmpipe* is something entirely different.
+
+*llvmpipe* is a *Software Rasterizer*. It is a "fake" GPU that runs entirely
+on your *CPU*.
+
+== 1. How it works
+Instead of sending your SPIR-V shaders and vertex data to a physical graphics
+card, the Vulkan loarder send them to a library (part of the Mesa project) that
+uses *LLVM* (a compiler infrastructure) to turn those shaders into x86 machine
+code. Your CPU then executes that code to "draw" the pixels in system RAM.
+
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Devices</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_devices">Devices</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Once Vulkan is initialized, devices and queues are the primary objects used to
+interact with a Vulkan implementation.</p>
+</div>
+<div class="paragraph">
+<p>Vulkan seperates the concept of <em>physical</em> and <em>logical</em> devices. A physical
+device usually represents a single implementation of Vulkan (excluding
+instance-level functionality) available to the host, of which there are a finite
+number. A logical device represents an instance of the implementation with its
+own state and resources independent of other logical devices.</p>
+</div>
+<div class="paragraph">
+<p>Physical devices cannot be independently destroyed, and are instead destroyed
+with the vkInstance that they were retrieved from.</p>
+</div>
+<div class="paragraph">
+<p>Physical devices are represented by <code>VkPhysicalDevice</code> handles:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre>// Provided by VK_VERSION_1_0
+VK_DEFINE_HANDLE(VkPhysicalDevice)</pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_my_devices">My devices</h2>
+<div class="sectionbody">
+<div class="listingblock">
+<div class="content">
+<pre>$ ./vk_cube
+Vulkan loader supports version 1.3.275
+device count: 2
+GPU Name: AMD Radeon RX 6600 (RADV NAVI23)
+GPU Name: llvmpipe (LLVM 20.1.2, 256 bits)
+image count: 4</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>In this output, you’re seeing two very different "engines." The first is your
+actual hardware (the AMD card), but <strong>llvmpipe</strong> is something entirely different.</p>
+</div>
+<div class="paragraph">
+<p><strong>llvmpipe</strong> is a <strong>Software Rasterizer</strong>. It is a "fake" GPU that runs entirely
+on your <strong>CPU</strong>.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_1_how_it_works">1. How it works</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Instead of sending your SPIR-V shaders and vertex data to a physical graphics
+card, the Vulkan loarder send them to a library (part of the Mesa project) that
+uses <strong>LLVM</strong> (a compiler infrastructure) to turn those shaders into x86 machine
+code. Your CPU then executes that code to "draw" the pixels in system RAM.</p>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+== Overview
+
+In Vulkan "Images in flight" referes to the number of frames the GPU is actively
+processing at the same time. It is the core concept that makes *Double and*
+*Tripple buffering* work.
+
+When you write a high-performace C application, you don't wnat your CPU to sit
+idle waiting for the GPU to finish a frame. "In flight" is the strategy of
+having the CPU start working on Frame 2 while the GPU is stil crunching the
+numbers for frame 1.
+
+== 1. The Relationship with Buffering
+
+"In flight" is the action, and Buffering is the infrastructure.
+
+* Double Buffering: You have 2 images. You can have 1 frame in flight. While the
+ monitor shows Image A, the GPU is working on Image B. The CPU then has to wait
+ for a "signal" (a Fence) that Image B is done before it can start recording
+ commands for the next frame.
+
+* Triple Buffering: You have 3 images. You can have 2 frames in flight. The CPU
+ can finish recording Frame 2 and immediately start on Frame 3 without waiting
+ for the GPU to finish Frame 1
+
+== 2. The "In Flight Sync Problem"
+
+In C, you manage this using an array of synchronization objects. If you want 2
+frames in flight, you need 2 of everything:
+
+[source,c]
+----
+#define MAX_FRAMES_IN_FLIGHT 2
+
+VkSemaphore imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinishedSemaphores[MAX_FRAMES_IN_FLIGHT];
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+----
+
+Every loop iteration, yo usue a `currentFrame` index (`0` or `1`) to pick which
+set of "sync tools" to use.
+
+1. *Wait*: The CPU waits for `inFlightFences[currentFrame]`. This ensures the
+ GPU is truly finished with that specific "slot."
+
+2. *Acquire*: You ask the Swapchain for an image.
+
+3. *Submit*: You give the GPU work.
+
+4. *Advance*: `currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;`
+
+== How Many Frames Should I Have in Flight?
+
+=== 1. The Case for 3 Frames (Max Throughput)
+
+Using 3 frames in flight is excellent for *benchmarking* or *non-interactive*
+*rendering* (like video encoding or sinematic sequences).
+
+* *GPU Utilization*: It provides a larger "buffer" of work. If the CPU has a
+ temporary stutter (background task, OS interrupt), the GPU has enough work
+ queued up in those 3 frames to keep running without pausing.
+
+* *Smoothing Frame Times*: If your frame delivery is inconsistent, 3 frames can
+ help "average out" the gaps, making the motion look smoother to the eye.
+
+=== 2. The Case Against 3 Frames (The Lag Factor)
+
+For a fast-paced game, 3 frames in flight is often considered sub-optimal
+because of "Input Lag."
+
+* The Math of Lag: If you are running at 60 FPS, each frame takes ~16.6ms.
+ - 1 Frame in Flight: ~16ms lag.
+ - 2 Frames in Flight: ~33ms lag.
+ - 3 Frames in Flight: ~50ms lag.
+
+=== 3. The "Sweet Spot" for Modern C Apps
+
+Most modern Vulkan engines (and the official Khronos samples) default to 2
+frames in flight.
+
+1. Frame 0: Being displayed by the monitor.
+2. Frame 1: Being processed by the GPU hardware.
+3. Frame 2: Being recorded by the CPU (your C code).
+
+This setup ensures that the GPU is never waiting for the CPU, but it keeps the
+latency low enough that the player doesn't feel the lag.
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Overview</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_overview">Overview</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In Vulkan "Images in flight" referes to the number of frames the GPU is actively
+processing at the same time. It is the core concept that makes <strong>Double and</strong>
+<strong>Tripple buffering</strong> work.</p>
+</div>
+<div class="paragraph">
+<p>When you write a high-performace C application, you don’t wnat your CPU to sit
+idle waiting for the GPU to finish a frame. "In flight" is the strategy of
+having the CPU start working on Frame 2 while the GPU is stil crunching the
+numbers for frame 1.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_1_the_relationship_with_buffering">1. The Relationship with Buffering</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>"In flight" is the action, and Buffering is the infrastructure.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Double Buffering: You have 2 images. You can have 1 frame in flight. While the
+monitor shows Image A, the GPU is working on Image B. The CPU then has to wait
+for a "signal" (a Fence) that Image B is done before it can start recording
+commands for the next frame.</p>
+</li>
+<li>
+<p>Triple Buffering: You have 3 images. You can have 2 frames in flight. The CPU
+can finish recording Frame 2 and immediately start on Frame 3 without waiting
+for the GPU to finish Frame 1</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_2_the_in_flight_sync_problem">2. The "In Flight Sync Problem"</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In C, you manage this using an array of synchronization objects. If you want 2
+frames in flight, you need 2 of everything:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-c" data-lang="c">#define MAX_FRAMES_IN_FLIGHT 2
+
+VkSemaphore imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinishedSemaphores[MAX_FRAMES_IN_FLIGHT];
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Every loop iteration, yo usue a <code>currentFrame</code> index (<code>0</code> or <code>1</code>) to pick which
+set of "sync tools" to use.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><strong>Wait</strong>: The CPU waits for <code>inFlightFences[currentFrame]</code>. This ensures the
+GPU is truly finished with that specific "slot."</p>
+</li>
+<li>
+<p><strong>Acquire</strong>: You ask the Swapchain for an image.</p>
+</li>
+<li>
+<p><strong>Submit</strong>: You give the GPU work.</p>
+</li>
+<li>
+<p><strong>Advance</strong>: <code>currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;</code></p>
+</li>
+</ol>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_how_many_frames_should_i_have_in_flight">How Many Frames Should I Have in Flight?</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_1_the_case_for_3_frames_max_throughput">1. The Case for 3 Frames (Max Throughput)</h3>
+<div class="paragraph">
+<p>Using 3 frames in flight is excellent for <strong>benchmarking</strong> or <strong>non-interactive</strong>
+<strong>rendering</strong> (like video encoding or sinematic sequences).</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>GPU Utilization</strong>: It provides a larger "buffer" of work. If the CPU has a
+temporary stutter (background task, OS interrupt), the GPU has enough work
+queued up in those 3 frames to keep running without pausing.</p>
+</li>
+<li>
+<p><strong>Smoothing Frame Times</strong>: If your frame delivery is inconsistent, 3 frames can
+help "average out" the gaps, making the motion look smoother to the eye.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_2_the_case_against_3_frames_the_lag_factor">2. The Case Against 3 Frames (The Lag Factor)</h3>
+<div class="paragraph">
+<p>For a fast-paced game, 3 frames in flight is often considered sub-optimal
+because of "Input Lag."</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>The Math of Lag: If you are running at 60 FPS, each frame takes ~16.6ms.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>1 Frame in Flight: ~16ms lag.</p>
+</li>
+<li>
+<p>2 Frames in Flight: ~33ms lag.</p>
+</li>
+<li>
+<p>3 Frames in Flight: ~50ms lag.</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_3_the_sweet_spot_for_modern_c_apps">3. The "Sweet Spot" for Modern C Apps</h3>
+<div class="paragraph">
+<p>Most modern Vulkan engines (and the official Khronos samples) default to 2
+frames in flight.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Frame 0: Being displayed by the monitor.</p>
+</li>
+<li>
+<p>Frame 1: Being processed by the GPU hardware.</p>
+</li>
+<li>
+<p>Frame 2: Being recorded by the CPU (your C code).</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>This setup ensures that the GPU is never waiting for the CPU, but it keeps the
+latency low enough that the player doesn’t feel the lag.</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+== Overview
+
+Dynamic rendering can be enabled with the `VK_KHR_dynamic_Rendering` extension.
+You need to be using a specific version of Vulkan for this to work.
+
+[%header, cols="1,1"]
+|===
+| Vulkan
+| Dynamic Rendering
+
+| Rendering begins with `vkCmdBeginRenderPass`
+| Rendering begins with `vkCmdBeginRenderingKHR`
+
+| Attachments are referenced by `VkFramebuffer`
+| Attachments are referenced by `VkRenderingwAttachmentInfoKHR`
+
+| `VkFramebuffer` objects are heap-allocated and opaque
+| `VkRenderingAttachmentInfoKHR` objects are stack-allocated
+
+| Graphics pipeline creation references a `VkRenderPass`
+| Graphics pipeline creation references a `VkPipelineRenderingCreateInfoKHR`
+|===
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Overview</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_overview">Overview</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Dynamic rendering can be enabled with the <code>VK_KHR_dynamic_Rendering</code> extension.
+You need to be using a specific version of Vulkan for this to work.</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Vulkan</th>
+<th class="tableblock halign-left valign-top">Dynamic Rendering</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Rendering begins with <code>vkCmdBeginRenderPass</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Rendering begins with <code>vkCmdBeginRenderingKHR</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Attachments are referenced by <code>VkFramebuffer</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Attachments are referenced by <code>VkRenderingwAttachmentInfoKHR</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VkFramebuffer</code> objects are heap-allocated and opaque</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VkRenderingAttachmentInfoKHR</code> objects are stack-allocated</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Graphics pipeline creation references a <code>VkRenderPass</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Graphics pipeline creation references a <code>VkPipelineRenderingCreateInfoKHR</code></p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+== Color Space
+In Computer graphics a *Color Space* is essentially a "translation manual" for
+numbers. It tells the hardware how a specific numerical value (like 0.5) should
+actually look in terms of physical brightness.
+
+== 1. Linear vs. Non-Linear (The Gamma Problem)
+The most important concet to understna din VUlkan color spaces is the difference
+between how *computers* think and how *human eyes* work.
+
+Linear Space::
+ If you double the numerical value of pixel, the monitor emits exactly double
+ the number of photons. This is how phsyics (an dyou rmath in shaders) works.
+
+Non-Liner (sRGB) Space::
+ Human eyes are much more sensitive to changes in dark colors than changes in
+ bright colors. To save data, we "compress" colors. The *sRGB* color space is
+ teh standard "Non-Linear" way to store colors so they look natural to
+ humans.
+
+== How HDR is "Packed" in Memory
+
+=== A. The "10-bit Pack" (`VK_FORMAT_A2B10G10R10_UNORM_PACK32`)
+
+Instad of 8 bits per channel, this packs 10 bits for Red, Green, and Blue into a
+32-bit variable (with 2 bits left over for Alpha). This is the standard HDR10.
+You can't write this as a 6-digit hex; it would be a 10 digit or very large
+integer.
+
+=== B. The "Half-Float" (`VK_FORMAT_R16G16B16A16_SFLOAT`)
+
+This is the "Gold Standard" for internal rendering. Each color gets 16 bits.
+This allows for values way above 1.0 and percision that prevents "banding" in
+dark shadows.
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Color Space</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_color_space">Color Space</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In Computer graphics a <strong>Color Space</strong> is essentially a "translation manual" for
+numbers. It tells the hardware how a specific numerical value (like 0.5) should
+actually look in terms of physical brightness.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_1_linear_vs_non_linear_the_gamma_problem">1. Linear vs. Non-Linear (The Gamma Problem)</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The most important concet to understna din VUlkan color spaces is the difference
+between how <strong>computers</strong> think and how <strong>human eyes</strong> work.</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Linear Space</dt>
+<dd>
+<p>If you double the numerical value of pixel, the monitor emits exactly double
+the number of photons. This is how phsyics (an dyou rmath in shaders) works.</p>
+</dd>
+<dt class="hdlist1">Non-Liner (sRGB) Space</dt>
+<dd>
+<p>Human eyes are much more sensitive to changes in dark colors than changes in
+bright colors. To save data, we "compress" colors. The <strong>sRGB</strong> color space is
+teh standard "Non-Linear" way to store colors so they look natural to
+humans.</p>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_how_hdr_is_packed_in_memory">How HDR is "Packed" in Memory</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_a_the_10_bit_pack_vk_format_a2b10g10r10_unorm_pack32">A. The "10-bit Pack" (<code>VK_FORMAT_A2B10G10R10_UNORM_PACK32</code>)</h3>
+<div class="paragraph">
+<p>Instad of 8 bits per channel, this packs 10 bits for Red, Green, and Blue into a
+32-bit variable (with 2 bits left over for Alpha). This is the standard HDR10.
+You can’t write this as a 6-digit hex; it would be a 10 digit or very large
+integer.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_b_the_half_float_vk_format_r16g16b16a16_sfloat">B. The "Half-Float" (<code>VK_FORMAT_R16G16B16A16_SFLOAT</code>)</h3>
+<div class="paragraph">
+<p>This is the "Gold Standard" for internal rendering. Each color gets 16 bits.
+This allows for values way above 1.0 and percision that prevents "banding" in
+dark shadows.</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+== Function Pointers
+
+=== Instance-Level Pointers
+
+[source, c]
+----
+vkCreateInstance(&ici,NULL,&instance);
+volkLoadInstance(instance);
+----
+
+this loads instance level pointers. there are also device specific pointers as
+well. instance level pointers handle tasks like querying physical device
+properties, creating surfaces, or picking a GPU
+
+The Overhead: when you call a fucntion like `vkQueueSubmit` using an
+instance-level pointer, the Vulkan loader has to look at the parameters,
+figure out which device you're talking to, and then jump to that specific
+drivers impoelementation. This "internal jump" happens every single time
+you call the function.
+
+
+=== Device-Level Pointers
+These function are tied to a specific `VkDevice` (a logical connection to a
+specific GPU).
+
+How you get them::
+ `volkLoadDevice(device)`
+
+What they do::
+ These are your "hot path" commands - drawing, dispatching compute
+ shaders, and memory management.
+
+The Benefit::
+ By loading device-specific pointers, you bypass the loader's lookup table.
+ You are calling the driver's function *directly*.
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Function Pointers</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_function_pointers">Function Pointers</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_instance_level_pointers">Instance-Level Pointers</h3>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-c" data-lang="c">vkCreateInstance(&ici,NULL,&instance);
+volkLoadInstance(instance);</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>this loads instance level pointers. there are also device specific pointers as
+well. instance level pointers handle tasks like querying physical device
+properties, creating surfaces, or picking a GPU</p>
+</div>
+<div class="paragraph">
+<p>The Overhead: when you call a fucntion like <code>vkQueueSubmit</code> using an
+instance-level pointer, the Vulkan loader has to look at the parameters,
+figure out which device you’re talking to, and then jump to that specific
+drivers impoelementation. This "internal jump" happens every single time
+you call the function.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_device_level_pointers">Device-Level Pointers</h3>
+<div class="paragraph">
+<p>These function are tied to a specific <code>VkDevice</code> (a logical connection to a
+specific GPU).</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">How you get them</dt>
+<dd>
+<p><code>volkLoadDevice(device)</code></p>
+</dd>
+<dt class="hdlist1">What they do</dt>
+<dd>
+<p>These are your "hot path" commands - drawing, dispatching compute
+shaders, and memory management.</p>
+</dd>
+<dt class="hdlist1">The Benefit</dt>
+<dd>
+<p>By loading device-specific pointers, you bypass the loader’s lookup table.
+You are calling the driver’s function <strong>directly</strong>.</p>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+== Queues
+
+Comamnd Buffer read up these queues
+
+The Hardware (execution Engines)::
+ Deep inside the GPU, there are ALUs that actually do math
+
+The Scheduler::
+ This is the "brain" of the GPU. It looks at all the active Queuers and
+ decides which commands get to use the ALUs right now
+
+The Queue (The buffer): this is a hardware backed ring buffer. When you call
+`vkQueueSubmit`, you are writing a pointer to your command buffer into this ring
+buffer.
+
+1. Asynchronous Execution
+ * When to submit work to a Queue, the function returns _immediately_ to
+ your CPU code. The GPU starts working on it in the background. You need
+ *Fence* or *Semaphores* to know when it's actually finished.
+
+2. In-Order Submission
+ * If you submit Command buffer A and then Command buffer B to the same
+ queue, the GPU is guarenteed to start A before it starts B. However, they
+ might finish at different times unless you use barriers.
+
+3. Cross-Queue Independence
+ * If you submit work to a Graphics Queue and a Transfer Queue at the same
+ time, they run *completely in parallel*. This is how modern games load
+ texture while maintaining 144 FPS.
+
+
+You don't "create" a queue. The queues already exists inside the device. You
+just ask for a handle to one after you've created your `VkDevice`.
+
+=== Handling one queue at a time
+VK_SHARING_MODE_EXCLUSIVE::
+ a resource is owned by *exactly one* queue family at a time.
+
+=== Handling multiple queues
+If you have different queues, you have two paths:
+
+=== Modes
+
+Path A:: `VK_SHARING_MODE_CONCURRENT`
+ * *Pros*: Easy to code. You just list both queue indices in your
+ `VkSwapchainCreateInfoKHR`. No extra barriers are needed to "hand off" the
+ image
+
+ * *Cons*: Potential performance hit. The GPU hardware might have to disable
+ certain optimizations because it has to assume both queues might touch the
+ image at any time.
+
+Path B:: `VK_SHARING_MODE_EXCLUSIVE`
+ * *Pros*: Maximum performance. The GPU can use the most aggressive memory
+ optimizations possible.
+
+ * *Cons*: You have to write the "Release/Acquire" barrier logic yourself.
+ This effectievely doubles you synchronization code for the swapchain.
+
+== Why are there "Families"
+You GPU likely has several different "conveyor belts" (Queues) grouped into
+*Families*. This is because different part of the GPU hardware are wired
+differently:
+
+Graphics Famility::
+ These conveyor belts lead to the parts of the GPU that handle the full 3D
+ pipeline. (Vertex shaders, Rasterizers, Fragmment shaders).
+
+Tansfer Family::
+ These belts are specialized. They might skip the 3D hardware entirely and go
+ straight to a *DMA (Direct Memory Access)* engine. This is much faster for
+ moving huge textures from RAM to VROM without interrupting the rendering of
+ your game.
+
+Compute Family::
+ These are for heavy math (like physics or AI) that doesn't need to be drawn
+ to screen.
+
+[%header, "1,1,1"]
+|===
+| Strategy | Performance | Implementation Difficulty
+
+| Unified Queue (Exculsize) | Best | Very Easy
+| Separate Queues (Concurrent) | Good | Easy
+| Separate Queues (Exclusive) | Great | Hard (Manual Transfers)
+|===
+
+
+== How you get a Queue in C
+
+- `VkPhysicalDevice`: The entire factory building.
+- `Queue Family`: A specific department
+- `VkQueue`: A specific conveyor belt in that department
+- `VKCommandBuffer`: The box of instructions you put on the belt.
+
+While most modern GPUs (especially on Windows/Linux) use the same queue family
+for both graphics and presentation, it is not guaranteed by the Vulkan spec.
+
+Your Code's Strategy: This is the "Easy Mode" strategy. If you find one family
+that does both, your swapchain logic is simple because the imageSharingMode will
+be VK_SHARING_MODE_EXCLUSIVE.
+
+The Alternative: On some hardware (rarely, but notably on some mobile or
+multi-GPU setups), the family that does Graphics might not be the one that can
+talk to the Surface.
+
+This loop may work for me, but it may not work for every device.
+
+[source, c]
+----
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+
+ // checking wheather a queue family of a physical device supports
+ // presentation to a given surface
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }
+----
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Queues</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_queues">Queues</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Comamnd Buffer read up these queues</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">The Hardware (execution Engines)</dt>
+<dd>
+<p>Deep inside the GPU, there are ALUs that actually do math</p>
+</dd>
+<dt class="hdlist1">The Scheduler</dt>
+<dd>
+<p>This is the "brain" of the GPU. It looks at all the active Queuers and
+decides which commands get to use the ALUs right now</p>
+</dd>
+</dl>
+</div>
+<div class="paragraph">
+<p>The Queue (The buffer): this is a hardware backed ring buffer. When you call
+<code>vkQueueSubmit</code>, you are writing a pointer to your command buffer into this ring
+buffer.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Asynchronous Execution</p>
+<div class="ulist">
+<ul>
+<li>
+<p>When to submit work to a Queue, the function returns <em>immediately</em> to
+your CPU code. The GPU starts working on it in the background. You need
+<strong>Fence</strong> or <strong>Semaphores</strong> to know when it’s actually finished.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>In-Order Submission</p>
+<div class="ulist">
+<ul>
+<li>
+<p>If you submit Command buffer A and then Command buffer B to the same
+queue, the GPU is guarenteed to start A before it starts B. However, they
+might finish at different times unless you use barriers.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Cross-Queue Independence</p>
+<div class="ulist">
+<ul>
+<li>
+<p>If you submit work to a Graphics Queue and a Transfer Queue at the same
+time, they run <strong>completely in parallel</strong>. This is how modern games load
+texture while maintaining 144 FPS.</p>
+</li>
+</ul>
+</div>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>You don’t "create" a queue. The queues already exists inside the device. You
+just ask for a handle to one after you’ve created your <code>VkDevice</code>.</p>
+</div>
+<div class="sect2">
+<h3 id="_handling_one_queue_at_a_time">Handling one queue at a time</h3>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">VK_SHARING_MODE_EXCLUSIVE</dt>
+<dd>
+<p>a resource is owned by <strong>exactly one</strong> queue family at a time.</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_handling_multiple_queues">Handling multiple queues</h3>
+<div class="paragraph">
+<p>If you have different queues, you have two paths:</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_modes">Modes</h3>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Path A</dt>
+<dd>
+<p><code>VK_SHARING_MODE_CONCURRENT</code></p>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Pros</strong>: Easy to code. You just list both queue indices in your
+<code>VkSwapchainCreateInfoKHR</code>. No extra barriers are needed to "hand off" the
+image</p>
+</li>
+<li>
+<p><strong>Cons</strong>: Potential performance hit. The GPU hardware might have to disable
+certain optimizations because it has to assume both queues might touch the
+image at any time.</p>
+</li>
+</ul>
+</div>
+</dd>
+<dt class="hdlist1">Path B</dt>
+<dd>
+<p><code>VK_SHARING_MODE_EXCLUSIVE</code></p>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Pros</strong>: Maximum performance. The GPU can use the most aggressive memory
+optimizations possible.</p>
+</li>
+<li>
+<p><strong>Cons</strong>: You have to write the "Release/Acquire" barrier logic yourself.
+This effectievely doubles you synchronization code for the swapchain.</p>
+</li>
+</ul>
+</div>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_why_are_there_families">Why are there "Families"</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>You GPU likely has several different "conveyor belts" (Queues) grouped into
+<strong>Families</strong>. This is because different part of the GPU hardware are wired
+differently:</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Graphics Famility</dt>
+<dd>
+<p>These conveyor belts lead to the parts of the GPU that handle the full 3D
+pipeline. (Vertex shaders, Rasterizers, Fragmment shaders).</p>
+</dd>
+<dt class="hdlist1">Tansfer Family</dt>
+<dd>
+<p>These belts are specialized. They might skip the 3D hardware entirely and go
+straight to a <strong>DMA (Direct Memory Access)</strong> engine. This is much faster for
+moving huge textures from RAM to VROM without interrupting the rendering of
+your game.</p>
+</dd>
+<dt class="hdlist1">Compute Family</dt>
+<dd>
+<p>These are for heavy math (like physics or AI) that doesn’t need to be drawn
+to screen.</p>
+</dd>
+</dl>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Strategy</th>
+<th class="tableblock halign-left valign-top">Performance</th>
+<th class="tableblock halign-left valign-top">Implementation Difficulty</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Unified Queue (Exculsize)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Best</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Very Easy</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Separate Queues (Concurrent)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Good</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Easy</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Separate Queues (Exclusive)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Great</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Hard (Manual Transfers)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_how_you_get_a_queue_in_c">How you get a Queue in C</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p><code>VkPhysicalDevice</code>: The entire factory building.</p>
+</li>
+<li>
+<p><code>Queue Family</code>: A specific department</p>
+</li>
+<li>
+<p><code>VkQueue</code>: A specific conveyor belt in that department</p>
+</li>
+<li>
+<p><code>VKCommandBuffer</code>: The box of instructions you put on the belt.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>While most modern GPUs (especially on Windows/Linux) use the same queue family
+for both graphics and presentation, it is not guaranteed by the Vulkan spec.</p>
+</div>
+<div class="paragraph">
+<p>Your Code’s Strategy: This is the "Easy Mode" strategy. If you find one family
+that does both, your swapchain logic is simple because the imageSharingMode will
+be VK_SHARING_MODE_EXCLUSIVE.</p>
+</div>
+<div class="paragraph">
+<p>The Alternative: On some hardware (rarely, but notably on some mobile or
+multi-GPU setups), the family that does Graphics might not be the one that can
+talk to the Surface.</p>
+</div>
+<div class="paragraph">
+<p>This loop may work for me, but it may not work for every device.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-c" data-lang="c"> int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+
+ // checking wheather a queue family of a physical device supports
+ // presentation to a given surface
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-23 09:51:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+== `VkVertexInputBindingDescription`
+
+
+=== `.inputRate`
+
+[source, c]
+----
+typedef enum VkVertexInputRate {
+ VK_VERTEX_INPUT_RATE_VERTEX = 0,
+ VK_VERTEX_INPUT_RATE_INSTANCE = 1,
+} VkVertexInputRate;
+----
+
+`inputRate` tells the GPU's *Input Assembler* how it should step through your
+vertex buffer memory as it draws.
+
+Think of it as a pointer increment rule. When the GPU moves form one "unit" of
+data to the next, does it do so after every *Instance*?
+
+`.inputRate`::
+ is a `VkVertexInputRate` value specifying whether vertex attribute
+ addressing is a function of the vertex index of the instance index.
+
+=== 1 `VK_VERTEX_INPUT_RATE_VERTEX`
+
+`VK_VERTEX_INPUT_RATE_VERTEX`::
+ specifies that vertex attribute addressing is a funciton of the instance
+ index
+
+This is the most common setting. It tells the GPU: "Every time you move to the
+next vertex in the shader, move the memory pointer forward by stride bytes."
+
+=== 1 `VK_VERTEX_INPUT_RATE_INSTANCE`
+
+`VK_VERTEX_INPUT_RATE_INSTANCE`::
+ specifies that vertex attribute addressing is a function of the instance
+ index.
+
+This is used for Instanced Rendering. It tells the GPU: "Hold this data constant
+for every vertex in the current object, and only move the memory pointer forward
+when you start drawing a brand new instance."
+
+* *Use Case*: Drawing a forest of 1,000 trees using the same vertex data, but
+ giving each tree a unique position or color.
+
+* *Behavior*: The GPU reads the data at Binding 0 for the entire first tree.
+ Only when it finishes that tree and starts the second tree does it jump
+ forward by stride bytes.
+
+== What is an Instance?
+Instance::
+ In the context of *Instancing*, and *instance* is a single "copy" of a 3D
+ model that shares the same geometry (vertices) as other copies but has its
+ own unique properties (position, color, scale).
+
+Instead of telling the GPU "Draw this tree 1,000 times" (which is 1,000 separate
+commands), you tell the GPU "Draw this tree model, but here is a list of 1,000
+positions." That list is your *Instance Data*
+
+=== 1. Real-World Examples of Instances
+
+If you can see many of the same "thing" on screen at once, it’s a candidate for
+an instance.
+
+* *Grass and Foliage*: Every blade of grass uses the same 5 or 6 vertices. The
+ "Instance Data" contains a slight rotation and a random height for each blade
+ so they don't look like perfect clones.
+
+* *Particle Systems*: Think of sparks from a fire or raindrops. Each spark is just
+ a square (2 triangles), but each instance has a different velocity and
+ lifetime.
+
+* *Strategy Game Units*: A crowd of 500 identical soldiers. The geometry is the
+ same, but the "Instance Data" might point to a specific frame of animation or
+ a team color.
+
+* *Voxel Worlds (Minecraft style)*: Every dirt block is the same cube. The
+ instance data simply stores the $(x, y, z)$ coordinate of where that block
+ sits in the world.
+
+== 2. Why do we distinguish the "Input Rate"?
+
+To make this work in your C code, you have to feed the GPU two different streams
+of data simultaneously:
+
+Stream A: The Vertex Data (INPUT_RATE_VERTEX)::
+ * Pointer moves: Every single vertex.
+ * Data: Local coordinates like $(0, 1, 0)$ for the top of a triangle.
+ * Goal: Define the shape.
+
+Stream B: The Instance Data (INPUT_RATE_INSTANCE)::
+ * Pointer moves: Only after the entire shape is finished.
+ * Data: World coordinates like "Put this copy at $(100, 5, 20)$."
+ * Goal: Define the placement.
+
+== 3. How this looks in your Shader
+
+In your GLSL/SPIR-V shader, you don't even have to do anything special to
+separate them. You just assign them different location indices that match your C
+code:
+
+[source, glsl]
+----
+layout(location = 0) in vec3 vPos; // From Binding 0 (Per-Vertex)
+layout(location = 1) in vec3 instancePos; // From Binding 1 (Per-Instance)
+
+void main() {
+ // Offset the vertex position by the instance position
+ gl_Position = vec4(vPos + instancePos, 1.0);
+}
+----
+
+== 4. The Benefit (CPU vs. GPU)
+
+The reason we do this is CPU Overhead.
+
+If you draw 1,000 trees using vkCmdDraw 1,000 times, the CPU has to "talk" to
+the GPU 1,000 times. This creates a massive bottleneck. With instancing, you
+call vkCmdDraw once, and the third parameter (instance count) is set to 1000.
+The GPU hardware takes over from there, which is much faster.
+
+[source, c]
+----
+// drawing 1000 instances in one go
+vkCmdDraw(commandBuffer, vertexCount, 1000, 0, 0);
+----
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>VkVertexInputBindingDescription</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_vkvertexinputbindingdescription"><code>VkVertexInputBindingDescription</code></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_inputrate"><code>.inputRate</code></h3>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-c" data-lang="c">typedef enum VkVertexInputRate {
+ VK_VERTEX_INPUT_RATE_VERTEX = 0,
+ VK_VERTEX_INPUT_RATE_INSTANCE = 1,
+} VkVertexInputRate;</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p><code>inputRate</code> tells the GPU’s <strong>Input Assembler</strong> how it should step through your
+vertex buffer memory as it draws.</p>
+</div>
+<div class="paragraph">
+<p>Think of it as a pointer increment rule. When the GPU moves form one "unit" of
+data to the next, does it do so after every <strong>Instance</strong>?</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1"><code>.inputRate</code></dt>
+<dd>
+<p>is a <code>VkVertexInputRate</code> value specifying whether vertex attribute
+addressing is a function of the vertex index of the instance index.</p>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_1_vk_vertex_input_rate_vertex">1 <code>VK_VERTEX_INPUT_RATE_VERTEX</code></h3>
+<div class="dlist">
+<dl>
+<dt class="hdlist1"><code>VK_VERTEX_INPUT_RATE_VERTEX</code></dt>
+<dd>
+<p>specifies that vertex attribute addressing is a funciton of the instance
+index</p>
+</dd>
+</dl>
+</div>
+<div class="paragraph">
+<p>This is the most common setting. It tells the GPU: "Every time you move to the
+next vertex in the shader, move the memory pointer forward by stride bytes."</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_1_vk_vertex_input_rate_instance">1 <code>VK_VERTEX_INPUT_RATE_INSTANCE</code></h3>
+<div class="dlist">
+<dl>
+<dt class="hdlist1"><code>VK_VERTEX_INPUT_RATE_INSTANCE</code></dt>
+<dd>
+<p>specifies that vertex attribute addressing is a function of the instance
+index.</p>
+</dd>
+</dl>
+</div>
+<div class="paragraph">
+<p>This is used for Instanced Rendering. It tells the GPU: "Hold this data constant
+for every vertex in the current object, and only move the memory pointer forward
+when you start drawing a brand new instance."</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Use Case</strong>: Drawing a forest of 1,000 trees using the same vertex data, but
+giving each tree a unique position or color.</p>
+</li>
+<li>
+<p><strong>Behavior</strong>: The GPU reads the data at Binding 0 for the entire first tree.
+Only when it finishes that tree and starts the second tree does it jump
+forward by stride bytes.</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_what_is_an_instance">What is an Instance?</h2>
+<div class="sectionbody">
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Instance</dt>
+<dd>
+<p>In the context of <strong>Instancing</strong>, and <strong>instance</strong> is a single "copy" of a 3D
+model that shares the same geometry (vertices) as other copies but has its
+own unique properties (position, color, scale).</p>
+</dd>
+</dl>
+</div>
+<div class="paragraph">
+<p>Instead of telling the GPU "Draw this tree 1,000 times" (which is 1,000 separate
+commands), you tell the GPU "Draw this tree model, but here is a list of 1,000
+positions." That list is your <strong>Instance Data</strong></p>
+</div>
+<div class="sect2">
+<h3 id="_1_real_world_examples_of_instances">1. Real-World Examples of Instances</h3>
+<div class="paragraph">
+<p>If you can see many of the same "thing" on screen at once, it’s a candidate for
+an instance.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Grass and Foliage</strong>: Every blade of grass uses the same 5 or 6 vertices. The
+"Instance Data" contains a slight rotation and a random height for each blade
+so they don’t look like perfect clones.</p>
+</li>
+<li>
+<p><strong>Particle Systems</strong>: Think of sparks from a fire or raindrops. Each spark is just
+a square (2 triangles), but each instance has a different velocity and
+lifetime.</p>
+</li>
+<li>
+<p><strong>Strategy Game Units</strong>: A crowd of 500 identical soldiers. The geometry is the
+same, but the "Instance Data" might point to a specific frame of animation or
+a team color.</p>
+</li>
+<li>
+<p><strong>Voxel Worlds (Minecraft style)</strong>: Every dirt block is the same cube. The
+instance data simply stores the $(x, y, z)$ coordinate of where that block
+sits in the world.</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_2_why_do_we_distinguish_the_input_rate">2. Why do we distinguish the "Input Rate"?</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>To make this work in your C code, you have to feed the GPU two different streams
+of data simultaneously:</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Stream A: The Vertex Data (INPUT_RATE_VERTEX)</dt>
+<dd>
+<div class="ulist">
+<ul>
+<li>
+<p>Pointer moves: Every single vertex.</p>
+</li>
+<li>
+<p>Data: Local coordinates like $(0, 1, 0)$ for the top of a triangle.</p>
+</li>
+<li>
+<p>Goal: Define the shape.</p>
+</li>
+</ul>
+</div>
+</dd>
+<dt class="hdlist1">Stream B: The Instance Data (INPUT_RATE_INSTANCE)</dt>
+<dd>
+<div class="ulist">
+<ul>
+<li>
+<p>Pointer moves: Only after the entire shape is finished.</p>
+</li>
+<li>
+<p>Data: World coordinates like "Put this copy at $(100, 5, 20)$."</p>
+</li>
+<li>
+<p>Goal: Define the placement.</p>
+</li>
+</ul>
+</div>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_3_how_this_looks_in_your_shader">3. How this looks in your Shader</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In your GLSL/SPIR-V shader, you don’t even have to do anything special to
+separate them. You just assign them different location indices that match your C
+code:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-glsl" data-lang="glsl">layout(location = 0) in vec3 vPos; // From Binding 0 (Per-Vertex)
+layout(location = 1) in vec3 instancePos; // From Binding 1 (Per-Instance)
+
+void main() {
+ // Offset the vertex position by the instance position
+ gl_Position = vec4(vPos + instancePos, 1.0);
+}</code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_4_the_benefit_cpu_vs_gpu">4. The Benefit (CPU vs. GPU)</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The reason we do this is CPU Overhead.</p>
+</div>
+<div class="paragraph">
+<p>If you draw 1,000 trees using vkCmdDraw 1,000 times, the CPU has to "talk" to
+the GPU 1,000 times. This creates a massive bottleneck. With instancing, you
+call vkCmdDraw once, and the third parameter (instance count) is set to 1000.
+The GPU hardware takes over from there, which is much faster.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="highlight"><code class="language-c" data-lang="c">// drawing 1000 instances in one go
+vkCmdDraw(commandBuffer, vertexCount, 1000, 0, 0);</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+:docinfo: shared
+:source-highlighter: pygments
+:pygments-style: monokai
+:icons: font
+:stem: latexmath
+:docinfodir: /usr/local/share/readme-to-html/
+
+
+== Swizzling (.components)
+
+In graphics programming, *Swizzling* refers to the ability to redirect or
+"re-route" color channels as the GPU reads them.
+
+Imagine your swapchain image is in BGRA format (Blue, Green, Red, Alpha), but
+your shader expects RGBA. Instead of writing code to swap the bytes manually,
+you can tell the hardware: "When I read the Red channel, actually give me the
+data from the Blue channel"
+
+=== What does `IDENTITY` do?
+
+`VK_COMPONENT_SWIZZLE_IDENTITY` is the "don't change anything" setting.
+
+[source, c]
+----
+VkImageViewCreateInfo ivInfo = {
+ .components = {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY
+ },
+}
+----
+
+== Selector (.subresourceRange)
+
+[source, c]
+----
+.subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+}
+----
+
+=== 1. Breaking down the members
+
+`.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT`::
+ This tell Vulkan what "kind" of data you are looking at.
+
+ * For a swapchain, you are loooking at colors.
+
+ * If you were creating a Depth Buffer, you would use
+ `VK_IMAGE_ASPECT_DEPTH_BIT`.
+
+ * Some formats even have `STENCIL` bits.
+
+`.baseMipLevel = 0` and `.levelCount = 1`::
+ Mip-maps are smaller, downscaled versions of your texture used for distance
+ objects.
+
+ * `basMipLevel = 0` means you start at the highest resolution (the original
+ image).
+
+ * `levelCount = 1` means you only wnat to see that one level. If you were
+ doing somehting like generating blurs or downscaling, you might increase
+ this.
+
+=== 2. Why is this necessary?
+
+Imagine you have a high-resolution texture for a brick wall that has 10 Mip-map
+levels.
+
+* You could create one VkImageView that sees all 10 levels (for the GPU to use
+ during rendering).
+
+* You could create a second VkImageView pointing to the exact same VkImage, but
+ set .baseMipLevel = 5. Now, when you use that second view, the GPU treats the
+ small 5th-level image as if it were the main texture.
+
+=== 3. The "C-Style" Memory Layout
+
+INside the GPU, the memroy is usually packed like this:
+`[Layer 0, Mip 0][Layer 0, Mip 1] ... [Layer 1, Mip 0] ...`
+
+The `subresourceRange` acts as a `bounding box` over that memory. By setting:
+
+* `levelCount = 1`
+* `layerCount = 2`
+
+...you are effectively tellling the GPU to "crop" the view to just the very
+first block of memory in that image.
+
+=== A note on `VK_REMAINING_...`
+
+In C, if you don't know how many mip levels or layers an image has but you want
+"all of them," you can use the special constants:
+
+* `VK_REMAINING_MIP_LEVELS`
+* `VK_REMAINING_ARRAY_LAYERS`
+
+== view types
+
+[cols="1,2,2", options="header"]
+|===
+| `viewType` | Description | Typical Use Case
+
+| `VK_IMAGE_VIEW_TYPE_1D`
+| A single line of pixels.
+| Lookup tables (LUTs) or 1D gradient strips.
+
+| `VK_IMAGE_VIEW_TYPE_2D`
+| A flat plane (the standard layout).
+| Standard textures, UI elements, and swapchain buffers.
+
+| `VK_IMAGE_VIEW_TYPE_3D`
+| A volume of pixels (voxels).
+| Volumetric fog, 3D noise, or medical imaging (CT/MRI).
+
+| `VK_IMAGE_VIEW_TYPE_CUBE`
+| Six 2D faces treated as a cube.
+| Skyboxes, environment maps, and omnidirectional shadows.
+
+| `VK_IMAGE_VIEW_TYPE_2D_ARRAY`
+| A stack of 2D images.
+| Texture arrays (atlases) or cascaded shadow maps.
+|===
+
+== Links
+https://docs.vulkan.org/spec/latest/chapters/resources.html#resources-image-mip-level-sizing[mip level sizing]
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Swizzling (.components)</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
+<style>
+pre.pygments .hll { background-color: #49483e }
+pre.pygments { background: #272822; color: #F8F8F2 }
+pre.pygments .tok-c { color: #959077 } /* Comment */
+pre.pygments .tok-err { color: #ED007E; background-color: #1E0010 } /* Error */
+pre.pygments .tok-esc { color: #F8F8F2 } /* Escape */
+pre.pygments .tok-g { color: #F8F8F2 } /* Generic */
+pre.pygments .tok-k { color: #66D9EF } /* Keyword */
+pre.pygments .tok-l { color: #AE81FF } /* Literal */
+pre.pygments .tok-n { color: #F8F8F2 } /* Name */
+pre.pygments .tok-o { color: #FF4689 } /* Operator */
+pre.pygments .tok-x { color: #F8F8F2 } /* Other */
+pre.pygments .tok-p { color: #F8F8F2 } /* Punctuation */
+pre.pygments .tok-ch { color: #959077 } /* Comment.Hashbang */
+pre.pygments .tok-cm { color: #959077 } /* Comment.Multiline */
+pre.pygments .tok-cp { color: #959077 } /* Comment.Preproc */
+pre.pygments .tok-cpf { color: #959077 } /* Comment.PreprocFile */
+pre.pygments .tok-c1 { color: #959077 } /* Comment.Single */
+pre.pygments .tok-cs { color: #959077 } /* Comment.Special */
+pre.pygments .tok-gd { color: #FF4689 } /* Generic.Deleted */
+pre.pygments .tok-ge { color: #F8F8F2; font-style: italic } /* Generic.Emph */
+pre.pygments .tok-ges { color: #F8F8F2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+pre.pygments .tok-gr { color: #F8F8F2 } /* Generic.Error */
+pre.pygments .tok-gh { color: #F8F8F2 } /* Generic.Heading */
+pre.pygments .tok-gi { color: #A6E22E } /* Generic.Inserted */
+pre.pygments .tok-go { color: #66D9EF } /* Generic.Output */
+pre.pygments .tok-gp { color: #FF4689; font-weight: bold } /* Generic.Prompt */
+pre.pygments .tok-gs { color: #F8F8F2; font-weight: bold } /* Generic.Strong */
+pre.pygments .tok-gu { color: #959077 } /* Generic.Subheading */
+pre.pygments .tok-gt { color: #F8F8F2 } /* Generic.Traceback */
+pre.pygments .tok-kc { color: #66D9EF } /* Keyword.Constant */
+pre.pygments .tok-kd { color: #66D9EF } /* Keyword.Declaration */
+pre.pygments .tok-kn { color: #FF4689 } /* Keyword.Namespace */
+pre.pygments .tok-kp { color: #66D9EF } /* Keyword.Pseudo */
+pre.pygments .tok-kr { color: #66D9EF } /* Keyword.Reserved */
+pre.pygments .tok-kt { color: #66D9EF } /* Keyword.Type */
+pre.pygments .tok-ld { color: #E6DB74 } /* Literal.Date */
+pre.pygments .tok-m { color: #AE81FF } /* Literal.Number */
+pre.pygments .tok-s { color: #E6DB74 } /* Literal.String */
+pre.pygments .tok-na { color: #A6E22E } /* Name.Attribute */
+pre.pygments .tok-nb { color: #F8F8F2 } /* Name.Builtin */
+pre.pygments .tok-nc { color: #A6E22E } /* Name.Class */
+pre.pygments .tok-no { color: #66D9EF } /* Name.Constant */
+pre.pygments .tok-nd { color: #A6E22E } /* Name.Decorator */
+pre.pygments .tok-ni { color: #F8F8F2 } /* Name.Entity */
+pre.pygments .tok-ne { color: #A6E22E } /* Name.Exception */
+pre.pygments .tok-nf { color: #A6E22E } /* Name.Function */
+pre.pygments .tok-nl { color: #F8F8F2 } /* Name.Label */
+pre.pygments .tok-nn { color: #F8F8F2 } /* Name.Namespace */
+pre.pygments .tok-nx { color: #A6E22E } /* Name.Other */
+pre.pygments .tok-py { color: #F8F8F2 } /* Name.Property */
+pre.pygments .tok-nt { color: #FF4689 } /* Name.Tag */
+pre.pygments .tok-nv { color: #F8F8F2 } /* Name.Variable */
+pre.pygments .tok-ow { color: #FF4689 } /* Operator.Word */
+pre.pygments .tok-pm { color: #F8F8F2 } /* Punctuation.Marker */
+pre.pygments .tok-w { color: #F8F8F2 } /* Text.Whitespace */
+pre.pygments .tok-mb { color: #AE81FF } /* Literal.Number.Bin */
+pre.pygments .tok-mf { color: #AE81FF } /* Literal.Number.Float */
+pre.pygments .tok-mh { color: #AE81FF } /* Literal.Number.Hex */
+pre.pygments .tok-mi { color: #AE81FF } /* Literal.Number.Integer */
+pre.pygments .tok-mo { color: #AE81FF } /* Literal.Number.Oct */
+pre.pygments .tok-sa { color: #E6DB74 } /* Literal.String.Affix */
+pre.pygments .tok-sb { color: #E6DB74 } /* Literal.String.Backtick */
+pre.pygments .tok-sc { color: #E6DB74 } /* Literal.String.Char */
+pre.pygments .tok-dl { color: #E6DB74 } /* Literal.String.Delimiter */
+pre.pygments .tok-sd { color: #E6DB74 } /* Literal.String.Doc */
+pre.pygments .tok-s2 { color: #E6DB74 } /* Literal.String.Double */
+pre.pygments .tok-se { color: #AE81FF } /* Literal.String.Escape */
+pre.pygments .tok-sh { color: #E6DB74 } /* Literal.String.Heredoc */
+pre.pygments .tok-si { color: #E6DB74 } /* Literal.String.Interpol */
+pre.pygments .tok-sx { color: #E6DB74 } /* Literal.String.Other */
+pre.pygments .tok-sr { color: #E6DB74 } /* Literal.String.Regex */
+pre.pygments .tok-s1 { color: #E6DB74 } /* Literal.String.Single */
+pre.pygments .tok-ss { color: #E6DB74 } /* Literal.String.Symbol */
+pre.pygments .tok-bp { color: #F8F8F2 } /* Name.Builtin.Pseudo */
+pre.pygments .tok-fm { color: #A6E22E } /* Name.Function.Magic */
+pre.pygments .tok-vc { color: #F8F8F2 } /* Name.Variable.Class */
+pre.pygments .tok-vg { color: #F8F8F2 } /* Name.Variable.Global */
+pre.pygments .tok-vi { color: #F8F8F2 } /* Name.Variable.Instance */
+pre.pygments .tok-vm { color: #F8F8F2 } /* Name.Variable.Magic */
+pre.pygments .tok-il { color: #AE81FF } /* Literal.Number.Integer.Long */
+</style>
+<head>
+<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=DotGothic16"> -->
+<!-- <link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/A-Larsen/math-notes/refs/heads/main/favicon.png"> -->
+<!-- <link rel="icon" type="image/png" href="N2DE_favicon.png"/> -->
+<!-- <link rel="preconnect" href="https://fonts.googleapis.com"> -->
+<!-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
+<!-- <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap" rel="stylesheet"> -->
+<style>
+
+/*--------------------------------------------------------------*/
+/*use '!important' to override default css used by asciidoctor*/
+/*--------------------------------------------------------------*/
+
+:root {
+ --gruvbox-black: #282828;
+ --gruvbox-black2: #1d2021;
+ --gruvbox-black3: #463f39;
+ --gruvbox-red: #fc7869;
+ --gruvbox-yellow: #fabd2f;
+ --gruvbox-white: #ebdbb2;
+ --gruvbox-white2: #a7a187;
+ --gruvbox-blue: #83a598;
+ --gruvbox-green: #b8bb26;
+ --gruvbox-bg1: #7c6f64;
+ --gruvbox-bg2: #665c54;
+
+}
+
+.title {
+ color: var(--gruvbox-red) !important;
+}
+
+blockquote {
+ color: var(--gruvbox-white) !important;
+}
+
+table {
+ margin: 0 auto !important;
+}
+
+.hdlist1 {
+ font-size: 1.3em;
+}
+
+cite {
+ color: var(--gruvbox-green) !important;
+}
+
+.tableblock {
+ background-color: inherit;
+ color: var(--gruvbox-white);
+ max-width: 95%;
+}
+
+.tableblock th {
+ background-color: var(--gruvbox-black3);
+ color: var(--gruvbox-white);
+ max-width: 97%;
+}
+
+code:not([data-lang]) {
+ background-color: var(--gruvbox-bg2) !important;
+ color: var(--gruvbox-white);
+ text-shadow: none;
+}
+
+.checklist {
+ text-align: left;
+ font-size: .8em;
+}
+
+.checklist li ul { margin-left: 3%;
+}
+
+#content, h1, pre {
+ border-radius: 8px !important;
+}
+
+#header, #content, #footnotes, #footer {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.api-type a:hover {
+ text-decoration: underline;
+}
+
+.api-type, .api-type a {
+ color: var(--gruvbox-green);
+ text-decoration: none;
+}
+
+pre {
+ border-style: solid !important;
+ border-color: white !important;
+ border-width: 2px !important;
+}
+
+h1 {
+ /* font-family: "DotGothic16", sans-serif !important;*/
+
+
+ background-color: var(--gruvbox-white2) !important;
+ color: var(--gruvbox-black) !important;
+}
+
+.admonitionblock > table td.content {
+ color: var(--gruvbox-white) !important;
+}
+
+/*
+html {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+}
+
+
+*/
+/*html {*/
+
+body {
+ /*background: black url("background-image-space.jpeg") no-repeat fixed center;*/
+ /*background-color: black;*/
+ background-color: var(--gruvbox-black);
+ background-attachment: fixed;
+ background-size: cover;
+ overflow-y: scroll;
+ /*
+ margin: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ */
+}
+
+
+/*
+body {
+ overflow-y: scroll;
+ background: rgba(0,0,0,0);
+ height: 100%;
+ margin: 2%;
+}
+*/
+
+#toc {
+ background-color: var(--gruvbox-black2) !important;
+ /*opacity: .9;*/
+}
+
+#toctitle {
+ color: var(--gruvbox-yellow);
+}
+
+a {
+ color: var(--gruvbox-bg1);
+}
+
+#content {
+ color: #ebdbb2;
+ background: var(--gruvbox-black);
+ /*opacity: .9;*/
+}
+
+#header > h1:nth-last-child(2) {
+ border-bottom: none !important;
+ background-color: rgba(0, 0, 0, .1);
+ padding-left: 2%;
+}
+
+.sect1 + .sect1 {
+ border-top: none !important;
+}
+
+.sect1 h2 {
+ background-size: cover;
+ background-attachment: fixed;
+ /*
+ border-bottom: 1px solid var(--gruvbox-white);
+ border-top: 1px solid var(--gruvbox-white);
+ */
+ padding: 10px;
+ text-align: left;
+ color: var(--gruvbox-red) !important;
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-black);
+ text-shadow: 3px 3px 3px var(--gruvbox-black);
+ text-shadow: 5px 5px 5px var(--gruvbox-blue);
+ */
+}
+
+h3, h4, h5 {
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-white);
+ */
+}
+
+h3 {
+ color: var(--gruvbox-red) !important;
+}
+
+h4 {
+ color: var(--gruvbox-yellow) !important;
+}
+
+h5 {
+ color: #83a598 !important;
+}
+
+h2 ~ *:not(h1) {
+ margin-left: .5em;
+}
+
+.pygments {
+ max-width: 97% !important;
+ white-space: pre;
+ overflow-x: scroll !important;
+}
+
+::before {
+ /*makes sure that the programming language that show up on hover is within
+ pre tag*/
+ padding-right: 3%;
+}
+
+@media screen and (max-width: 800px) {
+ .checklist li ul {
+ margin-left: 6% !important;
+ }
+ #toc {
+ padding-left: 10px;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+@media screen and (max-width: 535px) {
+ h3 ~ *:not(h2):not(h3) {
+ margin-left: 0 !important;
+ }
+
+ h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 0 !important;
+ }
+ h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 0 !important;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+h3 ~ *:not(h2):not(h3) {
+ margin-left: 1.5em;
+}
+
+h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 2em;
+}
+h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 2.5em;
+}
+
+
+.sect1 h2 {
+
+ /*
+ background: linear-gradient(
+ to bottom,
+ rgba(251, 189, 47, 0.2), /* yellow */
+ rgba(0, 0, 0, 0.4) /* black */
+ );
+ */
+ /*opacity: .9;*/
+ }
+
+#preamble p{
+ padding-left: 10px;
+ color: var(--gruvbox-white) !important;
+}
+
+#footer {
+ max-width: 62.5em !important;
+ padding-left: 10px !important;
+ text-align: center;
+}
+
+.logo img {
+ display: block !important;
+ margin-left: auto !important;
+ margin-right: auto !important;
+}
+
+#header > h1:first-child {
+ margin-top: .3em !important;
+ padding-top: .2em !important;
+ text-align: center;
+}
+
+footer {
+ max-width: 62.5em;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+
+}
+
+details[open] > summary::before {
+ /* display: none;*/
+ border-top: none;
+ content: "▼";
+ margin-top: -12px;
+}
+
+.example.stemblock {
+ border: solid;
+ font-size: 2vw;
+ max-width: 40%;
+ margin: auto;
+}
+
+.center {
+ max-width: 500px;
+ display: flex;
+ justify-content: center;
+}
+
+.highlight {
+ color: var(--gruvbox-yellow)
+}
+
+.border {
+ padding: 4px;
+ border: 2px solid rgba(255, 0, 0, .3);
+}
+
+.dull {
+ opacity: 40%;
+}
+
+.inline {
+ margin-right: 5%;
+ display: inline-block;
+}
+
+img {
+ margin: auto;
+ width: 100%;
+}
+
+
+</style>
+
+</head>
+
+<script>
+function sendHeight() {
+ const height = document.documentElement.scrollHeight;
+ parent.postMessage({ type: "HEIGHT", height }, "*");
+}
+
+window.addEventListener("load", sendHeight);
+window.addEventListener("resize", sendHeight);
+</script>
+
+
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_swizzling_components">Swizzling (.components)</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In graphics programming, <strong>Swizzling</strong> refers to the ability to redirect or
+"re-route" color channels as the GPU reads them.</p>
+</div>
+<div class="paragraph">
+<p>Imagine your swapchain image is in BGRA format (Blue, Green, Red, Alpha), but
+your shader expects RGBA. Instead of writing code to swap the bytes manually,
+you can tell the hardware: "When I read the Red channel, actually give me the
+data from the Blue channel"</p>
+</div>
+<div class="sect2">
+<h3 id="_what_does_identity_do">What does <code>IDENTITY</code> do?</h3>
+<div class="paragraph">
+<p><code>VK_COMPONENT_SWIZZLE_IDENTITY</code> is the "don’t change anything" setting.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-n">VkImageViewCreateInfo</span><span class="tok-w"> </span><span class="tok-n">ivInfo</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">components</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">r</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_COMPONENT_SWIZZLE_IDENTITY</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">g</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_COMPONENT_SWIZZLE_IDENTITY</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">b</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_COMPONENT_SWIZZLE_IDENTITY</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">a</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_COMPONENT_SWIZZLE_IDENTITY</span>
+<span class="tok-w"> </span><span class="tok-p">},</span>
+<span class="tok-p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_selector_subresourcerange">Selector (.subresourceRange)</h2>
+<div class="sectionbody">
+<div class="listingblock">
+<div class="content">
+<pre class="pygments highlight"><code data-lang="c"><span></span><span class="tok-p">.</span><span class="tok-n">subresourceRange</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-p">{</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">aspectMask</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-n">VK_IMAGE_ASPECT_COLOR_BIT</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">baseMipLevel</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">0</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">levelCount</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">1</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">baseArrayLayer</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">0</span><span class="tok-p">,</span>
+<span class="tok-w"> </span><span class="tok-p">.</span><span class="tok-n">layerCount</span><span class="tok-w"> </span><span class="tok-o">=</span><span class="tok-w"> </span><span class="tok-mi">1</span>
+<span class="tok-p">}</span></code></pre>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_1_breaking_down_the_members">1. Breaking down the members</h3>
+<div class="dlist">
+<dl>
+<dt class="hdlist1"><code>.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT</code></dt>
+<dd>
+<p>This tell Vulkan what "kind" of data you are looking at.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>For a swapchain, you are loooking at colors.</p>
+</li>
+<li>
+<p>If you were creating a Depth Buffer, you would use
+<code>VK_IMAGE_ASPECT_DEPTH_BIT</code>.</p>
+</li>
+<li>
+<p>Some formats even have <code>STENCIL</code> bits.</p>
+</li>
+</ul>
+</div>
+</dd>
+<dt class="hdlist1"><code>.baseMipLevel = 0</code> and <code>.levelCount = 1</code></dt>
+<dd>
+<p>Mip-maps are smaller, downscaled versions of your texture used for distance
+objects.</p>
+<div class="ulist">
+<ul>
+<li>
+<p><code>basMipLevel = 0</code> means you start at the highest resolution (the original
+image).</p>
+</li>
+<li>
+<p><code>levelCount = 1</code> means you only wnat to see that one level. If you were
+doing somehting like generating blurs or downscaling, you might increase
+this.</p>
+</li>
+</ul>
+</div>
+</dd>
+</dl>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_2_why_is_this_necessary">2. Why is this necessary?</h3>
+<div class="paragraph">
+<p>Imagine you have a high-resolution texture for a brick wall that has 10 Mip-map
+levels.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>You could create one VkImageView that sees all 10 levels (for the GPU to use
+during rendering).</p>
+</li>
+<li>
+<p>You could create a second VkImageView pointing to the exact same VkImage, but
+set .baseMipLevel = 5. Now, when you use that second view, the GPU treats the
+small 5th-level image as if it were the main texture.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_3_the_c_style_memory_layout">3. The "C-Style" Memory Layout</h3>
+<div class="paragraph">
+<p>INside the GPU, the memroy is usually packed like this:
+<code>[Layer 0, Mip 0][Layer 0, Mip 1] …​ [Layer 1, Mip 0] …​</code></p>
+</div>
+<div class="paragraph">
+<p>The <code>subresourceRange</code> acts as a <code>bounding box</code> over that memory. By setting:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>levelCount = 1</code></p>
+</li>
+<li>
+<p><code>layerCount = 2</code></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>…​you are effectively tellling the GPU to "crop" the view to just the very
+first block of memory in that image.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_a_note_on_vk_remaining">A note on <code>VK_REMAINING_…​</code></h3>
+<div class="paragraph">
+<p>In C, if you don’t know how many mip levels or layers an image has but you want
+"all of them," you can use the special constants:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>VK_REMAINING_MIP_LEVELS</code></p>
+</li>
+<li>
+<p><code>VK_REMAINING_ARRAY_LAYERS</code></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_view_types">view types</h2>
+<div class="sectionbody">
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 20%;">
+<col style="width: 40%;">
+<col style="width: 40%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top"><code>viewType</code></th>
+<th class="tableblock halign-left valign-top">Description</th>
+<th class="tableblock halign-left valign-top">Typical Use Case</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VK_IMAGE_VIEW_TYPE_1D</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A single line of pixels.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Lookup tables (LUTs) or 1D gradient strips.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VK_IMAGE_VIEW_TYPE_2D</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A flat plane (the standard layout).</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Standard textures, UI elements, and swapchain buffers.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VK_IMAGE_VIEW_TYPE_3D</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A volume of pixels (voxels).</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Volumetric fog, 3D noise, or medical imaging (CT/MRI).</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VK_IMAGE_VIEW_TYPE_CUBE</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Six 2D faces treated as a cube.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Skyboxes, environment maps, and omnidirectional shadows.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VK_IMAGE_VIEW_TYPE_2D_ARRAY</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A stack of 2D images.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Texture arrays (atlases) or cascaded shadow maps.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_links">Links</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p><a href="https://docs.vulkan.org/spec/latest/chapters/resources.html#resources-image-mip-level-sizing">mip level sizing</a></p>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-23 20:22:04 -0500
+</div>
+</div>
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+ messageStyle: "none",
+ tex2jax: {
+ inlineMath: [["\\(", "\\)"]],
+ displayMath: [["\\[", "\\]"]],
+ ignoreClass: "nostem|nolatexmath"
+ },
+ asciimath2jax: {
+ delimiters: [["\\$", "\\$"]],
+ ignoreClass: "nostem|noasciimath"
+ },
+ TeX: { equationNumbers: { autoNumber: "none" } }
+})
+MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
+ MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
+ data.math.root.display = "block"
+ }
+ return data
+ })
+})
+</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+Primitives::
+ Basic geometric shapes or objects, such as points, lines, and polygons, that
+ can be combined to create more complex images or models.
+
+Curves:: including splines, are controlled by sets of control points. Bézier
+curves, for instance, are defined by n+1 control points, where the first and
+last are endpoints and intermediate points influence the curve's shape without
+necessarily lying on it. The quadratic Bézier curve is mathematically
+represented as B(t) = (1–t)²P₀ + 2(1–t)tP₁ + t²P₂, with t in [0,1]. 7 More
+complex curves such as Hermite cubic splines and B-splines use control points
+and tangent vectors or basis functions to define their shapes. 6
+
+== Links
+* https://www.sciencedirect.com/topics/computer-science/graphic-primitive?__cf_chl_tk=FTw0yQ2WO9NCPpyl.Se2NvBdTKvaqSW5FD84FFx3KmI-1771529858-1.0.1.1-5_Y_c4oXgS.jr3uZdwR9TgJDOu202mwxoB3YFjmdXnM
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Links</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+</head>
+<body class="article">
+<div id="header">
+</div>
+<div id="content">
+<div class="dlist">
+<dl>
+<dt class="hdlist1">Primitives</dt>
+<dd>
+<p>Basic geometric shapes or objects, such as points, lines, and polygons, that
+can be combined to create more complex images or models.</p>
+</dd>
+<dt class="hdlist1">Curves</dt>
+<dd>
+<p>including splines, are controlled by sets of control points. Bézier
+curves, for instance, are defined by n+1 control points, where the first and
+last are endpoints and intermediate points influence the curve’s shape without
+necessarily lying on it. The quadratic Bézier curve is mathematically
+represented as B(t) = (1–t)²P₀ + 2(1–t)tP₁ + t²P₂, with t in [0,1]. 7 More
+complex curves such as Hermite cubic splines and B-splines use control points
+and tangent vectors or basis functions to define their shapes. 6</p>
+</dd>
+</dl>
+</div>
+<div class="sect1">
+<h2 id="_links">Links</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p><a href="https://www.sciencedirect.com/topics/computer-science/graphic-primitive?__cf_chl_tk=FTw0yQ2WO9NCPpyl.Se2NvBdTKvaqSW5FD84FFx3KmI-1771529858-1.0.1.1-5_Y_c4oXgS.jr3uZdwR9TgJDOu202mwxoB3YFjmdXnM" class="bare">https://www.sciencedirect.com/topics/computer-science/graphic-primitive?__cf_chl_tk=FTw0yQ2WO9NCPpyl.Se2NvBdTKvaqSW5FD84FFx3KmI-1771529858-1.0.1.1-5_Y_c4oXgS.jr3uZdwR9TgJDOu202mwxoB3YFjmdXnM</a></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-19 14:39:29 -0500
+</div>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+= Mip Level
+:docinfo: shared
+:docinfodir: /usr/local/share/readme-to-html/
+:stem: latexmath
+
+== How the GPU chooses a Mip level
+
+The GPU doesn't just look at "distance" as a raw number. It calculates the
+*Texture Coordinate Gradient*
+
+As your C code executes a draw call, the GPU hardware looks at how fast the
+texture coordinate (u,v) are changing relative to the screen pixels (x,y).
+
+* *Close up*: One screen pixel covers a tiny fraction of a texel. The GPU uses
+ *Mip 0* (full resolution)
+
+* *Far away*: One screen pixel covers 50 or 100 texels. The GPU realizes the
+ "footprint" is huge and switches to *Mip 3 or 4* (Lower resolution).
+
+== 2. Why this matters in Vulkan (The C perspective)
+
+In your C application, mipmaps aren't "free". You have to plan for them during
+the image creation phase:
+
+=== A. Memory Allocation
+
+When you create a `VKImage`, you specify `mipLevels`. If you set this to `1`,
+you only have the full-size image. If you want a full "chain," you calculate it
+as:
+
+[stem]
+++++
+levels = [\log_{2}(\max(width,heigth))] + 1
+++++
+
+because this is an inverse of `2^x` is is shrinking by half as small as the
+prevoius value
+
+== 3. Distance vs Perspective (The "Moire Effect")
+
+Even if an object is "close", if it is at a sharp angle (like a floor plane
+stretching into the distance), the GPU will use different mip levels for
+different parts of the same triangle,triangle.
+
+* *Trilinear Filtering*: Smooths the transition between Mip 1 and Mip 2 so you
+ don't see a "line" on the floor where the texture suddenly gets blurry.
+
+* *Aniscotrpic Filtering*: A more advanced version that handles cases where the
+ texture is squashed in one direction but not the other (very common for floors
+ and walls).
+
+== 4. Does the swapchain use Mip Levels?
+
+*Almost never*.
+
+Because the swapchain images are designed to match you window's resolution 1:1,
+there is no "distance" involved. You are drawing directly to the screen pixels.
+This is why in your `subresourceRange`, you set `.levelCount = 1` and
+`.baseMipLevel = 0`.
+
+You only really care about mip levels for *textures* (bricks, wood, character
+skins) that you load from files and draw _onto_ your geometery.
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Mip Level</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<head>
+<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=DotGothic16"> -->
+<!-- <link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/A-Larsen/math-notes/refs/heads/main/favicon.png"> -->
+<!-- <link rel="icon" type="image/png" href="N2DE_favicon.png"/> -->
+<!-- <link rel="preconnect" href="https://fonts.googleapis.com"> -->
+<!-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
+<!-- <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap" rel="stylesheet"> -->
+<style>
+
+/*--------------------------------------------------------------*/
+/*use '!important' to override default css used by asciidoctor*/
+/*--------------------------------------------------------------*/
+
+:root {
+ --gruvbox-black: #282828;
+ --gruvbox-black2: #1d2021;
+ --gruvbox-black3: #463f39;
+ --gruvbox-red: #fc7869;
+ --gruvbox-yellow: #fabd2f;
+ --gruvbox-white: #ebdbb2;
+ --gruvbox-white2: #a7a187;
+ --gruvbox-blue: #83a598;
+ --gruvbox-green: #b8bb26;
+ --gruvbox-bg1: #7c6f64;
+ --gruvbox-bg2: #665c54;
+
+}
+
+.title {
+ color: var(--gruvbox-red) !important;
+}
+
+blockquote {
+ color: var(--gruvbox-white) !important;
+}
+
+table {
+ margin: 0 auto !important;
+}
+
+.hdlist1 {
+ font-size: 1.3em;
+}
+
+cite {
+ color: var(--gruvbox-green) !important;
+}
+
+.tableblock {
+ background-color: inherit;
+ color: var(--gruvbox-white);
+ max-width: 95%;
+}
+
+.tableblock th {
+ background-color: var(--gruvbox-black3);
+ color: var(--gruvbox-white);
+ max-width: 97%;
+}
+
+code:not([data-lang]) {
+ background-color: var(--gruvbox-bg2) !important;
+ color: var(--gruvbox-white);
+ text-shadow: none;
+}
+
+.checklist {
+ text-align: left;
+ font-size: .8em;
+}
+
+.checklist li ul { margin-left: 3%;
+}
+
+#content, h1, pre {
+ border-radius: 8px !important;
+}
+
+#header, #content, #footnotes, #footer {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.api-type a:hover {
+ text-decoration: underline;
+}
+
+.api-type, .api-type a {
+ color: var(--gruvbox-green);
+ text-decoration: none;
+}
+
+pre {
+ border-style: solid !important;
+ border-color: white !important;
+ border-width: 2px !important;
+}
+
+h1 {
+ /* font-family: "DotGothic16", sans-serif !important;*/
+
+
+ background-color: var(--gruvbox-white2) !important;
+ color: var(--gruvbox-black) !important;
+}
+
+.admonitionblock > table td.content {
+ color: var(--gruvbox-white) !important;
+}
+
+/*
+html {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+}
+
+
+*/
+/*html {*/
+
+body {
+ /*background: black url("background-image-space.jpeg") no-repeat fixed center;*/
+ /*background-color: black;*/
+ background-color: var(--gruvbox-black);
+ background-attachment: fixed;
+ background-size: cover;
+ overflow-y: scroll;
+ /*
+ margin: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ */
+}
+
+
+/*
+body {
+ overflow-y: scroll;
+ background: rgba(0,0,0,0);
+ height: 100%;
+ margin: 2%;
+}
+*/
+
+#toc {
+ background-color: var(--gruvbox-black2) !important;
+ /*opacity: .9;*/
+}
+
+#toctitle {
+ color: var(--gruvbox-yellow);
+}
+
+a {
+ color: var(--gruvbox-bg1);
+}
+
+#content {
+ color: #ebdbb2;
+ background: var(--gruvbox-black);
+ /*opacity: .9;*/
+}
+
+#header > h1:nth-last-child(2) {
+ border-bottom: none !important;
+ background-color: rgba(0, 0, 0, .1);
+ padding-left: 2%;
+}
+
+.sect1 + .sect1 {
+ border-top: none !important;
+}
+
+.sect1 h2 {
+ background-size: cover;
+ background-attachment: fixed;
+ /*
+ border-bottom: 1px solid var(--gruvbox-white);
+ border-top: 1px solid var(--gruvbox-white);
+ */
+ padding: 10px;
+ text-align: left;
+ color: var(--gruvbox-red) !important;
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-black);
+ text-shadow: 3px 3px 3px var(--gruvbox-black);
+ text-shadow: 5px 5px 5px var(--gruvbox-blue);
+ */
+}
+
+h3, h4, h5 {
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-white);
+ */
+}
+
+h3 {
+ color: var(--gruvbox-red) !important;
+}
+
+h4 {
+ color: var(--gruvbox-yellow) !important;
+}
+
+h5 {
+ color: #83a598 !important;
+}
+
+h2 ~ *:not(h1) {
+ margin-left: .5em;
+}
+
+.pygments {
+ max-width: 97% !important;
+ white-space: pre;
+ overflow-x: scroll !important;
+}
+
+::before {
+ /*makes sure that the programming language that show up on hover is within
+ pre tag*/
+ padding-right: 3%;
+}
+
+@media screen and (max-width: 800px) {
+ .checklist li ul {
+ margin-left: 6% !important;
+ }
+ #toc {
+ padding-left: 10px;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+@media screen and (max-width: 535px) {
+ h3 ~ *:not(h2):not(h3) {
+ margin-left: 0 !important;
+ }
+
+ h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 0 !important;
+ }
+ h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 0 !important;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+h3 ~ *:not(h2):not(h3) {
+ margin-left: 1.5em;
+}
+
+h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 2em;
+}
+h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 2.5em;
+}
+
+
+.sect1 h2 {
+
+ /*
+ background: linear-gradient(
+ to bottom,
+ rgba(251, 189, 47, 0.2), /* yellow */
+ rgba(0, 0, 0, 0.4) /* black */
+ );
+ */
+ /*opacity: .9;*/
+ }
+
+#preamble p{
+ padding-left: 10px;
+ color: var(--gruvbox-white) !important;
+}
+
+#footer {
+ max-width: 62.5em !important;
+ padding-left: 10px !important;
+ text-align: center;
+}
+
+.logo img {
+ display: block !important;
+ margin-left: auto !important;
+ margin-right: auto !important;
+}
+
+#header > h1:first-child {
+ margin-top: .3em !important;
+ padding-top: .2em !important;
+ text-align: center;
+}
+
+footer {
+ max-width: 62.5em;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+
+}
+
+details[open] > summary::before {
+ /* display: none;*/
+ border-top: none;
+ content: "▼";
+ margin-top: -12px;
+}
+
+.example.stemblock {
+ border: solid;
+ font-size: 2vw;
+ max-width: 40%;
+ margin: auto;
+}
+
+.center {
+ max-width: 500px;
+ display: flex;
+ justify-content: center;
+}
+
+.highlight {
+ color: var(--gruvbox-yellow)
+}
+
+.border {
+ padding: 4px;
+ border: 2px solid rgba(255, 0, 0, .3);
+}
+
+.dull {
+ opacity: 40%;
+}
+
+.inline {
+ margin-right: 5%;
+ display: inline-block;
+}
+
+img {
+ margin: auto;
+ width: 100%;
+}
+
+
+</style>
+
+</head>
+
+<script>
+function sendHeight() {
+ const height = document.documentElement.scrollHeight;
+ parent.postMessage({ type: "HEIGHT", height }, "*");
+}
+
+window.addEventListener("load", sendHeight);
+window.addEventListener("resize", sendHeight);
+</script>
+
+
+</head>
+<body class="article">
+<div id="header">
+<h1>Mip Level</h1>
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_how_the_gpu_chooses_a_mip_level">How the GPU chooses a Mip level</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The GPU doesn’t just look at "distance" as a raw number. It calculates the
+<strong>Texture Coordinate Gradient</strong></p>
+</div>
+<div class="paragraph">
+<p>As your C code executes a draw call, the GPU hardware looks at how fast the
+texture coordinate (u,v) are changing relative to the screen pixels (x,y).</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Close up</strong>: One screen pixel covers a tiny fraction of a texel. The GPU uses
+<strong>Mip 0</strong> (full resolution)</p>
+</li>
+<li>
+<p><strong>Far away</strong>: One screen pixel covers 50 or 100 texels. The GPU realizes the
+"footprint" is huge and switches to <strong>Mip 3 or 4</strong> (Lower resolution).</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_2_why_this_matters_in_vulkan_the_c_perspective">2. Why this matters in Vulkan (The C perspective)</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>In your C application, mipmaps aren’t "free". You have to plan for them during
+the image creation phase:</p>
+</div>
+<div class="sect2">
+<h3 id="_a_memory_allocation">A. Memory Allocation</h3>
+<div class="paragraph">
+<p>When you create a <code>VKImage</code>, you specify <code>mipLevels</code>. If you set this to <code>1</code>,
+you only have the full-size image. If you want a full "chain," you calculate it
+as:</p>
+</div>
+<div class="stemblock">
+<div class="content">
+\[levels = [\log_{2}(\max(width,heigth))] + 1\]
+</div>
+</div>
+<div class="paragraph">
+<p>because this is an inverse of <code>2^x</code> is is shrinking by half as small as the
+prevoius value</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_3_distance_vs_perspective_the_moire_effect">3. Distance vs Perspective (The "Moire Effect")</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Even if an object is "close", if it is at a sharp angle (like a floor plane
+stretching into the distance), the GPU will use different mip levels for
+different parts of the same triangle,triangle.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Trilinear Filtering</strong>: Smooths the transition between Mip 1 and Mip 2 so you
+don’t see a "line" on the floor where the texture suddenly gets blurry.</p>
+</li>
+<li>
+<p><strong>Aniscotrpic Filtering</strong>: A more advanced version that handles cases where the
+texture is squashed in one direction but not the other (very common for floors
+and walls).</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_4_does_the_swapchain_use_mip_levels">4. Does the swapchain use Mip Levels?</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p><strong>Almost never</strong>.</p>
+</div>
+<div class="paragraph">
+<p>Because the swapchain images are designed to match you window’s resolution 1:1,
+there is no "distance" involved. You are drawing directly to the screen pixels.
+This is why in your <code>subresourceRange</code>, you set <code>.levelCount = 1</code> and
+<code>.baseMipLevel = 0</code>.</p>
+</div>
+<div class="paragraph">
+<p>You only really care about mip levels for <strong>textures</strong> (bricks, wood, character
+skins) that you load from files and draw <em>onto</em> your geometery.</p>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-18 17:12:30 -0500
+</div>
+</div>
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+ messageStyle: "none",
+ tex2jax: {
+ inlineMath: [["\\(", "\\)"]],
+ displayMath: [["\\[", "\\]"]],
+ ignoreClass: "nostem|nolatexmath"
+ },
+ asciimath2jax: {
+ delimiters: [["\\$", "\\$"]],
+ ignoreClass: "nostem|noasciimath"
+ },
+ TeX: { equationNumbers: { autoNumber: "none" } }
+})
+MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
+ MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
+ data.math.root.display = "block"
+ }
+ return data
+ })
+})
+</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+:docinfo: shared
+:source-highlighter: pygments
+:pygments-style: monokai
+:icons: font
+:stem:
+:docinfodir: /usr/local/share/asciidoctor-styles/dark1/
+
+= Swapchain
+
+== Summary
+
+Without a swapchain, you could render frames all day long, but they would just
+sit in your GPU's memory. The swapcahin is essectially a *buffer queue* that
+manages how "finished" images are handed off to the operating system's window
+manager.
+
+== 1. The "Double or Tripple Buffering" Concept
+
+Think of the swapchain as a series of *frames* (usually 2 or 3). While the
+monitor is busy showing Frame A, your GPU is busy drawing into Frame B. When the
+GPU is done with B, they "swap" roles.
+
+The Front Buffer::
+ The Image currently being scanned and displayed by your monitor
+
+The Back Buffer::
+ The images the GPU is currently "painting" on.
+
+== 2. Key Components of the Swapchain
+
+When you create a swapcain in C, you have to negotiate several settings with the
+hardware:
+
+=== A. Image Count
+
+Most modern applications use *Tripple Buffering* (3 images). This allows the GPU
+to keep working on a third frame even if the monitor hasn't finished display the
+first one, preventing the GPU from idiling.
+
+Images have the type `VKImage`
+
+Images will be drawn to the window surface (`VkSurfaceKHR`).
+
+=== B. Capabilities
+
+We'll check the surfaces capabilities with
+`vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps)`. This will
+tell the use the extent (width and height of image) of the surface, maximum
+number of images excepted and the minimum number of images excepted for our
+device surface.
+
+
+=== C. Surface Format
+
+we can check the surface format with
+
+`vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats)`
+
+This defines the color space and bit depth. Usually, you'll look for:
+
+* `VK_FORMAT_B8G8R8A8_SRGB` (Standard 32-bit color)
+* `VK_COLOR_SPACE_SRGB_NONLINEAR_KHR`
+
+
+=== D. Present Mode
+
+This determines how the "swap" actually happens. It's essentially how you handle
+*V-Sync*.
+
+`VK_PRESENT_MODE_IMMEDIATE_KHR`::
+ Swap as soon as the GPU is done. (Causes screen tearing).
+
+`VK_PRESENT_MODE_FIFO_KHR`::
+ Wait for the monitor's refresh (V-Sync). (Prevents tearing, may add
+ input add input lag).
+
+`VK_PRESENT_MODE_MAILBOX_KHR`::
+ The "Gold Standard". It replaces the oldest back buffer with the nesest one.
+ (Low lag, no tearing).
+
+== 3. The Swapchain Lifecycle
+
+Because the swapchain belongs to the *OS Surface* (the window), it is "fragile".
+If the user resizes the window, the swapchain because *out of date*, and you
+must destroy and recreate it.
+
+In your C code, the loop usually looks like this:
+
+1. `Acquire`: Ask the swapchain for an available image index:
+ `vkAcquireNextImageKHR`
+
+2. `Render`: Tell your command buffer to draw into that specific image index.
+
+3. `Present`: Put the image back into the swapchain "queue" to be shown:
+ `vkQueuePresentKHR`
+
+
+== 4. Why is it an Extension?
+
+You'll notice swapchain functions start with `KHR` (like vkCreateSwapchainKHR).
+This is because Vulkan is designed to run on things that don't have screens
+(like server-side compute nodes). The swapchain is an *extension* that bridges
+the gap between the Vulkan API and the Window System (Windows, X11, Wayland,
+etc.)
+
+== Common Gotcha: Image Views
+You cannot draw directly to a swapchain image. You must create a `VKImageView`
+for every image in the swapchain. Think of the `VKImage` as the raw memory block
+and the `VKImageView` as the "lens" that tells Vulkan how to look at that memory
+(e.g., "treat this as a 2D RGBA texture")
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.20">
+<title>Swapchain</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
+<head>
+<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=DotGothic16"> -->
+<!-- <link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/A-Larsen/math-notes/refs/heads/main/favicon.png"> -->
+<!-- <link rel="icon" type="image/png" href="N2DE_favicon.png"/> -->
+<!-- <link rel="preconnect" href="https://fonts.googleapis.com"> -->
+<!-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> -->
+<!-- <link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap" rel="stylesheet"> -->
+<style>
+
+/*--------------------------------------------------------------*/
+/*use '!important' to override default css used by asciidoctor*/
+/*--------------------------------------------------------------*/
+
+:root {
+ --gruvbox-black: #282828;
+ --gruvbox-black2: #1d2021;
+ --gruvbox-black3: #463f39;
+ --gruvbox-red: #fc7869;
+ --gruvbox-yellow: #fabd2f;
+ --gruvbox-white: #ebdbb2;
+ --gruvbox-white2: #a7a187;
+ --gruvbox-blue: #83a598;
+ --gruvbox-green: #b8bb26;
+ --gruvbox-bg1: #7c6f64;
+ --gruvbox-bg2: #665c54;
+
+}
+
+.title {
+ color: var(--gruvbox-red) !important;
+}
+
+blockquote {
+ color: var(--gruvbox-white) !important;
+}
+
+table {
+ margin: 0 auto !important;
+ margin-bottom: 20px !important;
+}
+
+.hdlist1 {
+ /*font-size: 1.3em;*/
+ text-decoration: underline;
+}
+
+/* for list element definitions * *define-me*: */
+li strong {
+ /*text-decoration: underline;*/
+ border-radius: .2;
+ color: grey;
+}
+
+cite {
+ color: var(--gruvbox-green) !important;
+}
+
+.tableblock {
+ background-color: inherit;
+ color: var(--gruvbox-white);
+ max-width: 95%;
+}
+
+.tableblock th {
+ background-color: var(--gruvbox-black3);
+ color: var(--gruvbox-white);
+ max-width: 97%;
+}
+
+code:not([data-lang]) {
+ background-color: var(--gruvbox-bg2) !important;
+ color: var(--gruvbox-white);
+ text-shadow: none;
+}
+
+.checklist {
+ text-align: left;
+}
+
+.checklist li ul { margin-left: 3%; }
+
+#content, h1, pre {
+ border-radius: 8px !important;
+}
+
+#header, #content, #footnotes, #footer {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.api-type a:hover {
+ text-decoration: underline;
+}
+
+.api-type, .api-type a {
+ color: var(--gruvbox-green);
+ text-decoration: none;
+}
+
+pre {
+ border-style: solid !important;
+ border-color: white !important;
+ border-width: 2px !important;
+}
+
+h1 {
+ /* font-family: "DotGothic16", sans-serif !important;*/
+
+
+ background-color: var(--gruvbox-white2) !important;
+ color: var(--gruvbox-black) !important;
+}
+
+.admonitionblock > table td.content {
+ color: var(--gruvbox-white) !important;
+}
+
+/*
+html {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+}
+
+
+*/
+/*html {*/
+
+body {
+ /*background: black url("background-image-space.jpeg") no-repeat fixed center;*/
+ /*background-color: black;*/
+ background-color: var(--gruvbox-black);
+ background-attachment: fixed;
+ background-size: cover;
+ overflow-y: scroll;
+ /*
+ margin: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ */
+}
+
+
+/*
+body {
+ overflow-y: scroll;
+ background: rgba(0,0,0,0);
+ height: 100%;
+ margin: 2%;
+}
+*/
+
+#toc {
+ background-color: var(--gruvbox-black2) !important;
+ /*opacity: .9;*/
+}
+
+#toctitle {
+ color: var(--gruvbox-yellow);
+}
+
+a {
+ color: var(--gruvbox-bg1);
+}
+
+#content {
+ color: #ebdbb2;
+ background: var(--gruvbox-black);
+ /*opacity: .9;*/
+}
+
+#header > h1:nth-last-child(2) {
+ border-bottom: none !important;
+ background-color: rgba(0, 0, 0, .1);
+ padding-left: 2%;
+}
+
+.sect1 {
+ padding-bottom: 0;
+}
+
+.sect1 + .sect1 {
+ border-top: none !important;
+}
+
+.sect1 h2 {
+ background-size: cover;
+ background-attachment: fixed;
+ /*
+ border-bottom: 1px solid var(--gruvbox-white);
+ border-top: 1px solid var(--gruvbox-white);
+ */
+ padding: 10px;
+ text-align: left;
+ color: var(--gruvbox-red) !important;
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-black);
+ text-shadow: 3px 3px 3px var(--gruvbox-black);
+ text-shadow: 5px 5px 5px var(--gruvbox-blue);
+ border-bottom: solid;
+ */
+ width: fit-content;
+}
+
+h3, h4, h5 {
+ /*
+ text-shadow: 1px 1px 1px var(--gruvbox-white);
+ */
+}
+
+h3 {
+ color: var(--gruvbox-green) !important;
+ opacity: 80%;
+}
+
+h4 {
+ color: var(--gruvbox-yellow) !important;
+}
+
+h5 {
+ color: #83a598 !important;
+}
+
+h2 ~ *:not(h1) {
+ margin-left: .5em;
+}
+
+.pygments {
+ max-width: 97% !important;
+ white-space: pre;
+ overflow-x: scroll !important;
+}
+
+::before {
+ /*makes sure that the programming language that show up on hover is within
+ pre tag*/
+ padding-right: 3%;
+}
+
+@media screen and (max-width: 800px) {
+ .checklist li ul {
+ margin-left: 6% !important;
+ }
+ #toc {
+ padding-left: 10px;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+@media screen and (max-width: 535px) {
+ h3 ~ *:not(h2):not(h3) {
+ margin-left: 0 !important;
+ }
+
+ h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 0 !important;
+ }
+ h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 0 !important;
+ }
+
+ footer {
+ margin-bottom: 10px !important;
+ }
+ html {
+ margin-bottom: 10px !important;
+ }
+}
+h3 ~ *:not(h2):not(h3) {
+ margin-left: 1.5em;
+}
+
+h4 ~ *:not(h2):not(h3):not(.h3):not(h4) {
+ margin-left: 2em;
+}
+h5 ~ *:not(h2):not(h3):not(.h3):not(h4):not(.h4):not(h5) {
+ margin-left: 2.5em;
+}
+
+
+.sect1 h2 {
+
+ /*
+ background: linear-gradient(
+ to bottom,
+ rgba(251, 189, 47, 0.2), /* yellow */
+ rgba(0, 0, 0, 0.4) /* black */
+ );
+ */
+ /*opacity: .9;*/
+ }
+
+#preamble p{
+ padding-left: 10px;
+ color: var(--gruvbox-white) !important;
+}
+
+#footer {
+ max-width: 62.5em !important;
+ padding-left: 10px !important;
+ text-align: center;
+}
+
+.logo img {
+ display: block !important;
+ margin-left: auto !important;
+ margin-right: auto !important;
+}
+
+#header > h1:first-child {
+ margin-top: .3em !important;
+ padding-top: .2em !important;
+ text-align: center;
+ margin-right: 12px;
+ margin-left: 12px;
+}
+
+footer {
+ max-width: 62.5em;
+ margin-left: auto;
+ margin-right: auto;
+ display: block;
+
+}
+
+details[open] > summary::before {
+ /* display: none;*/
+ border-top: none;
+ content: "▼";
+ margin-top: -12px;
+}
+
+.example.stemblock {
+ border: solid;
+ font-size: 2vw;
+ max-width: 40%;
+ margin: auto;
+}
+
+.center {
+ max-width: 500px;
+ display: flex;
+ justify-content: center;
+}
+
+.highlight {
+ color: var(--gruvbox-yellow)
+}
+
+.border {
+ padding: 4px;
+ border: 2px solid rgba(255, 0, 0, .3);
+}
+
+.dull {
+ opacity: 40%;
+}
+
+.inline {
+ margin-right: 5%;
+ display: inline-block;
+}
+
+img {
+ margin: auto;
+ width: 100%;
+}
+
+/* ================= SCROLLBARS (WebKit) ================= */
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--gruvbox-bg1);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+div.ulist {
+ max-width: 95%;
+}
+
+.paragraph {
+ max-width: 98%;
+}
+
+.todo-list li .checkbox {
+ display: inline-block; /* already set */
+ width: 1.5em; /* fixed width */
+ height: 1.5em; /* keep height same */
+ margin-right: 0.5em;
+ vertical-align: middle; /* align with text nicely */
+ text-align: center;
+}
+
+li.started .checkbox {
+ color: var(--gruvbox-yellow);
+}
+
+li.completed .checkbox {
+ color: var(--gruvbox-green);
+}
+
+li.empty .checkbox {
+ color: var(--gruvbox-red);
+}
+
+.todo-list li {
+ font-family: "inherit"; /* inherits whatever body is using */
+ line-height: 1.4em; /* optional: keep spacing nice */
+}
+
+ul.checklist li p {
+ font-size: 1.2em; /* same as body text */
+}
+
+
+
+
+div#header {
+ /*position: fixed;*/
+ background-color: var(--gruvbox-blue);
+ height: 100px;
+ top: 0px;
+ background-color: var(--gruvbox-black);
+ z-index: 1000;
+ border-bottom: 2px solid var(--gruvbox-white);
+ padding-bottom: 2px;
+}
+
+#content {
+ /*margin-top: 105px;*/
+}
+
+blockquote p {
+ color: var(--gruvbox-bg1) !important;
+}
+
+</style>
+
+</head>
+
+<script>
+function sendHeight() {
+ const height = document.documentElement.scrollHeight;
+ parent.postMessage({ type: "HEIGHT", height }, "*");
+}
+
+window.addEventListener("load", sendHeight);
+window.addEventListener("resize", sendHeight);
+
+/*
+document.addEventListener("keydown", (e) => {
+ if (e.key === "o") {
+ e.preventDefault();
+ parent.postMessage({ type: "CLOSE_NOTES" }, "*");
+ }
+});
+*/
+let scrollKeys = { j: false, k: false };
+let isScrolling = false;
+const SCROLL_SPEED = 10; // pixels per frame
+
+function scrollStep() {
+ let delta = 0;
+ if (scrollKeys.j) delta += SCROLL_SPEED;
+ if (scrollKeys.k) delta -= SCROLL_SPEED;
+
+ if (delta !== 0) {
+ window.scrollBy(0, delta);
+ requestAnimationFrame(scrollStep);
+ } else {
+ isScrolling = false;
+ }
+}
+
+document.addEventListener("keydown", (e) => {
+ const tag = document.activeElement.tagName;
+ if (tag === "INPUT" || tag === "TEXTAREA" || document.activeElement.isContentEditable) return;
+
+ if (e.key === "j" || e.key === "k") {
+ e.preventDefault();
+ scrollKeys[e.key] = true;
+
+ if (!isScrolling) {
+ isScrolling = true;
+ requestAnimationFrame(scrollStep);
+ }
+ }
+ if(e.key === "o") {
+ e.preventDefault();
+ parent.postMessage({ type: "CLOSE_NOTES" }, "*");
+ }
+});
+
+document.addEventListener("keyup", (e) => {
+ if (e.key === "j" || e.key === "k") {
+ scrollKeys[e.key] = false;
+ }
+});
+
+
+
+window.onload = function () {
+
+ // Select all checklist ULs
+ document.querySelectorAll('ul.checklist').forEach(function(ul) {
+ ul.classList.add('todo-list');
+
+ ul.querySelectorAll('li').forEach(function(li) {
+ const p = li.querySelector('p');
+ if (!p) return;
+
+ const text = p.textContent.trim();
+
+ let state = '';
+ if (text.startsWith('❏')) {
+ state = 'empty';
+ } else if (text.startsWith('✓')) {
+ state = 'started';
+ } else if (text.startsWith('[X]')) {
+ state = 'completed';
+ } else {
+ return;
+ }
+
+ li.classList.add(state);
+
+ // Remove the marker
+ let content = '';
+ if (state === 'completed') {
+ content = text.slice(3).trim();
+ } else {
+ content = text.slice(1).trim();
+ }
+
+ // Only rewrite the first <p>, keep nested divs/lists intact
+ p.innerHTML = '';
+
+ const checkbox = document.createElement('span');
+ checkbox.className = 'checkbox';
+
+ /*
+ if (state === 'empty') checkbox.textContent = '🌑';
+ else if (state === 'started') checkbox.textContent = '🌓';
+ else if (state === 'completed') checkbox.textContent = '🌕';
+ */
+ if (state === 'empty') checkbox.textContent = '☐';
+ else if (state === 'started') checkbox.textContent = '◪';
+ else if (state === 'completed') checkbox.textContent = '▣';
+
+ p.appendChild(checkbox);
+ p.appendChild(document.createTextNode(' ' + content));
+ });
+});
+
+};
+window.addEventListener("load", e => {
+ // Request the scroll position as soon as the iframe is loaded
+ window.parent.postMessage({
+ type: "SCROLL_REQUEST_GET"
+ }, "*");
+});
+
+// Capture scroll position while scrolling
+window.addEventListener("scroll", e => {
+ const scrollPosition = window.scrollY || document.documentElement.scrollTop;
+ window.parent.postMessage({
+ type: "SCROLL_REQUEST_SET",
+ scrollPosition: scrollPosition
+ }, "*");
+});
+
+// Receive messages to set the scroll position
+window.addEventListener("message", (e) => {
+ if (e.data.type === "SCROLL_POSITION") {
+ // Set the scroll position to the iframe
+ window.scrollTo(0, e.data.scrollPosition);
+ }
+});
+</script>
+</head>
+<body class="article">
+<div id="header">
+<h1>Swapchain</h1>
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_summary">Summary</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Without a swapchain, you could render frames all day long, but they would just
+sit in your GPU’s memory. The swapcahin is essectially a <strong>buffer queue</strong> that
+manages how "finished" images are handed off to the operating system’s window
+manager.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_1_the_double_or_tripple_buffering_concept">1. The "Double or Tripple Buffering" Concept</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Think of the swapchain as a series of <strong>frames</strong> (usually 2 or 3). While the
+monitor is busy showing Frame A, your GPU is busy drawing into Frame B. When the
+GPU is done with B, they "swap" roles.</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1">The Front Buffer</dt>
+<dd>
+<p>The Image currently being scanned and displayed by your monitor</p>
+</dd>
+<dt class="hdlist1">The Back Buffer</dt>
+<dd>
+<p>The images the GPU is currently "painting" on.</p>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_2_key_components_of_the_swapchain">2. Key Components of the Swapchain</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>When you create a swapcain in C, you have to negotiate several settings with the
+hardware:</p>
+</div>
+<div class="sect2">
+<h3 id="_a_image_count">A. Image Count</h3>
+<div class="paragraph">
+<p>Most modern applications use <strong>Tripple Buffering</strong> (3 images). This allows the GPU
+to keep working on a third frame even if the monitor hasn’t finished display the
+first one, preventing the GPU from idiling.</p>
+</div>
+<div class="paragraph">
+<p>Images have the type <code>VKImage</code></p>
+</div>
+<div class="paragraph">
+<p>Images will be drawn to the window surface (<code>VkSurfaceKHR</code>).</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_b_capabilities">B. Capabilities</h3>
+<div class="paragraph">
+<p>We’ll check the surfaces capabilities with
+<code>vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps)</code>. This will
+tell the use the extent (width and height of image) of the surface, maximum
+number of images excepted and the minimum number of images excepted for our
+device surface.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_c_surface_format">C. Surface Format</h3>
+<div class="paragraph">
+<p>we can check the surface format with</p>
+</div>
+<div class="paragraph">
+<p><code>vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats)</code></p>
+</div>
+<div class="paragraph">
+<p>This defines the color space and bit depth. Usually, you’ll look for:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>VK_FORMAT_B8G8R8A8_SRGB</code> (Standard 32-bit color)</p>
+</li>
+<li>
+<p><code>VK_COLOR_SPACE_SRGB_NONLINEAR_KHR</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_d_present_mode">D. Present Mode</h3>
+<div class="paragraph">
+<p>This determines how the "swap" actually happens. It’s essentially how you handle
+<strong>V-Sync</strong>.</p>
+</div>
+<div class="dlist">
+<dl>
+<dt class="hdlist1"><code>VK_PRESENT_MODE_IMMEDIATE_KHR</code></dt>
+<dd>
+<p>Swap as soon as the GPU is done. (Causes screen tearing).</p>
+</dd>
+<dt class="hdlist1"><code>VK_PRESENT_MODE_FIFO_KHR</code></dt>
+<dd>
+<p>Wait for the monitor’s refresh (V-Sync). (Prevents tearing, may add
+input add input lag).</p>
+</dd>
+<dt class="hdlist1"><code>VK_PRESENT_MODE_MAILBOX_KHR</code></dt>
+<dd>
+<p>The "Gold Standard". It replaces the oldest back buffer with the nesest one.
+(Low lag, no tearing).</p>
+</dd>
+</dl>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_3_the_swapchain_lifecycle">3. The Swapchain Lifecycle</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Because the swapchain belongs to the <strong>OS Surface</strong> (the window), it is "fragile".
+If the user resizes the window, the swapchain because <strong>out of date</strong>, and you
+must destroy and recreate it.</p>
+</div>
+<div class="paragraph">
+<p>In your C code, the loop usually looks like this:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><code>Acquire</code>: Ask the swapchain for an available image index:
+<code>vkAcquireNextImageKHR</code></p>
+</li>
+<li>
+<p><code>Render</code>: Tell your command buffer to draw into that specific image index.</p>
+</li>
+<li>
+<p><code>Present</code>: Put the image back into the swapchain "queue" to be shown:
+<code>vkQueuePresentKHR</code></p>
+</li>
+</ol>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_4_why_is_it_an_extension">4. Why is it an Extension?</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>You’ll notice swapchain functions start with <code>KHR</code> (like vkCreateSwapchainKHR).
+This is because Vulkan is designed to run on things that don’t have screens
+(like server-side compute nodes). The swapchain is an <strong>extension</strong> that bridges
+the gap between the Vulkan API and the Window System (Windows, X11, Wayland,
+etc.)</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_common_gotcha_image_views">Common Gotcha: Image Views</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>You cannot draw directly to a swapchain image. You must create a <code>VKImageView</code>
+for every image in the swapchain. Think of the <code>VKImage</code> as the raw memory block
+and the <code>VKImageView</code> as the "lens" that tells Vulkan how to look at that memory
+(e.g., "treat this as a 2D RGBA texture")</p>
+</div>
+</div>
+</div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2026-02-23 17:49:29 -0500
+</div>
+</div>
+<script type="text/x-mathjax-config">
+MathJax.Hub.Config({
+ messageStyle: "none",
+ tex2jax: {
+ inlineMath: [["\\(", "\\)"]],
+ displayMath: [["\\[", "\\]"]],
+ ignoreClass: "nostem|nolatexmath"
+ },
+ asciimath2jax: {
+ delimiters: [["\\$", "\\$"]],
+ ignoreClass: "nostem|noasciimath"
+ },
+ TeX: { equationNumbers: { autoNumber: "none" } }
+})
+MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
+ MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
+ if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
+ data.math.root.display = "block"
+ }
+ return data
+ })
+})
+</script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+#include <vulkan/vulkan.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h> #include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+#define MAX_FRAMES_IN_FLIGHT 3
+
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+
+VkSampler sampler;
+VkDescriptorSetLayout descriptorSetLayout;
+VkDescriptorPool descriptorPool;
+VkDescriptorSet descriptorSet;
+
+VkImage textureImage;
+VkDeviceMemory textureMemory;
+VkImageView textureImageView;
+
+VkFence* imagesInFlight = NULL; // tracks which fence is using each image
+
+GLFWwindow* window;
+
+float angle = 0.0f;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[3];
+ float uv[2];
+} Vertex;
+
+uint32_t vertexCount = 0;
+typedef struct _Vec3 { float x; float y; float z;} Vec3;
+typedef struct _Vec2 { float x; float y;} Vec2;
+
+typedef struct {
+ float x, y, z;
+ float u, v;
+} CubeVertex;
+
+CubeVertex cube[] = {
+ // front
+ {-0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f,-0.5f,-0.5f, 1,0},
+ { 0.5f, 0.5f,-0.5f, 1,1},
+
+ {-0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f,-0.5f, 1,1},
+ {-0.5f, 0.5f,-0.5f, 0,1},
+
+ // back
+ {-0.5f,-0.5f, 0.5f, 1,0},
+ {-0.5f, 0.5f, 0.5f, 1,1},
+ { 0.5f, 0.5f, 0.5f, 0,1},
+
+ {-0.5f,-0.5f, 0.5f, 1,0},
+ { 0.5f, 0.5f, 0.5f, 0,1},
+ { 0.5f,-0.5f, 0.5f, 0,0},
+
+ // left
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ {-0.5f,-0.5f,-0.5f, 1,0},
+ {-0.5f, 0.5f,-0.5f, 1,1},
+
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ {-0.5f, 0.5f,-0.5f, 1,1},
+ {-0.5f, 0.5f, 0.5f, 0,1},
+
+ // right
+ { 0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f,-0.5f, 0.5f, 1,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+
+ { 0.5f,-0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+ { 0.5f, 0.5f,-0.5f, 0,1},
+
+ // top
+ {-0.5f, 0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f,-0.5f, 1,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+
+ {-0.5f, 0.5f,-0.5f, 0,0},
+ { 0.5f, 0.5f, 0.5f, 1,1},
+ {-0.5f, 0.5f, 0.5f, 0,1},
+
+ // bottom
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ { 0.5f,-0.5f, 0.5f, 1,0},
+ { 0.5f,-0.5f,-0.5f, 1,1},
+
+ {-0.5f,-0.5f, 0.5f, 0,0},
+ { 0.5f,-0.5f,-0.5f, 1,1},
+ {-0.5f,-0.5f,-0.5f, 0,1},
+};
+
+// this finds which GPU memory type index you should use when allocating memory
+// for a buffer or image.
+// https://docs.vulkan.org/spec/latest/chapters/memory.html#VkMemoryPropertyFlagBits
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props) {
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+ // check each bit and see if it matches typeBits
+ // - `typeBits` is a bitmask
+ // - Each bit corresponds to a memory type index
+ // - If bit i is set -> that memory type can be used for this resource
+ if ((typeBits & (1 << i)) &&
+ // `propertyFlags` may have extra bits
+ // You only care that your requested bits are present
+ //
+ // exmaple:
+ // props = HOST_VISIBLE | HOST_COHERENT
+ // propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+ //
+ // The above would still return true
+
+ (memProps.memoryTypes[i].propertyFlags & props) == props) {
+ return i;
+ }
+ }
+
+ fprintf(stderr, "No suitable memory type\n");
+ exit(1);
+}
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+
+ for (uint32_t i = 0; i < fmtCount; i++) {
+ // Look for a format that is 8-bit BGRA and uses the sRGB color space
+ if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
+ formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ surf_fmt = formats[i];
+ break;
+ }
+ }
+ swapchainFormat = surf_fmt.format;
+
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount + 1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace, // sRBG, HDR10 etc..
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ // .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ // should check if GPU supports this presentMode
+ .presentMode = VK_PRESENT_MODE_MAILBOX_KHR,
+ .clipped = VK_TRUE
+ };
+ // pAllocator is set to NULL
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+ printf("image count: %d\n", swapchainImageCount);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .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
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+
+ vertexCount = SIZE(cube);
+ Vertex vertices[vertexCount];
+
+ for (uint32_t i = 0; i < vertexCount; ++i) {
+ vertices[i].pos[0] = cube[i].x;
+ vertices[i].pos[1] = cube[i].y;
+ vertices[i].pos[2] = cube[i].z;
+
+ vertices[i].uv[0] = cube[i].u;
+ vertices[i].uv[1] = cube[i].v;
+ }
+
+
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = sizeof(Vertex) * vertexCount,
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+ if(vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create vertex buffer\n"); exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = 0
+ };
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+ for(uint32_t i=0;i<memProps.memoryTypeCount;i++){
+ if((memReq.memoryTypeBits & (1<<i)) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)){
+ allocInfo.memoryTypeIndex = i;
+ break;
+ }
+ }
+
+ if(vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate vertex buffer memory\n"); exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufInfo.size, 0, &data);
+ memcpy(data, vertices, bufInfo.size);
+ // memcpy(data, vertices, sizeof(Vertex) * vertexCount);
+ vkUnmapMemory(device, vertexBufferMemory);
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("cube.vert.spv");
+ VkShaderModule fragModule = load_shader("cube.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ // One Vertex per vertex
+ // Move forward sizeof(Vertex) each vertex
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(CubeVertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+
+ VkVertexInputAttributeDescription attrDesc[] = {
+ {
+ .location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = offsetof(Vertex, pos)
+ },
+ {
+ .location = 1,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32_SFLOAT,
+ .offset = offsetof(Vertex, uv)
+ }
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=2,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_BACK_BIT,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ // .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ .rasterizationSamples = VK_SAMPLE_COUNT_4_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPushConstantRange pushRange = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(float)
+ };
+
+ VkDescriptorSetLayoutBinding texBinding = {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
+ };
+
+ VkDescriptorSetLayoutCreateInfo layoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .bindingCount = 1,
+ .pBindings = &texBinding
+ };
+
+ vkCreateDescriptorSetLayout(device, &layoutInfo, NULL, &descriptorSetLayout);
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pushRange,
+ .setLayoutCount = 1,
+ .pSetLayouts = &descriptorSetLayout,
+ };
+
+ vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout);
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+
+ VkPipelineRenderingCreateInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+ };
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = {
+ .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &renderingInfo,
+ .stageCount = 2,
+ .pStages = stages,
+ .pVertexInputState = &vertexInput,
+ .pInputAssemblyState = &inputAssembly,
+ .pViewportState = &vpState,
+ .pRasterizationState = &rasterizer,
+ .pMultisampleState = &multisampling,
+ .pColorBlendState = &colorBlending,
+ .layout = pipelineLayout,
+ .renderPass = VK_NULL_HANDLE,
+ .subpass = 0
+ };
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,
+ &graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+void create_texture_sampler() {
+ VkSamplerCreateInfo samplerInfo = {
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ // this creates to black stars from an artifact
+ // .magFilter = VK_FILTER_LINEAR,
+ // .minFilter = VK_FILTER_LINEAR,
+ .magFilter = VK_FILTER_NEAREST,
+ .minFilter = VK_FILTER_NEAREST,
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ };
+
+ vkCreateSampler(device, &samplerInfo, NULL, &sampler);
+}
+
+void create_descriptor_pool() {
+ VkDescriptorPoolSize poolSize = {
+ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1
+ };
+
+ VkDescriptorPoolCreateInfo poolInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .poolSizeCount = 1,
+ .pPoolSizes = &poolSize,
+ .maxSets = 1
+ };
+
+ vkCreateDescriptorPool(device, &poolInfo, NULL, &descriptorPool);
+}
+
+void create_descriptor_set() {
+ VkDescriptorSetAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .descriptorPool = descriptorPool,
+ .descriptorSetCount = 1,
+ .pSetLayouts = &descriptorSetLayout
+ };
+
+ vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet);
+}
+
+void create_texture_image() {
+ // CPU RAM
+ // ↓ memcpy
+ // HOST_VISIBLE buffer
+ // ↓ GPU copy
+ // DEVICE_LOCAL image
+ // ↓ shader sampling
+ // fragment shader
+ // 2x2 RGBA checkerboard
+ uint32_t pixels[] = {
+ 0xFFFFFFFF, 0xFFFF69B4, // top row: white, pink
+ 0xFFFF69B4, 0xFFFFFFFF // bottom row: pink, white
+ };
+
+ VkDeviceSize size = sizeof(pixels);
+
+ /* --- staging buffer --- */
+ VkBuffer staging;
+ VkDeviceMemory stagingMem;
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+ };
+ // this creates a buffer object that
+ // - Holds `size` bytes
+ // - Can be used as a transfer source
+ // WARNING: no memory is allocated yet - this is just the buffer
+ // description
+ vkCreateBuffer(device, &bufInfo, NULL, &staging);
+
+ // The driver tell you:
+ // - required memory size
+ // - required alignment
+ // - Which memory types are allows (memoryTypeBits)
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, staging, &memReq);
+
+ VkMemoryAllocateInfo alloc = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex =
+ // https://docs.vulkan.org/spec/latest/chapters/memory.html#VkMemoryPropertyFlagBits
+ // This means:
+ // “Find a memory type that
+ // ✔ the buffer supports
+ // ✔ I can map from CPU
+ // ✔ doesn’t require manual flush/invalidate”
+ find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+ // `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`
+ // bit specifies that memory
+ // allocated with this type can be mapped for host access using
+ // vkMapMemory.
+ //
+ // `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`
+ // "When I write to mapped memory, the GPU sees it automatically"
+ //
+ // This is slower than flushing.
+ // Writes are automatically visible
+ //
+ // without it, you must manually do:
+ //
+ // vkFlushMappedMemoryRanges(...)
+ // vkInvalidateMappedMemoryRanges(...)
+
+ };
+
+ // - Physical memory is allocated
+ // - The buffer is bound to that memory
+ //
+ // when allocating you are reserving a chunk of memroy from the GPU (or a
+ // heap).
+ //
+ // - `VkDeviceMemory` is just a handle that represents that chunk.
+ // - The memory exists, but nothing is stored in it yet
+ // - The GPU doesn't know which buffer or image will use it.
+ vkAllocateMemory(device, &alloc, NULL, &stagingMem);
+ // Binding memory:
+ // - Assigns a physical region of GPU-accessible memory
+ // - Tells Vulkan where the object’s data lives
+ // - Fixes alignment and offset requirements
+ vkBindBufferMemory(device, staging, stagingMem, 0);
+
+ // - CPU gets a pointer
+ // - Copies `size` of data
+ // - Unmaps (flush is automatic because of COHERENT)
+ // At this point, pixel data is in GPU-visable memory
+ void* data;
+ vkMapMemory(device, stagingMem, 0, size, 0, &data);
+ memcpy(data, pixels, size);
+ vkUnmapMemory(device, stagingMem);
+
+ /* --- image --- */
+ // Create the GPU image (the actual texture)
+ // This describes
+ // - A 2D image
+ // - 2x2 pixels
+ // - Optimal tiling (GPU-friendly layout)
+ // - can be:
+ // - copied into
+ // - sampled by shaders
+ //
+ VkImageCreateInfo img = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {2, 2, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+ };
+ // still no memory allocated yet for textureImage
+ vkCreateImage(device, &img, NULL, &textureImage);
+
+ vkGetImageMemoryRequirements(device, textureImage, &memReq);
+ // modify the alloc struct so we don't have to create another struct.
+ // for simplicity
+ alloc.allocationSize = memReq.size;
+ alloc.memoryTypeIndex =
+ find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ // `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`
+ // specifies that memory allocated
+ // with this type is the most efficient for device access. This property
+ // will be set if and only if the memory type belongs to a heap with the
+ //
+ // This memory cannot be accessed by the CPU, and is faster than
+ // `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, because there is not memory being
+ // mapped between the CPU and GPU.
+
+ // bind and allocated the textureMemory
+ // GPU can access it efficently
+ vkAllocateMemory(device, &alloc, NULL, &textureMemory);
+ vkBindImageMemory(device, textureImage, textureMemory, 0);
+
+ /* --- command buffer for copy --- */
+ // Redner a one-time command buffer
+ // Why?
+ // Because copies and layout transitions happen on the GPU
+ // https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1
+ };
+
+ VkCommandBuffer cb;
+ vkAllocateCommandBuffers(device, &cbAlloc, &cb);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ /* layout: undefined → transfer dst */
+ // - Vulkan images start in UNDEFINED
+ // - Copying requires TRANSFER_DST
+ VkImageMemoryBarrier toTransfer = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ // The layout the image is currently in.
+ // Here it is `UNDEFINED` because we just created the image
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ // The layout we want for the next operation.
+ // `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL` is optimized for copying data
+ // into the image
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ // VK_ACCESS_TRANSFER_WRITE_BIT tells Vulkan:
+ // "we're going to write this image in a transfer operation"
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ // The image we're transitioning
+ .image = textureImage,
+
+ .subresourceRange = {
+ // only the color aspect (not depth or stencil)
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ // noly the first mip level
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ // only the first layer (no cubemaps/arrays here)
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+
+ // This tells Vulkan:
+ // “Before transfer writes, make sure the image is ready.”
+ // https://docs.vulkan.org/spec/latest/chapters/synchronization.html#vkCmdPipelineBarrier
+ // https://docs.vulkan.org/spec/latest/chapters/synchronization.html#VkPipelineStageFlagBits
+ //
+ // vkCmdPipelineBarrier(
+ // VkCommandBuffer commandBuffer,
+ // VkPipelineStageFlags srcStageMask,
+ // VkPipelineStageFlags dstStageMask,
+ // ...
+ // );
+ // srcStageMask → which pipeline stage(s) must finish before the barrier
+ // dstStageMask → which pipeline stage(s) must wait until after the barrier
+ //
+ // TOP_OF_PIPE → Vertex → Fragment → Color Attachment → Bottom of Pipe
+ //
+ // so the the function below happens before the pipeline
+
+ // "Pipeline barriers also provide synchronization control within a command
+ // buffer, but at a single point, rather than with separate signal and wait
+ // operations. Pipeline barriers can be used to control resource access
+ // within a single queue"
+ // https://docs.vulkan.org/spec/latest/chapters/synchronization.html#vkCmdPipelineBarrier
+ vkCmdPipelineBarrier(
+ cb, // commandBufer
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask
+ VK_PIPELINE_STAGE_TRANSFER_BIT, // DstStageMask
+ // a bit mask of VkPipelineStageFlagBits specifying the destination
+ // stages
+ 0, // dependabctFlags
+ 0, // memoryBarrierCount
+ // A pointer to an array of `VkMemoryBarrier` structures.
+ NULL, // pMemoryBarriers
+ 0, // bufferMemoryBarrierCount
+ // a pointer array of `VkBufferMemoryBarrier`
+ NULL, // pBuferMemoryBarriers
+ 1, // imageMemoryBarrierCount
+ // a pointer to an array of VkImageMemoryBarrier
+ &toTransfer // pImageMemoryBarriers
+ );
+
+ /* copy buffer → image */
+ VkBufferImageCopy copy = {
+ .imageSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ },
+ .imageExtent = {.width = 2, .height = 2, .depth = 1}
+ };
+
+ // Copy data from a buffer object to an image object
+ vkCmdCopyBufferToImage(
+ cb,
+ staging,
+ textureImage,
+ // `VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL`
+
+ // must only be used as a destination image of a transfer command.
+ // This layout is valid only for image subresources of images
+ // created with the VK_IMAGE_USAGE_TRANSFER_DST_BIT usage flag set.
+
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1,
+ ©);
+
+ /* layout: transfer → shader read */
+ VkImageMemoryBarrier toSample = toTransfer;
+ toSample.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ toSample.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ toSample.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ toSample.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toSample
+ );
+
+ vkEndCommandBuffer(cb);
+
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb
+ };
+ // vkQueueSubmit(graphicsQueue, 1, &submit, VK_NULL_HANDLE);
+ // use fence instead of wait idle
+ // vkQueueWaitIdle(graphicsQueue);
+ // Fence lets CPU know when GPU is done
+ // CPU blocks until:
+ // - Copy completes
+ // - Image is ready for sampling
+ VkFence fence;
+ VkFenceCreateInfo fenceInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
+ vkCreateFence(device, &fenceInfo, NULL, &fence);
+ vkQueueSubmit(graphicsQueue, 1, &submit, fence);
+ vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+ vkDestroyFence(device, fence, NULL);
+
+
+ // cleanup staging resources
+ vkFreeCommandBuffers(device, commandPool, 1, &cb);
+ vkDestroyBuffer(device, staging, NULL);
+ vkFreeMemory(device, stagingMem, NULL);
+}
+
+void create_texture_image_view() {
+ VkImageViewCreateInfo view = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = textureImage,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+ };
+ vkCreateImageView(device, &view, NULL, &textureImageView);
+}
+
+void update_descriptor_set() {
+ VkDescriptorImageInfo imageInfo = {
+ .sampler = sampler,
+ .imageView = textureImageView,
+ .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+ };
+
+ VkWriteDescriptorSet write = {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = descriptorSet,
+ .dstBinding = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &imageInfo
+ };
+
+ vkUpdateDescriptorSets(device, 1, &write, 0, NULL);
+}
+
+
+/* --- Main --- */
+int main(void) {
+ double lastTime = glfwGetTime();
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+
+ uint32_t instanceVersion;
+ vkEnumerateInstanceVersion(&instanceVersion);
+ printf("Vulkan loader supports version %d.%d.%d\n",
+ VK_API_VERSION_MAJOR(instanceVersion),
+ VK_API_VERSION_MINOR(instanceVersion),
+ VK_API_VERSION_PATCH(instanceVersion)
+ );
+
+ if (!(VK_API_VERSION_MAJOR(instanceVersion) >= 1 &&
+ VK_API_VERSION_MINOR(instanceVersion) >= 3)) {
+ // you need at least vulkan 1.3 to use dynamic rendering
+ fprintf(stderr, "need at least vulkan 1.3");
+ exit(1);
+ }
+ uint32_t ext_count=0;
+ VkApplicationInfo appInfo = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = "cube",
+
+ // Developer supplied version number of the application
+ .applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0),
+
+ // must be the highest version of VUlkan that the application is
+ // designed to use.
+ .apiVersion = VK_API_VERSION_1_3
+ };
+
+ // This function returns an array of names of Vulkan instance extensions
+ // required by GLFW for creating Vulkan surfaces for GLFW windows. If
+ // successful, the list will always contain `VK_KHR_surface`, so if you
+ // don't require any additional extensions you can pass this list directly
+ // to the `vkInstanceCreateInfo` struct
+
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &appInfo,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ici,NULL,&instance);
+
+ volkLoadInstance(instance);
+
+ // This function creates a Vulkan surface of the specific window.
+
+ // If the Vulkan loader or at least one minimally function ICD were not
+ // fuond, this returns `VK_ERROR_INITIALIZATION_FAILED` and generates a
+ // `GLFW_API_UNAVAILABLE` error. call `glfwVUlkanSupported` to check whether
+ // Vulkan is at least minimally available
+
+ // Parameteres
+ //
+ // [in] *instance* The Vulkan instance to create the surface in.
+
+ // [in] *window* The window to create the surface for.
+
+ // [in] *allocator* The allocator to use, or `NULL` to use the default
+ // allocator.
+
+ // [out] *surface* Where to store the handle of the surface. This is set
+ // to `VK_NULL_HANDLE` if an error occurred.
+
+ // Returns
+ // `VK_SUCCESS` if successful, or a Vulkan error code if an *error*
+ // occured
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8];
+ vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ printf("device count: %d\n", physCount);
+
+ // getting the name of the devices
+ for (int i = 0; i < physCount; ++i) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(devices[i], &properties);
+ printf("GPU Name: %s\n", properties.deviceName);
+
+ // if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
+
+ // }
+ if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ physical = devices[i];
+ }
+
+ }
+ // physical=devices[0];
+
+ uint32_t qcount=0;
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ // VkQueueFamilyProperties qprops[16]; // just a arbitrary number..
+ VkQueueFamilyProperties qprops[qcount];
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+
+ // checking wheather a queue family of a physical device supports
+ // presentation to a given surface
+ //
+ // While most modern GPUs (especially on Windows/Linux) use the same
+ // queue family for both graphics and presentation, it is not guaranteed
+ // by the Vulkan spec.
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+
+ // we only grab one que because we are going to be using
+ // `VK_SHARING_MODE_EXCLUSIVE`
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }
+
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ // .pNext = NULL,
+ .dynamicRendering = VK_TRUE
+ };
+
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &dynamicRenderingFeature,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ imagesInFlight = malloc(sizeof(VkFence) * swapchainImageCount);
+ for (uint32_t i = 0; i < swapchainImageCount; i++)
+ imagesInFlight[i] = VK_NULL_HANDLE;
+
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+
+ create_texture_image();
+ create_texture_image_view();
+ create_texture_sampler();
+
+ create_descriptor_pool();
+ create_descriptor_set();
+ update_descriptor_set();
+
+ create_vertex_buffer();
+
+ VkSemaphoreCreateInfo sem = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
+ };
+
+ VkFenceCreateInfo fence = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT
+ };
+
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+ }
+
+ uint32_t currentFrame = 0;
+
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ double now = glfwGetTime();
+ double delta = now - lastTime;
+ if (delta > 0.03) delta = 0.03; // clamp to max ~33ms
+ lastTime = now;
+ float rotationSpeed = M_PI / 2.0f; // 90°/sec
+ angle += rotationSpeed * delta;
+
+ uint32_t imgIndex;
+
+ // Acquire next swapchain image
+ vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+ );
+
+ // Wait for previous frame using this image
+ if (imagesInFlight[imgIndex] != VK_NULL_HANDLE) {
+ vkWaitForFences(device, 1, &imagesInFlight[imgIndex], VK_TRUE, UINT64_MAX);
+ }
+
+ // Mark image as now in use by current frame's fence
+ imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+
+ // Reset current frame fence
+ vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+ // Get command buffer for this image
+ VkCommandBuffer cb = commandBuffers[imgIndex];
+ vkResetCommandBuffer(cb, 0);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ // Transition image: PRESENT -> COLOR_ATTACHMENT_OPTIMAL
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .image = swapchainImages[imgIndex],
+ .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }
+ };
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+ // Begin dynamic rendering
+ VkRenderingAttachmentInfo colorAttachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = swapchainImageViews[imgIndex],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = {{0.0f, 0.0f, 0.0f, 1.0f}}
+ };
+ VkRenderingInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {{0, 0}, swapchainExtent},
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorAttachment
+ };
+ vkCmdBeginRendering(cb, &renderingInfo);
+
+ // Bind pipeline, viewport, scissor
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+ VkViewport vp = {0, 0, (float)swapchainExtent.width, (float)swapchainExtent.height, 0.0f, 1.0f};
+ VkRect2D scissor = {{0,0}, swapchainExtent};
+ vkCmdSetViewport(cb, 0, 1, &vp);
+ vkCmdSetScissor(cb, 0, 1, &scissor);
+
+ // Bind vertex buffer
+ VkDeviceSize offset = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vertexBuffer, &offset);
+
+ // Push rotation angle
+ vkCmdPushConstants(cb, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float), &angle);
+
+ // Bind descriptor set
+ vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+ // Draw
+ vkCmdDraw(cb, vertexCount, 1, 0, 0);
+
+ vkCmdEndRendering(cb);
+
+ // Transition image: COLOR_ATTACHMENT_OPTIMAL -> PRESENT
+ barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &barrier);
+
+ vkEndCommandBuffer(cb);
+
+ // Submit
+ VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &imageAvailable[currentFrame],
+ .pWaitDstStageMask = &waitStage,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &renderFinished[currentFrame],
+ };
+ vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+ // Present
+ VkPresentInfoKHR present = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &renderFinished[currentFrame],
+ .swapchainCount = 1,
+ .pSwapchains = &swapchain,
+ .pImageIndices = &imgIndex
+ };
+ vkQueuePresentKHR(graphicsQueue, &present);
+
+ // Advance frame
+ currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+ }
+
+
+ // Wait for GPU to finish all work
+ vkDeviceWaitIdle(device);
+
+ // Destroy semaphores and fences
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (imageAvailable[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, imageAvailable[i], NULL);
+ if (renderFinished[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, renderFinished[i], NULL);
+ if (inFlightFences[i] != VK_NULL_HANDLE)
+ vkDestroyFence(device, inFlightFences[i], NULL);
+ }
+
+ // Free imagesInFlight array
+ free(imagesInFlight);
+
+ // Destroy vertex buffer
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+
+ // Destroy texture resources
+ vkDestroySampler(device, sampler, NULL);
+ vkDestroyImageView(device, textureImageView, NULL);
+ vkDestroyImage(device, textureImage, NULL);
+ vkFreeMemory(device, textureMemory, NULL);
+
+ // Destroy descriptor sets/pools/layout
+ vkDestroyDescriptorPool(device, descriptorPool, NULL);
+ vkDestroyDescriptorSetLayout(device, descriptorSetLayout, NULL);
+
+ // Destroy pipeline and pipeline layout
+ vkDestroyPipeline(device, graphicsPipeline, NULL);
+ vkDestroyPipelineLayout(device, pipelineLayout, NULL);
+
+ // Destroy command pool
+ vkDestroyCommandPool(device, commandPool, NULL);
+
+ // Destroy swapchain image views
+ for (uint32_t i = 0; i < swapchainImageCount; i++) {
+ vkDestroyImageView(device, swapchainImageViews[i], NULL);
+ }
+ free(swapchainImageViews);
+ free(swapchainImages);
+
+ // Destroy swapchain
+ vkDestroySwapchainKHR(device, swapchain, NULL);
+
+ // Destroy device, surface, and instance
+ vkDestroyDevice(device, NULL);
+ vkDestroySurfaceKHR(instance, surface, NULL);
+ vkDestroyInstance(instance, NULL);
+
+ // Destroy GLFW window
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
+* https://docs.vulkan.org/samples/latest/samples/extensions/dynamic_rendering/README.html
+* https://www.youtube.com/watch?v=m1RHLavNjKo
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = triangle.vert triangle.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_triangle
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkRenderPass renderPass;
+VkFramebuffer* framebuffers;
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+GLFWwindow* window;
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[16];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+ swapchainFormat = surf_fmt.format;
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount+1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace,
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ .clipped = VK_TRUE
+ };
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .components = {VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY},
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+/* --- Render pass --- */
+void create_render_pass() {
+ VkAttachmentDescription att = {
+ .format = swapchainFormat,
+ .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
+ };
+ VkAttachmentReference colorRef = {0,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
+ VkSubpassDescription subpass = {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorRef
+ };
+ VkRenderPassCreateInfo rpInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &att,
+ .subpassCount = 1,
+ .pSubpasses = &subpass
+ };
+ if(vkCreateRenderPass(device,&rpInfo,NULL,&renderPass)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create render pass\n"); exit(1);
+ }
+}
+
+/* --- Framebuffers --- */
+void create_framebuffers() {
+ framebuffers = malloc(sizeof(VkFramebuffer)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkFramebufferCreateInfo fbInfo = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = renderPass,
+ .attachmentCount = 1,
+ .pAttachments = &swapchainImageViews[i],
+ .width = swapchainExtent.width,
+ .height = swapchainExtent.height,
+ .layers = 1
+ };
+ if(vkCreateFramebuffer(device,&fbInfo,NULL,&framebuffers[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create framebuffer\n"); exit(1);
+ }
+ }
+}
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Graphics pipeline for triangle --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("triangle.vert.spv");
+ VkShaderModule fragModule = load_shader("triangle.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_BACK_BIT,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPipelineLayoutCreateInfo layoutInfo = { .sType=VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
+ vkCreatePipelineLayout(device,&layoutInfo,NULL,&pipelineLayout);
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = { .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = stages;
+ pipelineInfo.pVertexInputState = &vertexInput;
+ pipelineInfo.pInputAssemblyState = &inputAssembly;
+ pipelineInfo.pViewportState = &vpState;
+ pipelineInfo.pRasterizationState = &rasterizer;
+ pipelineInfo.pMultisampleState = &multisampling;
+ pipelineInfo.pColorBlendState = &colorBlending;
+ pipelineInfo.layout = pipelineLayout;
+ pipelineInfo.renderPass = renderPass;
+ pipelineInfo.subpass = 0;
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,&graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+/* --- Record command buffers with triangle --- */
+void record_command_buffers() {
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkCommandBufferBeginInfo begin={.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
+ vkBeginCommandBuffer(commandBuffers[i],&begin);
+
+ VkClearValue clearColor = {{0.1f,0.1f,0.1f,1.0f}};
+ VkRenderPassBeginInfo rpBegin = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = renderPass,
+ .framebuffer = framebuffers[i],
+ .renderArea = {.offset={0,0},.extent=swapchainExtent},
+ .clearValueCount = 1,
+ .pClearValues = &clearColor
+ };
+
+ vkCmdBeginRenderPass(commandBuffers[i],&rpBegin,VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(commandBuffers[i],VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline);
+ vkCmdDraw(commandBuffers[i],3,1,0,0);
+ vkCmdEndRenderPass(commandBuffers[i]);
+ vkEndCommandBuffer(commandBuffers[i]);
+ }
+}
+
+/* --- Draw frame --- */
+void draw_frame() {
+ uint32_t imgIndex;
+ vkAcquireNextImageKHR(device,swapchain,UINT64_MAX,VK_NULL_HANDLE,VK_NULL_HANDLE,&imgIndex);
+
+ VkSubmitInfo submit={.sType=VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount=1,
+ .pCommandBuffers=&commandBuffers[imgIndex]};
+ vkQueueSubmit(graphicsQueue,1,&submit,VK_NULL_HANDLE);
+ vkQueueWaitIdle(graphicsQueue);
+
+ VkPresentInfoKHR present={.sType=VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .swapchainCount=1,
+ .pSwapchains=&swapchain,
+ .pImageIndices=&imgIndex};
+ vkQueuePresentKHR(graphicsQueue,&present);
+}
+
+/* --- Main --- */
+int main(void) {
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Triangle",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+ uint32_t ext_count=0;
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ici,NULL,&instance);
+ volkLoadInstance(instance);
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8]; vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ physical=devices[0];
+
+ uint32_t qcount=0; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ VkQueueFamilyProperties qprops[16]; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres){graphicsQueueFamily=i;found=1;break;}
+ }
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ create_render_pass();
+ create_framebuffers();
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+ record_command_buffers();
+
+ while(!glfwWindowShouldClose(window)){
+ glfwPollEvents();
+ draw_frame();
+ }
+
+ vkDeviceWaitIdle(device);
+
+ for(uint32_t i=0;i<swapchainImageCount;i++) vkDestroyFramebuffer(device,framebuffers[i],NULL);
+ free(framebuffers); free(commandBuffers); free(swapchainImageViews); free(swapchainImages);
+ vkDestroyPipeline(device,graphicsPipeline,NULL);
+ vkDestroyPipelineLayout(device,pipelineLayout,NULL);
+ vkDestroyRenderPass(device,renderPass,NULL);
+ vkDestroySwapchainKHR(device,swapchain,NULL);
+ vkDestroyDevice(device,NULL);
+ vkDestroySurfaceKHR(instance,surface,NULL);
+ vkDestroyInstance(instance,NULL);
+ glfwDestroyWindow(window); glfwTerminate();
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
--- /dev/null
+#version 450
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
--- /dev/null
+#version 450
+
+vec2 positions[3] = vec2[](
+ vec2( 0.0, -0.5),
+ vec2( 0.5, 0.5),
+ vec2(-0.5, 0.5)
+);
+
+void main() {
+ gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+}
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = triangle.vert triangle.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_triangle
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+parser: objParser.c
+ gcc objParser.c -o objParser
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+#include <vulkan/vulkan.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+#define MAX_FRAMES_IN_FLIGHT 3
+
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+
+VkSampler sampler;
+VkDescriptorSetLayout descriptorSetLayout;
+// VkDescriptorPool descriptorPool;
+// VkDescriptorSet descriptorSet;
+
+VkImage textureImage;
+VkDeviceMemory textureMemory;
+VkImageView textureImageView;
+
+VkFence* imagesInFlight = NULL; // tracks which fence is using each image
+
+GLFWwindow* window;
+
+float angle = 0.0f;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[3];
+ float uv[2];
+} Vertex;
+
+uint32_t vertexCount = 0;
+typedef struct _Vec3 { float x; float y; float z;} Vec3;
+typedef struct _Vec2 { float x; float y;} Vec2;
+
+typedef struct {
+ float x, y, z;
+} CubeVertex;
+
+CubeVertex cube[] = {
+ // front
+ {-0.5f,-0.5f,-0.5f},
+ { 0.5f,-0.5f,-0.5f},
+ { 0.5f, 0.5f,-0.5f},
+
+ {-0.5f,-0.5f,-0.5f},
+ { 0.5f, 0.5f,-0.5f},
+ {-0.5f, 0.5f,-0.5f},
+
+ // back
+ {-0.5f,-0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ {-0.5f,-0.5f, 0.5f},
+ { 0.5f, 0.5f, 0.5f},
+ { 0.5f,-0.5f, 0.5f},
+
+ // left
+ {-0.5f,-0.5f, 0.5f},
+ {-0.5f,-0.5f,-0.5f},
+ {-0.5f, 0.5f,-0.5f},
+
+ {-0.5f,-0.5f, 0.5f},
+ {-0.5f, 0.5f,-0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ // right
+ { 0.5f,-0.5f,-0.5f},
+ { 0.5f,-0.5f, 0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ { 0.5f,-0.5f,-0.5f},
+ { 0.5f, 0.5f, 0.5f},
+ { 0.5f, 0.5f,-0.5f},
+
+ // top
+ {-0.5f, 0.5f,-0.5f},
+ { 0.5f, 0.5f,-0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ {-0.5f, 0.5f,-0.5f},
+ { 0.5f, 0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ // bottom
+ {-0.5f,-0.5f, 0.5f},
+ { 0.5f,-0.5f, 0.5f},
+ { 0.5f,-0.5f,-0.5f},
+
+ {-0.5f,-0.5f, 0.5f},
+ { 0.5f,-0.5f,-0.5f},
+ {-0.5f,-0.5f,-0.5f},
+};
+
+// this finds which GPU memory type index you should use when allocating memory
+// for a buffer or image.
+// https://docs.vulkan.org/spec/latest/chapters/memory.html#VkMemoryPropertyFlagBits
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props) {
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+ // check each bit and see if it matches typeBits
+ // - `typeBits` is a bitmask
+ // - Each bit corresponds to a memory type index
+ // - If bit i is set -> that memory type can be used for this resource
+ if ((typeBits & (1 << i)) &&
+ // `propertyFlags` may have extra bits
+ // You only care that your requested bits are present
+ //
+ // exmaple:
+ // props = HOST_VISIBLE | HOST_COHERENT
+ // propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+ //
+ // The above would still return true
+
+ (memProps.memoryTypes[i].propertyFlags & props) == props) {
+ return i;
+ }
+ }
+
+ fprintf(stderr, "No suitable memory type\n");
+ exit(1);
+}
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+
+ for (uint32_t i = 0; i < fmtCount; i++) {
+ // Look for a format that is 8-bit BGRA and uses the sRGB color space
+ if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
+ formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ surf_fmt = formats[i];
+ break;
+ }
+ }
+ swapchainFormat = surf_fmt.format;
+
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount + 1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace, // sRBG, HDR10 etc..
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ // .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ // should check if GPU supports this presentMode
+ .presentMode = VK_PRESENT_MODE_MAILBOX_KHR,
+ .clipped = VK_TRUE
+ };
+ // pAllocator is set to NULL
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+ printf("image count: %d\n", swapchainImageCount);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .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
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("triangle.vert.spv");
+ VkShaderModule fragModule = load_shader("triangle.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {
+ [0] = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT,
+ .module = vertModule,
+ .pName = "main"
+ },
+ [1] = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = fragModule,
+ .pName = "main"
+ }
+ };
+
+
+ // One Vertex per vertex
+ // Move forward sizeof(Vertex) each vertex
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 0,
+ .pVertexBindingDescriptions = NULL,
+ .vertexAttributeDescriptionCount = 0,
+ .pVertexAttributeDescriptions = NULL
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_NONE,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ };
+
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPushConstantRange pushRange = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(float)
+ };
+
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pushRange,
+ .setLayoutCount = 0,
+ .pSetLayouts = NULL,
+ };
+
+ vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout);
+
+ VkPipelineRenderingCreateInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+ };
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = {
+ .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &renderingInfo,
+ .stageCount = 2,
+ .pStages = stages,
+ .pVertexInputState = &vertexInput,
+ .pInputAssemblyState = &inputAssembly,
+ .pViewportState = &vpState,
+ .pRasterizationState = &rasterizer,
+ .pMultisampleState = &multisampling,
+ .pColorBlendState = &colorBlending,
+ .layout = pipelineLayout,
+ .renderPass = VK_NULL_HANDLE,
+ .subpass = 0
+ };
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,
+ &graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+void record_command_buffers() {
+ for (uint32_t i = 0; i < swapchainImageCount; i++) {
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+ };
+ vkBeginCommandBuffer(commandBuffers[i], &begin);
+
+ // --- 1️⃣ Transition image to COLOR_ATTACHMENT_OPTIMAL ---
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = swapchainImages[i],
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ },
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
+ };
+
+ vkCmdPipelineBarrier(
+ commandBuffers[i],
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+ // --- 2️⃣ Begin Dynamic Rendering ---
+ VkRenderingAttachmentInfo colorAttachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = swapchainImageViews[i],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = { .color = { {0.1f, 0.1f, 0.1f, 1.0f} } }
+ };
+
+ VkRenderingInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = { {0, 0}, swapchainExtent },
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorAttachment
+ };
+
+ vkCmdBeginRendering(commandBuffers[i], &renderingInfo);
+
+ // --- 3️⃣ Bind pipeline ---
+ vkCmdBindPipeline(
+ commandBuffers[i],
+ VK_PIPELINE_BIND_POINT_GRAPHICS,
+ graphicsPipeline
+ );
+
+ // --- 4️⃣ Draw ---
+ vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
+
+ // --- 5️⃣ End rendering ---
+ vkCmdEndRendering(commandBuffers[i]);
+
+ // --- 6️⃣ Transition to PRESENT layout ---
+ barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+
+ vkCmdPipelineBarrier(
+ commandBuffers[i],
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+ vkEndCommandBuffer(commandBuffers[i]);
+ }
+}
+
+
+void draw_frame() {
+ uint32_t imgIndex;
+ vkAcquireNextImageKHR(device,swapchain,UINT64_MAX,VK_NULL_HANDLE,VK_NULL_HANDLE,&imgIndex);
+ // vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
+ // imageAvailable[currentFrame],
+ // VK_NULL_HANDLE,
+ // &imgIndex);
+
+ VkSubmitInfo submit={.sType=VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount=1,
+ .pCommandBuffers=&commandBuffers[imgIndex]};
+ vkQueueSubmit(graphicsQueue,1,&submit,VK_NULL_HANDLE);
+ vkQueueWaitIdle(graphicsQueue);
+
+ VkPresentInfoKHR present={.sType=VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .swapchainCount=1,
+ .pSwapchains=&swapchain,
+ .pImageIndices=&imgIndex};
+ vkQueuePresentKHR(graphicsQueue,&present);
+}
+
+
+/* --- Main --- */
+int main(void) {
+ double lastTime = glfwGetTime();
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+
+ uint32_t instanceVersion;
+ vkEnumerateInstanceVersion(&instanceVersion);
+ printf("Vulkan loader supports version %d.%d.%d\n",
+ VK_API_VERSION_MAJOR(instanceVersion),
+ VK_API_VERSION_MINOR(instanceVersion),
+ VK_API_VERSION_PATCH(instanceVersion)
+ );
+
+ if (!(VK_API_VERSION_MAJOR(instanceVersion) >= 1 &&
+ VK_API_VERSION_MINOR(instanceVersion) >= 3)) {
+ fprintf(stderr, "need at least vulkan 1.3");
+ exit(1);
+ }
+
+ uint32_t ext_count=0;
+ VkApplicationInfo appInfo = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = "cube",
+ .applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0),
+ .apiVersion = VK_API_VERSION_1_3
+ };
+
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &appInfo,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+
+ vkCreateInstance(&ici,NULL,&instance);
+
+ // You get instance-level pointers this way
+ volkLoadInstance(instance);
+
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8];
+ vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ printf("device count: %d\n", physCount);
+
+ // getting the name of the devices
+ for (int i = 0; i < physCount; ++i) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(devices[i], &properties);
+ printf("GPU Name: %s\n", properties.deviceName);
+
+ // if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
+
+ // }
+ if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ physical = devices[i];
+ }
+
+ }
+ // physical=devices[0];
+
+ uint32_t qcount=0;
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ // VkQueueFamilyProperties qprops[16]; // just a arbitrary number..
+ VkQueueFamilyProperties qprops[qcount];
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }
+
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+
+ VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+ };
+
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &dynamicRenderingFeature,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ // You get Device-level pointers this way
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+
+ record_command_buffers();
+
+ while(!glfwWindowShouldClose(window)){
+ glfwPollEvents();
+ draw_frame();
+ }
+
+ vkDeviceWaitIdle(device);
+
+ vkDestroyPipeline(device, graphicsPipeline, NULL);
+ vkDestroyPipelineLayout(device, pipelineLayout, NULL);
+
+ vkDestroyCommandPool(device, commandPool, NULL);
+
+ for (uint32_t i = 0; i < swapchainImageCount; i++) {
+ vkDestroyImageView(device, swapchainImageViews[i], NULL);
+ }
+
+ free(swapchainImageViews);
+ free(swapchainImages);
+
+ // Destroy swapchain
+ vkDestroySwapchainKHR(device, swapchain, NULL);
+
+ // Destroy device, surface, and instance
+ vkDestroyDevice(device, NULL);
+ vkDestroySurfaceKHR(instance, surface, NULL);
+ vkDestroyInstance(instance, NULL);
+
+ // Destroy GLFW window
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}
--- /dev/null
+#version 450
+
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
+
--- /dev/null
+#version 450
+
+// Normalized Device Coordinates
+vec2 positions[3] = vec2[](
+ vec2( 0.0, -0.5),
+ vec2( 0.5, 0.5),
+ vec2(-0.5, 0.5)
+);
+
+void main() {
+ gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
+}
+
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = triangle.vert triangle.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_triangle
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+s 0
+usemtl Material
+f 1/1/1 5/2/1 7/3/1 3/4/1
+f 4/5/2 3/4/2 7/6/2 8/7/2
+f 8/8/3 7/9/3 5/10/3 6/11/3
+f 6/12/4 2/13/4 4/5/4 8/14/4
+f 2/13/5 1/1/5 3/4/5 4/5/5
+f 6/11/6 5/10/6 1/1/6 2/13/6
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkRenderPass renderPass;
+VkFramebuffer* framebuffers;
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+GLFWwindow* window;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[2];
+ float color[3];
+} Vertex;
+
+// vertices don't need a z value. you only can write z and y to screen we can
+// use the projecion formula to change this
+Vertex vertices[] = {
+ {{ 0.0f, -0.5f }, { 1.0f, 0.0f, 0.0f }},
+ {{ 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f }},
+ {{-0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f }},
+};
+uint32_t vertexCount = sizeof(vertices)/sizeof(vertices[0]);
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[16];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+ swapchainFormat = surf_fmt.format;
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount+1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace,
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ .clipped = VK_TRUE
+ };
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .components = {VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY,VK_COMPONENT_SWIZZLE_IDENTITY},
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+/* --- Render pass --- */
+void create_render_pass() {
+ VkAttachmentDescription att = {
+ .format = swapchainFormat,
+ .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
+ };
+ VkAttachmentReference colorRef = {0,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
+ VkSubpassDescription subpass = {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorRef
+ };
+ VkRenderPassCreateInfo rpInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &att,
+ .subpassCount = 1,
+ .pSubpasses = &subpass
+ };
+ if(vkCreateRenderPass(device,&rpInfo,NULL,&renderPass)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create render pass\n"); exit(1);
+ }
+}
+
+/* --- Framebuffers --- */
+void create_framebuffers() {
+ framebuffers = malloc(sizeof(VkFramebuffer)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkFramebufferCreateInfo fbInfo = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = renderPass,
+ .attachmentCount = 1,
+ .pAttachments = &swapchainImageViews[i],
+ .width = swapchainExtent.width,
+ .height = swapchainExtent.height,
+ .layers = 1
+ };
+ if(vkCreateFramebuffer(device,&fbInfo,NULL,&framebuffers[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create framebuffer\n"); exit(1);
+ }
+ }
+}
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = sizeof(vertices),
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+ if(vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create vertex buffer\n"); exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = 0
+ };
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+ for(uint32_t i=0;i<memProps.memoryTypeCount;i++){
+ if((memReq.memoryTypeBits & (1<<i)) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
+ (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)){
+ allocInfo.memoryTypeIndex = i;
+ break;
+ }
+ }
+
+ if(vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate vertex buffer memory\n"); exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufInfo.size, 0, &data);
+ memcpy(data, vertices, bufInfo.size);
+ vkUnmapMemory(device, vertexBufferMemory);
+}
+
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("triangle.vert.spv");
+ VkShaderModule fragModule = load_shader("triangle.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(Vertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+ VkVertexInputAttributeDescription attrDesc[2] = {
+ {0,0,VK_FORMAT_R32G32B32_SFLOAT,offsetof(Vertex,pos)},
+ {1,0,VK_FORMAT_R32G32B32_SFLOAT,offsetof(Vertex,color)}
+ };
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=2,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_FILL, .cullMode=VK_CULL_MODE_BACK_BIT,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPipelineLayoutCreateInfo layoutInfo = { .sType=VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
+ vkCreatePipelineLayout(device,&layoutInfo,NULL,&pipelineLayout);
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = { .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = stages;
+ pipelineInfo.pVertexInputState = &vertexInput;
+ pipelineInfo.pInputAssemblyState = &inputAssembly;
+ pipelineInfo.pViewportState = &vpState;
+ pipelineInfo.pRasterizationState = &rasterizer;
+ pipelineInfo.pMultisampleState = &multisampling;
+ pipelineInfo.pColorBlendState = &colorBlending;
+ pipelineInfo.layout = pipelineLayout;
+ pipelineInfo.renderPass = renderPass;
+ pipelineInfo.subpass = 0;
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,&graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+/* --- Record command buffers --- */
+void record_command_buffers() {
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkCommandBufferBeginInfo begin={.sType=VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
+ vkBeginCommandBuffer(commandBuffers[i],&begin);
+
+ VkClearValue clearColor = {{0.1f,0.1f,0.1f,1.0f}};
+ VkRenderPassBeginInfo rpBegin = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = renderPass,
+ .framebuffer = framebuffers[i],
+ .renderArea = {.offset={0,0},.extent=swapchainExtent},
+ .clearValueCount = 1,
+ .pClearValues = &clearColor
+ };
+
+ vkCmdBeginRenderPass(commandBuffers[i],&rpBegin,VK_SUBPASS_CONTENTS_INLINE);
+ vkCmdBindPipeline(commandBuffers[i],VK_PIPELINE_BIND_POINT_GRAPHICS,graphicsPipeline);
+
+ VkDeviceSize offsets[] = {0};
+ vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, &vertexBuffer, offsets);
+ vkCmdDraw(commandBuffers[i], vertexCount, 1, 0, 0);
+
+ vkCmdEndRenderPass(commandBuffers[i]);
+ vkEndCommandBuffer(commandBuffers[i]);
+ }
+}
+
+/* --- Draw frame --- */
+void draw_frame() {
+ uint32_t imgIndex;
+ vkAcquireNextImageKHR(device,swapchain,UINT64_MAX,VK_NULL_HANDLE,VK_NULL_HANDLE,&imgIndex);
+
+ VkSubmitInfo submit={.sType=VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount=1,
+ .pCommandBuffers=&commandBuffers[imgIndex]};
+ vkQueueSubmit(graphicsQueue,1,&submit,VK_NULL_HANDLE);
+ vkQueueWaitIdle(graphicsQueue);
+
+ VkPresentInfoKHR present={.sType=VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .swapchainCount=1,
+ .pSwapchains=&swapchain,
+ .pImageIndices=&imgIndex};
+ vkQueuePresentKHR(graphicsQueue,&present);
+}
+
+/* --- Main --- */
+int main(void) {
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+ uint32_t ext_count=0;
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+ vkCreateInstance(&ici,NULL,&instance);
+ volkLoadInstance(instance);
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8]; vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ physical=devices[0];
+
+ uint32_t qcount=0; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ VkQueueFamilyProperties qprops[16]; vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres){graphicsQueueFamily=i;found=1;break;}
+ }
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ create_render_pass();
+ create_framebuffers();
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+ create_vertex_buffer();
+ record_command_buffers();
+
+ while(!glfwWindowShouldClose(window)){
+ glfwPollEvents();
+ draw_frame();
+ }
+
+ vkDeviceWaitIdle(device);
+
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+ for(uint32_t i=0;i<swapchainImageCount;i++) vkDestroyFramebuffer(device,framebuffers[i],NULL);
+ free(framebuffers); free(commandBuffers); free(swapchainImageViews); free(swapchainImages);
+ vkDestroyPipeline(device,graphicsPipeline,NULL);
+ vkDestroyPipelineLayout(device,pipelineLayout,NULL);
+ vkDestroyRenderPass(device,renderPass,NULL);
+ vkDestroySwapchainKHR(device,swapchain,NULL);
+ vkDestroyDevice(device,NULL);
+ vkDestroySurfaceKHR(instance,surface,NULL);
+ vkDestroyInstance(instance,NULL);
+ glfwDestroyWindow(window); glfwTerminate();
+ return 0;
+}
--- /dev/null
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")
--- /dev/null
+#version 450
+layout(location = 0) in vec3 fragColor;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(fragColor, 1.0);
+}
--- /dev/null
+#version 450
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec3 inColor;
+
+layout(location = 0) out vec3 fragColor;
+
+void main() {
+ gl_Position = vec4(inPos, 1.0);
+ fragColor = inColor;
+}
--- /dev/null
+Mention memory packing. talk about how c packs it's memory and how memory
+alignment works
+
+You can minimize wasted space by ordering your struct members from *largest*
+*alignment to smallest*.
+
+Bad Packing (12 bytes):
+
+[source, c]
+----
+struct Bad {
+ char a; // 1
+ // 3 padding
+ int b; // 4
+ char c; // 1
+ // 3 padding
+};
+----
+
+Good Packing (8 bytes):
+
+[source, c]
+----
+struct Good {
+ int b; // 4
+ char a; // 1
+ char c; // 1
+ // 2 padding at the end to make the total 8 (multiple of 4)
+};
+----
+
+== Suballocation
+
+* Don't allocate a separate memory block for each resourece.
+ - Small limit on maximum number of allocations (e.g. 4096).
+
+* Allocate bigger blocks and sub-allocate ranges for your resuorces.
+ - 256 MiB is good default block size.
+
+ - For heaps <= 1 GiB use smaller blocks. (e.g. heap size / 8).
+
+* Allocations are slow.
+ - Prefer not to allocate or free memory blocks during gameplay to avoid
+ hitching.
+
+ - If you need to, you can do it on background thread.
+
+
+== Allocating Memory in Vulkan
+
+To Allocate resources and memory in Vulkan, you use a "split-level" approach.
+You don't just call one function to get a texture; you call one set of function
+to create the *Logical Object* and another set to handle the *Physical Memory*.
+
+Here is a step-by-step workflow and the specific functions involved.
+
+=== Phase 1: Creating the Resource (The "Empty Shell")
+
+First, you create the handle that defines _what_ the resource is (size, format,
+usage). At this stage, the resource has no memory backed to it.
+
+* For Buffers (Vertex/Index Data): `vkCreateBuffer`
+
+* For Images (Textures/Depth buffers): `vkCreateImage`
+
+=== Phase 2: Determining Requirements
+
+Since different GPUs have different rules (e.g., an NVIDIA GPU might need a
+texture to be aligned to 1024 bytes), while an AMD one needs 256), you must ask
+the driver for the "bill."
+
+* *Function*: `vkGetBufferMemoryRequirements` or `vkGetImageMemoryRequirements`
+
+* *Result*: A `vkMemoryRequirements` struct telling you:
+
+ - *Size*: Total bytes needed (often larger than the raw data due to
+ padding).
+
+ - *Alignment*: The "start address" requirement (e.g., must be a multiple of
+ 64).
+
+ - *MemoryTypeBits*: Which "heaps" on the GPU are compatible with this
+ resource.
+
+=== Phase 3: Allocating the Block (The "Land Purchase")
+
+As your text mentions, you don't want to do this for every single item. You
+allocate a large chunk of memory.
+
+* *function*: `vkAllocateMemory`
+
+* *Input*: `vkMemoryAllocateInfo` (specifying the size -- e.g., 256 MiB) -- and
+ the memory type index).
+
+* *Limit*: Remember, you can only call this ~4096 times total in many drivers,
+ so use it sparingly
+
+=== Phase 4: Sub-allocation (The "Slicing")
+
+This is a manual step. You take the offset (the starting byte) within your 256
+MiB block where you want this specific buffer or image to live.
+
+* *Function*: `vkBindBufferMemory` or `vkBindImageMemory`
+
+* *Argument*: You provide the *Resource*, the *Memory Block*, and the *Offset*.
+
+[cols="1,2,2", options="header"]
+|===
+| Step | Function Call | Purpose
+
+| *1* | `vkCreateBuffer(...)`
+| Create a 1 MB Vertex Buffer object (the "Logical" handle).
+
+| *2* | `vkGetBufferMemoryRequirements(...)`
+| Query the GPU for the "bill": Actual size needed plus alignment padding.
+
+| *3* | `vkAllocateMemory(...)`
+| Allocate a **256 MiB** giant block of physical VRAM from the OS.
+
+| *4* | `vkBindBufferMemory(buffer, block, 0)`
+| Link the first buffer to the start (offset 0) of that 256 MiB block.
+
+| *5* | `vkBindBufferMemory(nextBuf, block, 1048576)`
+| Link the next buffer at the next available aligned offset in the same block.
+|===
+
+== Over-commitment
+
+What happens when you exceed the maximum amount of physical video memroy?
+
+* It depends on the driver
+ ** Allocation may fail (VK_ERROR_OUT_OF_DEVICE_MEMORY).
+ ** Allocation may succeed (VK_SUCCESS).
+ *** Some blocks are silently migrated to system memory
+
+* Blocks may be migrated to sytem memory anyway.
+ ** You are not alone - other applications can use video memroy.
+ ** Using blocks migrated to system memory on GPU degrades performace
+
+* Don't use 100% of the heap
+ ** Leave room fro implicit resources
+ ** (e.g. 20% of DEVICE_LOCAL, 33% of DEVICE_LOCAL + HOST_VISIBLE).
+
+Explain mip levels
+
+1. VkImage: The Data
+A VkImage is the actual block of memory that contains the pixel values. It defines the "physical" properties:
+
+How big is it? (Width x Height)
+
+What is the format? (e.g., R8G8B8A8_UNORM)
+
+How many mip-levels does it have?
+
+However, you cannot bind a VkImage directly to a shader or a framebuffer. Vulkan requires you to be more specific about how you intend to use those pixels.
+
+2. VkImageView: The Interpretation
+A VkImageView is a wrapper around the image. It tells the GPU how to interpret the raw bits. It answers questions like:
+
+Dimensionality: Should I treat this 2D image as a 2D texture, or as a single layer of a 3D texture?
+
+Aspect: Am I looking at the "Color" part of the image, or the "Depth" part?
+
+Swizzling: Do I want to swap the Red and Blue channels when the shader reads them?
+
+Range: Am I looking at all the mip-maps, or just the top one?
+
+== Links
+* https://docs.vulkan.org/spec/latest/chapters/fundamentals.html
--- /dev/null
+CC = gcc
+CFLAGS = -std=c11 -O2
+
+# Set path to volk.h directory
+VOLK_PATH ?= ../../../tools/vulkan-1.4.328.1/x86_64/include
+CFLAGS += -I$(VOLK_PATH)/volk
+
+LIBGLFW = `pkg-config --static --libs glfw3`
+LIBVULKAN = `pkg-config --static --libs vulkan`
+
+LDFLAGS = -lm $(LIBGLFW) $(LIBVULKAN)
+
+SHADERS = cube.vert cube.frag
+SPV = $(SHADERS:.vert=.vert.spv)
+SPV := $(SPV:.frag=.frag.spv)
+TARGET = vk_cube
+SRC = main.c
+
+# Default target: compile shaders and main program
+all: $(SPV) $(TARGET)
+
+# Compile vertex shader to SPIR-V
+%.vert.spv: %.vert
+ glslc $< -o $@
+
+# Compile fragment shader to SPIR-V
+%.frag.spv: %.frag
+ glslc $< -o $@
+
+# Compile main.c
+$(TARGET): $(SRC)
+ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
+
+parser: objParser.c
+ gcc objParser.c -o objParser
+
+# Clean generated files
+clean:
+ rm -f $(TARGET) *.spv
--- /dev/null
+Great question — this gets to **one of the core ideas Vulkan is built on**, and it’s very different from OpenGL / D3D11.
+
+I’ll explain **what binding memory means**, **why Vulkan requires it**, and **what actually happens inside the driver/GPU**.
+
+---
+
+## The short answer (intuition first)
+
+> **Creating a buffer or image does *not* allocate memory.
+> Binding memory is what gives that object actual storage.**
+
+Until you bind memory:
+
+* A `VkBuffer` or `VkImage` is just a **description**
+* It has **no backing bytes**
+* The GPU cannot read or write it
+
+Binding memory:
+
+* Assigns a **physical region of GPU-accessible memory**
+* Tells Vulkan *where* the object’s data lives
+* Fixes alignment and offset requirements
+
+---
+
+## What Vulkan objects really are
+
+### 1️⃣ `VkBuffer` / `VkImage` (no memory yet)
+
+When you do:
+
+```c
+vkCreateBuffer(...)
+vkCreateImage(...)
+```
+
+You are creating:
+
+* A **metadata object**
+* Size, format, usage flags
+* No storage
+
+Think of it like:
+
+> “I want a 2D image, 512×512, RGBA, sampled and copied into”
+
+But **no memory is reserved yet**.
+
+---
+
+### 2️⃣ `VkDeviceMemory` (raw memory block)
+
+When you do:
+
+```c
+vkAllocateMemory(...)
+```
+
+You get:
+
+* A chunk of memory
+* From a specific **heap**
+* With specific properties (HOST_VISIBLE, DEVICE_LOCAL, etc.)
+
+This memory:
+
+* Is **untyped**
+* Has no idea if it will store vertices, textures, or uniforms
+
+It’s just **bytes**.
+
+---
+
+### 3️⃣ Binding connects the two
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+vkBindImageMemory(device, image, memory, offset);
+```
+
+This says:
+
+> “This buffer/image *lives* starting at this byte offset in that memory block.”
+
+After binding:
+
+* The buffer/image has **real storage**
+* GPU accesses to that object resolve to actual memory addresses
+* Alignment requirements are guaranteed
+
+---
+
+## What binding does internally
+
+When you bind memory, the driver:
+
+1. Verifies alignment and size requirements
+2. Computes GPU virtual addresses
+3. Programs internal page tables / mappings
+4. Associates resource metadata with physical memory
+
+On modern GPUs this involves:
+
+* IOMMU / GPU MMU
+* Virtual address translation
+* Cache policy setup
+
+📌 This is why Vulkan **cannot auto-bind** memory — it would hide too many important decisions.
+
+---
+
+## Why Vulkan separates creation and binding
+
+### 🔹 Explicit memory control
+
+Vulkan allows things like:
+
+* Sub-allocating **multiple buffers in one allocation**
+* Packing resources tightly
+* Choosing different heaps per resource
+* Avoiding fragmentation
+
+Example:
+
+```c
+VkDeviceMemory bigBlock;
+vkAllocateMemory(..., &bigBlock);
+
+vkBindBufferMemory(device, vertexBuffer, bigBlock, 0);
+vkBindBufferMemory(device, indexBuffer, bigBlock, 65536);
+vkBindImageMemory (device, textureImage, bigBlock, 131072);
+```
+
+This is **impossible** in OpenGL.
+
+---
+
+### 🔹 Performance predictability
+
+The driver:
+
+* Does **zero hidden allocations**
+* Does **no implicit migrations**
+* Never guesses where to put memory
+
+You choose:
+
+* Fast GPU memory
+* CPU-visible memory
+* BAR-mapped memory
+* Unified memory
+
+---
+
+### 🔹 Multi-heap GPUs
+
+GPUs often have:
+
+* VRAM
+* System RAM
+* BAR-mapped regions
+* Cached vs uncached heaps
+
+Binding ensures:
+
+* The resource lives in a compatible heap
+* Usage flags match memory properties
+
+---
+
+## What happens if you don’t bind?
+
+If you try to use an unbound resource:
+
+* Validation layers scream
+* Behavior is **undefined**
+* GPU may fault or crash
+
+Because:
+
+> The resource literally has no address.
+
+---
+
+## Mental model (very important)
+
+```
+VkBuffer / VkImage
+ ↓ describes usage & format
+VkDeviceMemory
+ ↓ raw storage
+vkBind*Memory
+ ↓ connects them
+GPU can now access bytes
+```
+
+---
+
+## Why other APIs hide this
+
+| API | Memory model |
+| ------ | --------------------------- |
+| OpenGL | Driver decides everything |
+| D3D11 | Driver allocates implicitly |
+| Vulkan | You control everything |
+| D3D12 | Same as Vulkan |
+
+Vulkan trades **verbosity** for:
+
+* Zero surprises
+* Maximum performance
+* Precise memory control
+
+---
+
+## Key takeaway
+
+> **Binding memory is what turns a “resource description” into a “real GPU resource.”**
+
+Without binding:
+
+* No storage
+* No address
+* No GPU access
+
+---
+
+If you want next, I can:
+
+* Show a diagram of heaps → memory → buffers
+* Explain why images have stricter alignment
+* Explain aliasing & memory reuse
+* Explain how bindless / sparse resources change this
+
+Just say the word.
--- /dev/null
+Ah, that sentence is a bit dense. Let’s unpack it carefully.
+
+---
+
+### 1️⃣ Context
+
+Vulkan is a low-level API. When you create resources (like images, buffers, pipelines, etc.), it can be **expensive** in terms of time or GPU/driver work. For example, creating a pipeline involves compiling shaders, setting up internal GPU structures, etc.
+
+The sentence usually appears in **documentation about command pools or resource creation**:
+
+> *“Allow the implementation to amortize the cost of resource creation across multiple command buffers.”*
+
+---
+
+### 2️⃣ Breaking it down
+
+* **Amortize the cost:**
+ In computer science, “amortize” means **spread out a one-time expensive operation over multiple uses** so that each individual use appears cheaper.
+
+* **Resource creation:**
+ This refers to creating Vulkan objects (buffers, images, pipelines, etc.) or allocating memory for them.
+
+* **Across multiple command buffers:**
+ Instead of paying the full cost every time you create a command buffer or resource, the driver can **share internal data or precompute things**, so that subsequent command buffers don’t have to redo all the work.
+
+---
+
+### 3️⃣ In practical Vulkan terms
+
+* If a command pool or pipeline is created with the right flags (like `VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT`), the driver might:
+
+ * Reuse internal memory for command buffers
+ * Cache GPU-side structures
+ * Avoid redundant validations
+* This **reduces the overhead per command buffer**, especially when creating many of them.
+
+---
+
+### 4️⃣ Analogy
+
+Think of it like **printing tickets at a theme park**:
+
+* Printing one ticket from scratch each time is slow.
+* If the printer can **preprint some templates or share ink across tickets**, then each ticket after the first is faster.
+
+That’s what “amortizing the cost” means in Vulkan: the driver can share the work of resource creation across multiple command buffers or operations, instead of starting from scratch every time.
+
+---
+
+If you want, I can make a **tiny diagram showing how Vulkan command buffers and resource creation share internal data** — it makes “amortization” very visual.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely — this is a **great** function to walk through, because it touches almost every core Vulkan concept: memory types, staging, layout transitions, and synchronization.
+
+I’ll go **top → bottom**, explaining **what each block does and *why***.
+I’ll avoid hand-wavy language and stick to concrete GPU/CPU behavior.
+
+---
+
+# `create_texture_image()` — line-by-line walkthrough
+
+---
+
+## 1️⃣ CPU-side pixel data
+
+```c
+uint32_t pixels[] = {
+ 0xFFFFFFFF, 0xFFFF69B4,
+ 0xFFFF69B4, 0xFFFFFFFF
+};
+```
+
+* This is a **2×2 RGBA texture** stored in **CPU memory**
+* Each `uint32_t` is `0xAABBGGRR` (little-endian)
+* Vulkan does **not** care where this comes from (file, procedural, etc.)
+
+---
+
+```c
+VkDeviceSize size = sizeof(pixels);
+```
+
+* Size of the upload = **16 bytes**
+* Used for buffer creation and `memcpy`
+
+---
+
+## 2️⃣ Create a staging buffer (CPU → GPU bridge)
+
+### Why a staging buffer?
+
+Because **DEVICE_LOCAL images are not CPU-mappable** on discrete GPUs.
+
+---
+
+```c
+VkBuffer staging;
+VkDeviceMemory stagingMem;
+```
+
+Handles for:
+
+* A temporary buffer
+* The memory backing it
+
+---
+
+### Create the buffer object
+
+```c
+VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+};
+vkCreateBuffer(device, &bufInfo, NULL, &staging);
+```
+
+This creates a **buffer object** that:
+
+* Holds `size` bytes
+* Can be used as a **transfer source**
+
+⚠️ No memory is allocated yet — this is just the *buffer description*.
+
+---
+
+### Query memory requirements
+
+```c
+VkMemoryRequirements memReq;
+vkGetBufferMemoryRequirements(device, staging, &memReq);
+```
+
+The driver tells you:
+
+* Required memory size
+* Required alignment
+* Which memory types are allowed (`memoryTypeBits`)
+
+---
+
+### Allocate HOST_VISIBLE + HOST_COHERENT memory
+
+```c
+VkMemoryAllocateInfo alloc = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+};
+```
+
+This means:
+
+| Flag | Meaning |
+| --------------- | ---------------------- |
+| `HOST_VISIBLE` | CPU can map it |
+| `HOST_COHERENT` | No manual flush needed |
+
+So:
+
+* CPU can write
+* GPU can read
+* Writes are automatically visible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &stagingMem);
+vkBindBufferMemory(device, staging, stagingMem, 0);
+```
+
+Now:
+
+* Physical memory is allocated
+* The buffer is bound to that memory
+
+---
+
+### Map and copy pixels
+
+```c
+void* data;
+vkMapMemory(device, stagingMem, 0, size, 0, &data);
+memcpy(data, pixels, size);
+vkUnmapMemory(device, stagingMem);
+```
+
+What happens:
+
+* CPU gets a pointer
+* Copies 16 bytes
+* Unmaps (flush is automatic because COHERENT)
+
+📌 **At this point, pixel data is in GPU-visible memory**
+
+---
+
+## 3️⃣ Create the GPU image (the actual texture)
+
+---
+
+```c
+VkImageCreateInfo img = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {2, 2, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage =
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+};
+vkCreateImage(device, &img, NULL, &textureImage);
+```
+
+This describes:
+
+* A **2D image**
+* 2×2 pixels
+* Optimal tiling (GPU-friendly layout)
+* Can be:
+
+ * Copied **into**
+ * Sampled by shaders
+
+Still **no memory allocated** yet.
+
+---
+
+### Allocate DEVICE_LOCAL memory
+
+```c
+vkGetImageMemoryRequirements(device, textureImage, &memReq);
+
+alloc.allocationSize = memReq.size;
+alloc.memoryTypeIndex =
+ find_memory_type(
+ memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+```
+
+This means:
+
+* Memory physically close to the GPU
+* Fast for sampling
+* Not CPU-accessible
+
+---
+
+```c
+vkAllocateMemory(device, &alloc, NULL, &textureMemory);
+vkBindImageMemory(device, textureImage, textureMemory, 0);
+```
+
+Now:
+
+* Image storage exists
+* GPU can access it efficiently
+
+---
+
+## 4️⃣ Record a one-time command buffer
+
+### Why?
+
+Because **copies and layout transitions happen on the GPU**
+
+---
+
+```c
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1
+};
+vkAllocateCommandBuffers(device, &cbAlloc, &cb);
+```
+
+---
+
+```c
+VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+};
+vkBeginCommandBuffer(cb, &begin);
+```
+
+---
+
+## 5️⃣ Layout transition: UNDEFINED → TRANSFER_DST
+
+```c
+VkImageMemoryBarrier toTransfer = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .image = textureImage,
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+};
+```
+
+Why?
+
+* Vulkan images start in **UNDEFINED**
+* Copying requires **TRANSFER_DST**
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toTransfer
+);
+```
+
+This tells Vulkan:
+
+> “Before transfer writes, make sure the image is ready.”
+
+---
+
+## 6️⃣ Copy buffer → image
+
+```c
+VkBufferImageCopy copy = {
+ .imageSubresource = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1
+ },
+ .imageExtent = {2, 2, 1}
+};
+```
+
+---
+
+```c
+vkCmdCopyBufferToImage(
+ cb,
+ staging,
+ textureImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1, ©
+);
+```
+
+GPU copies the data.
+
+📌 This is **very fast** — no CPU involvement.
+
+---
+
+## 7️⃣ Layout transition: TRANSFER_DST → SHADER_READ
+
+```c
+VkImageMemoryBarrier toSample = toTransfer;
+toSample.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+toSample.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+toSample.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+toSample.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+```
+
+Now the image becomes:
+
+* Readable by fragment shaders
+* Cache-safe
+
+---
+
+```c
+vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toSample
+);
+```
+
+---
+
+## 8️⃣ Submit and wait (safely)
+
+```c
+vkEndCommandBuffer(cb);
+```
+
+---
+
+```c
+VkFence fence;
+VkFenceCreateInfo fenceInfo = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
+};
+vkCreateFence(device, &fenceInfo, NULL, &fence);
+```
+
+Fence lets CPU know when GPU is done.
+
+---
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, fence);
+vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+vkDestroyFence(device, fence, NULL);
+```
+
+CPU blocks until:
+
+* Copy completes
+* Image is ready for sampling
+
+✅ **Much better than `vkQueueWaitIdle`**
+
+---
+
+## 9️⃣ Cleanup staging resources
+
+```c
+vkFreeCommandBuffers(device, commandPool, 1, &cb);
+vkDestroyBuffer(device, staging, NULL);
+vkFreeMemory(device, stagingMem, NULL);
+```
+
+Staging buffer is no longer needed.
+
+---
+
+# Final mental model (important)
+
+```
+CPU RAM
+ ↓ memcpy
+HOST_VISIBLE buffer
+ ↓ GPU copy
+DEVICE_LOCAL image
+ ↓ shader sampling
+fragment shader
+```
+
+---
+
+## You did this function **correctly**
+
+* Correct memory types
+* Correct layout transitions
+* Correct synchronization
+* Correct use of fences
+* Correct staging pattern
+
+This function is **textbook Vulkan**.
+
+If you want next, I can:
+
+* Annotate it with **timeline diagrams**
+* Show how to batch uploads
+* Explain how this changes with BAR memory
+* Explain why this does *not* cause stutter
+
+Just tell me 👍
+
--- /dev/null
+In this context, **“depth” means the per-pixel (or per-sample) distance from the camera used to decide what is in front of what**.
+
+I’ll explain it from the ground up, then tie it directly to **render passes and subpasses**.
+
+---
+
+## 1. What “depth” is (fundamental idea)
+
+**Depth** is a numerical value that represents **how far a fragment is from the camera**.
+
+For every pixel the GPU draws, it computes a depth value:
+
+```
+closer to camera → smaller depth
+farther away → larger depth
+```
+
+This value is stored in a **depth buffer** (also called a **Z buffer**).
+
+---
+
+## 2. Why depth exists at all
+
+Without depth, triangles would be drawn **in submission order**:
+
+```
+draw triangle A
+draw triangle B
+```
+
+If B is behind A but drawn later → it appears in front ❌
+
+Depth fixes this by answering:
+
+> “Is this fragment closer than what was drawn before?”
+
+---
+
+## 3. How depth testing works
+
+For each fragment:
+
+1. Compute its depth (from vertex positions → clip space)
+2. Compare it against the stored depth value
+3. If it passes → draw and update depth
+4. If it fails → discard fragment
+
+Typical comparison:
+
+```c
+VK_COMPARE_OP_LESS
+```
+
+Meaning:
+
+> “Only draw if this fragment is closer.”
+
+---
+
+## 4. Where depth comes from (pipeline view)
+
+### Vertex shader
+
+Outputs:
+
+```glsl
+gl_Position
+```
+
+This contains **clip-space coordinates**.
+
+### After projection
+
+The GPU computes:
+
+```
+depth = z / w
+```
+
+Mapped into:
+
+```
+[0, 1] (Vulkan default)
+```
+
+This is what ends up in the depth buffer.
+
+---
+
+## 5. The depth buffer itself
+
+Depth is stored in a **depth attachment**, just like color is stored in a color attachment.
+
+Common formats:
+
+* `VK_FORMAT_D32_SFLOAT`
+* `VK_FORMAT_D24_UNORM_S8_UINT`
+
+Depth attachments:
+
+* Are not visible on screen
+* Exist only for visibility testing (and sometimes reading later)
+
+---
+
+## 6. Depth + subpasses (this is what you’re asking about)
+
+When you see:
+
+```
+Subpass 0: write color + depth
+Subpass 1: read depth, write color
+```
+
+It means:
+
+### Subpass 0
+
+* Draw geometry
+* Write:
+
+ * Color → color attachment
+ * Depth → depth attachment
+
+This establishes **visibility information**.
+
+---
+
+### Subpass 1
+
+* Does **not redraw geometry**
+* Reads depth to know:
+
+ * Which pixels belong to visible surfaces
+ * How far they are from the camera
+
+Used for:
+
+* Deferred lighting
+* Screen-space effects
+* Depth-aware post-processing
+
+---
+
+## 7. Reading depth in a later subpass
+
+Depth can be read as:
+
+### A) Input attachment (subpass-local, fast)
+
+```glsl
+subpassInput depthInput;
+```
+
+### B) Sampled image (general, slower)
+
+```glsl
+sampler2D depthTex;
+```
+
+This allows effects like:
+
+* Screen-space ambient occlusion (SSAO)
+* Volumetric fog
+* Soft particles
+* Deferred lighting
+
+---
+
+## 8. Depth vs color (key difference)
+
+| Color | Depth |
+| ------------ | ---------------------- |
+| What you see | What is visible |
+| RGB(A) | Single scalar value |
+| Displayed | Used for testing |
+| Final output | Internal decision data |
+
+Depth controls **visibility**, not appearance.
+
+---
+
+## 9. Depth and multisampling (important)
+
+With MSAA:
+
+* Each pixel has **multiple depth samples**
+* Each sample can pass/fail independently
+* This is why MSAA fixes edge aliasing correctly
+
+Depth must use the **same sample count** as color.
+
+---
+
+## 10. What depth is NOT
+
+❌ Not “distance in meters”
+❌ Not linear by default
+❌ Not screen-space Z
+❌ Not optional for 3D rendering
+
+Depth is **non-linear**, optimized for precision near the camera.
+
+---
+
+## 11. Mental model (best way to remember)
+
+> **Depth is a hidden image that stores “how close” each pixel is, and the GPU uses it to decide what is allowed to be drawn.**
+
+---
+
+## 12. One-sentence summary
+
+> **Depth is a per-pixel value stored in a depth buffer that allows the GPU to determine which fragments are visible and to reuse that visibility information across subpasses.**
+
+---
+
+If you want, I can next:
+
+* Show **exact Vulkan structs for a depth attachment**
+* Explain **why depth is non-linear**
+* Walk through **depth testing step-by-step**
+* Explain **stencil vs depth** (they’re often confused)
+
+Just tell me.
--- /dev/null
+Got it — you’re asking **why depth is “not optional” for 3D rendering**.
+
+This is a *fundamental* reason tied to how projection and visibility work, not just a Vulkan convention.
+
+---
+
+## Short answer (intuition)
+
+**Because in 3D, multiple surfaces project onto the same pixel, and without depth there is no correct way to know which one is visible.**
+
+2D doesn’t have this problem.
+3D does.
+
+---
+
+## What actually goes wrong without depth
+
+In 3D, after projection, **everything becomes 2D**:
+
+```
+3D scene → projection → screen pixels
+```
+
+Multiple triangles can map to the **same pixel**:
+
+```
+Camera
+ ↓
+[Triangle A] ← closer
+[Triangle B] ← farther
+```
+
+On screen:
+
+```
+Pixel (x, y)
+```
+
+Without depth, the GPU only knows **draw order**.
+
+---
+
+## Case 1: No depth buffer (painter’s algorithm)
+
+If you don’t use depth:
+
+```c
+vkCmdDraw(triangleA);
+vkCmdDraw(triangleB);
+```
+
+Whichever triangle is drawn **last** wins.
+
+This causes:
+
+* Objects popping in front of each other
+* Incorrect overlap
+* Massive CPU-side sorting requirements
+* Failure in cases like:
+
+ * Intersecting geometry
+ * Self-overlapping meshes
+ * Transparent + opaque mixes
+
+This is **not solvable in general**.
+
+---
+
+## Case 2: With depth buffer (correct solution)
+
+With depth testing:
+
+1. GPU stores depth per pixel
+2. For every fragment:
+
+ * Compare new depth vs stored depth
+ * Keep the closest one
+3. Order of draw calls **no longer matters**
+
+This gives:
+
+✔ Correct visibility
+✔ Intersecting geometry works
+✔ Self-occlusion works
+✔ Parallel GPU execution works
+
+---
+
+## Why the GPU *must* do this, not the CPU
+
+### You cannot sort triangles correctly in 3D
+
+There is **no total ordering** for triangles in 3D space.
+
+Example:
+
+* Triangle A overlaps B
+* B overlaps C
+* C overlaps A
+
+This is called a **cyclic overlap**.
+
+No draw order can fix this.
+
+Depth testing resolves it **per pixel**, which is the only correct level.
+
+---
+
+## Why this is fundamentally required by projection
+
+Perspective projection collapses depth:
+
+```
+(x, y, z) → (x / z, y / z)
+```
+
+That division by `z` destroys ordering information.
+
+Depth restores it.
+
+---
+
+## Can you “fake” 3D without depth?
+
+Yes, but with **severe limitations**:
+
+### When depth is *not* required:
+
+* Skyboxes
+* Fullscreen quads
+* Billboards drawn in strict order
+* 2.5D games (fixed layers)
+
+These avoid the general 3D visibility problem.
+
+---
+
+## Why Vulkan treats depth as a first-class concept
+
+Vulkan gives depth:
+
+* Its own attachment
+* Its own load/store ops
+* Its own layouts
+* Its own tests
+* Its own sampling path
+
+Because **the entire rasterization pipeline assumes depth exists**.
+
+---
+
+## The key mental model
+
+> **3D rendering is fundamentally a visibility problem.
+> Depth is the data structure that solves it.**
+
+Without depth, you don’t have 3D — you have a fragile illusion.
+
+---
+
+## One-sentence answer
+
+> **Depth is not optional in 3D rendering because projection causes multiple surfaces to map to the same pixel, and only per-pixel depth testing can correctly determine which surface is visible.**
+
+If you want, next we can:
+
+* Prove why triangle sorting is impossible in general
+* Walk through depth testing in Vulkan line-by-line
+* Explain why depth precision breaks at far distances
+* Show how deferred renderers rely entirely on depth
+
+Just say where you want to go.
--- /dev/null
+This is a **fundamental Vulkan question**, and the confusion is very normal. The short answer is:
+
+> **`malloc` / `calloc` allocate CPU memory.
+> `vkAllocateMemory` allocates GPU-accessible device memory.
+> They live in completely different worlds.**
+
+Let’s break it down cleanly and precisely.
+
+---
+
+## 1️⃣ `malloc` / `calloc` vs `vkAllocateMemory`
+
+### `malloc` / `calloc`
+
+* Allocates **process virtual memory**
+* Owned by the **CPU / OS**
+* Only the CPU can access it
+* The GPU **cannot see or use it**
+* Vulkan has **no idea** this memory exists
+
+```c
+void* data = malloc(1024); // CPU RAM only
+```
+
+You **cannot** bind this memory to:
+
+* `VkBuffer`
+* `VkImage`
+* Any Vulkan object
+
+---
+
+### `vkAllocateMemory`
+
+* Allocates memory from the **physical device**
+* Managed by **Vulkan + the GPU driver**
+* Can be:
+
+ * GPU-only (VRAM)
+ * CPU-visible (mapped system memory)
+* Can be **bound** to buffers and images
+
+```c
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+vkBindBufferMemory(device, buffer, memory, 0);
+```
+
+This is the **only way** Vulkan objects get usable memory.
+
+---
+
+## 2️⃣ Why Vulkan can’t use `malloc`
+
+The GPU:
+
+* Does not understand your process’s virtual address space
+* Does not participate in `malloc`
+* Cannot dereference CPU pointers
+
+Even if memory is physically the same (iGPU / UMA):
+
+* Vulkan still needs **explicit ownership, caching, and visibility rules**
+* The driver must know how the memory is used
+
+So Vulkan **must** control allocation.
+
+---
+
+## 3️⃣ Why Vulkan separates allocation from objects
+
+In OpenGL:
+
+* `glBufferData` silently allocates memory for you
+* You don’t control where or how
+
+In Vulkan:
+
+* You control:
+
+ * Memory location
+ * Cache behavior
+ * Sharing
+ * Lifetime
+
+This enables:
+
+* Zero-copy paths
+* Predictable performance
+* No hidden stalls
+
+---
+
+## 4️⃣ What Vulkan memory really is
+
+Think of Vulkan memory as:
+
+> A **handle to a chunk of physical device memory**,
+> not a pointer.
+
+```c
+VkDeviceMemory mem; // not a pointer!
+```
+
+The GPU accesses it through:
+
+* Bindings
+* Descriptors
+* Command buffers
+
+The CPU accesses it **only if HOST_VISIBLE**, via `vkMapMemory`.
+
+---
+
+## 5️⃣ Why staging buffers exist
+
+Because:
+
+* GPU VRAM is **fast but CPU-inaccessible**
+* CPU RAM is **slow but CPU-accessible**
+
+So Vulkan uses a two-step approach:
+
+```text
+CPU RAM (malloc / staging)
+ ↓ copy
+GPU VRAM (vkAllocateMemory DEVICE_LOCAL)
+```
+
+Except the staging buffer is still allocated via Vulkan,
+because the GPU must be able to read from it.
+
+---
+
+## 6️⃣ Common mistake (you avoided it)
+
+❌ This does NOT work:
+
+```c
+float* verts = malloc(sizeof(vertices));
+vkBindBufferMemory(buffer, (VkDeviceMemory)verts, 0); // invalid
+```
+
+Because:
+
+* That memory is not tracked by Vulkan
+* The GPU cannot see it
+
+---
+
+## 7️⃣ Why Vulkan memory feels “manual”
+
+Because it is designed for:
+
+* Engines
+* Drivers
+* Explicit control
+* Zero hidden sync
+
+Vulkan trades convenience for **absolute predictability**.
+
+---
+
+## TL;DR
+
+* `malloc` / `calloc` → CPU memory only
+* `vkAllocateMemory` → GPU-accessible device memory
+* Vulkan objects **must** use `vkAllocateMemory`
+* The GPU **cannot see malloc’d memory**
+* Explicit control = performance + no hidden stalls
+
+---
+
+If you want, next we can:
+
+* Walk through a **full buffer allocation lifecycle**
+* Explain **BAR memory vs VRAM** in more depth
+* Show how Vulkan allocators (VMA) work internally
+* Tie this back directly to your stutter issue
+
+Just tell me 👍
--- /dev/null
+Yes — this is a *really important* Vulkan concept, and you’re already **using it correctly** in your code. I’ll explain it from the ground up and tie it directly to what you wrote.
+
+---
+
+# 1️⃣ What “dynamic rendering” is (high-level)
+
+**Dynamic rendering** is a newer Vulkan feature that lets you:
+
+> **Render without creating a `VkRenderPass` or `VkFramebuffer`.**
+
+Instead of baking attachments and subpasses ahead of time, you:
+
+* Describe the attachments **at draw time**
+* Begin rendering **directly in the command buffer**
+* Bind pipelines that declare what formats they expect
+
+It removes a *huge* amount of boilerplate.
+
+---
+
+# 2️⃣ What Vulkan looked like *before* dynamic rendering
+
+### Old model (classic Vulkan)
+
+You had to create:
+
+1. `VkRenderPass`
+
+ * Attachments
+ * Subpasses
+ * Load/store ops
+2. `VkFramebuffer`
+
+ * One per swapchain image
+3. Pipeline tied **per render pass**
+
+```text
+RenderPass → Framebuffer → Pipeline → Draw
+```
+
+Problems:
+
+* Render passes were **compile-time objects**
+* Changing formats or attachments required rebuilding pipelines
+* Framebuffers had to match exactly
+
+This was powerful but **extremely rigid**.
+
+---
+
+# 3️⃣ What dynamic rendering replaces
+
+Dynamic rendering **removes**:
+
+| Old object | Replaced by |
+| -------------------- | ----------- |
+| `VkRenderPass` | ❌ Gone |
+| `VkFramebuffer` | ❌ Gone |
+| Subpass descriptions | ❌ Gone |
+
+Instead, Vulkan introduces:
+
+* `vkCmdBeginRendering`
+* `VkRenderingInfo`
+* `VkRenderingAttachmentInfo`
+* `VkPipelineRenderingCreateInfo`
+
+---
+
+# 4️⃣ What *you* are doing with dynamic rendering
+
+Let’s map your code to the concept.
+
+---
+
+## A) Enabling dynamic rendering on the device
+
+```c
+VkPhysicalDeviceDynamicRenderingFeatures dyn = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ .dynamicRendering = VK_TRUE
+};
+```
+
+This says:
+
+> “This device is allowed to use `vkCmdBeginRendering`.”
+
+Without this → **validation error / undefined behavior**.
+
+---
+
+## B) Declaring attachment formats in the pipeline
+
+```c
+VkPipelineRenderingCreateInfo rendering = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+};
+```
+
+This replaces:
+
+* `VkRenderPass`
+* Subpass attachment format matching
+
+It tells Vulkan:
+
+> “This pipeline expects **one color attachment** with this format.”
+
+Pipelines still need to know formats **up-front**, but not layouts, load ops, or framebuffers.
+
+---
+
+## C) Beginning rendering (the new render pass)
+
+```c
+vkCmdBeginRendering(cb, &ri);
+```
+
+This is the **dynamic equivalent** of:
+
+```c
+vkCmdBeginRenderPass(...)
+```
+
+Except:
+
+* No framebuffer
+* No render pass object
+* No subpass index
+
+---
+
+## D) Describing attachments at draw time
+
+```c
+VkRenderingAttachmentInfo color = {
+ .imageView = swapchainViews[img],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue.color = {{0,0,0,1}}
+};
+```
+
+This replaces:
+
+```text
+VkAttachmentDescription
+VkAttachmentReference
+VkSubpassDescription
+```
+
+All **combined**, but now:
+
+* Fully dynamic
+* Chosen per frame
+* Chosen per command buffer
+
+---
+
+## E) Ending rendering
+
+```c
+vkCmdEndRendering(cb);
+```
+
+Equivalent to:
+
+```c
+vkCmdEndRenderPass(cb);
+```
+
+---
+
+# 5️⃣ What dynamic rendering does **NOT** change
+
+Dynamic rendering does **not** change:
+
+| Thing | Still required |
+| ------------------ | -------------- |
+| Pipelines | ✅ |
+| Descriptor sets | ✅ |
+| Layout transitions | ✅ |
+| Synchronization | ✅ |
+| Image views | ✅ |
+
+This is why you still need:
+
+```c
+VkImageMemoryBarrier
+```
+
+Dynamic rendering does **not** manage layouts for you.
+
+---
+
+# 6️⃣ Why dynamic rendering exists
+
+### Goals:
+
+* Reduce boilerplate
+* Make Vulkan usable for small engines
+* Allow more flexible rendering graphs
+* Match modern APIs (DX12 / Metal)
+
+### Typical wins:
+
+* No framebuffer creation
+* No render pass combinatorics
+* Easier swapchain recreation
+* Much easier multipass rendering
+
+---
+
+# 7️⃣ How dynamic rendering maps conceptually
+
+Think of it like this:
+
+### Classic Vulkan
+
+```text
+RenderPass (static)
+ └─ Subpass (static)
+ └─ Attachments (static)
+```
+
+### Dynamic Rendering
+
+```text
+Command Buffer
+ └─ Begin Rendering
+ └─ Attachments (chosen now)
+```
+
+You move **render-graph decisions** from *initialization time* to *record time*.
+
+---
+
+# 8️⃣ Common misconceptions (important)
+
+### ❌ “Dynamic rendering is slower”
+
+No. Drivers internally build render passes anyway.
+
+### ❌ “Dynamic rendering removes the need for barriers”
+
+Absolutely not. You still must manage layouts and sync.
+
+### ❌ “Dynamic rendering is optional”
+
+Yes — but it’s becoming the **preferred path**.
+
+---
+
+# 9️⃣ When NOT to use dynamic rendering
+
+You might still want classic render passes if:
+
+* You need **subpass input attachments**
+* You rely on **tile-based GPU subpass optimizations**
+* You want strict render-graph control
+
+Most beginner/intermediate engines should **use dynamic rendering**.
+
+---
+
+# 🔑 One-sentence summary
+
+> **Dynamic rendering lets you render directly to images without pre-declaring render passes or framebuffers, by describing attachments dynamically inside the command buffer.**
+
+---
+
+If you want next, I can:
+
+* Rewrite your code in **classic render-pass style** to compare
+* Explain **why subpasses exist** and when they outperform dynamic rendering
+* Show **depth + MSAA** with dynamic rendering
+* Show how dynamic rendering maps to **Vulkan synchronization2**
+
+Just tell me.
--- /dev/null
+Absolutely — this is a **core Vulkan helper**, and understanding it deeply will make a lot of other Vulkan code “click”. I’ll go **line by line**, then give you a **mental model** at the end.
+
+---
+
+## Function signature
+
+```c
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props)
+```
+
+### What this function does (high level)
+
+It finds **which GPU memory type index** you should use when allocating memory for a buffer or image.
+
+Vulkan does **not** let you just say “give me GPU memory”.
+Instead, you must:
+
+1. Ask Vulkan **what kinds of memory are allowed** (`typeBits`)
+2. Ask for **what properties you want** (`props`)
+3. Pick a memory **type index** that satisfies both
+
+This function does exactly that.
+
+---
+
+## Get physical device memory info
+
+```c
+VkPhysicalDeviceMemoryProperties memProps;
+vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+```
+
+### What’s happening
+
+* Vulkan exposes **all memory types supported by the GPU**
+* This includes:
+
+ * Device-local VRAM
+ * Host-visible memory
+ * Coherent / cached memory
+ * Unified memory (on iGPUs)
+
+`memProps` contains:
+
+```c
+memProps.memoryTypeCount
+memProps.memoryTypes[i].propertyFlags
+```
+
+Typical GPU: **8–32 memory types**
+
+---
+
+## Loop over every memory type
+
+```c
+for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+```
+
+You are checking **each possible memory type index**.
+
+Important:
+
+* The return value of this function is **this index (`i`)**
+* This index is what you pass to `VkMemoryAllocateInfo.memoryTypeIndex`
+
+---
+
+## Check if this memory type is allowed
+
+```c
+if ((typeBits & (1 << i)) &&
+```
+
+### What `typeBits` means
+
+`typeBits` comes from:
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+```
+
+* `req.memoryTypeBits` is a **bitmask**
+* Each bit corresponds to a memory type index
+* If bit `i` is set → that memory type **can be used** for this resource
+
+### This line checks:
+
+> “Is memory type `i` compatible with this buffer/image?”
+
+If not, skip it.
+
+---
+
+## Check if it has the properties you want
+
+```c
+(memProps.memoryTypes[i].propertyFlags & props) == props)
+```
+
+### `propertyFlags` examples
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+VK_MEMORY_PROPERTY_HOST_CACHED_BIT
+```
+
+### This line means:
+
+> “Does this memory type include **all** the properties I requested?”
+
+Why bitwise AND?
+
+* `propertyFlags` may have **extra bits**
+* You only care that **your requested bits are present**
+
+Example:
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+```
+
+This is valid.
+
+---
+
+## If both conditions pass → success
+
+```c
+return i;
+```
+
+You’ve found:
+
+* A memory type that **works for this resource**
+* With **the properties you want**
+
+You’re done.
+
+---
+
+## If no memory type matches
+
+```c
+fprintf(stderr, "No suitable memory type\n");
+exit(1);
+```
+
+This should **never happen** in a correct Vulkan program.
+
+If it does:
+
+* You requested impossible memory properties
+* Or passed the wrong `typeBits`
+* Or you misunderstood host vs device memory
+
+---
+
+## Mental model (this is the key)
+
+Think of Vulkan memory like this:
+
+> “I have a list of memory pools.
+> Each pool has certain capabilities.
+> Each resource tells me which pools it is allowed to use.
+> I must pick one pool that satisfies both.”
+
+This function answers:
+
+> **“Which pool index should I use?”**
+
+---
+
+## Example usage (buffer allocation)
+
+```c
+VkMemoryRequirements req;
+vkGetBufferMemoryRequirements(device, buffer, &req);
+
+uint32_t memoryType = find_memory_type(
+ req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+);
+```
+
+This means:
+
+> “Find a memory type that
+> ✔ the buffer supports
+> ✔ I can map from CPU
+> ✔ doesn’t require manual flush/invalidate”
+
+---
+
+## Why this function is critical
+
+Every Vulkan program needs it for:
+
+* Vertex buffers
+* Index buffers
+* Uniform buffers
+* Staging buffers
+* Images
+* Depth buffers
+* Textures
+
+If you understand **this function**, you understand:
+
+* Why Vulkan memory is explicit
+* Why performance is predictable
+* Why Vulkan avoids hidden stalls
+
+---
+
+If you want next, I can:
+
+* Draw a **diagram of Vulkan memory types**
+* Show **which memory properties to use for each buffer**
+* Explain **why HOST_VISIBLE is slower**
+* Explain **why staging buffers exist**
+
+Just tell me which one.
--- /dev/null
+Ah, now we’re getting into a subtle Vulkan topic: **swapchain image count vs performance**. Let’s break it down carefully.
+
+---
+
+## 1️⃣ How the swapchain works
+
+A Vulkan swapchain is basically a **queue of images** (framebuffers) the GPU can render into and the presentation engine (display) can show.
+
+* `vkCreateSwapchainKHR` lets you specify `minImageCount` — the number of images.
+* Typical values: **2–3** (double or triple buffering).
+
+Each image can be in one of three states:
+
+1. **Available** — ready to render to
+2. **Rendering** — GPU is drawing into it
+3. **Presenting** — waiting to be shown on screen
+
+---
+
+## 2️⃣ Double vs Triple Buffering
+
+| # Images | Behavior | Pros | Cons |
+| ---------- | ------------------------------------------------ | ---------------------------------------- | ---------------------------------------- |
+| 2 (double) | GPU renders to 1 image, display shows the other | Less memory usage, lower latency | GPU may stall if display is slow |
+| 3 (triple) | GPU has 1 image ready, 1 rendering, 1 displaying | GPU never waits for display → higher FPS | More memory usage, slightly more latency |
+
+✅ Triple buffering can **improve throughput** because the GPU always has an image to render to.
+❌ But adding **more than 3–4 images rarely helps** — the GPU already pipelines well.
+
+---
+
+## 3️⃣ Latency vs performance tradeoff
+
+* **More images → higher FPS potential** (GPU never blocks)
+* **More images → higher input/display latency** (you’re rendering frames that are “ahead” of the screen)
+
+For games:
+
+* Triple buffering is common for smooth FPS
+* Double buffering is often enough for low-latency apps
+
+---
+
+## 4️⃣ Vulkan-specific notes
+
+* Some GPU drivers **require at least 2 images**.
+
+* The swapchain must have **at least `surfaceCapabilities.minImageCount`** images.
+
+* If you request more than `surfaceCapabilities.maxImageCount` → creation fails.
+
+* Vulkan allows you to acquire the **next available image** via `vkAcquireNextImageKHR`. If all images are in use, the GPU may block until one becomes free. More images = less chance of blocking.
+
+---
+
+### ✅ TL;DR
+
+* **2 images:** safe, low memory, slightly higher chance GPU waits
+* **3 images:** smoother, less chance of GPU stall, slightly more latency
+* **4+ images:** rarely improves FPS, just uses more VRAM
+
+So yes, **more images *can* improve throughput**, but only up to a point (usually 3). Beyond that, it’s mostly wasted memory.
+
+---
+
+If you want, I can draw a **diagram showing the swapchain states over time** — it makes it super clear why 3 images can be smoother than 2.
+
+Do you want me to do that?
--- /dev/null
+Feature,Pattern Rule (%),Wildcard (*)
+Purpose,Defining a transformation rule.,Listing existing files.
+Usage,%.o: %.c,rm *.o
+Timing,Evaluated when make decides what to do.,Evaluated by the shell when the
+command runs. Example,"""How to turn any X into Y.""","""Do something to
+everything that ends in .X."""
+
+Symbol,What it does,Resulting Logic %.spv: %.vert,Links the names.,"""To get
+player.spv, look for player.vert.""" *.spv: *.vert,Literal expansion.,"""To get
+all spv files, look for all vert files."""
--- /dev/null
+Ah, yes — this is a subtle but very important distinction. It’s easy to get confused because Vulkan separates **allocation** from **binding**, and the wording can make it seem like they both “give memory” to the buffer. Let’s clarify carefully.
+
+---
+
+### Step 1: `vkAllocateMemory`
+
+When you call:
+
+```c
+VkDeviceMemory memory;
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+```
+
+You are **reserving a chunk of memory from the GPU (or a heap)**.
+
+Think of it like **booking a block of storage in a warehouse**:
+
+* `VkDeviceMemory` is just a handle that represents that chunk.
+* The memory **exists**, but nothing is stored in it yet.
+* The GPU doesn’t yet know which buffer or image will use it.
+
+It’s untyped, unbound storage — just “here are these bytes in GPU-accessible memory.”
+
+---
+
+### Step 2: `vkBindBufferMemory`
+
+When you call:
+
+```c
+vkBindBufferMemory(device, buffer, memory, offset);
+```
+
+You are saying:
+
+> “Buffer X will **live starting at this offset in the allocated memory block**.”
+
+Now:
+
+* The buffer has a **valid GPU address**.
+* The GPU can access it when drawing or copying.
+* Alignment and memory type are enforced.
+
+It’s like taking that chunk of warehouse space and **assigning it to a specific shelf (the buffer)**.
+
+---
+
+### Analogy
+
+| Vulkan Step | Analogy |
+| -------------------- | --------------------------------------------------------------------------------------- |
+| `vkAllocateMemory` | Renting a warehouse block of storage (size, type, location) |
+| `vkBindBufferMemory` | Putting a specific item (buffer or image) into a specific shelf in that warehouse block |
+
+---
+
+### Why not do it all in one call?
+
+Vulkan separates the two because:
+
+1. **Memory suballocation:**
+ One `VkDeviceMemory` block can hold multiple buffers/images.
+
+ ```c
+ vkBindBufferMemory(vertexBuffer, memory, 0);
+ vkBindBufferMemory(indexBuffer, memory, 65536);
+ ```
+2. **Explicit control:**
+ You choose **memory type** (device local, host visible, cached, BAR-mapped) and **alignment**.
+3. **Performance predictability:**
+ Drivers don’t do hidden allocations or copies.
+
+---
+
+✅ **Summary**
+
+* `vkAllocateMemory`: “Here is a raw block of memory on the GPU.”
+* `vkBindBufferMemory`: “This buffer will live inside that block at this offset.”
+
+Without `vkAllocateMemory`, `vkBindBufferMemory` has nothing to bind.
+Without `vkBindBufferMemory`, the buffer exists but has **no storage**.
+
+---
+
+If you want, I can draw a little diagram showing **buffers, images, memory blocks, and GPU access** — it makes this super clear.
+
+Do you want me to do that?
--- /dev/null
+Great question — this is one of the **most important Vulkan concepts**, and the wording is easy to gloss over.
+
+When I said:
+
+> **“I can map from CPU”**
+
+this refers **very specifically** to Vulkan’s ability to let your program access GPU memory **as a normal pointer** on the CPU.
+
+Let’s break that down precisely.
+
+---
+
+## What “map from CPU” means in Vulkan
+
+In Vulkan, memory lives in different places:
+
+* Some memory lives **only on the GPU** (fast, but CPU can’t touch it)
+* Some memory lives in **system RAM** or unified memory (CPU can touch it)
+* Some memory is shared / cached / coherent
+
+When memory has the flag:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+```
+
+it means:
+
+> **The CPU is allowed to directly access this memory**
+
+Concretely, this allows you to call:
+
+```c
+void* data;
+vkMapMemory(device, memory, 0, size, 0, &data);
+```
+
+And then do:
+
+```c
+memcpy(data, myVertices, size);
+```
+
+That’s what **“map from CPU”** means.
+
+---
+
+## What happens without HOST_VISIBLE
+
+If memory does **not** have `HOST_VISIBLE`:
+
+* `vkMapMemory` is **illegal**
+* You cannot get a pointer to it
+* The CPU cannot read or write it directly
+
+This is typical for:
+
+```c
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+which usually means:
+
+* Memory physically located on the GPU (VRAM)
+* Very fast for the GPU
+* Completely inaccessible to the CPU
+
+---
+
+## Why you care
+
+### Example: Vertex buffer
+
+#### Option A — HOST_VISIBLE memory
+
+```c
+props = HOST_VISIBLE | HOST_COHERENT
+```
+
+✔ Easy: `memcpy` vertices directly
+❌ Slower for GPU access
+❌ PCIe / cache penalties
+
+#### Option B — DEVICE_LOCAL memory
+
+```c
+props = DEVICE_LOCAL
+```
+
+✔ Very fast for GPU
+❌ CPU cannot write directly
+
+So how do you upload data in Option B?
+
+➡ **Staging buffer**
+
+---
+
+## The staging buffer pattern (this is key)
+
+1. Create a **CPU-visible staging buffer**
+
+ ```c
+ HOST_VISIBLE | HOST_COHERENT
+ ```
+
+2. Write data via `memcpy`
+
+3. Copy staging buffer → device-local buffer using a command buffer
+
+4. Free staging buffer
+
+This is why Vulkan makes you think explicitly.
+
+---
+
+## HOST_COHERENT (important related detail)
+
+Often you see:
+
+```c
+HOST_VISIBLE | HOST_COHERENT
+```
+
+### What HOST_COHERENT means
+
+It means:
+
+> “When I write to mapped memory, the GPU sees it automatically”
+
+Without it, you must manually do:
+
+```c
+vkFlushMappedMemoryRanges(...)
+vkInvalidateMappedMemoryRanges(...)
+```
+
+So:
+
+* **HOST_VISIBLE** = CPU *can* access
+* **HOST_COHERENT** = CPU/GPU memory stays in sync automatically
+
+---
+
+## Concrete analogy
+
+Think of GPU memory like rooms:
+
+| Room type | CPU can enter? | GPU speed |
+| ----------------------- | -------------- | ------------ |
+| Device local (VRAM) | ❌ No | 🚀 Fast |
+| Host visible | ✅ Yes | 🐢 Slower |
+| Host visible + coherent | ✅ Yes | 🐢🐢 Slowest |
+
+You’re choosing **which room** to put your data in.
+
+---
+
+## In your program specifically
+
+When you call:
+
+```c
+find_memory_type(req.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+```
+
+You are explicitly saying:
+
+> “Give me memory that my CPU can map with a pointer
+> and write to using memcpy,
+> without manual cache management.”
+
+That’s exactly what “I can map from CPU” means.
+
+---
+
+## One sentence summary (very precise)
+
+> **“Map from CPU” means the CPU can obtain a pointer to GPU memory via `vkMapMemory` and directly read/write it like normal RAM.”**
+
+---
+
+If you want next, I can explain:
+
+* Why HOST_VISIBLE memory is often slower
+* Why some GPUs don’t have coherent memory
+* How unified memory GPUs behave differently
+* Exactly when you should *not* use HOST_VISIBLE
+
+Just tell me which one you want.
+
+Short answer: **sometimes yes, sometimes no — and Vulkan deliberately doesn’t promise which**.
+
+Let’s be very precise.
+
+---
+
+## Is mapped memory “still on the GPU”?
+
+### Vulkan’s honest answer:
+
+> **Mapped memory is always allocated by the Vulkan device, but its *physical location* depends on the hardware and memory type.**
+
+When you call:
+
+```c
+vkAllocateMemory(device, ...)
+vkMapMemory(device, memory, ...)
+```
+
+You are **not** allocating “CPU memory” or “GPU memory” explicitly.
+
+You are allocating **device memory**, and Vulkan exposes a **CPU mapping** *if the memory type allows it*.
+
+---
+
+## What actually happens under the hood
+
+### Case 1: Discrete GPU (most desktops)
+
+Typical example:
+
+* CPU has system RAM
+* GPU has its own VRAM
+* Connected via PCIe
+
+For these GPUs:
+
+| Memory type | Physical location |
+| -------------- | ------------------------------- |
+| `DEVICE_LOCAL` | GPU VRAM |
+| `HOST_VISIBLE` | System RAM (or BAR-mapped VRAM) |
+
+So if memory is:
+
+```c
+HOST_VISIBLE
+```
+
+then **physically** it is usually:
+
+* Allocated in **system RAM**
+* GPU accesses it over PCIe
+* CPU maps it directly
+
+✅ CPU pointer is real RAM
+❌ GPU access is slower
+
+This is why HOST_VISIBLE is slower for rendering.
+
+---
+
+### Case 2: Integrated / UMA GPU (laptops, APUs)
+
+Typical example:
+
+* AMD APU
+* Intel iGPU
+* Apple Silicon
+
+Here:
+
+* CPU and GPU share **the same physical memory**
+
+So:
+
+```c
+HOST_VISIBLE | DEVICE_LOCAL
+```
+
+can both be true.
+
+In this case:
+
+* Memory is physically one pool
+* CPU pointer == GPU memory
+* No PCIe penalty
+
+This is the **best-case scenario**, and Vulkan allows it.
+
+---
+
+### Case 3: BAR-mapped VRAM (advanced discrete GPUs)
+
+Some modern GPUs expose parts of VRAM to the CPU via PCIe BAR:
+
+* Memory is **physically in VRAM**
+* CPU sees a mapped window
+* Still slower than RAM (system ram)
+* Limited size
+
+Still counts as `HOST_VISIBLE`.
+
+---
+
+## The key Vulkan rule (this matters)
+
+> **Vulkan never guarantees where the memory physically lives.**
+
+It only guarantees:
+
+* Whether the CPU *may* access it
+* Whether caches are coherent
+* Whether the GPU prefers it
+
+This is why Vulkan forces you to ask:
+
+```c
+vkGetPhysicalDeviceMemoryProperties(...)
+```
+
+And choose explicitly.
+
+---
+
+## So is the memory “on the GPU”?
+
+Let’s answer your question exactly:
+
+> **If the CPU can obtain the pointer, is the memory still allocated on the GPU then?**
+
+### Correct, nuanced answer:
+
+* ✅ **Yes**, it is Vulkan *device memory*
+* ❌ **No**, it is not necessarily physically in GPU VRAM
+* 🔁 It may be:
+
+ * system RAM
+ * unified memory
+ * BAR-mapped VRAM
+
+The CPU pointer is a **mapping**, not proof of physical location.
+
+---
+
+## Why Vulkan phrases it this way
+
+Vulkan is designed so that:
+
+* The same code works on phones, consoles, desktops, servers
+* You never assume memory topology
+* Performance comes from choosing wisely, not guessing
+
+That’s why:
+
+* You describe *requirements*
+* The driver chooses *placement*
+
+---
+
+## Mental model (use this)
+
+Think of Vulkan memory like this:
+
+> “I allocate memory *owned by the device*.
+> Sometimes the device lets the CPU borrow a pointer to it.”
+
+Not:
+
+> “I allocated CPU memory that the GPU can see.”
+
+That mental model will keep you out of trouble.
+
+---
+
+## One-sentence takeaway
+
+> **Mapped memory is always Vulkan device memory; CPU access is a permission, not a statement about physical location.**
+
+If you want next, I can:
+
+* Show how to detect UMA vs discrete GPUs
+* Explain why HOST_VISIBLE can stall rendering
+* Walk through a perfect staging-buffer upload
+* Explain why mapping every frame can cause stutter (relevant to your issue)
+
+Just tell me.
+
+Great question — this is exactly where the confusion usually is.
+Let’s answer it **very concretely** and correct one small wording issue first.
+
+> What you meant by *“Case 3”* is **BAR-mapped VRAM on a discrete GPU**
+> (Resizable BAR / Smart Access Memory), **not unified memory**.
+
+---
+
+## Short answer (TL;DR)
+
+**BAR-mapped VRAM is *not faster* than normal system RAM for the CPU.**
+Its advantages are **not raw speed**, but **capability and reduced copies**.
+
+---
+
+## First: what “non BAR-mapped” means
+
+### Traditional (non-BAR) discrete GPU memory
+
+* GPU VRAM is **not directly addressable** by the CPU
+* CPU must:
+
+ 1. Write to system RAM (staging buffer)
+ 2. Ask GPU to copy over PCIe (`vkCmdCopyBuffer`)
+* CPU **cannot** dereference VRAM pointers
+
+This is how GPUs worked for decades.
+
+---
+
+## What BAR-mapped VRAM actually is
+
+With **Resizable BAR**:
+
+* GPU VRAM is mapped into the CPU’s address space
+* CPU can get a pointer to VRAM
+* CPU loads/stores go **over PCIe**
+
+Important:
+
+> **PCIe is much slower and higher-latency than DRAM**
+
+---
+
+## Is BAR-mapped VRAM faster than non-BAR?
+
+### For the CPU?
+
+❌ **No — it is slower than system RAM**
+
+| Memory type | CPU access speed |
+| ------------------ | ---------------- |
+| System RAM | ⭐⭐⭐⭐⭐ |
+| BAR-mapped VRAM | ⭐⭐ |
+| GPU VRAM (non-BAR) | ❌ impossible |
+
+BAR gives **access**, not speed.
+
+---
+
+### For the GPU?
+
+⚠️ **Sometimes indirectly**
+
+BAR can reduce:
+
+* Extra copies
+* Command buffer overhead
+* Synchronization latency
+
+But GPU **still prefers DEVICE_LOCAL VRAM accessed by itself**.
+
+---
+
+## So what are the actual advantages?
+
+### ✅ 1. Fewer copies (big deal)
+
+Without BAR:
+
+```
+CPU → RAM → GPU copy → VRAM
+```
+
+With BAR:
+
+```
+CPU → VRAM
+```
+
+This matters when:
+
+* Streaming assets
+* Uploading many small buffers
+* Doing frequent updates
+
+---
+
+### ✅ 2. Lower latency for small, dynamic updates
+
+Examples:
+
+* Per-frame constants
+* Dynamic vertex buffers
+* Sparse data updates
+
+Avoids:
+
+* Command submission
+* Transfer queues
+* Extra synchronization
+
+---
+
+### ✅ 3. Better CPU-driven streaming
+
+Used heavily in:
+
+* Modern engines (DX12 / Vulkan)
+* Texture streaming systems
+* Nanite-style workloads
+
+BAR helps **bandwidth utilization**, not single-access speed.
+
+---
+
+### ❌ What BAR does NOT give you
+
+* ❌ Faster CPU memory access
+* ❌ Replacement for staging buffers in large uploads
+* ❌ Free performance
+
+If you memcpy 100 MB per frame into BAR VRAM → **you will stall badly**
+
+---
+
+## Why BAR can *hurt* if misused
+
+BAR-mapped VRAM:
+
+* Bypasses CPU caches
+* Has high write-combining costs
+* Causes PCIe traffic saturation
+
+So this is **bad**:
+
+```c
+memcpy(barMappedVRAM, bigTexture, size);
+```
+
+This is **good**:
+
+```c
+memcpy(stagingRAM, data, size);
+vkCmdCopyBuffer(...); // GPU does the heavy lifting
+```
+
+---
+
+## Vulkan perspective (important)
+
+Vulkan exposes BAR like this:
+
+```c
+VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+```
+
+If you see **both** on a discrete GPU:
+
+* That’s BAR-mapped VRAM
+* Treat it as **special-purpose**, not general RAM
+
+---
+
+## Practical rule (tattoo this mentally)
+
+> **BAR is for reducing copies, not increasing bandwidth**
+
+Use BAR-mapped memory when:
+
+* Updates are small
+* Latency matters
+* You want to avoid extra transfers
+
+Use staging buffers when:
+
+* Data is large
+* GPU will read it often
+* You want peak throughput
+
+---
+
+## Relating this back to your stuttering issue
+
+You were right to suspect memory behavior.
+
+Your current pipeline is good because:
+
+* Texture is uploaded once
+* GPU samples DEVICE_LOCAL memory
+* No per-frame BAR writes
+
+So your stutter is **not** BAR-related — it was primarily:
+
+* Swapchain image / frame-in-flight mismatch
+* Fence/image ownership issues (which you already fixed)
+
+---
+
+If you want next, I can:
+
+* Show how engines *mix* BAR + staging safely
+* Explain why BAR helps DX12 more than Vulkan
+* Show how to detect BAR memory at runtime
+
+Just tell me 👍
--- /dev/null
+Ah, yes! This is a subtle but important Vulkan concept. Let’s break it down carefully.
+
+---
+
+### 1️⃣ What “opaque object” means
+
+In Vulkan, when you see something described as an **opaque object**, it means:
+
+* **You don’t know or need to know what’s inside it.**
+* The API treats it as a **handle** (essentially a pointer or an integer under the hood).
+* You cannot directly access its internal structure — you can only interact with it via Vulkan functions.
+
+For example:
+
+```c
+VkCommandPool commandPool;
+vkCreateCommandPool(device, &cpInfo, NULL, &commandPool);
+```
+
+* `commandPool` is a **handle** to the actual command pool data.
+* Internally, the driver stores GPU memory, thread info, buffers, etc., but **Vulkan doesn’t expose those details**.
+* You just pass `commandPool` to Vulkan functions like `vkAllocateCommandBuffers` or `vkResetCommandPool`.
+
+---
+
+### 2️⃣ Why Vulkan uses opaque objects
+
+1. **Driver flexibility:** Different GPU vendors can implement the object however they want.
+2. **Safety:** You can’t accidentally mess with internal GPU data.
+3. **Abstraction:** Vulkan provides a uniform interface regardless of the underlying hardware.
+
+Other examples of opaque objects:
+
+* `VkDevice`
+* `VkBuffer`
+* `VkImage`
+* `VkPipeline`
+* `VkShaderModule`
+
+All of these are **handles**, not structs you can poke inside.
+
+---
+
+### 3️⃣ How you interact with them
+
+You always pass the handle to Vulkan functions:
+
+```c
+vkAllocateCommandBuffers(device, &allocInfo, &commandBuffers);
+vkDestroyCommandPool(device, commandPool, NULL);
+```
+
+You never dereference it or try to read its members, because **its memory layout is unknown**.
+
+---
+
+✅ In short:
+
+An **opaque object** in Vulkan is like a black box: you know **what it represents** and **how to use it**, but you **don’t see or modify its internals**. The driver takes care of the details.
+
+---
+
+If you want, I can make a **quick visual analogy showing Vulkan handles vs real GPU memory** — it helps a lot to internalize the concept.
+
+Do you want me to do that?
+
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
+
--- /dev/null
+Ah! I can see exactly why this is still stuttering, and it’s subtle but very common in Vulkan beginners’ loops. Let’s go through it carefully.
+
+---
+
+## 1️⃣ The root cause
+
+Right now, your code does this:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+vkResetCommandBuffer(cb, 0);
+...
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+* `MAX_FRAMES_IN_FLIGHT = 2` → you have **2 fences**, **2 semaphores**, and **2 command buffers**.
+* **Swapchain images = 4**.
+
+This works *sometimes*, but not reliably, because **you are recording one command buffer per frame in flight, but there are 4 swapchain images**.
+
+* If `vkAcquireNextImageKHR` returns image index 2 or 3 (which can happen randomly), you will record and submit **command buffer 0 or 1** to draw to that image.
+* If that image is still in use (GPU hasn’t finished writing to it yet), you can get **stutter or occasional tearing**.
+
+In short: **frames in flight and swapchain images are not aligned**, and your code assumes a fixed one-to-one mapping between them.
+
+---
+
+## 2️⃣ How Vulkan apps usually fix this
+
+There are **two approaches**:
+
+---
+
+### **Approach A — Command buffer per swapchain image**
+
+* Allocate **one command buffer per swapchain image**:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+* Record commands **per swapchain image** (or re-record each frame).
+* When rendering:
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* No matter which swapchain image is returned by `vkAcquireNextImageKHR`, you have a dedicated command buffer for it.
+* This is the cleanest solution if you’re recording per-frame commands dynamically.
+
+---
+
+### **Approach B — Keep frames in flight separate from swapchain images, but wait properly**
+
+* Keep `MAX_FRAMES_IN_FLIGHT` buffers/fences.
+* After acquiring the swapchain image, **check if it’s already in flight**:
+
+```c
+VkFence imageFence = imagesInFlight[imgIndex];
+if(imageFence != VK_NULL_HANDLE)
+ vkWaitForFences(device, 1, &imageFence, VK_TRUE, UINT64_MAX);
+imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+```
+
+* This ensures **you don’t overwrite a swapchain image that’s still being presented**.
+* `imagesInFlight` is an array of size `swapchainImageCount`, initialized to `VK_NULL_HANDLE`.
+
+---
+
+## 3️⃣ Other subtle stutter causes
+
+Even after fixing the above:
+
+1. **V-Sync mode (FIFO) + small floating-point inaccuracies**
+
+ * You are limiting rotation by `delta` each frame — good.
+ * But CPU/GPU timing can slightly drift → slight stutter.
+
+2. **`vkQueueWaitIdle()` or blocking calls**
+
+ * In your `create_texture_image()`, you do:
+
+```c
+vkQueueSubmit(graphicsQueue, 1, &submit, VK_NULL_HANDLE);
+vkQueueWaitIdle(graphicsQueue);
+```
+
+* That’s fine for setup, but make sure your **render loop doesn’t have extra waits** other than fences per frame.
+
+3. **Push constants and descriptor updates**
+
+ * Currently you only use **push constants** for rotation and a static texture → that’s fine.
+ * If you update descriptor sets per frame, that can stall if you don’t use separate sets per frame.
+
+---
+
+## ✅ Minimal fix for your current code
+
+I recommend **switching to one command buffer per swapchain image**.
+
+### Changes:
+
+```c
+// allocate command buffers per swapchain image
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+};
+vkAllocateCommandBuffers(device, &cbAlloc, commandBuffers);
+```
+
+Then, in your render loop:
+
+```c
+uint32_t imgIndex;
+vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE, &imgIndex);
+
+// use command buffer dedicated to this swapchain image
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* **No more mismatches**.
+* `currentFrame` is still used for fences/semaphores, not command buffers.
+
+---
+
+If you want, I can **rewrite your render loop fully corrected** with:
+
+* Multiple swapchain images
+* MAX_FRAMES_IN_FLIGHT fences/semaphores
+* Dynamic rendering + textures + push constant rotation
+
+so the stutter disappears completely.
+
+Do you want me to do that?
--- /dev/null
+Good catch — this is **not** a texturing problem anymore.
+What you’re seeing is **time-step stutter**, not GPU stutter.
+
+---
+
+## ✅ Root cause: frame-dependent rotation
+
+Right now you update rotation like this:
+
+```c
+angle += 0.01f;
+```
+
+That means:
+
+* Rotation speed depends on **how long the frame took**
+* If one frame takes longer (OS scheduling, compositor, swapchain wait)
+* The angle “jumps” more on that frame
+* Your eye interprets this as **texture stuttering**
+
+The texture is actually stable — the **motion is uneven**.
+
+---
+
+## Why Vulkan makes this obvious
+
+You are using:
+
+```c
+VK_PRESENT_MODE_FIFO_KHR
+```
+
+Which:
+
+* Syncs to v-blank (good)
+* Causes variable frame times when frames miss a refresh
+* Makes fixed-per-frame motion very noticeable
+
+This is **expected behavior**.
+
+---
+
+## ✅ Correct fix: time-based animation (delta time)
+
+You must rotate based on **elapsed time**, not frames.
+
+### Add these variables
+
+At the top of `main`:
+
+```c
+double lastTime = glfwGetTime();
+```
+
+### Replace your angle update with this
+
+Inside the loop:
+
+```c
+double now = glfwGetTime();
+double delta = now - lastTime;
+lastTime = now;
+
+angle += (float)(delta * 1.5); // radians per second
+```
+
+Now rotation speed is:
+
+* Smooth
+* Independent of frame rate
+* Independent of swapchain stalls
+
+---
+
+## 🎯 Why this fixes the “texture stutter”
+
+Perspective-correct interpolation is now correct
+✔ Texture is locked to geometry
+✔ Geometry moves smoothly in time
+✔ No visible swimming or jitter
+
+---
+
+## ❌ What is NOT the cause
+
+* ❌ Texture sampling
+* ❌ Descriptor sets
+* ❌ Dynamic rendering
+* ❌ Nearest vs linear filtering
+* ❌ Swapchain image count
+
+---
+
+## Optional improvements (not required)
+
+### Clamp large frame jumps (alt-tab, debugger pauses)
+
+```c
+if (delta > 0.1) delta = 0.1;
+```
+
+### Use radians explicitly (clarity)
+
+```c
+float rotationSpeed = M_PI / 2.0f; // 90°/sec
+angle += rotationSpeed * delta;
+```
+
+---
+
+## Final verdict
+
+✔ Your Vulkan pipeline is correct
+✔ Your texture mapping is correct
+✔ Your projection fix was correct
+❌ Motion was frame-rate dependent
+
+After switching to **delta-time animation**, the “texture stutter” should disappear entirely.
+
+If you want next, I can:
+
+* Add a depth buffer (very noticeable improvement)
+* Switch to indexed drawing
+* Add proper MVP matrices
+* Show how to decouple update & render timing
--- /dev/null
+Below is a **deep, practical explanation** of **what a “sample” is** and **what multisampling (MSAA) actually does**, tied directly to how GPUs and Vulkan work.
+
+I’ll go from fundamentals → rasterization → Vulkan specifics → tradeoffs.
+
+---
+
+# 1. What is a “sample”?
+
+A **sample** is **one coverage + depth + color evaluation at a specific sub-pixel location**.
+
+A **pixel** is not a single point. Internally, GPUs treat a pixel as an **area**, and a *sample* is a **point inside that area**.
+
+Think of a pixel like a square:
+
+```
++-------+
+| |
+| |
+| |
++-------+
+```
+
+A **sample** is a dot inside that square:
+
+```
++-------+
+| • |
+| |
+| |
++-------+
+```
+
+With **1 sample per pixel**, the GPU only checks that single dot.
+
+---
+
+# 2. Why 1 sample causes aliasing
+
+When you render geometry:
+
+* The GPU tests **whether the sample point is inside a triangle**
+* If yes → pixel is covered
+* If no → pixel is empty
+
+This causes **hard edges**:
+
+```
+Triangle edge
+███████
+███████
+███
+███
+```
+
+This is **aliasing**:
+
+* Jagged edges
+* Stair-stepping
+* Temporal shimmer when moving
+
+---
+
+# 3. What multisampling is (core idea)
+
+**Multisampling increases the number of sample points per pixel.**
+
+Instead of:
+
+```
+1 sample per pixel
+```
+
+You use:
+
+```
+2, 4, 8, or more samples per pixel
+```
+
+Example (4× MSAA):
+
+```
++-------+
+| • • |
+| |
+| • • |
++-------+
+```
+
+Each sample:
+
+* Has its **own depth**
+* Has its **own coverage**
+* May have its **own color**
+
+---
+
+# 4. What actually happens during rasterization
+
+For each triangle and each pixel it touches:
+
+1. The GPU checks **each sample**
+2. Determines:
+
+ * Is this sample inside the triangle?
+ * What is its depth?
+3. Stores coverage mask like:
+
+ ```
+ 1011
+ ```
+
+ (3 out of 4 samples covered)
+
+This is the key insight:
+
+> **Edges become partially covered instead of all-or-nothing.**
+
+---
+
+# 5. How color is handled (important)
+
+There are **two main modes**:
+
+### A) Per-pixel shading (most common)
+
+* Fragment shader runs **once per pixel**
+* Color is written to **all covered samples**
+* Only coverage varies
+
+This is fast and common.
+
+---
+
+### B) Per-sample shading (rare, expensive)
+
+* Fragment shader runs **per sample**
+* Each sample gets its own color
+
+Enabled with:
+
+```glsl
+layout(sample) out vec4 outColor;
+```
+
+Used for:
+
+* Alpha-to-coverage
+* Precise edge shading
+* Special effects
+
+---
+
+# 6. Resolving multisampling (MSAA resolve)
+
+Your display can only show **one color per pixel**.
+
+So the GPU must **resolve** the samples:
+
+```
+final_color = average(samples)
+```
+
+Example:
+
+| Sample | Color |
+| ------ | ----- |
+| 0 | white |
+| 1 | white |
+| 2 | black |
+| 3 | white |
+
+Final pixel = **75% white**
+
+That’s why edges look smooth.
+
+---
+
+# 7. Vulkan-specific: what `samples` means
+
+In Vulkan, this line:
+
+```c
+.samples = VK_SAMPLE_COUNT_4_BIT
+```
+
+Means:
+
+> “Each pixel has **4 samples** stored in memory.”
+
+This affects:
+
+* Color attachments
+* Depth attachments
+* Rasterization rules
+
+---
+
+# 8. Why swapchain images are usually 1 sample
+
+Your swapchain image is:
+
+```c
+.samples = VK_SAMPLE_COUNT_1_BIT
+```
+
+Why?
+
+Because:
+
+* The window system expects **single-sample images**
+* MSAA images must be **resolved** first
+
+So the usual Vulkan pattern is:
+
+```
+MSAA color image (4×)
+ ↓ resolve
+Swapchain image (1×)
+```
+
+---
+
+# 9. MSAA render pass layout (important)
+
+A typical MSAA render pass has **two color attachments**:
+
+1. **Multisampled color image**
+2. **Resolve image (swapchain)**
+
+Example:
+
+```c
+VkAttachmentDescription colorMSAA = {
+ .samples = VK_SAMPLE_COUNT_4_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE
+};
+
+VkAttachmentDescription resolve = {
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE
+};
+```
+
+Vulkan resolves automatically at the end of the subpass.
+
+---
+
+# 10. Depth + MSAA (critical detail)
+
+Depth buffers also have samples.
+
+Why?
+Because each sample has **its own depth**.
+
+This is what allows:
+
+* Correct depth testing at edges
+* Proper overlapping geometry
+
+Depth must use the **same sample count** as color.
+
+---
+
+# 11. What multisampling does NOT do
+
+❌ Does NOT blur textures
+❌ Does NOT fix shader aliasing
+❌ Does NOT fix specular shimmer
+❌ Does NOT replace temporal AA
+
+MSAA **only fixes geometric edge aliasing**.
+
+---
+
+# 12. Cost of multisampling
+
+### Memory cost
+
+| MSAA | Color | Depth |
+| ---- | ----- | ----- |
+| 2× | 2× | 2× |
+| 4× | 4× | 4× |
+| 8× | 8× | 8× |
+
+### Performance cost
+
+* More depth tests
+* More bandwidth
+* More memory
+
+Fragment shading often remains **1×**, so cost is manageable.
+
+---
+
+# 13. Why MSAA is still used
+
+✅ Excellent edge quality
+✅ No temporal blur
+✅ Stable under motion
+✅ Hardware-supported
+
+That’s why:
+
+* Vulkan
+* OpenGL
+* Console engines
+* CAD software
+
+still rely on MSAA.
+
+---
+
+# 14. Mental model (best way to remember)
+
+> **A pixel is an area. A sample is a point inside that area.
+> Multisampling checks more points, so edges become partial instead of binary.**
+
+---
+
+# 15. One-sentence summary
+
+> **Multisampling works by storing multiple depth and coverage samples per pixel and averaging them during resolve, producing smooth geometric edges without blurring the image.**
+
+---
+
+If you want, I can:
+
+* Show how to **add MSAA to your exact Vulkan render pass**
+* Explain **alpha-to-coverage**
+* Compare **MSAA vs FXAA vs TAA**
+* Explain why MSAA interacts badly with deferred rendering
+
+Just tell me where you want to go next.
--- /dev/null
+Ah — now I see exactly what’s happening. The “stuttering” you’re seeing is **not a problem with your delta-time rotation**, but instead comes from the **way you’re submitting command buffers and waiting for fences** in your render loop.
+
+Right now, your loop looks like this (simplified):
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlight);
+
+vkAcquireNextImageKHR(..., imageAvailable, VK_NULL_HANDLE, &imgIndex);
+
+vkResetCommandBuffer(cb, 0);
+vkBeginCommandBuffer(cb, ...);
+
+// record commands
+vkCmdBeginRendering(cb, &renderingInfo);
+vkCmdBindPipeline(...);
+vkCmdDraw(...);
+vkCmdEndRendering(cb);
+
+vkEndCommandBuffer(cb);
+
+VkSubmitInfo submit = { ... };
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlight);
+
+VkPresentInfoKHR present = { ... };
+vkQueuePresentKHR(graphicsQueue, &present);
+```
+
+---
+
+## 🔴 The problem
+
+You are using **one fence (`inFlight`) for all frames**:
+
+```c
+VkFence inFlight;
+```
+
+And your loop does:
+
+```c
+vkWaitForFences(device, 1, &inFlight, VK_TRUE, UINT64_MAX);
+```
+
+This means:
+
+* Each frame **blocks until the previous frame finishes entirely** (GPU + present)
+* If your GPU is fast, this is okay
+* If there’s any fluctuation (swapchain wait, vsync), your CPU **stalls**
+* Result: **stuttery motion**, even if delta-time rotation is correct
+
+Also, you **don’t use multiple in-flight frames**, so you can’t submit the next frame until the GPU is done.
+
+---
+
+## ✅ How to fix it
+
+You need to implement **double-buffered or triple-buffered frames in flight**.
+
+### Step 1: create multiple fences
+
+```c
+#define MAX_FRAMES_IN_FLIGHT 2
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+```
+
+### Step 2: cycle through frames
+
+```c
+uint32_t currentFrame = 0;
+```
+
+Inside the loop:
+
+```c
+vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+);
+
+// record command buffer ...
+// submit:
+VkSubmitInfo submit = { ... };
+submit.waitSemaphoreCount = 1;
+submit.pWaitSemaphores = &imageAvailable[currentFrame];
+submit.signalSemaphoreCount = 1;
+submit.pSignalSemaphores = &renderFinished[currentFrame];
+
+vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+// present:
+VkPresentInfoKHR present = { ... };
+present.waitSemaphoreCount = 1;
+present.pWaitSemaphores = &renderFinished[currentFrame];
+
+vkQueuePresentKHR(graphicsQueue, &present);
+
+currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+```
+
+---
+
+## Step 3: Why this fixes the stutter
+
+* Now **CPU doesn’t wait for GPU to finish the previous frame entirely**
+* You can have **two frames queued in flight**
+* Delta-time rotation is smooth
+* Swapchain image acquisition can happen asynchronously
+* Texture and cube rotation appear smooth
+
+---
+
+### Extra tip:
+
+Make sure your **swapchain present mode is FIFO (vsync)**:
+
+```c
+.preTransform = caps.currentTransform,
+.presentMode = VK_PRESENT_MODE_FIFO_KHR,
+```
+
+This ensures no tearing, but introduces a slight latency — which is fine with multiple frames in flight.
+
+---
+
+### ✅ TL;DR
+
+1. You currently block on a single fence every frame → causes stutter.
+2. Implement **multiple frames in flight** (double/triple buffering).
+3. Use **frame-indexed semaphores and fences** for acquire/submit/present.
+4. Keep delta-time rotation as you have — it will now appear smooth.
+
+---
+
+If you want, I can rewrite your **main render loop with double-buffered frames in flight** for your current code — fully Vulkan 1.3 + dynamic rendering ready.
+
+Do you want me to do that?
+
+Ah! I can see exactly why your program segfaults — it’s not a single one-liner; there are **multiple issues related to fences, semaphores, and memory allocation** in your current code. Let me break it down carefully and explain exactly what’s going wrong, and how to fix it.
+
+---
+
+## 1️⃣ Fences and semaphores creation
+
+You have:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+**Problem:**
+
+* `imageAvailable` and `renderFinished` are **arrays** of `MAX_FRAMES_IN_FLIGHT` elements.
+* `inFlightFences` is also an **array** of size `MAX_FRAMES_IN_FLIGHT`.
+* `vkCreateSemaphore` and `vkCreateFence` expect a **pointer to a single VkSemaphore/VkFence**, not an array.
+
+Right now, you’re passing `imageAvailable` (type `VkSemaphore[MAX_FRAMES_IN_FLIGHT]`) **without indexing**, so Vulkan interprets the pointer incorrectly → undefined behavior → **segfault**.
+
+**Fix:** create each semaphore/fence individually:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+}
+```
+
+✅ This is **mandatory**. Without it, your `vkWaitForFences` later will segfault because the fence handle is garbage.
+
+---
+
+## 2️⃣ Command buffers + swapchain images mismatch
+
+You allocated:
+
+```c
+commandBuffers = malloc(sizeof(VkCommandBuffer) * swapchainImageCount);
+```
+
+and
+
+```c
+VkCommandBuffer cb = commandBuffers[imgIndex];
+```
+
+* `imgIndex` comes from `vkAcquireNextImageKHR`.
+* But `swapchainImageCount` may be **larger than MAX_FRAMES_IN_FLIGHT**.
+* Your `currentFrame` index is used for fences and semaphores.
+* This can cause **index mismatches** if you have more swapchain images than frames in flight.
+
+**Fix:** use **frame indexing consistently**:
+
+```c
+VkCommandBuffer cb = commandBuffers[currentFrame];
+```
+
+Then record the command buffer for **that frame**.
+Or allocate **one command buffer per frame in flight**, not per swapchain image.
+
+---
+
+## 3️⃣ Push constants / pipeline layout
+
+You correctly defined a push constant of `sizeof(float)` for rotation.
+✅ This part is fine.
+
+---
+
+## 4️⃣ Descriptor sets
+
+You only have **1 descriptor set**, but you submit multiple frames in flight.
+
+* Technically okay since the texture is static, but if you ever update it dynamically, you’d need **one descriptor set per frame**.
+* For now, not a segfault cause.
+
+---
+
+## 5️⃣ Minor issues that can crash
+
+* `VkSurfaceFormatKHR formats[fmtCount];` — if `fmtCount` is zero, this will crash. Always check:
+
+```c
+if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+```
+
+* `vkCreateImageView` and `vkCreateBuffer` **do not check return values everywhere**. Make sure you check **every Vulkan call**.
+
+---
+
+## 6️⃣ Texture image staging
+
+* Your staging buffer is allocated, copied, and freed **synchronously**.
+* That part is fine.
+* Just make sure `vkQueueSubmit` waits for the copy to finish, which you do with `vkQueueWaitIdle`. ✅
+
+---
+
+## ✅ Corrected fence/semaphore creation
+
+Replace:
+
+```c
+vkCreateSemaphore(device, &sem, NULL, imageAvailable);
+vkCreateSemaphore(device, &sem, NULL, renderFinished);
+vkCreateFence(device, &fence, NULL, inFlightFences);
+```
+
+with:
+
+```c
+for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr,"Failed to create synchronization objects\n");
+ exit(1);
+ }
+}
+```
+
+---
+
+## 7️⃣ Recommended changes
+
+1. Allocate **one command buffer per frame in flight**, not per swapchain image.
+2. Use `currentFrame` to index fences, semaphores, and command buffers.
+3. Always check Vulkan function returns.
+4. Use `VK_NULL_HANDLE` fences/semaphores carefully — do **not** pass arrays directly.
+
+---
+
+If you want, I can **rewrite your main render loop + fence/semaphore setup** fully corrected for **double-buffered frames in flight** — so it will **stop segfaulting** and run smoothly.
+
+Do you want me to do that?
+
--- /dev/null
+Absolutely! Let’s break it down carefully.
+
+---
+
+### 1️⃣ What `u` and `v` are
+
+In your struct:
+
+```c
+typedef struct {
+ float x, y, z; // 3D position in space
+ float u, v; // texture coordinates
+} CubeVertex;
+```
+
+* `x, y, z` → define the **vertex position in 3D space**.
+* `u, v` → define **where on a 2D texture image this vertex maps to**.
+
+These are often called **UV coordinates** or **texture coordinates**.
+
+---
+
+### 2️⃣ How `u` and `v` relate to the texture
+
+Textures are images. Imagine a 2D image like this:
+
+```
+(0,1) ┌─────────────┐ (1,1)
+ │ │
+ │ │
+ │ │
+(0,0) └─────────────┘ (1,0)
+```
+
+* `u` → horizontal position on the texture (0 = left edge, 1 = right edge)
+* `v` → vertical position on the texture (0 = bottom edge, 1 = top edge)
+
+> Note: In Vulkan (and OpenGL), **v=0 is usually bottom**, **v=1 is top**. Some image libraries flip this, so sometimes `v` may need to be inverted.
+
+---
+
+### 3️⃣ How this works in rendering
+
+When Vulkan draws a triangle:
+
+1. Each vertex has `(x, y, z)` for position and `(u, v)` for texture mapping.
+2. The GPU **interpolates** `u` and `v` across the triangle.
+3. For every pixel in the triangle, the GPU calculates a corresponding `(u, v)` coordinate.
+4. The pixel color is sampled from the texture using that `(u, v)` coordinate.
+
+---
+
+### 4️⃣ Example
+
+If you have a cube face made of two triangles and a 2x2 texture like this:
+
+```
+Texture:
+[white][pink]
+[pink][white]
+```
+
+And your vertices are:
+
+```c
+{-0.5f,-0.5f,-0.5f, 0,0}, // bottom-left maps to texture bottom-left
+{ 0.5f,-0.5f,-0.5f, 1,0}, // bottom-right maps to texture bottom-right
+{ 0.5f, 0.5f,-0.5f, 1,1}, // top-right maps to texture top-right
+{-0.5f, 0.5f,-0.5f, 0,1} // top-left maps to texture top-left
+```
+
+* Each vertex "grabs" the color from that corner of the texture.
+* The GPU fills the interior of the triangle with colors **interpolated** between those corners.
+
+---
+
+### ✅ TL;DR
+
+* `u` = horizontal coordinate on the texture
+* `v` = vertical coordinate on the texture
+* `(u, v)` tell Vulkan **which part of the 2D image** to apply to the 3D vertex.
+* Without `u, v`, Vulkan wouldn’t know how to map the texture to the cube.
+
+---
+
+If you want, I can make a **small visual diagram showing a cube face, its vertices, and how u,v map the texture onto it**, which usually makes it click instantly.
+
+Do you want me to do that?
+
+Absolutely! Let’s connect the dots between your **CubeVertex struct** `(x, y, z, u, v)` and how the **shader** uses it to texture your cube. I’ll break it down step by step.
+
+---
+
+## 1️⃣ Vertex Shader
+
+A typical vertex shader in Vulkan GLSL might look like this:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec3 inPos; // x, y, z from CubeVertex
+layout(location = 1) in vec2 inUV; // u, v from CubeVertex
+
+layout(location = 0) out vec2 fragUV; // pass to fragment shader
+
+layout(push_constant) uniform PushConstants {
+ float angle;
+} pc;
+
+void main() {
+ // Apply rotation around Y axis
+ float s = sin(pc.angle);
+ float c = cos(pc.angle);
+ vec3 pos = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // Convert to clip space (just simple identity for example)
+ gl_Position = vec4(pos, 1.0);
+
+ // Pass the UV coordinates to the fragment shader
+ fragUV = inUV;
+}
+```
+
+### 🔑 Notes:
+
+* `layout(location = 0) in vec3 inPos` ← gets **x, y, z** from `CubeVertex`.
+* `layout(location = 1) in vec2 inUV` ← gets **u, v** from `CubeVertex`.
+* `fragUV = inUV;` → we pass the texture coordinates **to the fragment shader**, interpolated across the triangle.
+
+So every pixel in the triangle will have a `fragUV` coordinate that is **interpolated** between the vertices.
+
+---
+
+## 2️⃣ Fragment Shader
+
+Then, in the fragment shader:
+
+```glsl
+#version 450
+
+layout(location = 0) in vec2 fragUV; // interpolated UV
+layout(location = 0) out vec4 outColor; // final pixel color
+
+layout(set = 0, binding = 0) uniform sampler2D tex; // texture sampler
+
+void main() {
+ // Sample the texture at fragUV
+ outColor = texture(tex, fragUV);
+}
+```
+
+### 🔑 Notes:
+
+* `fragUV` = interpolated `(u,v)` coordinates from vertex shader.
+* `sampler2D tex` = your bound texture image + sampler.
+* `texture(tex, fragUV)` → returns the color of the texture at that `(u,v)` location.
+* The GPU writes `outColor` to the framebuffer.
+
+---
+
+## 3️⃣ How this maps from your CubeVertex:
+
+| CubeVertex field | Role in Shader |
+| ---------------- | ---------------------------------------------------------------------------------------- |
+| `x, y, z` | `inPos` in vertex shader → determines 3D position of vertex |
+| `u, v` | `inUV` in vertex shader → passed as `fragUV` to fragment shader → used to sample texture |
+
+**Flow:**
+
+```
+CubeVertex.u,v ---> vertex shader ---> fragUV (interpolated) ---> fragment shader ---> texture lookup ---> final pixel color
+```
+
+---
+
+## 4️⃣ Why this is important
+
+Without `u, v`:
+
+* Fragment shader wouldn’t know **which part of the texture** corresponds to each pixel.
+* Texture would be meaningless; all faces might just use the top-left pixel, for example.
+
+With `u, v`:
+
+* Each face of your cube can correctly map the texture, even after rotation.
+* The GPU interpolates `u, v` across triangles for smooth texturing.
+
+---
+
+If you want, I can also **draw a diagram showing the cube, a triangle, and how `(u,v)` coordinates are interpolated to map the texture properly**.
+
+Do you want me to make that visual?
--- /dev/null
+Ah, this is a **common misunderstanding in Vulkan** — the “3D” in `VkImageViewType` does **not mean a 3D object in the scene**, it’s about the **image resource itself** in GPU memory. Let me explain carefully.
+
+---
+
+## 1️⃣ What `viewType` really means
+
+`VkImageViewCreateInfo.viewType` can be:
+
+| Value | Meaning |
+| ------------------------- | -------------------------------------------------- |
+| `VK_IMAGE_VIEW_TYPE_1D` | The image is a 1D texture (width only) |
+| `VK_IMAGE_VIEW_TYPE_2D` | The image is a 2D texture (width × height) |
+| `VK_IMAGE_VIEW_TYPE_3D` | The image is a 3D texture (width × height × depth) |
+| `VK_IMAGE_VIEW_TYPE_CUBE` | Cube map texture (6 faces) |
+
+**Important:** This is about **how the GPU interprets the memory layout of the image**, not whether your scene is 3D.
+
+---
+
+## 2️⃣ Swapchain images are always 2D
+
+* Swapchain images are the **images that will be presented on your 2D screen**.
+* Even though your cube is a 3D object in the scene, the **swapchain image is a 2D framebuffer**.
+* Therefore, `viewType` **must remain `VK_IMAGE_VIEW_TYPE_2D`** for swapchain images.
+
+If you tried `VK_IMAGE_VIEW_TYPE_3D`:
+
+* Vulkan would expect a **3D image resource** in memory (with depth slices).
+* Swapchain images **aren’t 3D** — they only have width × height × 1.
+* `vkCreateImageView` would **fail** or behave incorrectly.
+
+---
+
+## 3️⃣ Summary
+
+* Your 3D cube **does not make the swapchain 3D**.
+* `VK_IMAGE_VIEW_TYPE_2D` is correct for **all normal onscreen rendering**.
+* `VK_IMAGE_VIEW_TYPE_3D` is only for **volumetric textures**, like 3D noise, 3D textures, or voxel data.
+
+---
+
+💡 **Analogy:**
+
+* The 3D cube is like a **3D model**.
+* The swapchain image is like the **photograph of that model** — a 2D picture.
+* The swapchain image view interprets the “photograph” — so it’s always 2D.
+
+---
+
+If you want, I can also explain **when you *would* use `VK_IMAGE_VIEW_TYPE_3D` in Vulkan** and how it differs from rendering a normal 3D scene. This is actually a pretty common point of confusion for newcomers.
+
+Do you want me to go into that?
--- /dev/null
+// Generated from cube.obj
+
+typedef struct { float pos[3]; float color[3]; } Vertex;
+
+Vertex vertices[] = {
+ { { 1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { 1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, -1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, 1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+ { { -1.0f, -1.0f, 1.0f }, {1.0f, 1.0f, 1.0f} },
+};
+
+uint32_t indices[] = {
+ 0, 4, 6,
+ 0, 6, 2,
+ 3, 2, 6,
+ 3, 6, 7,
+ 7, 6, 4,
+ 7, 4, 5,
+ 5, 1, 3,
+ 5, 3, 7,
+ 1, 0, 2,
+ 1, 2, 3,
+ 5, 4, 0,
+ 5, 0, 1,
+};
--- /dev/null
+#version 450
+
+layout(location = 0) in vec2 uv;
+layout(location = 0) out vec4 outColor;
+
+void main() {
+ outColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+
--- /dev/null
+# Blender 5.0.1 MTL File: 'None'
+# www.blender.org
+
+newmtl Material
+Ns 250.000000
+Ka 1.000000 1.000000 1.000000
+Kd 0.800000 0.800000 0.800000
+Ks 0.500000 0.500000 0.500000
+Ke 0.000000 0.000000 0.000000
+Ni 1.500000
+d 1.000000
+illum 2
--- /dev/null
+# Blender 5.0.1
+# www.blender.org
+mtllib cube.mtl
+o Cube
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vn -0.0000 1.0000 -0.0000
+vn -0.0000 -0.0000 1.0000
+vn -1.0000 -0.0000 -0.0000
+vn -0.0000 -1.0000 -0.0000
+vn 1.0000 -0.0000 -0.0000
+vn -0.0000 -0.0000 -1.0000
+vt 0.875000 0.500000
+vt 0.625000 0.750000
+vt 0.625000 0.500000
+vt 0.375000 1.000000
+vt 0.375000 0.750000
+vt 0.625000 0.000000
+vt 0.375000 0.250000
+vt 0.375000 0.000000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+vt 0.125000 0.500000
+vt 0.625000 0.250000
+vt 0.875000 0.750000
+vt 0.625000 1.000000
+s 0
+usemtl Material
+f 5/1/1 3/2/1 1/3/1
+f 3/2/2 8/4/2 4/5/2
+f 7/6/3 6/7/3 8/8/3
+f 2/9/4 8/10/4 6/11/4
+f 1/3/5 4/5/5 2/9/5
+f 5/12/6 2/9/6 6/7/6
+f 1/3/5 3/2/5 4/5/5
+f 5/12/6 1/3/6 2/9/6
+f 2/9/4 4/5/4 8/10/4
+f 7/6/3 5/12/3 6/7/3
+f 5/1/1 7/13/1 3/2/1
+f 3/2/2 7/14/2 8/4/2
--- /dev/null
+#version 450
+
+layout(location = 0) in vec3 inPos;
+layout(location = 1) in vec2 inUV;
+
+layout(location = 0) out vec2 fragUV;
+
+layout(push_constant) uniform Push {
+ float angle;
+} pc;
+
+void main() {
+ float c = cos(pc.angle);
+ float s = sin(pc.angle);
+
+ vec3 p = vec3(
+ inPos.x * c - inPos.z * s,
+ inPos.y,
+ inPos.x * s + inPos.z * c
+ );
+
+ // simple perspective
+ float z = p.z + 2.5;
+
+ gl_Position = vec4(p.xy, z, z); // ← KEY FIX
+ fragUV = inUV;
+}
--- /dev/null
+== NDC (Normalized Device Coordinates)
+
+Based on the math inside your `main()` function and the way you are passing your
+`inPos` data, we can "reverse engineer" exactly how your 3D world is oriented.
+
+Because you are using *Vulkan's default NDC* (Normalized Device Coordinates),
+your coordinate system is currently *Y-Down* and *Right-Handed*.
+
+== 1. Blender: The "Architect's" System
+
+Blender uses a *Right-Handed,Z-Up* system.
+
+- *X*: Left / Right
+
+- *Y*: Forward / Backword
+
+- *Z*: Up / Down (Imagine a blueprint lying on a table; Z is the height of the
+ walls)
+
+== 2. Vulkan: The "Screen" System
+
+Vulkan (by default) uses a *Right-Handed*, *Y-Down* system in it's Normalized
+Device Coordinates (NDC)
+
+- *X*: Right is positive (1.0), Left is negative (-1.0)
+
+- *Y*: Down is positive (1.0), Up is negative (-1.0).
+
+- *Z*: Forward (into the screen) is 0.0 to 1.0.
+
+Why Y-Down?::
+ Historically, computer graphics started with 2D window coordinates where the
+ top-left corner of your monitor was (0,0). Vulkan stays true to this legacy.
+
+.Summary Table
+|===
+| Axis | Blender | Vulkan (NDC)
+//---------------------------------
+| Up | +Z | +Y
+| Down | -Z | -Y
+| Right | +X | +X
+| Left | -X | -X
+| forward | +Y | +Z
+| Back | -Y | -Z
+|===
--- /dev/null
+== Overview
+
+* malloc /calloc allocate CPU memory
+* vkAllocate allocates GPU-accessible device memory
+* THey live in completely different worlds
+
+== Malloc / calloc
+
+* Allocates process virtual memory
+* owned by the CPU / OS
+* ONly the CPU can access it
+* the GPU cannot see or use it
+* VUlkan has no ide this memory exists
+
+== VkALlocateMemory
+
+* Allocates memory from the *physical device*
+* Managed by *Vulkan + the GPU driver*
+
+* can be:
+ - GPU-only (VRAM)
+ - CPU-visible (mapped system memory)
+ - Can be *bound* to buffers and images
+
+[source, c]
+----
+vkAllocateMemory(device, &allocInfo, NULL, &memory);
+vkBindBufferMemory(device, buffer, memory, 0);
+----
+
+== vkAlloacteMemory
+
+- VkDevice device
+- VkMemoryAllocateInfo pAllocateInfo
+- VkAllocateCallbacks* pAllocator
+- VkDeviceMemory* pMemory
+
+device::
+ is the logical device that owns the memory
+
+pAllocateInfo::
+ is a pointer to a `VkMemoryAllocateInfo` structure describing parameteres of
+ the allocation. A successfully returned allocation must use the requested
+ parameters -- no substitution is permited by the implementation.
+
+pAllocator::
+ controls host memory allocation as described in the Memory Allocation
+ chapter
+
+pMemory::
+ is a pointer to a `vkDeviceMemory` handle in which information about the
+ allocated memory is returned.
+
+== vkBindBufferMemory
+- VkDevice device
+- VkBuffer buffer
+- VkDeviceMemory memory
+- VkDeviceSize memoryOffset
+
+device::
+ Your logical device
+
+buffer::
+ the `VkBuffer` handle you created
+
+memory::
+ The `VkDeviceMemory` you allocated
+
+memoryOffset::
+ This is crucial. If you are using one big memory block for multiple buffers,
+ this tell Vulkan: "Start this specific buffer 1024 bytes into the memory
+ block." Note: This offset must be a multiple of the `alignment` requirment
+ returned by `vkGetBufferMemoryRequirements`
+
+== `VkPhysicalDeviceMemoryProperties`
+The `VkPhysicalDeviceMemoryProperties` structure describes a number of memory
+heaps as well as a number of memory types that can be used to access memory
+allocated in those heaps. Each heap descibes a memory resourece of a particular
+size, and each memory type describes a set of memory properties (e.g host cached
+vs. uncached) that can be used with a given memory heap. Allocations using a
+particular memory type will consume resources from teh heap indicated by that
+memory types heap index. More than one memory type may share each heap, and the
+heaps and memory types provide a mechanism to advertise an accurate size of the
+phsysical memory resources while allowing the memory to be used with a variety
+of different properties.
+
+The number of memory heaps is given by `memoryHeapCOunt` and is less than or
+equal to `VK_MAX_MEMORY_HEAPS`. Each heap is descibed by an elemtn of the
+`memoryHeaps` array as a `vkMemoryHeap` structure.
+
+At least one heap must include `VK_MEMORY_HEAP_DEVICE_LOCAL_BIT` in
+`VkMemoryHeap::flags`. If ther are multiple heaps that all have similar
+performance characteristics, they may all include
+`VK_MEMORY_HEAP_DEVICE_LOCAL_BIT`. In a unified memory architecture (UMA) system
+there is often a single memory heap which is considered to be equally "local" to
+the host and to the device, and such an implementation must advertise the heap
+as device-local.
+
+
+== HOST_VISIBLE and HOST_COHERENT
+
+=== 1. `HOST_VISIBLE_BIT`: "Can I talk to it?"
+This flag is all about *access*.
+
+If memory is `HOST_VISIBLE`, it means the CPU is physically wired to be able to
+"see" that memory address. If a memory type *does not* have this bit, you cannot
+use `vkMapMemory`.
+
+Analogy::
+ `HOST_VISIBLE` is like having a *window* into the GPU. Without the window,
+ you can't reach inside to put data (like your vertices) onto the shelf.
+
+When you makr memory as `HOST_VISIBLE`, you are triggering a hardare feature
+called Memory Mapped I/O (MMIO)
+
+To the CPU, the GPU is just another device on the PCIe bus. Normally, the CPU
+can't just "reach out" and touch the VRAM on you RX660. `HOST_VISIBLE` tells the
+system to take a portion of the GPU's memory and map it inot the CPU's own
+address space.
+
+== The Virtuasl Address Space
+When you call `vkMapMemory`, the operating system and the GPU driver work
+together to take a chunk of the GPU's physical memory and give it a "fake"
+address in teh CPU's list.
+
+== The PCIe Bus: The Highway
+WHen you perform
--- /dev/null
+= Index Buffer
+
+== Introduction
+
+The 3D meshes you'll be rendering in a real world application will often share
+vertices between multiple triangles. This already happens even with something
+simple like drawing a rectangle:
+
+----
+ v0
+v5 +----------+ v1
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ v4 +----------+ v2
+ v3
+
+ v0 +----------+ v1
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ | \ |
+ v3 +----------+ v2
+ Indices
+ {0, 1, 2, 2, 3, 0}
+----
+
+Drawing a rectangle takes two triangles, which means that we need a vertex
+buffer with 5 vertices. The problem is that the data of two vertices needs to be
+duplicated resulting in 50% redundancy. It only gets worse with more complex
+meshes, where vertices are reused in an average number of 3 traingles. The
+solution is to use an _index_ buffer.
+
+AN index buffer is essentially an array of pointers into the vertex buffer. It
+allows you to reorder the vertex data, and resuse existing data for multiple
+vertices.
+
+[source, c]
+----
+const std::vector<Vertex> vertices = {
+ {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
+ {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
+ {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
+ {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
+};
+
+const std::vector<uint16_t> indices = {
+ 0, 1, 2, 2, 3, 0
+};
+
+void initVulkan() {
+ ...
+ createVertexBuffer();
+ createIndexBuffer();
+ ...
+}
+
+void createIndexBuffer() {
+ VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();
+
+ VkBuffer stagingBuffer;
+ VkDeviceMemory stagingBufferMemory;
+ createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
+
+ void* data;
+ vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
+ memcpy(data, indices.data(), (size_t) bufferSize);
+ vkUnmapMemory(device, stagingBufferMemory);
+
+ createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);
+
+ copyBuffer(stagingBuffer, indexBuffer, bufferSize);
+
+ vkDestroyBuffer(device, stagingBuffer, nullptr);
+ vkFreeMemory(device, stagingBufferMemory, nullptr);
+}
+----
+
+== Links
+
+* https://vulkan-tutorial.com/Vertex_buffers/Index_buffer[index buffer]
--- /dev/null
+== NDC (Normalized Device Coordinates)
+
+Based on the math inside your `main()` function and the way you are passing your
+`inPos` data, we can "reverse engineer" exactly how your 3D world is oriented.
+
+Because you are using *Vulkan's default NDC* (Normalized Device Coordinates),
+your coordinate system is currently *Y-Down* and *Right-Handed*.
+
+== Links
+* https://www.youtube.com/watch?v=e14z9oOsPu0
+* https://www.khronos.org/assets/uploads/developers/presentations/Vulkanised-2023-Tutorial-PART1.pdf
+* https://docs.vulkan.org/samples/latest/samples/extensions/dynamic_rendering/README.html
+* https://www.youtube.com/watch?v=m1RHLavNjKo
--- /dev/null
+= Summary
+
+The reason Blender shows *8 vertices* While your C code likely
+has 36 (6 faces x 2 triangles x 3 vertices) comes down to the difference between
+*Geometric Vertices* and *GPU Vertices*.
+
+== 1. Geometric vs GPU Vertices
+
+In Blender's viewport, a "vertex" is just a point in space. Since a cube only
+has 8 corners, Blender tells you there are 8 vertices.
+
+However, a GPU Vertex is a unique combination of Position + UV + Normal. If two
+faces share a corner but have different texture coordinates (UVs) or point in
+different directions (Normals), the GPU cannot treat them as the same vertex,
+They must be duplicated.
+
+== Links
+* https://vulkan-tutorial.com/Vertex_buffers/Index_buffer[Index buffers]
--- /dev/null
+#define VOLK_IMPLEMENTATION
+#include <volk/volk.h>
+#include <vulkan/vulkan.h>
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h> #include <stdint.h>
+#include <string.h>
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#include "./objParser.h"
+
+#define WIDTH 800
+#define HEIGHT 600
+# define M_PI 3.14159265358979323846 /* pi */
+#define SIZE(x) sizeof(x)/sizeof(x[0])
+#define MAX_FRAMES_IN_FLIGHT 3
+
+
+/* --- Vulkan global handles --- */
+VkInstance instance;
+VkPhysicalDevice physical;
+VkDevice device;
+VkQueue graphicsQueue;
+uint32_t graphicsQueueFamily;
+VkSurfaceKHR surface;
+
+VkSwapchainKHR swapchain;
+VkFormat swapchainFormat;
+VkExtent2D swapchainExtent;
+VkImage* swapchainImages;
+uint32_t swapchainImageCount;
+VkImageView* swapchainImageViews;
+
+VkCommandPool commandPool;
+VkCommandBuffer* commandBuffers;
+VkPipelineLayout pipelineLayout;
+VkPipeline graphicsPipeline;
+
+VkBuffer vertexBuffer;
+VkDeviceMemory vertexBufferMemory;
+
+VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore imageAvailable[MAX_FRAMES_IN_FLIGHT];
+VkSemaphore renderFinished[MAX_FRAMES_IN_FLIGHT];
+
+VkSampler sampler;
+VkDescriptorSetLayout descriptorSetLayout;
+VkDescriptorPool descriptorPool;
+VkDescriptorSet descriptorSet;
+
+VkImage textureImage;
+VkDeviceMemory textureMemory;
+VkImageView textureImageView;
+
+VkFence* imagesInFlight = NULL; // tracks which fence is using each image
+
+GLFWwindow* window;
+
+float angle = 0.0f;
+
+/* --- Vertex data --- */
+typedef struct {
+ float pos[3];
+ float uv[2];
+} Vertex;
+
+uint32_t vertexCount = 0;
+typedef struct _Vec3 { float x; float y; float z;} Vec3;
+typedef struct _Vec2 { float x; float y;} Vec2;
+
+typedef struct {
+ float x, y, z;
+} CubeVertex;
+
+CubeVertex cube[] = {
+ // front
+ {-0.5f,-0.5f,-0.5f},
+ { 0.5f,-0.5f,-0.5f},
+ { 0.5f, 0.5f,-0.5f},
+
+ {-0.5f,-0.5f,-0.5f},
+ { 0.5f, 0.5f,-0.5f},
+ {-0.5f, 0.5f,-0.5f},
+
+ // back
+ {-0.5f,-0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ {-0.5f,-0.5f, 0.5f},
+ { 0.5f, 0.5f, 0.5f},
+ { 0.5f,-0.5f, 0.5f},
+
+ // left
+ {-0.5f,-0.5f, 0.5f},
+ {-0.5f,-0.5f,-0.5f},
+ {-0.5f, 0.5f,-0.5f},
+
+ {-0.5f,-0.5f, 0.5f},
+ {-0.5f, 0.5f,-0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ // right
+ { 0.5f,-0.5f,-0.5f},
+ { 0.5f,-0.5f, 0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ { 0.5f,-0.5f,-0.5f},
+ { 0.5f, 0.5f, 0.5f},
+ { 0.5f, 0.5f,-0.5f},
+
+ // top
+ {-0.5f, 0.5f,-0.5f},
+ { 0.5f, 0.5f,-0.5f},
+ { 0.5f, 0.5f, 0.5f},
+
+ {-0.5f, 0.5f,-0.5f},
+ { 0.5f, 0.5f, 0.5f},
+ {-0.5f, 0.5f, 0.5f},
+
+ // bottom
+ {-0.5f,-0.5f, 0.5f},
+ { 0.5f,-0.5f, 0.5f},
+ { 0.5f,-0.5f,-0.5f},
+
+ {-0.5f,-0.5f, 0.5f},
+ { 0.5f,-0.5f,-0.5f},
+ {-0.5f,-0.5f,-0.5f},
+};
+
+// this finds which GPU memory type index you should use when allocating memory
+// for a buffer or image.
+// https://docs.vulkan.org/spec/latest/chapters/memory.html#VkMemoryPropertyFlagBits
+uint32_t find_memory_type(uint32_t typeBits, VkMemoryPropertyFlags props) {
+ VkPhysicalDeviceMemoryProperties memProps;
+ vkGetPhysicalDeviceMemoryProperties(physical, &memProps);
+
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
+ // check each bit and see if it matches typeBits
+ // - `typeBits` is a bitmask
+ // - Each bit corresponds to a memory type index
+ // - If bit i is set -> that memory type can be used for this resource
+ if ((typeBits & (1 << i)) &&
+ // `propertyFlags` may have extra bits
+ // You only care that your requested bits are present
+ //
+ // exmaple:
+ // props = HOST_VISIBLE | HOST_COHERENT
+ // propertyFlags = HOST_VISIBLE | HOST_COHERENT | HOST_CACHED
+ //
+ // The above would still return true
+
+ (memProps.memoryTypes[i].propertyFlags & props) == props) {
+ return i;
+ }
+ }
+
+ fprintf(stderr, "No suitable memory type\n");
+ exit(1);
+}
+
+/* --- Utility: read SPIR-V shader file --- */
+char* read_file(const char* path, size_t* size) {
+ FILE* f = fopen(path, "rb");
+ if (!f) { fprintf(stderr,"Failed to open %s\n",path); exit(1);}
+ fseek(f,0,SEEK_END);
+ *size = ftell(f);
+ rewind(f);
+ char* data = malloc(*size);
+ fread(data,1,*size,f);
+ fclose(f);
+ return data;
+}
+
+VkShaderModule load_shader(const char* path) {
+ size_t size;
+ char* code = read_file(path,&size);
+ VkShaderModuleCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = size,
+ .pCode = (const uint32_t*)code
+ };
+ VkShaderModule module;
+ if (vkCreateShaderModule(device,&info,NULL,&module)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create shader module\n"); exit(1);
+ }
+ free(code);
+ return module;
+}
+
+/* --- Swapchain setup --- */
+void create_swapchain() {
+ VkSurfaceCapabilitiesKHR caps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical,surface,&caps);
+
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ if(fmtCount == 0){fprintf(stderr,"No surface formats found\n"); exit(1);}
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+ VkSurfaceFormatKHR surf_fmt = formats[0];
+
+ for (uint32_t i = 0; i < fmtCount; i++) {
+ // Look for a format that is 8-bit BGRA and uses the sRGB color space
+ if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
+ formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ surf_fmt = formats[i];
+ break;
+ }
+ }
+ swapchainFormat = surf_fmt.format;
+
+ swapchainExtent = caps.currentExtent;
+
+ VkSwapchainCreateInfoKHR scInfo = {
+ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+ .surface = surface,
+ .minImageCount = caps.minImageCount + 1,
+ .imageFormat = swapchainFormat,
+ .imageColorSpace = surf_fmt.colorSpace, // sRBG, HDR10 etc..
+ .imageExtent = swapchainExtent,
+ .imageArrayLayers = 1,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .preTransform = caps.currentTransform,
+ .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+ // .presentMode = VK_PRESENT_MODE_FIFO_KHR,
+ // should check if GPU supports this presentMode
+ .presentMode = VK_PRESENT_MODE_MAILBOX_KHR,
+ .clipped = VK_TRUE
+ };
+ // pAllocator is set to NULL
+ if (vkCreateSwapchainKHR(device,&scInfo,NULL,&swapchain)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create swapchain\n"); exit(1);
+ }
+
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,NULL);
+ swapchainImages = malloc(sizeof(VkImage)*swapchainImageCount);
+ vkGetSwapchainImagesKHR(device,swapchain,&swapchainImageCount,swapchainImages);
+ printf("image count: %d\n", swapchainImageCount);
+
+ swapchainImageViews = malloc(sizeof(VkImageView)*swapchainImageCount);
+ for(uint32_t i=0;i<swapchainImageCount;i++){
+ VkImageViewCreateInfo ivInfo = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = swapchainImages[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = swapchainFormat,
+ .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
+ }
+ };
+ if(vkCreateImageView(device,&ivInfo,NULL,&swapchainImageViews[i])!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create image view\n"); exit(1);
+ }
+ }
+}
+
+
+/* --- Command pool + buffers --- */
+void create_command_pool_buffers() {
+ VkCommandPoolCreateInfo cpInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .queueFamilyIndex = graphicsQueueFamily
+ };
+ if(vkCreateCommandPool(device,&cpInfo,NULL,&commandPool)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create command pool\n"); exit(1);
+ }
+
+ commandBuffers = malloc(sizeof(VkCommandBuffer)*swapchainImageCount);
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = swapchainImageCount
+ };
+ if(vkAllocateCommandBuffers(device,&cbAlloc,commandBuffers)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to allocate command buffers\n"); exit(1);
+ }
+}
+
+/* --- Vertex buffer --- */
+void create_vertex_buffer() {
+ // 1. Calculate the exact size of your cube array
+ // Since cube is defined as CubeVertex cube[], this is exactly 36 * 12 bytes
+ VkDeviceSize bufferSize = sizeof(cube);
+ vertexCount = sizeof(cube) / sizeof(cube[0]);
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = bufferSize,
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE
+ };
+
+ if (vkCreateBuffer(device, &bufInfo, NULL, &vertexBuffer) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create vertex buffer\n");
+ exit(1);
+ }
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq);
+
+ // 2. Find memory that is both Visible (can map it) and Coherent (no flush needed)
+ VkMemoryAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex = find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+ };
+
+ if (vkAllocateMemory(device, &allocInfo, NULL, &vertexBufferMemory) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to allocate vertex buffer memory\n");
+ exit(1);
+ }
+
+ vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
+
+ // 3. Map and copy DIRECTLY from your 'cube' array
+ // This removes the risk of struct-mismatch bugs
+ void* data;
+ vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data);
+ memcpy(data, cube, (size_t)bufferSize);
+ vkUnmapMemory(device, vertexBufferMemory);
+}
+/* --- Graphics pipeline --- */
+void create_graphics_pipeline() {
+ VkShaderModule vertModule = load_shader("cube.vert.spv");
+ VkShaderModule fragModule = load_shader("cube.frag.spv");
+
+ VkPipelineShaderStageCreateInfo stages[2] = {0};
+ stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+ stages[0].module = vertModule;
+ stages[0].pName = "main";
+
+ stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ stages[1].module = fragModule;
+ stages[1].pName = "main";
+
+ // One Vertex per vertex
+ // Move forward sizeof(Vertex) each vertex
+ VkVertexInputBindingDescription bindingDesc = {
+ .binding = 0,
+ .stride = sizeof(CubeVertex),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+ };
+
+ VkVertexInputAttributeDescription attrDesc[] = {
+ {
+ .location = 0,
+ .binding = 0,
+ .format = VK_FORMAT_R32G32B32_SFLOAT,
+ .offset = 0
+ }
+ };
+
+ VkPipelineVertexInputStateCreateInfo vertexInput = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount=1,
+ .pVertexBindingDescriptions=&bindingDesc,
+ .vertexAttributeDescriptionCount=1,
+ .pVertexAttributeDescriptions=attrDesc
+ };
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ // .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE
+ };
+ VkViewport viewport = {0.0f,0.0f,(float)swapchainExtent.width,(float)swapchainExtent.height,0.0f,1.0f};
+ VkRect2D scissor = { .offset={0,0}, .extent=swapchainExtent };
+ VkPipelineViewportStateCreateInfo vpState = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount=1, .pViewports=&viewport, .scissorCount=1, .pScissors=&scissor };
+ VkPipelineRasterizationStateCreateInfo rasterizer = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .depthClampEnable=VK_FALSE, .rasterizerDiscardEnable=VK_FALSE,
+ .polygonMode=VK_POLYGON_MODE_LINE, .cullMode=VK_CULL_MODE_NONE,
+ .frontFace=VK_FRONT_FACE_CLOCKWISE, .lineWidth=1.0f
+ };
+ VkPipelineMultisampleStateCreateInfo multisampling = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
+ // .rasterizationSamples = VK_SAMPLE_COUNT_4_BIT
+ };
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT|VK_COLOR_COMPONENT_G_BIT|VK_COLOR_COMPONENT_B_BIT|VK_COLOR_COMPONENT_A_BIT,
+ .blendEnable = VK_FALSE
+ };
+ VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount=1, .pAttachments=&colorBlendAttachment };
+
+ VkPushConstantRange pushRange = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(float)
+ };
+
+ VkDescriptorSetLayoutBinding texBinding = {
+ .binding = 0,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
+ };
+
+ VkDescriptorSetLayoutCreateInfo layoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .bindingCount = 1,
+ .pBindings = &texBinding
+ };
+
+ vkCreateDescriptorSetLayout(device, &layoutInfo, NULL, &descriptorSetLayout);
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pushRange,
+ .setLayoutCount = 0,
+ .pSetLayouts = NULL,
+ };
+
+ vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout);
+ uint32_t fmtCount=0;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,NULL);
+ VkSurfaceFormatKHR formats[fmtCount];
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physical,surface,&fmtCount,formats);
+
+ VkPipelineRenderingCreateInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
+ .colorAttachmentCount = 1,
+ .pColorAttachmentFormats = &swapchainFormat
+ };
+
+ VkGraphicsPipelineCreateInfo pipelineInfo = {
+ .sType=VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = &renderingInfo,
+ .stageCount = 2,
+ .pStages = stages,
+ .pVertexInputState = &vertexInput,
+ .pInputAssemblyState = &inputAssembly,
+ .pViewportState = &vpState,
+ .pRasterizationState = &rasterizer,
+ .pMultisampleState = &multisampling,
+ .pColorBlendState = &colorBlending,
+ .layout = pipelineLayout,
+ .renderPass = VK_NULL_HANDLE,
+ .subpass = 0
+ };
+
+ if(vkCreateGraphicsPipelines(device,VK_NULL_HANDLE,1,&pipelineInfo,NULL,
+ &graphicsPipeline)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create graphics pipeline\n"); exit(1);
+ }
+
+ vkDestroyShaderModule(device,vertModule,NULL);
+ vkDestroyShaderModule(device,fragModule,NULL);
+}
+
+void create_texture_sampler() {
+ VkSamplerCreateInfo samplerInfo = {
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ // this creates to black stars from an artifact
+ // .magFilter = VK_FILTER_LINEAR,
+ // .minFilter = VK_FILTER_LINEAR,
+ .magFilter = VK_FILTER_NEAREST,
+ .minFilter = VK_FILTER_NEAREST,
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+ };
+
+ vkCreateSampler(device, &samplerInfo, NULL, &sampler);
+}
+
+void create_descriptor_pool() {
+ VkDescriptorPoolSize poolSize = {
+ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1
+ };
+
+ VkDescriptorPoolCreateInfo poolInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ .poolSizeCount = 1,
+ .pPoolSizes = &poolSize,
+ .maxSets = 1
+ };
+
+ vkCreateDescriptorPool(device, &poolInfo, NULL, &descriptorPool);
+}
+
+void create_descriptor_set() {
+ VkDescriptorSetAllocateInfo allocInfo = {
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+ .descriptorPool = descriptorPool,
+ .descriptorSetCount = 1,
+ .pSetLayouts = &descriptorSetLayout
+ };
+
+ vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet);
+}
+
+void create_texture_image() {
+ uint32_t pixels[] = {
+ 0xFFFFFFFF, 0xFFFF69B4, // top row: white, pink
+ 0xFFFF69B4, 0xFFFFFFFF // bottom row: pink, white
+ };
+
+ VkDeviceSize size = sizeof(pixels);
+
+ VkBuffer staging;
+ VkDeviceMemory stagingMem;
+
+ VkBufferCreateInfo bufInfo = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = size,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+ };
+ vkCreateBuffer(device, &bufInfo, NULL, &staging);
+
+ VkMemoryRequirements memReq;
+ vkGetBufferMemoryRequirements(device, staging, &memReq);
+
+ VkMemoryAllocateInfo alloc = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReq.size,
+ .memoryTypeIndex =
+ find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
+ };
+
+ vkAllocateMemory(device, &alloc, NULL, &stagingMem);
+ vkBindBufferMemory(device, staging, stagingMem, 0);
+
+ void* data;
+ vkMapMemory(device, stagingMem, 0, size, 0, &data);
+ memcpy(data, pixels, size);
+ vkUnmapMemory(device, stagingMem);
+
+ VkImageCreateInfo img = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .extent = {2, 2, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED
+ };
+
+ vkCreateImage(device, &img, NULL, &textureImage);
+
+ vkGetImageMemoryRequirements(device, textureImage, &memReq);
+
+ alloc.allocationSize = memReq.size;
+ alloc.memoryTypeIndex =
+ find_memory_type(memReq.memoryTypeBits,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ vkAllocateMemory(device, &alloc, NULL, &textureMemory);
+ vkBindImageMemory(device, textureImage, textureMemory, 0);
+
+ VkCommandBufferAllocateInfo cbAlloc = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = commandPool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1
+ };
+
+ VkCommandBuffer cb;
+ vkAllocateCommandBuffers(device, &cbAlloc, &cb);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ VkImageMemoryBarrier toTransfer = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .image = textureImage,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ }
+ };
+
+ vkCmdPipelineBarrier(
+ cb, // commandBufer
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask
+ VK_PIPELINE_STAGE_TRANSFER_BIT, // DstStageMask
+ 0, // dependabctFlags
+ 0, // memoryBarrierCount
+ NULL, // pMemoryBarriers
+ 0, // bufferMemoryBarrierCount
+ NULL, // pBuferMemoryBarriers
+ 1, // imageMemoryBarrierCount
+ &toTransfer // pImageMemoryBarriers
+ );
+
+ VkBufferImageCopy copy = {
+ .imageSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1
+ },
+ .imageExtent = {.width = 2, .height = 2, .depth = 1}
+ };
+
+ vkCmdCopyBufferToImage(
+ cb,
+ staging,
+ textureImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1,
+ ©);
+
+ VkImageMemoryBarrier toSample = toTransfer;
+ toSample.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ toSample.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ toSample.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ toSample.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+ 0, 0, NULL, 0, NULL, 1, &toSample
+ );
+
+ vkEndCommandBuffer(cb);
+
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb
+ };
+ VkFence fence;
+ VkFenceCreateInfo fenceInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
+ vkCreateFence(device, &fenceInfo, NULL, &fence);
+ vkQueueSubmit(graphicsQueue, 1, &submit, fence);
+ vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+ vkDestroyFence(device, fence, NULL);
+
+
+ // cleanup staging resources
+ vkFreeCommandBuffers(device, commandPool, 1, &cb);
+ vkDestroyBuffer(device, staging, NULL);
+ vkFreeMemory(device, stagingMem, NULL);
+}
+
+void create_texture_image_view() {
+ VkImageViewCreateInfo view = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = textureImage,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = VK_FORMAT_R8G8B8A8_UNORM,
+ .subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1
+ }
+ };
+ vkCreateImageView(device, &view, NULL, &textureImageView);
+}
+
+void update_descriptor_set() {
+ VkDescriptorImageInfo imageInfo = {
+ .sampler = sampler,
+ .imageView = textureImageView,
+ .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+ };
+
+ VkWriteDescriptorSet write = {
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .dstSet = descriptorSet,
+ .dstBinding = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &imageInfo
+ };
+
+ vkUpdateDescriptorSets(device, 1, &write, 0, NULL);
+}
+
+
+/* --- Main --- */
+int main(void) {
+ double lastTime = glfwGetTime();
+ if(!glfwInit()){fprintf(stderr,"GLFW init failed\n");return 1;}
+ glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
+ window = glfwCreateWindow(WIDTH,HEIGHT,"Vulkan Mesh",NULL,NULL);
+
+ if(volkInitialize()!=VK_SUCCESS){fprintf(stderr,"volk init failed\n");return 1;}
+
+ uint32_t instanceVersion;
+ vkEnumerateInstanceVersion(&instanceVersion);
+ printf("Vulkan loader supports version %d.%d.%d\n",
+ VK_API_VERSION_MAJOR(instanceVersion),
+ VK_API_VERSION_MINOR(instanceVersion),
+ VK_API_VERSION_PATCH(instanceVersion)
+ );
+
+ if (!(VK_API_VERSION_MAJOR(instanceVersion) >= 1 &&
+ VK_API_VERSION_MINOR(instanceVersion) >= 3)) {
+ fprintf(stderr, "need at least vulkan 1.3");
+ exit(1);
+ }
+
+ uint32_t ext_count=0;
+ VkApplicationInfo appInfo = {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = NULL,
+ .pApplicationName = "cube",
+ .applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0),
+ .apiVersion = VK_API_VERSION_1_3
+ };
+
+ const char** exts=glfwGetRequiredInstanceExtensions(&ext_count);
+ VkInstanceCreateInfo ici={.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &appInfo,
+ .enabledExtensionCount=ext_count,
+ .ppEnabledExtensionNames=exts};
+
+ vkCreateInstance(&ici,NULL,&instance);
+
+ // You get instance-level pointers this way
+ volkLoadInstance(instance);
+
+
+ if(glfwCreateWindowSurface(instance,window,NULL,&surface)!=VK_SUCCESS){
+ fprintf(stderr,"Failed to create surface\n");return 1;
+ }
+
+ uint32_t physCount=0; vkEnumeratePhysicalDevices(instance,&physCount,NULL);
+ VkPhysicalDevice devices[8];
+ vkEnumeratePhysicalDevices(instance,&physCount,devices);
+ printf("device count: %d\n", physCount);
+
+ // getting the name of the devices
+ for (int i = 0; i < physCount; ++i) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(devices[i], &properties);
+ printf("GPU Name: %s\n", properties.deviceName);
+
+ // if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
+
+ // }
+ if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ physical = devices[i];
+ }
+
+ }
+ // physical=devices[0];
+
+ uint32_t qcount=0;
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,NULL);
+ // VkQueueFamilyProperties qprops[16]; // just a arbitrary number..
+ VkQueueFamilyProperties qprops[qcount];
+ vkGetPhysicalDeviceQueueFamilyProperties(physical,&qcount,qprops);
+
+ int found=0;
+ for(uint32_t i=0;i<qcount;i++){
+ VkBool32 pres=VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physical,i,surface,&pres);
+ if((qprops[i].queueFlags&VK_QUEUE_GRAPHICS_BIT)&&pres) {
+ graphicsQueueFamily=i;
+ found=1;
+ break;
+ }
+ }
+
+ if(!found){fprintf(stderr,"No suitable queue family\n");return 1;}
+
+ const char* devExts[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
+ float pri=1.0f;
+ VkDeviceQueueCreateInfo qci={.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .queueFamilyIndex=graphicsQueueFamily,
+ .queueCount=1,
+ .pQueuePriorities=&pri};
+ VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
+ // .pNext = NULL,
+ .dynamicRendering = VK_TRUE
+ };
+
+ VkDeviceCreateInfo dci={.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = &dynamicRenderingFeature,
+ .queueCreateInfoCount=1,
+ .pQueueCreateInfos=&qci,
+ .enabledExtensionCount=1,
+ .ppEnabledExtensionNames=devExts};
+ vkCreateDevice(physical,&dci,NULL,&device);
+ // You get Device-level pointers this way
+ volkLoadDevice(device);
+ vkGetDeviceQueue(device,graphicsQueueFamily,0,&graphicsQueue);
+
+ create_swapchain();
+ imagesInFlight = malloc(sizeof(VkFence) * swapchainImageCount);
+ for (uint32_t i = 0; i < swapchainImageCount; i++)
+ imagesInFlight[i] = VK_NULL_HANDLE;
+
+ create_command_pool_buffers();
+ create_graphics_pipeline();
+
+ create_texture_image();
+ create_texture_image_view();
+ create_texture_sampler();
+
+ create_descriptor_pool();
+ create_descriptor_set();
+ update_descriptor_set();
+
+ create_vertex_buffer();
+
+ VkSemaphoreCreateInfo sem = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
+ };
+
+ VkFenceCreateInfo fence = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT
+ };
+
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (vkCreateSemaphore(device, &sem, NULL, &imageAvailable[i]) != VK_SUCCESS ||
+ vkCreateSemaphore(device, &sem, NULL, &renderFinished[i]) != VK_SUCCESS ||
+ vkCreateFence(device, &fence, NULL, &inFlightFences[i]) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create sync objects\n");
+ exit(1);
+ }
+ }
+
+ uint32_t currentFrame = 0;
+
+
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
+
+ double now = glfwGetTime();
+ double delta = now - lastTime;
+ if (delta > 0.03) delta = 0.03; // clamp to max ~33ms
+ lastTime = now;
+ float rotationSpeed = M_PI / 2.0f; // 90°/sec
+ angle += rotationSpeed * delta;
+
+ uint32_t imgIndex;
+
+ // Acquire next swapchain image
+ vkAcquireNextImageKHR(
+ device,
+ swapchain,
+ UINT64_MAX,
+ imageAvailable[currentFrame],
+ VK_NULL_HANDLE,
+ &imgIndex
+ );
+
+ // Wait for previous frame using this image
+ if (imagesInFlight[imgIndex] != VK_NULL_HANDLE) {
+ vkWaitForFences(device, 1, &imagesInFlight[imgIndex], VK_TRUE, UINT64_MAX);
+ }
+
+ // Mark image as now in use by current frame's fence
+ imagesInFlight[imgIndex] = inFlightFences[currentFrame];
+
+ // Reset current frame fence
+ vkResetFences(device, 1, &inFlightFences[currentFrame]);
+
+ // Get command buffer for this image
+ VkCommandBuffer cb = commandBuffers[imgIndex];
+ vkResetCommandBuffer(cb, 0);
+
+ VkCommandBufferBeginInfo begin = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO
+ };
+ vkBeginCommandBuffer(cb, &begin);
+
+ // Transition image: PRESENT -> COLOR_ATTACHMENT_OPTIMAL
+ VkImageMemoryBarrier barrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .image = swapchainImages[imgIndex],
+ .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }
+ };
+ vkCmdPipelineBarrier(
+ cb,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier
+ );
+
+ // Begin dynamic rendering
+ VkRenderingAttachmentInfo colorAttachment = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
+ .imageView = swapchainImageViews[imgIndex],
+ .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .clearValue = {{0.0f, 0.0f, 0.0f, 1.0f}}
+ };
+ VkRenderingInfo renderingInfo = {
+ .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
+ .renderArea = {{0, 0}, swapchainExtent},
+ .layerCount = 1,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &colorAttachment
+ };
+ vkCmdBeginRendering(cb, &renderingInfo);
+
+ // Bind pipeline, viewport, scissor
+ vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
+ VkViewport vp = {0, 0, (float)swapchainExtent.width, (float)swapchainExtent.height, 0.0f, 1.0f};
+ VkRect2D scissor = {{0,0}, swapchainExtent};
+ vkCmdSetViewport(cb, 0, 1, &vp);
+ vkCmdSetScissor(cb, 0, 1, &scissor);
+
+ // Bind vertex buffer
+ VkDeviceSize offset = 0;
+ vkCmdBindVertexBuffers(cb, 0, 1, &vertexBuffer, &offset);
+
+ // Push rotation angle
+ vkCmdPushConstants(cb, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float), &angle);
+
+ // Bind descriptor set
+ vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+ // Draw
+ vkCmdDraw(cb, vertexCount, 1, 0, 0);
+
+ vkCmdEndRendering(cb);
+
+ // Transition image: COLOR_ATTACHMENT_OPTIMAL -> PRESENT
+ barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ barrier.dstAccessMask = 0;
+ vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &barrier);
+
+ vkEndCommandBuffer(cb);
+
+ // Submit
+ VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo submit = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &imageAvailable[currentFrame],
+ .pWaitDstStageMask = &waitStage,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cb,
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &renderFinished[currentFrame],
+ };
+ vkQueueSubmit(graphicsQueue, 1, &submit, inFlightFences[currentFrame]);
+
+ // Present
+ VkPresentInfoKHR present = {
+ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &renderFinished[currentFrame],
+ .swapchainCount = 1,
+ .pSwapchains = &swapchain,
+ .pImageIndices = &imgIndex
+ };
+ vkQueuePresentKHR(graphicsQueue, &present);
+
+ // Advance frame
+ currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+ }
+
+
+ // Wait for GPU to finish all work
+ vkDeviceWaitIdle(device);
+
+ // Destroy semaphores and fences
+ for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ if (imageAvailable[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, imageAvailable[i], NULL);
+ if (renderFinished[i] != VK_NULL_HANDLE)
+ vkDestroySemaphore(device, renderFinished[i], NULL);
+ if (inFlightFences[i] != VK_NULL_HANDLE)
+ vkDestroyFence(device, inFlightFences[i], NULL);
+ }
+
+ // Free imagesInFlight array
+ free(imagesInFlight);
+
+ // Destroy vertex buffer
+ vkDestroyBuffer(device, vertexBuffer, NULL);
+ vkFreeMemory(device, vertexBufferMemory, NULL);
+
+ // Destroy texture resources
+ vkDestroySampler(device, sampler, NULL);
+ vkDestroyImageView(device, textureImageView, NULL);
+ vkDestroyImage(device, textureImage, NULL);
+ vkFreeMemory(device, textureMemory, NULL);
+
+ // Destroy descriptor sets/pools/layout
+ vkDestroyDescriptorPool(device, descriptorPool, NULL);
+ vkDestroyDescriptorSetLayout(device, descriptorSetLayout, NULL);
+
+ // Destroy pipeline and pipeline layout
+ vkDestroyPipeline(device, graphicsPipeline, NULL);
+ vkDestroyPipelineLayout(device, pipelineLayout, NULL);
+
+ // Destroy command pool
+ vkDestroyCommandPool(device, commandPool, NULL);
+
+ // Destroy swapchain image views
+ for (uint32_t i = 0; i < swapchainImageCount; i++) {
+ vkDestroyImageView(device, swapchainImageViews[i], NULL);
+ }
+ free(swapchainImageViews);
+ free(swapchainImages);
+
+ // Destroy swapchain
+ vkDestroySwapchainKHR(device, swapchain, NULL);
+
+ // Destroy device, surface, and instance
+ vkDestroyDevice(device, NULL);
+ vkDestroySurfaceKHR(instance, surface, NULL);
+ vkDestroyInstance(instance, NULL);
+
+ // Destroy GLFW window
+ glfwDestroyWindow(window);
+ glfwTerminate();
+
+ return 0;
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <regex.h>
+#include <string.h>
+#include <stdint.h>
+
+#define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
+
+void getObjInfo(char *file, float **verts, uint16_t *vertCount) {
+ // printf("nope\n");
+ // return;
+ FILE *fp = fopen(file, "r");
+
+ if (fp == NULL)
+ fprintf(stderr, "cannot locate file\n");
+
+ size_t len = 0;
+ char *line = NULL;
+
+ // just for better readability
+ // float *pverts = (*verts);
+
+ (*vertCount) = 0;
+ regex_t regex;
+ if (regcomp(®ex, "^v ", REG_NEWLINE)) {
+ fprintf(stderr, "could not comp regular expression\n");
+ exit(1);
+ }
+
+ while (getline(&line, &len, fp) != -1) {
+ regmatch_t pmatch[1];
+ if (regexec(®ex, line, ARRAY_SIZE(pmatch), pmatch, 0) == REG_NOMATCH)
+ continue;
+
+ char *str = line + pmatch[0].rm_eo;
+
+ char *token = strtok(str, " ");
+
+ for (int i = 0; i < 3; ++i) {
+ float a = strtof(token, NULL);
+ (*verts) = (float *)reallocarray((*verts), (*vertCount) + 1, sizeof(float));
+ (*verts)[(*vertCount)] = a;
+ (*vertCount) += 1;
+ token = strtok(NULL, " ");
+ }
+ }
+ regfree(®ex);
+ free(line);
+ fclose(fp);
+}
+
+// void getObjInfo(char *file, float **verts, uint16_t *vertCount) {
+// FILE *fp = fopen(file, "r");
+// size_t len = 0;
+// char *line = NULL;
+
+// // just for better readability
+// float *pverts = (*verts);
+
+// (*vertCount) = 0;
+
+// while (getline(&line, &len, fp) != -1) {
+// regex_t regex;
+// regmatch_t pmatch[1];
+// if (regcomp(®ex, "^v ", REG_NEWLINE)) {
+// fprintf(stderr, "could not comp regular expression\n");
+// exit(1);
+// }
+// if (regexec(®ex, line, ARRAY_SIZE(pmatch), pmatch, 0) == REG_NOMATCH)
+// continue;
+
+// char *str = line + pmatch[0].rm_eo;
+
+// char *token = strtok(str, " ");
+// float a;
+// for (int i = 0; i < 3; ++i) {
+// a = strtof(token, NULL);
+// token = strtok(str, " ");
+// if ((*vertCount) == 0) {
+// pverts = calloc(1, sizeof(float));
+// } else {
+// pverts = reallocarray(pverts, (*vertCount) + 1, sizeof(float));
+// }
+// pverts[(*vertCount)] = a;
+// (*vertCount) += 1;
+// }
+// }
+
+// free(line);
+// fclose(fp);
+// (*verts) = pverts;
+// }
+
+int main(void)
+{
+ float *verts = NULL;
+ uint16_t size = 0;
+ getObjInfo("cube.obj", &verts, &size);
+ for (int i = 0; i < size; ++i) {
+ printf("%f\n", verts[i]);
+ }
+ if (verts) free(verts);
+}
--- /dev/null
+#ifndef objparser_h
+#define objparser_h
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <regex.h>
+#include <string.h>
+#include <stdint.h>
+
+#define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
+
+void getObjInfo(char *file, float **verts, uint16_t *vertCount) {
+ FILE *fp = fopen(file, "r");
+
+ if (fp == NULL)
+ fprintf(stderr, "cannot locate file\n");
+
+ size_t len = 0;
+
+ (*vertCount) = 0;
+
+ *vertCount = 0;
+
+ while (true) {
+ char *line = NULL;
+ ssize_t lineSize = getline(&line, &len, fp);
+ if (lineSize == -1) break;
+ if (strncmp(line, "v ", 2) != 0) continue;
+ char *str = line + 2;
+ char *token = strtok(str, " ");
+ for(int i = 0; i < 3; ++i){
+ if (!token) break;
+ float a = strtof(token, NULL);
+ (*verts) = (float *)realloc(*verts, (*vertCount + 1) * sizeof(float));
+ (*verts)[*vertCount] = a;
+ (*vertCount)++;
+ token = strtok(NULL, " ");
+ }
+ free(line);
+ }
+
+
+ fclose(fp);
+}
+#endif
--- /dev/null
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) < 3:
+ print("Usage: python3 obj_to_c.py input.obj output.c")
+ sys.exit(1)
+
+input_file = sys.argv[1]
+output_file = sys.argv[2]
+
+vertices = []
+indices = []
+
+with open(input_file, 'r') as f:
+ for line in f:
+ if line.startswith('v '): # Vertex position
+ parts = line.strip().split()
+ x, y, z = map(float, parts[1:4])
+ vertices.append((x, y, z))
+ elif line.startswith('f '): # Face (triangles only)
+ parts = line.strip().split()
+ # OBJ indices are 1-based
+ face = [int(p.split('/')[0]) - 1 for p in parts[1:]]
+ # Triangulate quads automatically if needed
+ if len(face) == 3:
+ indices.extend(face)
+ elif len(face) == 4:
+ # quad: a,b,c,d -> a,b,c + a,c,d
+ indices.extend([face[0], face[1], face[2], face[0], face[2], face[3]])
+
+# Write C struct
+with open(output_file, 'w') as f:
+ f.write("// Generated from {}\n\n".format(input_file))
+ f.write("typedef struct { float pos[3]; float color[3]; } Vertex;\n\n")
+ f.write("Vertex vertices[] = {\n")
+ for v in vertices:
+ # default color white
+ f.write(f" {{ {{ {v[0]}f, {v[1]}f, {v[2]}f }}, {{1.0f, 1.0f, 1.0f}} }},\n")
+ f.write("};\n\n")
+
+ f.write("uint32_t indices[] = {\n")
+ for i in range(0, len(indices), 3):
+ f.write(f" {indices[i]}, {indices[i+1]}, {indices[i+2]},\n")
+ f.write("};\n")