840 lines
28 KiB
C++
840 lines
28 KiB
C++
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
#ifdef __APPLE__
|
|
#include "vulkan/vulkan.h"
|
|
#else
|
|
#include "volk/volk.h"
|
|
#endif
|
|
|
|
#include "vulkan/vk_enum_string_helper.h"
|
|
|
|
#include "directxmath/directxmath.h"
|
|
#include "vulkan_helper.h"
|
|
#include "check.h"
|
|
#include "file.h"
|
|
|
|
#include "font/outline.h"
|
|
#include "font/outline_types.h"
|
|
#include "renpy/script.h"
|
|
|
|
#include "audio.h"
|
|
#include "poem.h"
|
|
|
|
namespace font::outline {
|
|
static const _Float16 vertexData[] = {
|
|
// x y u v
|
|
(_Float16)-1.0, (_Float16)-1.0, (_Float16)0.0, (_Float16)0.0,
|
|
(_Float16)1.0, (_Float16)-1.0, (_Float16)1.0, (_Float16)0.0,
|
|
(_Float16)-1.0, (_Float16)1.0, (_Float16)0.0, (_Float16)1.0,
|
|
(_Float16)1.0, (_Float16)1.0, (_Float16)1.0, (_Float16)1.0,
|
|
};
|
|
static const uint32_t vertexSize = (sizeof (vertexData));
|
|
|
|
static const uint16_t indexData[] = {
|
|
0, 1, 2, 3,
|
|
};
|
|
static const uint32_t indexSize = (sizeof (indexData));
|
|
|
|
void font::initial_state(VkInstance instance,
|
|
VkDevice device,
|
|
VkQueue queue,
|
|
VkCommandPool commandPool,
|
|
VkPhysicalDeviceProperties physicalDeviceProperties,
|
|
VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties,
|
|
VkFormat colorFormat,
|
|
VkFormat depthFormat,
|
|
VkSampler linearSampler)
|
|
{
|
|
this->instance = instance;
|
|
this->device = device;
|
|
this->queue = queue;
|
|
this->commandPool = commandPool;
|
|
|
|
this->physicalDeviceProperties = physicalDeviceProperties;
|
|
this->physicalDeviceMemoryProperties = physicalDeviceMemoryProperties;
|
|
|
|
this->colorFormat = colorFormat;
|
|
this->depthFormat = depthFormat;
|
|
|
|
this->linearSampler = linearSampler;
|
|
}
|
|
|
|
void font::init()
|
|
{
|
|
load_vertex_index_buffer();
|
|
load_shader();
|
|
create_descriptor_sets();
|
|
loadedFont = load_font(medieval_sharp[0]);
|
|
create_glyphs_buffer(loadedFont.font, loadedFont.glyphs);
|
|
write_descriptor_sets(loadedFont.allocatedImage.imageView);
|
|
create_instance_buffers();
|
|
create_pipeline();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// vertex index buffer
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void font::load_vertex_index_buffer()
|
|
{
|
|
void const * vertexStart = (void const *)vertexData;
|
|
void const * indexStart = (void const *)indexData;
|
|
|
|
vertexIndex = createVertexIndexBuffer(device,
|
|
physicalDeviceProperties,
|
|
physicalDeviceMemoryProperties,
|
|
vertexStart, vertexSize,
|
|
indexStart, indexSize);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// shader
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void font::load_shader()
|
|
{
|
|
uint32_t shaderSize;
|
|
void const * shaderStart = file::open("shader/font.spv", &shaderSize);
|
|
|
|
VkShaderModuleCreateInfo shaderModuleCreateInfo{
|
|
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
.codeSize = shaderSize,
|
|
.pCode = (uint32_t *)shaderStart
|
|
};
|
|
VK_CHECK(vkCreateShaderModule(device, &shaderModuleCreateInfo, nullptr, &shaderModule));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// pipeline
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void font::create_pipeline()
|
|
{
|
|
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
|
.setLayoutCount = descriptorSetLayoutCount,
|
|
.pSetLayouts = descriptorSetLayouts,
|
|
.pushConstantRangeCount = 0,
|
|
.pPushConstantRanges = nullptr
|
|
};
|
|
VK_CHECK(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
|
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP
|
|
};
|
|
|
|
VkPipelineShaderStageCreateInfo shaderStages[2]{
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
|
.module = shaderModule,
|
|
.pName = "VSMain"
|
|
},
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
.module = shaderModule,
|
|
.pName = "PSMain"
|
|
}
|
|
};
|
|
|
|
VkPipelineViewportStateCreateInfo viewportState{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
|
.viewportCount = 1,
|
|
.scissorCount = 1
|
|
};
|
|
|
|
constexpr uint32_t dynamicStateCount = 2;
|
|
VkDynamicState dynamicStates[dynamicStateCount]{
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR,
|
|
};
|
|
VkPipelineDynamicStateCreateInfo dynamicState{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
|
.dynamicStateCount = dynamicStateCount,
|
|
.pDynamicStates = dynamicStates
|
|
};
|
|
|
|
VkPipelineDepthStencilStateCreateInfo depthStencilState{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
|
.depthTestEnable = VK_FALSE,
|
|
.depthWriteEnable = VK_FALSE,
|
|
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
|
|
.stencilTestEnable = VK_FALSE,
|
|
.front = {
|
|
.failOp = VK_STENCIL_OP_REPLACE,
|
|
.passOp = VK_STENCIL_OP_REPLACE,
|
|
.depthFailOp = VK_STENCIL_OP_REPLACE,
|
|
.compareOp = VK_COMPARE_OP_ALWAYS,
|
|
.compareMask = 0x01,
|
|
.writeMask = 0x01,
|
|
.reference = 1,
|
|
},
|
|
.back = {
|
|
.failOp = VK_STENCIL_OP_REPLACE,
|
|
.passOp = VK_STENCIL_OP_REPLACE,
|
|
.depthFailOp = VK_STENCIL_OP_REPLACE,
|
|
.compareOp = VK_COMPARE_OP_ALWAYS,
|
|
.compareMask = 0x01,
|
|
.writeMask = 0x01,
|
|
.reference = 1,
|
|
},
|
|
};
|
|
|
|
VkPipelineRenderingCreateInfo renderingCreateInfo{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
|
|
.colorAttachmentCount = 1,
|
|
.pColorAttachmentFormats = &colorFormat,
|
|
.depthAttachmentFormat = depthFormat,
|
|
.stencilAttachmentFormat = depthFormat
|
|
};
|
|
|
|
VkPipelineColorBlendAttachmentState blendAttachment{
|
|
.blendEnable = VK_TRUE,
|
|
.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
|
|
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
|
.colorBlendOp = VK_BLEND_OP_ADD,
|
|
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
|
|
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
|
|
.alphaBlendOp = VK_BLEND_OP_ADD,
|
|
.colorWriteMask = 0xF,
|
|
};
|
|
VkPipelineColorBlendStateCreateInfo colorBlendState{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
|
.attachmentCount = 1,
|
|
.pAttachments = &blendAttachment
|
|
};
|
|
VkPipelineRasterizationStateCreateInfo rasterizationState{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
|
//.cullMode = VK_CULL_MODE_BACK_BIT,
|
|
.cullMode = VK_CULL_MODE_NONE,
|
|
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
|
.lineWidth = 1.0f
|
|
};
|
|
VkPipelineMultisampleStateCreateInfo multisampleState{
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
|
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
|
|
};
|
|
|
|
constexpr int vertexBindingDescriptionsCount = 2;
|
|
VkVertexInputBindingDescription vertexBindingDescriptions[vertexBindingDescriptionsCount]{
|
|
{
|
|
.binding = 0,
|
|
.stride = perVertexSize,
|
|
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX
|
|
},
|
|
{
|
|
.binding = 1,
|
|
.stride = perInstanceSize,
|
|
.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE
|
|
}
|
|
};
|
|
|
|
constexpr int vertexAttributeDescriptionsCount = 5;
|
|
VkVertexInputAttributeDescription vertexAttributeDescriptions[vertexAttributeDescriptionsCount]{
|
|
// per-vertex
|
|
{ // position
|
|
.location = 0,
|
|
.binding = 0,
|
|
.format = VK_FORMAT_R16G16_SFLOAT,
|
|
.offset = 0,
|
|
},
|
|
{ // texture
|
|
.location = 1,
|
|
.binding = 0,
|
|
.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 = vertexBindingDescriptionsCount,
|
|
.pVertexBindingDescriptions = vertexBindingDescriptions,
|
|
.vertexAttributeDescriptionCount = vertexAttributeDescriptionsCount,
|
|
.pVertexAttributeDescriptions = vertexAttributeDescriptions,
|
|
};
|
|
|
|
VkGraphicsPipelineCreateInfo pipelineCreateInfos[1]{
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
|
.pNext = &renderingCreateInfo,
|
|
.stageCount = 2,
|
|
.pStages = shaderStages,
|
|
.pVertexInputState = &vertexInputState,
|
|
.pInputAssemblyState = &inputAssemblyState,
|
|
.pViewportState = &viewportState,
|
|
.pRasterizationState = &rasterizationState,
|
|
.pMultisampleState = &multisampleState,
|
|
.pDepthStencilState = &depthStencilState,
|
|
.pColorBlendState = &colorBlendState,
|
|
.pDynamicState = &dynamicState,
|
|
.layout = pipelineLayout
|
|
},
|
|
};
|
|
|
|
VK_CHECK(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, pipelineCreateInfos, nullptr, &pipeline));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// load font
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
LoadedFont font::load_font(font_desc const& desc)
|
|
{
|
|
uint32_t font_data_size;
|
|
void const * font_data = file::open(desc.path, &font_data_size);
|
|
assert(font_data != nullptr);
|
|
|
|
types::font * font = (types::font *)font_data;
|
|
types::glyph * glyphs = (types::glyph *)(((ptrdiff_t)font_data) + (sizeof (types::font)));
|
|
|
|
void * texture_data = (void *)(((ptrdiff_t)glyphs) + (sizeof (types::glyph)) * font->glyph_count);
|
|
|
|
ptrdiff_t font_end = ((ptrdiff_t)font_data) + font_data_size;
|
|
int texture_size = font->texture_width * font->texture_height;
|
|
assert(font_end - ((ptrdiff_t)texture_data) == texture_size);
|
|
|
|
// transfer texture
|
|
|
|
VkCommandBuffer commandBuffer{};
|
|
VkCommandBufferAllocateInfo commandBufferAllocateInfo{
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
|
.commandPool = commandPool,
|
|
.commandBufferCount = 1
|
|
};
|
|
VK_CHECK(vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer));
|
|
|
|
VkFenceCreateInfo fenceCreateInfo{
|
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
|
|
};
|
|
VkFence fence{};
|
|
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
|
|
|
|
void * imageData = texture_data;
|
|
uint32_t imageDataSize = texture_size;
|
|
VkFormat format = VK_FORMAT_R8_UNORM;
|
|
uint32_t width = font->texture_width;
|
|
uint32_t height = font->texture_height;
|
|
uint32_t levelCount = 1;
|
|
uint32_t levelOffset = 0;
|
|
VkImage outImage;
|
|
VkDeviceMemory outMemory;
|
|
VkImageView outImageView;
|
|
|
|
createImage(device,
|
|
physicalDeviceProperties.limits.nonCoherentAtomSize,
|
|
physicalDeviceMemoryProperties,
|
|
format,
|
|
width,
|
|
height,
|
|
levelCount,
|
|
&outImage,
|
|
&outMemory,
|
|
&outImageView);
|
|
|
|
textureTransfer(device,
|
|
queue,
|
|
commandBuffer,
|
|
fence,
|
|
physicalDeviceProperties.limits.nonCoherentAtomSize,
|
|
physicalDeviceMemoryProperties,
|
|
imageDataSize,
|
|
imageData,
|
|
outImage,
|
|
width,
|
|
height,
|
|
levelCount,
|
|
&levelOffset);
|
|
|
|
vkDestroyFence(device, fence, nullptr);
|
|
vkFreeCommandBuffers(device,
|
|
commandPool,
|
|
1,
|
|
&commandBuffer);
|
|
|
|
// return
|
|
return {
|
|
.font = font,
|
|
.glyphs = glyphs,
|
|
.allocatedImage = {
|
|
.image = outImage,
|
|
.memory = outMemory,
|
|
.imageView = outImageView,
|
|
},
|
|
};
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// descriptor sets
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void font::create_descriptor_sets()
|
|
{
|
|
//
|
|
// pool
|
|
//
|
|
constexpr int descriptorPoolSizesCount = 3;
|
|
VkDescriptorPoolSize descriptorPoolSizes[descriptorPoolSizesCount]{
|
|
{ // linear sampler
|
|
.type = VK_DESCRIPTOR_TYPE_SAMPLER,
|
|
.descriptorCount = 1,
|
|
},
|
|
{
|
|
.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,
|
|
.maxSets = 1,
|
|
.poolSizeCount = descriptorPoolSizesCount,
|
|
.pPoolSizes = descriptorPoolSizes
|
|
};
|
|
VK_CHECK(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool));
|
|
|
|
//
|
|
// (set 0, constant)
|
|
//
|
|
{
|
|
constexpr int bindingCount = 3;
|
|
VkDescriptorSetLayoutBinding descriptorSetLayoutBindings[bindingCount]{
|
|
{
|
|
.binding = 0,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
|
|
.descriptorCount = 1,
|
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
|
|
},
|
|
{ // font image
|
|
.binding = 1,
|
|
.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
|
|
}
|
|
};
|
|
|
|
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{
|
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
|
.bindingCount = bindingCount,
|
|
.pBindings = descriptorSetLayoutBindings
|
|
};
|
|
VK_CHECK(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts[0]));
|
|
|
|
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{
|
|
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
|
.descriptorPool = descriptorPool,
|
|
.descriptorSetCount = 1,
|
|
.pSetLayouts = &descriptorSetLayouts[0]
|
|
};
|
|
VK_CHECK(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet0));
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// descriptor set writes
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void font::write_descriptor_sets(VkImageView fontImageView)
|
|
{
|
|
constexpr uint32_t writeCount = 3;
|
|
VkWriteDescriptorSet writeDescriptorSets[writeCount];
|
|
uint32_t writeIndex = 0;
|
|
|
|
// set1 bindings
|
|
VkDescriptorImageInfo samplerDescriptorImageInfo = {
|
|
.sampler = linearSampler,
|
|
};
|
|
writeDescriptorSets[writeIndex++] = {
|
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
.dstSet = descriptorSet0,
|
|
.dstBinding = 0,
|
|
.descriptorCount = 1,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
|
|
.pImageInfo = &samplerDescriptorImageInfo
|
|
};
|
|
VkDescriptorImageInfo terrainDescriptorImageInfo = {
|
|
.imageView = fontImageView,
|
|
.imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL
|
|
};
|
|
writeDescriptorSets[writeIndex++] = {
|
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
.dstSet = descriptorSet0,
|
|
.dstBinding = 1,
|
|
.descriptorCount = 1,
|
|
.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_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 glyphs 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);
|
|
}
|
|
|
|
void font::emit_line(int frameIndex,
|
|
char const * const string,
|
|
uint32_t x, uint32_t y,
|
|
int& outputIndex,
|
|
int startIndex,
|
|
int endIndex,
|
|
uint32_t color) const
|
|
{
|
|
for (int i = startIndex; i < endIndex; i++) {
|
|
bool highlight = string[i] < 0;
|
|
char c = abs(string[i]);
|
|
types::glyph const& glyph = loadedFont.glyphs[c - 32];
|
|
|
|
if (c != ' ' && c != '\n') {
|
|
instanceMappedData[maximumGlyphCount * frameIndex + outputIndex++] = {
|
|
(uint16_t)((x + glyph.metrics.horiBearingX) >> 6),
|
|
(uint16_t)((y - glyph.metrics.horiBearingY) >> 6),
|
|
(uint32_t)(c - 32),
|
|
highlight ? 0xa81524: color,
|
|
};
|
|
}
|
|
|
|
x += glyph.metrics.horiAdvance;
|
|
}
|
|
}
|
|
|
|
void font::centered(int frameIndex, char const * const string, uint32_t& x, uint32_t& y,
|
|
uint32_t minX,
|
|
uint32_t maxWidth,
|
|
int& outputIndex,
|
|
uint32_t color) const
|
|
{
|
|
int stringIndex = 0;
|
|
int lineStart = stringIndex;
|
|
int lineWidth = 0;
|
|
uint32_t leftOffset = 0;
|
|
|
|
while (true) {
|
|
char c = abs(string[stringIndex++]);
|
|
if (c == 0)
|
|
break;
|
|
|
|
if (c == '\n') {
|
|
continue;
|
|
}
|
|
|
|
types::glyph const& glyph = loadedFont.glyphs[c - 32];
|
|
|
|
if (lineWidth == 0) {
|
|
leftOffset = glyph.metrics.horiBearingX;
|
|
}
|
|
|
|
lineWidth += glyph.metrics.horiAdvance;
|
|
|
|
if ((lineWidth - leftOffset) + loadedFont.font->face_metrics.max_advance > (maxWidth << 6)) {
|
|
while (abs(string[stringIndex]) != ' ') {
|
|
char c = abs(string[--stringIndex]);
|
|
types::glyph const& glyph = loadedFont.glyphs[c - 32];
|
|
lineWidth -= glyph.metrics.horiAdvance;
|
|
}
|
|
uint32_t center = (minX + (maxWidth / 2)) << 6;
|
|
uint32_t left = center - (lineWidth / 2);
|
|
emit_line(frameIndex, string, left - leftOffset, y, outputIndex, lineStart, stringIndex, color);
|
|
while (abs(string[stringIndex]) == ' ') stringIndex++;
|
|
lineStart = stringIndex;
|
|
lineWidth = 0;
|
|
y += loadedFont.font->face_metrics.height * 1.2;
|
|
x = minX << 6;
|
|
}
|
|
};
|
|
if (stringIndex != lineStart) {
|
|
uint32_t center = (minX + (maxWidth / 2)) << 6;
|
|
uint32_t left = center - (lineWidth / 2);
|
|
emit_line(frameIndex, string, left - leftOffset, y, outputIndex, lineStart, stringIndex, color);
|
|
y += loadedFont.font->face_metrics.height * 1.2;
|
|
x = minX << 6;
|
|
}
|
|
}
|
|
|
|
static char line_buffer[1024];
|
|
char const * combine_words(char const * const * const words, int start, int count,
|
|
int word_index)
|
|
{
|
|
assert(count > 0);
|
|
int index = 0;
|
|
for (int i = start; i < start + count; i++) {
|
|
char const * word = words[i];
|
|
int sign = (word_index == i) ? -1 : 1;
|
|
while (true) {
|
|
int c = *word++;
|
|
if (c == 0)
|
|
break;
|
|
line_buffer[index++] = (char)(c * sign);
|
|
}
|
|
line_buffer[index++] = ' ';
|
|
}
|
|
assert(index > 0);
|
|
line_buffer[index - 1] = 0;
|
|
return line_buffer;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// draw
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void font::draw(VkCommandBuffer commandBuffer,
|
|
uint32_t frameIndex,
|
|
renpy::interpreter const& state) const
|
|
{
|
|
// transfer
|
|
int outputIndex = 0;
|
|
|
|
if (state.pause.menu) {
|
|
assert(state.menu.count != 0);
|
|
for (uint32_t i = 0; i < state.menu.count; i++) {
|
|
uint32_t x = 400 << 6;
|
|
uint32_t y = (100 * i + 130) << 6;
|
|
centered(frameIndex, renpy::script::options[state.menu.optionIndex + i].string,
|
|
x, y,
|
|
400, 480,
|
|
outputIndex,
|
|
0x000000);
|
|
}
|
|
} else {
|
|
bool say_poem = state.say.stringIndex == (uint32_t)renpy::script::strings_say_poem_index;
|
|
if ((audio::poem_playing != nullptr) && say_poem) {
|
|
poem::poem const * const poem = audio::poem_playing;
|
|
int word_index = poem->timestamps[audio::poem_timestamp_index].wordIndex;
|
|
char const * const string = combine_words(poem->words,
|
|
poem->lines[audio::poem_line_index].start,
|
|
poem->lines[audio::poem_line_index].length,
|
|
word_index);
|
|
uint32_t x = textboxLeft << 6;
|
|
uint32_t y = (600 + 20) << 6;
|
|
centered(frameIndex, string,
|
|
x, y,
|
|
textboxLeft, textboxWidth,
|
|
outputIndex,
|
|
0x000000);
|
|
|
|
if ((audio::poem_line_index + 1) < poem->lines_length) {
|
|
char const * const string = combine_words(poem->words,
|
|
poem->lines[audio::poem_line_index + 1].start,
|
|
poem->lines[audio::poem_line_index + 1].length,
|
|
-1);
|
|
uint32_t x = textboxLeft << 6;
|
|
//uint32_t y = (600 + 20) << 6;
|
|
y += (20 << 6);
|
|
centered(frameIndex, string,
|
|
x, y,
|
|
textboxLeft, textboxWidth,
|
|
outputIndex,
|
|
0x808080);
|
|
}
|
|
|
|
} else if (state.say.stringIndex != -1u) {
|
|
char const * const string = renpy::script::strings[state.say.stringIndex];
|
|
uint32_t x = textboxLeft << 6;
|
|
uint32_t y = 600 << 6;
|
|
centered(frameIndex, string,
|
|
x, y,
|
|
textboxLeft, textboxWidth,
|
|
outputIndex,
|
|
0x000000);
|
|
}
|
|
|
|
if (state.say.characterIndex != -1u) {
|
|
const renpy::language::character & character = renpy::script::characters[state.say.characterIndex];
|
|
char const * const string = character.characterName;
|
|
uint32_t x = 550 << 6;
|
|
uint32_t y = 565 << 6;
|
|
centered(frameIndex, string,
|
|
x, y,
|
|
550, 180,
|
|
outputIndex,
|
|
character.color);
|
|
}
|
|
}
|
|
|
|
// flush
|
|
constexpr int mappedMemoryRangesCount = 1;
|
|
VkMappedMemoryRange mappedMemoryRanges[mappedMemoryRangesCount]{
|
|
{
|
|
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
.memory = instanceMemory,
|
|
.offset = 0,
|
|
.size = (sizeof (GlyphInstance)) * outputIndex,
|
|
}
|
|
};
|
|
alignMappedMemoryRanges(physicalDeviceProperties.limits.nonCoherentAtomSize,
|
|
instanceMemorySize,
|
|
mappedMemoryRangesCount,
|
|
mappedMemoryRanges);
|
|
vkFlushMappedMemoryRanges(device, mappedMemoryRangesCount, mappedMemoryRanges);
|
|
|
|
// bind/draw
|
|
|
|
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
|
|
|
VkDescriptorSet descriptorSets[1] = {
|
|
descriptorSet0,
|
|
};
|
|
vkCmdBindDescriptorSets(commandBuffer,
|
|
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
pipelineLayout,
|
|
0, 1, descriptorSets,
|
|
0, nullptr);
|
|
|
|
vkCmdBindIndexBuffer(commandBuffer, vertexIndex.buffer, vertexIndex.indexOffset, VK_INDEX_TYPE_UINT16);
|
|
|
|
VkDeviceSize vertexOffsets[2]{ 0, instanceBufferOffset[frameIndex] };
|
|
VkBuffer vertexBuffers[2]{ vertexIndex.buffer, instanceBuffer };
|
|
vkCmdBindVertexBuffers(commandBuffer, 0, 2, vertexBuffers, vertexOffsets);
|
|
|
|
vkCmdDrawIndexed(commandBuffer, 4, outputIndex, 0, 0, 0);
|
|
}
|
|
}
|