@samitouri / QOSamiQemu / commits / 527b07d2c5

ati-vga: Implement scissor rectangle clipping for 2D operations

Use scissor registers to clip blit operations. This is required for text rendering in X using the r128 driver. Without it overly-wide glyphs are drawn and create all sorts of chaos. The visible destination rectangle (vis_dst) is the intersection of the scissor rectangle and the destination rectangle (dst). The src also needs to be offset if clipped on the top and/or left sides to ensure that src data is read correctly and appears clipped when drawn rather than shifted. Signed-off-by: Chad Jablonski <chad@jablonski.xyz> Reviewed-by: BALATON Zoltan <balaton@eik.bme.hu> [balaton: Fix build without pixman] Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu> Message-ID: <4752dfbe240102c2ffa1f2cdab4c9442d4f0dcfb.1773020351.git.balaton@eik.bme.hu> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>

Chad Jablonski committed Mar 9, 2026 at 02:47 UTC 527b07d2c59f2cda5186476b9d8b79371102689b
1 file changed +58 -29
hw/display/ati_2d.c
+58 -29
@@ -52,6 +52,7 @@ typedef struct {
52 uint32_t frgd_clr;
53 const uint8_t *palette;
54 const uint8_t *vram_end;
55 + QemuRect scissor;
56
57 QemuRect dst;
58 int dst_stride;
@@ -91,6 +92,11 @@ static void setup_2d_blt_ctx(const ATIVGAState *s, ATI2DCtx *ctx)
92 ctx->dst_offset = s->regs.dst_offset;
93 ctx->vram_end = s->vga.vram_ptr + s->vga.vram_size;
94
95 + ctx->scissor.width = s->regs.sc_right - s->regs.sc_left + 1;
96 + ctx->scissor.height = s->regs.sc_bottom - s->regs.sc_top + 1;
97 + ctx->scissor.x = s->regs.sc_left;
98 + ctx->scissor.y = s->regs.sc_top;
99 +
100 ctx->dst.width = s->regs.dst_width;
101 ctx->dst.height = s->regs.dst_height;
102 ctx->dst.x = (ctx->left_to_right ?
@@ -125,6 +131,8 @@ static void setup_2d_blt_ctx(const ATIVGAState *s, ATI2DCtx *ctx)
131
132 static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman)
133 {
134 + QemuRect vis_src, vis_dst;
135 +
136 if (!ctx->bpp) {
137 qemu_log_mask(LOG_GUEST_ERROR, "Invalid bpp\n");
138 return false;
@@ -139,6 +147,29 @@ static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman)
147 qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n");
148 return false;
149 }
150 + qemu_rect_intersect(&ctx->dst, &ctx->scissor, &vis_dst);
151 + if (!vis_dst.height || !vis_dst.width) {
152 + /* Nothing is visible, completely clipped */
153 + return false;
154 + }
155 + /*
156 + * The src must be offset if clipping is applied to the dst.
157 + * This is so that when the source is blit to a dst clipped
158 + * on the top or left the src image is not shifted into the
159 + * clipped region but actually clipped.
160 + */
161 + vis_src.x = ctx->src.x + (vis_dst.x - ctx->dst.x);
162 + vis_src.y = ctx->src.y + (vis_dst.y - ctx->dst.y);
163 + vis_src.width = vis_dst.width;
164 + vis_src.height = vis_dst.height;
165 +
166 + DPRINTF("dst: (%d,%d) %dx%d -> vis_dst: (%d,%d) %dx%d\n",
167 + ctx->dst.x, ctx->dst.y, ctx->dst.width, ctx->dst.height,
168 + vis_dst.x, vis_dst.y, vis_dst.width, vis_dst.height);
169 + DPRINTF("src: (%d,%d) %dx%d -> vis_src: (%d,%d) %dx%d\n",
170 + ctx->src.x, ctx->src.y, ctx->dst.width, ctx->dst.height,
171 + vis_src.x, vis_src.y, vis_src.width, vis_src.height);
172 +
173 switch (ctx->rop3) {
174 case ROP3_SRCCOPY:
175 {
@@ -147,10 +178,9 @@ static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman)
178 qemu_log_mask(LOG_GUEST_ERROR, "Zero source pitch\n");
179 return false;
180 }
150 - if (ctx->src.x > 0x3fff || ctx->src.y > 0x3fff ||
151 - ctx->src_bits >= ctx->vram_end ||
152 - ctx->src_bits + ctx->src.x + (ctx->src.y + ctx->dst.height) *
153 - ctx->src_stride >= ctx->vram_end) {
181 + if (vis_src.x > 0x3fff || vis_src.y > 0x3fff ||
182 + ctx->src_bits >= ctx->vram_end || ctx->src_bits + vis_src.x +
183 + (vis_src.y + vis_dst.height) * ctx->src_stride >= ctx->vram_end) {
184 qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n");
185 return false;
186 }
@@ -159,8 +189,8 @@ static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman)
189 ctx->src_bits, ctx->dst_bits,
190 ctx->src_stride / sizeof(uint32_t),
191 ctx->dst_stride / sizeof(uint32_t),
162 - ctx->bpp, ctx->bpp, ctx->src.x, ctx->src.y, ctx->dst.x,
163 - ctx->dst.y, ctx->dst.width, ctx->dst.height);
192 + ctx->bpp, ctx->bpp, vis_src.x, vis_src.y, vis_dst.x, vis_dst.y,
193 + vis_dst.width, vis_dst.height);
194 #ifdef CONFIG_PIXMAN
195 int src_stride_words = ctx->src_stride / sizeof(uint32_t);
196 int dst_stride_words = ctx->dst_stride / sizeof(uint32_t);
@@ -169,24 +199,24 @@ static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman)
199 fallback = !pixman_blt((uint32_t *)ctx->src_bits,
200 (uint32_t *)ctx->dst_bits, src_stride_words,
201 dst_stride_words, ctx->bpp, ctx->bpp,
172 - ctx->src.x, ctx->src.y, ctx->dst.x,
173 - ctx->dst.y, ctx->dst.width, ctx->dst.height);
202 + vis_src.x, vis_src.y, vis_dst.x, vis_dst.y,
203 + vis_dst.width, vis_dst.height);
204 } else if (use_pixman & BIT(1)) {
205 /* FIXME: We only really need a temporary if src and dst overlap */
176 - int llb = ctx->dst.width * (ctx->bpp / 8);
206 + int llb = vis_dst.width * (ctx->bpp / 8);
207 int tmp_stride_words = DIV_ROUND_UP(llb, sizeof(uint32_t));
208 uint32_t *tmp = g_malloc(tmp_stride_words * sizeof(uint32_t) *
179 - ctx->dst.height);
209 + vis_dst.height);
210 fallback = !pixman_blt((uint32_t *)ctx->src_bits, tmp,
211 src_stride_words, tmp_stride_words, ctx->bpp,
182 - ctx->bpp, ctx->src.x, ctx->src.y, 0, 0,
183 - ctx->dst.width, ctx->dst.height);
212 + ctx->bpp, vis_src.x, vis_src.y, 0, 0,
213 + vis_dst.width, vis_dst.height);
214 if (!fallback) {
215 fallback = !pixman_blt(tmp, (uint32_t *)ctx->dst_bits,
216 tmp_stride_words, dst_stride_words,
217 ctx->bpp, ctx->bpp, 0, 0,
188 - ctx->dst.x, ctx->dst.y,
189 - ctx->dst.width, ctx->dst.height);
218 + vis_dst.x, vis_dst.y,
219 + vis_dst.width, vis_dst.height);
220 }
221 g_free(tmp);
222 } else
@@ -196,20 +226,20 @@ static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman)
226 }
227 if (fallback) {
228 unsigned int y, i, j, bypp = ctx->bpp / 8;
199 - for (y = 0; y < ctx->dst.height; y++) {
200 - i = ctx->dst.x * bypp;
201 - j = ctx->src.x * bypp;
229 + for (y = 0; y < vis_dst.height; y++) {
230 + i = vis_dst.x * bypp;
231 + j = vis_src.x * bypp;
232 if (ctx->top_to_bottom) {
203 - i += (ctx->dst.y + y) * ctx->dst_stride;
204 - j += (ctx->src.y + y) * ctx->src_stride;
233 + i += (vis_dst.y + y) * ctx->dst_stride;
234 + j += (vis_src.y + y) * ctx->src_stride;
235 } else {
206 - i += (ctx->dst.y + ctx->dst.height - 1 - y)
236 + i += (vis_dst.y + vis_dst.height - 1 - y)
237 * ctx->dst_stride;
208 - j += (ctx->src.y + ctx->dst.height - 1 - y)
238 + j += (vis_src.y + vis_dst.height - 1 - y)
239 * ctx->src_stride;
240 }
241 memmove(&ctx->dst_bits[i], &ctx->src_bits[j],
212 - ctx->dst.width * bypp);
242 + vis_dst.width * bypp);
243 }
244 }
245 break;
@@ -238,21 +268,20 @@ static bool ati_2d_do_blt(ATI2DCtx *ctx, uint8_t use_pixman)
268
269 DPRINTF("pixman_fill(%p, %ld, %d, %d, %d, %d, %d, %x)\n",
270 ctx->dst_bits, ctx->dst_stride / sizeof(uint32_t), ctx->bpp,
241 - ctx->dst.x, ctx->dst.y, ctx->dst.width, ctx->dst.height,
242 - filler);
271 + vis_dst.x, vis_dst.y, vis_dst.width, vis_dst.height, filler);
272 #ifdef CONFIG_PIXMAN
273 if (!(use_pixman & BIT(0)) ||
274 !pixman_fill((uint32_t *)ctx->dst_bits,
275 ctx->dst_stride / sizeof(uint32_t), ctx->bpp,
247 - ctx->dst.x, ctx->dst.y,
248 - ctx->dst.width, ctx->dst.height, filler))
276 + vis_dst.x, vis_dst.y, vis_dst.width, vis_dst.height,
277 + filler))
278 #endif
279 {
280 /* fallback when pixman failed or we don't want to call it */
281 unsigned int x, y, i, bypp = ctx->bpp / 8;
253 - for (y = 0; y < ctx->dst.height; y++) {
254 - i = ctx->dst.x * bypp + (ctx->dst.y + y) * ctx->dst_stride;
255 - for (x = 0; x < ctx->dst.width; x++, i += bypp) {
282 + for (y = 0; y < vis_dst.height; y++) {
283 + i = vis_dst.x * bypp + (vis_dst.y + y) * ctx->dst_stride;
284 + for (x = 0; x < vis_dst.width; x++, i += bypp) {
285 stn_he_p(&ctx->dst_bits[i], bypp, filler);
286 }
287 }