]> git.sesse.net Git - nageru/blob - futatabi/chroma_subsampler.cpp
Fix an issue where scrubbing in Futatabi could lock up if there were no inputs going...
[nageru] / futatabi / chroma_subsampler.cpp
1 #include "chroma_subsampler.h"
2
3 #include "embedded_files.h"
4
5 #include <movit/util.h>
6 #include <string>
7
8 #define BUFFER_OFFSET(i) ((char *)nullptr + (i))
9
10 using namespace std;
11
12 string read_file(const string &filename, const unsigned char *start = nullptr, const size_t size = 0);
13 GLuint compile_shader(const string &shader_src, GLenum type);
14 GLuint link_program(GLuint vs_obj, GLuint fs_obj);
15 void bind_sampler(GLuint program, GLint location, GLuint texture_unit, GLuint tex, GLuint sampler);
16
17 extern GLuint linear_sampler;
18
19 ChromaSubsampler::ChromaSubsampler()
20 {
21         // Set up stuff for 4:2:2 conversion.
22         //
23         // Note: Due to the horizontally co-sited chroma/luma samples in H.264
24         // (chroma position is left for horizontal),
25         // we need to be a bit careful in our subsampling. A diagram will make
26         // this clearer, showing some luma and chroma samples:
27         //
28         //     a   b   c   d
29         //   +---+---+---+---+
30         //   |   |   |   |   |
31         //   | Y | Y | Y | Y |
32         //   |   |   |   |   |
33         //   +---+---+---+---+
34         //
35         // +-------+-------+
36         // |       |       |
37         // |   C   |   C   |
38         // |       |       |
39         // +-------+-------+
40         //
41         // Clearly, the rightmost chroma sample here needs to be equivalent to
42         // b/4 + c/2 + d/4. (We could also implement more sophisticated filters,
43         // of course, but as long as the upsampling is not going to be equally
44         // sophisticated, it's probably not worth it.) If we sample once with
45         // no mipmapping, we get just c, ie., no actual filtering in the
46         // horizontal direction. (For the vertical direction, we can just
47         // sample in the middle to get the right filtering.) One could imagine
48         // we could use mipmapping (assuming we can create mipmaps cheaply),
49         // but then, what we'd get is this:
50         //
51         //    (a+b)/2 (c+d)/2
52         //   +-------+-------+
53         //   |       |       |
54         //   |   Y   |   Y   |
55         //   |       |       |
56         //   +-------+-------+
57         //
58         // +-------+-------+
59         // |       |       |
60         // |   C   |   C   |
61         // |       |       |
62         // +-------+-------+
63         //
64         // which ends up sampling equally from a and b, which clearly isn't right. Instead,
65         // we need to do two (non-mipmapped) chroma samples, both hitting exactly in-between
66         // source pixels.
67         //
68         // Sampling in-between b and c gives us the sample (b+c)/2, and similarly for c and d.
69         // Taking the average of these gives of (b+c)/4 + (c+d)/4 = b/4 + c/2 + d/4, which is
70         // exactly what we want.
71         //
72         // See also http://www.poynton.com/PDFs/Merging_RGB_and_422.pdf, pages 6–7.
73
74         cbcr_vs_obj = compile_shader(read_file("chroma_subsample.vert", _binary_chroma_subsample_vert_data, _binary_chroma_subsample_vert_size), GL_VERTEX_SHADER);
75         cbcr_fs_obj = compile_shader(read_file("chroma_subsample.frag", _binary_chroma_subsample_frag_data, _binary_chroma_subsample_frag_size), GL_FRAGMENT_SHADER);
76         cbcr_program = link_program(cbcr_vs_obj, cbcr_fs_obj);
77
78         // Set up the VAO containing all the required position data.
79         glCreateVertexArrays(1, &vao);
80         glBindVertexArray(vao);
81
82         float vertices[] = {
83                 0.0f, 2.0f,
84                 0.0f, 0.0f,
85                 2.0f, 0.0f
86         };
87         glCreateBuffers(1, &vbo);
88         glNamedBufferData(vbo, sizeof(vertices), vertices, GL_STATIC_DRAW);
89         glBindBuffer(GL_ARRAY_BUFFER, vbo);
90
91         GLint position_attrib = 0;  // Hard-coded in every vertex shader.
92         glEnableVertexArrayAttrib(vao, position_attrib);
93         glVertexAttribPointer(position_attrib, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
94
95         uniform_cbcr_tex = glGetUniformLocation(cbcr_program, "cbcr_tex");
96         uniform_chroma_offset_0 = glGetUniformLocation(cbcr_program, "chroma_offset_0");
97         uniform_chroma_offset_1 = glGetUniformLocation(cbcr_program, "chroma_offset_1");
98 }
99
100 ChromaSubsampler::~ChromaSubsampler()
101 {
102         glDeleteProgram(cbcr_program);
103         check_error();
104         glDeleteBuffers(1, &vbo);
105         check_error();
106         glDeleteVertexArrays(1, &vao);
107         check_error();
108 }
109
110 void ChromaSubsampler::subsample_chroma(GLuint cbcr_tex, unsigned width, unsigned height, GLuint cb_tex, GLuint cr_tex)
111 {
112         glUseProgram(cbcr_program);
113         bind_sampler(cbcr_program, uniform_cbcr_tex, 0, cbcr_tex, linear_sampler);
114         glProgramUniform2f(cbcr_program, uniform_chroma_offset_0, -1.0f / width, 0.0f);
115         glProgramUniform2f(cbcr_program, uniform_chroma_offset_1, -0.0f / width, 0.0f);
116
117         glViewport(0, 0, width / 2, height);
118         fbos.render_to(cb_tex, cr_tex);
119
120         glBindVertexArray(vao);
121         glDrawArrays(GL_TRIANGLES, 0, 3);
122 }