Coverage Report

Created: 2026-05-16 07:24

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