/* * iter4 textured-quad probe for panvk-bifrost campaign. * * Tests the Bifrost-specific descriptor model + texture upload + sampled-image * read on PanVk-Bifrost (PineTab2 / Mali-G52 r1 MC1). * * Texel encoding for 4x4 source: R = 0x10 + 0x40*x, G = 0x10 + 0x40*y, * B = 0x80, A = 0xff (16 unique values). * Output pixel (col, row) == texel(col%4, row%4), repeated in a 16x16-tile * grid across the 64x64 attachment. */ #include #include #include #include #include #include #define IMG_W 64 #define IMG_H 64 #define PIXELS (IMG_W * IMG_H) #define BUFFER_BYTES (PIXELS * 4) /* 16384 */ #define TEX_W 4 #define TEX_H 4 #define TEX_PIXELS (TEX_W * TEX_H) #define TEX_BYTES (TEX_PIXELS * 4) /* 64 */ #define VSPV_PATH "probe_texture.vert.spv" #define FSPV_PATH "probe_texture.frag.spv" /* Source texel packed LE uint32 = (A<<24)|(B<<16)|(G<<8)|R */ static inline uint32_t texel_le(uint32_t x, uint32_t y) { uint32_t r = 0x10 + 0x40 * x; uint32_t g = 0x10 + 0x40 * y; return (0xffu << 24) | (0x80u << 16) | (g << 8) | r; } #define STEP(name) do { fprintf(stderr, "[step] " name "\n"); fflush(stderr); } while (0) #define VK_CHECK(call) do { \ VkResult _r = (call); \ if (_r != VK_SUCCESS) { \ fprintf(stderr, "[fail] " #call " => %d at %s:%d\n", \ (int)_r, __FILE__, __LINE__); \ exit(2); \ } \ } while (0) static uint32_t *read_spv(const char *path, size_t *out_bytes) { FILE *f = fopen(path, "rb"); if (!f) { fprintf(stderr, "[fail] open %s: %s\n", path, strerror(errno)); exit(3); } fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); if (n <= 0 || (n & 3)) { fprintf(stderr, "[fail] bad SPV size %ld\n", n); exit(3); } uint32_t *buf = malloc((size_t)n); if (fread(buf, 1, (size_t)n, f) != (size_t)n) { fprintf(stderr, "[fail] short read\n"); exit(3); } fclose(f); *out_bytes = (size_t)n; return buf; } static uint32_t pick_memtype(const VkPhysicalDeviceMemoryProperties *mp, uint32_t type_bits, VkMemoryPropertyFlags want) { for (uint32_t i = 0; i < mp->memoryTypeCount; i++) { if ((type_bits & (1u << i)) && (mp->memoryTypes[i].propertyFlags & want) == want) return i; } fprintf(stderr, "[fail] no memtype want=0x%x bits=0x%x\n", want, type_bits); exit(4); } static uint32_t pick_host_visible(const VkPhysicalDeviceMemoryProperties *mp, uint32_t type_bits) { VkMemoryPropertyFlags pref = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; for (uint32_t i = 0; i < mp->memoryTypeCount; i++) { if ((type_bits & (1u << i)) && (mp->memoryTypes[i].propertyFlags & pref) == pref) return i; } for (uint32_t i = 0; i < mp->memoryTypeCount; i++) { if ((type_bits & (1u << i)) && (mp->memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) return i; } fprintf(stderr, "[fail] no HOST_VISIBLE\n"); exit(4); } static void image_barrier(VkCommandBuffer cb, VkImage img, VkImageLayout old_layout, VkImageLayout new_layout, VkAccessFlags src_access, VkAccessFlags dst_access, VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage) { VkImageMemoryBarrier ib = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask = src_access, .dstAccessMask = dst_access, .oldLayout = old_layout, .newLayout = new_layout, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = img, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; vkCmdPipelineBarrier(cb, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &ib); } static VkShaderModule make_shader(VkDevice dev, const char *path) { size_t bytes = 0; uint32_t *code = read_spv(path, &bytes); VkShaderModuleCreateInfo smci = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = bytes, .pCode = code, }; VkShaderModule sm; VK_CHECK(vkCreateShaderModule(dev, &smci, NULL, &sm)); free(code); return sm; } int main(void) { STEP("vkCreateInstance"); const char *inst_exts[] = { "VK_KHR_get_physical_device_properties2" }; VkApplicationInfo app = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "panvk-bifrost iter4", .apiVersion = VK_API_VERSION_1_0, }; VkInstanceCreateInfo ici = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app, .enabledExtensionCount = 1, .ppEnabledExtensionNames = inst_exts, }; VkInstance inst; VK_CHECK(vkCreateInstance(&ici, NULL, &inst)); STEP("vkEnumeratePhysicalDevices"); uint32_t n_phys = 0; VK_CHECK(vkEnumeratePhysicalDevices(inst, &n_phys, NULL)); if (n_phys == 0) { fprintf(stderr, "[fail] no devices\n"); return 5; } VkPhysicalDevice *phys = calloc(n_phys, sizeof(*phys)); VK_CHECK(vkEnumeratePhysicalDevices(inst, &n_phys, phys)); VkPhysicalDevice gpu = phys[0]; VkPhysicalDeviceProperties pp; vkGetPhysicalDeviceProperties(gpu, &pp); fprintf(stderr, "[info] gpu='%s'\n", pp.deviceName); VkPhysicalDeviceMemoryProperties mp; vkGetPhysicalDeviceMemoryProperties(gpu, &mp); uint32_t n_qf = 0; vkGetPhysicalDeviceQueueFamilyProperties(gpu, &n_qf, NULL); VkQueueFamilyProperties *qfp = calloc(n_qf, sizeof(*qfp)); vkGetPhysicalDeviceQueueFamilyProperties(gpu, &n_qf, qfp); uint32_t qfam = UINT32_MAX; for (uint32_t i = 0; i < n_qf; i++) { if (qfp[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { qfam = i; break; } } if (qfam == UINT32_MAX) { fprintf(stderr, "[fail] no graphics queue\n"); return 6; } STEP("vkCreateDevice (+dynamic_rendering chain)"); const char *dev_exts[] = { "VK_KHR_multiview", "VK_KHR_maintenance2", "VK_KHR_create_renderpass2", "VK_KHR_depth_stencil_resolve", "VK_KHR_dynamic_rendering", }; VkPhysicalDeviceDynamicRenderingFeaturesKHR dyn_feat = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR, .dynamicRendering = VK_TRUE, }; float qprio = 1.0f; VkDeviceQueueCreateInfo qci = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = qfam, .queueCount = 1, .pQueuePriorities = &qprio, }; VkDeviceCreateInfo dci = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &dyn_feat, .queueCreateInfoCount = 1, .pQueueCreateInfos = &qci, .enabledExtensionCount = sizeof(dev_exts)/sizeof(dev_exts[0]), .ppEnabledExtensionNames = dev_exts, }; VkDevice dev; VK_CHECK(vkCreateDevice(gpu, &dci, NULL, &dev)); VkQueue queue; vkGetDeviceQueue(dev, qfam, 0, &queue); PFN_vkCmdBeginRenderingKHR pCmdBeginRendering = (PFN_vkCmdBeginRenderingKHR)vkGetDeviceProcAddr(dev, "vkCmdBeginRenderingKHR"); PFN_vkCmdEndRenderingKHR pCmdEndRendering = (PFN_vkCmdEndRenderingKHR)vkGetDeviceProcAddr(dev, "vkCmdEndRenderingKHR"); /* ---- source texture (4x4) ------------------------------------------- */ STEP("vkCreateImage source texture (4x4 RGBA8 SAMPLED|TRANSFER_DST)"); VkImageCreateInfo tex_ici = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = VK_FORMAT_R8G8B8A8_UNORM, .extent = { TEX_W, TEX_H, 1 }, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, }; VkImage tex; VK_CHECK(vkCreateImage(dev, &tex_ici, NULL, &tex)); VkMemoryRequirements tex_mr; vkGetImageMemoryRequirements(dev, tex, &tex_mr); fprintf(stderr, "[info] source texture memReq size=%llu align=%llu\n", (unsigned long long)tex_mr.size, (unsigned long long)tex_mr.alignment); VkMemoryAllocateInfo tex_mai = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = tex_mr.size, .memoryTypeIndex = pick_memtype(&mp, tex_mr.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), }; VkDeviceMemory tex_mem; VK_CHECK(vkAllocateMemory(dev, &tex_mai, NULL, &tex_mem)); VK_CHECK(vkBindImageMemory(dev, tex, tex_mem, 0)); VkImageViewCreateInfo tex_ivci = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = tex, .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, }, }; VkImageView tex_iv; VK_CHECK(vkCreateImageView(dev, &tex_ivci, NULL, &tex_iv)); /* ---- sampler -------------------------------------------------------- */ STEP("vkCreateSampler (NEAREST, CLAMP_TO_EDGE)"); VkSamplerCreateInfo sci = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_NEAREST, .minFilter = VK_FILTER_NEAREST, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .minLod = 0.0f, .maxLod = 0.0f, .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, .unnormalizedCoordinates = VK_FALSE, }; VkSampler samp; VK_CHECK(vkCreateSampler(dev, &sci, NULL, &samp)); /* ---- staging buffer for texture upload ----------------------------- */ uint32_t texel_data[TEX_PIXELS]; for (uint32_t y = 0; y < TEX_H; y++) for (uint32_t x = 0; x < TEX_W; x++) texel_data[y * TEX_W + x] = texel_le(x, y); VkBufferCreateInfo stage_bci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = TEX_BYTES, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; VkBuffer stage_buf; VK_CHECK(vkCreateBuffer(dev, &stage_bci, NULL, &stage_buf)); VkMemoryRequirements stage_mr; vkGetBufferMemoryRequirements(dev, stage_buf, &stage_mr); VkMemoryAllocateInfo stage_mai = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = stage_mr.size, .memoryTypeIndex = pick_host_visible(&mp, stage_mr.memoryTypeBits), }; VkDeviceMemory stage_mem; VK_CHECK(vkAllocateMemory(dev, &stage_mai, NULL, &stage_mem)); VK_CHECK(vkBindBufferMemory(dev, stage_buf, stage_mem, 0)); void *stage_mapped = NULL; VK_CHECK(vkMapMemory(dev, stage_mem, 0, VK_WHOLE_SIZE, 0, &stage_mapped)); memcpy(stage_mapped, texel_data, TEX_BYTES); vkUnmapMemory(dev, stage_mem); /* ---- color attachment image (64x64) -------------------------------- */ STEP("vkCreateImage color attachment (64x64 RGBA8 COLOR_ATTACHMENT|TRANSFER_SRC)"); VkImageCreateInfo att_ici = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = VK_FORMAT_R8G8B8A8_UNORM, .extent = { IMG_W, IMG_H, 1 }, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, }; VkImage att; VK_CHECK(vkCreateImage(dev, &att_ici, NULL, &att)); VkMemoryRequirements att_mr; vkGetImageMemoryRequirements(dev, att, &att_mr); VkMemoryAllocateInfo att_mai = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = att_mr.size, .memoryTypeIndex = pick_memtype(&mp, att_mr.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), }; VkDeviceMemory att_mem; VK_CHECK(vkAllocateMemory(dev, &att_mai, NULL, &att_mem)); VK_CHECK(vkBindImageMemory(dev, att, att_mem, 0)); VkImageViewCreateInfo att_ivci = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = att, .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, }, }; VkImageView att_iv; VK_CHECK(vkCreateImageView(dev, &att_ivci, NULL, &att_iv)); /* ---- readback buffer ------------------------------------------------ */ VkBufferCreateInfo rb_bci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = BUFFER_BYTES, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; VkBuffer rb; VK_CHECK(vkCreateBuffer(dev, &rb_bci, NULL, &rb)); VkMemoryRequirements rb_mr; vkGetBufferMemoryRequirements(dev, rb, &rb_mr); VkMemoryAllocateInfo rb_mai = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = rb_mr.size, .memoryTypeIndex = pick_host_visible(&mp, rb_mr.memoryTypeBits), }; VkDeviceMemory rb_mem; VK_CHECK(vkAllocateMemory(dev, &rb_mai, NULL, &rb_mem)); VK_CHECK(vkBindBufferMemory(dev, rb, rb_mem, 0)); void *rb_mapped = NULL; VK_CHECK(vkMapMemory(dev, rb_mem, 0, VK_WHOLE_SIZE, 0, &rb_mapped)); uint32_t *u32 = (uint32_t *)rb_mapped; for (uint32_t i = 0; i < PIXELS; i++) u32[i] = 0xDEADBEEFu; /* ---- descriptor set ------------------------------------------------- */ STEP("vkCreateDescriptorSetLayout (1 COMBINED_IMAGE_SAMPLER)"); VkDescriptorSetLayoutBinding dslb = { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }; VkDescriptorSetLayoutCreateInfo dslci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, .pBindings = &dslb, }; VkDescriptorSetLayout dsl; VK_CHECK(vkCreateDescriptorSetLayout(dev, &dslci, NULL, &dsl)); VkDescriptorPoolSize dps = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1 }; VkDescriptorPoolCreateInfo dpci = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets = 1, .poolSizeCount = 1, .pPoolSizes = &dps, }; VkDescriptorPool dpool; VK_CHECK(vkCreateDescriptorPool(dev, &dpci, NULL, &dpool)); VkDescriptorSetAllocateInfo dsai = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = dpool, .descriptorSetCount = 1, .pSetLayouts = &dsl, }; VkDescriptorSet dset; VK_CHECK(vkAllocateDescriptorSets(dev, &dsai, &dset)); /* descriptor update must be done after texture is in SHADER_READ layout, * but it's a CPU-side update — Vulkan allows it before image is in that * layout, as long as the image is in the correct layout at draw-submit time. */ VkDescriptorImageInfo dii = { .sampler = samp, .imageView = tex_iv, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; VkWriteDescriptorSet wds = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = dset, .dstBinding = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &dii, }; vkUpdateDescriptorSets(dev, 1, &wds, 0, NULL); /* ---- pipeline ------------------------------------------------------ */ STEP("vkCreatePipelineLayout + shaders"); VkPipelineLayoutCreateInfo plci = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &dsl, }; VkPipelineLayout pl; VK_CHECK(vkCreatePipelineLayout(dev, &plci, NULL, &pl)); VkShaderModule vsm = make_shader(dev, VSPV_PATH); VkShaderModule fsm = make_shader(dev, FSPV_PATH); VkPipelineShaderStageCreateInfo stages[2] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vsm, .pName = "main" }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = fsm, .pName = "main" }, }; VkPipelineVertexInputStateCreateInfo vi = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, }; VkPipelineInputAssemblyStateCreateInfo ia = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, }; VkViewport viewport = { 0, 0, IMG_W, IMG_H, 0.0f, 1.0f }; VkRect2D scissor = {{ 0, 0 }, { IMG_W, IMG_H }}; VkPipelineViewportStateCreateInfo vp = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor, }; VkPipelineRasterizationStateCreateInfo rs = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_NONE, .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .lineWidth = 1.0f, }; VkPipelineMultisampleStateCreateInfo ms = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, }; VkPipelineColorBlendAttachmentState cba = { .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, }; VkPipelineColorBlendStateCreateInfo cb_state = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .attachmentCount = 1, .pAttachments = &cba, }; VkFormat color_fmt = VK_FORMAT_R8G8B8A8_UNORM; VkPipelineRenderingCreateInfoKHR pri = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR, .colorAttachmentCount = 1, .pColorAttachmentFormats = &color_fmt, }; VkGraphicsPipelineCreateInfo gpci = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = &pri, .stageCount = 2, .pStages = stages, .pVertexInputState = &vi, .pInputAssemblyState = &ia, .pViewportState = &vp, .pRasterizationState = &rs, .pMultisampleState = &ms, .pColorBlendState = &cb_state, .layout = pl, }; STEP("vkCreateGraphicsPipelines"); VkPipeline pipe; VK_CHECK(vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &gpci, NULL, &pipe)); /* ---- cmd buffer ----------------------------------------------------- */ VkCommandPoolCreateInfo cpoolci = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = qfam, }; VkCommandPool cpool; VK_CHECK(vkCreateCommandPool(dev, &cpoolci, NULL, &cpool)); VkCommandBufferAllocateInfo cbai = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = cpool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; VkCommandBuffer cb; VK_CHECK(vkAllocateCommandBuffers(dev, &cbai, &cb)); STEP("record cmd buffer (tex upload + draw + readback)"); VkCommandBufferBeginInfo cbbi = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; VK_CHECK(vkBeginCommandBuffer(cb, &cbbi)); /* Source texture: UNDEFINED -> TRANSFER_DST */ image_barrier(cb, tex, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); /* Upload staging buffer -> source texture */ VkBufferImageCopy tex_copy = { .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1, }, .imageExtent = { TEX_W, TEX_H, 1 }, }; vkCmdCopyBufferToImage(cb, stage_buf, tex, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &tex_copy); /* Source texture: TRANSFER_DST -> SHADER_READ_ONLY */ image_barrier(cb, tex, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); /* Color attachment: UNDEFINED -> COLOR_ATTACHMENT */ image_barrier(cb, att, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); /* Render */ VkClearValue clear_black = {{{0.0f, 0.0f, 0.0f, 0.0f}}}; VkRenderingAttachmentInfoKHR color_attach = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, .imageView = att_iv, .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .clearValue = clear_black, }; VkRenderingInfoKHR ri = { .sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, .renderArea = {{ 0, 0 }, { IMG_W, IMG_H }}, .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &color_attach, }; pCmdBeginRendering(cb, &ri); vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, pl, 0, 1, &dset, 0, NULL); vkCmdDraw(cb, 3, 1, 0, 0); pCmdEndRendering(cb); /* Color attachment: COLOR_ATTACHMENT -> TRANSFER_SRC */ image_barrier(cb, att, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); /* Attachment -> readback buffer */ VkBufferImageCopy rb_copy = { .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1, }, .imageExtent = { IMG_W, IMG_H, 1 }, }; vkCmdCopyImageToBuffer(cb, att, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, rb, 1, &rb_copy); VkBufferMemoryBarrier bb = { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_HOST_READ_BIT, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .buffer = rb, .offset = 0, .size = VK_WHOLE_SIZE, }; vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, NULL, 1, &bb, 0, NULL); VK_CHECK(vkEndCommandBuffer(cb)); /* ---- submit ------------------------------------------------------- */ VkFenceCreateInfo fci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; VkFence fence; VK_CHECK(vkCreateFence(dev, &fci, NULL, &fence)); VkSubmitInfo si = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cb, }; STEP("submit + wait (10s)"); VK_CHECK(vkQueueSubmit(queue, 1, &si, fence)); VkResult wr = vkWaitForFences(dev, 1, &fence, VK_TRUE, 10ULL * 1000 * 1000 * 1000); if (wr == VK_TIMEOUT) { fprintf(stderr, "[fail] fence TIMEOUT\n"); return 7; } if (wr != VK_SUCCESS) { fprintf(stderr, "[fail] vkWaitForFences=>%d\n", wr); return 8; } /* ---- verify ------------------------------------------------------- */ STEP("invalidate + verify"); VkMappedMemoryRange mmr = { .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, .memory = rb_mem, .offset = 0, .size = VK_WHOLE_SIZE, }; vkInvalidateMappedMemoryRanges(dev, 1, &mmr); uint32_t mismatches = 0, sentinel = 0, black = 0; uint32_t first_diff_idx = UINT32_MAX; for (uint32_t row = 0; row < IMG_H; row++) { for (uint32_t col = 0; col < IMG_W; col++) { uint32_t idx = row * IMG_W + col; uint32_t got = u32[idx]; uint32_t want = texel_le(col % TEX_W, row % TEX_H); if (got != want) { if (first_diff_idx == UINT32_MAX) first_diff_idx = idx; if (got == 0xDEADBEEFu) sentinel++; else if (got == 0xff000000u || got == 0x00000000u) black++; mismatches++; } } } fprintf(stderr, "[info] mismatches=%u/%u sentinel=%u black=%u\n", mismatches, PIXELS, sentinel, black); if (mismatches) { uint32_t idx = first_diff_idx; uint32_t row = idx / IMG_W, col = idx % IMG_W; fprintf(stderr, "[diff] first mismatch (col=%u, row=%u): got=0x%08x want=0x%08x\n", col, row, u32[idx], texel_le(col % TEX_W, row % TEX_H)); /* Dump 4x4 top-left block — should be exact 4x4 source texture. */ fprintf(stderr, "[dump] top-left 4x4 block (expected = source texture):\n"); for (uint32_t r = 0; r < 4; r++) { fprintf(stderr, "[dump] "); for (uint32_t c = 0; c < 4; c++) { fprintf(stderr, "0x%08x ", u32[r * IMG_W + c]); } fprintf(stderr, " want: "); for (uint32_t c = 0; c < 4; c++) { fprintf(stderr, "0x%08x ", texel_le(c, r)); } fprintf(stderr, "\n"); } } /* ---- teardown ----------------------------------------------------- */ vkUnmapMemory(dev, rb_mem); vkDestroyFence(dev, fence, NULL); vkDestroyCommandPool(dev, cpool, NULL); vkDestroyPipeline(dev, pipe, NULL); vkDestroyShaderModule(dev, vsm, NULL); vkDestroyShaderModule(dev, fsm, NULL); vkDestroyPipelineLayout(dev, pl, NULL); vkDestroyDescriptorPool(dev, dpool, NULL); vkDestroyDescriptorSetLayout(dev, dsl, NULL); vkDestroyBuffer(dev, rb, NULL); vkFreeMemory(dev, rb_mem, NULL); vkDestroyImageView(dev, att_iv, NULL); vkDestroyImage(dev, att, NULL); vkFreeMemory(dev, att_mem, NULL); vkDestroyBuffer(dev, stage_buf, NULL); vkFreeMemory(dev, stage_mem, NULL); vkDestroySampler(dev, samp, NULL); vkDestroyImageView(dev, tex_iv, NULL); vkDestroyImage(dev, tex, NULL); vkFreeMemory(dev, tex_mem, NULL); vkDestroyDevice(dev, NULL); vkDestroyInstance(inst, NULL); free(phys); free(qfp); if (mismatches == 0) { fprintf(stderr, "[PASS] PanVk-Bifrost textured quad: all %u pixels match.\n", PIXELS); return 0; } else { fprintf(stderr, "[FAIL] %u / %u mismatched.\n", mismatches, PIXELS); return 1; } }