图集渲染文本

This commit is contained in:
Nanako 2024-12-17 10:54:19 +08:00
parent 8a7ee7de46
commit cdd77d3cbb
43 changed files with 1042 additions and 528 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/cmake-build-debug
/cmake-build-release
/.idea

0
.gitmodules vendored Normal file
View File

View File

@ -10,7 +10,6 @@ include(cmake/compile_shaders.cmake)
find_package(Eigen3 REQUIRED)
find_package(spdlog REQUIRED)
find_package(glfw3 REQUIRED)
find_package(Freetype REQUIRED)
find_package(Stb REQUIRED)
# Debug,

View File

@ -1,5 +1,7 @@
#include <iostream>
#include "core/renderer/renderer.h"
#include "core/window/window_manager.h"
#include "core/window/renderer_window.h"

View File

@ -26,7 +26,7 @@ if (METAL_BACKEND)
endif ()
add_library(${PROJECT_NAME} STATIC ${RENDERER_SOURCES})
target_link_libraries(${PROJECT_NAME} PUBLIC Eigen3::Eigen spdlog::spdlog glfw aorii_core Freetype::Freetype)
target_link_libraries(${PROJECT_NAME} PUBLIC Eigen3::Eigen spdlog::spdlog glfw aorii_core)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${Stb_INCLUDE_DIR})
target_compile_definitions(${PROJECT_NAME} PRIVATE NOMINMAX)
add_os_definitions(${PROJECT_NAME})
@ -67,6 +67,7 @@ compile_aorii_shader(aorii_rect)
compile_aorii_shader(aorii_rounded_rect)
compile_aorii_shader(aorii_texture)
compile_aorii_shader(aorii_segment)
compile_aorii_shader(aorii_sdf_text)
add_compile_shaders_target("compile_aorii_shaders" ${PROJECT_NAME})
# example, shader

View File

@ -9,15 +9,20 @@ class dx_buffer : public renderer_buffer {
public:
explicit dx_buffer(buffer_type in_type, int32_t in_element_byte, int32_t in_size): renderer_buffer(in_type, in_element_byte, in_size) {
const auto d3d_device = aorii::get_renderer<dx_renderer>()->get_d3d_device();
int32_t byte_size = in_element_byte * in_size;
// 对齐到16字节
byte_size = (byte_size + 15) & ~15;
D3D11_BUFFER_DESC buffer_desc = {};
buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
buffer_desc.ByteWidth = in_element_byte * in_size;
buffer_desc.ByteWidth = byte_size;
buffer_desc.BindFlags = get_dx_buffer_type();
buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
auto hr = d3d_device->CreateBuffer(&buffer_desc, nullptr, &buffer);
if (FAILED(hr)) { spdlog::critical("无法创建缓冲区: {0}", hr); }
if (FAILED(hr)) {
spdlog::critical("无法创建缓冲区: {0}", hr);
}
}
~dx_buffer() override { if (buffer) { buffer->Release(); } }
@ -58,9 +63,12 @@ protected:
if (buffer) {
buffer->GetDesc(&old_desc);
}
int32_t byte_size = element_byte * new_count;
// 对齐到16字节
byte_size = (byte_size + 15) & ~15;
D3D11_BUFFER_DESC buffer_desc = old_desc;
buffer_desc.ByteWidth = element_byte * new_count;
buffer_desc.ByteWidth = byte_size;
ID3D11Buffer* new_buffer = nullptr;
auto hr = d3d_device->CreateBuffer(&buffer_desc, nullptr, &new_buffer);
if (FAILED(hr)) {

View File

@ -5,6 +5,7 @@
#include "dx_buffer.h"
#include "dx_texture.h"
#include "dx_texture_array.h"
#include "dx_window.h"
#include "core/window/window_manager.h"
#include "spdlog/spdlog.h"
@ -35,6 +36,7 @@ bool dx_renderer::init() {
rounded_rect_p.init();
texture_p.init();
segment_p.init();
text_p.init();
return true;
}
@ -56,10 +58,14 @@ bool dx_renderer::render() {
return true;
}
renderer_texture* dx_renderer::create_texture(int in_size_width, int in_size_height, texture_format in_format) {
renderer_texture* dx_renderer::create_texture(uint32_t in_size_width, uint32_t in_size_height, texture_format in_format) {
return new dx_texture({ in_size_width, in_size_height }, in_format);
}
renderer_texture_array* dx_renderer::create_texture_array(uint32_t in_size_width, uint32_t in_size_height, uint32_t in_count, texture_format in_format) {
return new dx_texture_array({ in_size_width, in_size_height }, in_format, in_count);
}
Eigen::Matrix4f dx_renderer::make_projection_matrix(const Eigen::Vector2i& size) {
// 创建一个单位矩阵
Eigen::Matrix4f matrix = Eigen::Matrix4f::Identity();

View File

@ -4,6 +4,7 @@
#include "core/renderer/renderer.h"
#include "pipeline/dx_rect_pipeline.h"
#include "pipeline/dx_rounded_rect_pipeline.h"
#include "pipeline/dx_sdf_text_pipeline.h"
#include "pipeline/dx_segment_pipeline.h"
#include "pipeline/dx_texture_pipeline.h"
@ -15,7 +16,8 @@ public:
void destroy() override;
bool render() override;
renderer_texture* create_texture(int in_size_width, int in_size_height, texture_format in_format) override;
renderer_texture* create_texture(uint32_t in_size_width, uint32_t in_size_height, texture_format in_format) override;
renderer_texture_array* create_texture_array(uint32_t in_size_width, uint32_t in_size_height, uint32_t in_count, texture_format in_format) override;
[[nodiscard]] ID3D11Device* get_d3d_device() const { return device; }
[[nodiscard]] ID3D11DeviceContext* get_d3d_context() const { return context; }
@ -24,6 +26,7 @@ public:
rounded_rect_pipeline* get_rounded_rect_pipeline() override { return &rounded_rect_p; }
texture_pipeline* get_texture_pipeline() override { return &texture_p; }
segment_pipeline* get_segment_pipeline() override { return &segment_p; }
sdf_text_pipeline* get_text_pipeline() override { return &text_p; }
Eigen::Matrix4f make_projection_matrix(const Eigen::Vector2i& size) override;
@ -45,6 +48,7 @@ private:
dx_rounded_rect_pipeline rounded_rect_p;
dx_texture_pipeline texture_p;
dx_segment_pipeline segment_p;
dx_sdf_text_pipeline text_p;
};
inline DXGI_FORMAT get_dxgi_format(texture_format format) {
@ -72,12 +76,6 @@ inline DXGI_FORMAT get_dxgi_format(texture_format format) {
// 24-bit formats
case texture_format::RGB8_UNORM:
return DXGI_FORMAT_B8G8R8X8_UNORM; // Note: DXGI doesn't have a direct RGB8 format
case texture_format::RGB8_SNORM:
return DXGI_FORMAT_UNKNOWN; // No direct equivalent
case texture_format::RGB8_UINT:
return DXGI_FORMAT_UNKNOWN; // No direct equivalent
case texture_format::RGB8_SINT:
return DXGI_FORMAT_UNKNOWN; // No direct equivalent
// 32-bit formats
case texture_format::RGBA8_UNORM:
@ -100,8 +98,6 @@ inline DXGI_FORMAT get_dxgi_format(texture_format format) {
return DXGI_FORMAT_R16_FLOAT;
case texture_format::RG16_FLOAT:
return DXGI_FORMAT_R16G16_FLOAT;
case texture_format::RGB16_FLOAT:
return DXGI_FORMAT_R16G16B16A16_FLOAT; // Note: No direct RGB16 format
case texture_format::RGBA16_FLOAT:
return DXGI_FORMAT_R16G16B16A16_FLOAT;
@ -182,6 +178,7 @@ inline DXGI_FORMAT get_dxgi_format(texture_format format) {
return DXGI_FORMAT_BC7_UNORM_SRGB;
default:
assert(0);
return DXGI_FORMAT_UNKNOWN;
}
}

View File

@ -2,24 +2,108 @@
#include "dx_renderer.h"
dx_texture::dx_texture(const Eigen::Vector2i& size, texture_format format) : renderer_texture(format) {
dx_texture::dx_texture(const Eigen::Vector2i& size, const texture_format format, uint32_t count) : renderer_texture(format) {
create_texture(size, format, count);
}
dx_texture::~dx_texture() {
release_texture();
}
void* dx_texture::lock(uint32_t* out_row_pitch) {
return lock(0, out_row_pitch);
}
void dx_texture::unlock() {
return unlock(0);
}
void* dx_texture::lock(uint32_t index, uint32_t* out_row_pitch) const {
D3D11_TEXTURE2D_DESC desc;
m_texture->GetDesc(&desc);
D3D11_MAPPED_SUBRESOURCE mappedResource;
auto context = aorii::get_renderer<dx_renderer>()->get_d3d_context();
context->Map(m_texture, index, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (out_row_pitch) *out_row_pitch = mappedResource.RowPitch;
return mappedResource.pData;
}
void dx_texture::unlock(uint32_t index) const {
auto context = aorii::get_renderer<dx_renderer>()->get_d3d_context();
context->Unmap(m_texture, index);
}
void dx_texture::update_subresource(const Eigen::AlignedBox2i& in_area, const void* in_buffer, const uint32_t in_row_pitch) {
update_subresource(0, in_area, in_buffer, in_row_pitch);
}
void dx_texture::update_subresource(uint32_t index, const Eigen::AlignedBox2i& in_area, const void* in_buffer,
const uint32_t in_row_pitch) const {
D3D11_BOX box = {};
box.left = in_area.min().x();
box.top = in_area.min().y();
box.right = in_area.max().x();
box.bottom = in_area.max().y();
box.front = 0;
box.back = 1;
const auto context = aorii::get_renderer<dx_renderer>()->get_d3d_context();
context->UpdateSubresource(m_texture, index, &box, in_buffer, in_row_pitch, 0);
}
bool dx_texture::resize(const Eigen::Vector2i& size) {
D3D11_TEXTURE2D_DESC desc;
m_texture->GetDesc(&desc);
if (desc.Width == size.x() && desc.Height == size.y()) {
return true;
}
return create_texture(size, get_format());
}
Eigen::Vector2i dx_texture::size() {
D3D11_TEXTURE2D_DESC desc;
m_texture->GetDesc(&desc);
return { desc.Width, desc.Height };
}
uint32_t dx_texture::get_count() const {
D3D11_TEXTURE2D_DESC desc;
m_texture->GetDesc(&desc);
return desc.ArraySize;
}
void dx_texture::set_count(const uint32_t in_count) {
if (in_count == get_count()) {
return;
}
D3D11_TEXTURE2D_DESC desc;
m_texture->GetDesc(&desc);
desc.ArraySize = in_count;
create_texture({ desc.Width, desc.Height }, get_format(), in_count);
}
bool dx_texture::create_texture(const Eigen::Vector2i& in_size, texture_format in_format, uint32_t in_count) {
// 释放旧资源
release_texture();
// 创建新资源
auto d3d_device = aorii::get_renderer<dx_renderer>()->get_d3d_device();
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = size.x();
desc.Height = size.y();
desc.Width = in_size.x();
desc.Height = in_size.y();
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = get_dxgi_format(format);
desc.ArraySize = in_count;
desc.Format = get_dxgi_format(in_format);
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.Usage = in_count > 1 ? D3D11_USAGE_DEFAULT : D3D11_USAGE_DYNAMIC;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
desc.MiscFlags = 0;
auto hr = d3d_device->CreateTexture2D(&desc, nullptr, &m_texture);
if (FAILED(hr)) {
spdlog::critical("无法创建纹理, 错误: {0}", hr);
return;
return false;
}
D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
@ -30,34 +114,14 @@ dx_texture::dx_texture(const Eigen::Vector2i& size, texture_format format) : ren
hr = d3d_device->CreateShaderResourceView(m_texture, &srv_desc, &srv);
if (FAILED(hr)) {
spdlog::critical("无法创建着色器资源视图, 错误: {0}", hr);
return;
return false;
}
return true;
}
dx_texture::~dx_texture() {
void dx_texture::release_texture() {
if (srv) srv->Release();
if (m_texture) m_texture->Release();
}
void* dx_texture::lock(unsigned int* out_row_pitch) {
D3D11_TEXTURE2D_DESC desc;
m_texture->GetDesc(&desc);
D3D11_MAPPED_SUBRESOURCE mappedResource;
auto context = aorii::get_renderer<dx_renderer>()->get_d3d_context();
context->Map(m_texture, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (out_row_pitch) *out_row_pitch = mappedResource.RowPitch;
return mappedResource.pData;
}
void dx_texture::unlock() {
auto context = aorii::get_renderer<dx_renderer>()->get_d3d_context();
context->Unmap(m_texture, 0);
}
bool dx_texture::resize(const Eigen::Vector2i& size) { return true; }
Eigen::Vector2i dx_texture::size() {
D3D11_TEXTURE2D_DESC desc;
m_texture->GetDesc(&desc);
return { desc.Width, desc.Height };
srv = nullptr;
m_texture = nullptr;
}

View File

@ -7,18 +7,31 @@
// DX11纹理
class dx_texture : public renderer_texture {
public:
dx_texture(const Eigen::Vector2i& size, texture_format format);
dx_texture(const Eigen::Vector2i& size, texture_format format, uint32_t count = 1);
~dx_texture() override;
void* lock(unsigned int* out_row_pitch) override;
void* lock(uint32_t* out_row_pitch) override;
void unlock() override;
void* lock(uint32_t index, uint32_t* out_row_pitch) const;
void unlock(uint32_t index) const;
void update_subresource(const Eigen::AlignedBox2i& in_area, const void* in_buffer, uint32_t in_row_pitch) override;
void update_subresource(uint32_t index, const Eigen::AlignedBox2i& in_area, const void* in_buffer, uint32_t in_row_pitch) const;
bool resize(const Eigen::Vector2i& size) override;
Eigen::Vector2i size() override;
[[nodiscard]] ID3D11ShaderResourceView* get_srv() const { return srv; }
void* get_native_handle() override { return m_texture; }
void* get_shader_resource_view() override { return srv; }
[[nodiscard]] uint32_t get_count() const;
void set_count(uint32_t in_count);
private:
bool create_texture(const Eigen::Vector2i& in_size, texture_format in_format, uint32_t in_count = 1);
void release_texture();
ID3D11Texture2D* m_texture{};
ID3D11ShaderResourceView* srv{};
};

View File

@ -0,0 +1 @@
#include "dx_texture_array.h"

View File

@ -0,0 +1,32 @@
#pragma once
#include "dx_texture.h"
#include "core/renderer/renderer_texture_array.h"
class dx_texture;
class dx_texture_array : public renderer_texture_array {
public:
dx_texture_array(const Eigen::Vector2i& size, texture_format format, const uint32_t count): renderer_texture_array(format) {
texture = new dx_texture(size, format, count);
}
~dx_texture_array() override { delete texture; }
void* lock(const uint32_t index, uint32_t* out_row_pitch) override { return texture->lock(index, out_row_pitch); }
void unlock(const uint32_t index) override { texture->unlock(index); }
void update_subresource(const uint32_t index, const Eigen::AlignedBox2i& in_area, const void* in_buffer, uint32_t in_buffer_size) override {
texture->update_subresource(index, in_area, in_buffer, in_buffer_size);
}
bool resize(const Eigen::Vector2i& size) override { return texture->resize(size); }
Eigen::Vector2i size() override { return texture->size(); }
void* get_native_handle() override { return texture->get_native_handle(); }
void* get_shader_resource_view() override { return texture->get_shader_resource_view(); }
[[nodiscard]] uint32_t get_count() override { return texture->get_count(); }
protected:
void on_set_count(const uint32_t in_count) override { texture->set_count(in_count); }
private:
dx_texture* texture;
};

View File

@ -1,4 +1,4 @@
#include "dx_window.h"
#include "dx_window.h"
#include <spdlog/spdlog.h>
@ -11,25 +11,16 @@
using namespace aorii;
renderer_texture* test_texture = nullptr;
text_font* text = nullptr;
int current_char = 33;
// text_font* text = nullptr;
wchar_t current_char = u'a';
void delta_char_texture(int in_delta) {
current_char -= in_delta;
// current_char = std::clamp(current_char, 33, 126);
// const auto& character = text->glyphs[current_char];
// test_texture = character.glyph_texture;
}
void on_mouse_scroll(GLFWwindow* window, double xoffset, double yoffset) {
current_char -= yoffset;
current_char = std::clamp(current_char, 33, 126);
const auto& character = text->glyphs[current_char];
if (!character.buffer)
return;
const auto font_image = image_accessor<pixel_r8>(character.buffer, character.size);
delete test_texture;
test_texture = get_renderer_raw()->create_texture(character.size, texture_format::RGB16_FLOAT);
uint32_t row_pitch = 0;
const auto data = test_texture->lock(&row_pitch);
auto image = image_accessor<pixel_rgb16f>(data, test_texture->size());
image.set_row_pitch(row_pitch);
image.copy_from(font_image);
test_texture->unlock();
delta_char_texture(yoffset);
}
bool dx_window::create_surface(GLFWwindow* in_window) {
@ -94,8 +85,8 @@ bool dx_window::create_surface(GLFWwindow* in_window) {
// test_texture = dx->load_image(R"(D:\69054578_p0.jpg)", texture_format::RGBA8_UNORM);
glfwSetScrollCallback(get_glfw_window(), on_mouse_scroll);
text = aorii_text::load_font("C:/Windows/Fonts/simsunb.ttf", false, 48);
on_mouse_scroll(nullptr, 0, 0);
// text = aorii_text::load_font("C:/Windows/Fonts/simsunb.ttf", 32);
// delta_char_texture(0);
return true;
}
@ -120,7 +111,8 @@ void dx_window::begin_frame() {
// auto height = sin(get_total_time().count()) * 100;
// context.draw_line( { 600, 600 }, { mouse_x, mouse_y }, { 1, 0, 1, 1 }, thickness);
if (test_texture) context.draw_texture({ 0.f, 0.f }, test_texture->size().cast<float>(), test_texture);
// if (test_texture) context.draw_texture({ 0.f, 0.f }, test_texture->size().cast<float>(), test_texture);
context.draw_string({0, 0}, L"你好,世界!", 64);
context.flush();

View File

@ -19,15 +19,19 @@ bool dx_pipeline_loader::load(const char* vertex_shader_name, const char* pixel_
spdlog::critical("rect_pipeline 无法创建像素着色器: {0}", hr);
return false;
}
uint32_t offset = 0;
D3D11_INPUT_ELEMENT_DESC layout_desc[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0},
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0},
{ "TEXCOORD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 48, D3D11_INPUT_PER_VERTEX_DATA, 0},
{ "TEXCOORD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 64, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
constexpr int32_t element_count = sizeof(layout_desc) / sizeof(D3D11_INPUT_ELEMENT_DESC);
hr = d3d_device->CreateInputLayout(layout_desc, element_count, vertex_shader_code.data(), vertex_shader_code.size(), &input_layout);
if (FAILED(hr)) {
spdlog::critical("rect_pipeline 无法创建输入布局: {0}", hr);
spdlog::critical("{0} 无法创建输入布局: {1}", vertex_shader_name, hr);
return false;
}
return true;

View File

@ -1,4 +1,4 @@
#include "dx_rect_pipeline.h"
#include "dx_rect_pipeline.h"
#include "backend/dx/dx_renderer.h"

View File

@ -1,4 +1,4 @@
#include "dx_rounded_rect_pipeline.h"
#include "dx_rounded_rect_pipeline.h"
#include "backend/dx/dx_renderer.h"

View File

@ -1,9 +1,23 @@
#include "dx_sdf_text_pipeline.h"
#include "backend/dx/dx_renderer.h"
#include "core/renderer/renderer_texture_array.h"
bool dx_sdf_text_pipeline::init() {
sdf_text_pipeline::init();
const auto d3d_device = aorii::get_renderer<dx_renderer>()->get_d3d_device();
D3D11_SAMPLER_DESC sampler_desc = {};
sampler_desc.Filter = D3D11_FILTER_ANISOTROPIC;
sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampler_desc.MinLOD = 0;
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
d3d_device->CreateSamplerState(&sampler_desc, &sampler_state);
loader.load("aorii_sdf_text_vertex_main", "aorii_sdf_text_pixel_main");
return true;
}
@ -13,15 +27,30 @@ void dx_sdf_text_pipeline::use() {
}
void dx_sdf_text_pipeline::draw() {
if (!glyph_texture)
return;
const auto d3d_context = aorii::get_renderer<dx_renderer>()->get_d3d_context();
// 绘制
d3d_context->DrawIndexed(index_buffer->get_size(), 0, 0);
d3d_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}
// 设置顶点缓冲区
constexpr UINT stride = sizeof(aorii_vertex);
constexpr UINT offset = 0;
auto* v_buffer = static_cast<ID3D11Buffer*>(vertex_buffer->get_native_handle());
auto* i_buffer = static_cast<ID3D11Buffer*>(index_buffer->get_native_handle());
auto* c_buffer = static_cast<ID3D11Buffer*>(param_buffer->get_native_handle());
auto* c2_buffer = static_cast<ID3D11Buffer*>(sdf_font_param_buffer->get_native_handle());
auto* srv = static_cast<ID3D11ShaderResourceView*>(glyph_texture->get_shader_resource_view());
void dx_sdf_text_pipeline::set_param(const param& in_param) { sdf_text_pipeline::set_param(in_param); }
d3d_context->IASetVertexBuffers(0, 1, &v_buffer, &stride, &offset);
d3d_context->IASetIndexBuffer(i_buffer, DXGI_FORMAT_R32_UINT, 0);
void dx_sdf_text_pipeline::set_sdf_font_param(const sdf_font_param& in_param) {
sdf_text_pipeline::set_sdf_font_param(in_param);
d3d_context->PSSetSamplers(0, 1, &sampler_state);
d3d_context->VSSetConstantBuffers(0, 1, &c_buffer);
d3d_context->PSSetConstantBuffers(0, 1, &c2_buffer);
d3d_context->PSSetShaderResources(0, 1, &srv);
// 设置图元拓扑
d3d_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
// 绘制矩形
d3d_context->Draw(vertex_buffer->get_size(), 0);
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "dx_pipeline_loader.h"
#include "core/pipeline/sdf_text_pipeline.h"
#include "core/renderer/renderer_texture.h"
class dx_sdf_text_pipeline : public sdf_text_pipeline {
public:
@ -8,9 +9,8 @@ public:
void use() override;
void draw() override;
void set_param(const param& in_param) override;
void set_sdf_font_param(const sdf_font_param& in_param) override;
private:
dx_pipeline_loader loader;
ID3D11SamplerState* sampler_state = nullptr;
};

View File

@ -1,4 +1,4 @@
#include "dx_segment_pipeline.h"
#include "dx_segment_pipeline.h"
#include "backend/dx/dx_renderer.h"

View File

@ -1,4 +1,4 @@
#include "dx_texture_pipeline.h"
#include "dx_texture_pipeline.h"
#include "backend/dx/dx_renderer.h"
#include "backend/dx/dx_texture.h"
@ -47,7 +47,3 @@ void dx_texture_pipeline::draw() {
// 绘制矩形
d3d_context->Draw(vertex_buffer->get_size(), 0);
}
void dx_texture_pipeline::set_texture(renderer_texture* in_texture) {
texture = static_cast<dx_texture*>(in_texture);
}

View File

@ -9,11 +9,8 @@ public:
bool init() override;
void use() override;
void draw() override;
void set_texture(renderer_texture* in_texture) override;
private:
dx_pipeline_loader loader;
ID3D11SamplerState* sampler_state = nullptr;
renderer_texture* texture = nullptr;
};

View File

@ -6,10 +6,26 @@
#include "core/renderer/renderer_buffer.h"
#include "misc/color.h"
struct aorii_vertex_param {
float param_a1;
float param_a2;
float param_a3;
float param_a4;
float param_b1;
float param_b2;
float param_b3;
float param_b4;
float param_c1;
float param_c2;
float param_c3;
float param_c4;
};
struct aorii_vertex {
Eigen::Vector2f position;
Eigen::Vector2f uv;
linear_color color;
aorii_vertex_param param;
};
struct aorii_triangle {
uint32_t index[3];

View File

@ -6,6 +6,9 @@
#include FT_FREETYPE_H
#include <omp.h>
class renderer_texture_array;
class renderer_texture;
class sdf_text_pipeline : public pipeline {
public:
struct param {
@ -22,7 +25,11 @@ public:
virtual void set_param(const param& in_param);
virtual void set_sdf_font_param(const sdf_font_param& in_param);
private:
virtual void set_glyph_texture(renderer_texture_array* in_texture) {
glyph_texture = in_texture;
}
protected:
renderer_buffer* param_buffer = nullptr;
renderer_buffer* sdf_font_param_buffer = nullptr;
renderer_texture_array* glyph_texture = nullptr;
};

View File

@ -12,7 +12,10 @@ public:
void destroy() override;
void set_param(const param& in_param);
virtual void set_texture(renderer_texture* in_texture) = 0;
void set_texture(renderer_texture* in_texture) {
texture = in_texture;
}
protected:
renderer_buffer* param_buffer = nullptr;
renderer_texture* texture = nullptr;
};

View File

@ -1,53 +1,74 @@
#pragma once
#pragma once
#include <cstdint>
#include <Eigen/Eigen>
template<typename From, typename To>
To convert(const From& value) {
// 获取 From 类型的最小值和最大值
constexpr auto source_min = std::numeric_limits<From>::min(); // 最小值
constexpr auto source_max = std::numeric_limits<From>::max(); // 最大值
// 获取 To 类型的最小值和最大值
constexpr auto target_min = std::numeric_limits<To>::min(); // 最小值
constexpr auto target_max = std::numeric_limits<To>::max(); // 最大值
// 如果源和目标类型相同,直接返回
if constexpr (std::is_same_v<From, To>) {
return value;
}
// 获取 From 类型的最小值和最大值
const auto source_min = static_cast<double>(std::numeric_limits<From>::lowest());
const auto source_max = static_cast<double>(std::numeric_limits<From>::max());
// 获取 To 类型的最小值和最大值
const auto target_min = static_cast<double>(std::numeric_limits<To>::lowest());
const auto target_max = static_cast<double>(std::numeric_limits<To>::max());
// 线性映射公式
auto a = static_cast<double>(value) - source_min;
auto b = static_cast<double>(target_max) - target_min;
auto c = (source_max - source_min);
return static_cast<To>(target_min + a * b / c);
auto normalized = (static_cast<double>(value) - source_min) / (source_max - source_min); // [0, 1]
return static_cast<To>(target_min + normalized * (target_max - target_min));
}
template<typename From, typename To>
To color_convert(const From& value) {
// 获取 To 类型的最小值和最大值
constexpr auto target_min = std::numeric_limits<To>::min(); // 最小值
constexpr auto target_max = std::numeric_limits<To>::max(); // 最大值
constexpr auto source_min = std::numeric_limits<From>::min(); // 最小值
constexpr auto source_max = std::numeric_limits<From>::max(); // 最大值
// 如果源和目标类型相同,直接返回
if constexpr (std::is_same_v<From, To>) {
return value;
}
// 如果是整形
// 如果源和目标都是整数,使用整数到整数的转换
if constexpr (std::is_integral_v<From> && std::is_integral_v<To>) {
return convert<From, To>(value);
}
// 如果是浮点型, 将value映射到 [0, 1] 区间
auto a = To(value - source_min);
auto b = To(source_max - source_min);
auto result = a / b;
return result;
// 如果是整数到浮点数的映射
if constexpr (std::is_integral_v<From> && std::is_floating_point_v<To>) {
const auto source_min = static_cast<double>(std::numeric_limits<From>::lowest()); // 整数最小值
const auto source_max = static_cast<double>(std::numeric_limits<From>::max()); // 整数最大值
// 将整数值映射到 [0, 1] 区间
double normalized = (static_cast<double>(value) - source_min) / (source_max - source_min);
return static_cast<To>(normalized); // 映射到浮点数
}
// 如果是浮点数到整数的映射
if constexpr (std::is_floating_point_v<From> && std::is_integral_v<To>) {
const auto target_min = static_cast<double>(std::numeric_limits<To>::lowest()); // 整数最小值
const auto target_max = static_cast<double>(std::numeric_limits<To>::max()); // 整数最大值
// 将浮点数从 [0, 1] 的区间映射到目标整数范围
double clamped = std::clamp(static_cast<double>(value), 0.0, 1.0); // 限制到 [0, 1]
return static_cast<To>(target_min + clamped * (target_max - target_min));
}
// 如果是浮点数到浮点数的映射
if constexpr (std::is_floating_point_v<From> && std::is_floating_point_v<To>) {
const auto source_min = static_cast<double>(std::numeric_limits<From>::lowest());
const auto source_max = static_cast<double>(std::numeric_limits<From>::max());
const auto target_min = static_cast<double>(std::numeric_limits<To>::lowest());
const auto target_max = static_cast<double>(std::numeric_limits<To>::max());
// 线性映射
double normalized = (static_cast<double>(value) - source_min) / (source_max - source_min); // [0, 1]
return static_cast<To>(target_min + normalized * (target_max - target_min));
}
// 默认情况(应该不会触发)
return static_cast<To>(value);
}
template<typename T, int N>
@ -55,7 +76,11 @@ struct pixel {
using pixel_type = T;
pixel() = default;
pixel(const pixel& rhs) = default;
pixel(const pixel& rhs) {
for (int i = 0; i < N; i++) {
data[i] = rhs.data[i];
}
}
// 从不同类型的像素转换
template<typename U, int N2>
pixel(const pixel<U, N2>& rhs) {
@ -157,9 +182,17 @@ struct pixel {
auto operator=(const pixel<U, N2>& rhs) {
constexpr int Num = std::min(N, N2);
memset(data, 0, sizeof(data));
for (int i = 0; i < Num; ++i) { data[i] = color_convert<U, T>(rhs.data[i]); }
for (int i = 0; i < Num; ++i) {
data[i] = color_convert<U, T>(rhs.data[i]);
}
return *this;
}
template<typename U, int N2>
operator pixel() {
pixel<U, N2> result = *this;
return result;
}
};
// 图像访问器
@ -168,11 +201,11 @@ struct image_accessor {
using pixel_type = typename P::pixel_type;
image_accessor(void* in_data, const int in_width, const int in_height)
: data(in_data), width(in_width), height(in_height), row_pitch(in_width) {}
: data(in_data), width(in_width), height(in_height), row_pitch(in_width * sizeof(P)) {}
template<typename T>
image_accessor(void* in_data, const Eigen::Vector2<T>& in_size)
: data(in_data), width(in_size.x()), height(in_size.y()), row_pitch(in_size.x()) {
: data(in_data), width(in_size.x()), height(in_size.y()), row_pitch(in_size.x() * sizeof(P)) {
}
/**
@ -184,9 +217,19 @@ struct image_accessor {
void copy_from(const image_accessor<T>& rhs) {
const int temp_width = std::min(width, rhs.width);
const int temp_height = std::min(height, rhs.height);
for (int y = 0; y < temp_height; ++y) {
for (int x = 0; x < temp_width; ++x) {
get_pixel(x, y) = rhs.get_pixel(x, y);
if constexpr (std::is_same<T, float>::value) {
auto row_size = std::min(temp_width * sizeof(T), row_pitch);
for (int y = 0; y < temp_height; ++y) {
auto src_row = rhs.get_row(y);
auto dst_row = get_row(y);
memcpy(dst_row, data, row_size);
}
}
else {
for (int y = 0; y < temp_height; ++y) {
for (int x = 0; x < temp_width; ++x) {
get_pixel(x, y) = rhs.get_pixel(x, y);
}
}
}
}
@ -218,9 +261,18 @@ struct image_accessor {
void offset_copy_from(const image_accessor<T>& rhs, const Eigen::Vector2i& in_start) {
const int temp_width = std::min(width - in_start.x(), rhs.width);
const int temp_height = std::min(height - in_start.y(), rhs.height);
for (int y = 0; y < temp_height; ++y) {
for (int x = 0; x < temp_width; ++x) {
get_pixel(x + in_start.x(), y + in_start.y()) = rhs.get_pixel(x, y);
if constexpr (std::is_same<P, T>::value) {
for (int y = 0; y < temp_height; ++y) {
auto src_row = rhs.get_row(y);
auto dst_row = get_row(y + in_start.y()) + in_start.x() * sizeof(T);
memcpy(dst_row, src_row, temp_width * sizeof(T));
}
}
else {
for (int y = 0; y < temp_height; ++y) {
for (int x = 0; x < temp_width; ++x) {
get_pixel(x + in_start.x(), y + in_start.y()) = rhs.get_pixel(x, y);
}
}
}
}
@ -256,11 +308,29 @@ struct image_accessor {
assert(in_row_pitch >= width);
row_pitch = in_row_pitch;
}
void flip_y() {
std::vector<uint8_t> temp_row(row_pitch); // 临时存储一行数据
for (size_t y = 0; y < height / 2; ++y) {
// 计算要交换的两行的起始地址
uint8_t* top_row = (uint8_t*)data + y * row_pitch;
uint8_t* bottom_row = (uint8_t*)data + (height - 1 - y) * row_pitch;
// 交换两行数据
std::memcpy(temp_row.data(), top_row, row_pitch); // 将 top_row 保存到临时缓冲区
std::memcpy(top_row, bottom_row, row_pitch); // 将 bottom_row 移动到 top_row
std::memcpy(bottom_row, temp_row.data(), row_pitch); // 将临时缓冲区的数据移动到 bottom_row
}
}
void* const data;
const int width;
const int height;
int row_pitch; // 行跨度,支持对齐
private:
uint8_t* get_row(const int y) const {
return (uint8_t*)data + y * row_pitch;
}
};
using pixel_r8 = pixel<uint8_t, 1>;

View File

@ -7,6 +7,7 @@
#define STB_IMAGE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include "renderer_text.h"
#include "renderer_texture_array.h"
#include "stb_image.h"
#if DX_BACKEND
@ -24,6 +25,8 @@ void renderer::tick() {
void renderer::destroy_texture(renderer_texture* texture) { delete texture; }
void renderer::destroy_texture_array(renderer_texture_array* texture_array) { delete texture_array; }
renderer_texture* renderer::load_image(const std::string& file_path, texture_format in_format) {
int width, height, channels;
const auto result = stbi_load(file_path.c_str(), &width, &height, &channels, 0);
@ -88,7 +91,6 @@ renderer* aorii::get_renderer_raw() {
bool aorii::create_renderer(renderer_api api) {
if (!aorii_text::init_freetype())
return false;
aorii_text::load_font("C:/Windows/Fonts/simsun.ttc", false, 48);
begin_time = std::chrono::high_resolution_clock::now();
if (s_renderer) return true;
switch (api) {

View File

@ -6,6 +6,8 @@
#include "renderer_buffer.h"
#include "core/pipeline/pipeline.h"
class renderer_texture_array;
class sdf_text_pipeline;
class segment_pipeline;
class texture_pipeline;
class rounded_rect_pipeline;
@ -222,8 +224,14 @@ public:
renderer_texture* create_texture(const Eigen::Vector2<T>& in_size, texture_format in_format) {
return create_texture(in_size.x(), in_size.y(), in_format);
}
virtual renderer_texture* create_texture(int in_size_width, int in_size_height, texture_format in_format) = 0;
virtual renderer_texture* create_texture(uint32_t in_size_width, uint32_t in_size_height, texture_format in_format) = 0;
virtual void destroy_texture(renderer_texture* texture);
template<typename T>
renderer_texture_array* create_texture_array(const Eigen::Vector2<T>& in_size, const int in_count, const texture_format in_format) {
return create_texture_array(in_size.x(), in_size.y(), in_count, in_format);
}
virtual renderer_texture_array* create_texture_array(uint32_t in_size_width, uint32_t in_size_height, uint32_t in_count, texture_format in_format) = 0;
virtual void destroy_texture_array(renderer_texture_array* texture_array);
renderer_texture* load_image(const std::string& file_path, texture_format in_format);
@ -247,6 +255,7 @@ public:
virtual rounded_rect_pipeline* get_rounded_rect_pipeline() = 0;
virtual texture_pipeline* get_texture_pipeline() = 0;
virtual segment_pipeline* get_segment_pipeline() = 0;
virtual sdf_text_pipeline* get_text_pipeline() = 0;
virtual Eigen::Matrix4f make_projection_matrix(const Eigen::Vector2i& size) = 0;
private:

View File

@ -1,9 +1,14 @@
#include "renderer_context.h"
#include "renderer.h"
#include "renderer_text.h"
#include "backend/dx/dx_renderer.h"
aorii_text* text = nullptr;
void renderer_context::init() {
text = new aorii_text();
text->initialize(LR"(C:\Windows\Fonts\msyh.ttc)", 32);
text->precache_common_characters();
}
void renderer_context::draw_rectangle(const Eigen::Vector2f& in_pos, const Eigen::Vector2f& in_size, const linear_color& in_color) {
@ -34,6 +39,31 @@ void renderer_context::draw_texture(const Eigen::Vector2f& in_pos, const Eigen::
flush();
}
void renderer_context::draw_string(const Eigen::Vector2f& in_pos, const std::wstring& in_str, float in_height,
const linear_color& in_color) {
to_text_pipeline(in_color);
float cursor_x = in_pos.x();
const float scale = in_height / 32;
for (const auto c : in_str) {
character_info info{};
text->get_or_create_character(c, info);
// 根据in_height缩放字符大小
const Eigen::Vector2f size = { info.width * scale, info.height * scale };
const Eigen::Vector2f pos = { cursor_x, in_pos.y() - info.height };
cursor_x += size.x() + info.offset_x;
aorii_vertex_param param{};
param.param_a1 = info.tex_u;
param.param_a2 = info.tex_v;
param.param_a3 = info.tex_z;
param.param_b1 = info.width;
param.param_b2 = info.height;
make_rect(pos, size, in_color, 0, param);
}
}
void renderer_context::set_projection_matrix(const Eigen::Matrix4f& in_matrix, const Eigen::Vector2i& in_framebuffer_size) {
projection_matrix = in_matrix;
framebuffer_size = in_framebuffer_size;
@ -58,7 +88,6 @@ void renderer_context::to_rounded_rect_pipeline(const Eigen::Vector2f& in_pos, c
const auto rounded_rect_p = aorii::get_renderer<dx_renderer>()->get_rounded_rect_pipeline();
switch_pipeline(rounded_rect_p);
rounded_rect_pipeline::param param;
param.projection_matrix = projection_matrix;
param.size = in_size;
@ -85,12 +114,30 @@ void renderer_context::to_segment_pipeline(const Eigen::Vector2f& in_pos_p1, con
segment_p->set_param(param);
}
void renderer_context::to_text_pipeline(const linear_color& in_color) {
const auto text_p = aorii::get_renderer<dx_renderer>()->get_text_pipeline();
switch_pipeline(text_p);
sdf_text_pipeline::param param;
param.projection_matrix = projection_matrix;
text_p->set_param(param);
sdf_text_pipeline::sdf_font_param font_param;
font_param.smoothing = 1;
font_param.thickness = 1;
font_param.outline_width = 0;
font_param.outline_color = in_color;
text_p->set_sdf_font_param(font_param);
text_p->set_glyph_texture(text->get_texture_array());
}
void renderer_context::make_rect(const Eigen::Vector2f& in_pos, const Eigen::Vector2f& in_size,
const linear_color& in_color, float in_angle) {
aorii_vertex v1{ { in_pos.x(), in_pos.y() }, { 0, 0 }, in_color }; // 左上角
aorii_vertex v2{ { in_pos.x() + in_size.x(), in_pos.y() }, { 1, 0 }, in_color }; // 右上角
aorii_vertex v3{ { in_pos.x(), in_pos.y() + in_size.y() }, { 0, 1 }, in_color }; // 左下角
aorii_vertex v4{ { in_pos.x() + in_size.x(), in_pos.y() + in_size.y() }, { 1, 1 }, in_color }; // 右下角
const linear_color& in_color, float in_angle, const aorii_vertex_param& in_param) {
aorii_vertex v1{ { in_pos.x(), in_pos.y() }, { 0, 0 }, in_color, in_param }; // 左上角
aorii_vertex v2{ { in_pos.x() + in_size.x(), in_pos.y() }, { 1, 0 }, in_color, in_param }; // 右上角
aorii_vertex v3{ { in_pos.x(), in_pos.y() + in_size.y() }, { 0, 1 }, in_color, in_param }; // 左下角
aorii_vertex v4{ { in_pos.x() + in_size.x(), in_pos.y() + in_size.y() }, { 1, 1 }, in_color, in_param }; // 右下角
if (in_angle != 0) {
auto center = in_pos + in_size / 2;

View File

@ -60,6 +60,8 @@ public:
*/
void draw_texture(const Eigen::Vector2f& in_pos, const Eigen::Vector2f& in_size, renderer_texture* in_texture, const linear_color& in_color = linear_color::white);
void draw_string(const Eigen::Vector2f& in_pos, const std::wstring& in_str, float in_height, const linear_color& in_color = linear_color::white);
void clear() {
vertices.clear();
triangles.clear();
@ -98,6 +100,7 @@ protected:
void to_rounded_rect_pipeline(const Eigen::Vector2f& in_pos, const Eigen::Vector2f& in_size, const rect_radius& in_radius);
void to_texture_pipeline(renderer_texture* in_texture);
void to_segment_pipeline(const Eigen::Vector2f& in_pos_p1, const Eigen::Vector2f& in_pos_p2, float in_thickness);
void to_text_pipeline(const linear_color& in_color);
private:
std::vector<aorii_vertex> vertices;
std::vector<aorii_triangle> triangles;
@ -106,6 +109,6 @@ private:
Eigen::Matrix4f projection_matrix;
Eigen::Vector2i framebuffer_size;
private:
void make_rect(const Eigen::Vector2f& in_pos, const Eigen::Vector2f& in_size, const linear_color& in_color, float in_angle = 0);
void make_rect(const Eigen::Vector2f& in_pos, const Eigen::Vector2f& in_size, const linear_color& in_color, float in_angle = 0, const aorii_vertex_param& in_param = {});
void rotate_vertex(float in_angle, const Eigen::Vector2f& in_center);
};

View File

@ -1,296 +1,228 @@
#include "renderer_text.h"
#include <future>
#include <utility>
#include <spdlog/spdlog.h>
text_font::~text_font() {
for (auto& [glyph, character] : glyphs) {
std::free(character.buffer);
}
glyphs.clear();
assert(atlas == nullptr);
}
#include "renderer.h"
#include "renderer_texture_array.h"
text_atlas::text_atlas(const Eigen::Vector2i& in_size, std::function<void(text_atlas* atlas)> in_update_function) {
update_function = std::move(in_update_function);
size = in_size;
data = new uint8_t[size.x() * size.y()];
memset(data, 0, size.x() * size.y());
slices.emplace_back(0, size.y());
}
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb_truetype.h>
text_atlas::~text_atlas() {
delete[] data;
slices.clear();
}
// 在SDFFontCache.cpp中添加实现
const std::wstring aorii_text::COMMON_ASCII =
L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
bool text_atlas::add_font(text_font* in_font) {
if (in_font->atlas_rect_height > size.y()) {
spdlog::error("字体超过图集大小! 通过配置增加最大图集大小");
return false;
}
const std::wstring aorii_text::COMMON_CHINESE =
L"的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严";
in_font->atlas = this;
uint32_t best_slice_diff = size.y();
slice* best_slice = nullptr;
const std::wstring aorii_text::COMMON_PUNCTUATION =
L",。!?:;''""《》【】()、";
for (auto& slice : slices) {
if (slice.height < in_font->atlas_rect_height) {
continue;
}
const std::wstring aorii_text::COMMON_NUMBERS =
L"0123456789";
const uint32_t diff = slice.height - in_font->atlas_rect_height;
if (diff < best_slice_diff) {
best_slice_diff = diff;
best_slice = &slice;
}
}
if (best_slice == nullptr) {
spdlog::error("无法找到合适的切片");
return false;
}
in_font->atlas_rect_pos = best_slice->pos;
uint32_t start_x = 0;
uint32_t start_y = best_slice->pos;
uint32_t max_height = 0;
const Eigen::Vector2f& temp_size = size.cast<float>();
for (auto& [glyph, character] : in_font->glyphs) {
const auto& sz = character.size.cast<uint32_t>();
if (start_x + sz.x() >= size.x()) {
start_x = 0;
start_y += max_height;
max_height = 0;
}
const auto uv1x = start_x / temp_size.x();
const auto uv1y = start_y / temp_size.y();
const auto uv2x = (start_x + character.size.x()) / temp_size.x();
const auto uv2y = start_y / temp_size.y();
const auto uv3y = (start_y + character.size.y()) / temp_size.y();
const auto uv4y = (start_y + character.size.y()) / temp_size.y();
const Eigen::Vector2f uv1{uv1x, uv1y};
const Eigen::Vector2f uv2{uv2x, uv2y};
const Eigen::Vector2f uv3{uv2x, uv3y};
const Eigen::Vector2f uv4{uv1x, uv4y};
character.uv12 = {uv1.x(), uv1.y(), uv2.x(), uv2.y()};
character.uv34 = {uv3.x(), uv3.y(), uv4.x(), uv4.y()};
uint32_t start_offset = start_y * size.x() + start_x;
const auto width = sz.x();
for (uint32_t row = 0; row < sz.y(); row++) {
std::memcpy(data + start_offset, &character.buffer[row * width], width);
start_offset += size.x();
}
max_height = std::max(max_height, sz.y());
start_x += sz.x() + 1;
}
if (best_slice->height > in_font->atlas_rect_height) {
slices.emplace_back(best_slice->pos + in_font->atlas_rect_height, best_slice->height - in_font->atlas_rect_height);
}
auto it = std::ranges::find_if(slices, [best_slice](const slice& s) {
return s.pos == best_slice->pos && s.height == best_slice->height;
});
slices.erase(it);
update_function(this);
return true;
}
void text_atlas::remove_font(uint32_t pos, uint32_t height) {
slices.emplace_back(pos, height);
const auto start = pos * size.x();
memset(data + start, 0, height * size.x());
update_function(this);
aorii_text::aorii_text(const uint32_t in_texture_size, const uint32_t in_char_size) : font(nullptr), texture_array(nullptr),
texture_size(in_texture_size), char_size(in_char_size),
current_x(0), current_y(0), current_texture_index(0), scale(0) {
}
aorii_text::~aorii_text() {
for (auto* atlas : atlases)
delete atlas;
delete font;
}
bool aorii_text::init_freetype() {
if (FT_Init_FreeType(&library)) {
spdlog::error("无法初始化FreeType");
return false;
}
return true;
}
void aorii_text::destroy_freetype() {
FT_Done_FreeType(library);
}
text_font* aorii_text::load_font(const std::string& in_file, bool load_as_sdf, int size, glyph_encoding* custom_ranges,
int32_t custom_ranges_size, bool use_kerning_if_available) {
FT_Face face;
if (FT_New_Face(library, in_file.c_str(), 0, &face)) {
spdlog::error("无法加载字体: {}", in_file);
return nullptr;
bool aorii_text::initialize(const wchar_t* in_font_path, const float in_font_pixel_size) {
const std::string font_path_a(in_font_path, in_font_path + wcslen(in_font_path));
font = new stbtt_fontinfo();
// 加载字体文件
FILE* font_file = nullptr;
fopen_s(&font_file, font_path_a.c_str(), "rb");
if (!font_file) {
printf("Failed to open font file\n");
return false;
}
return setup_font(face, load_as_sdf, size, custom_ranges, custom_ranges_size, use_kerning_if_available);
fseek(font_file, 0, SEEK_END);
size_t font_size = ftell(font_file);
fseek(font_file, 0, SEEK_SET);
auto* font_buffer = static_cast<uint8_t*>(malloc(font_size));
fread(font_buffer, 1, font_size, font_file);
fclose(font_file);
if (!stbtt_InitFont(font, font_buffer, stbtt_GetFontOffsetForIndex(font_buffer, 0))) {
spdlog::error("Failed to initialize font");
free(font_buffer);
delete font;
font = nullptr;
return false;
}
scale = stbtt_ScaleForPixelHeight(font, in_font_pixel_size);
// 获取字体的垂直度量
int ascent, descent, lineGap;
stbtt_GetFontVMetrics(font, &ascent, &descent, &lineGap);
spdlog::info("Font vertical metrics:\n Ascent: {}, Descent: {}, LineGap: {}", ascent, descent, lineGap);
// Create initial texture atlas
return create_new_texture_atlas();
}
text_font* aorii_text::setup_font(FT_Face& in_face, bool load_as_sdf, int size, glyph_encoding* custom_ranges,
int32_t custom_ranges_size, bool use_kerning_if_available) {
FT_Error err = FT_Set_Pixel_Sizes(in_face, 0, size);
if (err) {
spdlog::error("无法设置字体大小: {}", size);
return nullptr;
}
err = FT_Select_Charmap(in_face, FT_ENCODING_UNICODE);
if (err) {
spdlog::error("无法选择字体编码");
return nullptr;
}
bool aorii_text::create_new_texture_atlas() {
// 每张纹理包含的字符数量
const uint32_t count_per_texture = (texture_size / char_size) * (texture_size / char_size);
const uint32_t font_count = get_font_char_count();
// 预计需要的纹理数量
const uint32_t expected_textures = (font_count + count_per_texture - 1) / count_per_texture;
texture_array = aorii::get_renderer_raw()->create_texture_array(texture_size, texture_size, expected_textures, texture_format::R8_UNORM);
return texture_array != nullptr;
}
text_font* font = new text_font();
font->size = size;
font->is_sdf = load_as_sdf;
font->supports_unicode = custom_ranges != nullptr;
font->supports_kerning = use_kerning_if_available && FT_HAS_KERNING(in_face) != 0;
font->new_line_height = in_face->size->metrics.height / 64.0f;
auto& character_map = font->glyphs;
auto slot = in_face->glyph;
uint32_t size_ctr_x = 0;
uint32_t size_ctr_y = 0;
auto set_sizes = [&](uint32_t c) {
const auto i = FT_Get_Char_Index(in_face, c);
if (i == 0) return true;
err = FT_Load_Glyph(in_face, i, FT_LOAD_DEFAULT);
text_character& ch = character_map[c];
if (err) {
spdlog::error("无法加载字形: {}", c);
return false;
}
if (load_as_sdf)
err = FT_Render_Glyph(slot, FT_RENDER_MODE_SDF);
else
err = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
if (err) {
spdlog::error("无法渲染字形: {}", c);
return false;
}
const uint32_t glyph_width = slot->bitmap.width;
const uint32_t glyph_rows = slot->bitmap.rows;
const auto buffer_size = glyph_width * glyph_rows;
if (slot->bitmap.buffer) {
ch.buffer = static_cast<uint8_t*>(std::malloc(buffer_size));
if (ch.buffer) {
memcpy(ch.buffer, slot->bitmap.buffer, buffer_size);
}
}
ch.size = {glyph_width, glyph_rows};
ch.bearing = {slot->bitmap_left, slot->bitmap_top};
ch.advance = {slot->advance.x >> 6, slot->advance.y >> 6};
size_ctr_y = std::max(size_ctr_y, glyph_rows);
if (size_ctr_x + glyph_width >= max_font_atlas_size) {
size_ctr_x = 0;
font->atlas_rect_height += size_ctr_y + 1;
}
size_ctr_x += glyph_width + 1;
bool aorii_text::get_or_create_character(const wchar_t ch, character_info& out_info) {
if (const auto it = character_map.find(ch); it != character_map.end()) {
out_info = it->second;
return true;
};
auto store_kerning = [&](uint32_t first, uint32_t second) {
auto i = FT_Get_Char_Index(in_face, first);
auto j = FT_Get_Char_Index(in_face, second);
FT_Vector delta;
err = FT_Get_Kerning(in_face, i, j, FT_KERNING_DEFAULT, &delta);
if (err) {
spdlog::error("无法获取字距: {} {}", first, second);
}
font->kerning_table[first].x_advances[second] = delta.x;
};
for (uint32_t c = 32; c < 128; c++) {
set_sizes(c);
if (font->supports_kerning) {
for (uint32_t a = 32; a < c; a++) {
store_kerning(a, c);
}
}
}
bool use_custom_ranges = custom_ranges_size != 0;
if (custom_ranges_size % 2 == 1) {
use_custom_ranges = false;
if (custom_ranges) {
spdlog::error("自定义范围大小必须是2的倍数");
}
// 加载目标字符(以 'A' 为例)
int glyph_index = stbtt_FindGlyphIndex(font, ch);
if (glyph_index == 0) {
printf("Failed to find glyph for character\n");
return false;
}
if (use_custom_ranges) {
int32_t index = 0;
const int32_t custom_range_count = custom_ranges_size / 2;
for (int32_t i = 0; i < custom_range_count; i++) {
if (custom_ranges[index] == custom_ranges[index + 1]) {
set_sizes(custom_ranges[index]);
} else {
for (uint32_t c = custom_ranges[index]; c < custom_ranges[index + 1]; c++) {
set_sizes(c);
}
}
index += 2;
}
}
font->atlas_rect_height += size_ctr_y + 1;
font->space_advance = character_map[' '].advance.x();
err = FT_Done_Face(in_face);
if (err) {
spdlog::error("无法释放字体");
}
return font;
// Try to add to current atlas
out_info = add_character_to_atlas(glyph_index, ch);
return true;
}
void aorii_text::add_font_to_atlas(text_font* in_font) {
text_atlas* found_atlas = nullptr;
for (auto& atlas : atlases) {
if (atlas->add_font(in_font)) {
found_atlas = atlas;
break;
bool aorii_text::precache_characters(const std::wstring& characters) {
if (!font) {
spdlog::error("Font not initialized");
return false;
}
// 创建进度通知回调的函数指针类型
using ProgressCallback = std::function<void(float progress, const wchar_t currentChar)>;
bool success = true;
// 使用线程池并行生成SDF数据
const size_t max_threads = std::thread::hardware_concurrency();
std::vector<std::future<std::vector<uint8_t>>> futures;
std::mutex map_mutex;
for (size_t i = 0; i < characters.length(); i++) {
wchar_t ch = characters[i];
// 检查字符是否已经缓存
if (character_map.contains(ch)) {
continue;
}
}
if (found_atlas)
return;
auto* new_atlas = new text_atlas({max_font_atlas_size, max_font_atlas_size}, update_function);
// 加载目标字符(以 'A' 为例)
int glyph_index = stbtt_FindGlyphIndex(font, ch);
if (glyph_index == 0) {
continue;
}
if (!new_atlas->add_font(in_font)) {
spdlog::error("无法添加字体到图集");
delete new_atlas;
return;
// 生成SDF数据
add_character_to_atlas(glyph_index, ch);
}
atlases.push_back(new_atlas);
return success;
}
void aorii_text::remove_font_from_atlas(text_font* in_font) {
if (in_font->atlas)
return;
in_font->atlas->remove_font(in_font->atlas_rect_pos, in_font->atlas_rect_height);
in_font->atlas = nullptr;
bool aorii_text::precache_common_characters() {
std::wstring all_common_chars;
all_common_chars.reserve(COMMON_ASCII.length() +
COMMON_CHINESE.length() +
COMMON_PUNCTUATION.length() +
COMMON_NUMBERS.length());
all_common_chars += COMMON_ASCII;
all_common_chars += COMMON_CHINESE;
all_common_chars += COMMON_PUNCTUATION;
all_common_chars += COMMON_NUMBERS;
return precache_characters(all_common_chars);
}
float aorii_text::get_atlas_utilization() const {
const size_t total_slots = (texture_size / char_size) * (texture_size / char_size) * (1 + current_texture_index);
return static_cast<float>(character_map.size()) / total_slots;
}
uint32_t aorii_text::get_font_char_count() const { return font->numGlyphs; }
character_info& aorii_text::add_character_to_atlas(int32_t glyph_index, const wchar_t ch) {
// Check if we need to move to next row
if (current_x + char_size > texture_size) {
current_x = 0;
current_y += char_size;
}
// Check if we have room in current texture
if (current_y + char_size > texture_size) {
current_texture_index++;
current_x = 0;
current_y = 0;
}
int32_t width, height, x_offset, y_offset;
// 生成 SDF
constexpr int padding = 8; // 距离场的扩展范围
constexpr uint8_t on_edge_value = 128; // 边界处的值(通常为 128
constexpr float pixel_dist_scale = 64.0f / (float)padding; // 控制距离场的缩放比例
const auto* sdf_bitmap = stbtt_GetGlyphSDF(
font, // 字体信息
scale, // 缩放
glyph_index, // 字形索引
padding, // 间距
on_edge_value, // 距离场边界值
pixel_dist_scale, // 距离缩放
&width, &height, // 位图尺寸和填充
&x_offset, &y_offset // 字形偏移
);
// Update texture
Eigen::AlignedBox2i box = {};
box.min().x() = current_x;
box.min().y() = current_y;
box.max().x() = current_x + width;
box.max().y() = current_y + height;
texture_array->update_subresource(current_texture_index, box, sdf_bitmap, width);
// 获取字形的水平度量
int advance_width, left_side_bearing;
stbtt_GetGlyphHMetrics(font, glyph_index, &advance_width, &left_side_bearing);
// Store character info
character_info info = {};
info.tex_u = current_x / static_cast<float>(texture_size);
info.tex_v = current_y / static_cast<float>(texture_size);
info.tex_z = current_texture_index;
info.advance = advance_width * scale;
info.width = width;
info.height = height;
info.offset_x = x_offset;
info.offset_y = y_offset;
// Update current position
current_x += char_size;
return character_map[ch] = info;
}

View File

@ -1,92 +1,67 @@
#pragma once
// from LinaVG
#pragma once
#include <freetype/freetype.h>
#include <Eigen/Eigen>
class text_atlas;
using glyph_encoding = FT_ULong;
#include "core/pixel_format/pixel.h"
struct text_character {
Eigen::Vector4f uv12;
Eigen::Vector4f uv34;
Eigen::Vector2f size;
Eigen::Vector2f bearing;
Eigen::Vector2f advance;
float ascent;
float descent;
#include <unordered_map>
uint8_t* buffer = nullptr;
};
class renderer_texture_array;
struct stbtt_fontinfo;
struct kerning_information {
std::unordered_map<unsigned long, unsigned long> x_advances;
};
class text_font {
public:
~text_font();
int32_t size = 0;
float new_line_height = 0.0f;
float space_advance = 0.0f;
bool supports_unicode = false;
bool is_sdf = false;
bool supports_kerning = false;
uint32_t atlas_rect_height = 0;
uint32_t atlas_rect_pos = 0;
text_atlas* atlas = nullptr;
std::unordered_map<glyph_encoding, text_character> glyphs;
std::unordered_map<uint32_t, kerning_information> kerning_table;
};
class text_atlas {
public:
struct slice {
slice(const uint32_t pos, const uint32_t height) : pos(pos), height(height) {};
uint32_t pos = 0;
uint32_t height = 0;
};
text_atlas(const Eigen::Vector2i& in_size, std::function<void(text_atlas* atlas)> in_update_function);
~text_atlas();
bool add_font(text_font* in_font);
void remove_font(uint32_t pos, uint32_t height);
[[nodiscard]] const auto& get_size() const { return size; }
[[nodiscard]] auto get_data() const { return data; }
private:
std::function<void(text_atlas* atlas)> update_function;
std::vector<slice> slices;
Eigen::Vector2i size;
uint8_t* data;
struct character_info {
float tex_u; // X position in texture
float tex_v; // Y position in texture
uint8_t tex_z; // Z position in texture array
uint8_t width; // Character width
uint8_t height; // Character height
float advance; // Advance width
int8_t offset_x; // Offset from baseline to left
int8_t offset_y; // Offset from baseline to top
};
class aorii_text {
public:
aorii_text(uint32_t in_texture_size = 2048, uint32_t in_char_size = 32);
~aorii_text();
static bool init_freetype();
static void destroy_freetype();
static text_font* load_font(const std::string& in_file, bool load_as_sdf, int size = 48, glyph_encoding* custom_ranges = nullptr, int32_t custom_ranges_size = 0, bool use_kerning_if_available = true);
static text_font* setup_font(FT_Face& in_face, bool load_as_sdf, int size, glyph_encoding* custom_ranges, int32_t custom_ranges_size, bool use_kerning_if_available);
void add_font_to_atlas(text_font* in_font);
void remove_font_from_atlas(text_font* in_font);
bool initialize(const wchar_t* in_font_path, float in_font_pixel_size);
bool get_or_create_character(wchar_t ch, character_info& out_info);
inline static float global_aa_multiplier = 1.0f;
inline static float miter_limit = 150.f;
inline static uint32_t max_font_atlas_size = 768;
inline static int32_t gc_collect_interval = 600;
inline static int32_t default_buffer_reserve = 50;
inline static bool text_caching_enabled = false;
inline static int32_t text_cache_reserve = 300;
inline static int32_t text_cache_expire_interval = 3000;
// 预缓存一组字符
bool precache_characters(const std::wstring& characters);
// 预缓存常用字符集
bool precache_common_characters();
// 获取缓存状态
[[nodiscard]] size_t get_cached_character_count() const { return character_map.size(); }
[[nodiscard]] float get_atlas_utilization() const;
// 获取字体字符数量
[[nodiscard]] uint32_t get_font_char_count() const;
[[nodiscard]] renderer_texture_array* get_texture_array() const { return texture_array; }
private:
inline static FT_Library library = nullptr;
bool create_new_texture_atlas();
character_info& add_character_to_atlas(int32_t glyph_index, const wchar_t ch);
stbtt_fontinfo* font;
std::vector<text_atlas*> atlases;
std::function<void(text_atlas*)> update_function;
renderer_texture_array* texture_array;
const uint32_t texture_size;
const uint32_t char_size;
uint32_t current_x;
uint32_t current_y;
uint8_t current_texture_index;
float scale;
std::unordered_map<wchar_t, character_info> character_map;
// 常用字符集定义
static const std::wstring COMMON_ASCII; // ASCII字符
static const std::wstring COMMON_CHINESE; // 常用汉字
static const std::wstring COMMON_PUNCTUATION; // 常用标点符号
static const std::wstring COMMON_NUMBERS; // 数字
};

View File

@ -1,16 +1,19 @@
#pragma once
#include <Eigen/Eigen>
#include <vector>
#include "renderer.h"
class renderer_texture {
public:
renderer_texture(texture_format in_format) : format(in_format) {}
explicit renderer_texture(const texture_format in_format) : format(in_format) {}
virtual ~renderer_texture() = default;
virtual void* lock(unsigned int* out_row_pitch) = 0;
virtual void* lock(uint32_t* out_row_pitch) = 0;
virtual void unlock() = 0;
virtual void update_subresource(const Eigen::AlignedBox2i& in_area, const void* in_buffer, uint32_t in_row_pitch) = 0;
virtual bool resize(const Eigen::Vector2i& size) = 0;
virtual Eigen::Vector2i size() = 0;

View File

@ -0,0 +1 @@
#include "renderer_texture_array.h"

View File

@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
#include "renderer.h"
class renderer_texture_array {
public:
explicit renderer_texture_array(const texture_format in_format) : format(in_format) {}
virtual ~renderer_texture_array() = default;
virtual void* lock(const uint32_t index, uint32_t* out_row_pitch) = 0;
virtual void unlock(const uint32_t index) = 0;
virtual void update_subresource(const uint32_t index, const Eigen::AlignedBox2i& in_area, const void* in_buffer, uint32_t in_buffer_size) = 0;
void set_count(const uint32_t in_count) {
if (in_count > MAX_TEXTURE_SIZE) {
throw std::runtime_error("Texture array size is too large.");
}
if (in_count == 0) {
throw std::runtime_error("Texture array size must be greater than 0.");
}
if (in_count == get_count()) {
return;
}
on_set_count(in_count);
}
virtual bool resize(const Eigen::Vector2i& size) = 0;
virtual Eigen::Vector2i size() = 0;
virtual void* get_native_handle() = 0;
virtual void* get_shader_resource_view() = 0;
[[nodiscard]] virtual uint32_t get_count() = 0;
[[nodiscard]] texture_format get_format() const { return format; }
protected:
virtual void on_set_count(uint32_t in_count) = 0;
private:
const uint32_t MAX_TEXTURE_SIZE = 256;
const texture_format format;
};

View File

@ -3,6 +3,8 @@
class linear_color {
public:
linear_color() : r(1), g(1), b(1), a(1) {
}
linear_color(float in_r, float in_g, float in_b, float in_a = 1.0f) : r(in_r), g(in_g), b(in_b), a(in_a) {}
static linear_color from_srgb(float in_r, float in_g, float in_b, float in_a = 1.0f) {

View File

@ -0,0 +1,157 @@
// Push constants block
cbuffer PushConstants : register(b0)
{
float2 window_extent;
float2 viewport_scale;
float2 red_subpixel_orientation;
float2 blue_subpixel_orientation;
bool has_subpixels;
}
// Specialization constants
static const float sdf_max_distance : SPECIALIZE_CONSTANT = 1.0;
static const float atlas_image_width : SPECIALIZE_CONSTANT = 1.0;
// Sampler and textures
// Texture2D in_textures[128] : register(t0);
Texture2DArray in_textures_array : register(t0);
SamplerState in_sampler : register(s0);
// Input structure for vertex shader
struct VSInput
{
float3 in_position : POSITION;
float4 in_clipping_rectangle : TEXCOORD0;
float3 in_texture_coord : TEXCOORD1;
float4 in_color : TEXCOORD2;
};
// Output structure for vertex shader (input for pixel shader)
struct VSOutput
{
float4 gl_Position : SV_POSITION;
float4 out_clipping_rectangle : TEXCOORD0;
float3 out_texture_coord : TEXCOORD1;
float4 out_color : TEXCOORD2;
float4 out_color_sqrt_rgby : TEXCOORD3;
};
// Output structure for pixel shader
struct PSOutput
{
float4 out_color : SV_Target0;
float4 out_blend_factor : SV_Target1;
};
// Include utility functions (you need to provide the utility functions from utils_vulkan.glsl)
#include "utils_vulkan.slang"
// ---- Utility Functions (Shared between Vertex and Fragment) ----
// Function to convert position to viewport space
float4 convert_position_to_viewport(float3 window_position)
{
float x = window_position.x * viewport_scale.x - 1.0;
float y = (window_extent.y - window_position.y) * viewport_scale.y - 1.0;
return float4(x, y, 1.0 - window_position.z * 0.01, 1.0);
}
// Function to convert clipping rectangle to screen space
float4 convert_clipping_rectangle_to_screen(float4 clipping_rectangle)
{
return float4(
clipping_rectangle.x,
window_extent.y - clipping_rectangle.w,
clipping_rectangle.z,
window_extent.y - clipping_rectangle.y
);
}
// Function to calculate texture stride
float4 get_texture_stride(VSOutput input)
{
float2 horizontal_texture_stride = ddx(input.out_texture_coord.xy);
float2 vertical_texture_stride = ddy(input.out_texture_coord.xy);
return float4(horizontal_texture_stride, vertical_texture_stride);
}
float2 green_coord(float4 texture_stride, float2 coord)
{
return coord;
}
float2 red_coord(float4 texture_stride, float2 coord)
{
float4 tmp = texture_stride * float4(red_subpixel_orientation.x, red_subpixel_orientation.y, red_subpixel_orientation.x, red_subpixel_orientation.y);
return coord + tmp.xy + tmp.zw;
}
float2 blue_coord(float4 texture_stride, float2 coord)
{
float4 tmp = texture_stride * float4(blue_subpixel_orientation.x, blue_subpixel_orientation.y, blue_subpixel_orientation.x, blue_subpixel_orientation.y);
return coord + tmp.xy + tmp.zw;
}
// Calculate distance from sub-pixel to edge
float3 get_subpixel_to_edge_distances(VSOutput input)
{
const int image_nr = int(input.out_texture_coord.z);
float2 image_coord = input.out_texture_coord.xy;
float4 texture_stride = get_texture_stride(input);
// float green_distance = in_textures[image_nr].Sample(in_sampler, green_coord(texture_stride, image_coord)).r;
float green_distance = in_textures_array.Sample(in_sampler, float3(image_coord, image_nr)).r;
float3 distances = float3(green_distance, green_distance, green_distance);
if (has_subpixels) {
// distances.r = in_textures[image_nr].Sample(in_sampler, red_coord(texture_stride, image_coord)).r;
// distances.b = in_textures[image_nr].Sample(in_sampler, blue_coord(texture_stride, image_coord)).r;
distances.r = in_textures_array.Sample(in_sampler, float3(red_coord(texture_stride, image_coord), image_nr)).r;
distances.b = in_textures_array.Sample(in_sampler, float3(blue_coord(texture_stride, image_coord), image_nr)).r;
}
float pixel_distance = length(texture_stride.xy);
float distance_multiplier = sdf_max_distance / (pixel_distance * atlas_image_width);
return distances * distance_multiplier;
}
// ---- Vertex Shader ----
VSOutput vertex_main(VSInput input)
{
VSOutput output;
output.gl_Position = convert_position_to_viewport(input.in_position);
output.out_clipping_rectangle = convert_clipping_rectangle_to_screen(input.in_clipping_rectangle);
output.out_texture_coord = input.in_texture_coord;
float4 color = multiply_alpha(input.in_color);
output.out_color = color;
output.out_color_sqrt_rgby = sqrt(clamp(rgb_to_rgby(color.rgb), 0.0, 1.0));
return output;
}
// ---- Pixel Shader ----
PSOutput pixel_main(VSOutput input)
{
PSOutput output;
// Check if fragment is within the clipping rectangle
if (!contains(input.out_clipping_rectangle, input.gl_Position.xy)) {
discard;
}
float3 distances = get_subpixel_to_edge_distances(input);
float3 coverage = clamp(distances + 0.5, 0.0, 1.0);
if (all(coverage == float3(0.0, 0.0, 0.0))) {
discard;
}
float4 alpha = coverage_to_alpha(float4(coverage, coverage.g), input.out_color_sqrt_rgby);
output.out_color = float4(input.out_color * alpha);
output.out_blend_factor = input.out_color.a * alpha;
return output;
}

View File

@ -1,15 +1,11 @@
#include "aorii_util.slang"
struct ParamBuffer
{
matrix transform;
};
ParameterBlock<ParamBuffer> param_buffer : register(b0);
struct VSInput {
float2 position : POSITION; // 窗口坐标
float2 uv : TEXCOORD0; // 纹理坐标
float4 color : COLOR; // 颜色
};
struct PSInput {
float4 position : SV_POSITION; // 裁剪空间坐标
float4 color : COLOR; // 颜色

View File

@ -8,12 +8,6 @@ cbuffer ParamBuffer : register(b0)
float4 radius; // 四角圆角像素单位 左上 右上 左下 右下
};
struct VSInput {
float2 position : POSITION; // 窗口坐标
float2 uv : TEXCOORD0; // 纹理坐标
float4 color : COLOR; // 颜色
};
struct PSInput {
float4 position : SV_POSITION; // 裁剪空间坐标
float2 uv : TEXCOORD0; // 纹理坐标

View File

@ -1,13 +1,20 @@
struct VSInput {
float2 position : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR0;
};
#include "aorii_util.slang"
struct FontParams {
float smoothing; // 平滑度
float thickness; // 字体粗细
float outline_width; // 描边宽度
float4 outline_color; // 描边颜色
}
ParameterBlock<FontParams> font_param : register(b1);
struct PSInput {
float4 position : SV_Position;
float2 uv : TEXCOORD0;
float4 color : COLOR0;
float2 altas_uv : TEXCOORD1;
float altas_index : TEXCOORD2;
float2 char_size : TEXCOORD3;
};
struct Constants {
@ -20,37 +27,22 @@ PSInput vertex_main(VSInput input) {
output.position = mul(float4(input.position, 0.0f, 1.0f), param_buffer.transform);
output.uv = input.uv;
output.color = input.color;
output.altas_uv = float2(input.param_a.x, input.param_a.y);
output.altas_index = input.param_a.z;
output.char_size = float2(input.param_b.x, input.param_b.y);
return output;
}
Texture2D sdf_texture : register(t0);
SamplerState sdf_sampler : register(s0);
struct FontParams {
float smoothing; // 平滑度
float thickness; // 字体粗细
float outline_width; // 描边宽度
float4 outline_color; // 描边颜色
}
ParameterBlock<FontParams> font_param : register(b1);
Texture2DArray atlas_texture : register(t0);
SamplerState sampler_state : register(s0);
float4 pixel_main(PSInput input) : SV_Target {
// 采样SDF纹理
float distance = sdf_texture.Sample(sdf_sampler, input.uv).r;
// 计算主要形状
float alpha = smoothstep(font_param.thickness - font_param.smoothing,
font_param.thickness + font_param.smoothing,
distance);
// 计算轮廓
float outline_alpha = smoothstep(font_param.thickness - font_param.outline_width - font_param.smoothing,
font_param.thickness - font_param.outline_width + font_param.smoothing,
distance);
// 混合主要颜色和轮廓颜色
float4 main_color = input.color * alpha;
float4 outline = font_param.outline_color * (outline_alpha - alpha);
return main_color + outline;
}
float2 uv = input.altas_uv + input.uv / 64;
float distance = atlas_texture.Sample(sampler_state, float3(uv, input.altas_index)).r;
// return float4(distance, distance, distance, 1.0);
float alpha = smoothstep(0.49, 0.51, distance);
float4 color = input.color;
color.a *= alpha;
return color;
}

View File

@ -1,8 +1,4 @@
struct VSInput {
float2 position : POSITION; // 窗口坐标
float2 uv : TEXCOORD0; // 纹理坐标
float4 color : COLOR; // 颜色
};
#include "aorii_util.slang"
struct PSInput {
float4 position : SV_POSITION; // 裁剪空间坐标

View File

@ -1,15 +1,11 @@
#include "aorii_util.slang"
struct ParamBuffer
{
matrix transform;
};
ParameterBlock<ParamBuffer> param_buffer : register(b0);
struct VSInput {
float2 position : POSITION; // 窗口坐标
float2 uv : TEXCOORD0; // 纹理坐标
float4 color : COLOR; // 颜色
};
struct PSInput {
float4 position : SV_POSITION; // 裁剪空间坐标
float2 uv : TEXCOORD0; // 纹理坐标

View File

@ -1,3 +1,11 @@
struct VSInput {
float2 position : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR0;
float4 param_a : TEXCOORD1;
float4 param_b : TEXCOORD2;
float4 param_c : TEXCOORD3;
};
// 将uv坐标系移动到[-1, -1] ~ [1, 1]
float2 uv_to_ndc(float2 uv) {

View File

@ -0,0 +1,118 @@
// Copyright Take Vos 2021.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
/** logical and.
*/
bool4 and(bool4 lhs, bool4 rhs)
{
return bool4(
lhs.x && rhs.x,
lhs.y && rhs.y,
lhs.z && rhs.z,
lhs.w && rhs.w
);
}
T mix<T:IFloat>(T x, T y, T a)
{
return x * (T(1) - a) + y * a;
}
/** Check if the rectangle contains the point.
*
* @param rectangle A axis-aligned rectangle encoded as left-bottom=(x,y), right-top=(z,w)
* @param point A 2D point
* @return True if the point is inside the rectangle.
*/
bool contains(float4 rectangle, float2 point)
{
return all(point.xyxy >= rectangle == bool4(true, true, false, false));
}
/** Convert coverage to a perceptual uniform alpha.
*
* This function takes into account the lightness of the full pixel, then
* determines based on this if the background is either black or white, or
* if linear conversion of coverage to alpha is needed.
*
* On black and white background we measure the target lightness of each sub-pixel
* then convert to target luminosity and eventually the alpha value.
*
* The alpha-component of the return value is calculated based on the full pixel
* lightness and from the green sub-pixel coverage.
*
* The full formula to convert coverage to alpha taking into account perceptual
* uniform lightness between foreground and background colors:
* ```
* F = foreground color
* B = background color
* T = target color
* c = coverage
* a = alpha
* T = mix(sqrt(F), sqrt(B), c) ^ 2
*
* a = (T - B) / (F - B) if F != B
* a = c otherwise
* ```
*
* To simplify this formula and remove the division we fill in the foreground and background
* with black and white and the other way around:
* ```
* a = c^2 if F == 1 and B == 0
* a = 2c - c^2 if F == 0 and B == 1
* ```
*
* Now we mix based on the foreground color, expecting the background color to mirror.
* ```
* a = mix(2c - c^2, c^2, F^2) if B^2 == 1 - F^2
* ```
*
* @param coverage The amount of coverage. Elements must be between 0.0 and 1.0
* @param foreground_sq The sqrt of the foreground. Elements must be between 0.0 and 1.0
* @return The alpha value for the red, blue, green, alpha color components.
*/
float coverage_to_alpha(float coverage, float sqrt_foreground)
{
float coverage_sq = coverage * coverage;
float coverage_2 = coverage + coverage;
return mix(coverage_2 - coverage_sq, coverage_sq, sqrt_foreground);
}
/** Convert coverage to a perceptual uniform alpha.
*
* @see coverage_to_alpha(float, float)
*/
float4 coverage_to_alpha(float4 coverage, float4 sqrt_foreground)
{
float4 coverage_sq = coverage * coverage;
float4 coverage_2 = coverage + coverage;
return mix(coverage_2 - coverage_sq, coverage_sq, sqrt_foreground);
}
/** Multiply the alpha with the color.
*
* @param color The color+alpha without pre-multiplication.
* @return The color+alpha where the color is multiplied with the alpha.
*/
float4 multiply_alpha(float4 color)
{
return float4(color.rgb * color.a, color.a);
}
/** Convert RGB to Y.
*/
float rgb_to_y(float3 color)
{
float3 tmp = color * float3(0.2126, 0.7152, 0.0722);
return tmp.r + tmp.g + tmp.b;
}
/** Convert RGB to RGBY.
*/
float4 rgb_to_rgby(float3 color)
{
return float4(color, rgb_to_y(color));
}