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