blob: 577ea25487a2b00b367e940975ee1ebad9a28bbf [file] [log] [blame]
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "tools/render/text.h"
#include "absl/strings/str_cat.h"
#include "tools/render/font_data.h"
namespace quic_trace {
namespace render {
namespace {
// The following shader renders the text as specified in window coordinates and
// assigns appropriate texture
const char* kVertexShader = R"(
uniform float texture_w;
uniform float texture_h;
in vec2 coord;
out vec2 texcoord;
void main(void) {
gl_Position = windowToGl(coord);
// Textures the squares based on the following assumed ordering of vertices:
// 0 1
// 2 3
texcoord = vec2((gl_VertexID % 2) * texture_w, (gl_VertexID % 4) < 2 ? 0 : texture_h);
}
)";
const char* kFragmentShader = R"(
in vec2 texcoord;
out vec4 color;
uniform sampler2D tex;
void main(void) {
color = texture(tex, texcoord);
}
)";
} // namespace
TextRenderer::TextRenderer(const ProgramState* state)
: state_(state), shader_(kVertexShader, kFragmentShader) {
CHECK_EQ(TTF_Init(), 0) << "Failed to initialize SDL_ttf: " << TTF_GetError();
SDL_RWops* font_data =
SDL_RWFromConstMem(::kDroidSansBlob.data(), ::kDroidSansBlob.size());
font_ = TTF_OpenFontRW(font_data, /*freesrc=*/1, state->ScaleForDpi(14));
CHECK(font_ != nullptr) << "Failed to load regular font";
}
TextRenderer::~TextRenderer() {
if (font_ != nullptr) {
TTF_CloseFont(font_);
}
TTF_Quit();
}
static size_t NearestPowerOfTwo(size_t x) {
size_t result = 1;
while (result < x) {
result *= 2;
}
return result;
}
void TextRenderer::AddText(std::shared_ptr<const Text> text, int x, int y) {
texts_to_draw_.push_back(TextToDraw{
.text = std::move(text),
.x = x,
.y = y,
});
}
void TextRenderer::DrawAll() {
// Common setup for drawing text.
glUseProgram(*shader_);
state_->Bind(shader_);
for (const TextToDraw& text_to_draw : texts_to_draw_) {
text_to_draw.text->Draw(text_to_draw.x, text_to_draw.y);
}
texts_to_draw_.clear();
// Clean up the cache.
for (auto it = cache_.begin(); it != cache_.end();) {
if (it->second.live) {
it->second.live = false;
it++;
} else {
cache_.erase(it++);
}
}
DCHECK(cache_.size() < 4096) << "The text cache is too big, which indicates "
"that it's most likely leaking";
}
std::shared_ptr<const Text> TextRenderer::RenderTextInner(
const std::string& text,
int style) {
// Check if we have the text in the cache.
std::string cache_key = absl::StrCat(style, "/", text);
auto cache_it = cache_.find(cache_key);
if (cache_it != cache_.end()) {
cache_it->second.live = true;
return cache_it->second.text;
}
if (TTF_GetFontStyle(font_) != style) {
TTF_SetFontStyle(font_, style);
}
// Render the text.
ScopedSdlSurface surface;
if (text.empty()) {
// SDL2_ttf returns error when asked to render an empty string. Return a
// 1x1 white square instead.
surface = ScopedSdlSurface(
SDL_CreateRGBSurfaceWithFormat(0, 1, 1, 32, SDL_PIXELFORMAT_RGBA32));
CHECK(!SDL_MUSTLOCK(*surface));
memset(surface->pixels, 0xff, surface->h * surface->pitch);
} else {
surface = TTF_RenderUTF8_Blended(font_, text.c_str(), SDL_Color{0, 0, 0});
}
if (*surface == nullptr) {
LOG(FATAL) << "Failed to render text: \"" << text.size() << "\"";
}
std::unique_ptr<Text> result(new Text(this));
// Find the correct size for the texture, which is required to be a power of
// two.
size_t size =
std::max(NearestPowerOfTwo(surface->w), NearestPowerOfTwo(surface->h));
result->width_ = surface->w;
result->height_ = surface->h;
result->texture_size_ = size;
// Create a |size|x|size| texture buffer in RAM, and move the texture there.
ScopedSdlSurface scaled_surface(SDL_CreateRGBSurfaceWithFormat(
0, size, size, 32, SDL_PIXELFORMAT_RGBA32));
SDL_Rect target{0, 0, surface->w, surface->h};
SDL_BlitSurface(*surface, nullptr, *scaled_surface, &target);
CHECK_EQ(scaled_surface->format->format, SDL_PIXELFORMAT_RGBA32);
CHECK_EQ(scaled_surface->pitch, size * 4);
CHECK(!SDL_MUSTLOCK(*scaled_surface));
// Upload the texture onto the GPU.
glBindTexture(GL_TEXTURE_2D, *result->texture_);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA,
GL_UNSIGNED_BYTE, scaled_surface->pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
std::shared_ptr<const Text> cached_result(result.release());
cache_.emplace(cache_key, CacheEntry{cached_result, true});
return cached_result;
}
void Text::Draw(int x, int y) const {
const float fx = x, fy = y, fw = width_, fh = height_;
const float vertices[] = {
fx, fy + fh, // upper left
fx + fw, fy + fh, // upper right
fx, fy, // lower left
fx + fw, fy, // lower right
};
GlVertexBuffer buffer;
glBindBuffer(GL_ARRAY_BUFFER, *buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
GlVertexArray array;
glBindVertexArray(*array);
glBindTexture(GL_TEXTURE_2D, *texture_);
factory_->shader_.SetUniform("texture_w", width_ / texture_size_);
factory_->shader_.SetUniform("texture_h", height_ / texture_size_);
GlVertexArrayAttrib coord(factory_->shader_, "coord");
glVertexAttribPointer(*coord, 2, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
} // namespace render
} // namespace quic_trace