Procházet zdrojové kódy

testyuv: verify the YUV surface locking path

Also implemented YUV surface locking for Vulkan
Sam Lantinga před 1 týdnem
rodič
revize
0f5a7cbd2c
3 změnil soubory, kde provedl 338 přidání a 24 odebrání
  1. 14 14
      src/render/SDL_yuv_sw.c
  2. 35 0
      src/render/vulkan/SDL_render_vulkan.c
  3. 289 10
      test/testyuv.c

+ 14 - 14
src/render/SDL_yuv_sw.c

@@ -73,12 +73,13 @@ SDL_SW_YUVTexture *SDL_SW_CreateYUVTexture(SDL_PixelFormat format, SDL_Colorspac
     }
 
     // Find the pitch and offset values for the texture
+    const int bpp = SDL_BYTESPERPIXEL(format);
     switch (format) {
     case SDL_PIXELFORMAT_YV12:
     case SDL_PIXELFORMAT_IYUV:
     case SDL_PIXELFORMAT_I0FL:
-        swdata->pitches[0] = w * SDL_BYTESPERPIXEL(format);
-        swdata->pitches[1] = ((w + 1) / 2) * SDL_BYTESPERPIXEL(format);
+        swdata->pitches[0] = w * bpp;
+        swdata->pitches[1] = (((swdata->pitches[0] / bpp) + 1) / 2) * bpp;
         swdata->pitches[2] = swdata->pitches[1];
         swdata->planes[0] = swdata->pixels;
         swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h;
@@ -86,7 +87,7 @@ SDL_SW_YUVTexture *SDL_SW_CreateYUVTexture(SDL_PixelFormat format, SDL_Colorspac
         break;
     case SDL_PIXELFORMAT_I444:
     case SDL_PIXELFORMAT_I4FL:
-        swdata->pitches[0] = w * SDL_BYTESPERPIXEL(format);
+        swdata->pitches[0] = w * bpp;
         swdata->pitches[1] = swdata->pitches[0];
         swdata->pitches[2] = swdata->pitches[1];
         swdata->planes[0] = swdata->pixels;
@@ -127,6 +128,8 @@ bool SDL_SW_QueryYUVTexturePixels(SDL_SW_YUVTexture *swdata, void **pixels,
 bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
                             const void *pixels, int pitch)
 {
+    const int bpp = SDL_BYTESPERPIXEL(swdata->format);
+
     switch (swdata->format) {
     case SDL_PIXELFORMAT_YV12:
     case SDL_PIXELFORMAT_IYUV:
@@ -134,17 +137,16 @@ bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
         if (rect->x == 0 && rect->y == 0 &&
             rect->w == swdata->w && rect->h == swdata->h && pitch == swdata->pitches[0]) {
             SDL_memcpy(swdata->pixels, pixels,
-                       (size_t)(swdata->h * swdata->w) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2));
+                       (size_t)(swdata->h * swdata->w * bpp) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2) * bpp);
         } else {
             Uint8 *src, *dst;
             int row;
             size_t length;
-            const int bpp = SDL_BYTESPERPIXEL(swdata->format);
             const int UVpitch = ((pitch / bpp + 1) / 2) * bpp;
 
             // Copy the Y plane
             src = (Uint8 *)pixels;
-            dst = swdata->pixels + rect->y * swdata->w + rect->x;
+            dst = swdata->pixels + rect->y * swdata->w * bpp + rect->x * bpp;
             length = rect->w * bpp;
             for (row = 0; row < rect->h; ++row) {
                 SDL_memcpy(dst, src, length);
@@ -154,8 +156,8 @@ bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
 
             // Copy the next plane
             src = (Uint8 *)pixels + rect->h * pitch;
-            dst = swdata->pixels + swdata->h * swdata->w;
-            dst += rect->y / 2 * ((swdata->w + 1) / 2) + rect->x / 2;
+            dst = swdata->pixels + swdata->h * swdata->pitches[0];
+            dst += (rect->y / 2) * swdata->pitches[1] + (rect->x / 2) * bpp;
             length = ((rect->w + 1) / 2) * bpp;
             for (row = 0; row < (rect->h + 1) / 2; ++row) {
                 SDL_memcpy(dst, src, length);
@@ -165,9 +167,8 @@ bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
 
             // Copy the next plane
             src = (Uint8 *)pixels + rect->h * pitch + ((rect->h + 1) / 2) * UVpitch;
-            dst = swdata->pixels + swdata->h * swdata->w +
-                  ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2);
-            dst += rect->y / 2 * ((swdata->w + 1) / 2) + rect->x / 2;
+            dst = swdata->pixels + swdata->h * swdata->pitches[0] + ((rect->h + 1) / 2) * swdata->pitches[1];
+            dst += (rect->y / 2) * swdata->pitches[2] + (rect->x / 2) * bpp;
             length = ((rect->w + 1) / 2) * bpp;
             for (row = 0; row < (rect->h + 1) / 2; ++row) {
                 SDL_memcpy(dst, src, length);
@@ -185,12 +186,11 @@ bool SDL_SW_UpdateYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
             Uint8 *src, *dst;
             int row;
             size_t length;
-            const int bpp = SDL_BYTESPERPIXEL(swdata->format);
 
             // Copy the Y plane
             src = (Uint8 *)pixels;
             dst = swdata->pixels;
-            dst += rect->y * swdata->pitches[0] + rect->x *bpp;
+            dst += rect->y * swdata->pitches[0] + rect->x * bpp;
             length = rect->w * bpp;
             for (row = 0; row < rect->h; ++row) {
                 SDL_memcpy(dst, src, length);
@@ -403,7 +403,7 @@ bool SDL_SW_LockYUVTexture(SDL_SW_YUVTexture *swdata, const SDL_Rect *rect,
         }
         break;
     default:
-        return SDL_SetError("Unsupported YUV format");
+        break;
     }
 
     if (rect) {

+ 35 - 0
src/render/vulkan/SDL_render_vulkan.c

@@ -37,6 +37,7 @@
 #include "../SDL_sysrender.h"
 #include "../SDL_d3dmath.h"
 #include "../../video/SDL_pixels_c.h"
+#include "../../video/SDL_yuv_c.h"
 #include "SDL_shaders_vulkan.h"
 
 #define SET_ERROR_CODE(message, rc)                                                                 \
@@ -267,6 +268,8 @@ typedef struct
     // Pipeline layout with immutable sampler descriptor set layout
     VkPipelineLayout pipelineLayoutYcbcr;
 
+    Uint8 *pixels;
+    int pitch;
 } VULKAN_TextureData;
 
 // Pipeline State Object data
@@ -2872,6 +2875,7 @@ static void VULKAN_DestroyTexture(SDL_Renderer *renderer,
         }
     }
 
+    SDL_free(textureData->pixels);
     SDL_free(textureData);
     texture->internal = NULL;
 }
@@ -3105,6 +3109,28 @@ static bool VULKAN_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
         return SDL_SetError("texture is already locked");
     }
 
+    bool yuv = (textureData->descriptorSetLayoutYcbcr != VK_NULL_HANDLE);
+    if (yuv) {
+        // It's more efficient to upload directly...
+        if (!textureData->pixels) {
+            size_t size, calculated_pitch;
+            if (!SDL_CalculateYUVSize(texture->format, texture->w, texture->h, &size, &calculated_pitch)) {
+                return false;
+            }
+            textureData->pitch = (int)calculated_pitch;
+            textureData->pixels = (Uint8 *)SDL_malloc(size);
+            if (!textureData->pixels) {
+                return false;
+            }
+        }
+        textureData->lockedRect = *rect;
+        *pixels =
+            (void *)(textureData->pixels + rect->y * textureData->pitch +
+                     rect->x * SDL_BYTESPERPIXEL(texture->format));
+        *pitch = textureData->pitch;
+        return true;
+    }
+
     VkDeviceSize pixelSize = VULKAN_GetBytesPerPixel(textureData->mainImage.format, 0);
     VkDeviceSize length = rect->w * pixelSize;
     VkDeviceSize stagingBufferSize = length * rect->h;
@@ -3142,6 +3168,15 @@ static void VULKAN_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
         return;
     }
 
+    bool yuv = (textureData->descriptorSetLayoutYcbcr != VK_NULL_HANDLE);
+    if (yuv) {
+        const SDL_Rect *rect = &textureData->lockedRect;
+        const Uint8 *pixels = textureData->pixels + rect->y * textureData->pitch + rect->x * SDL_BYTESPERPIXEL(texture->format);
+
+        VULKAN_UpdateTexture(renderer, texture, rect, pixels, textureData->pitch);
+        return;
+    }
+
     VULKAN_EnsureCommandBuffer(rendererData);
 
      // Make sure the destination is in the correct resource state

+ 289 - 10
test/testyuv.c

@@ -373,7 +373,266 @@ done:
     return result;
 }
 
-static bool create_textures(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool monochrome, int luminance, SDL_Texture *output[3])
+// Copied from SDL_yuv_sw.c to verify functionality
+
+struct SW_YUVTexture
+{
+    SDL_PixelFormat format;
+    int w, h;
+    Uint8 *pixels;
+    int pitches[3];
+    Uint8 *planes[3];
+};
+
+typedef struct SW_YUVTexture SW_YUVTexture;
+
+static SW_YUVTexture *SW_CreateYUVTexture(SDL_PixelFormat format, int w, int h, void *pixels, int pitch)
+{
+    SW_YUVTexture *swdata;
+
+    switch (format) {
+    case SDL_PIXELFORMAT_YV12:
+    case SDL_PIXELFORMAT_IYUV:
+    case SDL_PIXELFORMAT_I444:
+    case SDL_PIXELFORMAT_I0FL:
+    case SDL_PIXELFORMAT_I4FL:
+    case SDL_PIXELFORMAT_YUY2:
+    case SDL_PIXELFORMAT_UYVY:
+    case SDL_PIXELFORMAT_YVYU:
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
+        break;
+    default:
+        SDL_SetError("Unsupported YUV format");
+        return NULL;
+    }
+
+    swdata = (SW_YUVTexture *)SDL_calloc(1, sizeof(*swdata));
+    if (!swdata) {
+        return NULL;
+    }
+
+    swdata->format = format;
+    swdata->w = w;
+    swdata->h = h;
+    swdata->pixels = (Uint8 *)pixels;
+
+    // Find the pitch and offset values for the texture
+    const int bpp = SDL_BYTESPERPIXEL(format);
+    switch (format) {
+    case SDL_PIXELFORMAT_YV12:
+    case SDL_PIXELFORMAT_IYUV:
+    case SDL_PIXELFORMAT_I0FL:
+        swdata->pitches[0] = pitch;
+        swdata->pitches[1] = (((swdata->pitches[0] / bpp) + 1) / 2) * bpp;
+        swdata->pitches[2] = swdata->pitches[1];
+        swdata->planes[0] = swdata->pixels;
+        swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h;
+        swdata->planes[2] = swdata->planes[1] + swdata->pitches[1] * ((h + 1) / 2);
+        break;
+    case SDL_PIXELFORMAT_I444:
+    case SDL_PIXELFORMAT_I4FL:
+        swdata->pitches[0] = w * bpp;
+        swdata->pitches[1] = swdata->pitches[0];
+        swdata->pitches[2] = swdata->pitches[1];
+        swdata->planes[0] = swdata->pixels;
+        swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h;
+        swdata->planes[2] = swdata->planes[1] + swdata->pitches[1] * h;
+        break;
+    case SDL_PIXELFORMAT_YUY2:
+    case SDL_PIXELFORMAT_UYVY:
+    case SDL_PIXELFORMAT_YVYU:
+        swdata->pitches[0] = ((w + 1) / 2) * 4;
+        swdata->planes[0] = swdata->pixels;
+        break;
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
+        swdata->pitches[0] = w;
+        swdata->pitches[1] = 2 * ((swdata->pitches[0] + 1) / 2);
+        swdata->planes[0] = swdata->pixels;
+        swdata->planes[1] = swdata->planes[0] + swdata->pitches[0] * h;
+        break;
+
+    default:
+        SDL_assert(!"We should never get here (caught above)");
+        break;
+    }
+
+    // We're all done..
+    return swdata;
+}
+
+static bool SW_UpdateYUVTexture(SW_YUVTexture *swdata, const SDL_Rect *rect, const void *pixels, int pitch)
+{
+    const int bpp = SDL_BYTESPERPIXEL(swdata->format);
+
+    switch (swdata->format) {
+    case SDL_PIXELFORMAT_YV12:
+    case SDL_PIXELFORMAT_IYUV:
+    case SDL_PIXELFORMAT_I0FL:
+        if (rect->x == 0 && rect->y == 0 &&
+            rect->w == swdata->w && rect->h == swdata->h && pitch == swdata->pitches[0]) {
+            SDL_memcpy(swdata->pixels, pixels,
+                       (size_t)(swdata->h * swdata->w * bpp) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2) * bpp);
+        } else {
+            Uint8 *src, *dst;
+            int row;
+            size_t length;
+            const int UVpitch = ((pitch / bpp + 1) / 2) * bpp;
+
+            // Copy the Y plane
+            src = (Uint8 *)pixels;
+            dst = swdata->pixels + rect->y * swdata->w * bpp + rect->x * bpp;
+            length = rect->w * bpp;
+            for (row = 0; row < rect->h; ++row) {
+                SDL_memcpy(dst, src, length);
+                src += pitch;
+                dst += swdata->pitches[0];
+            }
+
+            // Copy the next plane
+            src = (Uint8 *)pixels + rect->h * pitch;
+            dst = swdata->pixels + swdata->h * swdata->pitches[0];
+            dst += (rect->y / 2) * swdata->pitches[1] + (rect->x / 2) * bpp;
+            length = ((rect->w + 1) / 2) * bpp;
+            for (row = 0; row < (rect->h + 1) / 2; ++row) {
+                SDL_memcpy(dst, src, length);
+                src += UVpitch;
+                dst += swdata->pitches[1];
+            }
+
+            // Copy the next plane
+            src = (Uint8 *)pixels + rect->h * pitch + ((rect->h + 1) / 2) * UVpitch;
+            dst = swdata->pixels + swdata->h * swdata->pitches[0] + ((rect->h + 1) / 2) * swdata->pitches[1];
+            dst += (rect->y / 2) * swdata->pitches[2] + (rect->x / 2) * bpp;
+            length = ((rect->w + 1) / 2) * bpp;
+            for (row = 0; row < (rect->h + 1) / 2; ++row) {
+                SDL_memcpy(dst, src, length);
+                src += UVpitch;
+                dst += swdata->pitches[2];
+            }
+        }
+        break;
+    case SDL_PIXELFORMAT_I444:
+    case SDL_PIXELFORMAT_I4FL:
+        if (rect->x == 0 && rect->y == 0 &&
+            rect->w == swdata->w && rect->h == swdata->h && pitch == swdata->pitches[0]) {
+            SDL_memcpy(swdata->pixels, pixels, (size_t)(swdata->h * pitch * 3));
+        } else {
+            Uint8 *src, *dst;
+            int row;
+            size_t length;
+
+            // Copy the Y plane
+            src = (Uint8 *)pixels;
+            dst = swdata->pixels;
+            dst += rect->y * swdata->pitches[0] + rect->x * bpp;
+            length = rect->w * bpp;
+            for (row = 0; row < rect->h; ++row) {
+                SDL_memcpy(dst, src, length);
+                src += pitch;
+                dst += swdata->pitches[0];
+            }
+
+            // Copy the next plane
+            dst = swdata->pixels + swdata->h * swdata->pitches[0];
+            dst += rect->y * swdata->pitches[1] + rect->x * bpp;
+            for (row = 0; row < rect->h; ++row) {
+                SDL_memcpy(dst, src, length);
+                src += pitch;
+                dst += swdata->pitches[1];
+            }
+
+            // Copy the next plane
+            dst = swdata->pixels + swdata->h * swdata->pitches[0] + swdata->h * swdata->pitches[1];
+            dst += rect->y * swdata->pitches[2] + rect->x * bpp;
+            for (row = 0; row < rect->h; ++row) {
+                SDL_memcpy(dst, src, length);
+                src += pitch;
+                dst += swdata->pitches[2];
+            }
+        }
+        break;
+    case SDL_PIXELFORMAT_YUY2:
+    case SDL_PIXELFORMAT_UYVY:
+    case SDL_PIXELFORMAT_YVYU:
+    {
+        Uint8 *src, *dst;
+        int row;
+        size_t length;
+
+        src = (Uint8 *)pixels;
+        dst =
+            swdata->planes[0] + rect->y * swdata->pitches[0] +
+            rect->x * 2;
+        length = 4 * (((size_t)rect->w + 1) / 2);
+        for (row = 0; row < rect->h; ++row) {
+            SDL_memcpy(dst, src, length);
+            src += pitch;
+            dst += swdata->pitches[0];
+        }
+    } break;
+    case SDL_PIXELFORMAT_NV12:
+    case SDL_PIXELFORMAT_NV21:
+    {
+        if (rect->x == 0 && rect->y == 0 && rect->w == swdata->w && rect->h == swdata->h) {
+            SDL_memcpy(swdata->pixels, pixels,
+                       (size_t)(swdata->h * swdata->w) + 2 * ((swdata->h + 1) / 2) * ((swdata->w + 1) / 2));
+        } else {
+
+            Uint8 *src, *dst;
+            int row;
+            size_t length;
+
+            // Copy the Y plane
+            src = (Uint8 *)pixels;
+            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 += pitch;
+                dst += swdata->w;
+            }
+
+            // Copy the next plane
+            src = (Uint8 *)pixels + rect->h * pitch;
+            dst = swdata->pixels + swdata->h * swdata->w;
+            dst += 2 * ((rect->y + 1) / 2) * ((swdata->w + 1) / 2) + 2 * (rect->x / 2);
+            length = 2 * (((size_t)rect->w + 1) / 2);
+            for (row = 0; row < (rect->h + 1) / 2; ++row) {
+                SDL_memcpy(dst, src, length);
+                src += 2 * ((pitch + 1) / 2);
+                dst += 2 * ((swdata->w + 1) / 2);
+            }
+        }
+    } break;
+    default:
+        return SDL_SetError("Unsupported YUV format");
+    }
+    return true;
+}
+
+static void SW_DestroyYUVTexture(SW_YUVTexture *swdata)
+{
+    if (swdata) {
+        SDL_free(swdata);
+    }
+}
+
+static bool update_locked_texture(SDL_PixelFormat format, int w, int h, const void *pixels, int pitch, void *output_pixels, int output_pitch)
+{
+    SW_YUVTexture *swdata = SW_CreateYUVTexture(format, w, h, output_pixels, output_pitch);
+    if (!swdata) {
+        return false;
+    }
+
+    SDL_Rect rect = { 0, 0, w, h };
+    bool result = SW_UpdateYUVTexture(swdata, &rect, pixels, pitch);
+    SW_DestroyYUVTexture(swdata);
+    return result;
+}
+
+static bool create_textures(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool lock, bool monochrome, int luminance, SDL_Texture *output[3])
 {
     SDL_Colorspace rgb_colorspace = SDL_COLORSPACE_SRGB;
     SDL_Colorspace yuv_colorspace;
@@ -556,6 +815,19 @@ static bool create_textures(SDL_Renderer *renderer, SDL_Surface *original, SDL_P
         SDL_UpdateNVTexture(output[2], NULL, plane0, Ypitch, plane1, UVpitch);
         SDL_free(plane0);
         SDL_free(plane1);
+    } else if (lock) {
+        void *dst;
+        int dst_pitch;
+        if (!SDL_LockTexture(output[2], NULL, &dst, &dst_pitch)) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't lock YUV texture: %s", SDL_GetError());
+            goto done;
+        }
+        bool updated = update_locked_texture(yuv_format, original->w, original->h, raw_yuv, pitch, dst, dst_pitch);
+        SDL_UnlockTexture(output[2]);
+        if (!updated) {
+            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't update YUV texture: %s", SDL_GetError());
+            goto done;
+        }
     } else {
         SDL_UpdateTexture(output[2], NULL, raw_yuv, pitch);
     }
@@ -610,12 +882,12 @@ static bool check_output(SDL_Renderer *renderer, SDL_Surface *original, SDL_Text
     return result;
 }
 
-static bool run_single_format_test(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar)
+static bool run_single_format_test(SDL_Renderer *renderer, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool lock)
 {
     SDL_Texture *output[3];
     bool result = true;
 
-    if (!create_textures(renderer, original, yuv_format, rgb_format, planar, false, 100, output)) {
+    if (!create_textures(renderer, original, yuv_format, rgb_format, planar, lock, false, 100, output)) {
         return false;
     }
 
@@ -668,7 +940,7 @@ static bool run_all_format_test(SDL_Window *window, const char *requested_render
         { YUV_CONVERSION_JPEG, "JPEG" },
         { YUV_CONVERSION_BT601, "BT601" },
         { YUV_CONVERSION_BT709, "BT709" },
-        { YUV_CONVERSION_BT2020, "BT2020" }
+        //{ YUV_CONVERSION_BT2020, "BT2020" }
     };
     bool quit = false;
     bool result = true;
@@ -700,10 +972,13 @@ static bool run_all_format_test(SDL_Window *window, const char *requested_render
                     SDL_PixelFormat rgb_format = rgb_formats[n];
 
                     SDL_Log("Testing: %s %s %s %s (planar)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format));
-                    result &= run_single_format_test(renderer, original, yuv_format, rgb_format, true);
+                    result &= run_single_format_test(renderer, original, yuv_format, rgb_format, true, false);
 
                     SDL_Log("Testing: %s %s %s %s (packed)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format));
-                    result &= run_single_format_test(renderer, original, yuv_format, rgb_format, false);
+                    result &= run_single_format_test(renderer, original, yuv_format, rgb_format, false, false);
+
+                    SDL_Log("Testing: %s %s %s %s (locked)", renderer_name, colorspaces[j].name, SDL_GetPixelFormatName(yuv_format), SDL_GetPixelFormatName(rgb_format));
+                    result &= run_single_format_test(renderer, original, yuv_format, rgb_format, false, true);
 
                     SDL_Event event;
                     while (SDL_PollEvent(&event)) {
@@ -720,7 +995,7 @@ static bool run_all_format_test(SDL_Window *window, const char *requested_render
     return result;
 }
 
-static bool run_interactive(SDL_Window *window, const char *renderer_name, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool monochrome, int luminance)
+static bool run_interactive(SDL_Window *window, const char *renderer_name, SDL_Surface *original, SDL_PixelFormat yuv_format, SDL_PixelFormat rgb_format, bool planar, bool lock, bool monochrome, int luminance)
 {
     const char *titles[3] = { "ORIGINAL", "SOFTWARE", "HARDWARE" };
     char title[128];
@@ -739,7 +1014,7 @@ static bool run_interactive(SDL_Window *window, const char *renderer_name, SDL_S
     renderer_name = SDL_GetRendererName(renderer);
 
     SDL_Texture *output[3];
-    if (!create_textures(renderer, original, yuv_format, rgb_format, planar, monochrome, luminance, output)) {
+    if (!create_textures(renderer, original, yuv_format, rgb_format, planar, lock, monochrome, luminance, output)) {
         goto done;
     }
 
@@ -865,6 +1140,7 @@ int main(int argc, char **argv)
     Uint32 yuv_format = SDL_PIXELFORMAT_YV12;
     Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888;
     bool planar = false;
+    bool lock = false;
     bool monochrome = false;
     int luminance = 100;
     int i;
@@ -964,6 +1240,9 @@ int main(int argc, char **argv)
             } else if (SDL_strcmp(argv[i], "--planar") == 0) {
                 planar = true;
                 consumed = 1;
+            } else if (SDL_strcmp(argv[i], "--lock") == 0) {
+                lock = true;
+                consumed = 1;
             } else if (SDL_strcmp(argv[i], "--monochrome") == 0) {
                 monochrome = true;
                 consumed = 1;
@@ -989,7 +1268,7 @@ int main(int argc, char **argv)
                 "[--jpeg|--bt601|--bt709|--auto]",
                 "[--yv12|--iyuv|--i444|--yuy2|--uyvy|--yvyu|--nv12|--nv21|--p010|--i4fl]",
                 "[--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra]",
-                "[--monochrome] [--luminance N%] [--planar]",
+                "[--monochrome] [--luminance N%] [--planar] [--lock]",
                 "[--automated] [--colorspace-test] [--renderer NAME]",
                 "[sample.png]",
                 NULL,
@@ -1047,7 +1326,7 @@ int main(int argc, char **argv)
             result = 5;
         }
     } else {
-        if (!run_interactive(window, renderer_name, original, yuv_format, rgb_format, planar, monochrome, luminance)) {
+        if (!run_interactive(window, renderer_name, original, yuv_format, rgb_format, planar, lock, monochrome, luminance)) {
             result = 5;
         }
     }