]> git.sesse.net Git - nageru/blob - chroma_subsampler.cpp
Move chroma subsampling into its own class.
[nageru] / chroma_subsampler.cpp
1 #include "chroma_subsampler.h"
2
3 #include <vector>
4
5 #include <movit/effect_util.h>
6 #include <movit/resource_pool.h>
7 #include <movit/util.h>
8
9 using namespace movit;
10 using namespace std;
11
12 ChromaSubsampler::ChromaSubsampler(ResourcePool *resource_pool)
13         : resource_pool(resource_pool)
14 {
15         // Set up stuff for NV12 conversion.
16         //
17         // Note: Due to the horizontally co-sited chroma/luma samples in H.264
18         // (chrome position is left for horizontal and center for vertical),
19         // we need to be a bit careful in our subsampling. A diagram will make
20         // this clearer, showing some luma and chroma samples:
21         //
22         //     a   b   c   d
23         //   +---+---+---+---+
24         //   |   |   |   |   |
25         //   | Y | Y | Y | Y |
26         //   |   |   |   |   |
27         //   +---+---+---+---+
28         //
29         // +-------+-------+
30         // |       |       |
31         // |   C   |   C   |
32         // |       |       |
33         // +-------+-------+
34         //
35         // Clearly, the rightmost chroma sample here needs to be equivalent to
36         // b/4 + c/2 + d/4. (We could also implement more sophisticated filters,
37         // of course, but as long as the upsampling is not going to be equally
38         // sophisticated, it's probably not worth it.) If we sample once with
39         // no mipmapping, we get just c, ie., no actual filtering in the
40         // horizontal direction. (For the vertical direction, we can just
41         // sample in the middle to get the right filtering.) One could imagine
42         // we could use mipmapping (assuming we can create mipmaps cheaply),
43         // but then, what we'd get is this:
44         //
45         //    (a+b)/2 (c+d)/2
46         //   +-------+-------+
47         //   |       |       |
48         //   |   Y   |   Y   |
49         //   |       |       |
50         //   +-------+-------+
51         //
52         // +-------+-------+
53         // |       |       |
54         // |   C   |   C   |
55         // |       |       |
56         // +-------+-------+
57         //
58         // which ends up sampling equally from a and b, which clearly isn't right. Instead,
59         // we need to do two (non-mipmapped) chroma samples, both hitting exactly in-between
60         // source pixels.
61         //
62         // Sampling in-between b and c gives us the sample (b+c)/2, and similarly for c and d.
63         // Taking the average of these gives of (b+c)/4 + (c+d)/4 = b/4 + c/2 + d/4, which is
64         // exactly what we want.
65         //
66         // See also http://www.poynton.com/PDFs/Merging_RGB_and_422.pdf, pages 6–7.
67
68         // Cb/Cr shader.
69         string cbcr_vert_shader =
70                 "#version 130 \n"
71                 " \n"
72                 "in vec2 position; \n"
73                 "in vec2 texcoord; \n"
74                 "out vec2 tc0, tc1; \n"
75                 "uniform vec2 foo_chroma_offset_0; \n"
76                 "uniform vec2 foo_chroma_offset_1; \n"
77                 " \n"
78                 "void main() \n"
79                 "{ \n"
80                 "    // The result of glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0) is: \n"
81                 "    // \n"
82                 "    //   2.000  0.000  0.000 -1.000 \n"
83                 "    //   0.000  2.000  0.000 -1.000 \n"
84                 "    //   0.000  0.000 -2.000 -1.000 \n"
85                 "    //   0.000  0.000  0.000  1.000 \n"
86                 "    gl_Position = vec4(2.0 * position.x - 1.0, 2.0 * position.y - 1.0, -1.0, 1.0); \n"
87                 "    vec2 flipped_tc = texcoord; \n"
88                 "    tc0 = flipped_tc + foo_chroma_offset_0; \n"
89                 "    tc1 = flipped_tc + foo_chroma_offset_1; \n"
90                 "} \n";
91         string cbcr_frag_shader =
92                 "#version 130 \n"
93                 "in vec2 tc0, tc1; \n"
94                 "uniform sampler2D cbcr_tex; \n"
95                 "out vec4 FragColor; \n"
96                 "void main() { \n"
97                 "    FragColor = 0.5 * (texture(cbcr_tex, tc0) + texture(cbcr_tex, tc1)); \n"
98                 "} \n";
99         vector<string> frag_shader_outputs;
100         cbcr_program_num = resource_pool->compile_glsl_program(cbcr_vert_shader, cbcr_frag_shader, frag_shader_outputs);
101         check_error();
102
103         float vertices[] = {
104                 0.0f, 2.0f,
105                 0.0f, 0.0f,
106                 2.0f, 0.0f
107         };
108         cbcr_vbo = generate_vbo(2, GL_FLOAT, sizeof(vertices), vertices);
109         check_error();
110         cbcr_texture_sampler_uniform = glGetUniformLocation(cbcr_program_num, "cbcr_tex");
111         check_error();
112         cbcr_position_attribute_index = glGetAttribLocation(cbcr_program_num, "position");
113         check_error();
114         cbcr_texcoord_attribute_index = glGetAttribLocation(cbcr_program_num, "texcoord");
115         check_error();
116 }
117
118 ChromaSubsampler::~ChromaSubsampler()
119 {
120         resource_pool->release_glsl_program(cbcr_program_num);
121         check_error();
122         glDeleteBuffers(1, &cbcr_vbo);
123         check_error();
124 }
125
126 void ChromaSubsampler::subsample_chroma(GLuint cbcr_tex, unsigned width, unsigned height, GLuint dst_tex)
127 {
128         GLuint vao;
129         glGenVertexArrays(1, &vao);
130         check_error();
131
132         glBindVertexArray(vao);
133         check_error();
134
135         // Extract Cb/Cr.
136         GLuint fbo = resource_pool->create_fbo(dst_tex);
137         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
138         glViewport(0, 0, width/2, height/2);
139         check_error();
140
141         glUseProgram(cbcr_program_num);
142         check_error();
143
144         glActiveTexture(GL_TEXTURE0);
145         check_error();
146         glBindTexture(GL_TEXTURE_2D, cbcr_tex);
147         check_error();
148         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
149         check_error();
150         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
151         check_error();
152         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
153         check_error();
154
155         float chroma_offset_0[] = { -1.0f / width, 0.0f };
156         float chroma_offset_1[] = { -0.0f / width, 0.0f };
157         set_uniform_vec2(cbcr_program_num, "foo", "chroma_offset_0", chroma_offset_0);
158         set_uniform_vec2(cbcr_program_num, "foo", "chroma_offset_1", chroma_offset_1);
159
160         glUniform1i(cbcr_texture_sampler_uniform, 0);
161
162         glBindBuffer(GL_ARRAY_BUFFER, cbcr_vbo);
163         check_error();
164
165         for (GLint attr_index : { cbcr_position_attribute_index, cbcr_texcoord_attribute_index }) {
166                 glEnableVertexAttribArray(attr_index);
167                 check_error();
168                 glVertexAttribPointer(attr_index, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
169                 check_error();
170         }
171
172         glDrawArrays(GL_TRIANGLES, 0, 3);
173         check_error();
174
175         for (GLint attr_index : { cbcr_position_attribute_index, cbcr_texcoord_attribute_index }) {
176                 glDisableVertexAttribArray(attr_index);
177                 check_error();
178         }
179
180         glUseProgram(0);
181         check_error();
182         glBindFramebuffer(GL_FRAMEBUFFER, 0);
183         check_error();
184
185         resource_pool->release_fbo(fbo);
186         glDeleteVertexArrays(1, &vao);
187         check_error();
188 }