1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "tools/render/text.h"
16
17 #include "absl/strings/str_cat.h"
18 #include "tools/render/font_data.h"
19
20 namespace quic_trace {
21 namespace render {
22
23 namespace {
24 // The following shader renders the text as specified in window coordinates and
25 // assigns appropriate texture
26 const char* kVertexShader = R"(
27 uniform float texture_w;
28 uniform float texture_h;
29
30 in vec2 coord;
31 out vec2 texcoord;
32 void main(void) {
33 gl_Position = windowToGl(coord);
34
35 // Textures the squares based on the following assumed ordering of vertices:
36 // 0 1
37 // 2 3
38 texcoord = vec2((gl_VertexID % 2) * texture_w, (gl_VertexID % 4) < 2 ? 0 : texture_h);
39 }
40 )";
41
42 const char* kFragmentShader = R"(
43 in vec2 texcoord;
44 out vec4 color;
45
46 uniform sampler2D tex;
47
48 void main(void) {
49 color = texture(tex, texcoord);
50 }
51 )";
52 } // namespace
53
TextRenderer(const ProgramState * state)54 TextRenderer::TextRenderer(const ProgramState* state)
55 : state_(state), shader_(kVertexShader, kFragmentShader) {
56 CHECK_EQ(TTF_Init(), 0) << "Failed to initialize SDL_ttf: " << TTF_GetError();
57
58 SDL_RWops* font_data =
59 SDL_RWFromConstMem(::kDroidSansBlob.data(), ::kDroidSansBlob.size());
60 font_ = TTF_OpenFontRW(font_data, /*freesrc=*/1, state->ScaleForDpi(14));
61 CHECK(font_ != nullptr) << "Failed to load regular font";
62 }
63
~TextRenderer()64 TextRenderer::~TextRenderer() {
65 if (font_ != nullptr) {
66 TTF_CloseFont(font_);
67 }
68
69 TTF_Quit();
70 }
71
NearestPowerOfTwo(size_t x)72 static size_t NearestPowerOfTwo(size_t x) {
73 size_t result = 1;
74 while (result < x) {
75 result *= 2;
76 }
77 return result;
78 }
79
AddText(std::shared_ptr<const Text> text,int x,int y)80 void TextRenderer::AddText(std::shared_ptr<const Text> text, int x, int y) {
81 texts_to_draw_.push_back(TextToDraw{
82 .text = std::move(text),
83 .x = x,
84 .y = y,
85 });
86 }
87
DrawAll()88 void TextRenderer::DrawAll() {
89 // Common setup for drawing text.
90 glUseProgram(*shader_);
91 state_->Bind(shader_);
92
93 for (const TextToDraw& text_to_draw : texts_to_draw_) {
94 text_to_draw.text->Draw(text_to_draw.x, text_to_draw.y);
95 }
96 texts_to_draw_.clear();
97
98 // Clean up the cache.
99 for (auto it = cache_.begin(); it != cache_.end();) {
100 if (it->second.live) {
101 it->second.live = false;
102 it++;
103 } else {
104 cache_.erase(it++);
105 }
106 }
107 DCHECK(cache_.size() < 4096) << "The text cache is too big, which indicates "
108 "that it's most likely leaking";
109 }
110
RenderTextInner(const std::string & text,int style)111 std::shared_ptr<const Text> TextRenderer::RenderTextInner(
112 const std::string& text,
113 int style) {
114 // Check if we have the text in the cache.
115 std::string cache_key = absl::StrCat(style, "/", text);
116 auto cache_it = cache_.find(cache_key);
117 if (cache_it != cache_.end()) {
118 cache_it->second.live = true;
119 return cache_it->second.text;
120 }
121
122 if (TTF_GetFontStyle(font_) != style) {
123 TTF_SetFontStyle(font_, style);
124 }
125
126 // Render the text.
127 ScopedSdlSurface surface;
128 if (text.empty()) {
129 // SDL2_ttf returns error when asked to render an empty string. Return a
130 // 1x1 white square instead.
131 surface = ScopedSdlSurface(
132 SDL_CreateRGBSurfaceWithFormat(0, 1, 1, 32, SDL_PIXELFORMAT_RGBA32));
133 CHECK(!SDL_MUSTLOCK(*surface));
134 memset(surface->pixels, 0xff, surface->h * surface->pitch);
135 } else {
136 surface = TTF_RenderUTF8_Blended(font_, text.c_str(), SDL_Color{0, 0, 0});
137 }
138 if (*surface == nullptr) {
139 LOG(FATAL) << "Failed to render text: \"" << text.size() << "\"";
140 }
141
142 std::unique_ptr<Text> result(new Text(this));
143 // Find the correct size for the texture, which is required to be a power of
144 // two.
145 size_t size =
146 std::max(NearestPowerOfTwo(surface->w), NearestPowerOfTwo(surface->h));
147 result->width_ = surface->w;
148 result->height_ = surface->h;
149 result->texture_size_ = size;
150
151 // Create a |size|x|size| texture buffer in RAM, and move the texture there.
152 ScopedSdlSurface scaled_surface(SDL_CreateRGBSurfaceWithFormat(
153 0, size, size, 32, SDL_PIXELFORMAT_RGBA32));
154 SDL_Rect target{0, 0, surface->w, surface->h};
155 SDL_BlitSurface(*surface, nullptr, *scaled_surface, &target);
156
157 CHECK_EQ(scaled_surface->format->format, SDL_PIXELFORMAT_RGBA32);
158 CHECK_EQ(scaled_surface->pitch, size * 4);
159 CHECK(!SDL_MUSTLOCK(*scaled_surface));
160
161 // Upload the texture onto the GPU.
162 glBindTexture(GL_TEXTURE_2D, *result->texture_);
163 glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
164 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA,
165 GL_UNSIGNED_BYTE, scaled_surface->pixels);
166
167 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
168 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
169
170 std::shared_ptr<const Text> cached_result(result.release());
171 cache_.emplace(cache_key, CacheEntry{cached_result, true});
172 return cached_result;
173 }
174
Draw(int x,int y) const175 void Text::Draw(int x, int y) const {
176 const float fx = x, fy = y, fw = width_, fh = height_;
177 const float vertices[] = {
178 fx, fy + fh, // upper left
179 fx + fw, fy + fh, // upper right
180 fx, fy, // lower left
181 fx + fw, fy, // lower right
182 };
183
184 GlVertexBuffer buffer;
185 glBindBuffer(GL_ARRAY_BUFFER, *buffer);
186 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
187
188 GlVertexArray array;
189 glBindVertexArray(*array);
190
191 glBindTexture(GL_TEXTURE_2D, *texture_);
192 factory_->shader_.SetUniform("texture_w", width_ / texture_size_);
193 factory_->shader_.SetUniform("texture_h", height_ / texture_size_);
194
195 GlVertexArrayAttrib coord(factory_->shader_, "coord");
196 glVertexAttribPointer(*coord, 2, GL_FLOAT, GL_FALSE, 0, 0);
197 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
198 }
199
200 } // namespace render
201 } // namespace quic_trace
202