Skip to content

Commit 3ea1867

Browse files
committed
add outline support for text
1 parent ddf53a1 commit 3ea1867

20 files changed

+275
-96
lines changed

examples/11-text.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ int main()
3131
text_data.character_size = 64;
3232
text_data.color = gf::darker(gf::Azure, 0.2f);
3333
// text_data.outline_color = gf::Gray;
34-
// text_data.outline_thickness = 2.0f;
34+
// text_data.outline_thickness = 0.2f;
3535

3636
gf::TextEntity text_entity(&atlas, &face, text_data, scene_manager.render_manager());
3737
text_entity.set_location({ 1000.0f, 1000.0f });

include/gf2/core/TextData.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ namespace gf {
2626
std::string content;
2727
float character_size = 0.0f;
2828
Color color = Black;
29-
// float outline_thickness = 0.0f;
30-
// Color outline_color = White;
29+
Color outline_color = Transparent;
30+
float outline_thickness = 0.0f;
3131
Alignment alignment = Alignment::None;
3232
float paragraph_width = 0.0f;
3333
float line_spacing_factor = 1.0f;
@@ -37,7 +37,7 @@ namespace gf {
3737
template<typename Archive>
3838
Archive& operator|(Archive& ar, MaybeConst<TextData, Archive>& data)
3939
{
40-
return ar | data.content | data.character_size | data.color | data.alignment | data.paragraph_width | data.line_spacing_factor | data.letter_spacing_factor;
40+
return ar | data.content | data.character_size | data.color | data.outline_color | data.outline_thickness | data.alignment | data.paragraph_width | data.line_spacing_factor | data.letter_spacing_factor;
4141
}
4242

4343
struct GF_CORE_API TextResource {

include/gf2/graphics/RenderRecorder.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
#include <vector>
99

10+
#include <gf2/core/Color.h>
1011
#include <gf2/core/Mat4.h>
1112
#include <gf2/core/Rect.h>
1213

1314
#include "Buffer.h"
1415
#include "GraphicsApi.h"
1516
#include "RenderObject.h"
17+
#include "TextEffect.h"
1618

1719
namespace gf {
1820
class RenderManager;
@@ -33,6 +35,7 @@ namespace gf {
3335

3436
void update_view(const Mat3F& view_matrix, const RectF& viewport);
3537
void update_scissor(const RectI& scissor);
38+
void update_text_effect(const TextEffect& effect);
3639
void record(const RenderObject& object);
3740

3841
void sort();
@@ -44,6 +47,7 @@ namespace gf {
4447
enum class RecordType : uint8_t {
4548
View,
4649
Scissor,
50+
Text,
4751
Object,
4852
};
4953

@@ -61,16 +65,24 @@ namespace gf {
6165
RectI scissor;
6266
};
6367

68+
struct TextRecord {
69+
uint32_t text_effect_buffer_index = 0;
70+
};
71+
6472
RenderManager* m_render_manager;
6573

6674
std::vector<Record> m_records;
6775

6876
std::vector<ViewRecord> m_views;
6977
std::vector<ScissorRecord> m_scissors;
78+
std::vector<TextRecord> m_texts;
7079
std::vector<RenderObject> m_objects;
7180

7281
uint32_t m_next_view_matrix_index = 0;
7382
std::vector<Buffer> m_view_matrix_buffers;
83+
84+
uint32_t m_next_text_effect_index = 0;
85+
std::vector<Buffer> m_text_effect_buffers;
7486
};
7587

7688
}

include/gf2/graphics/SceneManager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ namespace gf {
5050

5151
DescriptorLayout m_camera_descriptor_layout;
5252
DescriptorLayout m_sampler_descriptor_layout;
53+
DescriptorLayout m_text_descriptor_layout;
5354

5455
RenderPipelineLayout m_default_pipeline_layout;
56+
RenderPipelineLayout m_text_pipeline_layout;
5557
RenderPipelineLayout m_fullscreen_pipeline_layout;
5658

5759
RenderPipeline m_default_pipeline;

include/gf2/graphics/Text.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "FontAtlas.h"
1313
#include "GraphicsApi.h"
1414
#include "RenderObject.h"
15+
#include "TextEffect.h"
1516

1617
namespace gf {
1718
class RenderManager;
@@ -31,6 +32,11 @@ namespace gf {
3132
return m_atlas->texture();
3233
}
3334

35+
TextEffect effects()
36+
{
37+
return m_effects;
38+
}
39+
3440
RectF bounds() const
3541
{
3642
return m_bounds;
@@ -42,6 +48,7 @@ namespace gf {
4248
FontFace* m_font = nullptr;
4349
DynamicBuffer m_vertices;
4450
DynamicBuffer m_indices;
51+
TextEffect m_effects = {};
4552
RectF m_bounds = {};
4653
};
4754

@@ -59,6 +66,11 @@ namespace gf {
5966
return m_atlas->texture();
6067
}
6168

69+
TextEffect effects()
70+
{
71+
return m_effects;
72+
}
73+
6274
RectF bounds() const
6375
{
6476
return m_bounds;
@@ -73,6 +85,7 @@ namespace gf {
7385
FontFace* m_bold_italic_font = nullptr;
7486
DynamicBuffer m_vertices;
7587
DynamicBuffer m_indices;
88+
TextEffect m_effects = {};
7689
RectF m_bounds = {};
7790
};
7891

include/gf2/graphics/TextEffect.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: Zlib
2+
// Copyright (c) 2023-2025 Julien Bernard
3+
#ifndef GF_TEXT_EFFECT_H
4+
#define GF_TEXT_EFFECT_H
5+
6+
#include <gf2/core/Color.h>
7+
8+
#include "GraphicsApi.h"
9+
10+
namespace gf {
11+
12+
struct GF_GRAPHICS_API TextEffect {
13+
Color outline_color = Transparent;
14+
float outline_thickness = 0.0f;
15+
};
16+
17+
}
18+
19+
#endif // GF_TEXT_EFFECT_H

library/graphics/RenderRecorder.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99

1010
namespace gf {
1111

12+
namespace {
13+
14+
std::array<float, 5> compute_raw(const TextEffect& effect)
15+
{
16+
return { effect.outline_color.r, effect.outline_color.g, effect.outline_color.b, effect.outline_color.a, effect.outline_thickness };
17+
}
18+
19+
}
20+
1221
RenderRecorder::RenderRecorder(RenderManager* render_manager)
1322
: m_render_manager(render_manager)
1423
{
@@ -41,6 +50,25 @@ namespace gf {
4150
m_scissors.push_back({ scissor });
4251
}
4352

53+
void RenderRecorder::update_text_effect(const TextEffect& effect)
54+
{
55+
auto effect_data = compute_raw(effect);
56+
57+
if (m_next_text_effect_index == m_text_effect_buffers.size()) {
58+
Buffer buffer(BufferType::Device, BufferUsage::Uniform, effect_data.size(), sizeof(float), effect_data.data(), m_render_manager);
59+
buffer.set_debug_name("[gf2] Text Effect Buffer #" + std::to_string(m_next_text_effect_index));
60+
m_text_effect_buffers.push_back(std::move(buffer));
61+
} else {
62+
m_text_effect_buffers[m_next_text_effect_index].update(effect_data.size(), sizeof(float), effect_data.data(), m_render_manager);
63+
}
64+
65+
const Record record = { RecordType::Text, m_texts.size() };
66+
m_records.push_back(record);
67+
68+
m_texts.push_back({ m_next_text_effect_index });
69+
++m_next_text_effect_index;
70+
}
71+
4472
void RenderRecorder::record(const RenderObject& object)
4573
{
4674
const Record record = { RecordType::Object, m_objects.size() };
@@ -59,8 +87,10 @@ namespace gf {
5987
m_records.clear();
6088
m_views.clear();
6189
m_scissors.clear();
90+
m_texts.clear();
6291
m_objects.clear();
6392
m_next_view_matrix_index = 0;
93+
m_next_text_effect_index = 0;
6494
}
6595

6696
}

library/graphics/SceneManager.cc

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <gf2/graphics/RenderRecorder.h>
1616
#include <gf2/graphics/Scene.h>
1717
#include <gf2/graphics/Vertex.h>
18+
#include "gf2/graphics/Descriptor.h"
1819

1920
namespace gf {
2021

@@ -115,6 +116,13 @@ namespace gf {
115116
} else if (record.type == RenderRecorder::RecordType::Scissor) {
116117
assert(record.index < recorder.m_scissors.size());
117118
command_buffer.set_scissor(recorder.m_scissors[record.index].scissor);
119+
} else if (record.type == RenderRecorder::RecordType::Text) {
120+
assert(record.index < recorder.m_texts.size());
121+
auto text = recorder.m_texts[record.index];
122+
123+
auto text_descriptor = m_render_manager.allocate_descriptor_for_layout(&m_text_descriptor_layout);
124+
text_descriptor.write(0, &recorder.m_text_effect_buffers[text.text_effect_buffer_index]);
125+
command_buffer.bind_descriptor(&m_text_pipeline_layout, 2, text_descriptor);
118126
} else if (record.type == RenderRecorder::RecordType::Object) {
119127
assert(record.index < recorder.m_objects.size());
120128
render_object(command_buffer, recorder.m_objects[record.index]);
@@ -182,6 +190,14 @@ namespace gf {
182190

183191
m_sampler_descriptor_layout = DescriptorLayout(sampler_binding, render_manager());
184192

193+
// text_descriptor_layout
194+
195+
const DescriptorBinding text_binding[] = {
196+
{ 0, DescriptorType::Buffer, ShaderStage::Fragment }
197+
};
198+
199+
m_text_descriptor_layout = DescriptorLayout(text_binding, render_manager());
200+
185201
// default pipeline layout
186202

187203
RenderPipelineLayoutBuilder default_pipeline_layout_builder;
@@ -193,6 +209,18 @@ namespace gf {
193209
m_default_pipeline_layout = default_pipeline_layout_builder.build(render_manager());
194210
m_default_pipeline_layout.set_debug_name("[gf2] Default Pipeline Layout");
195211

212+
// text pipeline layout
213+
214+
RenderPipelineLayoutBuilder text_pipeline_layout_builder;
215+
216+
text_pipeline_layout_builder.set_push_constant_parameters(ShaderStage::Vertex, sizeof(float) * 16)
217+
.add_descriptor_layout(&m_camera_descriptor_layout)
218+
.add_descriptor_layout(&m_sampler_descriptor_layout)
219+
.add_descriptor_layout(&m_text_descriptor_layout);
220+
221+
m_text_pipeline_layout = text_pipeline_layout_builder.build(render_manager());
222+
m_text_pipeline_layout.set_debug_name("[gf2] Text Pipeline Layout");
223+
196224
// fullscreen pipeline layout
197225

198226
RenderPipelineLayoutBuilder fullscreen_pipeline_layout_builder;
@@ -219,7 +247,7 @@ namespace gf {
219247
text_pipeline_builder.set_vertex_input(Vertex::compute_input())
220248
.add_shader(ShaderStage::Vertex, gf::span(default_vert_shader_code))
221249
.add_shader(ShaderStage::Fragment, gf::span(text_frag_shader_code))
222-
.set_pipeline_layout(&m_default_pipeline_layout);
250+
.set_pipeline_layout(&m_text_pipeline_layout);
223251

224252
m_text_pipeline = text_pipeline_builder.build(render_manager());
225253
m_text_pipeline.set_debug_name("[gf2] Text Pipeline");

library/graphics/Text.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,9 @@ namespace gf {
859859
m_vertices.update(geometry.vertices.data(), geometry.vertices.size(), render_manager);
860860
m_indices.update(geometry.indices.data(), geometry.indices.size(), render_manager);
861861

862+
m_effects.outline_color = data.outline_color;
863+
m_effects.outline_thickness = data.outline_thickness;
864+
862865
m_bounds = geometry.compute_bounds();
863866
fix_bounds(data.alignment, data.paragraph_width, m_bounds);
864867
}
@@ -922,6 +925,9 @@ namespace gf {
922925
m_vertices.update(geometry.vertices.data(), geometry.vertices.size(), render_manager);
923926
m_indices.update(geometry.indices.data(), geometry.indices.size(), render_manager);
924927

928+
m_effects.outline_color = data.outline_color;
929+
m_effects.outline_thickness = data.outline_thickness;
930+
925931
m_bounds = geometry.compute_bounds().shrink_by(static_cast<float>(FontManager::spread()));
926932
fix_bounds(data.alignment, data.paragraph_width, m_bounds);
927933
}

library/graphics/TextEffect.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// SPDX-License-Identifier: Zlib
2+
// Copyright (c) 2023-2025 Julien Bernard
3+
4+
#include <gf2/graphics/TextEffect.h>

0 commit comments

Comments
 (0)