[PATCH v2 7/7] ati-vga: Implement HOST_DATA blit source with color expansion

Chad Jablonski posted 7 patches 1 week, 4 days ago
[PATCH v2 7/7] ati-vga: Implement HOST_DATA blit source with color expansion
Posted by Chad Jablonski 1 week, 4 days ago
SRCCOPY blits using 1bpp HOST_DATA as a source are expanded
to 32bpp ARGB. If pixman is enabled any additional color depth conversions
are handled. The fallback path does not yet support color conversion
and logs an error.

Unlike VRAM sourced blits, host data blits are not triggered by writing
to the dst width registers. They're only triggered on HOST_DATA_LAST
writes.

Supports MONO_FRGD_BKGD and COLOR datatypes. MONO_FRGD (transparent
background) is left for future work and logged.
GMC_SRC_SOURCE_HOST_DATA_ALIGNED is unimplemented and also logged.
Neither of these are used for xterm text rendering.

This combines clipping, host data transfers and color expansion to
enable text rendering in xterm under X.org.

Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
---
 hw/display/ati.c      |  28 +++++++--
 hw/display/ati_2d.c   | 135 +++++++++++++++++++++++++++++++++++-------
 hw/display/ati_regs.h |  13 ++++
 3 files changed, 152 insertions(+), 24 deletions(-)

diff --git a/hw/display/ati.c b/hw/display/ati.c
index 0a686750ae..6ec50279ed 100644
--- a/hw/display/ati.c
+++ b/hw/display/ati.c
@@ -813,9 +813,14 @@ static void ati_mm_write(void *opaque, hwaddr addr,
         }
         break;
     case DST_WIDTH:
+    {
+        uint32_t src = s->regs.dp_gui_master_cntl & GMC_SRC_SOURCE_MASK;
         s->regs.dst_width = data & 0x3fff;
-        ati_2d_blt(s);
+        if (src != GMC_SRC_SOURCE_HOST_DATA) {
+            ati_2d_blt(s);
+        }
         break;
+    }
     case DST_HEIGHT:
         s->regs.dst_height = data & 0x3fff;
         break;
@@ -862,10 +867,15 @@ static void ati_mm_write(void *opaque, hwaddr addr,
         s->regs.dst_y = (data >> 16) & 0x3fff;
         break;
     case DST_HEIGHT_WIDTH:
+    {
+        uint32_t src = s->regs.dp_gui_master_cntl & GMC_SRC_SOURCE_MASK;
         s->regs.dst_width = data & 0x3fff;
         s->regs.dst_height = (data >> 16) & 0x3fff;
-        ati_2d_blt(s);
+        if (src != GMC_SRC_SOURCE_HOST_DATA) {
+            ati_2d_blt(s);
+        }
         break;
+    }
     case DP_GUI_MASTER_CNTL:
         s->regs.dp_gui_master_cntl = data & 0xff00000f;
         s->regs.dp_datatype = (data & 0x0f00) >> 8 | (data & 0x30f0) << 4 |
@@ -881,10 +891,15 @@ static void ati_mm_write(void *opaque, hwaddr addr,
         }
         break;
     case DST_WIDTH_X:
+    {
+        uint32_t src = s->regs.dp_gui_master_cntl & GMC_SRC_SOURCE_MASK;
         s->regs.dst_x = data & 0x3fff;
         s->regs.dst_width = (data >> 16) & 0x3fff;
-        ati_2d_blt(s);
+        if (src != GMC_SRC_SOURCE_HOST_DATA) {
+            ati_2d_blt(s);
+        }
         break;
+    }
     case SRC_X_Y:
         s->regs.src_y = data & 0x3fff;
         s->regs.src_x = (data >> 16) & 0x3fff;
@@ -894,10 +909,15 @@ static void ati_mm_write(void *opaque, hwaddr addr,
         s->regs.dst_x = (data >> 16) & 0x3fff;
         break;
     case DST_WIDTH_HEIGHT:
+    {
+        uint32_t src = s->regs.dp_gui_master_cntl & GMC_SRC_SOURCE_MASK;
         s->regs.dst_height = data & 0x3fff;
         s->regs.dst_width = (data >> 16) & 0x3fff;
-        ati_2d_blt(s);
+        if (src != GMC_SRC_SOURCE_HOST_DATA) {
+            ati_2d_blt(s);
+        }
         break;
+    }
     case DST_HEIGHT_Y:
         s->regs.dst_y = data & 0x3fff;
         s->regs.dst_height = (data >> 16) & 0x3fff;
diff --git a/hw/display/ati_2d.c b/hw/display/ati_2d.c
index 181bf634f0..c177686338 100644
--- a/hw/display/ati_2d.c
+++ b/hw/display/ati_2d.c
@@ -25,6 +25,9 @@
  * possible.
  */
 
+#define DEFAULT_CNTL (s->regs.dp_gui_master_cntl & GMC_DST_PITCH_OFFSET_CNTL)
+#define EXPANDED_SRC_BPP 32
+
 static int ati_bpp_from_datatype(ATIVGAState *s)
 {
     switch (s->regs.dp_datatype & 0xf) {
@@ -44,7 +47,6 @@ static int ati_bpp_from_datatype(ATIVGAState *s)
     }
 }
 
-#define DEFAULT_CNTL (s->regs.dp_gui_master_cntl & GMC_DST_PITCH_OFFSET_CNTL)
 /* Convert 1bpp monochrome data to 32bpp ARGB using color expansion */
 static void expand_colors(uint8_t *color_dst, const uint8_t *mono_src,
                           uint32_t width, uint32_t height,
@@ -139,30 +141,112 @@ void ati_2d_blt(ATIVGAState *s)
     switch (s->regs.dp_mix & GMC_ROP3_MASK) {
     case ROP3_SRCCOPY:
     {
+        uint32_t src = s->regs.dp_gui_master_cntl & GMC_SRC_SOURCE_MASK;
+        uint8_t *color_data = NULL;
         bool fallback = false;
-        unsigned src_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ?
-                         s->regs.src_x + clip_left :
-                         s->regs.src_x + 1 - dst.width + clip_left);
-        unsigned src_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ?
-                         s->regs.src_y + clip_top :
-                         s->regs.src_y + 1 - dst.height + clip_top);
-        int src_stride = DEFAULT_CNTL ?
+        unsigned src_x, src_y, src_stride, src_bpp;
+        uint8_t *src_bits;
+
+        switch (src) {
+        case GMC_SRC_SOURCE_HOST_DATA:
+        {
+            unsigned src_datatype = s->regs.dp_gui_master_cntl &
+                                    GMC_SRC_DATATYPE_MASK;
+            switch (src_datatype) {
+            case GMC_SRC_DATATYPE_MONO_FRGD_BKGD:
+            {
+                bool lsb_to_msb = s->regs.dp_gui_master_cntl &
+                                  GMC_BYTE_ORDER_LSB_TO_MSB;
+                /* Monochrome source is 1 bpp aligned to 32-bit rows */
+                uint32_t mono_size = ((dst.width + 31) / 32) * 4 * dst.height;
+                if (s->host_data_pos < mono_size) {
+                    qemu_log_mask(LOG_UNIMP,
+                                  "HOST_DATA blit requires %u bytes, buffer holds %u "
+                                  "(increase buffer size)\n",
+                                  mono_size, s->host_data_pos);
+                    return;
+                }
+
+                /* Expand all of the source, clipping will be applied later */
+                color_data = g_malloc(dst.width * dst.height *
+                                      sizeof(uint32_t));
+                src_bpp = EXPANDED_SRC_BPP;
+                expand_colors(color_data, s->host_data_buffer,
+                              dst.width, dst.height, s->regs.dp_src_frgd_clr,
+                              s->regs.dp_src_bkgd_clr, lsb_to_msb);
+                break;
+            }
+            case GMC_SRC_DATATYPE_COLOR:
+            {
+                uint32_t color_size = dst.width * dst.height * (bpp / 8);
+                if (s->host_data_pos < color_size) {
+                    qemu_log_mask(LOG_UNIMP,
+                                  "HOST_DATA blit requires %u bytes, buffer holds %u "
+                                  "(increase buffer size)\n",
+                                  color_size, s->host_data_pos);
+                    return;
+                }
+                /*
+                 * The rage128 register guide states that the bit depth in this
+                 * case matches the bit depth of the dst. There is no
+                 * independent bit depth register for the src.
+                 */
+                src_bpp = bpp;
+                color_data = s->host_data_buffer;
+                break;
+            }
+            case GMC_SRC_DATATYPE_MONO_FRGD:
+                qemu_log_mask(LOG_UNIMP, "ati_2d blt source datatype "
+                              "MONO_FRGD (leave-alone) not yet supported\n");
+                return;
+            default:
+                qemu_log_mask(LOG_UNIMP, "ati_2d blt source datatype %x is "
+                              "not yet supported\n", src_datatype);
+                return;
+            }
+
+            src_x = clip_left;
+            src_y = clip_top;
+            src_stride = dst.width * (src_bpp / 8);
+            src_bits = color_data;
+            s->host_data_pos = 0;
+            break;
+        }
+        case GMC_SRC_SOURCE_MEMORY:
+        {
+            src_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ?
+                     s->regs.src_x + clip_left :
+                     s->regs.src_x + 1 - dst.width + clip_left);
+            src_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ?
+                     s->regs.src_y + clip_top :
+                     s->regs.src_y + 1 - dst.height + clip_top);
+            src_stride = DEFAULT_CNTL ?
                          s->regs.src_pitch : s->regs.default_pitch;
-        if (!src_stride) {
-            qemu_log_mask(LOG_GUEST_ERROR, "Zero source pitch\n");
+            src_bpp = bpp;
+            src_bits = s->vga.vram_ptr + (DEFAULT_CNTL ?
+                       s->regs.src_offset : s->regs.default_offset);
+            if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+                src_bits += s->regs.crtc_offset & 0x07ffffff;
+                src_stride *= bpp;
+            }
+            if (src_x > 0x3fff || src_y > 0x3fff || src_bits >= end
+                || src_bits + src_x
+                 + (src_y + clipped.height) * src_stride >= end) {
+                qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n");
+                return;
+            }
+            break;
+        }
+        case GMC_SRC_SOURCE_HOST_DATA_ALIGNED:
+        default:
+            qemu_log_mask(LOG_UNIMP, "ati_2d blt source %x is not "
+                          "yet supported\n", src);
             return;
         }
-        uint8_t *src_bits = s->vga.vram_ptr + (DEFAULT_CNTL ?
-                            s->regs.src_offset : s->regs.default_offset);
 
-        if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
-            src_bits += s->regs.crtc_offset & 0x07ffffff;
-            src_stride *= bpp;
-        }
-        if (src_x > 0x3fff || src_y > 0x3fff || src_bits >= end
-            || src_bits + src_x
-             + (src_y + clipped.height) * src_stride >= end) {
-            qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n");
+        if (!src_stride) {
+            qemu_log_mask(LOG_GUEST_ERROR, "Zero source pitch\n");
+            g_free(color_data);
             return;
         }
 
@@ -207,6 +291,15 @@ void ati_2d_blt(ATIVGAState *s)
             unsigned int src_pitch = src_stride * sizeof(uint32_t);
             unsigned int dst_pitch = dst_stride * sizeof(uint32_t);
 
+            if (src_bpp != bpp) {
+                qemu_log_mask(LOG_UNIMP,
+                              "Mismatched bit depths not yet supported "
+                              "in the fallback (non-pixman) implementation. "
+                              "src: %d != dst: %d\n", src_bpp, bpp);
+                g_free(color_data);
+                return;
+            }
+
             for (y = 0; y < clipped.height; y++) {
                 i = clipped.x * bypp;
                 j = src_x * bypp;
@@ -232,6 +325,8 @@ void ati_2d_blt(ATIVGAState *s)
                          clipped.x + clipped.width : clipped.x);
         s->regs.dst_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ?
                          clipped.y + clipped.height : clipped.y);
+
+        g_free(color_data);
         break;
     }
     case ROP3_PATCOPY:
diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h
index 9b52b61dcb..b741c3c6b8 100644
--- a/hw/display/ati_regs.h
+++ b/hw/display/ati_regs.h
@@ -404,6 +404,7 @@
 #define GMC_BRUSH_SOLIDCOLOR                    0x000000d0
 #define GMC_SRC_DSTCOLOR                        0x00003000
 #define GMC_BYTE_ORDER_MSB_TO_LSB               0x00000000
+#define GMC_BYTE_ORDER_LSB_TO_MSB               0x00004000
 #define GMC_DP_SRC_RECT                         0x02000000
 #define GMC_3D_FCN_EN_CLR                       0x00000000
 #define GMC_AUX_CLIP_CLEAR                      0x20000000
@@ -421,6 +422,18 @@
 #define GMC_DST_CLIP_DEFAULT                    0x00000000
 #define GMC_DST_CLIP_LEAVE_ALONE                0x00000008
 
+/* DP_GUI_MASTER_CNTL DP_SRC_DATATYPE named constants */
+#define GMC_SRC_DATATYPE_MASK                   0x00003000
+#define GMC_SRC_DATATYPE_MONO_FRGD_BKGD         0x00000000
+#define GMC_SRC_DATATYPE_MONO_FRGD              0x00001000
+#define GMC_SRC_DATATYPE_COLOR                  0x00003000
+
+/* DP_GUI_MASTER_CNTL DP_SRC_SOURCE named constants */
+#define GMC_SRC_SOURCE_MASK                     0x07000000
+#define GMC_SRC_SOURCE_MEMORY                   0x02000000
+#define GMC_SRC_SOURCE_HOST_DATA                0x03000000
+#define GMC_SRC_SOURCE_HOST_DATA_ALIGNED        0x04000000
+
 /* DP_GUI_MASTER_CNTL ROP3 named constants */
 #define GMC_ROP3_MASK                           0x00ff0000
 #define ROP3_BLACKNESS                          0x00000000
-- 
2.51.0