Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions render.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,57 @@ const (
// video frame should be rendered.
const RenderUpdateFrame uint64 = 1 << 0

// RenderContextOption configures a render context at creation.
type RenderContextOption func(*renderContextOptions)

type renderContextOptions struct {
advancedControl bool
}

// WithAdvancedControl sets MPV_RENDER_PARAM_ADVANCED_CONTROL. Call
// SetUpdateCallback right after creation; see the requirements in render.h.
func WithAdvancedControl() RenderContextOption {
return func(o *renderContextOptions) { o.advancedControl = true }
}

func newRenderContextOptions(opts []RenderContextOption) renderContextOptions {
var o renderContextOptions
for _, opt := range opts {
opt(&o)
}
return o
}

// RenderOption configures a RenderSW or RenderGL call.
type RenderOption func(*renderOptions)

type renderOptions struct {
blockForTargetTime bool
}

// WithoutBlockForTargetTime clears MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME, so
// rendering returns without waiting for the frame's display time; pace frames
// yourself, see render.h.
func WithoutBlockForTargetTime() RenderOption {
return func(o *renderOptions) { o.blockForTargetTime = false }
}

func newRenderOptions(opts []RenderOption) renderOptions {
o := renderOptions{blockForTargetTime: true}
for _, opt := range opts {
opt(&o)
}
return o
}

// cFlag converts b to an mpv flag.
func cFlag(b bool) int32 {
if b {
return 1
}
return 0
}

// Render callbacks live in a token-keyed registry; the token is passed to C as the
// callback context, so one trampoline dispatches without handing Go pointers to C.
var (
Expand Down
36 changes: 20 additions & 16 deletions render_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,48 @@ package mpv
void *goMpvGetProcAddress(void *ctx, const char *name);
void goMpvRenderUpdate(void *ctx);

static int render_create_sw(mpv_render_context **ctx, mpv_handle *mpv) {
static int render_create_sw(mpv_render_context **ctx, mpv_handle *mpv, int advanced) {
mpv_render_param params[] = {
{MPV_RENDER_PARAM_API_TYPE, (void *)MPV_RENDER_API_TYPE_SW},
{MPV_RENDER_PARAM_ADVANCED_CONTROL, &advanced},
{0},
};
return mpv_render_context_create(ctx, mpv, params);
}

static int render_sw(mpv_render_context *ctx, int w, int h, const char *format, size_t stride, void *ptr) {
static int render_sw(mpv_render_context *ctx, int w, int h, const char *format, size_t stride, void *ptr, int block) {
int size[2] = {w, h};
mpv_render_param params[] = {
{MPV_RENDER_PARAM_SW_SIZE, &size[0]},
{MPV_RENDER_PARAM_SW_FORMAT, (void *)format},
{MPV_RENDER_PARAM_SW_STRIDE, &stride},
{MPV_RENDER_PARAM_SW_POINTER, ptr},
{MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME, &block},
{0},
};
return mpv_render_context_render(ctx, params);
}

static int render_create_gl(mpv_render_context **ctx, mpv_handle *mpv, uintptr_t cb_ctx) {
static int render_create_gl(mpv_render_context **ctx, mpv_handle *mpv, uintptr_t cb_ctx, int advanced) {
mpv_opengl_init_params gl = {
.get_proc_address = goMpvGetProcAddress,
.get_proc_address_ctx = (void *)cb_ctx,
};
mpv_render_param params[] = {
{MPV_RENDER_PARAM_API_TYPE, (void *)MPV_RENDER_API_TYPE_OPENGL},
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl},
{MPV_RENDER_PARAM_ADVANCED_CONTROL, &advanced},
{0},
};
return mpv_render_context_create(ctx, mpv, params);
}

static int render_gl(mpv_render_context *ctx, int fbo, int w, int h, int flip_y) {
static int render_gl(mpv_render_context *ctx, int fbo, int w, int h, int flip_y, int block) {
mpv_opengl_fbo gl_fbo = {.fbo = fbo, .w = w, .h = h};
mpv_render_param params[] = {
{MPV_RENDER_PARAM_OPENGL_FBO, &gl_fbo},
{MPV_RENDER_PARAM_FLIP_Y, &flip_y},
{MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME, &block},
{0},
};
return mpv_render_context_render(ctx, params);
Expand All @@ -73,11 +77,12 @@ type RenderContext struct {

// NewRenderContextSW creates a software (CPU) render context. The mpv instance
// must have the "vo" option set to "libmpv".
func (m *Mpv) NewRenderContextSW() (*RenderContext, error) {
func (m *Mpv) NewRenderContextSW(opts ...RenderContextOption) (*RenderContext, error) {
o := newRenderContextOptions(opts)
id := registerRenderCallbacks()

var ctx *C.mpv_render_context
err := newError(int(C.render_create_sw(&ctx, m.handle)))
err := newError(int(C.render_create_sw(&ctx, m.handle, C.int(cFlag(o.advancedControl)))))
if err != nil {
unregisterRenderCallbacks(id)
return nil, err
Expand All @@ -88,12 +93,13 @@ func (m *Mpv) NewRenderContextSW() (*RenderContext, error) {

// NewRenderContextGL creates an OpenGL render context; getProcAddress resolves GL
// functions. Requires vo=libmpv and the GL context current on the calling thread.
func (m *Mpv) NewRenderContextGL(getProcAddress func(name string) unsafe.Pointer) (*RenderContext, error) {
func (m *Mpv) NewRenderContextGL(getProcAddress func(name string) unsafe.Pointer, opts ...RenderContextOption) (*RenderContext, error) {
o := newRenderContextOptions(opts)
id := registerRenderCallbacks()
setRenderProcAddress(id, getProcAddress)

var ctx *C.mpv_render_context
err := newError(int(C.render_create_gl(&ctx, m.handle, C.uintptr_t(id))))
err := newError(int(C.render_create_gl(&ctx, m.handle, C.uintptr_t(id), C.int(cFlag(o.advancedControl)))))
if err != nil {
unregisterRenderCallbacks(id)
return nil, err
Expand All @@ -104,22 +110,20 @@ func (m *Mpv) NewRenderContextGL(getProcAddress func(name string) unsafe.Pointer

// RenderSW renders the current frame into buf, which must hold stride*height
// bytes. format is one of "rgb0", "bgr0", "0bgr", "0rgb".
func (rc *RenderContext) RenderSW(width, height, stride int, format string, buf []byte) error {
func (rc *RenderContext) RenderSW(width, height, stride int, format string, buf []byte, opts ...RenderOption) error {
o := newRenderOptions(opts)
cformat := C.CString(format)
defer C.free(unsafe.Pointer(cformat))

return newError(int(C.render_sw(rc.ctx, C.int(width), C.int(height), cformat, C.size_t(stride), unsafe.Pointer(&buf[0]))))
return newError(int(C.render_sw(rc.ctx, C.int(width), C.int(height), cformat, C.size_t(stride), unsafe.Pointer(&buf[0]), C.int(cFlag(o.blockForTargetTime)))))
}

// RenderGL renders the current frame into the given OpenGL framebuffer object
// (0 for the default framebuffer). Set flipY for bottom-up coordinate systems.
func (rc *RenderContext) RenderGL(fbo, width, height int, flipY bool) error {
var flip C.int
if flipY {
flip = 1
}
func (rc *RenderContext) RenderGL(fbo, width, height int, flipY bool, opts ...RenderOption) error {
o := newRenderOptions(opts)

return newError(int(C.render_gl(rc.ctx, C.int(fbo), C.int(width), C.int(height), flip)))
return newError(int(C.render_gl(rc.ctx, C.int(fbo), C.int(width), C.int(height), C.int(cFlag(flipY)), C.int(cFlag(o.blockForTargetTime)))))
}

// SetUpdateCallback sets fn to run when a new frame is ready. fn runs on an mpv
Expand Down
27 changes: 19 additions & 8 deletions render_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const (
renderParamOpenGLInitParams = 2
renderParamOpenGLFBO = 3
renderParamFlipY = 4
renderParamAdvancedControl = 10
renderParamBlockForTarget = 12
renderParamSWSize = 17
renderParamSWFormat = 18
renderParamSWStride = 19
Expand Down Expand Up @@ -81,12 +83,15 @@ type RenderContext struct {

// NewRenderContextSW creates a software (CPU) render context. The mpv instance
// must have the "vo" option set to "libmpv".
func (m *Mpv) NewRenderContextSW() (*RenderContext, error) {
func (m *Mpv) NewRenderContextSW(opts ...RenderContextOption) (*RenderContext, error) {
o := newRenderContextOptions(opts)
id := registerRenderCallbacks()

apiType := cStr(RenderAPITypeSW)
advanced := cFlag(o.advancedControl)
params := []cRenderParam{
{typ: renderParamAPIType, data: unsafe.Pointer(apiType)},
{typ: renderParamAdvancedControl, data: unsafe.Pointer(&advanced)},
{},
}

Expand All @@ -102,16 +107,19 @@ func (m *Mpv) NewRenderContextSW() (*RenderContext, error) {

// NewRenderContextGL creates an OpenGL render context; getProcAddress resolves GL
// functions. Requires vo=libmpv and the GL context current on the calling thread.
func (m *Mpv) NewRenderContextGL(getProcAddress func(name string) unsafe.Pointer) (*RenderContext, error) {
func (m *Mpv) NewRenderContextGL(getProcAddress func(name string) unsafe.Pointer, opts ...RenderContextOption) (*RenderContext, error) {
o := newRenderContextOptions(opts)
ensureRenderCallbacks()
id := registerRenderCallbacks()
setRenderProcAddress(id, getProcAddress)

apiType := cStr(RenderAPITypeOpenGL)
gl := cOpenGLInitParams{getProcAddress: procAddrCb, ctx: id}
advanced := cFlag(o.advancedControl)
params := []cRenderParam{
{typ: renderParamAPIType, data: unsafe.Pointer(apiType)},
{typ: renderParamOpenGLInitParams, data: unsafe.Pointer(&gl)},
{typ: renderParamAdvancedControl, data: unsafe.Pointer(&advanced)},
{},
}

Expand All @@ -127,15 +135,18 @@ func (m *Mpv) NewRenderContextGL(getProcAddress func(name string) unsafe.Pointer

// RenderSW renders the current frame into buf, which must hold stride*height
// bytes. format is one of "rgb0", "bgr0", "0bgr", "0rgb".
func (rc *RenderContext) RenderSW(width, height, stride int, format string, buf []byte) error {
func (rc *RenderContext) RenderSW(width, height, stride int, format string, buf []byte, opts ...RenderOption) error {
o := newRenderOptions(opts)
size := [2]int32{int32(width), int32(height)}
cformat := cStr(format)
cstride := uintptr(stride)
block := cFlag(o.blockForTargetTime)
params := []cRenderParam{
{typ: renderParamSWSize, data: unsafe.Pointer(&size[0])},
{typ: renderParamSWFormat, data: unsafe.Pointer(cformat)},
{typ: renderParamSWStride, data: unsafe.Pointer(&cstride)},
{typ: renderParamSWPointer, data: unsafe.Pointer(&buf[0])},
{typ: renderParamBlockForTarget, data: unsafe.Pointer(&block)},
{},
}

Expand All @@ -144,15 +155,15 @@ func (rc *RenderContext) RenderSW(width, height, stride int, format string, buf

// RenderGL renders the current frame into the given OpenGL framebuffer object
// (0 for the default framebuffer). Set flipY for bottom-up coordinate systems.
func (rc *RenderContext) RenderGL(fbo, width, height int, flipY bool) error {
func (rc *RenderContext) RenderGL(fbo, width, height int, flipY bool, opts ...RenderOption) error {
o := newRenderOptions(opts)
gl := cOpenGLFBO{fbo: int32(fbo), w: int32(width), h: int32(height)}
flip := int32(0)
if flipY {
flip = 1
}
flip := cFlag(flipY)
block := cFlag(o.blockForTargetTime)
params := []cRenderParam{
{typ: renderParamOpenGLFBO, data: unsafe.Pointer(&gl)},
{typ: renderParamFlipY, data: unsafe.Pointer(&flip)},
{typ: renderParamBlockForTarget, data: unsafe.Pointer(&block)},
{},
}

Expand Down
46 changes: 46 additions & 0 deletions render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,49 @@ func TestRenderUpdateCallback(t *testing.T) {

t.Fatal("update callback was not called")
}

// TestRenderAdvancedControl renders with both render options set.
func TestRenderAdvancedControl(t *testing.T) {
m := New()
defer m.TerminateDestroy()

if err := m.SetOptionString("vo", "libmpv"); err != nil {
t.Fatalf("set vo=libmpv: %v", err)
}
if err := m.SetOptionString("ao", "null"); err != nil {
t.Fatalf("set ao=null: %v", err)
}
if err := m.Initialize(); err != nil {
t.Fatalf("Initialize: %v", err)
}

rc, err := m.NewRenderContextSW(WithAdvancedControl())
if err != nil {
t.Fatalf("NewRenderContextSW: %v", err)
}
defer rc.Free()
// Required with advanced control.
rc.SetUpdateCallback(func() {})

if err := m.Command([]string{"loadfile", "testdata/test.mpg"}); err != nil {
t.Fatalf("loadfile: %v", err)
}

const w, h, stride = 320, 240, 320 * 4
buf := make([]byte, stride*h)
for i := 0; i < 200; i++ {
if rc.Update()&RenderUpdateFrame != 0 {
if err := rc.RenderSW(w, h, stride, "rgb0", buf, WithoutBlockForTargetTime()); err != nil {
t.Fatalf("RenderSW: %v", err)
}
for _, b := range buf {
if b != 0 {
return
}
}
}
m.WaitEvent(0.05)
}

t.Fatal("no non-black frame rendered")
}
Loading