#pragma once #include "VKVertexProgram.h" #include "VKFragmentProgram.h" #include "VKRenderPass.h" #include "VKPipelineCompiler.h" #include "vkutils/framebuffer_object.hpp" #include "../Common/TextGlyphs.h" #include namespace vk { class text_writer { private: std::unique_ptr m_vertex_buffer; std::unique_ptr m_uniforms_buffer; std::unique_ptr m_program; vk::glsl::shader m_vertex_shader; vk::glsl::shader m_fragment_shader; vk::descriptor_pool m_descriptor_pool; vk::descriptor_set m_descriptor_set; VkDescriptorSetLayout m_descriptor_layout = nullptr; VkPipelineLayout m_pipeline_layout = nullptr; u32 m_used_descriptors = 0; VkRenderPass m_render_pass; VkDevice device = nullptr; u32 m_uniform_buffer_offset = 0; u32 m_uniform_buffer_size = 0; f32 m_scale = 1.0f; bool initialized = false; std::unordered_map> m_offsets; void init_descriptor_set(vk::render_device &dev) { VkDescriptorPoolSize descriptor_pools[1] = { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 120 }, }; // Reserve descriptor pools m_descriptor_pool.create(dev, descriptor_pools, 1, 120, 2); // Scale and offset data plus output color std::vector bindings = { { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .pImmutableSamplers = nullptr } }; m_descriptor_layout = vk::descriptors::create_layout(bindings); VkPipelineLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; layout_info.setLayoutCount = 1; layout_info.pSetLayouts = &m_descriptor_layout; CHECK_RESULT(vkCreatePipelineLayout(dev, &layout_info, nullptr, &m_pipeline_layout)); } void init_program() { std::string vs = { "#version 450\n" "#extension GL_ARB_separate_shader_objects : enable\n" "layout(location=0) in vec2 pos;\n" "layout(std140, set=0, binding=0) uniform scale_offset_buffer\n" "{\n" " vec4 offsets[510];\n" " vec4 scale;\n" " vec4 text_color;\n" "};\n" "\n" "layout(location=1) out vec4 draw_color;\n" "\n" "void main()\n" "{\n" " vec2 offset = offsets[gl_InstanceIndex].xy;\n" " gl_Position = vec4(pos, 0., 1.);\n" " gl_Position.xy = gl_Position.xy * scale.xy + offset;\n" " draw_color = text_color;\n" "}\n" }; std::string fs = { "#version 420\n" "#extension GL_ARB_separate_shader_objects : enable\n" "layout(location=1) in vec4 draw_color;\n" "layout(location=0) out vec4 col0;\n" "\n" "void main()\n" "{\n" " col0 = draw_color;\n" "}\n" }; m_vertex_shader.create(::glsl::program_domain::glsl_vertex_program, vs); m_vertex_shader.compile(); m_fragment_shader.create(::glsl::program_domain::glsl_fragment_program, fs); m_fragment_shader.compile(); VkPipelineShaderStageCreateInfo shader_stages[2] = {}; shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shader_stages[0].module = m_vertex_shader.get_handle(); shader_stages[0].pName = "main"; shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stages[1].module = m_fragment_shader.get_handle(); shader_stages[1].pName = "main"; std::vector dynamic_state_descriptors; VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; dynamic_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_VIEWPORT); dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_SCISSOR); dynamic_state_info.pDynamicStates = dynamic_state_descriptors.data(); dynamic_state_info.dynamicStateCount = ::size32(dynamic_state_descriptors); VkVertexInputAttributeDescription vdesc; VkVertexInputBindingDescription vbind; vdesc.binding = 0; vdesc.format = VK_FORMAT_R32G32_SFLOAT; vdesc.location = 0; vdesc.offset = 0; vbind.binding = 0; vbind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; vbind.stride = 8; VkPipelineVertexInputStateCreateInfo vi = {}; vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vi.vertexAttributeDescriptionCount = 1; vi.vertexBindingDescriptionCount = 1; vi.pVertexAttributeDescriptions = &vdesc; vi.pVertexBindingDescriptions = &vbind; VkPipelineViewportStateCreateInfo vp = {}; vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; vp.scissorCount = 1; vp.viewportCount = 1; VkPipelineMultisampleStateCreateInfo ms = {}; ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; ms.pSampleMask = NULL; ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineInputAssemblyStateCreateInfo ia = {}; ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; ia.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; VkPipelineRasterizationStateCreateInfo rs = {}; rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rs.lineWidth = 1.f; VkPipelineColorBlendAttachmentState att = {}; att.colorWriteMask = 0xf; VkPipelineColorBlendStateCreateInfo cs = {}; cs.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; cs.attachmentCount = 1; cs.pAttachments = &att; VkPipelineDepthStencilStateCreateInfo ds = {}; ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; VkGraphicsPipelineCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; info.pVertexInputState = &vi; info.pInputAssemblyState = &ia; info.pRasterizationState = &rs; info.pColorBlendState = &cs; info.pMultisampleState = &ms; info.pViewportState = &vp; info.pDepthStencilState = &ds; info.stageCount = 2; info.pStages = shader_stages; info.pDynamicState = &dynamic_state_info; info.layout = m_pipeline_layout; info.basePipelineIndex = -1; info.basePipelineHandle = VK_NULL_HANDLE; info.renderPass = m_render_pass; auto compiler = vk::get_pipe_compiler(); m_program = compiler->compile(info, m_pipeline_layout, vk::pipe_compiler::COMPILE_INLINE); } void load_program(vk::command_buffer &cmd, float scale_x, float scale_y, const float *offsets, usz nb_offsets, std::array color) { ensure(m_used_descriptors < 120); VkDescriptorSetAllocateInfo alloc_info = {}; alloc_info.descriptorPool = m_descriptor_pool; alloc_info.descriptorSetCount = 1; alloc_info.pSetLayouts = &m_descriptor_layout; alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; CHECK_RESULT(vkAllocateDescriptorSets(device, &alloc_info, m_descriptor_set.ptr())); m_used_descriptors++; float scale[] = { scale_x, scale_y }; float colors[] = { color[0], color[1], color[2], color[3] }; float* dst = static_cast(m_uniforms_buffer->map(m_uniform_buffer_offset, 8192)); //std140 spec demands that arrays be multiples of 16 bytes for (usz i = 0; i < nb_offsets; ++i) { dst[i * 4] = offsets[i * 2]; dst[i * 4 + 1] = offsets[i * 2 + 1]; } memcpy(&dst[510*4], scale, 8); memcpy(&dst[511*4], colors, 16); m_uniforms_buffer->unmap(); m_program->bind_uniform({ m_uniforms_buffer->value, m_uniform_buffer_offset, 8192 }, 0, m_descriptor_set); m_uniform_buffer_offset = (m_uniform_buffer_offset + 8192) % m_uniform_buffer_size; vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_program->pipeline); m_descriptor_set.bind(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout); VkDeviceSize zero = 0; vkCmdBindVertexBuffers(cmd, 0, 1, &m_vertex_buffer->value, &zero); } public: text_writer() = default; ~text_writer() { if (initialized) { m_vertex_shader.destroy(); m_fragment_shader.destroy(); vkDestroyDescriptorSetLayout(device, m_descriptor_layout, nullptr); vkDestroyPipelineLayout(device, m_pipeline_layout, nullptr); m_descriptor_pool.destroy(); } } void init(vk::render_device &dev, VkRenderPass render_pass) { ensure(render_pass != VK_NULL_HANDLE); //At worst case, 1 char = 16*16*8 bytes (average about 24*8), so ~256K for 128 chars. Allocating 512k for verts //uniform params are 8k in size, allocating for 120 lines (max lines at 4k, one column per row. Can be expanded m_vertex_buffer = std::make_unique(dev, 524288, dev.get_memory_mapping().host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 0, VMM_ALLOCATION_POOL_UNDEFINED); m_uniforms_buffer = std::make_unique(dev, 983040, dev.get_memory_mapping().host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 0, VMM_ALLOCATION_POOL_UNDEFINED); m_render_pass = render_pass; m_uniform_buffer_size = 983040; init_descriptor_set(dev); init_program(); GlyphManager glyph_source; auto points = glyph_source.generate_point_map(); const usz buffer_size = points.size() * sizeof(GlyphManager::glyph_point); u8* dst = static_cast(m_vertex_buffer->map(0, buffer_size)); memcpy(dst, points.data(), buffer_size); m_vertex_buffer->unmap(); m_offsets = glyph_source.get_glyph_offsets(); device = dev; initialized = true; } void print_text(vk::command_buffer &cmd, vk::framebuffer &target, int x, int y, int target_w, int target_h, const std::string &text, std::array color = { 0.3f, 1.f, 0.3f, 1.f }) { std::vector offsets; std::vector counts; std::vector shader_offsets; char *s = const_cast(text.c_str()); //Y is in raster coordinates: convert to bottom-left origin y = (static_cast(target_h / m_scale) - y - 16); //Compress [0, w] and [0, h] into range [-1, 1] //Flip Y scaling float scale_x = m_scale * +2.f / target_w; float scale_y = m_scale * -2.f / target_h; float base_offset = 0.f; shader_offsets.reserve(text.length() * 2); while (*s) { u8 offset = static_cast(*s); bool to_draw = false; //Can be false for space or unsupported characters auto o = m_offsets.find(offset); if (o != m_offsets.end()) { if (o->second.second > 0) { to_draw = true; offsets.push_back(o->second.first); counts.push_back(o->second.second); } } if (to_draw) { //Generate a scale_offset pair for this entry float offset_x = scale_x * (x + base_offset); offset_x -= 1.f; float offset_y = scale_y * y; offset_y += 1.f; shader_offsets.push_back(offset_x); shader_offsets.push_back(offset_y); } base_offset += 9.f; s++; } VkViewport vp{}; vp.width = static_cast(target_w); vp.height = static_cast(target_h); vp.minDepth = 0.f; vp.maxDepth = 1.f; vkCmdSetViewport(cmd, 0, 1, &vp); VkRect2D vs = { {0, 0}, {0u+target_w, 0u+target_h} }; vkCmdSetScissor(cmd, 0, 1, &vs); //TODO: Add drop shadow if deemed necessary for visibility load_program(cmd, scale_x, scale_y, shader_offsets.data(), counts.size(), color); const coordu viewport = { positionu{0u, 0u}, sizeu{target.width(), target.height() } }; vk::begin_renderpass(cmd, m_render_pass, target.value, viewport); for (uint i = 0; i < counts.size(); ++i) { vkCmdDraw(cmd, counts[i], 1, offsets[i], i); } } void reset_descriptors() { if (m_used_descriptors == 0) return; m_descriptor_pool.reset(0); m_used_descriptors = 0; } void set_scale(double scale) { // Restrict scale to 2. The dots are gonna be too sparse otherwise. m_scale = std::min(static_cast(scale), 2.0f); } }; }