Implement read and write operations on SC_TOP_LEFT, SC_BOTTOM_RIGHT,
and SRC_SC_BOTTOM_RIGHT registers. These registers are also updated
when the src and/or dst clipping fields on DP_GUI_MASTER_CNTL are set
to default clipping.
Scissor clipping is used when rendering text in X.org. The r128 driver
sends host data much wider than is necessary to draw a glyph and cuts it
down to size using clipping before rendering. The actual clipping
implementation follows in a future patch.
This also includes a very minor refactor of the combined
default_sc_bottom_right field in the registers struct to
default_sc_bottom and default_sc_right. This was done to
stay consistent with the other scissor registers and prevent repeated
masking and extraction.
Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
Reviewed-by: BALATON Zoltan <balaton@eik.bme.hu>
---
v5: Removed unused default_sc_bottom_right field
---
hw/display/ati.c | 70 +++++++++++++++++++++++++++++++++++++++++--
hw/display/ati_int.h | 9 +++++-
hw/display/ati_regs.h | 2 ++
3 files changed, 78 insertions(+), 3 deletions(-)
diff --git a/hw/display/ati.c b/hw/display/ati.c
index 968ee4efca..21339d7e26 100644
--- a/hw/display/ati.c
+++ b/hw/display/ati.c
@@ -521,7 +521,32 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size)
val |= s->regs.default_tile << 16;
break;
case DEFAULT_SC_BOTTOM_RIGHT:
- val = s->regs.default_sc_bottom_right;
+ val = (s->regs.default_sc_bottom << 16) |
+ s->regs.default_sc_right;
+ break;
+ case SC_TOP:
+ val = s->regs.sc_top;
+ break;
+ case SC_LEFT:
+ val = s->regs.sc_left;
+ break;
+ case SC_BOTTOM:
+ val = s->regs.sc_bottom;
+ break;
+ case SC_RIGHT:
+ val = s->regs.sc_right;
+ break;
+ case SRC_SC_BOTTOM:
+ val = s->regs.src_sc_bottom;
+ break;
+ case SRC_SC_RIGHT:
+ val = s->regs.src_sc_right;
+ break;
+ case SC_TOP_LEFT:
+ case SC_BOTTOM_RIGHT:
+ case SRC_SC_BOTTOM_RIGHT:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Read from write-only register 0x%x\n", (unsigned)addr);
break;
default:
break;
@@ -884,6 +909,16 @@ static void ati_mm_write(void *opaque, hwaddr addr,
s->regs.dst_offset = s->regs.default_offset;
s->regs.dst_pitch = s->regs.default_pitch;
}
+ if (!(data & GMC_SRC_CLIPPING)) {
+ s->regs.src_sc_right = s->regs.default_sc_right;
+ s->regs.src_sc_bottom = s->regs.default_sc_bottom;
+ }
+ if (!(data & GMC_DST_CLIPPING)) {
+ s->regs.sc_top = 0;
+ s->regs.sc_left = 0;
+ s->regs.sc_right = s->regs.default_sc_right;
+ s->regs.sc_bottom = s->regs.default_sc_bottom;
+ }
break;
case DST_WIDTH_X:
s->regs.dst_x = data & 0x3fff;
@@ -963,7 +998,38 @@ static void ati_mm_write(void *opaque, hwaddr addr,
}
break;
case DEFAULT_SC_BOTTOM_RIGHT:
- s->regs.default_sc_bottom_right = data & 0x3fff3fff;
+ s->regs.default_sc_right = data & 0x3fff;
+ s->regs.default_sc_bottom = (data >> 16) & 0x3fff;
+ break;
+ case SC_TOP_LEFT:
+ s->regs.sc_left = data & 0x3fff;
+ s->regs.sc_top = (data >> 16) & 0x3fff;
+ break;
+ case SC_LEFT:
+ s->regs.sc_left = data & 0x3fff;
+ break;
+ case SC_TOP:
+ s->regs.sc_top = data & 0x3fff;
+ break;
+ case SC_BOTTOM_RIGHT:
+ s->regs.sc_right = data & 0x3fff;
+ s->regs.sc_bottom = (data >> 16) & 0x3fff;
+ break;
+ case SC_RIGHT:
+ s->regs.sc_right = data & 0x3fff;
+ break;
+ case SC_BOTTOM:
+ s->regs.sc_bottom = data & 0x3fff;
+ break;
+ case SRC_SC_BOTTOM_RIGHT:
+ s->regs.src_sc_right = data & 0x3fff;
+ s->regs.src_sc_bottom = (data >> 16) & 0x3fff;
+ break;
+ case SRC_SC_RIGHT:
+ s->regs.src_sc_right = data & 0x3fff;
+ break;
+ case SRC_SC_BOTTOM:
+ s->regs.src_sc_bottom = data & 0x3fff;
break;
default:
break;
diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h
index f5a47b82b0..7cf0933fa1 100644
--- a/hw/display/ati_int.h
+++ b/hw/display/ati_int.h
@@ -81,7 +81,14 @@ typedef struct ATIVGARegs {
uint32_t default_offset;
uint32_t default_pitch;
uint32_t default_tile;
- uint32_t default_sc_bottom_right;
+ uint16_t default_sc_bottom;
+ uint16_t default_sc_right;
+ uint16_t sc_top;
+ uint16_t sc_left;
+ uint16_t sc_bottom;
+ uint16_t sc_right;
+ uint16_t src_sc_bottom;
+ uint16_t src_sc_right;
} ATIVGARegs;
struct ATIVGAState {
diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h
index 0a0825db04..3999edb9b7 100644
--- a/hw/display/ati_regs.h
+++ b/hw/display/ati_regs.h
@@ -397,6 +397,8 @@
#define GMC_DST_PITCH_OFFSET_CNTL 0x00000002
#define GMC_SRC_CLIP_DEFAULT 0x00000000
#define GMC_DST_CLIP_DEFAULT 0x00000000
+#define GMC_SRC_CLIPPING 0x00000004
+#define GMC_DST_CLIPPING 0x00000008
#define GMC_BRUSH_SOLIDCOLOR 0x000000d0
#define GMC_SRC_DSTCOLOR 0x00003000
#define GMC_BYTE_ORDER_MSB_TO_LSB 0x00000000
--
2.51.2
On Thu, 15 Jan 2026, Chad Jablonski wrote:
> Implement read and write operations on SC_TOP_LEFT, SC_BOTTOM_RIGHT,
> and SRC_SC_BOTTOM_RIGHT registers. These registers are also updated
> when the src and/or dst clipping fields on DP_GUI_MASTER_CNTL are set
> to default clipping.
>
> Scissor clipping is used when rendering text in X.org. The r128 driver
> sends host data much wider than is necessary to draw a glyph and cuts it
> down to size using clipping before rendering. The actual clipping
> implementation follows in a future patch.
>
> This also includes a very minor refactor of the combined
> default_sc_bottom_right field in the registers struct to
> default_sc_bottom and default_sc_right. This was done to
> stay consistent with the other scissor registers and prevent repeated
> masking and extraction.
>
> Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
> Reviewed-by: BALATON Zoltan <balaton@eik.bme.hu>
>
> ---
>
> v5: Removed unused default_sc_bottom_right field
> ---
> hw/display/ati.c | 70 +++++++++++++++++++++++++++++++++++++++++--
> hw/display/ati_int.h | 9 +++++-
> hw/display/ati_regs.h | 2 ++
> 3 files changed, 78 insertions(+), 3 deletions(-)
>
> diff --git a/hw/display/ati.c b/hw/display/ati.c
> index 968ee4efca..21339d7e26 100644
> --- a/hw/display/ati.c
> +++ b/hw/display/ati.c
> @@ -521,7 +521,32 @@ static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size)
> val |= s->regs.default_tile << 16;
> break;
> case DEFAULT_SC_BOTTOM_RIGHT:
> - val = s->regs.default_sc_bottom_right;
> + val = (s->regs.default_sc_bottom << 16) |
> + s->regs.default_sc_right;
> + break;
> + case SC_TOP:
> + val = s->regs.sc_top;
> + break;
> + case SC_LEFT:
> + val = s->regs.sc_left;
> + break;
> + case SC_BOTTOM:
> + val = s->regs.sc_bottom;
> + break;
> + case SC_RIGHT:
> + val = s->regs.sc_right;
> + break;
> + case SRC_SC_BOTTOM:
> + val = s->regs.src_sc_bottom;
> + break;
> + case SRC_SC_RIGHT:
> + val = s->regs.src_sc_right;
> + break;
> + case SC_TOP_LEFT:
> + case SC_BOTTOM_RIGHT:
> + case SRC_SC_BOTTOM_RIGHT:
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "Read from write-only register 0x%x\n", (unsigned)addr);
> break;
> default:
> break;
> @@ -884,6 +909,16 @@ static void ati_mm_write(void *opaque, hwaddr addr,
> s->regs.dst_offset = s->regs.default_offset;
> s->regs.dst_pitch = s->regs.default_pitch;
> }
> + if (!(data & GMC_SRC_CLIPPING)) {
> + s->regs.src_sc_right = s->regs.default_sc_right;
> + s->regs.src_sc_bottom = s->regs.default_sc_bottom;
> + }
> + if (!(data & GMC_DST_CLIPPING)) {
> + s->regs.sc_top = 0;
> + s->regs.sc_left = 0;
> + s->regs.sc_right = s->regs.default_sc_right;
> + s->regs.sc_bottom = s->regs.default_sc_bottom;
> + }
> break;
> case DST_WIDTH_X:
> s->regs.dst_x = data & 0x3fff;
> @@ -963,7 +998,38 @@ static void ati_mm_write(void *opaque, hwaddr addr,
> }
> break;
> case DEFAULT_SC_BOTTOM_RIGHT:
> - s->regs.default_sc_bottom_right = data & 0x3fff3fff;
> + s->regs.default_sc_right = data & 0x3fff;
> + s->regs.default_sc_bottom = (data >> 16) & 0x3fff;
> + break;
> + case SC_TOP_LEFT:
> + s->regs.sc_left = data & 0x3fff;
> + s->regs.sc_top = (data >> 16) & 0x3fff;
> + break;
> + case SC_LEFT:
> + s->regs.sc_left = data & 0x3fff;
> + break;
> + case SC_TOP:
> + s->regs.sc_top = data & 0x3fff;
> + break;
> + case SC_BOTTOM_RIGHT:
> + s->regs.sc_right = data & 0x3fff;
> + s->regs.sc_bottom = (data >> 16) & 0x3fff;
> + break;
> + case SC_RIGHT:
> + s->regs.sc_right = data & 0x3fff;
> + break;
> + case SC_BOTTOM:
> + s->regs.sc_bottom = data & 0x3fff;
> + break;
> + case SRC_SC_BOTTOM_RIGHT:
> + s->regs.src_sc_right = data & 0x3fff;
> + s->regs.src_sc_bottom = (data >> 16) & 0x3fff;
> + break;
> + case SRC_SC_RIGHT:
> + s->regs.src_sc_right = data & 0x3fff;
> + break;
> + case SRC_SC_BOTTOM:
> + s->regs.src_sc_bottom = data & 0x3fff;
> break;
> default:
> break;
> diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h
> index f5a47b82b0..7cf0933fa1 100644
> --- a/hw/display/ati_int.h
> +++ b/hw/display/ati_int.h
> @@ -81,7 +81,14 @@ typedef struct ATIVGARegs {
> uint32_t default_offset;
> uint32_t default_pitch;
> uint32_t default_tile;
> - uint32_t default_sc_bottom_right;
> + uint16_t default_sc_bottom;
> + uint16_t default_sc_right;
> + uint16_t sc_top;
> + uint16_t sc_left;
> + uint16_t sc_bottom;
> + uint16_t sc_right;
> + uint16_t src_sc_bottom;
> + uint16_t src_sc_right;
I've tried to sort these to match register offsers so these should be
between dp_src_bkgd_clr and dp_cntl I think but second check that as I've
only quickly checked.
Regards,
BALATON Zoltan
> } ATIVGARegs;
>
> struct ATIVGAState {
> diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h
> index 0a0825db04..3999edb9b7 100644
> --- a/hw/display/ati_regs.h
> +++ b/hw/display/ati_regs.h
> @@ -397,6 +397,8 @@
> #define GMC_DST_PITCH_OFFSET_CNTL 0x00000002
> #define GMC_SRC_CLIP_DEFAULT 0x00000000
> #define GMC_DST_CLIP_DEFAULT 0x00000000
> +#define GMC_SRC_CLIPPING 0x00000004
> +#define GMC_DST_CLIPPING 0x00000008
> #define GMC_BRUSH_SOLIDCOLOR 0x000000d0
> #define GMC_SRC_DSTCOLOR 0x00003000
> #define GMC_BYTE_ORDER_MSB_TO_LSB 0x00000000
>
© 2016 - 2026 Red Hat, Inc.