Coverage Report

Created: 2025-10-27 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/video/out/opengl/utils.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 * Parts based on MPlayer code by Reimar Döffinger.
4
 *
5
 * mpv is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9
 *
10
 * mpv is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
#include <stddef.h>
20
#include <stdint.h>
21
#include <stdlib.h>
22
#include <string.h>
23
#include <stdarg.h>
24
#include <assert.h>
25
26
#include <libavutil/sha.h>
27
#include <libavutil/intreadwrite.h>
28
#include <libavutil/mem.h>
29
30
#include "osdep/io.h"
31
32
#include "common/common.h"
33
#include "options/path.h"
34
#include "stream/stream.h"
35
#include "formats.h"
36
#include "utils.h"
37
38
// GLU has this as gluErrorString (we don't use GLU, as it is legacy-OpenGL)
39
static const char *gl_error_to_string(GLenum error)
40
0
{
41
0
    switch (error) {
42
0
    case GL_INVALID_ENUM: return "INVALID_ENUM";
43
0
    case GL_INVALID_VALUE: return "INVALID_VALUE";
44
0
    case GL_INVALID_OPERATION: return "INVALID_OPERATION";
45
0
    case GL_INVALID_FRAMEBUFFER_OPERATION: return "INVALID_FRAMEBUFFER_OPERATION";
46
0
    case GL_OUT_OF_MEMORY: return "OUT_OF_MEMORY";
47
0
    default: return "unknown";
48
0
    }
49
0
}
50
51
void gl_check_error(GL *gl, struct mp_log *log, const char *info)
52
0
{
53
0
    for (;;) {
54
0
        GLenum error = gl->GetError();
55
0
        if (error == GL_NO_ERROR)
56
0
            break;
57
0
        mp_msg(log, MSGL_ERR, "%s: OpenGL error %s.\n", info,
58
0
               gl_error_to_string(error));
59
0
    }
60
0
}
61
62
static int get_alignment(int stride)
63
0
{
64
0
    if (stride % 8 == 0)
65
0
        return 8;
66
0
    if (stride % 4 == 0)
67
0
        return 4;
68
0
    if (stride % 2 == 0)
69
0
        return 2;
70
0
    return 1;
71
0
}
72
73
// upload a texture, handling things like stride and slices
74
//  target: texture target, usually GL_TEXTURE_2D
75
//  format, type: texture parameters
76
//  dataptr, stride: image data
77
//  x, y, width, height: part of the image to upload
78
void gl_upload_tex(GL *gl, GLenum target, GLenum format, GLenum type,
79
                   const void *dataptr, int stride,
80
                   int x, int y, int w, int h)
81
0
{
82
0
    int bpp = gl_bytes_per_pixel(format, type);
83
0
    uintptr_t data = (uintptr_t)dataptr;
84
0
    int y_max = y + h;
85
0
    if (w <= 0 || h <= 0 || !bpp)
86
0
        return;
87
0
    mp_assert(stride > 0);
88
0
    gl->PixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(stride));
89
0
    int slice = h;
90
0
    if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH) {
91
        // this is not always correct, but should work for MPlayer
92
0
        gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride / bpp);
93
0
    } else {
94
0
        if (stride != bpp * w)
95
0
            slice = 1; // very inefficient, but at least it works
96
0
    }
97
0
    for (; y + slice <= y_max; y += slice) {
98
0
        gl->TexSubImage2D(target, 0, x, y, w, slice, format, type, (void *)data);
99
0
        data += stride * slice;
100
0
    }
101
0
    if (y < y_max)
102
0
        gl->TexSubImage2D(target, 0, x, y, w, y_max - y, format, type, (void *)data);
103
0
    if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH)
104
0
        gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
105
0
    gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
106
0
}
107
108
bool gl_read_fbo_contents(GL *gl, int fbo, int dir, GLenum format, GLenum type,
109
                          int w, int h, uint8_t *dst, int dst_stride)
110
0
{
111
0
    mp_assert(dir == 1 || dir == -1);
112
0
    if (fbo == 0 && gl->es)
113
0
        return false; // ES can't read from front buffer
114
0
    gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
115
0
    GLenum obj = fbo ? GL_COLOR_ATTACHMENT0 : GL_FRONT;
116
0
    gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
117
0
    gl->ReadBuffer(obj);
118
    // reading by line allows flipping, and avoids stride-related trouble
119
0
    int y1 = dir > 0 ? 0 : h;
120
0
    for (int y = 0; y < h; y++)
121
0
        gl->ReadPixels(0, y, w, 1, format, type, dst + (y1 + dir * y) * dst_stride);
122
0
    gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
123
0
    gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
124
0
    return true;
125
0
}
126
127
static void gl_vao_enable_attribs(struct gl_vao *vao)
128
0
{
129
0
    GL *gl = vao->gl;
130
131
0
    for (int n = 0; n < vao->num_entries; n++) {
132
0
        const struct ra_renderpass_input *e = &vao->entries[n];
133
0
        GLenum type = 0;
134
0
        bool normalized = false;
135
0
        switch (e->type) {
136
0
        case RA_VARTYPE_INT:
137
0
            type = GL_INT;
138
0
            break;
139
0
        case RA_VARTYPE_FLOAT:
140
0
            type = GL_FLOAT;
141
0
            break;
142
0
        case RA_VARTYPE_BYTE_UNORM:
143
0
            type = GL_UNSIGNED_BYTE;
144
0
            normalized = true;
145
0
            break;
146
0
        default:
147
0
            abort();
148
0
        }
149
0
        mp_assert(e->dim_m == 1);
150
151
0
        gl->EnableVertexAttribArray(n);
152
0
        gl->VertexAttribPointer(n, e->dim_v, type, normalized,
153
0
                                vao->stride, (void *)(intptr_t)e->offset);
154
0
    }
155
0
}
156
157
void gl_vao_init(struct gl_vao *vao, GL *gl, int stride,
158
                 const struct ra_renderpass_input *entries,
159
                 int num_entries)
160
0
{
161
0
    mp_assert(!vao->vao);
162
0
    mp_assert(!vao->buffer);
163
164
0
    *vao = (struct gl_vao){
165
0
        .gl = gl,
166
0
        .stride = stride,
167
0
        .entries = entries,
168
0
        .num_entries = num_entries,
169
0
    };
170
171
0
    gl->GenBuffers(1, &vao->buffer);
172
173
0
    if (gl->BindVertexArray) {
174
0
        gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
175
176
0
        gl->GenVertexArrays(1, &vao->vao);
177
0
        gl->BindVertexArray(vao->vao);
178
0
        gl_vao_enable_attribs(vao);
179
0
        gl->BindVertexArray(0);
180
181
0
        gl->BindBuffer(GL_ARRAY_BUFFER, 0);
182
0
    }
183
0
}
184
185
void gl_vao_uninit(struct gl_vao *vao)
186
0
{
187
0
    GL *gl = vao->gl;
188
0
    if (!gl)
189
0
        return;
190
191
0
    if (gl->DeleteVertexArrays)
192
0
        gl->DeleteVertexArrays(1, &vao->vao);
193
0
    gl->DeleteBuffers(1, &vao->buffer);
194
195
0
    *vao = (struct gl_vao){0};
196
0
}
197
198
static void gl_vao_bind(struct gl_vao *vao)
199
0
{
200
0
    GL *gl = vao->gl;
201
202
0
    if (gl->BindVertexArray) {
203
0
        gl->BindVertexArray(vao->vao);
204
0
    } else {
205
0
        gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
206
0
        gl_vao_enable_attribs(vao);
207
0
        gl->BindBuffer(GL_ARRAY_BUFFER, 0);
208
0
    }
209
0
}
210
211
static void gl_vao_unbind(struct gl_vao *vao)
212
0
{
213
0
    GL *gl = vao->gl;
214
215
0
    if (gl->BindVertexArray) {
216
0
        gl->BindVertexArray(0);
217
0
    } else {
218
0
        for (int n = 0; n < vao->num_entries; n++)
219
0
            gl->DisableVertexAttribArray(n);
220
0
    }
221
0
}
222
223
// Draw the vertex data (as described by the gl_vao_entry entries) in ptr
224
// to the screen. num is the number of vertices. prim is usually GL_TRIANGLES.
225
// If ptr is NULL, then skip the upload, and use the data uploaded with the
226
// previous call.
227
void gl_vao_draw_data(struct gl_vao *vao, GLenum prim, void *ptr, size_t num)
228
0
{
229
0
    GL *gl = vao->gl;
230
231
0
    if (ptr) {
232
0
        gl->BindBuffer(GL_ARRAY_BUFFER, vao->buffer);
233
0
        gl->BufferData(GL_ARRAY_BUFFER, num * vao->stride, ptr, GL_STREAM_DRAW);
234
0
        gl->BindBuffer(GL_ARRAY_BUFFER, 0);
235
0
    }
236
237
0
    gl_vao_bind(vao);
238
239
0
    gl->DrawArrays(prim, 0, num);
240
241
0
    gl_vao_unbind(vao);
242
0
}
243
244
static void GLAPIENTRY gl_debug_cb(GLenum source, GLenum type, GLuint id,
245
                                   GLenum severity, GLsizei length,
246
                                   const GLchar *message, const void *userParam)
247
0
{
248
    // keep in mind that the debug callback can be asynchronous
249
0
    struct mp_log *log = (void *)userParam;
250
0
    int level = MSGL_ERR;
251
0
    switch (severity) {
252
0
    case GL_DEBUG_SEVERITY_NOTIFICATION:level = MSGL_V; break;
253
0
    case GL_DEBUG_SEVERITY_LOW:         level = MSGL_INFO; break;
254
0
    case GL_DEBUG_SEVERITY_MEDIUM:      level = MSGL_WARN; break;
255
0
    case GL_DEBUG_SEVERITY_HIGH:        level = MSGL_ERR; break;
256
0
    }
257
0
    mp_msg(log, level, "GL: %s\n", message);
258
0
}
259
260
void gl_set_debug_logger(GL *gl, struct mp_log *log)
261
0
{
262
0
    if (gl->DebugMessageCallback)
263
0
        gl->DebugMessageCallback(log ? gl_debug_cb : NULL, log);
264
0
}
265
266
// Given a GL combined extension string in extensions, find out whether ext
267
// is included in it. Basically, a word search.
268
bool gl_check_extension(const char *extensions, const char *ext)
269
0
{
270
0
    int len = strlen(ext);
271
0
    const char *cur = extensions;
272
0
    while (cur) {
273
0
        cur = strstr(cur, ext);
274
0
        if (!cur)
275
0
            break;
276
0
        if ((cur == extensions || cur[-1] == ' ') &&
277
0
            (cur[len] == '\0' || cur[len] == ' '))
278
0
            return true;
279
0
        cur += len;
280
0
    }
281
0
    return false;
282
0
}