Text Rendering WIP

This commit is contained in:
Brandon Mak 2022-11-08 10:57:07 +08:00
parent 1165b9fa47
commit 572700fbb3
8 changed files with 175 additions and 296 deletions

View File

@ -1,13 +1,3 @@
/*************************************************************************//**
* \file SHFontAsset.h
* \author Brandon Mak
* \date 5 November 2022
* \brief
*
* Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
*****************************************************************************/
#pragma once
#include "SHAssetData.h"
@ -22,12 +12,14 @@ namespace SHADE
struct SH_API SHFontAsset : SHAssetData
{
using GlyphData = std::tuple<msdfgen::unicode_t, SHMatrix>;
static constexpr uint32_t NUM_CHANNELS = 3;
static constexpr uint32_t BYTES_PER_CHANNEL = 3;
/*-----------------------------------------------------------------------*/
/* MEMBER VARIABLES */
/*-----------------------------------------------------------------------*/
//! Name of the shader file (without parent path)
//std::string fontName;
std::string fontName;
//! Data containing character and uv transformation data and other misc data
std::vector<GlyphData> glyphTransformations;
@ -35,7 +27,13 @@ namespace SHADE
//! The actual data of the atlas to go into the binary
std::unique_ptr<unsigned char[]> bitmapData;
//! size of the bitmap
uint32_t bitmapSize;
//! Width of the bitmap
uint32_t bitmapWidth;
//! Height of the bitmap
uint32_t bitmapHeight;
//! Font geometry required to get kerning from
msdf_atlas::FontGeometry fontGeometry;
};
}
}

View File

@ -1,253 +0,0 @@
#include "SHpch.h"
#include "SHFontCompiler.h"
#include "Graphics/MiddleEnd/TextRendering/SHFreetypeInstance.h"
#include "Assets/Asset Types/SHFontAsset.h"
#include "Math/Vector/SHVec2.h"
#include <fstream>
#include <iostream>
namespace SHADE
{
/***************************************************************************/
/*!
\brief
Given a valid ptr to a font asset and relevant data, initialize the data
inside the font asset. See SHFontAsset for details.
\param fontAsset
The ptr to the font asset.
\param glyphData
Individual glyph data.
\param fontBitmap
Actual bitmap data
\param fontGeometry
Font geometry required to get advance
*/
/***************************************************************************/
void SHFontCompiler::WriteToFontAsset(SHFontAsset* fontAsset, std::vector<msdf_atlas::GlyphGeometry> const& glyphData, msdfgen::Bitmap<msdfgen::byte, 3> const& fontBitmap, msdf_atlas::FontGeometry const& fontGeometry) noexcept
{
if (!fontAsset)
return;
uint32_t numGlyphs = static_cast<uint32_t>(glyphData.size());
for (uint32_t i = 0; i < numGlyphs; ++i)
{
// bounding box of the glyph in atlas
double atlasL = 0.0, atlasR = 0.0, atlasT = 0.0, atlasB = 0.0;
// bounding box of glyph as it should be placed on the baseline
double atlasPL = 0.0, atlasPR = 0.0, atlasPT = 0.0, atlasPB = 0.0;
// initialize the bounding boxes
glyphData[i].getQuadAtlasBounds(atlasL, atlasB, atlasR, atlasT);
glyphData[i].getQuadPlaneBounds(atlasPL, atlasPB, atlasPR, atlasPT);
// normalize the bounding box to (0 - 1).
atlasL /= fontBitmap.width();
atlasR /= fontBitmap.width();
atlasT /= fontBitmap.height();
atlasB /= fontBitmap.height();
// Normalized texture dimensions
SHVec2 const NORMALIZED_TEX_DIMS{ static_cast<float> (atlasR - atlasL), static_cast<float> (atlasT - atlasB) };
// When we render the quad, it has to correctly scale depending on what letter/glyph we are rendering. This is for that scale.
SHVec2 const QUAD_SCALE { static_cast<float> (atlasPR - atlasL), static_cast<float> (atlasT - atlasB) };
// initialize a matrix for uv and quad transformation data
SHMatrix transformData
{
// For scaling the tex coords
NORMALIZED_TEX_DIMS[0], 0.0f, 0.0f, 0.0f,
0.0f, NORMALIZED_TEX_DIMS[1], 0.0f, 0.0f,
// For translating the tex coords
static_cast<float>(atlasL), static_cast<float>(atlasB), 1.0f, 0.0f,
// Stores the transformation for a quad to correctly shape the glyph (first 2 values) and the bearing (last 2)
QUAD_SCALE[0], QUAD_SCALE[1], static_cast<float>(atlasPL), static_cast<float>(atlasPB)
};
// Initialize new data (we want the matrix transposed for shader use)
SHFontAsset::GlyphData newData = std::make_tuple(glyphData[i].getCodepoint(), SHMatrix::Transpose(transformData));
// Push 1 set of data for a character/glyph into the asset.
fontAsset->glyphTransformations.push_back(newData);
}
uint32_t bytesRequired = fontBitmap.width() * fontBitmap.height() * 3 * sizeof(float);
// copy data from bitmap to asset. Each channel is a 32 bit float and there are 3 channels.
fontAsset->bitmapData = std::make_unique<unsigned char[]>(bytesRequired);
fontAsset->bitmapSize = bytesRequired;
}
/***************************************************************************/
/*!
\brief
Loads and compiles a font to binary format. Returns a path to the binary
data (XQ please confirm kor kor thanks <3).
\param path
Path to the font file (truetype font file) to load.
\return
Path to newly created binary data.
*/
/***************************************************************************/
std::optional<AssetPath> SHFontCompiler::LoadAndCompileFont(AssetPath path) noexcept
{
msdfgen::FontHandle* fontHandle = nullptr;
// XQ I need your help for path manipulation to actually load the msdfgen::FontHandle here. Am I doing this correctly?
fontHandle = msdfgen::loadFont(SHFreetypeInstance::GetFreetypeHandle(), path.string().c_str());
if (fontHandle)
{
// Compile a font asset
auto* fontAsset = CompileFontToMemory(fontHandle);
// No path to binary format
if (!fontAsset)
return {};
return CompileFontToBinary(path, *fontAsset);
}
SHLOG_ERROR("Unable to open font file: {}", path.string());
return {};
}
/***************************************************************************/
/*!
\brief
This function takes in a font handle and generates a font asset from it.
It first generates an atlas and all relevant data before creating the
asset.
\param fontHandle
MSDF font handle required to initialize member variables in SHFontAsset.
\return
A pointer to a brand new font asset.
*/
/***************************************************************************/
SHADE::SHFontAsset const* SHFontCompiler::CompileFontToMemory(msdfgen::FontHandle* fontHandle) noexcept
{
// Individual glyph geometry
std::vector<msdf_atlas::GlyphGeometry> glyphData;
// Actual bitmap data
msdfgen::Bitmap<msdfgen::byte, 3> fontBitmap;
// Font geometry required to get advance
msdf_atlas::FontGeometry fontGeometry (&glyphData);
// Load char set
fontGeometry.loadCharset(fontHandle, 1.0, msdf_atlas::Charset::ASCII);
// Apply MSDF edge coloring
const double maxCornerAngle = 3.0;
for (msdf_atlas::GlyphGeometry& glyph : glyphData)
glyph.edgeColoring(&msdfgen::edgeColoringInkTrap, maxCornerAngle, 0);
// configure parameters for atlas generation
msdf_atlas::TightAtlasPacker atlasPacker;
atlasPacker.setDimensionsConstraint(msdf_atlas::TightAtlasPacker::DimensionsConstraint::SQUARE);
atlasPacker.setMinimumScale(64.0);
atlasPacker.setPixelRange(2.0);
atlasPacker.setMiterLimit(1.0);
atlasPacker.pack(glyphData.data(), static_cast<int>(glyphData.size()));
// Get the dimensions after applying parameters
int width = 0, height = 0;
atlasPacker.getDimensions(width, height);
// generate the atlas
msdf_atlas::ImmediateAtlasGenerator<float, 3, msdf_atlas::msdfGenerator, msdf_atlas::BitmapAtlasStorage<float, 3>> generator(width, height);
msdf_atlas::GeneratorAttributes genAttribs;
generator.setAttributes(genAttribs);
generator.setThreadCount(4);
generator.generate(glyphData.data(), static_cast<int>(glyphData.size()));
fontBitmap = std::move(((msdfgen::Bitmap<msdfgen::byte, 3>&&)generator.atlasStorage()));
// at this point we have all the required data to initialize a font asset.
// Dynamically allocate new asset
SHFontAsset* newAsset = new SHFontAsset();
// Now we populate it with data
WriteToFontAsset(newAsset, glyphData, fontBitmap, fontGeometry);
return newAsset;
}
/***************************************************************************/
/*!
\brief
After generating the asset we call this function to serialize the font
data into binary data.
\param path
path to font file (?).
\param asset
Asset to write.
\return
Path the asset.
*/
/***************************************************************************/
std::string SHFontCompiler::CompileFontToBinary(AssetPath path, SHFontAsset const& asset) noexcept
{
std::string newPath{ path.string() };
newPath = newPath.substr(0, newPath.find_last_of('.'));
newPath += SHADER_BUILT_IN_EXTENSION.data();
std::ofstream file{ newPath, std::ios::binary | std::ios::out | std::ios::trunc };
uint32_t numGlyphs = asset.glyphTransformations.size();
// Write number of glyphs first
file.write(reinterpret_cast<char const*>(&numGlyphs), sizeof (uint32_t));
for (uint32_t i = 0; i < numGlyphs; ++i)
{
auto const& [glyph, data] = asset.glyphTransformations[i];
// write the glyph first
file.write (reinterpret_cast<char const*>(&glyph), sizeof (msdfgen::unicode_t));
// then write the data next to it
file.write (reinterpret_cast<char const*>(&data), sizeof (SHMatrix));
}
// Write bytes required for bitmap
file.write(reinterpret_cast<char const*>(asset.bitmapSize), sizeof (uint32_t));
// now we write the actual bitmap
file.write (reinterpret_cast<char const*>(asset.bitmapData.get()), asset.bitmapSize);
file.close();
return newPath;
}
}

View File

@ -1,22 +0,0 @@
#pragma once
#include "Assets/SHAssetMacros.h"
#include "msdf-atlas-gen/msdf-atlas-gen.h"
namespace SHADE
{
class SHFontAsset;
class SHFontCompiler
{
private:
static void WriteToFontAsset (SHFontAsset* fontAsset, std::vector<msdf_atlas::GlyphGeometry> const& glyphData, msdfgen::Bitmap<msdfgen::byte, 3> const& fontBitmap, msdf_atlas::FontGeometry const& fontGeometry) noexcept;
public:
static std::optional<AssetPath> LoadAndCompileFont (AssetPath path) noexcept;
static SHFontAsset const* CompileFontToMemory (msdfgen::FontHandle* fontHandle) noexcept;
static std::string CompileFontToBinary (AssetPath path, SHFontAsset const& asset) noexcept;
};
}

View File

@ -37,12 +37,17 @@ namespace SHADE
file.read(reinterpret_cast<char*>(&std::get<1>(newFontAsset->glyphTransformations[i])), sizeof(SHMatrix));
}
// Read bytes required for the bitmap
file.read(reinterpret_cast<char*>(&newFontAsset->bitmapSize), sizeof(uint32_t));
// read the width
file.read(reinterpret_cast<char*>(&newFontAsset->bitmapWidth), sizeof(SHFontAsset::bitmapWidth));
// read the height
file.read(reinterpret_cast<char*>(&newFontAsset->bitmapHeight), sizeof(SHFontAsset::bitmapHeight));
uint32_t bytesRequired = newFontAsset->bitmapWidth * newFontAsset->bitmapHeight * SHFontAsset::BYTES_PER_CHANNEL * SHFontAsset::NUM_CHANNELS;
// Read the bitmap
newFontAsset->bitmapData = std::make_unique<unsigned char[]>(newFontAsset->bitmapSize);
file.read (reinterpret_cast<char*>(newFontAsset->bitmapData.get()), newFontAsset->bitmapSize);
newFontAsset->bitmapData = std::make_unique<unsigned char[]>(bytesRequired);
file.read (reinterpret_cast<char*>(newFontAsset->bitmapData.get()), bytesRequired);
file.close();

View File

@ -45,7 +45,7 @@ namespace SHADE
};
// For global data (generic data and textures)
Handle<SHVkDescriptorSetLayout> staticGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS,{ genericDataBinding, texturesBinding });
Handle<SHVkDescriptorSetLayout> staticGlobalLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::STATIC_GLOBALS,{ genericDataBinding, texturesBinding});
std::vector<SHVkDescriptorSetLayout::Binding> lightBindings{};
@ -98,6 +98,25 @@ namespace SHADE
// For High frequency global data (camera)
Handle<SHVkDescriptorSetLayout> materialDataPerInstanceLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::PER_INSTANCE, { materialDataBinding });
SHVkDescriptorSetLayout::Binding fontBitmapBinding
{
.Type = vk::DescriptorType::eCombinedImageSampler,
.Stage = vk::ShaderStageFlagBits::eFragment,
.BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA,
.DescriptorCount = 1,
};
SHVkDescriptorSetLayout::Binding fontMatrixBinding
{
.Type = vk::DescriptorType::eStorageBuffer,
.Stage = vk::ShaderStageFlagBits::eVertex,
.BindPoint = SHGraphicsConstants::DescriptorSetBindings::FONT_MATRIX_DATA,
.DescriptorCount = 1,
};
Handle<SHVkDescriptorSetLayout> fontDataLayout = logicalDevice->CreateDescriptorSetLayout(SHGraphicsConstants::DescriptorSetIndex::FONT_DATA, { fontBitmapBinding, fontMatrixBinding });
globalDescSetLayouts.push_back(staticGlobalLayout);
globalDescSetLayouts.push_back(dynamicGlobalLayout);
globalDescSetLayouts.push_back(cameraDataGlobalLayout);

View File

@ -82,6 +82,14 @@ namespace SHADE
/***************************************************************************/
static constexpr uint32_t RENDERGRAPH_NODE_COMPUTE_RESOURCE = 5;
/***************************************************************************/
/*!
\brief
To store font data.
*/
/***************************************************************************/
static constexpr uint32_t FONT_DATA = 6;
};
struct DescriptorSetBindings
@ -107,7 +115,7 @@ namespace SHADE
/***************************************************************************/
/*!
\brief
DescriptorSet binding for combined image sampler data.
DescriptorSet binding for light data.
*/
/***************************************************************************/
@ -116,7 +124,7 @@ namespace SHADE
/***************************************************************************/
/*!
\brief
DescriptorSet binding for lights.
DescriptorSet binding for camera data.
*/
/***************************************************************************/
@ -130,6 +138,24 @@ namespace SHADE
/***************************************************************************/
static constexpr uint32_t BATCHED_PER_INST_DATA = 0;
/***************************************************************************/
/*!
\brief
Descriptor set binding for font bitmaps.
*/
/***************************************************************************/
static constexpr uint32_t FONT_BITMAP_DATA = 0;
/***************************************************************************/
/*!
\brief
Descriptor set binding for font matrix data.
*/
/***************************************************************************/
static constexpr uint32_t FONT_MATRIX_DATA = 1;
};
struct VertexBufferBindings

View File

@ -0,0 +1,60 @@
#include "SHpch.h"
#include "SHFont.h"
#include "Graphics/Devices/SHVkLogicalDevice.h"
#include "Graphics/MiddleEnd/GlobalData/SHGraphicsGlobalData.h"
#include "Graphics/Descriptors/SHVkDescriptorSetGroup.h"
namespace SHADE
{
SHFont::SHFont(Handle<SHVkLogicalDevice> const& inLogicalDeviceHdl, Handle<SHVkCommandPool> commandPool, Handle<SHVkDescriptorPool> descPool, SHFontAsset const& asset) noexcept
{
// assign device for convenient usage
logicalDevice = inLogicalDeviceHdl;
SHImageCreateParams imageParams
{
.imageType = vk::ImageType::e2D,
.width = asset.bitmapWidth,
.height = asset.bitmapHeight,
.depth = 1,
.levels = 1,
.arrayLayers = 1,
.imageFormat = vk::Format::eR32G32B32Sfloat,
.usageFlags = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst,
.createFlags = {}
};
uint32_t bytesRequired = asset.bitmapWidth * asset.bitmapHeight * SHFontAsset::BYTES_PER_CHANNEL * SHFontAsset::NUM_CHANNELS;
uint32_t mipOffset = 0;
// Create the image
bitmapDataImage = logicalDevice->CreateImage(imageParams, asset.bitmapData.get(), bytesRequired, { &mipOffset, 1 }, VmaMemoryUsage::VMA_MEMORY_USAGE_AUTO, {});
//SHImageViewDetails viewDetails
//{
// .viewType = vk::ImageViewType::e2D,
// .format = vk::Format::eR32G32B32Sfloat,
// .imageAspectFlags = vk::ImageAspectFlagBits::eColor,
// .baseMipLevel = 0,
// .mipLevelCount = 1,
// .baseArrayLayer = 0,
// .layerCount = 1,
//};
//bitmapDataImageView = bitmapDataImage->CreateImageView(logicalDevice, bitmapDataImage, )
uint32_t glyphDataSize = asset.glyphTransformations.size() * sizeof (SHMatrix);
// allocate GPU buffer for matrices
matrixDataBuffer = logicalDevice->CreateBuffer(glyphDataSize, nullptr, glyphDataSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer, VMA_MEMORY_USAGE_AUTO, {});
// Font data desc set layout
auto fontDataLayout = SHGraphicsGlobalData::GetDescSetLayouts()[SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA];
// allocate desc set for the bitmap and matrix data
descSet = descPool->Allocate({fontDataLayout}, {1, 1});
//auto viewLayoutSampler = std::make_tuple()
descSet->ModifyWriteDescImage(SHGraphicsConstants::DescriptorSetIndex::FONT_DATA, SHGraphicsConstants::DescriptorSetBindings::FONT_BITMAP_DATA, {});
}
}

View File

@ -0,0 +1,46 @@
#pragma once
#include "Resource/SHHandle.h"
#include "msdf-atlas-gen/msdf-atlas-gen.h"
#include "Assets/Asset Types/SHFontAsset.h"
namespace SHADE
{
class SHVkLogicalDevice;
class SHVkDescriptorPool;
class SHVkDescriptorSetGroup;
class SHVkCommandBuffer;
class SHVkCommandPool;
class SHVkImage;
class SHVkImageView;
class SHVkBuffer;
class SHFont
{
private:
//! Device for creation and destruction
Handle<SHVkLogicalDevice> logicalDevice;
//! Font asset contains exactly what we need, so we'll use it
Handle<SHFontAsset> fontAsset;
//! Device memory that stores bitmap data
Handle<SHVkImage> bitmapDataImage;
//! View to device memory
Handle<SHVkImageView> bitmapDataImageView;
//! Device memory that stores matrix data
Handle<SHVkBuffer> matrixDataBuffer;
//! Descriptor set required to store the bitmap AND matrix data for the UV and quad transformation
Handle<SHVkDescriptorSetGroup> descSet;
//! Used for getting the correct indices into the matrix data buffer
std::unordered_map<msdfgen::unicode_t, uint32_t> unicodeIndexing;
public:
SHFont (Handle<SHVkLogicalDevice> const& inLogicalDeviceHdl, Handle<SHVkCommandPool> commandPool, Handle<SHVkDescriptorPool> descPool, SHFontAsset const& asset) noexcept;
};
}