From 025f0a0dc861fc19c4ba25093821f3dcefb3b773 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Fri, 7 Aug 2026 07:48:31 -0700 Subject: [PATCH 1/3] atirage: Support solid and color host rectangle fills Mac OS 8.6 programs GUI_TRAJ_CNTL to set draw-engine direction and issues fills through several destination register initiators. Model the composite register, destination initiators, signed inclusive scissors, tiling side effects, and Always_1 source selection. Accept source mix 7 independently of the unused background mix for solid fills, and consume 8-bit HOST_DATA writes in trajectory order. This lets the desktop paint and prevents host uploads from being reversed or clipped. References: - ATI 3D Rage LT Pro Register Reference Guide, pp. 5-8--5-12, 5-33, 5-39--5-42, 5-47--5-51, 5-58, and 5-64--5-65. - ATI Rage Pro and Derivatives Programmer's Guide, pp. 6-5, 6-11--6-19, and 6-27--6-29. --- devices/video/atimach64defs.h | 34 +++-- devices/video/atirage.cpp | 243 +++++++++++++++++++++++++++++++--- devices/video/atirage.h | 11 +- 3 files changed, 256 insertions(+), 32 deletions(-) diff --git a/devices/video/atimach64defs.h b/devices/video/atimach64defs.h index c5cabf32f1..383fb2f411 100644 --- a/devices/video/atimach64defs.h +++ b/devices/video/atimach64defs.h @@ -41,6 +41,16 @@ enum { ATI_PIX_FMT_ARGB8888 = 6, }; +/* Draw engine data-path values. */ +enum { + ATI_DP_COLOR_SRC_BKGD_CLR = 0, + ATI_DP_COLOR_SRC_FRGD_CLR = 1, + ATI_DP_COLOR_SRC_HOST = 2, + ATI_DP_COLOR_SRC_BLIT = 3, + ATI_DP_MONO_SRC_ALWAYS_1 = 0, + ATI_DP_MIX_SRC = 7, +}; + /** Mach64 register offsets. */ enum { ATI_CRTC_H_TOTAL_DISP = 0x000, // 0x0000 @@ -509,6 +519,8 @@ enum { ATI_SCALE_Y_INC = 0x07D, // 0x01F4 ATI_SCALE_VACC = 0x07E, // 0x01F8 ATI_SCALE_3D_CNTL = 0x07F, // 0x01FC + ATI_HOST_DATA0 = 0x080, // 0x0200 + ATI_HOST_DATA15 = 0x08F, // 0x023C ATI_HOST_CNTL = 0x090, // 0x0240 ATI_BM_HOSTDATA = 0x091, // 0x0244 ATI_BM_ADDR = 0x092, // 0x0248 @@ -520,10 +532,10 @@ enum { ATI_PAT_CNTL = 0x0A2, // 0x0288 ATI_SC_LEFT = 0x0A8, // 0x02A0 - ATI_SC_LEFT_pos = 0, ATI_SC_LEFT_size = 13, + ATI_SC_LEFT_pos = 0, ATI_SC_LEFT_size = 14, ATI_SC_RIGHT = 0x0A9, // 0x02A4 - ATI_SC_RIGHT_pos = 0, ATI_SC_RIGHT_size = 13, + ATI_SC_RIGHT_pos = 0, ATI_SC_RIGHT_size = 14, ATI_SC_LEFT_RIGHT = 0x0AA, // 0x02A8 @@ -599,14 +611,16 @@ enum { ATI_SRC_PATT_EN = 16, // VT/GT ATI_SRC_PATT_ROT_EN = 17, // VT/GT ATI_SRC_LINEAR_EN = 18, // VT/GT - ATI_SRC_LINE_X_DIR = 19, // VT/GT - ATI_SRC_TRACK_DST = 20, // VT/GT - ATI_PAT_MONO_EN = 23, // GT - ATI_PAT_CLR_4x2_EN = 24, // VT/GT - ATI_PAT_CLR_8x1_EN = 25, // VT/GT - ATI_HOST_BYTE_ALIGN = 26, // VT/GT - ATI_HOST_BIG_ENDIAN_EN = 28, // VT/GT - ATI_SRC_BYTE_ALIGN = 29, // VT/GT + ATI_SRC_BYTE_ALIGN = 19, // VT/GT + ATI_SRC_LINE_X_DIR = 20, // VT/GT + ATI_SRC_8x8x8_BRUSH = 21, // GT + ATI_FAST_FILL_EN = 22, // GT + ATI_SRC_TRACK_DST = 23, // VT/GT + ATI_PAT_MONO_EN = 24, // GT + ATI_PAT_CLR_4x2_EN = 25, // VT/GT + ATI_PAT_CLR_8x1_EN = 26, // VT/GT + ATI_HOST_BYTE_ALIGN = 28, // VT/GT + ATI_HOST_BIG_ENDIAN_EN = 29, // VT/GT ATI_GUI_STAT = 0x0CE, // 0x0338 ATI_GUI_ACTIVE = 0, diff --git a/devices/video/atirage.cpp b/devices/video/atirage.cpp index e718f5332b..98e4badcf1 100644 --- a/devices/video/atirage.cpp +++ b/devices/video/atirage.cpp @@ -37,6 +37,28 @@ static const int mach64_post_div[8] = { 3, 5, 6, 12 // alternate post dividers }; +static int32_t mach64_extract_signed(uint32_t value, int pos, int width) { + value = extract_bits(value, pos, width); + return static_cast(value << (32 - width)) >> (32 - width); +} + +static bool mach64_clip_axis(int coord, int increment, uint32_t length, + int scissor_min, int scissor_max, + int& skip, uint32_t& clipped_length) { + int first = increment > 0 ? scissor_min - coord : coord - scissor_max; + int last = increment > 0 ? scissor_max - coord : coord - scissor_min; + + first = std::max(first, 0); + last = std::min(last, int(length) - 1); + if (first > last) { + return false; + } + + skip = first; + clipped_length = last - first + 1; + return true; +} + /* Human readable Mach64 HW register names for easier debugging. */ static const std::map mach64_reg_names = { #define one_reg_name(x) {ATI_ ## x, #x} @@ -266,6 +288,13 @@ uint32_t ATIRage::read_reg(uint32_t reg_offset, uint32_t size) { case ATI_GUI_STAT: result = uint64_t(this->cmd_fifo_size << 16); // HACK: pretend empty FIFO break; + case ATI_GUI_TRAJ_CNTL: + result = 0; + insert_bits(result, this->regs[ATI_DST_CNTL], 0, 16); + insert_bits(result, this->regs[ATI_SRC_CNTL], 16, 8); + insert_bits(result, this->regs[ATI_PAT_CNTL], 24, 3); + insert_bits(result, this->regs[ATI_HOST_CNTL], 28, 2); + break; case ATI_DP_BKGD_CLR: case ATI_DP_FRGD_CLR: uint32_t pix_fmt = extract_bits( @@ -320,6 +349,11 @@ void ATIRage::write_reg(uint32_t reg_offset, uint32_t value, uint32_t size) { uint32_t old_value = this->regs[reg_num]; uint32_t new_value; + if (reg_num >= ATI_HOST_DATA0 && reg_num <= ATI_HOST_DATA15) { + this->write_host_data(value, size); + return; + } + if (offset || size != 4) { // slow path if ((offset + size) > 4) { ABORT_F("%s: unaligned DWORD writes not implemented", this->name.c_str()); @@ -581,9 +615,30 @@ void ATIRage::write_reg(uint32_t reg_offset, uint32_t value, uint32_t size) { this->regs[ATI_SRC_HEIGHT1] = extract_bits(value, 0, 16); this->regs[ATI_SRC_WIDTH1] = extract_bits(value, 16, 16); break; + case ATI_SC_LEFT_RIGHT: + new_value = value; + this->regs[ATI_SC_LEFT] = extract_bits(value, 0, ATI_SC_LEFT_size); + this->regs[ATI_SC_RIGHT] = extract_bits(value, 16, ATI_SC_RIGHT_size); + break; + case ATI_SC_TOP_BOTTOM: + new_value = value; + this->regs[ATI_SC_TOP] = extract_bits(value, 0, ATI_SC_TOP_size); + this->regs[ATI_SC_BOTTOM] = extract_bits(value, 16, ATI_SC_BOTTOM_size); + break; + case ATI_GUI_TRAJ_CNTL: + new_value = value; + insert_bits(this->regs[ATI_DST_CNTL], value, 0, 16); + insert_bits(this->regs[ATI_SRC_CNTL], value >> 16, 0, 8); + insert_bits(this->regs[ATI_PAT_CNTL], value >> 24, 0, 3); + insert_bits(this->regs[ATI_HOST_CNTL], value >> 28, 0, 2); + break; case ATI_DST_WIDTH: case ATI_DST_HEIGHT_WIDTH: case ATI_DST_X_WIDTH: + case ATI_DST_WIDTH_HEIGHT: + this->begin_drawing(reg_num, value); + new_value = this->regs[reg_num]; + break; case ATI_DST_BRES_LNTH: this->begin_drawing(reg_num, value); break; @@ -997,10 +1052,46 @@ int ATIRage::device_postinit() // =================================== Draw Engine ===================================== void ATIRage::begin_drawing(uint32_t initiator, uint32_t value) { switch(initiator) { - case ATI_DST_HEIGHT_WIDTH: + case ATI_DST_WIDTH: { + uint32_t width = extract_bits(value, ATI_DST_WIDTH_pos, ATI_DST_WIDTH_size); + this->regs[ATI_DST_WIDTH] = width; + if (!bit_set(value, DST_WIDTH_FILL_DIS)) { + this->draw_rect( + width, + extract_bits( + this->regs[ATI_DST_HEIGHT], ATI_DST_HEIGHT_pos, ATI_DST_HEIGHT_size)); + } + break; + } + case ATI_DST_HEIGHT_WIDTH: { + uint32_t width = extract_bits(value, 16, ATI_DST_WIDTH_size); + uint32_t height = extract_bits(value, 0, ATI_DST_HEIGHT_size); this->regs[ATI_DST_HEIGHT_WIDTH] = value; - this->draw_rect(extract_bits(value, 16, 14), extract_bits(value, 0, 15)); + this->regs[ATI_DST_WIDTH] = width; + this->regs[ATI_DST_HEIGHT] = height; + this->draw_rect(width, height); break; + } + case ATI_DST_X_WIDTH: { + uint32_t width = extract_bits(value, 16, ATI_DST_WIDTH_size); + this->regs[ATI_DST_X_WIDTH] = value; + this->regs[ATI_DST_X] = extract_bits(value, 0, ATI_DST_X_size); + this->regs[ATI_DST_WIDTH] = width; + this->draw_rect( + width, + extract_bits( + this->regs[ATI_DST_HEIGHT], ATI_DST_HEIGHT_pos, ATI_DST_HEIGHT_size)); + break; + } + case ATI_DST_WIDTH_HEIGHT: { + uint32_t width = extract_bits(value, 0, ATI_DST_WIDTH_size); + uint32_t height = extract_bits(value, 16, ATI_DST_HEIGHT_size); + this->regs[ATI_DST_WIDTH_HEIGHT] = value; + this->regs[ATI_DST_WIDTH] = width; + this->regs[ATI_DST_HEIGHT] = height; + this->draw_rect(width, height); + break; + } default: LOG_F(WARNING, "%s: unimplemented engine operation, initiator=0x%X", this->name.c_str(), initiator); @@ -1008,14 +1099,26 @@ void ATIRage::begin_drawing(uint32_t initiator, uint32_t value) { } void ATIRage::draw_rect(uint32_t width, uint32_t height) { - uint8_t frgd_src = extract_bits(this->regs[ATI_DP_SRC], ATI_DP_FRGD_SRC, ATI_DP_FRGD_SRC_size); - uint8_t bkgd_src = extract_bits(this->regs[ATI_DP_SRC], ATI_DP_BKGD_SRC, ATI_DP_BKGD_SRC_size); - uint8_t mono_src = extract_bits(this->regs[ATI_DP_SRC], ATI_DP_MONO_SRC, ATI_DP_MONO_SRC_size); - - if (frgd_src == 1 && !mono_src) { // rectangle fill with foreground color - this->fill_rect(width, height); - } else if (frgd_src == 3 && !mono_src) { // rectangle copy from the blit source + uint8_t frgd_src = extract_bits( + this->regs[ATI_DP_SRC], ATI_DP_FRGD_SRC, ATI_DP_FRGD_SRC_size); + uint8_t mono_src = extract_bits( + this->regs[ATI_DP_SRC], ATI_DP_MONO_SRC, ATI_DP_MONO_SRC_size); + + this->host_data_active = false; + + if ((frgd_src == ATI_DP_COLOR_SRC_BKGD_CLR || frgd_src == ATI_DP_COLOR_SRC_FRGD_CLR) && + mono_src == ATI_DP_MONO_SRC_ALWAYS_1) { + uint32_t color = frgd_src == ATI_DP_COLOR_SRC_BKGD_CLR ? this->regs[ATI_DP_BKGD_CLR] + : this->regs[ATI_DP_FRGD_CLR]; + this->fill_rect(width, height, color); + this->finish_rect(width, height); + } else if (frgd_src == ATI_DP_COLOR_SRC_HOST && + mono_src == ATI_DP_MONO_SRC_ALWAYS_1) { + this->start_host_rect(width, height); + } else if (frgd_src == ATI_DP_COLOR_SRC_BLIT && + mono_src == ATI_DP_MONO_SRC_ALWAYS_1) { this->blit_rect(width, height); + this->finish_rect(width, height); } else { LOG_F(WARNING, "%s: unimplemented rectangle draw op, DP_SRC=0x%08X, DP_MIX=0x%08X, " "DP_PIX_WIDTH=0x%08X, SRC_CNTL=0x%08X", this->name.c_str(), @@ -1024,14 +1127,29 @@ void ATIRage::draw_rect(uint32_t width, uint32_t height) { } } -void ATIRage::fill_rect(uint32_t dst_width, uint32_t dst_height) { - uint8_t frgd_mix = extract_bits(this->regs[ATI_DP_MIX], ATI_DP_FRGD_MIX, ATI_DP_FRGD_MIX_size); - uint8_t bkgd_mix = extract_bits(this->regs[ATI_DP_MIX], ATI_DP_BKGD_MIX, ATI_DP_BKGD_MIX_size); +void ATIRage::finish_rect(uint32_t width, uint32_t height) { + int dst_x = mach64_extract_signed(this->regs[ATI_DST_X], ATI_DST_X_pos, ATI_DST_X_size); + int dst_y = mach64_extract_signed(this->regs[ATI_DST_Y], ATI_DST_Y_pos, ATI_DST_Y_size); + int x_delta = int(width); + int y_delta = int(height); + + if (bit_set(this->regs[ATI_DST_CNTL], ATI_DST_X_TILE)) { + dst_x += bit_set(this->regs[ATI_DST_CNTL], ATI_DST_X_DIR) ? x_delta : -x_delta; + insert_bits(this->regs[ATI_DST_X], dst_x, ATI_DST_X_pos, ATI_DST_X_size); + } + if (bit_set(this->regs[ATI_DST_CNTL], ATI_DST_Y_TILE)) { + dst_y += bit_set(this->regs[ATI_DST_CNTL], ATI_DST_Y_DIR) ? y_delta : -y_delta; + insert_bits(this->regs[ATI_DST_Y], dst_y, ATI_DST_Y_pos, ATI_DST_Y_size); + } +} + +void ATIRage::fill_rect(uint32_t dst_width, uint32_t dst_height, uint32_t color) { + uint8_t frgd_mix = extract_bits(this->regs[ATI_DP_MIX], ATI_DP_FRGD_MIX, + ATI_DP_FRGD_MIX_size); - // check for non-trivial operations - if (frgd_mix != 7 || bkgd_mix != 3) { - LOG_F(WARNING, "%s: unimplemented rectangle fill op, DP_FRGD_MIX=0x%X, DP_BKGD_MIX=0x%X", - this->name.c_str(), frgd_mix, bkgd_mix); + if (frgd_mix != ATI_DP_MIX_SRC) { + LOG_F(WARNING, "%s: unimplemented rectangle fill op, DP_FRGD_MIX=0x%X", + this->name.c_str(), frgd_mix); return; } @@ -1077,16 +1195,31 @@ void ATIRage::fill_rect(uint32_t dst_width, uint32_t dst_height) { // grab trajectory params int dst_offs = extract_bits(this->regs[ATI_DST_OFF_PITCH], ATI_DST_OFFSET, ATI_DST_OFFSET_size); int dst_pitch = extract_bits(this->regs[ATI_DST_OFF_PITCH], ATI_DST_PITCH, ATI_DST_PITCH_size); - int dst_x = extract_bits(this->regs[ATI_DST_X], ATI_DST_X_pos, ATI_DST_X_size); - int dst_y = extract_bits(this->regs[ATI_DST_Y], ATI_DST_Y_pos, ATI_DST_Y_size); + int dst_x = mach64_extract_signed(this->regs[ATI_DST_X], ATI_DST_X_pos, ATI_DST_X_size); + int dst_y = mach64_extract_signed(this->regs[ATI_DST_Y], ATI_DST_Y_pos, ATI_DST_Y_size); dst_offs *= 8; dst_pitch *= 8 * bytes_per_pixel; - int x_inc = (this->regs[ATI_DST_CNTL] & 1) ? 1 : -1; - int y_inc = (this->regs[ATI_DST_CNTL] & 2) ? 1 : -1; + int x_inc = bit_set(this->regs[ATI_DST_CNTL], ATI_DST_X_DIR) ? 1 : -1; + int y_inc = bit_set(this->regs[ATI_DST_CNTL], ATI_DST_Y_DIR) ? 1 : -1; + + int sc_left = mach64_extract_signed(this->regs[ATI_SC_LEFT], ATI_SC_LEFT_pos, ATI_SC_LEFT_size); + int sc_right = mach64_extract_signed( + this->regs[ATI_SC_RIGHT], ATI_SC_RIGHT_pos, ATI_SC_RIGHT_size); + int sc_top = mach64_extract_signed(this->regs[ATI_SC_TOP], ATI_SC_TOP_pos, ATI_SC_TOP_size); + int sc_bottom = mach64_extract_signed( + this->regs[ATI_SC_BOTTOM], ATI_SC_BOTTOM_pos, ATI_SC_BOTTOM_size); - uint32_t pix = this->regs[ATI_DP_FRGD_CLR] & this->regs[ATI_DP_WRITE_MSK]; + int x_skip, y_skip; + if (!mach64_clip_axis(dst_x, x_inc, dst_width, sc_left, sc_right, x_skip, dst_width) || + !mach64_clip_axis(dst_y, y_inc, dst_height, sc_top, sc_bottom, y_skip, dst_height)) { + return; + } + dst_x += x_skip * x_inc; + dst_y += y_skip * y_inc; + + uint32_t pix = color & this->regs[ATI_DP_WRITE_MSK]; switch (dst_pix_fmt) { case ATI_PIX_FMT_8BPP: @@ -1140,6 +1273,74 @@ void ATIRage::fill_rect(uint32_t dst_width, uint32_t dst_height) { this->draw_fb = true; } +void ATIRage::start_host_rect(uint32_t dst_width, uint32_t dst_height) { + uint8_t frgd_mix = extract_bits(this->regs[ATI_DP_MIX], ATI_DP_FRGD_MIX, + ATI_DP_FRGD_MIX_size); + uint8_t host_pix_fmt = extract_bits(this->regs[ATI_DP_PIX_WIDTH], ATI_DP_HOST_PIX_WIDTH, + ATI_DP_HOST_PIX_WIDTH_size); + uint8_t dst_pix_fmt = extract_bits(this->regs[ATI_DP_PIX_WIDTH], ATI_DP_DST_PIX_WIDTH, + ATI_DP_DST_PIX_WIDTH_size); + + if (frgd_mix != ATI_DP_MIX_SRC || host_pix_fmt != ATI_PIX_FMT_8BPP || + dst_pix_fmt != ATI_PIX_FMT_8BPP || this->regs[ATI_CLR_CMP_CNTL]) { + LOG_F(WARNING, "%s: unsupported color host rectangle, DP_MIX=0x%08X, " + "DP_PIX_WIDTH=0x%08X, CLR_CMP_CNTL=0x%08X", this->name.c_str(), + this->regs[ATI_DP_MIX], this->regs[ATI_DP_PIX_WIDTH], + this->regs[ATI_CLR_CMP_CNTL]); + return; + } + + this->host_dst_width = dst_width; + this->host_dst_height = dst_height; + this->host_dst_col = 0; + this->host_dst_row = 0; + this->host_data_active = true; +} + +void ATIRage::write_host_data(uint32_t value, uint32_t size) { + if (!this->host_data_active) { + return; + } + + int dst_offs = extract_bits( + this->regs[ATI_DST_OFF_PITCH], ATI_DST_OFFSET, ATI_DST_OFFSET_size) * + 8; + int dst_pitch = + extract_bits(this->regs[ATI_DST_OFF_PITCH], ATI_DST_PITCH, ATI_DST_PITCH_size) * 8; + int dst_x = mach64_extract_signed(this->regs[ATI_DST_X], ATI_DST_X_pos, ATI_DST_X_size); + int dst_y = mach64_extract_signed(this->regs[ATI_DST_Y], ATI_DST_Y_pos, ATI_DST_Y_size); + int sc_left = mach64_extract_signed(this->regs[ATI_SC_LEFT], ATI_SC_LEFT_pos, ATI_SC_LEFT_size); + int sc_right = mach64_extract_signed( + this->regs[ATI_SC_RIGHT], ATI_SC_RIGHT_pos, ATI_SC_RIGHT_size); + int sc_top = mach64_extract_signed(this->regs[ATI_SC_TOP], ATI_SC_TOP_pos, ATI_SC_TOP_size); + int sc_bottom = mach64_extract_signed( + this->regs[ATI_SC_BOTTOM], ATI_SC_BOTTOM_pos, ATI_SC_BOTTOM_size); + int x_inc = bit_set(this->regs[ATI_DST_CNTL], ATI_DST_X_DIR) ? 1 : -1; + int y_inc = bit_set(this->regs[ATI_DST_CNTL], ATI_DST_Y_DIR) ? 1 : -1; + uint8_t write_mask = this->regs[ATI_DP_WRITE_MSK]; + + for (uint32_t byte = 0; byte < size && this->host_data_active; byte++) { + int shift = x_inc > 0 ? int(byte) * 8 : int(size - byte - 1) * 8; + uint8_t pixel = value >> shift; + int x = dst_x + int(this->host_dst_col) * x_inc; + int y = dst_y + int(this->host_dst_row) * y_inc; + + if (x >= sc_left && x <= sc_right && y >= sc_top && y <= sc_bottom) { + uint8_t* dst = &this->vram_ptr[dst_offs + y * dst_pitch + x]; + *dst = (*dst & ~write_mask) | (pixel & write_mask); + this->draw_fb = true; + } + + if (++this->host_dst_col >= this->host_dst_width) { + this->host_dst_col = 0; + if (++this->host_dst_row >= this->host_dst_height) { + this->host_data_active = false; + this->finish_rect(this->host_dst_width, this->host_dst_height); + } + } + } +} + void ATIRage::blit_rect(uint32_t dst_width, uint32_t dst_height) { uint8_t frgd_mix = extract_bits(this->regs[ATI_DP_MIX], ATI_DP_FRGD_MIX, ATI_DP_FRGD_MIX_size); diff --git a/devices/video/atirage.h b/devices/video/atirage.h index 4ce37e507f..2b636197e5 100644 --- a/devices/video/atirage.h +++ b/devices/video/atirage.h @@ -92,14 +92,23 @@ class ATIRage : public PCIDevice, public VideoCtrlBase { void begin_drawing(uint32_t initiator, uint32_t value); void draw_rect(uint32_t width, uint32_t height); - void fill_rect(uint32_t dst_width, uint32_t dst_height); + void finish_rect(uint32_t width, uint32_t height); + void fill_rect(uint32_t dst_width, uint32_t dst_height, uint32_t color); void blit_rect(uint32_t dst_width, uint32_t dst_height); + void start_host_rect(uint32_t dst_width, uint32_t dst_height); + void write_host_data(uint32_t value, uint32_t size); uint32_t regs[512] = {}; // internal registers uint8_t plls[64] = {}; // internal PLL registers uint8_t cmd_fifo_size = 0; + bool host_data_active = false; + uint32_t host_dst_width = 0; + uint32_t host_dst_height = 0; + uint32_t host_dst_col = 0; + uint32_t host_dst_row = 0; + // Video RAM variables std::unique_ptr vram_ptr; uint32_t vram_size; From a63513fe17b24b136457b77d8d568f12bcce0e5b Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Fri, 7 Aug 2026 07:49:23 -0700 Subject: [PATCH 2/3] atirage: Support general-pattern source blits Mac OS 8.6 uses general and rotated source trajectories when drawing Finder icons and the Control Strip. Preserve both source size/start composites and repeat their first and second rectangles according to SRC_CNTL. Use the documented signed source coordinates and inclusive scissors while advancing the source for clipped destination pixels. Unbounded blits continue to use the existing linear trajectory. References: - ATI 3D Rage LT Pro Register Reference Guide, pp. 5-21--5-31 and 5-39--5-42. - ATI Rage Pro and Derivatives Programmer's Guide, pp. 6-11--6-19, 6-31--6-33, and 6-42. --- devices/video/atimach64defs.h | 11 +++- devices/video/atirage.cpp | 119 ++++++++++++++++++++++++++++------ 2 files changed, 109 insertions(+), 21 deletions(-) diff --git a/devices/video/atimach64defs.h b/devices/video/atimach64defs.h index 383fb2f411..8ae4e9406d 100644 --- a/devices/video/atimach64defs.h +++ b/devices/video/atimach64defs.h @@ -49,6 +49,9 @@ enum { ATI_DP_COLOR_SRC_BLIT = 3, ATI_DP_MONO_SRC_ALWAYS_1 = 0, ATI_DP_MIX_SRC = 7, + ATI_SRC_TRAJ_UNBOUNDED = 0, + ATI_SRC_TRAJ_PATTERN = 1, + ATI_SRC_TRAJ_ROTATED = 3, }; /** Mach64 register offsets. */ @@ -495,20 +498,26 @@ enum { ATI_SRC_PITCH = 22, ATI_SRC_PITCH_size = 10, ATI_SRC_X = 0x061, // 0x0184 - ATI_SRC_X_pos = 0, ATI_SRC_X_size = 13, + ATI_SRC_X_pos = 0, ATI_SRC_X_size = 14, ATI_SRC_Y = 0x062, // 0x0188 ATI_SRC_Y_pos = 0, ATI_SRC_Y_size = 15, ATI_SRC_Y_X = 0x063, // 0x018C ATI_SRC_WIDTH1 = 0x064, // 0x0190 + ATI_SRC_WIDTH1_pos = 0, ATI_SRC_WIDTH1_size = 14, ATI_SRC_HEIGHT1 = 0x065, // 0x0194 + ATI_SRC_HEIGHT1_pos = 0, ATI_SRC_HEIGHT1_size = 15, ATI_SRC_HEIGHT1_WIDTH1 = 0x066, // 0x0198 ATI_SRC_X_START = 0x067, // 0x019C + ATI_SRC_X_START_pos = 0, ATI_SRC_X_START_size = 14, ATI_SRC_Y_START = 0x068, // 0x01A0 + ATI_SRC_Y_START_pos = 0, ATI_SRC_Y_START_size = 15, ATI_SRC_Y_X_START = 0x069, // 0x01A4 ATI_SRC_WIDTH2 = 0x06A, // 0x01A8 + ATI_SRC_WIDTH2_pos = 0, ATI_SRC_WIDTH2_size = 14, ATI_SRC_HEIGHT2 = 0x06B, // 0x01AC + ATI_SRC_HEIGHT2_pos = 0, ATI_SRC_HEIGHT2_size = 15, ATI_SRC_HEIGHT2_WIDTH2 = 0x06C, // 0x01B0 ATI_SRC_CNTL = 0x06D, // 0x01B4 ATI_SCALE_OFF = 0x070, // 0x01C0 diff --git a/devices/video/atirage.cpp b/devices/video/atirage.cpp index 98e4badcf1..972b7cd4f8 100644 --- a/devices/video/atirage.cpp +++ b/devices/video/atirage.cpp @@ -612,8 +612,18 @@ void ATIRage::write_reg(uint32_t reg_offset, uint32_t value, uint32_t size) { break; case ATI_SRC_HEIGHT1_WIDTH1: new_value = value; - this->regs[ATI_SRC_HEIGHT1] = extract_bits(value, 0, 16); - this->regs[ATI_SRC_WIDTH1] = extract_bits(value, 16, 16); + this->regs[ATI_SRC_HEIGHT1] = extract_bits(value, 0, ATI_SRC_HEIGHT1_size); + this->regs[ATI_SRC_WIDTH1] = extract_bits(value, 16, ATI_SRC_WIDTH1_size); + break; + case ATI_SRC_Y_X_START: + new_value = value; + this->regs[ATI_SRC_Y_START] = extract_bits(value, 0, ATI_SRC_Y_START_size); + this->regs[ATI_SRC_X_START] = extract_bits(value, 16, ATI_SRC_X_START_size); + break; + case ATI_SRC_HEIGHT2_WIDTH2: + new_value = value; + this->regs[ATI_SRC_HEIGHT2] = extract_bits(value, 0, ATI_SRC_HEIGHT2_size); + this->regs[ATI_SRC_WIDTH2] = extract_bits(value, 16, ATI_SRC_WIDTH2_size); break; case ATI_SC_LEFT_RIGHT: new_value = value; @@ -1369,7 +1379,11 @@ void ATIRage::blit_rect(uint32_t dst_width, uint32_t dst_height) { uint32_t pixel_mask = bytes_per_pixel == 4 ? 0xFFFFFFFFU : ((1U << (bytes_per_pixel * 8)) - 1); - if (frgd_mix != 7 || this->regs[ATI_SRC_CNTL] || this->regs[ATI_CLR_CMP_CNTL] || + uint32_t src_cntl = this->regs[ATI_SRC_CNTL]; + bool supported_src_trajectory = src_cntl == ATI_SRC_TRAJ_UNBOUNDED || + src_cntl == ATI_SRC_TRAJ_PATTERN || src_cntl == ATI_SRC_TRAJ_ROTATED; + + if (frgd_mix != ATI_DP_MIX_SRC || !supported_src_trajectory || this->regs[ATI_CLR_CMP_CNTL] || src_pix_fmt != dst_pix_fmt || !bytes_per_pixel || (this->regs[ATI_DP_WRITE_MSK] & pixel_mask) != pixel_mask) { LOG_F(WARNING, "%s: unsupported rectangle blit, DP_SRC=0x%08X, DP_MIX=0x%08X, " @@ -1380,27 +1394,92 @@ void ATIRage::blit_rect(uint32_t dst_width, uint32_t dst_height) { return; } - int src_offs = extract_bits(this->regs[ATI_SRC_OFF_PITCH], ATI_SRC_OFFSET, - ATI_SRC_OFFSET_size) * 8; - int src_pitch = extract_bits(this->regs[ATI_SRC_OFF_PITCH], ATI_SRC_PITCH, - ATI_SRC_PITCH_size) * 8 * bytes_per_pixel; - int src_x = extract_bits(this->regs[ATI_SRC_X], ATI_SRC_X_pos, ATI_SRC_X_size); - int src_y = extract_bits(this->regs[ATI_SRC_Y], ATI_SRC_Y_pos, ATI_SRC_Y_size); + int src_offs = extract_bits( + this->regs[ATI_SRC_OFF_PITCH], ATI_SRC_OFFSET, ATI_SRC_OFFSET_size) * + 8; + int src_pitch = extract_bits( + this->regs[ATI_SRC_OFF_PITCH], ATI_SRC_PITCH, ATI_SRC_PITCH_size) * + 8 * bytes_per_pixel; + int src_x = mach64_extract_signed(this->regs[ATI_SRC_X], ATI_SRC_X_pos, ATI_SRC_X_size); + int src_y = mach64_extract_signed(this->regs[ATI_SRC_Y], ATI_SRC_Y_pos, ATI_SRC_Y_size); + int src_x_start = mach64_extract_signed( + this->regs[ATI_SRC_X_START], ATI_SRC_X_START_pos, ATI_SRC_X_START_size); + int src_y_start = mach64_extract_signed( + this->regs[ATI_SRC_Y_START], ATI_SRC_Y_START_pos, ATI_SRC_Y_START_size); + uint32_t src_width1 = extract_bits( + this->regs[ATI_SRC_WIDTH1], ATI_SRC_WIDTH1_pos, ATI_SRC_WIDTH1_size); + uint32_t src_height1 = extract_bits( + this->regs[ATI_SRC_HEIGHT1], ATI_SRC_HEIGHT1_pos, ATI_SRC_HEIGHT1_size); + uint32_t src_width2 = extract_bits( + this->regs[ATI_SRC_WIDTH2], ATI_SRC_WIDTH2_pos, ATI_SRC_WIDTH2_size); + uint32_t src_height2 = extract_bits( + this->regs[ATI_SRC_HEIGHT2], ATI_SRC_HEIGHT2_pos, ATI_SRC_HEIGHT2_size); + + if ((src_cntl == ATI_SRC_TRAJ_PATTERN && (!src_width1 || !src_height1)) || + (src_cntl == ATI_SRC_TRAJ_ROTATED && + (!src_width1 || !src_height1 || !src_width2 || !src_height2))) { + LOG_F(WARNING, "%s: invalid rectangle blit source trajectory, SRC_CNTL=0x%08X, " + "SRC_HEIGHT1_WIDTH1=0x%08X, SRC_HEIGHT2_WIDTH2=0x%08X", this->name.c_str(), + src_cntl, this->regs[ATI_SRC_HEIGHT1_WIDTH1], + this->regs[ATI_SRC_HEIGHT2_WIDTH2]); + return; + } - int dst_offs = extract_bits(this->regs[ATI_DST_OFF_PITCH], ATI_DST_OFFSET, - ATI_DST_OFFSET_size) * 8; - int dst_pitch = extract_bits(this->regs[ATI_DST_OFF_PITCH], ATI_DST_PITCH, - ATI_DST_PITCH_size) * 8 * bytes_per_pixel; - int dst_x = extract_bits(this->regs[ATI_DST_X], ATI_DST_X_pos, ATI_DST_X_size); - int dst_y = extract_bits(this->regs[ATI_DST_Y], ATI_DST_Y_pos, ATI_DST_Y_size); + int dst_offs = extract_bits( + this->regs[ATI_DST_OFF_PITCH], ATI_DST_OFFSET, ATI_DST_OFFSET_size) * + 8; + int dst_pitch = extract_bits( + this->regs[ATI_DST_OFF_PITCH], ATI_DST_PITCH, ATI_DST_PITCH_size) * + 8 * bytes_per_pixel; + int dst_x = mach64_extract_signed(this->regs[ATI_DST_X], ATI_DST_X_pos, ATI_DST_X_size); + int dst_y = mach64_extract_signed(this->regs[ATI_DST_Y], ATI_DST_Y_pos, ATI_DST_Y_size); + + int x_inc = bit_set(this->regs[ATI_DST_CNTL], ATI_DST_X_DIR) ? 1 : -1; + int y_inc = bit_set(this->regs[ATI_DST_CNTL], ATI_DST_Y_DIR) ? 1 : -1; + + int sc_left = mach64_extract_signed(this->regs[ATI_SC_LEFT], ATI_SC_LEFT_pos, ATI_SC_LEFT_size); + int sc_right = mach64_extract_signed( + this->regs[ATI_SC_RIGHT], ATI_SC_RIGHT_pos, ATI_SC_RIGHT_size); + int sc_top = mach64_extract_signed(this->regs[ATI_SC_TOP], ATI_SC_TOP_pos, ATI_SC_TOP_size); + int sc_bottom = mach64_extract_signed( + this->regs[ATI_SC_BOTTOM], ATI_SC_BOTTOM_pos, ATI_SC_BOTTOM_size); - int x_inc = (this->regs[ATI_DST_CNTL] & 1) ? 1 : -1; - int y_inc = (this->regs[ATI_DST_CNTL] & 2) ? 1 : -1; + int x_skip, y_skip; + if (!mach64_clip_axis(dst_x, x_inc, dst_width, sc_left, sc_right, x_skip, dst_width) || + !mach64_clip_axis(dst_y, y_inc, dst_height, sc_top, sc_bottom, y_skip, dst_height)) { + return; + } + dst_x += x_skip * x_inc; + dst_y += y_skip * y_inc; for (uint32_t y = 0; y < dst_height; y++) { - uint8_t* src_row = &this->vram_ptr[src_offs + (src_y + int(y) * y_inc) * src_pitch]; - uint8_t* dst_row = &this->vram_ptr[dst_offs + (dst_y + int(y) * y_inc) * dst_pitch]; - for (int sx = src_x, dx = dst_x, width = dst_width; width-- > 0; sx += x_inc, dx += x_inc) { + uint32_t src_row_index = y_skip + y; + int sy; + if (src_cntl == ATI_SRC_TRAJ_PATTERN) { + sy = src_y + int(src_row_index % src_height1) * y_inc; + } else if (src_cntl == ATI_SRC_TRAJ_ROTATED && src_row_index >= src_height1) { + sy = src_y_start + int((src_row_index - src_height1) % src_height2) * y_inc; + } else { + sy = src_y + int(src_row_index) * y_inc; + } + + uint8_t* src_row = &this->vram_ptr[src_offs + sy * src_pitch]; + int dy = dst_y + int(y) * y_inc; + uint8_t* dst_row = &this->vram_ptr[dst_offs + dy * dst_pitch]; + for (uint32_t x = 0; x < dst_width; x++) { + uint32_t src_col_index = x_skip + x; + int sx; + if (src_cntl == ATI_SRC_TRAJ_PATTERN) { + sx = src_x + int(src_col_index % src_width1) * x_inc; + } else if (src_cntl == ATI_SRC_TRAJ_ROTATED && src_row_index > 0) { + sx = src_x_start + int(src_col_index % src_width2) * x_inc; + } else if (src_cntl == ATI_SRC_TRAJ_ROTATED && src_col_index >= src_width1) { + sx = src_x_start + int((src_col_index - src_width1) % src_width2) * x_inc; + } else { + sx = src_x + int(src_col_index) * x_inc; + } + + int dx = dst_x + int(x) * x_inc; uint8_t* src_pixel = &src_row[sx * bytes_per_pixel]; uint8_t* dst_pixel = &dst_row[dx * bytes_per_pixel]; for (int byte = 0; byte < bytes_per_pixel; byte++) { From 0d533dbd2df0a601e9bd05688f14d7369ad4d39c Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Fri, 7 Aug 2026 07:49:51 -0700 Subject: [PATCH 3/3] atirage: Handle SGRAM color register blits The Mac OS 8.6 driver enables color-register writes without enabling block writes to update SGRAM state. Treat that command as a register-only operation instead of sending it through the VRAM blitter, while retaining the documented destination tiling side effects. This prevents the command from producing stray pixels near the menu bar and Control Strip. References: - ATI 3D Rage LT Pro Register Reference Guide, pp. 5-21--5-24. - ATI Rage Pro and Derivatives Programmer's Guide, pp. 6-11--6-19. --- devices/video/atimach64defs.h | 2 ++ devices/video/atirage.cpp | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/devices/video/atimach64defs.h b/devices/video/atimach64defs.h index 8ae4e9406d..f272dbe59b 100644 --- a/devices/video/atimach64defs.h +++ b/devices/video/atimach64defs.h @@ -520,6 +520,8 @@ enum { ATI_SRC_HEIGHT2_pos = 0, ATI_SRC_HEIGHT2_size = 15, ATI_SRC_HEIGHT2_WIDTH2 = 0x06C, // 0x01B0 ATI_SRC_CNTL = 0x06D, // 0x01B4 + ATI_SRC_COLOR_REG_WRITE_EN = 13, + ATI_SRC_BLOCK_WRITE_EN = 14, ATI_SCALE_OFF = 0x070, // 0x01C0 ATI_SCALE_WIDTH = 0x077, // 0x01DC ATI_SCALE_HEIGHT = 0x078, // 0x01E0 diff --git a/devices/video/atirage.cpp b/devices/video/atirage.cpp index 972b7cd4f8..e56a1b28ae 100644 --- a/devices/video/atirage.cpp +++ b/devices/video/atirage.cpp @@ -1113,9 +1113,17 @@ void ATIRage::draw_rect(uint32_t width, uint32_t height) { this->regs[ATI_DP_SRC], ATI_DP_FRGD_SRC, ATI_DP_FRGD_SRC_size); uint8_t mono_src = extract_bits( this->regs[ATI_DP_SRC], ATI_DP_MONO_SRC, ATI_DP_MONO_SRC_size); + uint32_t src_cntl = this->regs[ATI_SRC_CNTL]; this->host_data_active = false; + // Color register blits update SGRAM state rather than VRAM. + if (bit_set(src_cntl, ATI_SRC_COLOR_REG_WRITE_EN) && + !bit_set(src_cntl, ATI_SRC_BLOCK_WRITE_EN)) { + this->finish_rect(width, height); + return; + } + if ((frgd_src == ATI_DP_COLOR_SRC_BKGD_CLR || frgd_src == ATI_DP_COLOR_SRC_FRGD_CLR) && mono_src == ATI_DP_MONO_SRC_ALWAYS_1) { uint32_t color = frgd_src == ATI_DP_COLOR_SRC_BKGD_CLR ? this->regs[ATI_DP_BKGD_CLR]