diff --git a/include/font/outline.h b/include/font/outline.h index 7295189..683633e 100644 --- a/include/font/outline.h +++ b/include/font/outline.h @@ -28,9 +28,26 @@ namespace font::outline { AllocatedImage allocatedImage; }; + struct GlyphInstance { + uint16_t x; + uint16_t y; + uint32_t glyph; + uint32_t color; + }; + static_assert((sizeof (GlyphInstance)) == 4 * 3); + + struct Glyph { + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; + }; + struct font { static constexpr int perVertexSize = (4) * 2; - static constexpr int perInstanceSize = (0) * 2; + static constexpr int perInstanceSize = (sizeof (GlyphInstance)); + + static constexpr int maximumGlyphCount = 1024; VkInstance instance; VkDevice device; @@ -49,6 +66,16 @@ namespace font::outline { VertexIndex vertexIndex; LoadedFont loadedFont; + VkDeviceSize instanceBufferOffset[2]; + VkBuffer instanceBuffer; + VkDeviceMemory instanceMemory; + VkDeviceSize instanceMemorySize; + GlyphInstance * instanceMappedData; + + VkBuffer glyphsBuffer; + VkDeviceMemory glyphsMemory; + VkDeviceSize glyphsBufferSize; + VkDescriptorPool descriptorPool{ VK_NULL_HANDLE }; static constexpr int descriptorSetLayoutCount = 1; VkDescriptorSetLayout descriptorSetLayouts[descriptorSetLayoutCount]; // unrelated to maxFrames, unrelated to descriptorCount @@ -70,7 +97,9 @@ namespace font::outline { void load_shader(); void create_descriptor_sets(); void write_descriptor_sets(VkImageView fontImageView); + void create_instance_buffers(); void create_pipeline(); + void create_glyphs_buffer(types::font const * const font, types::glyph const * const glyphs); void draw(VkCommandBuffer commandBuffer, uint32_t frameIndex); diff --git a/shader/font.hlsl b/shader/font.hlsl index 035e729..0a56838 100644 --- a/shader/font.hlsl +++ b/shader/font.hlsl @@ -1,11 +1,22 @@ -// set 1: constant +struct GlyphBitmap +{ + uint2 Position; // x y, in texels + uint2 Size; // width height +}; + +// set 0: constant [[vk::binding(0, 0)]] SamplerState ClosestSampler; [[vk::binding(1, 0)]] Texture2D FontTexture; +[[vk::binding(2, 0)]] StructuredBuffer Glyphs; struct VSInput { float2 Position : POSITION0; float2 Texture : TEXCOORD0; + // per-instance + uint2 InstancePosition : InstancePosition; + uint InstanceGlyph : InstanceGlyph; + float4 InstanceColor : InstanceColor; }; struct VSOutput @@ -17,9 +28,14 @@ struct VSOutput [shader("vertex")] VSOutput VSMain(VSInput input) { + float2 inverseTexel = float2(1.0 / 256.0, 1.0 / 256.0); + float2 inversePixel = float2(1.0 / 1024.0, 1.0 / 1024.0); + int index = input.InstanceGlyph; + VSOutput output = (VSOutput)0; - output.Position = float4(input.Position, 0, 1); - output.Texture = input.Texture; + float2 position = (input.Texture * Glyphs[index].Size + input.InstancePosition) * inversePixel; + output.Position = float4(position * 2.0 - 1.0, 0, 1); + output.Texture = (input.Texture * Glyphs[index].Size + Glyphs[index].Position) * inverseTexel; return output; } diff --git a/src/font/outline.cpp b/src/font/outline.cpp index 27defea..8b5d0ae 100644 --- a/src/font/outline.cpp +++ b/src/font/outline.cpp @@ -58,7 +58,9 @@ namespace font::outline { load_shader(); create_descriptor_sets(); loadedFont = load_font(uncial_antiqua[0]); + create_glyphs_buffer(loadedFont.font, loadedFont.glyphs); write_descriptor_sets(loadedFont.allocatedImage.imageView); + create_instance_buffers(); create_pipeline(); } @@ -201,7 +203,8 @@ namespace font::outline { .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT }; - VkVertexInputBindingDescription vertexBindingDescriptions[2]{ + constexpr int vertexBindingDescriptionsCount = 2; + VkVertexInputBindingDescription vertexBindingDescriptions[vertexBindingDescriptionsCount]{ { .binding = 0, .stride = perVertexSize, @@ -214,7 +217,8 @@ namespace font::outline { } }; - VkVertexInputAttributeDescription vertexAttributeDescriptions[2]{ + constexpr int vertexAttributeDescriptionsCount = 5; + VkVertexInputAttributeDescription vertexAttributeDescriptions[vertexAttributeDescriptionsCount]{ // per-vertex { // position .location = 0, @@ -228,14 +232,32 @@ namespace font::outline { .format = VK_FORMAT_R16G16_SFLOAT, .offset = 4, }, + // per-instance + { + .location = 2, + .binding = 1, + .format = VK_FORMAT_R16G16_UINT, + .offset = 0, + }, + { + .location = 3, + .binding = 1, + .format = VK_FORMAT_R32_UINT, + .offset = 4, + }, + { + .location = 4, + .binding = 1, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .offset = 8, + }, }; VkPipelineVertexInputStateCreateInfo vertexInputState{ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, - //.vertexBindingDescriptionCount = 2, - .vertexBindingDescriptionCount = 1, + .vertexBindingDescriptionCount = vertexBindingDescriptionsCount, .pVertexBindingDescriptions = vertexBindingDescriptions, - .vertexAttributeDescriptionCount = 2, + .vertexAttributeDescriptionCount = vertexAttributeDescriptionsCount, .pVertexAttributeDescriptions = vertexAttributeDescriptions, }; @@ -358,7 +380,7 @@ namespace font::outline { // // pool // - constexpr int descriptorPoolSizesCount = 2; + constexpr int descriptorPoolSizesCount = 3; VkDescriptorPoolSize descriptorPoolSizes[descriptorPoolSizesCount]{ { // linear sampler .type = VK_DESCRIPTOR_TYPE_SAMPLER, @@ -367,7 +389,11 @@ namespace font::outline { { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 1, - } + }, + { + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + }, }; VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, @@ -381,7 +407,7 @@ namespace font::outline { // (set 0, constant) // { - constexpr int bindingCount = 2; + constexpr int bindingCount = 3; VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[bindingCount]{ { .binding = 0, @@ -394,6 +420,12 @@ namespace font::outline { .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT + }, + { + .binding = 2, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT } }; @@ -420,7 +452,7 @@ namespace font::outline { void font::write_descriptor_sets(VkImageView fontImageView) { - constexpr uint32_t writeCount = 2; + constexpr uint32_t writeCount = 3; VkWriteDescriptorSet writeDescriptorSets[writeCount]; uint32_t writeIndex = 0; @@ -448,11 +480,129 @@ namespace font::outline { .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .pImageInfo = &terrainDescriptorImageInfo }; - + VkDescriptorBufferInfo glyphsDescriptorBufferInfo{ + .buffer = glyphsBuffer, + .offset = 0, + .range = glyphsBufferSize, + }; + writeDescriptorSets[writeIndex++] = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSet0, + .dstBinding = 2, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .pBufferInfo = &glyphsDescriptorBufferInfo + }; assert(writeIndex == writeCount); vkUpdateDescriptorSets(device, writeIndex, writeDescriptorSets, 0, nullptr); } + ////////////////////////////////////////////////////////////////////// + // create instance buffer + ////////////////////////////////////////////////////////////////////// + + void font::create_instance_buffers() + { + constexpr VkDeviceSize bufferSize{ maximumGlyphCount * (sizeof (GlyphInstance)) }; + instanceMemorySize = bufferSize * 2; + instanceBufferOffset[0] = bufferSize * 0; + instanceBufferOffset[1] = bufferSize * 1; + + // create buffer + VkBufferCreateInfo bufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = instanceMemorySize, + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VK_CHECK(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &instanceBuffer)); + + // allocate memory + + VkMemoryRequirements memoryRequirements; + vkGetBufferMemoryRequirements(device, instanceBuffer, &memoryRequirements); + VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT }; + VkMemoryAllocateFlags memoryAllocateFlags{}; + VkDeviceSize stride; + allocateFromMemoryRequirements(device, + physicalDeviceProperties.limits.nonCoherentAtomSize, + physicalDeviceMemoryProperties, + memoryRequirements, + memoryPropertyFlags, + memoryAllocateFlags, + 1, + &instanceMemory, + &stride); + + VK_CHECK(vkBindBufferMemory(device, instanceBuffer, instanceMemory, 0)); + + // map memory + + VK_CHECK(vkMapMemory(device, instanceMemory, 0, VK_WHOLE_SIZE, 0, (void **)&instanceMappedData)); + } + + ////////////////////////////////////////////////////////////////////// + // create instance buffer + ////////////////////////////////////////////////////////////////////// + + void font::create_glyphs_buffer(types::font const * const font, types::glyph const * const glyphs) + { + glyphsBufferSize = (sizeof (Glyph)) * font->glyph_count; + + // create buffer + VkBufferCreateInfo bufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = glyphsBufferSize, + .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VK_CHECK(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &glyphsBuffer)); + + // allocate memory + + VkMemoryRequirements memoryRequirements; + vkGetBufferMemoryRequirements(device, glyphsBuffer, &memoryRequirements); + VkMemoryPropertyFlags memoryPropertyFlags{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT }; + VkMemoryAllocateFlags memoryAllocateFlags{}; + VkDeviceSize stride; + allocateFromMemoryRequirements(device, + physicalDeviceProperties.limits.nonCoherentAtomSize, + physicalDeviceMemoryProperties, + memoryRequirements, + memoryPropertyFlags, + memoryAllocateFlags, + 1, + &glyphsMemory, + &stride); + + VK_CHECK(vkBindBufferMemory(device, glyphsBuffer, glyphsMemory, 0)); + + // map memory + Glyph * glyphsMappedData; + VK_CHECK(vkMapMemory(device, glyphsMemory, 0, VK_WHOLE_SIZE, 0, (void **)&glyphsMappedData)); + + for (int i = 0; i < font->glyph_count; i++) { + glyphsMappedData[i].x = glyphs[i].bitmap.x; + glyphsMappedData[i].y = glyphs[i].bitmap.y; + glyphsMappedData[i].width = glyphs[i].bitmap.width; + glyphsMappedData[i].height = glyphs[i].bitmap.height; + } + + // flush + constexpr int mappedMemoryRangesCount = 1; + VkMappedMemoryRange mappedMemoryRanges[mappedMemoryRangesCount]{ + { + .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, + .memory = glyphsMemory, + .offset = 0, + .size = VK_WHOLE_SIZE, + } + }; + vkFlushMappedMemoryRanges(device, mappedMemoryRangesCount, mappedMemoryRanges); + + vkUnmapMemory(device, glyphsMemory); + } + ////////////////////////////////////////////////////////////////////// // draw ////////////////////////////////////////////////////////////////////// @@ -460,6 +610,52 @@ namespace font::outline { void font::draw(VkCommandBuffer commandBuffer, uint32_t frameIndex) { + // transfer + const char * string = "so when Nico wants to run this game on his\n4K monitor, he gets a dinky little 1280x720\nwindow instead?"; + int outputIndex = 0; + int stringIndex = 0; + + uint32_t x = 64 << 6; + uint32_t y = 64 << 6; + while (true) { + char c = string[stringIndex++]; + if (c == 0) + break; + + if (c != ' ') { + instanceMappedData[maximumGlyphCount * frameIndex + outputIndex++] = { + (uint16_t)((x + loadedFont.glyphs[c - 32].metrics.horiBearingX) >> 6), + (uint16_t)((y - loadedFont.glyphs[c - 32].metrics.horiBearingY) >> 6), + (uint32_t)(c - 32), + 0xaabbccdd, + }; + } + + if (c == '\n') { + y += loadedFont.font->face_metrics.height * 1.2; + x = 64 << 6; + } else { + x += loadedFont.glyphs[c - 32].metrics.horiAdvance; + } + }; + // flush + constexpr int mappedMemoryRangesCount = 1; + VkMappedMemoryRange mappedMemoryRanges[mappedMemoryRangesCount]{ + { + .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, + .memory = instanceMemory, + .offset = 0, + .size = (sizeof (GlyphInstance)), + } + }; + alignMappedMemoryRanges(physicalDeviceProperties.limits.nonCoherentAtomSize, + instanceMemorySize, + mappedMemoryRangesCount, + mappedMemoryRanges); + vkFlushMappedMemoryRanges(device, mappedMemoryRangesCount, mappedMemoryRanges); + + // bind/draw + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); VkDescriptorSet descriptorSets[1] = { @@ -473,9 +669,10 @@ namespace font::outline { vkCmdBindIndexBuffer(commandBuffer, vertexIndex.buffer, vertexIndex.indexOffset, VK_INDEX_TYPE_UINT16); - VkDeviceSize vertexOffset{ 0 }; - vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexIndex.buffer, &vertexOffset); + VkDeviceSize vertexOffsets[2]{ 0, instanceBufferOffset[frameIndex] }; + VkBuffer vertexBuffers[2]{ vertexIndex.buffer, instanceBuffer }; + vkCmdBindVertexBuffers(commandBuffer, 0, 2, vertexBuffers, vertexOffsets); - vkCmdDrawIndexed(commandBuffer, 4, 1, 0, 0, 0); + vkCmdDrawIndexed(commandBuffer, 4, outputIndex, 0, 0, 0); } } diff --git a/src/main.cpp b/src/main.cpp index d4e59af..c7bc7fc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1001,7 +1001,8 @@ int main() .imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .clearValue{ .color{ 0.0f, 0.0f, 0.2f, 1.0f } } + //.clearValue{ .color{ 0.0f, 0.0f, 0.2f, 1.0f } } + .clearValue{ .color{ 0.0f, 0.0f, 0.0f, 0.0f } } }; VkRenderingAttachmentInfo depthRenderingAttachmentInfo{ .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,