]> git.sesse.net Git - nageru/blob - timecode_renderer.cpp
Release Nageru 1.7.2.
[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
18 using namespace std;
19 using namespace movit;
20
21 TimecodeRenderer::TimecodeRenderer(movit::ResourcePool *resource_pool, unsigned display_width, unsigned display_height)
22         : resource_pool(resource_pool), display_width(display_width), display_height(display_height), height(28)
23 {
24         string vert_shader =
25                 "#version 130 \n"
26                 " \n"
27                 "in vec2 position; \n"
28                 "in vec2 texcoord; \n"
29                 "out vec2 tc0; \n"
30                 " \n"
31                 "void main() \n"
32                 "{ \n"
33                 "    // The result of glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0) is: \n"
34                 "    // \n"
35                 "    //   2.000  0.000  0.000 -1.000 \n"
36                 "    //   0.000  2.000  0.000 -1.000 \n"
37                 "    //   0.000  0.000 -2.000 -1.000 \n"
38                 "    //   0.000  0.000  0.000  1.000 \n"
39                 "    gl_Position = vec4(2.0 * position.x - 1.0, 2.0 * position.y - 1.0, -1.0, 1.0); \n"
40                 "    tc0 = texcoord; \n"
41                 "} \n";
42         string frag_shader =
43                 "#version 130 \n"
44                 "in vec2 tc0; \n"
45                 "uniform sampler2D tex; \n"
46                 "out vec4 Y, CbCr, YCbCr; \n"
47                 "void main() { \n"
48                 "    vec4 gray = texture(tex, tc0); \n";
49         if (global_flags.ten_bit_output) {
50                 frag_shader +=
51                         "    gray.r = gray.r * ((940.0-16.0)/65535.0) + 16.0/65535.0; \n"  // Limited-range Y'CbCr.
52                         "    CbCr = vec4(512.0/65535.0, 512.0/65535.0, 0.0, 1.0); \n";
53         } else {
54                 frag_shader +=
55                         "    gray.r = gray.r * ((235.0-16.0)/255.0) + 16.0/255.0; \n"  // Limited-range Y'CbCr.
56                         "    CbCr = vec4(128.0/255.0, 128.0/255.0, 0.0, 1.0); \n";
57         }
58         frag_shader +=
59                 "    Y = gray.rrra; \n"
60                 "    YCbCr = vec4(Y.r, CbCr.r, CbCr.g, CbCr.a); \n"
61                 "} \n";
62
63         vector<string> frag_shader_outputs;
64         program_num = resource_pool->compile_glsl_program(vert_shader, frag_shader, frag_shader_outputs);
65         check_error();
66
67         texture_sampler_uniform = glGetUniformLocation(program_num, "tex");
68         check_error();
69         position_attribute_index = glGetAttribLocation(program_num, "position");
70         check_error();
71         texcoord_attribute_index = glGetAttribLocation(program_num, "texcoord");
72         check_error();
73
74         // Shared between the two.
75         float vertices[] = {
76                 0.0f, 2.0f,
77                 0.0f, 0.0f,
78                 2.0f, 0.0f
79         };
80         vbo = generate_vbo(2, GL_FLOAT, sizeof(vertices), vertices);
81         check_error();
82
83         tex = resource_pool->create_2d_texture(GL_R8, display_width, height);
84
85         image.reset(new QImage(display_width, height, QImage::Format_Grayscale8));
86 }
87
88 TimecodeRenderer::~TimecodeRenderer()
89 {
90         resource_pool->release_2d_texture(tex);
91         check_error();
92         resource_pool->release_glsl_program(program_num);
93         check_error();
94         glDeleteBuffers(1, &vbo);
95         check_error();
96 }
97
98 string TimecodeRenderer::get_timecode_text(double pts, unsigned frame_num)
99 {
100         // Find the wall time, and round it to the nearest millisecond.
101         timeval now;
102         gettimeofday(&now, nullptr);
103         time_t unixtime = now.tv_sec;
104         unsigned msecs = (now.tv_usec + 500) / 1000;
105         if (msecs >= 1000) {
106                 msecs -= 1000;
107                 ++unixtime;
108         }
109
110         tm utc_tm;
111         gmtime_r(&unixtime, &utc_tm);
112         char clock_text[256];
113         strftime(clock_text, sizeof(clock_text), "%Y-%m-%d %H:%M:%S", &utc_tm);
114
115         // Make the stream timecode, rounded to the nearest millisecond.
116         long stream_time = lrint(pts * 1e3);
117         assert(stream_time >= 0);
118         unsigned stream_time_ms = stream_time % 1000;
119         stream_time /= 1000;
120         unsigned stream_time_sec = stream_time % 60;
121         stream_time /= 60;
122         unsigned stream_time_min = stream_time % 60;
123         unsigned stream_time_hour = stream_time / 60;
124
125         char timecode_text[256];
126         snprintf(timecode_text, sizeof(timecode_text), "Nageru - %s.%03u UTC - Stream time %02u:%02u:%02u.%03u (frame %u)",
127                 clock_text, msecs, stream_time_hour, stream_time_min, stream_time_sec, stream_time_ms, frame_num);
128         return timecode_text;
129 }
130
131 void TimecodeRenderer::render_timecode(GLuint fbo, const string &text)
132 {
133         render_string_to_buffer(text);
134         render_buffer_to_fbo(fbo);
135 }
136
137 void TimecodeRenderer::render_string_to_buffer(const string &text)
138 {
139         image->fill(0);
140         QPainter painter(image.get());
141
142         painter.setPen(Qt::white);
143         QFont font = painter.font();
144         font.setPointSize(16);
145         painter.setFont(font);
146
147         painter.drawText(QRectF(0, 0, display_width, height), Qt::AlignCenter, QString::fromStdString(text));
148 }
149
150 void TimecodeRenderer::render_buffer_to_fbo(GLuint fbo)
151 {
152         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
153         check_error();
154
155         GLuint vao;
156         glGenVertexArrays(1, &vao);
157         check_error();
158
159         glBindVertexArray(vao);
160         check_error();
161
162         glViewport(0, display_height - height, display_width, height);
163         check_error();
164
165         glActiveTexture(GL_TEXTURE0);
166         check_error();
167         glBindTexture(GL_TEXTURE_2D, tex);
168         check_error();
169         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
170         check_error();
171         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
172         check_error();
173         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
174         check_error();
175
176         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, display_width, height, GL_RED, GL_UNSIGNED_BYTE, image->bits());
177         check_error();
178
179         glUseProgram(program_num);
180         check_error();
181         glUniform1i(texture_sampler_uniform, 0);
182         check_error();
183
184         glBindBuffer(GL_ARRAY_BUFFER, vbo);
185         check_error();
186
187         for (GLint attr_index : { position_attribute_index, texcoord_attribute_index }) {
188                 if (attr_index == -1) continue;
189                 glEnableVertexAttribArray(attr_index);
190                 check_error();
191                 glVertexAttribPointer(attr_index, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
192                 check_error();
193         }
194
195         glDrawArrays(GL_TRIANGLES, 0, 3);
196         check_error();
197
198         for (GLint attr_index : { position_attribute_index, texcoord_attribute_index }) {
199                 if (attr_index == -1) continue;
200                 glDisableVertexAttribArray(attr_index);
201                 check_error();
202         }
203
204         glActiveTexture(GL_TEXTURE0);
205         check_error();
206         glUseProgram(0);
207         check_error();
208
209         glDeleteVertexArrays(1, &vao);
210         check_error();
211
212         glBindFramebuffer(GL_FRAMEBUFFER, 0);
213         check_error();
214 }