#include "vui_internal.h" namespace vui { uint32_t Renderer::Impl::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { VkPhysicalDeviceMemoryProperties memProps; vkGetPhysicalDeviceMemoryProperties(physDevice, &memProps); for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) { if ((typeFilter & (1 << i)) && (memProps.memoryTypes[i].propertyFlags & properties) == properties) return i; } return 0; } VkShaderModule Renderer::Impl::createShaderModule(const uint32_t *code, size_t sizeBytes) { VkShaderModuleCreateInfo ci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; ci.codeSize = sizeBytes; ci.pCode = code; VkShaderModule mod; vkCreateShaderModule(device, &ci, nullptr, &mod); return mod; } void Renderer::Impl::initRenderPass() { VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapchainFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorRef; VkSubpassDependency dep{}; dep.srcSubpass = VK_SUBPASS_EXTERNAL; dep.dstSubpass = 0; dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dep.srcAccessMask = 0; dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo rpci{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; rpci.attachmentCount = 1; rpci.pAttachments = &colorAttachment; rpci.subpassCount = 1; rpci.pSubpasses = &subpass; rpci.dependencyCount = 1; rpci.pDependencies = &dep; vkCreateRenderPass(device, &rpci, nullptr, &renderPass); } void Renderer::Impl::initPipeline() { VkDescriptorSetLayoutBinding binding{}; binding.binding = 0; binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; binding.descriptorCount = 1; binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutCreateInfo dslci{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; dslci.bindingCount = 1; dslci.pBindings = &binding; vkCreateDescriptorSetLayout(device, &dslci, nullptr, &descriptorSetLayout); VkPushConstantRange pushRange{}; pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; pushRange.offset = 0; pushRange.size = sizeof(float) * 16; VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; plci.setLayoutCount = 1; plci.pSetLayouts = &descriptorSetLayout; plci.pushConstantRangeCount = 1; plci.pPushConstantRanges = &pushRange; vkCreatePipelineLayout(device, &plci, nullptr, &pipelineLayout); VkShaderModule vertMod = createShaderModule(vertShaderSpv, sizeof(vertShaderSpv)); VkShaderModule fragMod = createShaderModule(fragShaderSpv, sizeof(fragShaderSpv)); VkPipelineShaderStageCreateInfo stages[2]{}; stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; stages[0].module = vertMod; 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 = fragMod; stages[1].pName = "main"; VkVertexInputBindingDescription bindingDesc{}; bindingDesc.binding = 0; bindingDesc.stride = sizeof(Vertex); bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; VkVertexInputAttributeDescription attrs[3]{}; attrs[0].location = 0; attrs[0].binding = 0; attrs[0].format = VK_FORMAT_R32G32_SFLOAT; attrs[0].offset = offsetof(Vertex, pos); attrs[1].location = 1; attrs[1].binding = 0; attrs[1].format = VK_FORMAT_R32G32_SFLOAT; attrs[1].offset = offsetof(Vertex, uv); attrs[2].location = 2; attrs[2].binding = 0; attrs[2].format = VK_FORMAT_R8G8B8A8_UNORM; attrs[2].offset = offsetof(Vertex, color); VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; vertexInput.vertexBindingDescriptionCount = 1; vertexInput.pVertexBindingDescriptions = &bindingDesc; vertexInput.vertexAttributeDescriptionCount = 3; vertexInput.pVertexAttributeDescriptions = attrs; VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkPipelineViewportStateCreateInfo vps{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; vps.viewportCount = 1; vps.scissorCount = 1; VkPipelineRasterizationStateCreateInfo raster{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; raster.polygonMode = VK_POLYGON_MODE_FILL; raster.cullMode = VK_CULL_MODE_NONE; raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; raster.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineColorBlendAttachmentState blendAttachment{}; blendAttachment.blendEnable = VK_TRUE; blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachment.colorBlendOp = VK_BLEND_OP_ADD; blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; blend.attachmentCount = 1; blend.pAttachments = &blendAttachment; VkDynamicState dynStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; VkPipelineDynamicStateCreateInfo dyn{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; dyn.dynamicStateCount = 2; dyn.pDynamicStates = dynStates; VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO}; VkGraphicsPipelineCreateInfo pci{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; pci.stageCount = 2; pci.pStages = stages; pci.pVertexInputState = &vertexInput; pci.pInputAssemblyState = &ia; pci.pViewportState = &vps; pci.pRasterizationState = &raster; pci.pMultisampleState = &ms; pci.pColorBlendState = &blend; pci.pDynamicState = &dyn; pci.pDepthStencilState = &depthStencil; pci.layout = pipelineLayout; pci.renderPass = renderPass; pci.subpass = 0; vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pci, nullptr, &pipeline); vkDestroyShaderModule(device, vertMod, nullptr); vkDestroyShaderModule(device, fragMod, nullptr); } void Renderer::Impl::initSampler() { VkSamplerCreateInfo sci{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; sci.magFilter = VK_FILTER_LINEAR; sci.minFilter = VK_FILTER_LINEAR; sci.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sci.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sci.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sci.maxAnisotropy = 1.0f; sci.maxLod = 1.0f; vkCreateSampler(device, &sci, nullptr, &sampler); } void Renderer::Impl::initDescriptorPool() { VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 256}; VkDescriptorPoolCreateInfo dpci{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; dpci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; dpci.maxSets = 256; dpci.poolSizeCount = 1; dpci.pPoolSizes = &poolSize; vkCreateDescriptorPool(device, &dpci, nullptr, &descriptorPool); } void Renderer::Impl::initVertexBuffer(uint32_t count) { if (vertexBuffer != VK_NULL_HANDLE) { vkDestroyBuffer(device, vertexBuffer, nullptr); vkFreeMemory(device, vertexMemory, nullptr); } vertexBufferCapacity = count; VkBufferCreateInfo bci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; bci.size = sizeof(Vertex) * count; bci.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; vkCreateBuffer(device, &bci, nullptr, &vertexBuffer); VkMemoryRequirements memReq; vkGetBufferMemoryRequirements(device, vertexBuffer, &memReq); VkMemoryAllocateInfo mai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; mai.allocationSize = memReq.size; mai.memoryTypeIndex = findMemoryType(memReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); vkAllocateMemory(device, &mai, nullptr, &vertexMemory); vkBindBufferMemory(device, vertexBuffer, vertexMemory, 0); } void Renderer::Impl::uploadTextureData(TextureEntry &tex, int x, int y, int w, int h, const void *rgba) { VkDeviceSize imageSize = (VkDeviceSize)w * h * 4; VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; VkBufferCreateInfo bci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; bci.size = imageSize; bci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; vkCreateBuffer(device, &bci, nullptr, &stagingBuffer); VkMemoryRequirements memReq; vkGetBufferMemoryRequirements(device, stagingBuffer, &memReq); VkMemoryAllocateInfo mai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; mai.allocationSize = memReq.size; mai.memoryTypeIndex = findMemoryType(memReq.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); vkAllocateMemory(device, &mai, nullptr, &stagingMemory); vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0); void *mapped; vkMapMemory(device, stagingMemory, 0, imageSize, 0, &mapped); memcpy(mapped, rgba, imageSize); vkUnmapMemory(device, stagingMemory); VkCommandPoolCreateInfo cpci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; cpci.queueFamilyIndex = queueFamily; cpci.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; VkCommandPool tmpPool; vkCreateCommandPool(device, &cpci, nullptr, &tmpPool); VkCommandBufferAllocateInfo abci{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; abci.commandPool = tmpPool; abci.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; abci.commandBufferCount = 1; VkCommandBuffer cmd; vkAllocateCommandBuffers(device, &abci, &cmd); VkCommandBufferBeginInfo bbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; bbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(cmd, &bbi); bool fullImage = (x == 0 && y == 0 && w == tex.width && h == tex.height); VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; barrier.image = tex.image; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.layerCount = 1; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.oldLayout = fullImage ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); VkBufferImageCopy region{}; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.layerCount = 1; region.imageOffset = {x, y, 0}; region.imageExtent = {(uint32_t)w, (uint32_t)h, 1}; vkCmdCopyBufferToImage(cmd, stagingBuffer, tex.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); vkEndCommandBuffer(cmd); VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO}; si.commandBufferCount = 1; si.pCommandBuffers = &cmd; vkQueueSubmit(graphicsQueue, 1, &si, VK_NULL_HANDLE); vkQueueWaitIdle(graphicsQueue); vkDestroyCommandPool(device, tmpPool, nullptr); vkDestroyBuffer(device, stagingBuffer, nullptr); vkFreeMemory(device, stagingMemory, nullptr); } int Renderer::Impl::allocTexture(int width, int height, const void *rgba) { TextureEntry tex{}; tex.width = width; tex.height = height; tex.active = true; VkImageCreateInfo ici{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; ici.imageType = VK_IMAGE_TYPE_2D; ici.format = VK_FORMAT_R8G8B8A8_UNORM; ici.extent = {(uint32_t)width, (uint32_t)height, 1}; ici.mipLevels = 1; ici.arrayLayers = 1; ici.samples = VK_SAMPLE_COUNT_1_BIT; ici.tiling = VK_IMAGE_TILING_OPTIMAL; ici.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; vkCreateImage(device, &ici, nullptr, &tex.image); VkMemoryRequirements memReq; vkGetImageMemoryRequirements(device, tex.image, &memReq); VkMemoryAllocateInfo mai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; mai.allocationSize = memReq.size; mai.memoryTypeIndex = findMemoryType(memReq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); vkAllocateMemory(device, &mai, nullptr, &tex.memory); vkBindImageMemory(device, tex.image, tex.memory, 0); VkImageViewCreateInfo vci{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; vci.image = tex.image; vci.viewType = VK_IMAGE_VIEW_TYPE_2D; vci.format = VK_FORMAT_R8G8B8A8_UNORM; vci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; vci.subresourceRange.levelCount = 1; vci.subresourceRange.layerCount = 1; vkCreateImageView(device, &vci, nullptr, &tex.view); VkDescriptorSetAllocateInfo dsai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; dsai.descriptorPool = descriptorPool; dsai.descriptorSetCount = 1; dsai.pSetLayouts = &descriptorSetLayout; vkAllocateDescriptorSets(device, &dsai, &tex.descriptorSet); VkDescriptorImageInfo dii{}; dii.sampler = sampler; dii.imageView = tex.view; dii.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkWriteDescriptorSet wds{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; wds.dstSet = tex.descriptorSet; wds.dstBinding = 0; wds.descriptorCount = 1; wds.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; wds.pImageInfo = &dii; vkUpdateDescriptorSets(device, 1, &wds, 0, nullptr); if (rgba) uploadTextureData(tex, 0, 0, width, height, rgba); int id = -1; for (int i = 0; i < (int)textures.size(); i++) { if (!textures[i].active) { id = i; break; } } if (id < 0) { id = (int)textures.size(); textures.push_back(tex); } else { textures[id] = tex; } return id; } void Renderer::Impl::createWhiteTexture() { uint32_t white = 0xFFFFFFFF; allocTexture(1, 1, &white); } void Renderer::Impl::addQuad(int texId, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, Color c) { Vertex verts[6]; float positions[6][2] = { {x0, y0}, {x1, y0}, {x1, y1}, {x0, y0}, {x1, y1}, {x0, y1} }; float uvs[6][2] = { {u0, v0}, {u1, v0}, {u1, v1}, {u0, v0}, {u1, v1}, {u0, v1} }; for (int i = 0; i < 6; i++) { float px = positions[i][0], py = positions[i][1]; currentTransform.transformPoint(px, py); verts[i].pos[0] = px; verts[i].pos[1] = py; verts[i].uv[0] = uvs[i][0]; verts[i].uv[1] = uvs[i][1]; verts[i].color[0] = c.r; verts[i].color[1] = c.g; verts[i].color[2] = c.b; verts[i].color[3] = c.a; } bool merged = false; if (!drawCmds.empty()) { auto &last = drawCmds.back(); bool sameScissor = (!last.hasScissor && scissorStack.empty()) || (last.hasScissor && !scissorStack.empty() && last.scissorX == scissorStack.back().x && last.scissorY == scissorStack.back().y && last.scissorW == scissorStack.back().w && last.scissorH == scissorStack.back().h); if (last.textureId == texId && sameScissor) { last.vertexCount += 6; merged = true; } } if (!merged) { DrawCmd dc; dc.textureId = texId; dc.vertexOffset = (uint32_t)vertices.size(); dc.vertexCount = 6; dc.hasScissor = !scissorStack.empty(); if (dc.hasScissor) { dc.scissorX = scissorStack.back().x; dc.scissorY = scissorStack.back().y; dc.scissorW = scissorStack.back().w; dc.scissorH = scissorStack.back().h; } drawCmds.push_back(dc); } for (int i = 0; i < 6; i++) vertices.push_back(verts[i]); } } // namespace vui