# Mode7 rotozoomer: PowerPC 604e guest code submits an affine texture map to
# the IE blitter. The CPU calculates signed fixed-point origins and step
# vectors, renders into a back buffer, then presents at the next vblank edge.
#
# Assemble from the repository root with:
#   make rotozoomer-ppc
#
# (c) 2024-2026 Zayn Otley - GPLv3 or later

        .include "ieppc.inc"

# ============================================================================
# CONSTANTS
# ============================================================================
# The texture occupies 256*256*4 bytes. Each framebuffer occupies 640*480*4
# bytes. These areas are separate from the programme, stack and MMIO.
        .set TEXTURE_BASE,0x600000
        .set BACK_BUFFER_A,0x900000
        .set BACK_BUFFER_B,0xB00000
        .set RENDER_W,640
        .set RENDER_H,480
        .set TEX_STRIDE,1024
        .set OUTPUT_STRIDE,2560
        .set ANGLE_INC,313
        .set SCALE_INC,104

# ============================================================================
# ENTRY POINT
# ============================================================================
# The flat image loads at PROGRAM_START (0x4000). VASM uses this origin for
# label addresses; the file begins with the first instruction, without padding.
#
# Register allocation throughout the programme:
#   r20 = angle accumulator       r21 = scale accumulator
#   r22 = draw framebuffer        r23 = video register base
#   r24 = sine table              r25 = reciprocal table
#   r26 = CA                      r27 = SA
#   r28 = U0                      r29 = V0
#   r3-r12 = scratch
#
# BL saves the return address in LR, unlike the M68K's stack-based BSR.
# Each subroutine is a leaf, or ends with a tail branch to another routine,
# so no nested call overwrites LR. R1 still receives an aligned stack top.
        .org PROGRAM_START
start:
        lis     r1,STACK_TOP>>16
        lis     r23,VIDEO_CTRL>>16
        li      r3,VIDEO_CTRL_ENABLE
        stw     r3,VIDEO_CTRL-VIDEO_CTRL(r23)
        li      r3,0
        stw     r3,VIDEO_MODE-VIDEO_CTRL(r23)
        stw     r3,BLT_FLAGS-VIDEO_CTRL(r23)
        lis     r3,VRAM_START>>16
        stw     r3,VIDEO_FB_BASE-VIDEO_CTRL(r23)
        bl      load_texture
        bl      setup_mode7

        # --- Initialise animation state ---
        # The low eight bits retain fractional steps. The high eight bits
        # select a table entry. Both accumulators wrap after 65536 steps.
        li      r20,0
        li      r21,0
        lis     r22,BACK_BUFFER_A>>16
        lis     r24,sine_table@h
        ori     r24,r24,sine_table@l
        lis     r25,recip_table@h
        ori     r25,r25,recip_table@l

        # --- Start TED music playback ---
        # The player consumes the embedded register dump independently of
        # the CPU. Control value 5 sets START (bit 0) and LOOP (bit 2).
        lis     r3,ted_data@h
        ori     r3,r3,ted_data@l
        stw     r3,TED_PLAY_PTR-VIDEO_CTRL(r23)
        lis     r3,(ted_data_end-ted_data)@h
        ori     r3,r3,(ted_data_end-ted_data)@l
        stw     r3,TED_PLAY_LEN-VIDEO_CTRL(r23)
        li      r3,5
        stw     r3,TED_PLAY_CTRL-VIDEO_CTRL(r23)

# ============================================================================
# MAIN LOOP
# ============================================================================
# Render first, wait for a fresh vblank edge, then display the completed
# buffer. The next frame writes into the other buffer.
main_loop:
        bl      compute_frame
        bl      render_mode7
        bl      wait_vsync
present_frame:
        stw     r22,VIDEO_FB_BASE-VIDEO_CTRL(r23)
        # The two buffer addresses differ only in bit 21.
        xoris   r22,r22,0x20
        bl      advance_animation
        b       main_loop

# ============================================================================
# LOAD TEXTURE - 256x256 RGBA from Embedded Raw Data
# ============================================================================
# The raw image is a byte sequence, copied unchanged by the blitter. PPC
# halfword tables are big-endian, but texture bytes need no conversion.
# Numeric MMIO registers accept ordinary STW/LWZ accesses: writing 256
# writes the number 256, without a byte-reversing store.
load_texture:
        li      r3,BLT_OP_COPY
        stw     r3,BLT_OP-VIDEO_CTRL(r23)
        lis     r3,texture_data@h
        ori     r3,r3,texture_data@l
        stw     r3,BLT_SRC-VIDEO_CTRL(r23)
        lis     r3,TEXTURE_BASE>>16
        stw     r3,BLT_DST-VIDEO_CTRL(r23)
        li      r3,256
        stw     r3,BLT_WIDTH-VIDEO_CTRL(r23)
        stw     r3,BLT_HEIGHT-VIDEO_CTRL(r23)
        li      r3,TEX_STRIDE
        stw     r3,BLT_SRC_STRIDE-VIDEO_CTRL(r23)
        stw     r3,BLT_DST_STRIDE-VIDEO_CTRL(r23)
        b       submit_blit

# ============================================================================
# SET UP MODE7 - Parameters Shared by Every Frame
# ============================================================================
# Source stride is 256 pixels * 4 bytes. Destination stride is 640 * 4.
# Masks of 255 wrap the texture coordinates at each 256-texel boundary.
setup_mode7:
        li      r3,BLT_OP_MODE7
        stw     r3,BLT_OP-VIDEO_CTRL(r23)
        lis     r3,TEXTURE_BASE>>16
        stw     r3,BLT_SRC-VIDEO_CTRL(r23)
        li      r3,RENDER_W
        stw     r3,BLT_WIDTH-VIDEO_CTRL(r23)
        li      r3,RENDER_H
        stw     r3,BLT_HEIGHT-VIDEO_CTRL(r23)
        li      r3,OUTPUT_STRIDE
        stw     r3,BLT_DST_STRIDE-VIDEO_CTRL(r23)
        li      r3,255
        stw     r3,BLT_MODE7_TEX_W-VIDEO_CTRL(r23)
        stw     r3,BLT_MODE7_TEX_H-VIDEO_CTRL(r23)
        blr

# ============================================================================
# COMPUTE FRAME - Rotation and Zoom in Signed 16.16 Fixed-Point
# ============================================================================
# Sine entries represent -1.0 to +1.0 as -256 to +256. The reciprocal
# table holds round(256 / (0.5 + sin(i * 2*pi / 256) * 0.3)). Multiplying
# these two 8.8 values gives a 16.16 result directly, without a shift.
#
# LHAX sign-extends the sine halfword; LHZX zero-extends the reciprocal.
# MULLW keeps the low 32 product bits, which contain the complete result:
# the largest magnitude is 256 * 1280 = 327680.
compute_frame:
        srwi    r3,r20,8
        slwi    r4,r3,1
        lhax    r5,r24,r4                 # sin(angle)
        addi    r3,r3,64                  # cosine is sine a quarter turn ahead
        andi.   r3,r3,255
        slwi    r4,r3,1
        lhax    r6,r24,r4                 # cos(angle)
        srwi    r3,r21,8
        slwi    r4,r3,1
        lhzx    r7,r25,r4                 # reciprocal zoom factor
        mullw   r26,r6,r7                 # CA = cos * reciprocal
        mullw   r27,r5,r7                 # SA = sin * reciprocal

        # --- Centre the transformation ---
        # The screen centre (320,240) maps to texture centre (128,128).
        # U0 = (128 << 16) - CA*320 + SA*240
        # V0 = (128 << 16) - SA*320 - CA*240
        # MULLI accepts a full 32-bit register and a signed immediate.
        # All intermediate values fit in signed 32 bits.
        mulli   r3,r26,320
        mulli   r4,r27,240
        lis     r28,128
        subf    r28,r3,r28
        add     r28,r28,r4
        mulli   r3,r27,320
        mulli   r4,r26,240
        lis     r29,128
        subf    r29,r3,r29
        subf    r29,r4,r29
        blr

# ============================================================================
# RENDER MODE7 - Submit the Six Affine Parameters
# ============================================================================
# For screen pixel (x,y), the blitter samples:
#   U = U0 + CA*x - SA*y
#   V = V0 + SA*x + CA*y
# The integer parts select a texel; the masks provide repeating tiles.
render_mode7:
        stw     r22,BLT_DST-VIDEO_CTRL(r23)
        stw     r28,BLT_MODE7_U0-VIDEO_CTRL(r23)
        stw     r29,BLT_MODE7_V0-VIDEO_CTRL(r23)
        stw     r26,BLT_MODE7_DU_COL-VIDEO_CTRL(r23)
        stw     r27,BLT_MODE7_DV_COL-VIDEO_CTRL(r23)
        neg     r3,r27
        stw     r3,BLT_MODE7_DU_ROW-VIDEO_CTRL(r23)
        stw     r26,BLT_MODE7_DV_ROW-VIDEO_CTRL(r23)
submit_blit:
        li      r3,1
        stw     r3,BLT_CTRL-VIDEO_CTRL(r23)
wait_blit:
        # BLT_CTRL bit 1 is BUSY. BLT_STATUS bit 1 is DONE, not BUSY.
        lwz     r3,BLT_CTRL-VIDEO_CTRL(r23)
        andi.   r3,r3,2
        bne     wait_blit
        lwz     r3,BLT_STATUS-VIDEO_CTRL(r23)
        andi.   r3,r3,1                   # ERR: leave the last displayed frame
        bne     blit_error
        blr
blit_error:
        b       blit_error

# ============================================================================
# WAIT FOR VSYNC - Observe a Fresh Vertical Blank Edge
# ============================================================================
# Wait for any current blank to end, then wait for the next one to begin.
# This prevents two frames from being presented within the same blank.
wait_vsync:
        lwz     r3,VIDEO_STATUS-VIDEO_CTRL(r23)
        andi.   r3,r3,STATUS_VBLANK
        bne     wait_vsync
wait_vsync_start:
        lwz     r3,VIDEO_STATUS-VIDEO_CTRL(r23)
        andi.   r3,r3,STATUS_VBLANK
        beq     wait_vsync_start
        blr

# ============================================================================
# ADVANCE ANIMATION - Update Fractional Accumulators
# ============================================================================
# 313 gives about 0.03 radians per frame; 104 gives about 0.01. Masking
# to 16 bits wraps the table index while retaining the fractional part.
advance_animation:
        addi    r20,r20,ANGLE_INC
        andi.   r20,r20,65535
        addi    r21,r21,SCALE_INC
        andi.   r21,r21,65535
        blr

# ============================================================================
# SINE TABLE - 256 Entries, Signed 16-bit
# ============================================================================
# round(sin(i * 2*pi / 256) * 256), stored as big-endian halfwords.
# Entries 0,64,128,192 are 0,+256,0,-256 respectively.
        .balign 4
sine_table:
                .short    0,6,13,19,25,31,38,44,50,56,62,68,74,80,86,92
                .short    98,104,109,115,121,126,132,137,142,147,152,157,162,167,172,177
                .short    181,185,190,194,198,202,206,209,213,216,220,223,226,229,231,234
                .short    237,239,241,243,245,247,248,250,251,252,253,254,255,255,256,256
                .short    256,256,256,255,255,254,253,252,251,250,248,247,245,243,241,239
                .short    237,234,231,229,226,223,220,216,213,209,206,202,198,194,190,185
                .short    181,177,172,167,162,157,152,147,142,137,132,126,121,115,109,104
                .short    98,92,86,80,74,68,62,56,50,44,38,31,25,19,13,6
                .short    0,-6,-13,-19,-25,-31,-38,-44,-50,-56,-62,-68,-74,-80,-86,-92
                .short    -98,-104,-109,-115,-121,-126,-132,-137,-142,-147,-152,-157,-162,-167,-172,-177
                .short    -181,-185,-190,-194,-198,-202,-206,-209,-213,-216,-220,-223,-226,-229,-231,-234
                .short    -237,-239,-241,-243,-245,-247,-248,-250,-251,-252,-253,-254,-255,-255,-256,-256
                .short    -256,-256,-256,-255,-255,-254,-253,-252,-251,-250,-248,-247,-245,-243,-241,-239
                .short    -237,-234,-231,-229,-226,-223,-220,-216,-213,-209,-206,-202,-198,-194,-190,-185
                .short    -181,-177,-172,-167,-162,-157,-152,-147,-142,-137,-132,-126,-121,-115,-109,-104
                .short    -98,-92,-86,-80,-74,-68,-62,-56,-50,-44,-38,-31,-25,-19,-13,-6

# ============================================================================
# RECIPROCAL TABLE - 256 Entries, Unsigned 16-bit
# ============================================================================
# round(256 / (0.5 + sin(i * 2*pi / 256) * 0.3)), range 320 to 1280.
# The denominator stays between 0.2 and 0.8, so zoom remains positive.
recip_table:
                .short    512,505,497,490,484,477,471,464,458,453,447,441,436,431,426,421
                .short    416,412,407,403,399,395,391,388,384,381,377,374,371,368,365,362
                .short    359,357,354,352,350,348,345,343,342,340,338,336,335,333,332,331
                .short    329,328,327,326,325,324,324,323,322,322,321,321,321,320,320,320
                .short    320,320,320,320,321,321,321,322,322,323,324,324,325,326,327,328
                .short    329,331,332,333,335,336,338,340,342,343,345,348,350,352,354,357
                .short    359,362,365,368,371,374,377,381,384,388,391,395,399,403,407,412
                .short    416,421,426,431,436,441,447,453,458,464,471,477,484,490,497,505
                .short    512,520,528,536,544,553,561,571,580,589,599,610,620,631,642,653
                .short    665,676,689,701,714,727,740,754,768,782,797,812,827,842,858,873
                .short    889,905,922,938,955,972,988,1005,1022,1038,1055,1071,1087,1103,1119,1134
                .short    1149,1163,1177,1190,1202,1214,1225,1235,1244,1252,1260,1266,1271,1275,1278,1279
                .short    1280,1279,1278,1275,1271,1266,1260,1252,1244,1235,1225,1214,1202,1190,1177,1163
                .short    1149,1134,1119,1103,1087,1071,1055,1038,1022,1005,988,972,955,938,922,905
                .short    889,873,858,842,827,812,797,782,768,754,740,727,714,701,689,676
                .short    665,653,642,631,620,610,599,589,580,571,561,553,544,536,528,520

# ============================================================================
# TEXTURE AND MUSIC DATA
# ============================================================================
# Keep the RGBA image and TED register dump as raw bytes. Neither asset
# contains PPC words, so the assembler must not change their byte order.
        .balign 4
texture_data:
        .incbin "../assets/rotozoomtexture_ppc.raw"
        .balign 4
ted_data:
        .incbin "../assets/music/chromatic_admiration.ted"
ted_data_end:
