Skip to content

Clip Word-Aligned Amiga Bobs

Crop a 32×16 blitter object against a 320×256 viewport, with separate source and screen strides and no blit for fully invisible objects.

Original amiga scene with masked objects cropped at the four screen corners and drawn inside the viewport

Original Code198x program and artwork captured in Emu198x.

Code status: maintained routines and an emulator-executed caller. Execution record.

A bob can start beyond the display and still have a visible part. This routine clips a fixed 32×16 source before programming the blitter, advances the mask/image pointers to the surviving pixels, and keeps the original source stride when a side is cropped.

Call it with source and screen pointers

Include clip.inc and draw.inc once. Pass signed X in D0.w, measured in sixteen-pixel words, and signed Y in D1.w, measured in rows. A0 points to the opaque mask, A1 to the image and A2 to the 320×256 one-plane screen base.

Each source is 64 bytes: sixteen rows of four bytes. Mask 1 selects image and mask 0 retains the destination. This is the opposite polarity to the Spectrum/BBC keep-masks. All pointers must be even and every accessed span must be owned chip RAM, separate from the other live spans, code and stack. The caller must enable DMA and own the blitter for the whole operation.

D0-D7 and A0-A5 survive; CCR changes. The routine uses 68 bytes of saved stack data, plus call-return and interrupt requirements. It waits before programming and before returning. It sets up no copper list, interrupts or operating-system ownership.

Clip both axes and preserve source stride across a cropped blit

draw.inc
; SPDX-License-Identifier: MIT
; draw_clipped: D0.w signed X in 16-pixel words, D1.w signed Y in rows.
; A0=opaque mask (1 selects image), A1=image, A2=320x256 one-plane bitmap.
; Source is exactly 32x16, four bytes per row (64 bytes each). Even chip-RAM
; pointers, all ranges owned/disjoint. Caller enables/owns blitter throughout.
; Preserves D0-D7/A0-A5 (CCR changes); 68 stack bytes plus return/IRQ needs.
; Synchronous, no shift/clipping within a word, no OS/copper/IRQ ownership.
draw_clipped:
    movem.l d0-d7/a0-a5,-(sp)
    move.w d1,d6
    moveq #2,d1
    moveq #20,d2
    bsr clip_axis
    tst.w d2
    beq draw_clipped_exit
    movem.l d0-d2,-(sp)
    move.w d6,d0
    moveq #16,d1
    move.w #256,d2
    bsr clip_axis
    movem.l (sp)+,d5-d7
    tst.w d2
    beq draw_clipped_exit
    ; Y outputs D0 destination, D1 source skip, D2 count.
    ; X outputs D5 destination, D6 source skip, D7 count.
    lsl.w #2,d1
    add.w d6,d1
    add.w d6,d1
    adda.w d1,a0
    adda.w d1,a1
    mulu.w #40,d0
    add.w d5,d0
    add.w d5,d0
    adda.l d0,a2
    lea $dff000,a5
    bsr clipped_wait
    move.w #$0fca,$040(a5)
    move.w #0,$042(a5)
    move.w #$ffff,$044(a5)
    move.w #$ffff,$046(a5)
    moveq #4,d3
    sub.w d7,d3
    sub.w d7,d3
    move.w d3,$064(a5)
    move.w d3,$062(a5)
    moveq #40,d3
    sub.w d7,d3
    sub.w d7,d3
    move.w d3,$060(a5)
    move.w d3,$066(a5)
    move.l a0,$050(a5)
    move.l a1,$04c(a5)
    move.l a2,$048(a5)
    move.l a2,$054(a5)
    lsl.w #6,d2
    or.w d7,d2
    move.w d2,$058(a5)
    bsr clipped_wait
draw_clipped_exit:
    movem.l (sp)+,d0-d7/a0-a5
    rts
clipped_wait:
    btst #6,$002(a5)
clipped_busy:
    btst #6,$002(a5)
    bne.s clipped_busy
    rts

A left crop advances both source pointers by a word. A top crop advances them by complete four-byte source rows. The A/B source modulos retain that original row width, while C/D retain the forty-byte screen row. The Amiga Hardware Reference Manual, chapter 6 describes chip-memory access, channels and row modulos.

The independent axis helper takes signed position in D0.w, extent 0..256 in D1.w and viewport limit 1..256 in D2.w. It returns destination, source skip and visible count in those same registers, all zero when empty. D3/D4 and CCR change; address registers survive. Upper data-register words are unspecified.

Clip an interval before forming a bitplane address

clip.inc
; SPDX-License-Identifier: MIT
; clip_axis: D0.w=signed position, D1.w=extent 0..256, D2.w=limit 1..256.
; Return D0.w=destination, D1.w=source skip, D2.w=visible count.
; All three zero when empty. D3/D4 and CCR change; A0-A6 survive.
; Upper words of data registers unspecified. No hardware or static state.
clip_axis:
    tst.w d1
    beq clip_empty
    tst.w d0
    bpl clip_positive
    neg.w d0
    cmp.w d1,d0
    bcc clip_empty
    sub.w d0,d1
    move.w d0,d3
    moveq #0,d0
    bra clip_capacity
clip_positive:
    cmp.w d2,d0
    bcc clip_empty
    moveq #0,d3
clip_capacity:
    sub.w d0,d2
    cmp.w d2,d1
    bcc clip_count_ready
    move.w d1,d2
clip_count_ready:
    move.w d3,d1
    rts
clip_empty:
    moveq #0,d0
    moveq #0,d1
    moveq #0,d2
    rts

Boot the complete caller

Build with make ASM198X=/path/to/asm198x BUILD198X=/path/to/build198x-adf and boot build/demo.adf on A500 OCS PAL/Kickstart1.3. The caller owns the custom chips and one 320×256 bitplane. It draws cropped corners, interior objects and two fully invisible calls, then continues frame servicing. Reset is required to leave it.

Original ADF caller with cropped bobs at all four corners

demo.asm
; A500 OCS PAL/KS1.3, all data in chip RAM. Owns chips; reset to leave.
    section code,code_c
start:
    lea $dff000,a5
    move.w #$7fff,$09a(a5)
    move.w #$7fff,$09c(a5)
    move.w #$7fff,$096(a5)
    lea plane,a0
    move.w #255,d7
paint_row:
    move.w #19,d6
paint_word:
    move.w #$5555,(a0)+
    dbra d6,paint_word
    dbra d7,paint_row
    lea plane,a0
    move.l a0,d0
    lea copbpl,a1
    move.w d0,6(a1)
    swap d0
    move.w d0,2(a1)
    lea copperlist,a0
    move.l a0,$080(a5)
    move.w #0,$088(a5)
    move.w #$83c0,$096(a5)
    move.w #-1,d0
    move.w #-8,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #19,d0
    move.w #-8,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #-1,d0
    move.w #248,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #19,d0
    move.w #248,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #9,d0
    move.w #120,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #0,d0
    move.w #80,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #18,d0
    move.w #160,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #-2,d0
    move.w #32,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #20,d0
    move.w #32,d1
    lea mask,a0
    lea image_a,a1
    lea plane,a2
    bsr draw_clipped
    move.w #$cafe,ready
main:
    bsr wait_frame
    addq.w #1,updates
    bra main
signature: dc.b "198XCLIPPING!",0
    even
ready: dc.w 0
updates: dc.w 0
    include "clip.inc"
    include "draw.inc"
    include "shapes.inc"
    include "../../framework/game-loop/frame.inc"
    even
copperlist:
    dc.w $08e,$2c81,$090,$2cc1,$092,$0038,$094,$00d0
    dc.w $100,$1200,$102,0,$104,0,$108,0,$10a,0
    dc.w $180,$012,$182,$6ed
copbpl:
    dc.w $0e0,0,$0e2,0
    dc.w $ffff,$fffe
before_plane: dcb.b 16,$a7
plane: dcb.b 10240,0
after_plane: dcb.b 16,$a7

Build on the bounded version

This wrapper handles word-aligned sides and individual pixel rows at top/bottom. It has no arbitrary-X shifts, colour-plane iteration or asynchronous blit queue. A fully invisible call returns before touching blitter registers.

For movement over scenery, combine it with save/draw/restore and retain the previous clipped address and dimensions. The shared clipping entry describes the update order and stale backgrounds. Smaller windows, shifted sources and colour bobs are onward work; this scene establishes the named configuration and output, not a whole game’s display-time budget.

For individual pixel X positions, select prepared source phases and keep the padded footprint and original source stride explicit.