From be4cfd51c3aaf4f34ab6eab30db8ff8e4a89b885 Mon Sep 17 00:00:00 2001 From: Sylvain Becker Date: Tue, 5 Jan 2021 11:56:22 +0100 Subject: [PATCH] Add SDL_UpdateNVTexture() to update NV12/21 Texture (bug #5430) for renderer software, opengl, and opengles2 --- include/SDL_render.h | 22 +++++ src/render/SDL_render.c | 115 ++++++++++++++++++++++++ src/render/SDL_sysrender.h | 4 + src/render/SDL_yuv_sw.c | 34 +++++++ src/render/SDL_yuv_sw_c.h | 3 + src/render/opengl/SDL_render_gl.c | 33 +++++++ src/render/opengles2/SDL_render_gles2.c | 43 +++++++++ 7 files changed, 254 insertions(+) diff --git a/include/SDL_render.h b/include/SDL_render.h index fc669d36b..980109afb 100644 --- a/include/SDL_render.h +++ b/include/SDL_render.h @@ -450,6 +450,28 @@ extern DECLSPEC int SDLCALL SDL_UpdateYUVTexture(SDL_Texture * texture, const Uint8 *Uplane, int Upitch, const Uint8 *Vplane, int Vpitch); +/** + * \brief Update a rectangle within a planar NV12 or NV21 texture with new pixel data. + * + * \param texture The texture to update + * \param rect A pointer to the rectangle of pixels to update, or NULL to + * update the entire texture. + * \param Yplane The raw pixel data for the Y plane. + * \param Ypitch The number of bytes between rows of pixel data for the Y plane. + * \param UVplane The raw pixel data for the UV plane. + * \param UVpitch The number of bytes between rows of pixel data for the UV plane. + * + * \return 0 on success, or -1 if the texture is not valid. + * + * \note You can use SDL_UpdateTexture() as long as your pixel data is + * a contiguous block of NV12/21 planes in the proper order, but + * this function is available if your pixel data is not contiguous. + */ +extern DECLSPEC int SDLCALL SDL_UpdateNVTexture(SDL_Texture * texture, + const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch); + /** * \brief Lock a portion of the texture for write-only pixel access. * diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c index 57632ea72..4102b7a61 100644 --- a/src/render/SDL_render.c +++ b/src/render/SDL_render.c @@ -1628,6 +1628,59 @@ SDL_UpdateTextureYUVPlanar(SDL_Texture * texture, const SDL_Rect * rect, } return 0; } + +static int +SDL_UpdateTextureNVPlanar(SDL_Texture * texture, const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch) +{ + SDL_Texture *native = texture->native; + SDL_Rect full_rect; + + if (SDL_SW_UpdateNVTexturePlanar(texture->yuv, rect, Yplane, Ypitch, UVplane, UVpitch) < 0) { + return -1; + } + + full_rect.x = 0; + full_rect.y = 0; + full_rect.w = texture->w; + full_rect.h = texture->h; + rect = &full_rect; + + if (!rect->w || !rect->h) { + return 0; /* nothing to do. */ + } + + if (texture->access == SDL_TEXTUREACCESS_STREAMING) { + /* We can lock the texture and copy to it */ + void *native_pixels = NULL; + int native_pitch = 0; + + if (SDL_LockTexture(native, rect, &native_pixels, &native_pitch) < 0) { + return -1; + } + SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, + rect->w, rect->h, native_pixels, native_pitch); + SDL_UnlockTexture(native); + } else { + /* Use a temporary buffer for updating */ + const int temp_pitch = (((rect->w * SDL_BYTESPERPIXEL(native->format)) + 3) & ~3); + const size_t alloclen = rect->h * temp_pitch; + if (alloclen > 0) { + void *temp_pixels = SDL_malloc(alloclen); + if (!temp_pixels) { + return SDL_OutOfMemory(); + } + SDL_SW_CopyYUVToRGB(texture->yuv, rect, native->format, + rect->w, rect->h, temp_pixels, temp_pitch); + SDL_UpdateTexture(native, rect, temp_pixels, temp_pitch); + SDL_free(temp_pixels); + } + } + return 0; +} + + #endif /* SDL_HAVE_YUV */ int SDL_UpdateYUVTexture(SDL_Texture * texture, const SDL_Rect * rect, @@ -1697,6 +1750,68 @@ int SDL_UpdateYUVTexture(SDL_Texture * texture, const SDL_Rect * rect, #endif } +int SDL_UpdateNVTexture(SDL_Texture * texture, const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch) +{ +#if SDL_HAVE_YUV + SDL_Renderer *renderer; + SDL_Rect full_rect; + + CHECK_TEXTURE_MAGIC(texture, -1); + + if (!Yplane) { + return SDL_InvalidParamError("Yplane"); + } + if (!Ypitch) { + return SDL_InvalidParamError("Ypitch"); + } + if (!UVplane) { + return SDL_InvalidParamError("UVplane"); + } + if (!UVpitch) { + return SDL_InvalidParamError("UVpitch"); + } + + if (texture->format != SDL_PIXELFORMAT_NV12 && + texture->format != SDL_PIXELFORMAT_NV21) { + return SDL_SetError("Texture format must by NV12 or NV21"); + } + + if (!rect) { + full_rect.x = 0; + full_rect.y = 0; + full_rect.w = texture->w; + full_rect.h = texture->h; + rect = &full_rect; + } + + if (!rect->w || !rect->h) { + return 0; /* nothing to do. */ + } + + if (texture->yuv) { + return SDL_UpdateTextureNVPlanar(texture, rect, Yplane, Ypitch, UVplane, UVpitch); + } else { + SDL_assert(!texture->native); + renderer = texture->renderer; + SDL_assert(renderer->UpdateTextureNV); + if (renderer->UpdateTextureNV) { + if (FlushRenderCommandsIfTextureNeeded(texture) < 0) { + return -1; + } + return renderer->UpdateTextureNV(renderer, texture, rect, Yplane, Ypitch, UVplane, UVpitch); + } else { + return SDL_Unsupported(); + } + } +#else + return -1; +#endif +} + + + #if SDL_HAVE_YUV static int SDL_LockTextureYUV(SDL_Texture * texture, const SDL_Rect * rect, diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h index 969ff5c2d..3150195ce 100644 --- a/src/render/SDL_sysrender.h +++ b/src/render/SDL_sysrender.h @@ -136,6 +136,10 @@ struct SDL_Renderer const Uint8 *Yplane, int Ypitch, const Uint8 *Uplane, int Upitch, const Uint8 *Vplane, int Vpitch); + int (*UpdateTextureNV) (SDL_Renderer * renderer, SDL_Texture * texture, + const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch); int (*LockTexture) (SDL_Renderer * renderer, SDL_Texture * texture, const SDL_Rect * rect, void **pixels, int *pitch); void (*UnlockTexture) (SDL_Renderer * renderer, SDL_Texture * texture); diff --git a/src/render/SDL_yuv_sw.c b/src/render/SDL_yuv_sw.c index 97cec9b6d..ae153c29e 100644 --- a/src/render/SDL_yuv_sw.c +++ b/src/render/SDL_yuv_sw.c @@ -299,6 +299,40 @@ SDL_SW_UpdateYUVTexturePlanar(SDL_SW_YUVTexture * swdata, const SDL_Rect * rect, return 0; } +int SDL_SW_UpdateNVTexturePlanar(SDL_SW_YUVTexture * swdata, const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch) +{ + const Uint8 *src; + Uint8 *dst; + int row; + size_t length; + + /* Copy the Y plane */ + src = Yplane; + dst = swdata->pixels + rect->y * swdata->w + rect->x; + length = rect->w; + for (row = 0; row < rect->h; ++row) { + SDL_memcpy(dst, src, length); + src += Ypitch; + dst += swdata->w; + } + + /* Copy the UV or VU plane */ + src = UVplane; + dst = swdata->pixels + swdata->h * swdata->w; + dst += rect->y/2 * ((swdata->w + 1)/2) + rect->x/2; + length = (rect->w + 1) / 2; + length *= 2; + for (row = 0; row < (rect->h + 1)/2; ++row) { + SDL_memcpy(dst, src, length); + src += UVpitch; + dst += 2 * ((swdata->w + 1)/2); + } + + return 0; +} + int SDL_SW_LockYUVTexture(SDL_SW_YUVTexture * swdata, const SDL_Rect * rect, void **pixels, int *pitch) diff --git a/src/render/SDL_yuv_sw_c.h b/src/render/SDL_yuv_sw_c.h index 7e3800df7..14938feb2 100644 --- a/src/render/SDL_yuv_sw_c.h +++ b/src/render/SDL_yuv_sw_c.h @@ -55,6 +55,9 @@ int SDL_SW_UpdateYUVTexturePlanar(SDL_SW_YUVTexture * swdata, const SDL_Rect * r const Uint8 *Yplane, int Ypitch, const Uint8 *Uplane, int Upitch, const Uint8 *Vplane, int Vpitch); +int SDL_SW_UpdateNVTexturePlanar(SDL_SW_YUVTexture * swdata, const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch); int SDL_SW_LockYUVTexture(SDL_SW_YUVTexture * swdata, const SDL_Rect * rect, void **pixels, int *pitch); void SDL_SW_UnlockYUVTexture(SDL_SW_YUVTexture * swdata); diff --git a/src/render/opengl/SDL_render_gl.c b/src/render/opengl/SDL_render_gl.c index 5995e7ca7..722d30e76 100644 --- a/src/render/opengl/SDL_render_gl.c +++ b/src/render/opengl/SDL_render_gl.c @@ -728,6 +728,38 @@ GL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture, return GL_CheckError("glTexSubImage2D()", renderer); } +static int +GL_UpdateTextureNV(SDL_Renderer * renderer, SDL_Texture * texture, + const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch) +{ + GL_RenderData *renderdata = (GL_RenderData *) renderer->driverdata; + const GLenum textype = renderdata->textype; + GL_TextureData *data = (GL_TextureData *) texture->driverdata; + + GL_ActivateRenderer(renderer); + + renderdata->drawstate.texture = NULL; /* we trash this state. */ + + renderdata->glBindTexture(textype, data->texture); + renderdata->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH, Ypitch); + renderdata->glTexSubImage2D(textype, 0, rect->x, rect->y, rect->w, + rect->h, data->format, data->formattype, + Yplane); + + + renderdata->glPixelStorei(GL_UNPACK_ROW_LENGTH, UVpitch / 2); + renderdata->glBindTexture(textype, data->utexture); + renderdata->glTexSubImage2D(textype, 0, rect->x/2, rect->y/2, + (rect->w + 1)/2, (rect->h + 1)/2, + GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, UVplane); + + return GL_CheckError("glTexSubImage2D()", renderer); +} + + static int GL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture, const SDL_Rect * rect, void **pixels, int *pitch) @@ -1590,6 +1622,7 @@ GL_CreateRenderer(SDL_Window * window, Uint32 flags) renderer->CreateTexture = GL_CreateTexture; renderer->UpdateTexture = GL_UpdateTexture; renderer->UpdateTextureYUV = GL_UpdateTextureYUV; + renderer->UpdateTextureNV = GL_UpdateTextureNV; renderer->LockTexture = GL_LockTexture; renderer->UnlockTexture = GL_UnlockTexture; renderer->SetTextureScaleMode = GL_SetTextureScaleMode; diff --git a/src/render/opengles2/SDL_render_gles2.c b/src/render/opengles2/SDL_render_gles2.c index c61d9a13b..bf6bf93ac 100644 --- a/src/render/opengles2/SDL_render_gles2.c +++ b/src/render/opengles2/SDL_render_gles2.c @@ -1748,6 +1748,48 @@ GLES2_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture, return GL_CheckError("glTexSubImage2D()", renderer); } +static int +GLES2_UpdateTextureNV(SDL_Renderer * renderer, SDL_Texture * texture, + const SDL_Rect * rect, + const Uint8 *Yplane, int Ypitch, + const Uint8 *UVplane, int UVpitch) +{ + GLES2_RenderData *data = (GLES2_RenderData *)renderer->driverdata; + GLES2_TextureData *tdata = (GLES2_TextureData *)texture->driverdata; + + GLES2_ActivateRenderer(renderer); + + /* Bail out if we're supposed to update an empty rectangle */ + if (rect->w <= 0 || rect->h <= 0) { + return 0; + } + + data->drawstate.texture = NULL; /* we trash this state. */ + + data->glBindTexture(tdata->texture_type, tdata->texture_u); + GLES2_TexSubImage2D(data, tdata->texture_type, + rect->x / 2, + rect->y / 2, + (rect->w + 1) / 2, + (rect->h + 1) / 2, + GL_LUMINANCE_ALPHA, + GL_UNSIGNED_BYTE, + UVplane, UVpitch / 2, 1); + + data->glBindTexture(tdata->texture_type, tdata->texture); + GLES2_TexSubImage2D(data, tdata->texture_type, + rect->x, + rect->y, + rect->w, + rect->h, + tdata->pixel_format, + tdata->pixel_type, + Yplane, Ypitch, 1); + + return GL_CheckError("glTexSubImage2D()", renderer); +} + + static int GLES2_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch) @@ -2126,6 +2168,7 @@ GLES2_CreateRenderer(SDL_Window *window, Uint32 flags) renderer->CreateTexture = GLES2_CreateTexture; renderer->UpdateTexture = GLES2_UpdateTexture; renderer->UpdateTextureYUV = GLES2_UpdateTextureYUV; + renderer->UpdateTextureNV = GLES2_UpdateTextureNV; renderer->LockTexture = GLES2_LockTexture; renderer->UnlockTexture = GLES2_UnlockTexture; renderer->SetTextureScaleMode = GLES2_SetTextureScaleMode;