0
# Advanced Rendering
1
2
OpenGL render context management for custom rendering scenarios and integration with graphics frameworks. Enables advanced video output control, custom rendering pipelines, and integration with OpenGL applications.
3
4
## Capabilities
5
6
### Render Context Creation
7
8
Create and manage OpenGL render contexts for custom video output.
9
10
```python { .api }
11
class MpvRenderContext:
12
"""OpenGL render context for advanced rendering scenarios."""
13
14
def __init__(self, mpv: 'MPV', api_type: str, **kwargs):
15
"""
16
Initialize render context.
17
18
Parameters:
19
- mpv: MPV player instance
20
- api_type: Render API type ('opengl')
21
- kwargs: API-specific parameters
22
23
For OpenGL API:
24
- get_proc_address: Function to retrieve OpenGL function pointers
25
- gl_params: MpvOpenGLInitParams object
26
"""
27
28
def render(self, **kwargs) -> int:
29
"""
30
Render current frame to OpenGL context.
31
32
Parameters:
33
- kwargs: Render parameters (fbo, viewport, etc.)
34
35
Returns:
36
Render result code (0 for success)
37
"""
38
39
def update(self) -> bool:
40
"""
41
Update render context and check for new frames.
42
43
Returns:
44
True if new frame is available for rendering
45
"""
46
47
def report_swap(self):
48
"""Report that frame buffer swap has occurred."""
49
50
def free(self):
51
"""Free render context resources."""
52
```
53
54
### Filter Commands
55
56
Send commands to audio and video filter chains for dynamic filter control.
57
58
```python { .api }
59
def vf_command(self, label: str, command: str, argument):
60
"""
61
Send command to a video filter.
62
63
Parameters:
64
- label: Filter label/name in the filter chain
65
- command: Command name to send to the filter
66
- argument: Command argument/parameter
67
"""
68
69
def af_command(self, label: str, command: str, argument):
70
"""
71
Send command to an audio filter.
72
73
Parameters:
74
- label: Filter label/name in the filter chain
75
- command: Command name to send to the filter
76
- argument: Command argument/parameter
77
"""
78
```
79
80
### OpenGL Parameter Classes
81
82
Parameter classes for configuring OpenGL rendering.
83
84
```python { .api }
85
class MpvOpenGLInitParams:
86
"""OpenGL initialization parameters."""
87
88
def __init__(self, get_proc_address):
89
"""
90
Initialize OpenGL parameters.
91
92
Parameters:
93
- get_proc_address: Function pointer to get OpenGL function addresses
94
"""
95
96
class MpvOpenGLFBO:
97
"""OpenGL framebuffer object parameters."""
98
99
def __init__(self, w: int, h: int, fbo: int = 0, internal_format: int = 0):
100
"""
101
Initialize FBO parameters.
102
103
Parameters:
104
- w, h: Framebuffer dimensions
105
- fbo: Framebuffer object ID (0 for default framebuffer)
106
- internal_format: Internal format (0 for auto)
107
"""
108
109
class MpvRenderFrameInfo:
110
"""Information about rendered frame."""
111
112
def as_dict(self) -> dict:
113
"""
114
Get frame information as dictionary.
115
116
Returns:
117
Dictionary with frame metadata
118
"""
119
120
class MpvRenderParam:
121
"""Generic render parameter container."""
122
123
def __init__(self, name: str, value=None):
124
"""
125
Initialize render parameter.
126
127
Parameters:
128
- name: Parameter name
129
- value: Parameter value
130
"""
131
132
# Parameter type constants
133
TYPES = {
134
'invalid': 0,
135
'api_type': 1,
136
'opengl_init_params': 2,
137
'opengl_fbo': 3,
138
'flip_y': 4,
139
'depth': 5,
140
'icc_profile': 6,
141
'ambient_light': 7,
142
'x11_display': 8,
143
'wl_display': 9,
144
'advanced_control': 10,
145
'next_frame_info': 11,
146
'block_for_target_time': 12,
147
'skip_rendering': 13,
148
'opengl_drm_params': 14,
149
'opengl_drm_draw_surface_size': 15,
150
'opengl_drm_params_v2': 16,
151
'sw_size': 17,
152
'sw_format': 18,
153
'sw_stride': 19,
154
'sw_pointer': 20
155
}
156
```
157
158
### DRM (Direct Rendering Manager) Support
159
160
Classes for hardware-accelerated rendering on Linux.
161
162
```python { .api }
163
class MpvOpenGLDRMParams:
164
"""DRM parameters for direct hardware rendering."""
165
166
class MpvOpenGLDRMDrawSurfaceSize:
167
"""DRM draw surface size specification."""
168
169
class MpvOpenGLDRMParamsV2:
170
"""Enhanced DRM parameters with additional features."""
171
172
def __init__(self, crtc_id: int, connector_id: int, atomic_request_ptr, fd: int = -1, render_fd: int = -1):
173
"""
174
Initialize DRM parameters.
175
176
Parameters:
177
- crtc_id: CRTC (display controller) ID
178
- connector_id: Display connector ID
179
- atomic_request_ptr: Atomic mode setting request
180
- fd: DRM file descriptor
181
- render_fd: Render node file descriptor
182
"""
183
```
184
185
## Usage Examples
186
187
### Basic OpenGL Integration
188
189
```python
190
import mpv
191
import OpenGL.GL as gl
192
from OpenGL.GL import *
193
import ctypes
194
195
# OpenGL context setup (varies by platform/framework)
196
def setup_opengl_context():
197
"""Setup OpenGL context - implementation depends on your framework."""
198
# This is pseudocode - actual implementation depends on your OpenGL framework
199
# (pygame, GLFW, Qt, tkinter, etc.)
200
pass
201
202
def get_proc_address(name):
203
"""Get OpenGL function pointer."""
204
# Implementation depends on your OpenGL loading library
205
# For example, with GLFW:
206
# return glfw.get_proc_address(name.decode('utf-8'))
207
pass
208
209
# Initialize player and render context
210
player = mpv.MPV(vo='libmpv') # Use libmpv video output
211
212
# Create OpenGL initialization parameters
213
gl_init_params = mpv.MpvOpenGLInitParams(get_proc_address)
214
215
# Create render context
216
render_ctx = mpv.MpvRenderContext(
217
player,
218
'opengl',
219
opengl_init_params=gl_init_params
220
)
221
222
# Basic rendering loop
223
def render_frame():
224
"""Render mpv frame to current OpenGL context."""
225
226
# Check for new frame
227
if render_ctx.update():
228
# Get viewport dimensions
229
viewport = gl.glGetIntegerv(gl.GL_VIEWPORT)
230
width, height = viewport[2], viewport[3]
231
232
# Create FBO parameters for default framebuffer
233
fbo = mpv.MpvOpenGLFBO(width, height)
234
235
# Render frame
236
result = render_ctx.render(opengl_fbo=fbo)
237
238
# Report buffer swap
239
render_ctx.report_swap()
240
241
return result == 0 # Success
242
243
return False
244
245
# Usage in main loop
246
player.play('/path/to/video.mp4')
247
248
while True: # Your main loop
249
if render_frame():
250
# Frame was rendered
251
pass
252
253
# Handle other events, swap buffers, etc.
254
# This depends on your OpenGL framework
255
```
256
257
### Framebuffer Object Rendering
258
259
```python
260
import numpy as np
261
262
class OffscreenRenderer:
263
def __init__(self, player, width, height):
264
self.player = player
265
self.width = width
266
self.height = height
267
268
# Create OpenGL resources
269
self.setup_fbo()
270
self.setup_render_context()
271
272
def setup_fbo(self):
273
"""Create framebuffer object for offscreen rendering."""
274
275
# Generate framebuffer
276
self.fbo = gl.glGenFramebuffers(1)
277
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
278
279
# Create color texture
280
self.color_texture = gl.glGenTextures(1)
281
gl.glBindTexture(gl.GL_TEXTURE_2D, self.color_texture)
282
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8,
283
self.width, self.height, 0,
284
gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
285
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
286
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
287
288
# Attach texture to framebuffer
289
gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
290
gl.GL_TEXTURE_2D, self.color_texture, 0)
291
292
# Check framebuffer completeness
293
if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
294
raise RuntimeError("Framebuffer not complete")
295
296
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
297
298
def setup_render_context(self):
299
"""Setup mpv render context."""
300
gl_init_params = mpv.MpvOpenGLInitParams(get_proc_address)
301
302
self.render_ctx = mpv.MpvRenderContext(
303
self.player,
304
'opengl',
305
opengl_init_params=gl_init_params
306
)
307
308
def render_to_texture(self):
309
"""Render current frame to texture."""
310
311
if not self.render_ctx.update():
312
return False
313
314
# Bind our framebuffer
315
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
316
gl.glViewport(0, 0, self.width, self.height)
317
318
# Clear framebuffer
319
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
320
321
# Render mpv frame
322
fbo_params = mpv.MpvOpenGLFBO(self.width, self.height, self.fbo)
323
result = self.render_ctx.render(opengl_fbo=fbo_params)
324
325
# Report swap
326
self.render_ctx.report_swap()
327
328
# Restore default framebuffer
329
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
330
331
return result == 0
332
333
def read_pixels(self):
334
"""Read rendered pixels from texture."""
335
336
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
337
338
# Read pixels
339
pixels = gl.glReadPixels(0, 0, self.width, self.height,
340
gl.GL_RGBA, gl.GL_UNSIGNED_BYTE)
341
342
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
343
344
# Convert to numpy array
345
pixel_array = np.frombuffer(pixels, dtype=np.uint8)
346
pixel_array = pixel_array.reshape((self.height, self.width, 4))
347
348
# Flip vertically (OpenGL convention)
349
pixel_array = np.flip(pixel_array, axis=0)
350
351
return pixel_array
352
353
def cleanup(self):
354
"""Clean up OpenGL resources."""
355
gl.glDeleteFramebuffers(1, [self.fbo])
356
gl.glDeleteTextures(1, [self.color_texture])
357
self.render_ctx.free()
358
359
# Usage
360
offscreen = OffscreenRenderer(player, 1920, 1080)
361
362
player.play('/path/to/video.mp4')
363
player.wait_until_playing()
364
365
# Render frames and capture
366
for i in range(100): # Capture 100 frames
367
if offscreen.render_to_texture():
368
pixels = offscreen.read_pixels()
369
370
# Save frame as image
371
from PIL import Image
372
img = Image.fromarray(pixels[:, :, :3]) # Remove alpha channel
373
img.save(f'frame_{i:04d}.png')
374
375
# Advance to next frame
376
player.frame_step()
377
378
offscreen.cleanup()
379
```
380
381
### Multi-Context Rendering
382
383
```python
384
class MultiContextRenderer:
385
def __init__(self, player):
386
self.player = player
387
self.contexts = {}
388
389
def add_render_target(self, name, width, height, context_setup_func):
390
"""Add a new render target with its own context."""
391
392
# Setup context-specific OpenGL state
393
context_setup_func()
394
395
# Create render context for this target
396
gl_init_params = mpv.MpvOpenGLInitParams(get_proc_address)
397
render_ctx = mpv.MpvRenderContext(
398
self.player,
399
'opengl',
400
opengl_init_params=gl_init_params
401
)
402
403
self.contexts[name] = {
404
'render_ctx': render_ctx,
405
'width': width,
406
'height': height,
407
'setup_func': context_setup_func
408
}
409
410
def render_to_target(self, target_name, fbo_id=0):
411
"""Render to specific target."""
412
413
if target_name not in self.contexts:
414
return False
415
416
ctx_info = self.contexts[target_name]
417
render_ctx = ctx_info['render_ctx']
418
419
# Setup target context
420
ctx_info['setup_func']()
421
422
if render_ctx.update():
423
# Create FBO parameters
424
fbo_params = mpv.MpvOpenGLFBO(
425
ctx_info['width'],
426
ctx_info['height'],
427
fbo_id
428
)
429
430
# Render
431
result = render_ctx.render(opengl_fbo=fbo_params)
432
render_ctx.report_swap()
433
434
return result == 0
435
436
return False
437
438
def cleanup(self):
439
"""Clean up all render contexts."""
440
for ctx_info in self.contexts.values():
441
ctx_info['render_ctx'].free()
442
443
# Usage with multiple windows/contexts
444
multi_renderer = MultiContextRenderer(player)
445
446
# Add different render targets
447
def setup_main_window():
448
# Setup main window OpenGL context
449
pass
450
451
def setup_preview_window():
452
# Setup preview window OpenGL context
453
pass
454
455
multi_renderer.add_render_target('main', 1920, 1080, setup_main_window)
456
multi_renderer.add_render_target('preview', 320, 240, setup_preview_window)
457
458
# Render to different targets
459
player.play('/path/to/video.mp4')
460
461
while True: # Main loop
462
# Render to main window
463
multi_renderer.render_to_target('main')
464
465
# Render to preview window
466
multi_renderer.render_to_target('preview')
467
468
# Handle events, etc.
469
```
470
471
### Custom Render Pipeline
472
473
```python
474
class CustomRenderPipeline:
475
def __init__(self, player):
476
self.player = player
477
self.render_ctx = None
478
self.post_process_shaders = []
479
480
def setup_pipeline(self):
481
"""Setup custom rendering pipeline."""
482
483
# Create render context
484
gl_init_params = mpv.MpvOpenGLInitParams(get_proc_address)
485
self.render_ctx = mpv.MpvRenderContext(
486
self.player,
487
'opengl',
488
opengl_init_params=gl_init_params
489
)
490
491
# Create intermediate framebuffers for post-processing
492
self.setup_framebuffers()
493
494
def setup_framebuffers(self):
495
"""Create framebuffers for pipeline stages."""
496
497
# Main rendering FBO
498
self.main_fbo = gl.glGenFramebuffers(1)
499
self.main_texture = gl.glGenTextures(1)
500
501
# Post-processing FBOs
502
self.post_fbos = []
503
self.post_textures = []
504
505
for i in range(2): # Ping-pong buffers
506
fbo = gl.glGenFramebuffers(1)
507
texture = gl.glGenTextures(1)
508
509
gl.glBindTexture(gl.GL_TEXTURE_2D, texture)
510
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8,
511
1920, 1080, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
512
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
513
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
514
515
self.post_fbos.append(fbo)
516
self.post_textures.append(texture)
517
518
def add_post_process_shader(self, vertex_shader, fragment_shader):
519
"""Add post-processing shader to pipeline."""
520
521
# Compile and link shader program
522
program = self.compile_shader_program(vertex_shader, fragment_shader)
523
self.post_process_shaders.append(program)
524
525
def compile_shader_program(self, vertex_src, fragment_src):
526
"""Compile OpenGL shader program."""
527
528
# Vertex shader
529
vertex_shader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
530
gl.glShaderSource(vertex_shader, vertex_src)
531
gl.glCompileShader(vertex_shader)
532
533
# Fragment shader
534
fragment_shader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
535
gl.glShaderSource(fragment_shader, fragment_src)
536
gl.glCompileShader(fragment_shader)
537
538
# Program
539
program = gl.glCreateProgram()
540
gl.glAttachShader(program, vertex_shader)
541
gl.glAttachShader(program, fragment_shader)
542
gl.glLinkProgram(program)
543
544
# Cleanup individual shaders
545
gl.glDeleteShader(vertex_shader)
546
gl.glDeleteShader(fragment_shader)
547
548
return program
549
550
def render_with_pipeline(self):
551
"""Render frame through custom pipeline."""
552
553
if not self.render_ctx.update():
554
return False
555
556
# Render mpv frame to main FBO
557
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.main_fbo)
558
559
fbo_params = mpv.MpvOpenGLFBO(1920, 1080, self.main_fbo)
560
result = self.render_ctx.render(opengl_fbo=fbo_params)
561
562
if result != 0:
563
return False
564
565
# Apply post-processing pipeline
566
current_input = self.main_texture
567
568
for i, shader in enumerate(self.post_process_shaders):
569
# Use ping-pong buffers
570
output_idx = i % 2
571
output_fbo = self.post_fbos[output_idx]
572
output_texture = self.post_textures[output_idx]
573
574
# Bind output framebuffer
575
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, output_fbo)
576
gl.glViewport(0, 0, 1920, 1080)
577
578
# Use shader
579
gl.glUseProgram(shader)
580
581
# Bind input texture
582
gl.glActiveTexture(gl.GL_TEXTURE0)
583
gl.glBindTexture(gl.GL_TEXTURE_2D, current_input)
584
gl.glUniform1i(gl.glGetUniformLocation(shader, "inputTexture"), 0)
585
586
# Render fullscreen quad
587
self.render_fullscreen_quad()
588
589
current_input = output_texture
590
591
# Final output to screen
592
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
593
gl.glViewport(0, 0, 1920, 1080)
594
595
# Simple blit of final result
596
gl.glUseProgram(0)
597
gl.glActiveTexture(gl.GL_TEXTURE0)
598
gl.glBindTexture(gl.GL_TEXTURE_2D, current_input)
599
gl.glEnable(gl.GL_TEXTURE_2D)
600
601
# Render final quad
602
self.render_fullscreen_quad()
603
604
self.render_ctx.report_swap()
605
return True
606
607
def render_fullscreen_quad(self):
608
"""Render a fullscreen quad for post-processing."""
609
gl.glBegin(gl.GL_QUADS)
610
gl.glTexCoord2f(0, 0); gl.glVertex2f(-1, -1)
611
gl.glTexCoord2f(1, 0); gl.glVertex2f(1, -1)
612
gl.glTexCoord2f(1, 1); gl.glVertex2f(1, 1)
613
gl.glTexCoord2f(0, 1); gl.glVertex2f(-1, 1)
614
gl.glEnd()
615
616
# Usage
617
pipeline = CustomRenderPipeline(player)
618
pipeline.setup_pipeline()
619
620
# Add bloom effect shader
621
bloom_fragment = """
622
#version 330 core
623
uniform sampler2D inputTexture;
624
in vec2 texCoord;
625
out vec4 fragColor;
626
627
void main() {
628
vec3 color = texture(inputTexture, texCoord).rgb;
629
630
// Simple bloom effect
631
float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722));
632
if (brightness > 0.8) {
633
color *= 1.5;
634
}
635
636
fragColor = vec4(color, 1.0);
637
}
638
"""
639
640
bloom_vertex = """
641
#version 330 core
642
layout(location = 0) in vec2 position;
643
out vec2 texCoord;
644
645
void main() {
646
texCoord = position * 0.5 + 0.5;
647
gl_Position = vec4(position, 0.0, 1.0);
648
}
649
"""
650
651
pipeline.add_post_process_shader(bloom_vertex, bloom_fragment)
652
653
# Render with custom pipeline
654
player.play('/path/to/video.mp4')
655
656
while True: # Main loop
657
pipeline.render_with_pipeline()
658
# Swap buffers, handle events, etc.
659
```