diff --git a/src/mapi/glapi/gen/gl_API.xml b/src/mapi/glapi/gen/gl_API.xml
index 19fe019f211..01f1598f851 100644
--- a/src/mapi/glapi/gen/gl_API.xml
+++ b/src/mapi/glapi/gen/gl_API.xml
@@ -1161,8 +1161,7 @@
-
+
diff --git a/src/mesa/main/glthread_pixels.c b/src/mesa/main/glthread_pixels.c
new file mode 100644
index 00000000000..c8fed4bb7db
--- /dev/null
+++ b/src/mesa/main/glthread_pixels.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 Advanced Micro Devices, Inc.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "main/glthread_marshal.h"
+#include "main/dispatch.h"
+#include "main/image.h"
+
+#define MAX_BITMAP_BYTE_SIZE 4096
+
+struct marshal_cmd_Bitmap
+{
+ struct marshal_cmd_base cmd_base;
+ uint16_t num_slots;
+ GLsizei width;
+ GLsizei height;
+ GLfloat xorig;
+ GLfloat yorig;
+ GLfloat xmove;
+ GLfloat ymove;
+ GLubyte *bitmap;
+};
+
+uint32_t
+_mesa_unmarshal_Bitmap(struct gl_context *ctx,
+ const struct marshal_cmd_Bitmap *restrict cmd)
+{
+ CALL_Bitmap(ctx->Dispatch.Current,
+ (cmd->width, cmd->height, cmd->xorig, cmd->yorig, cmd->xmove,
+ cmd->ymove, cmd->bitmap));
+ return cmd->num_slots;
+}
+
+void GLAPIENTRY
+_mesa_marshal_Bitmap(GLsizei width, GLsizei height, GLfloat xorig,
+ GLfloat yorig, GLfloat xmove, GLfloat ymove,
+ const GLubyte *bitmap)
+{
+ GET_CURRENT_CONTEXT(ctx);
+ int cmd_size = sizeof(struct marshal_cmd_Bitmap);
+
+ /* If not building a display list... */
+ if (!ctx->GLThread.ListMode) {
+ /* PBO path or bitmap == NULL (which means xmove/ymove only move the raster
+ * pos.
+ */
+ if (!bitmap || !_mesa_glthread_has_no_unpack_buffer(ctx)) {
+ struct marshal_cmd_Bitmap *cmd =
+ _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_Bitmap,
+ cmd_size);
+ cmd->num_slots = align(cmd_size, 8) / 8;
+ cmd->width = width;
+ cmd->height = height;
+ cmd->xorig = xorig;
+ cmd->yorig = yorig;
+ cmd->xmove = xmove;
+ cmd->ymove = ymove;
+ cmd->bitmap = (GLubyte *)bitmap;
+ return;
+ }
+
+ size_t bitmap_size =
+ (size_t)_mesa_image_row_stride(&ctx->GLThread.Unpack, width,
+ GL_COLOR_INDEX, GL_BITMAP) * height;
+
+ /* If the bitmap is small enough, copy it into the batch. */
+ if (bitmap_size <= MAX_BITMAP_BYTE_SIZE) {
+ struct marshal_cmd_Bitmap *cmd =
+ _mesa_glthread_allocate_command(ctx, DISPATCH_CMD_Bitmap,
+ cmd_size + bitmap_size);
+ cmd->num_slots = align(cmd_size + bitmap_size, 8) / 8;
+ cmd->width = width;
+ cmd->height = height;
+ cmd->xorig = xorig;
+ cmd->yorig = yorig;
+ cmd->xmove = xmove;
+ cmd->ymove = ymove;
+ cmd->bitmap = (GLubyte *)(cmd + 1);
+ memcpy(cmd->bitmap, bitmap, bitmap_size);
+ return;
+ }
+ }
+
+ _mesa_glthread_finish_before(ctx, "Bitmap");
+ CALL_Bitmap(ctx->Dispatch.Current,
+ (width, height, xorig, yorig, xmove, ymove, bitmap));
+}
diff --git a/src/mesa/meson.build b/src/mesa/meson.build
index d07b8fb33a7..f5fbcb327ea 100644
--- a/src/mesa/meson.build
+++ b/src/mesa/meson.build
@@ -123,6 +123,7 @@ files_libmesa = files(
'main/glthread_get.c',
'main/glthread_list.c',
'main/glthread_marshal.h',
+ 'main/glthread_pixels.c',
'main/glthread_shaderobj.c',
'main/glthread_varray.c',
'main/hash.c',