/* * _bgi_blit_cols_op_raw — accel-блит КОЛОНКАМИ с ЛОГИЧЕСКОЙ ОПЕРАЦИЕЙ: * приёмник = приёмник источник, где = AND / OR / XOR. Клон * _bgi_blit_cols_raw (тот же column-major источник, тот же бесплатный * горизонтальный флип), но между чтением источника и записью вставлен * ТРЕТИЙ burst — чтение приёмника в режиме операции. * * КАК ЭТО РАБОТАЕТ (docs/new/06-accel.md §6.2, подтверждено драйвером MAME * src/mame/sinclair/sprinter.cpp — accel_control_r/update_accel_buffer). * Опкоды `AND (HL)` #A6, `OR (HL)` #B6, `XOR (HL)` #AE НЕ являются * отдельным режимом акселератора: их M1-выборка переводит внутренний * автомат из «буфер := память» в «буфер = память», а сам burst * запускается операндным чтением (HL) — в ТЕКУЩЕМ режиме направления * (гориз. LD L,L / верт. LD A,A). То есть операция ОРТОГОНАЛЬНА * направлению: для column-major спрайтов работает так же, как копия. * Следующее армирование (LD L,L / LD A,A / LD B,B) возвращает автомат в * «буфер := память». * * ОДНА функция на три операции: опкод операции патчится SMC при входе * (байт op), ровно как размер блока и страйд. op — это и есть опкод * (GFX_OP_AND/OR/XOR в ); проверку значения делает вызывающий. * * ПОСЛЕДОВАТЕЛЬНОСТЬ НА КОЛОНКУ (три burst'а вместо двух): * OUT Port_Y = y0 ; accel разоружён — операнд OUT безопасен * LD L,L / LD A,(DE) ; гориз. чтение: h смежных байт src -> буфер * LD A,A / (HL) ; верт. чтение: буфер = видео-колонка * LD B,B ; СТОП — разоружить перед вторым OUT * OUT Port_Y = y0 ; вернуть Y: верт. burst сдвинул его на +h * LD A,A / LD (HL),A ; верт. запись: буфер -> видео-колонка * LD B,B * Второй OUT обязателен: вертикальный burst ШАГАЕТ Port_Y (§6.1), и после * op-чтения он стоит на y0+h. СТОП перед OUT обязателен по квирку * memory/accel_operand_fetch_retrigger (fetch операнда под армированным * accel перезапускает burst). Буфер и размер блока переживают LD B,B * (§6.3) — это и позволяет разнести чтение и запись. * * ЧТО ЧИТАЕТ op-burst: страницы #50..#5F на ЧТЕНИЕ всегда отдают ОЗУ-копию * экрана, а не видео-ОЗУ (memory/sprinter_vram_transparency). Значит при * банке 0x54/0x5C (бит 2 — «не писать в ОЗУ-копию») операция считает * ЧИСТЫЙ ФОН, а не то, что уже нарисовано поверх него. Нужна операция с * реально видимым содержимым — вызывающий обязан рисовать банком 0x50 * (GFX_BANK_NORMAL), тогда копия и видео-ОЗУ совпадают. * * ПРОЗРАЧНОСТЬ. Аппаратная прозрачность (банки 0x58/0x5C — не писать байт * #FF) действует на РЕЗУЛЬТАТ, а не на источник, поэтому даром достаётся * только двум операциям: * AND — #FF нейтрален сам по себе (#FF & bg = bg); * OR — #FF | bg = #FF, и запись такого байта подавляется банком 0x58/0x5C; * XOR — НЕ работает: #FF ^ bg = ~bg, инверсия фона на прозрачных пикселях. * Для XOR источник обязан иметь прозрачный индекс 0x00 (нейтральный к XOR * и к OR) и рисоваться банком без подавления. Подробно — шапка * common/gfx_blit_cols_part_wx_op.c. * * ВХОД (__sdcccall(1)): src→HL, dst→DE; стек: w 4(ix), h 5(ix), * sstride 6/7(ix), y0 8(ix), op 9(ix). Смысл w/h/sstride/y0 — как у * _bgi_blit_cols_raw. Callee-pop 6 Б. * * Raw: без клиппинга (вызывающий); W3 замаплен (_bgi_begin/end снаружи); * банк выставлен вызывающим. src ВНЕ W3. ОДИН DI на весь блит (втрое * больше трафика, чем у копии — для больших спрайтов резать вызывающему). * Клоббер: AF/BC/DE/HL; IX сохраняется. */ #include "../_bgi.h" void _bgi_blit_cols_op_raw(const uint8_t *src, uint8_t *dst, uint8_t w, uint8_t h, int sstride, uint8_t y0, uint8_t op) __naked { (void)src; (void)dst; (void)w; (void)h; (void)sstride; (void)y0; (void)op; __asm push ix ld ix, #0 add ix, sp ;; SMC: размер accel-блока <- h (высота колонки) ld a, 5 (ix) ld (bo_len_imm), a ;; SMC: src-страйд между колонками (8-бит adc-цепочка, ± на вызов) ld a, 6 (ix) ld (bo_slo_imm), a ld a, 7 (ix) ld (bo_shi_imm), a ;; SMC: опкод операции — and/or/xor (hl) (#A6/#B6/#AE) ld a, 9 (ix) ld (bo_op_imm), a ld b, 4 (ix) ; B = число колонок (0 => 256: djnz) ld c, 8 (ix) ; C = y0 (Port_Y, константа) ;; Операция существует только в форме (HL) — приёмник обязан ;; жить в HL; источник переезжает в DE (читается LD A,(DE)). ex de, hl ; HL = dst (видео), DE = src (ОЗУ) di ; один DI на весь блит ld d, d ; 0x52 — режим размера блока (no-op для D) ld a, #0 bo_len_imm = . - 1 ld b, b ; 0x40 — стоп (B=счётчик не тронут) bo_col: ld a, c out (#0x89), a ; Port_Y = y0 (верх колонки) ;; 1) ЧТЕНИЕ ИСТОЧНИКА — ГОРИЗОНТАЛЬНОЙ копией (LD L,L): колонка ;; спрайта лежит в ОЗУ СМЕЖНО, шагает адрес, не Port_Y. ld l, l ; 0x6D — гориз. режим ld a, (de) ; read-burst: h байт src -> буфер accel ;; 2) ОПЕРАЦИЯ с приёмником — ВЕРТИКАЛЬНЫМ чтением (LD A,A): ;; буфер = видео-колонка; Port_Y уезжает на y0+h. ld a, a ; 0x7F — верт. режим xor a, (hl) ; SMC: and/or/xor (hl) — op-burst bo_op_imm = . - 1 ld b, b ; 0x40 — стоп (разоружить перед OUT) ;; 3) ЗАПИСЬ — снова с верха колонки. ld a, c out (#0x89), a ; Port_Y = y0 (accel разоружён) ld a, a ; 0x7F — верт. режим ld (hl), a ; write-burst: буфер -> видео-колонка ld b, b ; 0x40 — стоп ;; src += sstride (следующая колонка источника) ld a, e add a, #0 bo_slo_imm = . - 1 ld e, a ld a, d adc a, #0 bo_shi_imm = . - 1 ld d, a inc hl ; dst += 1 (следующий x) djnz bo_col ei ; закрыть DI-окно pop ix ;; callee-pop 6 байт (w,h,sstride:2,y0,op) pop hl ; ret-адрес pop af pop af pop af jp (hl) __endasm; }