]> git.sesse.net Git - nageru/blob - nageru/timecode_renderer.cpp
2ada19abb910e2381c9fa2eeaf19f56625dd9740
[nageru] / nageru / timecode_renderer.cpp
1 #include "timecode_renderer.h"
2
3 #include <memory>
4 #include <string>
5 #include <vector>
6
7 #include <QImage>
8 #include <QPainter>
9
10 #include <epoxy/gl.h>
11 #include <movit/effect_util.h>
12 #include <movit/resource_pool.h>
13 #include <movit/util.h>
14 #include <sys/time.h>
15
16 #include "flags.h"
17 #include "embedded_files.h"
18 #include "shared/read_file.h"
19
20 using namespace std;
21 using namespace movit;
22
23 TimecodeRenderer::TimecodeRenderer(movit::ResourcePool *resource_pool, unsigned display_width, unsigned display_height)
24         : resource_pool(resource_pool), display_width(display_width), display_height(display_height), height(28)
25 {
26         string vert_shader = read_file("timecode.vert", _binary_timecode_vert_data, _binary_timecode_vert_size);
27         string frag_shader;
28         if (global_flags.ten_bit_output) {
29                 frag_shader = read_file("timecode_10bit.frag", _binary_timecode_10bit_frag_data, _binary_timecode_10bit_frag_size);
30         } else {
31                 frag_shader = read_file("timecode.frag", _binary_timecode_frag_data, _binary_timecode_frag_size);
32         }
33
34         vector<string> frag_shader_outputs;
35         program_num = resource_pool->compile_glsl_program(vert_shader, frag_shader, frag_shader_outputs);
36         check_error();
37
38         texture_sampler_uniform = glGetUniformLocation(program_num, "tex");
39         check_error();
40         position_attribute_index = glGetAttribLocation(program_num, "position");
41         check_error();
42         texcoord_attribute_index = glGetAttribLocation(program_num, "texcoord");
43         check_error();
44
45         // Shared between the two.
46         float vertices[] = {
47                 0.0f, 2.0f,
48                 0.0f, 0.0f,
49                 2.0f, 0.0f
50         };
51         vbo = generate_vbo(2, GL_FLOAT, sizeof(vertices), vertices);
52         check_error();
53
54         tex = resource_pool->create_2d_texture(GL_R8, display_width, height);
55
56         image.reset(new QImage(display_width, height, QImage::Format_Grayscale8));
57 }
58
59 TimecodeRenderer::~TimecodeRenderer()
60 {
61         resource_pool->release_2d_texture(tex);
62         check_error();
63         resource_pool->release_glsl_program(program_num);
64         check_error();
65         glDeleteBuffers(1, &vbo);
66         check_error();
67 }
68
69 string TimecodeRenderer::get_timecode_text(double pts, unsigned frame_num)
70 {
71         // Find the wall time, and round it to the nearest millisecond.
72         timeval now;
73         gettimeofday(&now, nullptr);
74         time_t unixtime = now.tv_sec;
75         unsigned msecs = (now.tv_usec + 500) / 1000;
76         if (msecs >= 1000) {
77                 msecs -= 1000;
78                 ++unixtime;
79         }
80
81         tm utc_tm;
82         gmtime_r(&unixtime, &utc_tm);
83         char clock_text[256];
84         strftime(clock_text, sizeof(clock_text), "%Y-%m-%d %H:%M:%S", &utc_tm);
85
86         // Make the stream timecode, rounded to the nearest millisecond.
87         long stream_time = lrint(pts * 1e3);
88         assert(stream_time >= 0);
89         unsigned stream_time_ms = stream_time % 1000;
90         stream_time /= 1000;
91         unsigned stream_time_sec = stream_time % 60;
92         stream_time /= 60;
93         unsigned stream_time_min = stream_time % 60;
94         unsigned stream_time_hour = stream_time / 60;
95
96         char timecode_text[512];
97         snprintf(timecode_text, sizeof(timecode_text), "Nageru - %s.%03u UTC - Stream time %02u:%02u:%02u.%03u (frame %u)",
98                 clock_text, msecs, stream_time_hour, stream_time_min, stream_time_sec, stream_time_ms, frame_num);
99         return timecode_text;
100 }
101
102 void TimecodeRenderer::render_timecode(GLuint fbo, const string &text)
103 {
104         render_string_to_buffer(text);
105         render_buffer_to_fbo(fbo);
106 }
107
108 void TimecodeRenderer::render_string_to_buffer(const string &text)
109 {
110         image->fill(0);
111         QPainter painter(image.get());
112
113         painter.setPen(Qt::white);
114         QFont font = painter.font();
115         font.setPointSize(16);
116         painter.setFont(font);
117
118         painter.drawText(QRectF(0, 0, display_width, height), Qt::AlignCenter, QString::fromStdString(text));
119 }
120
121 void TimecodeRenderer::render_buffer_to_fbo(GLuint fbo)
122 {
123         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
124         check_error();
125
126         GLuint vao;
127         glGenVertexArrays(1, &vao);
128         check_error();
129
130         glBindVertexArray(vao);
131         check_error();
132
133         glViewport(0, display_height - height, display_width, height);
134         check_error();
135
136         glActiveTexture(GL_TEXTURE0);
137         check_error();
138         glBindTexture(GL_TEXTURE_2D, tex);
139         check_error();
140         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
141         check_error();
142         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
143         check_error();
144         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
145         check_error();
146
147         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, display_width, height, GL_RED, GL_UNSIGNED_BYTE, image->bits());
148         check_error();
149
150         glUseProgram(program_num);
151         check_error();
152         glUniform1i(texture_sampler_uniform, 0);
153         check_error();
154
155         glBindBuffer(GL_ARRAY_BUFFER, vbo);
156         check_error();
157
158         for (GLint attr_index : { position_attribute_index, texcoord_attribute_index }) {
159                 if (attr_index == -1) continue;
160                 glEnableVertexAttribArray(attr_index);
161                 check_error();
162                 glVertexAttribPointer(attr_index, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
163                 check_error();
164         }
165
166         glDrawArrays(GL_TRIANGLES, 0, 3);
167         check_error();
168
169         for (GLint attr_index : { position_attribute_index, texcoord_attribute_index }) {
170                 if (attr_index == -1) continue;
171                 glDisableVertexAttribArray(attr_index);
172                 check_error();
173         }
174
175         glActiveTexture(GL_TEXTURE0);
176         check_error();
177         glUseProgram(0);
178         check_error();
179
180         glDeleteVertexArrays(1, &vao);
181         check_error();
182
183         glBindFramebuffer(GL_FRAMEBUFFER, 0);
184         check_error();
185 }