]> git.sesse.net Git - nageru/blob - pbo_frame_allocator.cpp
Add functionality for MJPEG export.
[nageru] / pbo_frame_allocator.cpp
1 #include "pbo_frame_allocator.h"
2
3 #include <bmusb/bmusb.h>
4 #include <movit/util.h>
5 #include <stdbool.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 #include <cstddef>
9
10 #include "flags.h"
11 #include "v210_converter.h"
12
13 using namespace std;
14
15 namespace {
16
17 void set_clamp_to_edge()
18 {
19         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
20         check_error();
21         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
22         check_error();
23         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
24         check_error();
25 }
26
27 }  // namespace
28
29 PBOFrameAllocator::PBOFrameAllocator(bmusb::PixelFormat pixel_format, size_t frame_size, GLuint width, GLuint height, size_t num_queued_frames, GLenum buffer, GLenum permissions, GLenum map_bits)
30         : pixel_format(pixel_format), buffer(buffer)
31 {
32         userdata.reset(new Userdata[num_queued_frames]);
33         for (size_t i = 0; i < num_queued_frames; ++i) {
34                 init_frame(i, frame_size, width, height, permissions, map_bits);
35         }
36         glBindBuffer(buffer, 0);
37         check_error();
38         glBindTexture(GL_TEXTURE_2D, 0);
39         check_error();
40 }
41
42 void PBOFrameAllocator::init_frame(size_t frame_idx, size_t frame_size, GLuint width, GLuint height, GLenum permissions, GLenum map_bits)
43 {
44         GLuint pbo;
45         glGenBuffers(1, &pbo);
46         check_error();
47         glBindBuffer(buffer, pbo);
48         check_error();
49         glBufferStorage(buffer, frame_size, nullptr, permissions | GL_MAP_PERSISTENT_BIT);
50         check_error();
51
52         Frame frame;
53         frame.data = (uint8_t *)glMapBufferRange(buffer, 0, frame_size, permissions | map_bits | GL_MAP_PERSISTENT_BIT);
54         frame.data2 = frame.data + frame_size / 2;
55         frame.data_copy = new uint8_t[frame_size];
56         check_error();
57         frame.size = frame_size;
58         frame.userdata = &userdata[frame_idx];
59         userdata[frame_idx].pbo = pbo;
60         userdata[frame_idx].pixel_format = pixel_format;
61         frame.owner = this;
62
63         // For 8-bit non-planar Y'CbCr, we ask the driver to split Y' and Cb/Cr
64         // into separate textures. For 10-bit, the input format (v210)
65         // is complicated enough that we need to interpolate up to 4:4:4,
66         // which we do in a compute shader ourselves. For BGRA, the data
67         // is already 4:4:4:4.
68         frame.interleaved = (pixel_format == bmusb::PixelFormat_8BitYCbCr);
69
70         // Create textures. We don't allocate any data for the second field at this point
71         // (just create the texture state with the samplers), since our default assumed
72         // resolution is progressive.
73         switch (pixel_format) {
74         case bmusb::PixelFormat_8BitYCbCr:
75                 glGenTextures(2, userdata[frame_idx].tex_y);
76                 check_error();
77                 glGenTextures(2, userdata[frame_idx].tex_cbcr);
78                 check_error();
79                 break;
80         case bmusb::PixelFormat_10BitYCbCr:
81                 glGenTextures(2, userdata[frame_idx].tex_v210);
82                 check_error();
83                 glGenTextures(2, userdata[frame_idx].tex_444);
84                 check_error();
85                 break;
86         case bmusb::PixelFormat_8BitBGRA:
87                 glGenTextures(2, userdata[frame_idx].tex_rgba);
88                 check_error();
89                 break;
90         case bmusb::PixelFormat_8BitYCbCrPlanar:
91                 glGenTextures(2, userdata[frame_idx].tex_y);
92                 check_error();
93                 glGenTextures(2, userdata[frame_idx].tex_cb);
94                 check_error();
95                 glGenTextures(2, userdata[frame_idx].tex_cr);
96                 check_error();
97                 break;
98         default:
99                 assert(false);
100         }
101
102         userdata[frame_idx].last_width[0] = width;
103         userdata[frame_idx].last_height[0] = height;
104         userdata[frame_idx].last_cbcr_width[0] = width / 2;
105         userdata[frame_idx].last_cbcr_height[0] = height;
106         userdata[frame_idx].last_v210_width[0] = 0;
107
108         userdata[frame_idx].last_width[1] = 0;
109         userdata[frame_idx].last_height[1] = 0;
110         userdata[frame_idx].last_cbcr_width[1] = 0;
111         userdata[frame_idx].last_cbcr_height[1] = 0;
112         userdata[frame_idx].last_v210_width[1] = 0;
113
114         userdata[frame_idx].last_interlaced = false;
115         userdata[frame_idx].last_has_signal = false;
116         userdata[frame_idx].last_is_connected = false;
117         for (unsigned field = 0; field < 2; ++field) {
118                 switch (pixel_format) {
119                 case bmusb::PixelFormat_10BitYCbCr: {
120                         const size_t v210_width = v210Converter::get_minimum_v210_texture_width(width);
121
122                         // Seemingly we need to set the minification filter even though
123                         // shader image loads don't use them, or NVIDIA will just give us
124                         // zero back.
125                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_v210[field]);
126                         check_error();
127                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
128                         check_error();
129                         if (field == 0) {
130                                 userdata[frame_idx].last_v210_width[0] = v210_width;
131                                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, v210_width, height, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr);
132                                 check_error();
133                         }
134
135                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_444[field]);
136                         check_error();
137                         set_clamp_to_edge();
138                         if (field == 0) {
139                                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr);
140                                 check_error();
141                         }
142                         break;
143                 }
144                 case bmusb::PixelFormat_8BitYCbCr:
145                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_y[field]);
146                         check_error();
147                         set_clamp_to_edge();
148                         if (field == 0) {
149                                 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
150                                 check_error();
151                         }
152
153                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_cbcr[field]);
154                         check_error();
155                         set_clamp_to_edge();
156                         if (field == 0) {
157                                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, width / 2, height, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr);
158                                 check_error();
159                         }
160                         break;
161                 case bmusb::PixelFormat_8BitBGRA:
162                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_rgba[field]);
163                         check_error();
164                         set_clamp_to_edge();
165                         if (field == 0) {
166                                 if (global_flags.can_disable_srgb_decoder) {  // See the comments in tweaked_inputs.h.
167                                         glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
168                                 } else {
169                                         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
170                                 }
171                                 check_error();
172                         }
173                         break;
174                 case bmusb::PixelFormat_8BitYCbCrPlanar:
175                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_y[field]);
176                         check_error();
177                         set_clamp_to_edge();
178                         if (field == 0) {
179                                 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
180                                 check_error();
181                         }
182
183                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_cb[field]);
184                         check_error();
185                         set_clamp_to_edge();
186                         if (field == 0) {
187                                 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width / 2, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
188                                 check_error();
189                         }
190
191                         glBindTexture(GL_TEXTURE_2D, userdata[frame_idx].tex_cr[field]);
192                         check_error();
193                         set_clamp_to_edge();
194                         if (field == 0) {
195                                 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width / 2, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
196                                 check_error();
197                         }
198                         break;
199                 default:
200                         assert(false);
201                 }
202         }
203
204         freelist.push(frame);
205 }
206
207 PBOFrameAllocator::~PBOFrameAllocator()
208 {
209         while (!freelist.empty()) {
210                 Frame frame = freelist.front();
211                 freelist.pop();
212                 destroy_frame(&frame);
213         }
214 }
215
216 void PBOFrameAllocator::destroy_frame(Frame *frame)
217 {
218         delete[] frame->data_copy;
219
220         GLuint pbo = ((Userdata *)frame->userdata)->pbo;
221         glBindBuffer(buffer, pbo);
222         check_error();
223         glUnmapBuffer(buffer);
224         check_error();
225         glBindBuffer(buffer, 0);
226         check_error();
227         glDeleteBuffers(1, &pbo);
228         check_error();
229         switch (pixel_format) {
230         case bmusb::PixelFormat_10BitYCbCr:
231                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_v210);
232                 check_error();
233                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_444);
234                 check_error();
235                 break;
236         case bmusb::PixelFormat_8BitYCbCr:
237                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_y);
238                 check_error();
239                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_cbcr);
240                 check_error();
241                 break;
242         case bmusb::PixelFormat_8BitBGRA:
243                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_rgba);
244                 check_error();
245                 break;
246         case bmusb::PixelFormat_8BitYCbCrPlanar:
247                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_y);
248                 check_error();
249                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_cb);
250                 check_error();
251                 glDeleteTextures(2, ((Userdata *)frame->userdata)->tex_cr);
252                 check_error();
253                 break;
254         default:
255                 assert(false);
256         }
257 }
258 //static int sumsum = 0;
259
260 bmusb::FrameAllocator::Frame PBOFrameAllocator::alloc_frame()
261 {
262         Frame vf;
263
264         unique_lock<mutex> lock(freelist_mutex);  // Meh.
265         if (freelist.empty()) {
266                 printf("Frame overrun (no more spare PBO frames), dropping frame!\n");
267         } else {
268                 //fprintf(stderr, "freelist has %d allocated\n", ++sumsum);
269                 vf = freelist.front();
270                 freelist.pop();  // Meh.
271         }
272         vf.len = 0;
273         vf.overflow = 0;
274         return vf;
275 }
276
277 void PBOFrameAllocator::release_frame(Frame frame)
278 {
279         if (frame.overflow > 0) {
280                 printf("%d bytes overflow after last (PBO) frame\n", int(frame.overflow));
281         }
282
283 #if 0
284         // Poison the page. (Note that this might be bogus if you don't have an OpenGL context.)
285         memset(frame.data, 0, frame.size);
286         Userdata *userdata = (Userdata *)frame.userdata;
287         for (unsigned field = 0; field < 2; ++field) {
288                 glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]);
289                 check_error();
290                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
291                 check_error();
292                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
293                 check_error();
294                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
295                 check_error();
296                 glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, userdata->last_width[field], userdata->last_height[field], 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
297                 check_error();
298
299                 glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]);
300                 check_error();
301                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
302                 check_error();
303                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
304                 check_error();
305                 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
306                 check_error();
307                 glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, userdata->last_width[field] / 2, userdata->last_height[field], 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
308                 check_error();
309         }
310 #endif
311
312         unique_lock<mutex> lock(freelist_mutex);
313         freelist.push(frame);
314         //--sumsum;
315 }