• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "ultrahdr/ultrahdrcommon.h"
18 #include "ultrahdr/gainmapmath.h"
19 #include "ultrahdr/jpegr.h"
20 
21 namespace ultrahdr {
22 
23 extern const std::string vertex_shader = R"__SHADER__(#version 300 es
24   precision highp float;
25 
26   layout(location = 0) in vec4 aPos;
27   layout(location = 1) in vec2 aTexCoord;
28 
29   out vec2 TexCoord;
30 
31   void main() {
32     gl_Position = aPos;
33     TexCoord = aTexCoord;
34   }
35 )__SHADER__";
36 
37 static const std::string getYuv444PixelShader = R"__SHADER__(
38   uniform sampler2D yuvTexture;
39   uniform int pWidth, pHeight;
40 
41   vec3 getYUVPixel() {
42     // Convert texCoord to pixel coordinates
43     ivec2 pixelCoord = ivec2(TexCoord * vec2(pWidth, pHeight));
44 
45     float y = texelFetch(yuvTexture, ivec2(pixelCoord.r, pixelCoord.g), 0).r;
46     float u = texelFetch(yuvTexture, ivec2(pixelCoord.r, pixelCoord.g + pHeight), 0).r;
47     float v = texelFetch(yuvTexture, ivec2(pixelCoord.r, pixelCoord.g + 2 * pHeight), 0).r;
48 
49     return vec3(y, u, v);
50   }
51 )__SHADER__";
52 
53 static const std::string getYuv422PixelShader = R"__SHADER__(
54   uniform sampler2D yuvTexture;
55   uniform int pWidth, pHeight;
56 
57   vec3 getYUVPixel() {
58     // Convert texCoord to pixel coordinates
59     ivec2 pixelCoord = ivec2(TexCoord * vec2(pWidth, pHeight));
60     ivec2 uvCoord = ivec2(pixelCoord.r / 2, pixelCoord.g);
61     int uvWidth = pWidth / 2;
62     int uvHeight = pHeight;
63     uint yPlaneSize = uint(pWidth) * uint(pHeight);
64     uint uPlaneSize = uint(uvWidth) * uint(uvHeight);
65     uint yIndex = uint(pixelCoord.g * pWidth + pixelCoord.r);
66     uint uIndex = yPlaneSize + uint(uvCoord.g * uvWidth + uvCoord.r);
67     uint vIndex = yPlaneSize + uPlaneSize + uint(uvCoord.g * uvWidth + uvCoord.r);
68 
69     float y = texelFetch(yuvTexture, ivec2(yIndex % uint(pWidth), yIndex / uint(pWidth)), 0).r;
70     float u = texelFetch(yuvTexture, ivec2(uIndex % uint(pWidth), uIndex / uint(pWidth)), 0).r;
71     float v = texelFetch(yuvTexture, ivec2(vIndex % uint(pWidth), vIndex / uint(pWidth)), 0).r;
72 
73     return vec3(y, u, v);
74   }
75 )__SHADER__";
76 
77 static const std::string getYuv420PixelShader = R"__SHADER__(
78   uniform sampler2D yuvTexture;
79   uniform int pWidth, pHeight;
80 
81   vec3 getYUVPixel() {
82     // Convert texCoord to pixel coordinates
83     ivec2 pixelCoord = ivec2(TexCoord * vec2(pWidth, pHeight));
84     ivec2 uvCoord = pixelCoord / 2;
85     int uvWidth = pWidth / 2;
86     int uvHeight = pHeight / 2;
87     uint yPlaneSize = uint(pWidth) * uint(pHeight);
88     uint uPlaneSize = uint(uvWidth) * uint(uvHeight);
89     uint yIndex = uint(pixelCoord.g * pWidth + pixelCoord.r);
90     uint uIndex = yPlaneSize + uint(uvCoord.g * uvWidth + uvCoord.r);
91     uint vIndex = yPlaneSize + uPlaneSize + uint(uvCoord.g * uvWidth + uvCoord.r);
92 
93     float y = texelFetch(yuvTexture, ivec2(yIndex % uint(pWidth), yIndex / uint(pWidth)), 0).r;
94     float u = texelFetch(yuvTexture, ivec2(uIndex % uint(pWidth), uIndex / uint(pWidth)), 0).r;
95     float v = texelFetch(yuvTexture, ivec2(vIndex % uint(pWidth), vIndex / uint(pWidth)), 0).r;
96 
97     return vec3(y, u, v);
98   }
99 )__SHADER__";
100 
101 static const std::string p3YUVToRGBShader = R"__SHADER__(
102   vec3 p3YuvToRgb(const vec3 color) {
103     const vec3 offset = vec3(0.0, 128.0f / 255.0f, 128.0f / 255.0f);
104     const mat3 transform = mat3(
105         1.0,  1.0, 1.0,
106         0.0, -0.344136286, 1.772,
107         1.402, -0.714136286, 0.0);
108     return clamp(transform * (color - offset), 0.0, 1.0);
109   }
110 )__SHADER__";
111 
112 static const std::string sRGBEOTFShader = R"__SHADER__(
113   float sRGBEOTF(float e_gamma) {
114     return e_gamma <= 0.04045 ? e_gamma / 12.92 : pow((e_gamma + 0.055) / 1.055, 2.4);
115   }
116 
117   vec3 sRGBEOTF(const vec3 e_gamma) {
118     return vec3(sRGBEOTF(e_gamma.r), sRGBEOTF(e_gamma.g), sRGBEOTF(e_gamma.b));
119   }
120 )__SHADER__";
121 
122 static const std::string getGainMapSampleSingleChannel = R"__SHADER__(
123   uniform sampler2D gainMapTexture;
124 
125   vec3 sampleMap(sampler2D map) { return vec3(texture(map, TexCoord).r); }
126 )__SHADER__";
127 
128 static const std::string getGainMapSampleMultiChannel = R"__SHADER__(
129   uniform sampler2D gainMapTexture;
130 
131   vec3 sampleMap(sampler2D map) { return texture(map, TexCoord).rgb; }
132 )__SHADER__";
133 
134 static const std::string applyGainMapShader = R"__SHADER__(
135   uniform float gamma[3];
136   uniform float logMinBoost[3];
137   uniform float logMaxBoost[3];
138   uniform float weight;
139   uniform float offsetSdr[3];
140   uniform float offsetHdr[3];
141   uniform float normalize;
142 
143   float applyGainMapSample(const float channel, float gain, int idx) {
144     gain = pow(gain, 1.0f / gamma[idx]);
145     float logBoost = logMinBoost[idx] * (1.0f - gain) + logMaxBoost[idx] * gain;
146     logBoost = exp2(logBoost * weight);
147     return ((channel + offsetSdr[idx]) * logBoost - offsetHdr[idx]) / normalize;
148   }
149 
150   vec3 applyGain(const vec3 color, const vec3 gain) {
151     return vec3(applyGainMapSample(color.r, gain.r, 0),
152             applyGainMapSample(color.g, gain.g, 1),
153             applyGainMapSample(color.b, gain.b, 2));
154   }
155 )__SHADER__";
156 
157 static const std::string hlgOETFShader = R"__SHADER__(
158   float OETF(const float linear) {
159     const float kHlgA = 0.17883277;
160     const float kHlgB = 0.28466892;
161     const float kHlgC = 0.55991073;
162     return linear <= 1.0 / 12.0 ? sqrt(3.0 * linear) : kHlgA * log(12.0 * linear - kHlgB) + kHlgC;
163   }
164 
165   vec3 OETF(const vec3 linear) {
166     return vec3(OETF(linear.r), OETF(linear.g), OETF(linear.b));
167   }
168 )__SHADER__";
169 
170 static const std::string pqOETFShader = R"__SHADER__(
171   vec3 OETF(const vec3 linear) {
172     const float kPqM1 = (2610.0 / 4096.0) / 4.0;
173     const float kPqM2 = (2523.0 / 4096.0) * 128.0;
174     const float kPqC1 = (3424.0 / 4096.0);
175     const float kPqC2 = (2413.0 / 4096.0) * 32.0;
176     const float kPqC3 = (2392.0 / 4096.0) * 32.0;
177     vec3 tmp = pow(linear, vec3(kPqM1));
178     tmp = (kPqC1 + kPqC2 * tmp) / (1.0 + kPqC3 * tmp);
179     return pow(tmp, vec3(kPqM2));
180   }
181 )__SHADER__";
182 
183 static const std::string hlgInverseOOTFShader = R"__SHADER__(
184   float InverseOOTF(const float linear) {
185     const float kOotfGamma = 1.2f;
186     return pow(linear, 1.0f / kOotfGamma);
187   }
188 
189   vec3 InverseOOTF(const vec3 linear) {
190     return vec3(InverseOOTF(linear.r), InverseOOTF(linear.g), InverseOOTF(linear.b));
191   }
192 )__SHADER__";
193 
194 template <typename... Args>
StringFormat(const std::string & format,Args...args)195 std::string StringFormat(const std::string& format, Args... args) {
196   auto size = std::snprintf(nullptr, 0, format.c_str(), args...);
197   if (size < 0) return std::string();
198   std::vector<char> buffer(size + 1);  // Add 1 for terminating null byte
199   std::snprintf(buffer.data(), buffer.size(), format.c_str(), args...);
200   return std::string(buffer.data(), size);  // Exclude the terminating null byte
201 }
202 
getClampPixelFloatShader(uhdr_color_transfer_t output_ct)203 std::string getClampPixelFloatShader(uhdr_color_transfer_t output_ct) {
204   return StringFormat(
205       "  vec3 clampPixelFloat(const vec3 color) {\n"
206       "    return clamp(color, 0.0, %f);\n"
207       "  }\n",
208       output_ct == UHDR_CT_LINEAR ? kMaxPixelFloatHdrLinear : kMaxPixelFloat);
209 }
210 
getGamutConversionShader(uhdr_color_gamut_t src_cg,uhdr_color_gamut_t dst_cg)211 std::string getGamutConversionShader(uhdr_color_gamut_t src_cg, uhdr_color_gamut_t dst_cg) {
212   const float* coeffs = nullptr;
213   if (dst_cg == UHDR_CG_BT_709) {
214     if (src_cg == UHDR_CG_DISPLAY_P3) {
215       coeffs = kP3ToBt709.data();
216     } else if (src_cg == UHDR_CG_BT_2100) {
217       coeffs = kBt2100ToBt709.data();
218     }
219   } else if (dst_cg == UHDR_CG_DISPLAY_P3) {
220     if (src_cg == UHDR_CG_BT_709) {
221       coeffs = kBt709ToP3.data();
222     }
223     if (src_cg == UHDR_CG_BT_2100) {
224       coeffs = kBt2100ToP3.data();
225     }
226   } else if (dst_cg == UHDR_CG_BT_2100) {
227     if (src_cg == UHDR_CG_BT_709) {
228       coeffs = kBt709ToBt2100.data();
229     } else if (src_cg == UHDR_CG_DISPLAY_P3) {
230       coeffs = kP3ToBt2100.data();
231     }
232   }
233   return StringFormat(
234       "  vec3 gamutConversion(const vec3 color) {\n"
235       "    const mat3 transform = mat3(\n"
236       "      %f, %f, %f,\n"
237       "      %f, %f, %f,\n"
238       "      %f, %f, %f);\n"
239       "    return transform * color;\n"
240       "  }\n",
241       coeffs[0], coeffs[3], coeffs[6], coeffs[1], coeffs[4], coeffs[7], coeffs[2], coeffs[5],
242       coeffs[8]);
243 }
244 
getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt,uhdr_img_fmt gm_fmt,uhdr_color_transfer output_ct,uhdr_color_gamut_t sdr_cg,uhdr_color_gamut_t hdr_cg,bool use_base_cg)245 std::string getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt, uhdr_img_fmt gm_fmt,
246                                           uhdr_color_transfer output_ct, uhdr_color_gamut_t sdr_cg,
247                                           uhdr_color_gamut_t hdr_cg, bool use_base_cg) {
248   std::string shader_code = R"__SHADER__(#version 300 es
249     precision highp float;
250     precision highp int;
251 
252     out vec4 FragColor;
253     in vec2 TexCoord;
254   )__SHADER__";
255 
256   if (sdr_fmt == UHDR_IMG_FMT_24bppYCbCr444) {
257     shader_code.append(getYuv444PixelShader);
258   } else if (sdr_fmt == UHDR_IMG_FMT_16bppYCbCr422) {
259     shader_code.append(getYuv422PixelShader);
260   } else if (sdr_fmt == UHDR_IMG_FMT_12bppYCbCr420) {
261     shader_code.append(getYuv420PixelShader);
262   }
263   shader_code.append(p3YUVToRGBShader);
264   shader_code.append(sRGBEOTFShader);
265   shader_code.append(gm_fmt == UHDR_IMG_FMT_8bppYCbCr400 ? getGainMapSampleSingleChannel
266                                                          : getGainMapSampleMultiChannel);
267   shader_code.append(applyGainMapShader);
268   if (sdr_cg != hdr_cg) shader_code.append(getGamutConversionShader(sdr_cg, hdr_cg));
269   shader_code.append(getClampPixelFloatShader(output_ct));
270   if (output_ct == UHDR_CT_HLG) {
271     shader_code.append(hlgInverseOOTFShader);
272     shader_code.append(hlgOETFShader);
273   } else if (output_ct == UHDR_CT_PQ) {
274     shader_code.append(pqOETFShader);
275   }
276   shader_code.append(R"__SHADER__(
277     void main() {
278       vec3 yuv_gamma_sdr = getYUVPixel();
279       vec3 rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
280       vec3 rgb_sdr = sRGBEOTF(rgb_gamma_sdr);
281   )__SHADER__");
282   if (sdr_cg != hdr_cg && !use_base_cg) {
283     shader_code.append(R"__SHADER__(
284       rgb_sdr = gamutConversion(rgb_sdr);
285     )__SHADER__");
286   }
287   shader_code.append(R"__SHADER__(
288       vec3 gain = sampleMap(gainMapTexture);
289       vec3 rgb_hdr = applyGain(rgb_sdr, gain);
290   )__SHADER__");
291   if (sdr_cg != hdr_cg && use_base_cg) {
292     shader_code.append(R"__SHADER__(
293       rgb_hdr = gamutConversion(rgb_hdr);
294     )__SHADER__");
295   }
296   shader_code.append(R"__SHADER__(
297       rgb_hdr = clampPixelFloat(rgb_hdr);
298   )__SHADER__");
299   if (output_ct == UHDR_CT_HLG) {
300     shader_code.append(R"__SHADER__(
301       rgb_hdr = InverseOOTF(rgb_hdr);
302       rgb_hdr = OETF(rgb_hdr);
303     )__SHADER__");
304   } else if (output_ct == UHDR_CT_PQ) {
305     shader_code.append(R"__SHADER__(
306       rgb_hdr = OETF(rgb_hdr);
307     )__SHADER__");
308   }
309   shader_code.append(R"__SHADER__(
310       FragColor = vec4(rgb_hdr, 1.0);
311     }
312   )__SHADER__");
313   return shader_code;
314 }
315 
isBufferDataContiguous(uhdr_raw_image_t * img)316 bool isBufferDataContiguous(uhdr_raw_image_t* img) {
317   if (img->fmt == UHDR_IMG_FMT_32bppRGBA8888 || img->fmt == UHDR_IMG_FMT_24bppRGB888 ||
318       img->fmt == UHDR_IMG_FMT_8bppYCbCr400 || img->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
319       img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
320     return img->stride[UHDR_PLANE_PACKED] == img->w;
321   } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
322     uint16_t* y = static_cast<uint16_t*>(img->planes[UHDR_PLANE_Y]);
323     uint16_t* u = static_cast<uint16_t*>(img->planes[UHDR_PLANE_UV]);
324     std::ptrdiff_t sz = u - y;
325     long pixels = img->w * img->h;
326     return img->stride[UHDR_PLANE_Y] == img->w && img->stride[UHDR_PLANE_UV] == img->w &&
327            sz == pixels;
328   } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420 || img->fmt == UHDR_IMG_FMT_24bppYCbCr444 ||
329              img->fmt == UHDR_IMG_FMT_16bppYCbCr422) {
330     int h_samp_factor = img->fmt == UHDR_IMG_FMT_24bppYCbCr444 ? 1 : 2;
331     int v_samp_factor = img->fmt == UHDR_IMG_FMT_12bppYCbCr420 ? 2 : 1;
332     uint8_t* y = static_cast<uint8_t*>(img->planes[UHDR_PLANE_Y]);
333     uint8_t* u = static_cast<uint8_t*>(img->planes[UHDR_PLANE_U]);
334     uint8_t* v = static_cast<uint8_t*>(img->planes[UHDR_PLANE_V]);
335     std::ptrdiff_t sz_a = u - y, sz_b = v - u;
336     long pixels = img->w * img->h;
337     return img->stride[UHDR_PLANE_Y] == img->w &&
338            img->stride[UHDR_PLANE_U] == img->w / h_samp_factor &&
339            img->stride[UHDR_PLANE_V] == img->w / h_samp_factor && sz_a == pixels &&
340            sz_b == pixels / (h_samp_factor * v_samp_factor);
341   }
342   return false;
343 }
344 
applyGainMapGLES(uhdr_raw_image_t * sdr_intent,uhdr_raw_image_t * gainmap_img,uhdr_gainmap_metadata_ext_t * gainmap_metadata,uhdr_color_transfer_t output_ct,float display_boost,uhdr_color_gamut_t sdr_cg,uhdr_color_gamut_t hdr_cg,uhdr_opengl_ctxt_t * opengl_ctxt)345 uhdr_error_info_t applyGainMapGLES(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img,
346                                    uhdr_gainmap_metadata_ext_t* gainmap_metadata,
347                                    uhdr_color_transfer_t output_ct, float display_boost,
348                                    uhdr_color_gamut_t sdr_cg, uhdr_color_gamut_t hdr_cg,
349                                    uhdr_opengl_ctxt_t* opengl_ctxt) {
350   GLuint shaderProgram = 0;  // shader program
351   GLuint yuvTexture = 0;     // sdr intent texture
352   GLuint frameBuffer = 0;
353 
354 #define RET_IF_ERR()                                           \
355   if (opengl_ctxt->mErrorStatus.error_code != UHDR_CODEC_OK) { \
356     if (frameBuffer) glDeleteFramebuffers(1, &frameBuffer);    \
357     if (yuvTexture) glDeleteTextures(1, &yuvTexture);          \
358     if (shaderProgram) glDeleteProgram(shaderProgram);         \
359     return opengl_ctxt->mErrorStatus;                          \
360   }
361 
362   shaderProgram = opengl_ctxt->create_shader_program(
363       vertex_shader.c_str(),
364       getApplyGainMapFragmentShader(sdr_intent->fmt, gainmap_img->fmt, output_ct, sdr_cg, hdr_cg,
365                                     gainmap_metadata->use_base_cg)
366           .c_str());
367   RET_IF_ERR()
368 
369   yuvTexture = opengl_ctxt->create_texture(sdr_intent->fmt, sdr_intent->w, sdr_intent->h,
370                                            sdr_intent->planes[0]);
371   opengl_ctxt->mGainmapImgTexture = opengl_ctxt->create_texture(
372       gainmap_img->fmt, gainmap_img->w, gainmap_img->h, gainmap_img->planes[0]);
373   opengl_ctxt->mDecodedImgTexture = opengl_ctxt->create_texture(
374       output_ct == UHDR_CT_LINEAR ? UHDR_IMG_FMT_64bppRGBAHalfFloat : UHDR_IMG_FMT_32bppRGBA1010102,
375       sdr_intent->w, sdr_intent->h, nullptr);
376   RET_IF_ERR()
377 
378   frameBuffer = opengl_ctxt->setup_framebuffer(opengl_ctxt->mDecodedImgTexture);
379   RET_IF_ERR()
380 
381   glViewport(0, 0, sdr_intent->w, sdr_intent->h);
382   glUseProgram(shaderProgram);
383 
384   // Get the location of the uniform variables
385   GLint pWidthLocation = glGetUniformLocation(shaderProgram, "pWidth");
386   GLint pHeightLocation = glGetUniformLocation(shaderProgram, "pHeight");
387   GLint gammaLocation = glGetUniformLocation(shaderProgram, "gamma");
388   GLint logMinBoostLocation = glGetUniformLocation(shaderProgram, "logMinBoost");
389   GLint logMaxBoostLocation = glGetUniformLocation(shaderProgram, "logMaxBoost");
390   GLint weightLocation = glGetUniformLocation(shaderProgram, "weight");
391   GLint offsetSdrLocation = glGetUniformLocation(shaderProgram, "offsetSdr");
392   GLint offsetHdrLocation = glGetUniformLocation(shaderProgram, "offsetHdr");
393   GLint normalizeLocation = glGetUniformLocation(shaderProgram, "normalize");
394 
395   glUniform1i(pWidthLocation, sdr_intent->w);
396   glUniform1i(pHeightLocation, sdr_intent->h);
397   glUniform1fv(gammaLocation, 3, gainmap_metadata->gamma);
398   float logMinBoostValues[3] = {static_cast<float>(log2(gainmap_metadata->min_content_boost[0])),
399                                 static_cast<float>(log2(gainmap_metadata->min_content_boost[1])),
400                                 static_cast<float>(log2(gainmap_metadata->min_content_boost[2]))};
401   float logMaxBoostValues[3] = {static_cast<float>(log2(gainmap_metadata->max_content_boost[0])),
402                                 static_cast<float>(log2(gainmap_metadata->max_content_boost[1])),
403                                 static_cast<float>(log2(gainmap_metadata->max_content_boost[2]))};
404   glUniform1fv(logMinBoostLocation, 3, logMinBoostValues);
405   glUniform1fv(logMaxBoostLocation, 3, logMaxBoostValues);
406   glUniform1fv(offsetSdrLocation, 3, gainmap_metadata->offset_sdr);
407   glUniform1fv(offsetHdrLocation, 3, gainmap_metadata->offset_hdr);
408   float gainmap_weight;
409   if (display_boost != gainmap_metadata->hdr_capacity_max) {
410     gainmap_weight =
411         (log2(display_boost) - log2(gainmap_metadata->hdr_capacity_min)) /
412         (log2(gainmap_metadata->hdr_capacity_max) - log2(gainmap_metadata->hdr_capacity_min));
413     // avoid extrapolating the gain map to fill the displayable range
414     gainmap_weight = CLIP3(0.0f, gainmap_weight, 1.0f);
415   } else {
416     gainmap_weight = 1.0f;
417   }
418   glUniform1f(weightLocation, gainmap_weight);
419   float normalize = 1.0f;
420   if (output_ct == UHDR_CT_HLG)
421     normalize = kHlgMaxNits / kSdrWhiteNits;
422   else if (output_ct == UHDR_CT_PQ)
423     normalize = kPqMaxNits / kSdrWhiteNits;
424   glUniform1f(normalizeLocation, normalize);
425 
426   glActiveTexture(GL_TEXTURE0);
427   glBindTexture(GL_TEXTURE_2D, yuvTexture);
428   glUniform1i(glGetUniformLocation(shaderProgram, "yuvTexture"), 0);
429 
430   glActiveTexture(GL_TEXTURE1);
431   glBindTexture(GL_TEXTURE_2D, opengl_ctxt->mGainmapImgTexture);
432   glUniform1i(glGetUniformLocation(shaderProgram, "gainMapTexture"), 1);
433 
434   opengl_ctxt->check_gl_errors("binding values to uniforms");
435   RET_IF_ERR()
436 
437   glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
438 
439   glBindFramebuffer(GL_FRAMEBUFFER, 0);
440 
441   opengl_ctxt->check_gl_errors("reading gles output");
442   RET_IF_ERR()
443 
444   if (frameBuffer) glDeleteFramebuffers(1, &frameBuffer);
445   if (yuvTexture) glDeleteTextures(1, &yuvTexture);
446   if (shaderProgram) glDeleteProgram(shaderProgram);
447 
448   return opengl_ctxt->mErrorStatus;
449 }
450 
451 }  // namespace ultrahdr
452