font: glyph per-instance data, draw multiple individual glyphs
This commit is contained in:
parent
4042bbd623
commit
bb6f76cf72
@ -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);
|
||||
|
||||
|
||||
@ -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<GlyphBitmap> 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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user